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都会检查:
- 请求的地址落在哪个区域?
- 这个区域的安全属性是什么?
- 请求的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内部还有更多花样。