3.4 MPU与SMU——物理隔离与告警中枢
你的同事正在调试一个“幽灵bug“,电动助力转向EPS在连续运行23小时后,扭矩传感器读数偶发性地跳变到0,持续一个通信周期然后恢复正常。这个bug无法稳定复现,有时48小时跑不出来一次。他用Lauterbach Trace32抓了整整一周的逻辑分析仪数据,最终锁定了一个令人毛骨悚然的根因:
诊断日志记录任务(QM级,非安全相关)中的memset()调用写穿了栈。日志任务的栈底地址是0x7000FFF0,而紧挨着它的是EPS扭矩传感器滤波缓冲区的首地址,恰好是0x70010000。memset(buffer, 0, size)中的size被一个野指针变成了0x00010014,也就是65556字节。这65556字节直接淹没了扭矩缓冲区、CAN发送邮箱、以及半个DMA描述符表。
为什么MPU没有挡住?因为MPU的地址区域配置是按“任务角色“划分的——日志任务和EPS扭矩任务被分配在同一个MPU数据区域中(都标记为“应用数据“),MPU只能阻止日志任务访问OS内核区或外设寄存器区,但挡不住它跨过自己栈边界写到相邻的应用数据。区域划分的粒度不够细:在一个MPU region里,所有地址对拥有该region权限的任务来说是平权的。
更可怕的是:覆盖扭矩缓冲区的值全是0x00。日志任务的memset本意就是用零填充。扭矩信号的正常范围恰好包含0(零转矩)。所以整个EPS算法在错误的输入上运行了整整一个周期,它认为驾驶员没有施加任何转向力:然后数据又被正常的传感器采样覆盖。整个过程静默无声,除了驾驶员可能感觉到方向盘突然变轻了一下。
你的同事在调试笔记里写了一行:“MPU区域划分的颗粒度,决定了安全边界的有效性。”
MPU的原理:用硬件定义领土边界
MPU(Memory Protection Unit)是ARM Cortex-R系列处理器中的一个硬件模块,它允许软件定义最多12或16个(取决于具体实现)内存区域(Memory Region),并对每个区域设置独立的访问权限。
与MMU(Memory Management Unit)不同,MPU不做地址翻译,它不提供虚拟内存。物理地址就是物理地址。MPU只做一件事:当CPU发起一次内存访问时,检查访问的目标地址是否在某个已定义的区域内,以及当前的访问模式(读/写/执行)和特权级别(特权模式/用户模式)是否被该区域允许。
MPU 访问控制判定流程:
CPU 发起内存访问
(地址 + 读/写 + 特权/用户)
│
▼
┌─────────────────────────────────┐
│ MPU 逐个检查已配置区域 │
│ │
│ for each region (0..11): │
│ if (addr >= base && │
│ addr < base + size) │
│ { │
│ 检查该区域的权限属性: │
│ - AP[2:0] (访问权限) │
│ - XN (执行禁止) │
│ │
│ if (access 被允许) │
│ 继续正常访问 │
│ else │
│ 触发 MemManage Fault │
│ } │
│ │
│ 如果没有匹配任何区域: │
│ 后台区域(如果使能)规则适用 │
│ 否则 → 触发 MemManage Fault │
└─────────────────────────────────┘
Cortex-R5的MPU使用CP15协处理器寄存器来配置,不像Cortex-M那样走内存映射地址,而是通过MCR/MRC协处理器指令操作c6寄存器组:
/* Cortex-R5 MPU 区域配置 — 通过 CP15 c6 寄存器 */
static inline void Mpu_WriteRegion(uint8 region, uint32_t base, uint32_t size_and_attr) {
asm volatile("MCR p15, 0, %0, c6, c2, 0" :: "r"(region)); /* 选择区域号 */
asm volatile("MCR p15, 0, %0, c6, c1, 0" :: "r"(base)); /* 写基址 */
asm volatile("MCR p15, 0, %0, c6, c1, 2" :: "r"(size_and_attr)); /* 写大小+属性 */
}
void Mpu_ConfigureRegion(uint8 region,
uint32 base_addr,
uint32 size,
uint8 access_permissions,
uint8 attributes)
{
/* RBAR: 位[31:5] = 基址(必须按区域大小对齐)
* 位[3:0] = 区域号(用于硬件验证)
*/
MPU_RBAR(region) = (base_addr & 0xFFFFFFE0U) | (region & 0xFU);
/* RASR: 位[31:29] = 保留
* 位[28] = XN(执行禁止)
* 位[26:24] = AP[2:0](访问权限)
* 位[21:19,18,17,16] = TEX, S, C, B(缓存属性)
* 位[15:8] = 子区域禁用位
* 位[5:1] = SIZE(区域大小编码)
* 位[0] = ENABLE
*/
uint32 rasr_val = (uint32)access_permissions; /* AP[2:0] 移到 bits[26:24] */
rasr_val |= (uint32)attributes; /* TEX, C, B 等 */
rasr_val |= (size << 1U); /* SIZE 编码 */
rasr_val |= 1U; /* ENABLE */
MPU_RASR(region) = rasr_val;
}
MPU区域大小和基址对齐之间的约束经常被忽略:区域基地址必须按区域大小自然对齐。一个64KB的区域,其基址的低16位必须是0。一个4KB的区域,基址的低12位必须是0。如果你试图配一个基址0x70010010、大小4KB的区域,硬件的行为是未定义的:有些实现会忽略低位的地址位,有些会触发故障。
MPU在AUTOSAR OS中的集成:让保护成为运行时语境
裸机代码中的MPU配置是静态的:系统启动时配置一次,之后不变。但在AUTOSAR OS中,不同的任务和ISR可能属于不同的安全完整性等级(ASIL D、ASIL B、QM),它们对内存的访问需求截然不同。如果所有任务共享同一套MPU配置,那么要么配置过于宽松(QM任务也能访问ASIL D数据),要么过于严格(ASIL D任务被阻止访问必需的共享内存)。
AUTOSAR OS的解决方案是:将MPU配置与任务的上下文切换绑定。当调度器从一个任务切换到另一个任务时,同步切换MPU的活跃区域配置。
任务切换时的MPU上下文切换:
Task_A (ASIL D, 转向) Task_B (QM, 诊断日志)
┌───────────────────┐ ┌───────────────────┐
│ MPU 区域配置: │ │ MPU 区域配置: │
│ Region 0: Flash │ │ Region 0: Flash │
│ Region 1: RAM_D0 │ ◄─切换──► │ Region 1: RAM_Q0 │
│ Region 2: RAM_D1 │ │ Region 2: RAM_Q1 │
│ Region 3: Stack_D │ │ Region 3: Stack_Q │
│ Region 4: CAN_Buf │ │ Region 4: 无 │
│ Region 5: PWM_Reg │ │ Region 5: 无 │
└───────────────────┘ └───────────────────┘
Task_A 访问 RAM_Q0 → MPU Fault Task_B 访问 PWM_Reg → MPU Fault
AUTOSAR OS的Os_Hal_Arch_ContextSave和Os_Hal_Arch_ContextRestore钩子函数中包含了MPU上下文的保存和恢复逻辑。这个过程的延迟直接影响上下文切换时间,在Cortex-R5 @ 400MHz上,写入12个MPU区域寄存器对(24个32位寄存器)大约需要300ns。虽然看起来可以忽略,但在一个10μs周期的超高速控制环中,每次MPU切换占3%的CPU时间是一个需要认真权衡的设计选择。
AUTOSAR 4.4引入了MPU支持的一个实用特性:堆栈溢出检测。这不是通过MPU本身实现的,而是利用MPU的区域机制,在任务堆栈的底部放置一个不可访问的“保护页“(Guard Page)。
任务堆栈布局(含MPU保护页):
高地址 ┌──────────────┐
│ 堆栈正常空间 │ ← SP 初始值
│ │
│ (可读写) │
│ │
低地址 ├──────────────┤
│ 保护页 │ ← MPU 配置为 不可访问
│ (4KB) │
更低地址 └──────────────┘
│ 其他任务的RAM │
当堆栈向下增长时,SP会逐渐减小。正常情况下,SP永远不会进入保护页。如果任务使用了过多栈空间(递归太深、局部变量太大),SP就会进入保护页的地址范围。下一次堆栈操作(push、函数调用等)将触发MPU Fault。这个异常被OS捕获,OS可以判断为栈溢出并采取相应的恢复措施。
但这个机制有一个“竞争条件“式的盲区:如果堆栈一次增长超过4KB(保护页大小),SP会直接跳过保护页进入相邻内存区域,而不会触发MPU Fault。这就是为什么正确的栈使用量分析(Stack Usage Analysis)在安全关键系统中不是可选的:MPU保护页是最后一道防线,但不一定够快。
SMU:安全管理单元的告警聚合逻辑
MPU触发的访问违例、锁步检测到的核间失配、ECC报告的不可纠正错误、看门狗的超时。这些来自不同来源的故障信号都指向同一个目的地:SMU(Safety Management Unit)。
SMU是整个芯片的“安全告警中枢“。它汇聚来自芯片各方各面的故障信号,根据预配置的故障响应协议(FSP, Fault Signaling Protocol)决定采取什么行动。以英飞凌AURIX TC3xx的SMU为例,其架构分为三个层次:
AURIX TC3xx SMU 架构:
┌─────────────────────────────────────────────────────────┐
│ 告警来源 │
│ │
│ 锁步失配 ECC错误 MPU违例 看门狗 电源监控 温度告警 │
│ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SMU 告警聚合层 │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Alarm 0 │ │ Alarm 1 │ │ Alarm N │ ... │ │
│ │ │ 组配置 │ │ 组配置 │ │ 组配置 │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────┼────────────┘ │ │
│ │ ▼ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ 内部状态机 (ISM) │ │ │
│ │ │ │ │ │
│ │ │ 当前状态: │ │ │
│ │ │ RUN → ALARM │ │ │
│ │ │ → FAULT │ │ │
│ │ └────────┬─────────┘ │ │
│ └───────────────────┼───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 故障响应协议 (FSP) │ │
│ │ │ │
│ │ Alarm Level → IRQ │ │
│ │ Fault Level → NMI │ │
│ │ Fatal Level → Reset │ │
│ └────────────┬───────────┘ │
└──────────────────────┼──────────────────────────────────┘
│
▼
┌────────────────────────┐
│ SCU 复位控制单元 │
│ → 触发系统复位 │
│ → 触发PORST引脚 │
└────────────────────────┘
SMU的告警输入端可以配置为“直通“或“过滤“模式。在过滤模式下,你可以指定“只有告警信号持续N个采样周期后才视为有效“,这对抗瞬态干扰(如EMC测试中的单次脉冲干扰导致的虚假告警)非常有效。采样周期的典型值在微秒到毫秒之间,取决于告警源的物理特性。锁步失配告警通常在纳秒级(无需过滤,锁步失配不可能是噪声),而外部电压监控告警可能需要毫秒级的过滤(因为电源线上的噪声很常见)。
核心的SMU内部状态机(ISM)是一个硬件实现的多状态有限状态机:
SMU 内部状态机简化:
┌──────────┐
上电/复位 │ INIT │
└────┬─────┘
│ 初始化完成
▼
┌──────────┐ 任何告警到来
│ RUN │ ──────────────────┐
└──────────┘ │
▲ ▼
│ ┌──────────────┐
│ 告警清除 │ ALARM │
│ │ (可恢复告警) │
│ └──────┬───────┘
│ │
│ │ 告警升级为故障
│ ▼
│ ┌──────────────┐
│ │ FAULT │
│ │ (严重故障) │
│ └──────┬───────┘
│ │
└─────────────────────────┘
通过复位或故障恢复机制返回
FSP根据ISM的当前状态触发不同级别的CPU响应:
- Alarm → 可屏蔽中断(IRQ)→ 软件可以记录日志、尝试恢复
- Fault → 不可屏蔽中断(NMI)→ 软件必须立即处理,无法屏蔽
- Fatal → 系统复位 → 硬件直接复位MCU
这个分级响应机制的精妙之处在于:不是所有错误都需要立即复位系统。有些告警(如单bit ECC错误)可以被记录并在维护周期处理,系统可以继续运行。有些故障(如锁步失配)证明核心计算已经不可信赖,必须立即进入安全状态。有些灾难性事件(如核心电压跌落至阈值以下)使得任何软件处理都不可靠,只能硬件触发复位。
FSP:从告警到动作
FSP(Fault Signaling Protocol)是SMU中定义告警到响应映射的配置层。每个告警源可以映射到一个或多个“动作“。在AURIX TC3xx中,一个FSP动作可以是:
- 触发CPU中断(IRQ或NMI)
- 设置故障状态标志位
- 拉低SCU的Fault-to-Error引脚(PORST或ESR信号)
- 触发系统复位
- 复位特定的外设模块
在AUTOSAR环境中,SMU告警的处理路径通常涉及MCAL(Microcontroller Abstraction Layer)中的Mcu、Gpt、Port驱动,以及EcuM的故障处理回调:
告警 → 响应的完整路径:
硬件告警源(如 锁步比较器)
│
▼
SMU 告警输入 0x0B
│
├──► SMU ISM: RUN → ALARM → FAULT
│ │
│ ▼
├──► FSP 动作: 触发 NMI
│ │
│ ▼
├──► CPU NMI 异常向量
│ │
│ ▼
├──► Mcu 驱动: Mcu_PerformReset()
│ │
│ ▼
└──► EcuM: EcuM_AL_DriverInitZero()
EcuM_AL_DriverInitOne()
...
系统复位并重启
在设计FSP配置时,有一个容易被忽视的考虑:故障响应的时序预算。ISO 26262-4:2018要求安全机制必须满足分配给它的故障处理时间间隔(FHTI, Fault Handling Time Interval)。从故障发生到系统进入安全状态的总延迟必须不超过这个值。
对于一个ASIL D的转向系统,典型的FHTI是10ms到50ms。你需要对这个时间预算进行精确分配:
FHTI 时序预算分配示例 (ASIL D 转向, 50ms FHTI):
故障发生(α粒子击中锁步核心)
↓ ≤ 5ns 锁步比较器检测到失配
↓ ≤ 50ns SMU告警输入锁存
↓ ≤ 1μs SMU ISM 状态转换 (RUN→ALARM→FAULT)
↓ ≤ 2μs NMI向量跳转
↓ ≤ 100μs NMI处理程序:记录故障上下文
↓ ≤ 5ms 关断电机驱动桥(硬件PWM紧急停止)
↓ ≤ 10ms 通知外部看门狗停止喂狗
↓ ≤ 20ms 整车降级模式激活(CAN报告)
↓ ≤ 50ms 驾驶员感知到警告灯/转向变重
这只是一个理想化的分析。实际系统中,软件NMI处理程序的WCET可能远超100μs,尤其是如果NMI处理程序尝试记录完整的故障上下文(调用栈、任务状态、寄存器快照)到NVM。在安全概念评审中,你经常需要在“记录足够信息用于事后分析“和“尽快进入安全状态“之间做权衡。
Fault-to-Error引脚:最后一根筋
SMU还有一个物理层面的机制值得特别关注:Fault-to-Error引脚(通常标注为/PORST或/ESR)。
这是一个开漏输出的硬件引脚。当SMU的内部状态机进入FAULT状态时,这个引脚会被拉低。在典型的系统设计中,这个引脚直接连接到外部电源管理芯片(PMIC/SBC)的使能输入。当它被拉低时,PMIC切断MCU的VDD电源,强制物理断电。经过一个配置的延迟后(让所有电容完全放电),PMIC重新上电,MCU执行完整的POR序列。
外部安全监视器连接:
MCU PMIC/SBC
┌──────────────────────┐ ┌──────────────────┐
│ │ │ │
│ SMU ──► /FaultOut ──┼──────────┼──► /ENABLE_MCU │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ VDD_CORE = 0V │
│ │ │ │
│ │ │ 延迟 10ms 后 │
│ │ │ VDD_CORE 恢复 │
│ │ │ │
└──────────────────────┘ └──────────────────┘
这个设计解决了一个致命的“鸡生蛋“问题:如果SMU检测到核心电压已经跌落到无法保证CPU可靠运行的水平,那么你如何让CPU执行故障响应代码?答案是你不能。Fault-to-Error引脚是纯硬件通路,不经过任何CPU代码、不依赖任何时钟、不需要任何正在运行的软件。它只是硅片上的一个NMOS下拉管被SMU状态机的输出驱动。即使CPU已经完全死锁,/FaultOut引脚仍然能被拉低。
这是功能安全硬件设计中最底层的保障机制。如果这个引脚也失效了,就像一个人连下丘脑的散热中枢都瘫痪了:只能等待外部观察者发现并干预。在汽车系统中,这个“外部观察者“通常是由另外一颗独立MCU驱动的组合仪表警告灯,或者第二颗域控制器。
MPU和SMU代表功能安全架构中两种不同的哲学。MPU是预防性的,通过空间隔离防止故障跨安全完整性等级传播。SMU是反应性的:汇聚各方的告警信号,统一决策和分级响应。
一个耐人寻味的设计事实是:MPU配置越严格,SMU被触发的频率越高。反过来,如果SMU报警很少,可能说明MPU配置太宽松了:故障在静默地跨域传播而没有触发访问违例。好的安全架构师会利用SMU的告警统计数据来持续优化MPU的区域划分策略。
MPU和SMU的配置不是一次性活动。随着域融合和中央计算平台的发展,ASIL D、ASIL B和QM软件共享同一颗SoC的场景越来越普遍。MPU配置的完备性审查和SMU告警路径的故障注入测试必须成为每次软件发布的强制性检查项。
本篇小结
- MPU通过硬件访问控制区域实现空间隔离,不依赖软件配合;AUTOSAR OS将MPU配置与任务上下文切换绑定,不同ASIL等级的任务拥有独立的区域权限。
- MPU区域基地址必须按大小自然对齐(64KB区域低16位必须为0),配置错误会导致行为未定义。
- SMU汇聚锁步失配、ECC错误、MPU违例、看门狗超时等所有硬件告警,通过FSP(Fault Signaling Protocol)分级响应为IRQ/NMI/硬件复位。
- FHTI时序预算必须精确分配(从故障发生到安全状态的每一纳秒),典型ASIL D转向系统FHTI为10-50ms,超阈值则危害事件必然发生。
- Fault-to-Error引脚是纯硬件安全路径,不依赖任何软件或时钟,可在CPU完全死锁时直接拉低PMIC使能引脚强制断电。
【下集预告】: MPU在SoC内部划清了安全边界,但数据一旦离开芯片走上CAN总线,边界就消失了。一条12厘米长的PCB走线从DC-DC电源模块上方经过,EMI脉冲翻转了一个比特,3.2°的转向角变成327.6°——接收方的MPU完全看不见,因为它只检查地址不检查内容。E2E保护为每一条消息打上三重抗原标记:CRC验证内容完整性、Counter防止重放攻击、DataID确认消息身份。下一节看接收方如何用这三枚“分子标签“在信号到达应用层之前验明正身。