Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_ContextSaveOs_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)中的McuGptPort驱动,以及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告警路径的故障注入测试必须成为每次软件发布的强制性检查项。

本篇小结

  1. MPU通过硬件访问控制区域实现空间隔离,不依赖软件配合;AUTOSAR OS将MPU配置与任务上下文切换绑定,不同ASIL等级的任务拥有独立的区域权限。
  2. MPU区域基地址必须按大小自然对齐(64KB区域低16位必须为0),配置错误会导致行为未定义。
  3. SMU汇聚锁步失配、ECC错误、MPU违例、看门狗超时等所有硬件告警,通过FSP(Fault Signaling Protocol)分级响应为IRQ/NMI/硬件复位。
  4. FHTI时序预算必须精确分配(从故障发生到安全状态的每一纳秒),典型ASIL D转向系统FHTI为10-50ms,超阈值则危害事件必然发生。
  5. Fault-to-Error引脚是纯硬件安全路径,不依赖任何软件或时钟,可在CPU完全死锁时直接拉低PMIC使能引脚强制断电。

【下集预告】: MPU在SoC内部划清了安全边界,但数据一旦离开芯片走上CAN总线,边界就消失了。一条12厘米长的PCB走线从DC-DC电源模块上方经过,EMI脉冲翻转了一个比特,3.2°的转向角变成327.6°——接收方的MPU完全看不见,因为它只检查地址不检查内容。E2E保护为每一条消息打上三重抗原标记:CRC验证内容完整性、Counter防止重放攻击、DataID确认消息身份。下一节看接收方如何用这三枚“分子标签“在信号到达应用层之前验明正身。