2、硬件架构基础:系统总线(AXI/ACE)安全信号、TZASC原理

好,咱们进入第二章。这一章讲的是TrustZone的“血管”和“关卡”——系统总线和地址空间控制器。说白了,安全世界和普通世界怎么通信?数据怎么隔离?全靠这套硬件机制。

我个人习惯把TrustZone比作一栋大楼。CPU是核心办公区,内存是仓库,外设是各个部门。那总线就是走廊和电梯,TZASC就是大楼的安保闸机。没有它们,安全就是个空话。

2.1 AXI总线上的安全信号:NS位

ARM的AXI总线,是高性能片上通信的骨干。TrustZone在AXI协议里加了一个关键信号——AxPROT[1],也就是我们常说的NS位(Non-Secure bit)。

这个信号只有1比特。0表示安全世界访问,1表示普通世界访问。就这么简单。但它的传播路径,决定了整个系统的安全边界。

我举个例子。CPU执行一条加载指令,读内存地址0x8000_0000。这条指令如果是安全世界发的,总线上的AxPROT[1]就是0。如果是普通世界发的,就是1。总线上的所有设备——内存控制器、外设、DMA——都能看到这个信号。

核心原则:安全世界可以访问一切。普通世界只能访问标记为“非安全”的资源。这个决策点,就在总线上。

嗯,这里要注意。NS位不仅仅在CPU发起的传输中有。DMA、GPU这些总线主设备,也得支持这个信号。我在项目中遇到过一个问题:一个老旧的DMA控制器,根本不认识AxPROT信号。结果普通世界的恶意程序通过DMA直接读走了安全内存的数据。那叫一个惨。

避坑指南:我曾经在一个IoT芯片项目里,因为DMA控制器不支持TrustZone,不得不额外加一个硬件防火墙来过滤DMA的访问请求。所以选IP的时候,一定要确认所有总线主设备都支持AXI安全扩展。

2.2 ACE与一致性:多核场景下的安全挑战

多核系统里,事情就复杂了。ACE(AXI Coherency Extensions)在AXI基础上加了缓存一致性协议。你想想看,两个CPU核,一个在安全世界,一个在普通世界,它们缓存了同一块内存的数据。如果安全核改了数据,普通核读到的还是旧值,那不乱套了?

ACE协议通过ACPROT[1]信号来传递安全属性。这个信号和AXI的AxPROT[1]功能一样,但用在一致性事务里。比如监听请求(Snoop request)也会带上安全位。

我个人经验是,多核TrustZone系统里,最容易出问题的就是缓存一致性和安全属性的组合。举个例子:

  • 安全核写了一个安全内存地址,数据留在L1缓存里
  • 普通核通过DMA直接读物理内存(绕过缓存)
  • 普通核读到的是旧数据,但更危险的是——如果DMA把数据写回内存,安全核的缓存可能还是旧值

ACE协议通过屏障指令一致性域来解决这个问题。但说实话,软件工程师很容易忽略这些细节。我建议你在设计多核系统时,对共享内存区域做严格的安全分区,别让安全和非安全数据混在一起。

信号 协议 作用
AxPROT[1] AXI 标记单次传输的安全/非安全属性
ACPROT[1] ACE 标记一致性事务的安全属性
AxPROT[0] AXI/ACE 标记数据/指令访问(0=数据,1=指令)

2.3 TZASC:地址空间的“守门员”

好了,总线上的NS位传过来了。但谁来决定“这个地址能不能被普通世界访问”?答案是TZASC——TrustZone Address Space Controller。

TZASC本质上是一个可编程的地址过滤器。它挂在系统总线上,监控所有对内存和外设的访问。每个访问请求,TZASC都会检查:

  1. 请求的地址落在哪个区域?
  2. 这个区域的安全属性是什么?
  3. 请求的NS位和区域安全属性匹配吗?

如果不匹配,TZASC直接返回错误响应,或者触发一个安全异常。硬件级别的拦截,软件根本绕不过去。

小技巧:我习惯把TZASC配置成8个或16个区域。每个区域可以独立设置基地址、大小、安全属性。比如区域0放安全OS,区域1放普通OS,区域2放安全外设。这样分区清晰,调试也方便。

2.4 TZASC的配置与编程

TZASC的寄存器,通常只能由安全世界访问。普通世界想改?门都没有。配置流程大概是这样的:

// 伪代码:配置TZASC区域
// 假设TZASC基地址为0xE000_0000

// 1. 禁用区域(先关掉再改,防止中间状态)
write_reg(0xE000_0000 + REGION_CTRL(3), 0);

// 2. 设置基地址(必须对齐到区域大小)
write_reg(0xE000_0000 + REGION_BASE(3), 0x8000_0000);

// 3. 设置区域大小(比如256MB)
write_reg(0xE000_0000 + REGION_SIZE(3), 0x1000_0000);

// 4. 设置安全属性:0=安全,1=非安全
write_reg(0xE000_0000 + REGION_ATTR(3), SECURE);

// 5. 启用区域
write_reg(0xE000_0000 + REGION_CTRL(3), 1);

嗯,这里有个坑。区域大小必须和基地址对齐。比如你设了256MB的区域,基地址必须是256MB的整数倍。否则TZASC会直接报错,或者更糟——静默地忽略你的配置。我曾经因为基地址没对齐,排查了整整两天。

重要提醒:TZASC的配置,必须在系统启动早期完成。一旦普通世界开始运行,再改配置就来不及了。我建议你在安全启动流程里,把TZASC初始化放在前三步。

2.5 多级TZASC与系统拓扑

复杂系统里,可能不止一个TZASC。比如:

  • 一个TZASC管DDR内存
  • 另一个TZASC管片上SRAM
  • 第三个TZASC管外设总线

每个TZASC独立工作,但安全策略必须一致。否则就会出现“内存是安全的,但外设把数据泄露了”这种尴尬情况。

我记得有个项目,客户把TZASC配置得滴水不漏,但忘了给加密引擎加保护。结果攻击者通过普通世界的DMA,直接读走了加密引擎的密钥寄存器。嗯,硬件设计就是这样——木桶效应,最短的那块板决定安全水位。

总结一下这一章的核心:

  • AXI/ACE总线通过NS位传递安全属性
  • TZASC在地址层面强制执行安全策略
  • 两者配合,才能构建完整的硬件隔离

下一章,我们会深入Cache和TLB的安全设计。到时候你会看到,TrustZone在CPU内部还有更多花样。