2. ARMv8-A 启动流程与异常等级切换分析

任务说明

本案例目标是在ARM TrustZone环境中配置TZASC(TrustZone Address Space Controller),实现将外部DDR内存划分为安全区域与非安全区域。完成配置后,安全世界处理器可访问全部内存,非安全世界处理器仅可访问被标记为非安全的区域,访问安全区域时将触发异常。

环境依赖

本案例基于ARMv8-A架构,使用QEMU的virt平台进行验证(已内置TZASC控制器模型)。需要以下工具:

  • QEMU 7.0+(支持ARMv8-A virt平台)
  • aarch64-linux-gnu-gcc交叉编译器
  • GNU Make

核心实现

以下代码实现TZASC初始化与区域配置。系统共1GB DDR内存(地址0x40000000~0x7FFFFFFF),将其划分为:安全区域(低512MB)与非安全区域(高512MB)。

// tzasc_config.c
#include <stdint.h>

// TZASC寄存器基地址(QEMU virt平台)
#define TZASC_BASE      0xFFFF000000000000ULL  // 实际映射由平台决定,此处为逻辑地址
// 实际QEMU中TZASC位于内存映射控制器的GIC之后,此处使用物理地址
#define TZASC_PHYS_BASE 0x0C000000ULL

// TZASC寄存器偏移
#define TZASC_REG_CONFIG      0x000
#define TZASC_REG_ACTION      0x004
#define TZASC_REG_GATE_KEEPER 0x008
#define TZASC_REG_REGION_BASE 0x100
#define TZASC_REG_REGION_TOP  0x104
#define TZASC_REG_REGION_ATTR 0x108

// 区域属性位定义
#define REGION_ATTR_ENABLE    (1ULL << 0)
#define REGION_ATTR_SECURE    (1ULL << 1)   // 1=安全, 0=非安全
#define REGION_ATTR_SIZE_SHIFT 32
#define REGION_ATTR_SIZE_MASK  (0xFULL << REGION_ATTR_SIZE_SHIFT)

// 内存布局
#define DDR_BASE      0x40000000ULL
#define DDR_SIZE      0x40000000ULL  // 1GB
#define SECURE_SIZE   0x20000000ULL  // 512MB安全区域

static volatile uint32_t* tzasc_reg(uint32_t offset) {
    return (volatile uint32_t*)(TZASC_PHYS_BASE + offset);
}

static void tzasc_write(uint32_t offset, uint32_t value) {
    *tzasc_reg(offset) = value;
    __asm volatile("dsb sy");
}

static uint32_t tzasc_read(uint32_t offset) {
    return *tzasc_reg(offset);
}

// 配置TZASC:将DDR分为安全/非安全两个区域
void tzasc_init(void) {
    // 1. 关闭TZASC(通过GATE_KEEPER)
    tzasc_write(TZASC_REG_GATE_KEEPER, 0x0);
    
    // 2. 设置全局配置:启用TZASC,非安全访问安全区域时触发异常
    tzasc_write(TZASC_REG_CONFIG, 0x1);
    tzasc_write(TZASC_REG_ACTION, 0x1);  // 错误响应类型
    
    // 3. 配置区域0:整个DDR默认为安全(由硬件复位决定)
    //    此处显式设置以确保一致性
    tzasc_write(TZASC_REG_REGION_BASE + 0x00, (uint32_t)(DDR_BASE >> 32));
    tzasc_write(TZASC_REG_REGION_BASE + 0x04, (uint32_t)(DDR_BASE & 0xFFFFFFFF));
    tzasc_write(TZASC_REG_REGION_TOP + 0x00, (uint32_t)((DDR_BASE + DDR_SIZE - 1) >> 32));
    tzasc_write(TZASC_REG_REGION_TOP + 0x04, (uint32_t)((DDR_BASE + DDR_SIZE - 1) & 0xFFFFFFFF));
    
    // 区域0属性:启用,安全,粒度1KB(size字段=0表示1KB粒度)
    uint64_t attr0 = REGION_ATTR_ENABLE | REGION_ATTR_SECURE;
    tzasc_write(TZASC_REG_REGION_ATTR + 0x00, (uint32_t)(attr0 >> 32));
    tzasc_write(TZASC_REG_REGION_ATTR + 0x04, (uint32_t)(attr0 & 0xFFFFFFFF));
    
    // 4. 配置区域1:非安全区域(DDR高512MB)
    uint64_t ns_base = DDR_BASE + SECURE_SIZE;
    uint64_t ns_top  = DDR_BASE + DDR_SIZE - 1;
    
    tzasc_write(TZASC_REG_REGION_BASE + 0x10, (uint32_t)(ns_base >> 32));
    tzasc_write(TZASC_REG_REGION_BASE + 0x14, (uint32_t)(ns_base & 0xFFFFFFFF));
    tzasc_write(TZASC_REG_REGION_TOP + 0x10, (uint32_t)(ns_top >> 32));
    tzasc_write(TZASC_REG_REGION_TOP + 0x14, (uint32_t)(ns_top & 0xFFFFFFFF));
    
    // 区域1属性:启用,非安全(清除SECURE位)
    uint64_t attr1 = REGION_ATTR_ENABLE;  // 不设置SECURE位
    tzasc_write(TZASC_REG_REGION_ATTR + 0x10, (uint32_t)(attr1 >> 32));
    tzasc_write(TZASC_REG_REGION_ATTR + 0x14, (uint32_t)(attr1 & 0xFFFFFFFF));
    
    // 5. 使能TZASC
    tzasc_write(TZASC_REG_GATE_KEEPER, 0x1);
}

// 验证函数:读取当前配置
void tzasc_dump_config(void) {
    uint32_t gate = tzasc_read(TZASC_REG_GATE_KEEPER);
    uint32_t cfg  = tzasc_read(TZASC_REG_CONFIG);
    
    // 读取区域0属性
    uint32_t attr0_hi = tzasc_read(TZASC_REG_REGION_ATTR + 0x00);
    uint32_t attr0_lo = tzasc_read(TZASC_REG_REGION_ATTR + 0x04);
    uint64_t attr0 = ((uint64_t)attr0_hi << 32) | attr0_lo;
    
    // 读取区域1属性
    uint32_t attr1_hi = tzasc_read(TZASC_REG_REGION_ATTR + 0x10);
    uint32_t attr1_lo = tzasc_read(TZASC_REG_REGION_ATTR + 0x14);
    uint64_t attr1 = ((uint64_t)attr1_hi << 32) | attr1_lo;
    
    // 通过串口输出(实际环境中使用UART驱动)
    // 此处仅作示意,验证时通过调试器读取寄存器
    (void)gate; (void)cfg; (void)attr0; (void)attr1;
}

// 简单测试入口
void tzasc_test(void) {
    tzasc_init();
    tzasc_dump_config();
    
    // 预期结果:
    // - GATE_KEEPER = 0x1
    // - 区域0属性 = 0x3 (ENABLE | SECURE)
    // - 区域1属性 = 0x1 (仅ENABLE,非安全)
}

编译与运行

创建最小启动文件并编译:

# start.S - 最小启动代码
.section .text
.global _start
_start:
    ldr x0, =0x0C000000
    // 调用tzasc_test
    bl tzasc_test
1:  b 1b  // 死循环

# Makefile
CROSS = aarch64-linux-gnu-
CFLAGS = -nostdlib -ffreestanding -Wall

all: tzasc.bin

tzasc.bin: start.o tzasc_config.o
    $(CROSS)ld -Ttext=0x40000000 -o tzasc.elf $^
    $(CROSS)objcopy -O binary tzasc.elf tzasc.bin

%.o: %.c
    $(CROSS)gcc $(CFLAGS) -c $< -o $@

%.o: %.S
    $(CROSS)gcc $(CFLAGS) -c $< -o $@

clean:
    rm -f *.o *.elf *.bin

# 运行(QEMU)
# qemu-system-aarch64 -machine virt -cpu cortex-a57 -m 1G -nographic -kernel tzasc.bin

验证方法

使用QEMU的调试接口验证配置结果:

# 启动QEMU并暂停执行
qemu-system-aarch64 -machine virt -cpu cortex-a57 -m 1G -nographic -kernel tzasc.bin -S -s

# 在另一个终端连接GDB
aarch64-linux-gnu-gdb tzasc.elf
(gdb) target remote :1234
(gdb) continue
(gdb) Ctrl+C  # 暂停执行

# 检查TZASC寄存器
(gdb) x/4wx 0x0C000000   # GATE_KEEPER应为0x1
(gdb) x/4wx 0x0C000100   # 区域0属性应为0x3
(gdb) x/4wx 0x0C000110   # 区域1属性应为0x1

通过条件:

  • GATE_KEEPER寄存器值为0x1,表示TZASC已使能
  • 区域0属性寄存器值为0x3(启用且安全)
  • 区域1属性寄存器值为0x1(启用且非安全)
  • 区域1的基地址寄存器指向0x60000000,顶地址指向0x7FFFFFFF

若以上条件全部满足,则TZASC地址空间隔离配置成功。非安全世界访问0x60000000~0x7FFFFFFF地址段将正常进行,而访问0x40000000~0x5FFFFFFF地址段将触发异常。

TZASC内存区域划分示意 DDR 内存 (1GB) 安全区域 (Region 0) 0x40000000 - 0x5FFFFFFF 仅安全世界可访问 非安全区域 (Region 1) 0x60000000 - 0x7FFFFFFF 安全/非安全世界均可访问 TZASC通过区域属性控制总线访问权限,非安全访问安全区域时触发异常