2. nRF54硬件安全基础

在 nRF54 系列(如 nRF54L15、nRF54H20)上,安全启动并不是从软件里“加一段校验代码”开始的,而是从芯片复位那一刻由硬件安全属性单元决定的。Cortex-M33 内核集成的 Arm TrustZone-M 把整个地址空间和中断资源在硬件层面切成“安全(Secure)”与“非安全(Non-Secure)”两个世界,nRF54 又在此基础上叠加了 Nordic 自己的 SPU(Security Peripheral Unit)等外设安全控制单元。理解这一层硬件基础,是后续做密钥存储、安全启动链和固件升级的前提。

TrustZone-M 的核心:两个世界与一个边界

TrustZone-M 不是把 CPU 分成两个核,而是让同一个 Cortex-M33 内核在两种状态下运行:安全状态和非安全状态。每个物理地址在硬件上带有一个安全属性位,访问是否被允许由当前状态与目标地址属性共同决定。安全状态可以访问安全和非安全资源,非安全状态访问安全资源会触发 SecureFault(安全违规异常)。

两个世界之间的切换不是普通函数调用,而是通过专门的入口:非安全代码通过 SG(Secure Gateway)指令跳转到安全侧预定义的入口,安全侧通过 BXNS/BLXNS 返回非安全侧。切换时硬件会做寄存器清理(如把非安全可见的寄存器清零),防止安全数据泄漏到非安全世界。中断也分安全和非安全,非安全中断不能抢占安全中断处理,TargetNS 位决定某个异常由哪个世界处理。

对 nRF54 而言,这意味着启动代码、密钥操作、签名验证应放在安全世界;应用逻辑、协议栈、蓝牙/Thread 业务放在非安全世界。两者共享同一颗 CPU,但隔离由硬件保证。

安全与非安全分区:SAU 与 MPU 的分工

地址空间的安全属性由 SAU(Security Attribution Unit)划分。SAU 是内核内部的单元,把地址区间标记为安全、非安全或“由 IDAU 决定”。nRF54 同时有 IDAU(Implementation Defined Attribution Unit),由 Nordic 在芯片层面定义哪些区域天然属于安全(如部分 Flash、安全外设、密钥存储区)。最终属性是 SAU 与 IDAU 的“更安全者优先”结果:只要有一方标为安全,该地址就是安全的。

SAU 只决定“这块内存属于哪个世界”,不决定“当前代码能不能读写”。后者由 MPU(Memory Protection Unit)负责。Cortex-M33 有独立的 Secure MPU 和 Non-Secure MPU:安全 MPU 管安全世界的访问权限,非安全 MPU 管非安全世界的访问权限。典型做法是:

  • SAU 把 Flash 的启动区、密钥区、安全外设寄存器区标为安全;把应用 Flash、RAM 的大部分标为非安全。
  • Secure MPU 保护安全区不被安全世界内部越权访问(例如安全应用误写密钥区)。
  • Non-Secure MPU 限制非安全应用只能访问自己的代码、数据和允许的外设,防止它读写安全 RAM 或篡改安全配置。

需要注意:SAU/MPU 的配置寄存器本身是安全的,非安全代码无法修改。一旦在启动早期锁定,后续任何非安全代码都无法放宽隔离边界。

安全属性单元在启动流程中的作用

nRF54 上电后,CPU 从安全向量表取复位向量,此时默认处于安全状态。启动流程中安全属性单元的介入顺序大致如下:

  1. 复位释放,内核进入安全状态,SAU 初始为“全部安全”或由芯片预设,IDAU 已生效。
  2. 安全启动代码(BL1/BL2 或 Secure Bootloader)先配置 SAU 区域,把非安全代码/数据区划出去,再配置 Secure MPU 保护安全区。
  3. 配置 SPU 等外设安全单元,把 GPIO、UART、RADIO 等外设按需分配给安全或非安全世界。
  4. 设置非安全向量表基址(VTOR_NS),准备非安全镜像。
  5. 通过 BXNS 跳转到非安全复位入口,CPU 进入非安全状态,应用开始运行。

这里的关键点是:SAU/MPU/SPU 的配置必须在跳转到非安全世界之前完成并锁定。如果顺序颠倒,非安全代码可能先运行并篡改后续配置,隔离就形同虚设。Nordic 的 Secure Bootloader 会在验证签名通过后才执行上述配置,确保只有可信镜像能进入非安全世界。

nRF54 启动流程中的安全属性单元作用 安全世界 (Secure State) 安全启动代码 BL1/BL2 复位入口 · 签名验证 · 密钥操作 SAU 配置 划分安全/非安全地址区间 Secure MPU + SPU 配置 保护安全区 · 分配外设归属 设置 VTOR_NS 指向非安全向量表 BXNS 跳转 切换到非安全状态 非安全世界 (Non-Secure State) 非安全应用固件 蓝牙/Thread 协议栈 · 业务逻辑 Non-Secure MPU 限制非安全访问范围 非安全外设 GPIO · UART · RADIO(按需分配) 非安全 RAM / Flash 应用数据 · 堆栈 · 代码 运行中 无法访问安全资源 TrustZone-M 硬件边界 BXNS SAU/MPU/SPU 必须在 BXNS 之前完成配置并锁定,否则隔离失效

SAU 与 MPU 配置的实际写法

在 nRF Connect SDK 中,安全分区通常通过 DTS(设备树)和分区管理器描述,但底层仍落到 SAU/MPU 寄存器。下面给出一段示意性的 SAU 配置代码,用于说明“先划分、后保护”的顺序(参数为教学示例,非实测值):

/* 教学示例:SAU 区域划分(非完整可编译代码) */
/* 区域0:安全 Flash 启动区 0x0000_0000 - 0x0001_FFFF */
SAU->RBAR = 0x00000000 | SAU_RBAR_ENABLE;
SAU->RLAR = 0x0001FFFF | SAU_RLAR_ENABLE | SAU_RLAR_SECURE;

/* 区域1:非安全应用 Flash 0x0002_0000 - 0x000F_FFFF */
SAU->RBAR = 0x00020000 | SAU_RBAR_ENABLE;
SAU->RLAR = 0x000FFFFF | SAU_RLAR_ENABLE | SAU_RLAR_NON_SECURE;

/* 区域2:安全 RAM 0x2000_0000 - 0x2000_7FFF */
SAU->RBAR = 0x20000000 | SAU_RBAR_ENABLE;
SAU->RLAR = 0x20007FFF | SAU_RLAR_ENABLE | SAU_RLAR_SECURE;

/* 区域3:非安全 RAM 0x2000_8000 - 0x2001_FFFF */
SAU->RBAR = 0x20008000 | SAU_RBAR_ENABLE;
SAU->RLAR = 0x2001FFFF | SAU_RLAR_ENABLE | SAU_RLAR_NON_SECURE;

/* 启用 SAU 并锁定 */
SAU->CTRL = SAU_CTRL_ENABLE | SAU_CTRL_ALLNS_MASK;

配置完成后,安全代码再设置 Secure MPU 的 region,例如把密钥区设为“仅安全特权可读写”,把安全代码区设为“安全只读+可执行”。Non-Secure MPU 则由非安全启动代码或安全代码代为设置,限制应用只能访问自己的区域。

SAU 区域数量有限(Cortex-M33 通常支持 8 个区域),nRF54 的 IDAU 已经预定义了大量安全区域,因此实际需要手动配置的 SAU 区域往往比想象中少。优先依赖 IDAU,再用 SAU 补充应用自定义分区,可以减少配置错误。

常见配置错误与判断依据

调试 TrustZone-M 隔离问题时,最直接的判断依据是 SecureFault 和总线错误。如果非安全代码访问了安全地址,会触发 SecureFault,可以通过 SCB->CFSR 和 SAU->SFSR 查看违规地址和原因。常见错误包括:

  • SAU 区域重叠或未对齐:SAU 要求区域按 32 字节对齐,重叠时行为未定义,可能导致部分地址属性错误。
  • 忘记设置 ALLNS 位:默认所有未覆盖地址可能仍为安全,导致非安全代码无法访问自己的 RAM。
  • VTOR_NS 未设置或指向安全向量表:非安全复位后会取到错误向量,表现为 HardFault 或直接跑飞。
  • SPU 外设分配与 SAU 不一致:例如 UART 被标为非安全,但引脚仍由安全 GPIO 控制,导致外设无法工作。

验证隔离是否生效,可以在非安全代码中故意读取一个安全地址,观察是否触发 SecureFault;也可以在安全代码中读取非安全地址,确认可以正常访问。这种“正反双向测试”比只看编译通过更能说明硬件隔离真正生效。

SAU/MPU 配置一旦锁定(例如设置 SAU_CTRL_ENABLE 后不再修改),在复位前无法更改。因此安全启动代码必须在锁定前完成所有分区决策,并确保非安全镜像已经通过验证。锁定后再想调整,只能复位重来。

小结:硬件安全基础决定了启动链的上限