[STM32U5] STM32U5使用TrustZone的技巧和注意事项

[复制链接]
1|0
renzheshengui 发表于 2026-8-8 14:52 | 显示全部楼层 |阅读模式
这里以stm32u575开发板为例子,其他芯片可能存在细小区别,大部分都是相同的,理解了就好。

1. SAU 与 IDAU
SAU(Security Attribution Unit)
ARM  核内置的安全属性单元,由软件配置,定义哪些地址范围是 Secure / Non-Secure / NSC。

最多 8 个 region,每个 region 可独立配置
最小粒度 32 字节
IDAU(Implementation Defined Attribution Unit)
芯片厂商(ST)在 SoC  层面实现的安全属性单元,硬件固化,软件无法修改。

STM32U5 的 IDAU 按地址高位划分:

372636a72a58847a77.png

SAU 与 IDAU 的关系
最终安全属性取两者中更严格的那个:

IDAU 说是 S,SAU 说是 NS → 结果是 S(IDAU 优先)
IDAU 说是 NS,SAU 说是 S → 结果是 S(SAU 优先)
两者都说 NS → 结果是 NS
2. GTZC 三个子模块详解
GTZC这个需要重点掌握,GTZC分为三个子模块,分别是TZSC,TZIC,MPCBB.

TZSC(TrustZone Security Controller)
负责配置外设的安全属性,决定每个外设归属安全区还是非安全区。

每个外设可独立配置为 Secure 或 Non-Secure
同时支持 Privileged / Non-Privileged 权限控制
非安全区访问被标记为 Secure 的外设,会触发 TZIC 中断并被阻断
本工程配置示例:

将uart1配置为安全外设,非安全区无法直接访问uart1.
配置TIM6为NS分区使用
配置PC7/PB7/PG2为NS分区使用.
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  __HAL_RCC_TIM6_CLK_ENABLE();
  if (HAL_GTZC_TZSC_ConfigPeriphAttributes(GTZC_PERIPH_TIM6, GTZC_TZSC_PERIPH_NSEC|GTZC_TZSC_PERIPH_NPRIV) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_GTZC_TZSC_ConfigPeriphAttributes(GTZC_PERIPH_USART1, GTZC_TZSC_PERIPH_SEC|GTZC_TZSC_PERIPH_NPRIV) != HAL_OK)
  {
    Error_Handler();
  }
  /* Configure PC7, PB7, PG2 as non-secure for NonSecure world usage */
  HAL_GPIO_ConfigPinAttributes(GPIOC, GPIO_PIN_7, GPIO_PIN_NSEC);
  HAL_GPIO_ConfigPinAttributes(GPIOB, GPIO_PIN_7, GPIO_PIN_NSEC);
  HAL_GPIO_ConfigPinAttributes(GPIOG, GPIO_PIN_2, GPIO_PIN_NSEC);


TZIC(TrustZone Illegal Access Controller)
负责监控和上报安全违规事件,当非安全区非法访问安全资源时产生中断通知安全区。

监控来自 TZSC 和 MPCBB 的所有违规访问
每个受保护资源对应一个违规标志位,可独立使能中断
安全区在中断处理函数中读取违规来源,决定如何响应(复位、记录、忽略等)
违规来源分两类:

808436a72a573ef1d8.png

参考用例,配置uart1和sram2的TZIC中断,监控NS非法访问S的外设和RAM

/* Enable TZIC interrupts for USART1 and SRAM2 */
HAL_GTZC_TZIC_EnableIT(GTZC_PERIPH_USART1);
HAL_GTZC_TZIC_EnableIT(GTZC_PERIPH_SRAM2);
HAL_NVIC_SetPriority(GTZC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(GTZC_IRQn);

void GTZC_IRQHandler(void)
{
  HAL_GTZC_IRQHandler();
}

void HAL_GTZC_TZIC_Callback(uint32_t PeriphId)
{
  if (PeriphId == GTZC_PERIPH_USART1)
    printf("[S] TZIC: illegal access to USART1\r\n");
  else if (PeriphId == GTZC_PERIPH_SRAM2)
    printf("[S] TZIC: illegal access to SRAM2\r\n");
}



MPCBB(Memory Protection Controller Block-Based)
负责对 SRAM 进行 block 级别的安全属性配置,是比 SAU 更细粒度的 SRAM 保护机制。

SAU 以地址范围划分安全区,最小粒度 32 字节,通常按 KB 划分
MPCBB 以 256 字节为一个 block,可对每个 block 单独设置 Secure/Non-Secure
支持 SRAM1、SRAM2、SRAM3、SRAM4 独立配置
还支持 PrivConfig_array 对每个 block 配置 Privileged/Non-Privileged 访问权限
SRAM3 容量 512KB,共 2048 个 block,对应 SecConfig_array[0..31](32 个元素 × 32 bit):

SecConfig_array[0]  bit0 → block0  (0x20040000 ~ 0x200400FF)
SecConfig_array[0]  bit1 → block1  (0x20040100 ~ 0x200401FF)
...
SecConfig_array[31] bit31 → block2047


比如我这里配置SRAM1为NS使用,SRAM2为S分区使用,这样NS就无法访问SRAM2了。

815906a72a561ec0c3.png

  /* --- SRAM1: 192KB, 24 elements, all Non-Secure (NS) --- */
  MPCBB_Area_Desc.SecureRWIllegalMode = GTZC_MPCBB_SRWILADIS_ENABLE;
  MPCBB_Area_Desc.InvertSecureState = GTZC_MPCBB_INVSECSTATE_NOT_INVERTED;
  for (int i = 0; i < 24; i++) {
    MPCBB_Area_Desc.AttributeConfig.MPCBB_SecConfig_array  = 0x00000000;
    MPCBB_Area_Desc.AttributeConfig.MPCBB_PrivConfig_array = 0xFFFFFFFF;
  }
  MPCBB_Area_Desc.AttributeConfig.MPCBB_LockConfig_array[0] = 0x00000000;
  if (HAL_GTZC_MPCBB_ConfigMem(SRAM1_BASE, &MPCBB_Area_Desc) != HAL_OK)
  {
    Error_Handler();
  }

  /* --- SRAM2: 64KB, 8 elements, all Secure (S) --- */
  for (int i = 0; i < 8; i++) {
    MPCBB_Area_Desc.AttributeConfig.MPCBB_SecConfig_array  = 0xFFFFFFFF;
    MPCBB_Area_Desc.AttributeConfig.MPCBB_PrivConfig_array = 0xFFFFFFFF;
  }
  MPCBB_Area_Desc.AttributeConfig.MPCBB_LockConfig_array[0] = 0x00000000;
  if (HAL_GTZC_MPCBB_ConfigMem(SRAM2_BASE, &MPCBB_Area_Desc) != HAL_OK)
  {
    Error_Handler();
  }




本工程使用U575时 SAU 配置
SAU配置在partition_stm32u575xx.h文件中,这里的配置为 SAU 禁用 + ALLNS=1 模式,所有地址默认对 NS 可见,安全边界完全由 MPCBB 和 TZSC 控制。SAU 粒度粗(通常按 KB),无法对 SRAM 做 block 级别(256 字节)的细粒度划分,因此用 MPCBB 作为唯一的 SRAM 安全边界。

914246a72a550a6aa4.png

这里仅启用的 SAU Region:

668056a72a546225de.png

配置了SAU,外设,GTZC属性,但是想要在NS中使用中断,还有一个细节需要注意
NVIC 中断路由(Interrupt Target Non-Secure)
TrustZone 架构下,所有中断默认归 Secure 处理。若外设已通过 TZSC 配置为 Non-Secure,还必须在 partition_stm32u575xx.h 中将对应中断路由到 NS,否则 NS 区的中断处理函数永远不会被调用。

中断路由通过 NVIC->ITNS[n] 寄存器控制,每个 bit 对应一个 IRQn:

0 = Secure(默认)
1 = Non-Secure
计算方法
ITNS 寄存器索引 = IRQn / 32
bit 位置       = IRQn % 32


本工程配置

731426a72a538680cb.png

举个例子:

/*
// Interrupts 0..31
//   <o.0>  WWDG_IRQn             <0=> Secure state
//   <o.1>  PVD_AVD_IRQn          <0=> Secure state
//   <o.2>  RTC_IRQn              <0=> Secure state
//   <o.3>  RTC_S_IRQn            <0=> Secure state
//   <o.4>  TAMP_IRQn             <0=> Secure state
//   <o.5>  RAMCFG_IRQn           <0=> Secure state
//   <o.6>  FLASH_IRQn            <0=> Secure state
//   <o.7>  FLASH_S_IRQn          <0=> Secure state
//   <o.8>  GTZC_IRQn             <0=> Secure state
//   <o.9>  RCC_IRQn              <0=> Secure state
//   <o.10> RCC_S_IRQn            <0=> Secure state
//   <o.11> EXTI0_IRQn            <0=> Secure state
//   <o.12> EXTI1_IRQn            <0=> Secure state
//   <o.13> EXTI2_IRQn            <0=> Secure state
//   <o.14> EXTI3_IRQn            <0=> Secure state
//   <o.15> EXTI4_IRQn            <0=> Secure state
//   <o.16> EXTI5_IRQn            <0=> Secure state
//   <o.17> EXTI6_IRQn            <0=> Secure state
//   <o.18> EXTI7_IRQn            <0=> Secure state
//   <o.19> EXTI8_IRQn            <0=> Secure state
//   <o.20> EXTI9_IRQn            <0=> Secure state
//   <o.21> EXTI10_IRQn           <0=> Secure state
//   <o.22> EXTI11_IRQn           <0=> Secure state
//   <o.23> EXTI12_IRQn           <0=> Secure state
//   <o.24> EXTI13_IRQn           <0=> Secure state
//   <o.25> EXTI14_IRQn           <0=> Secure state
//   <o.26> EXTI15_IRQn           <0=> Secure state
//   <o.27> IWDG_IRQn             <0=> Secure state

//   <o.29> GPDMA1_Channel0_IRQn  <0=> Secure state
//   <o.30> GPDMA1_Channel1_IRQn  <0=> Secure state
//   <o.31> GPDMA1_Channel2_IRQn  <0=> Secure state
*/
#define NVIC_INIT_ITNS0_VAL      0x60000000




3. S与NS分区通信方法
NS发消息给S
可以在secure_nsc.c中添加API
举个例子,比如uart1在S分区使用,但是NS想要通过uart1打印日志怎么办?
只需要添加一个NS调用API即可

在secure_nsc.c中添加SECURE_UART_Send函数
在secure_nsc.h中添加声明void SECURE_UART_Send(const char *msg);
CMSE_NS_ENTRY void SECURE_UART_Send(const char *msg)
{
  if (msg != NULL)
  {
        printf(msg);
  }
}



NS中添加printf用的__io_putchar,然后NS直接使用printf即可打印日志了
int __io_putchar(int ch)
{
  SECURE_UART_Send((char *)&ch);
  return ch;
}



S发消息给NS
共享内存方式
可以在secure_nsc.c中添加注册回调
同样举个例子,还是uart1,我这里想把uart1接收到的数据发给NS,因为串口接收到的数据有的多有的少,不确定,而之前已经将RAM划分好了S和NS,S直接传大数据 给NS是没用的,因为NS无法访问S的RAM,这里提出一种共享内存方式。

定义一个共享内存SRAM4:
  /* --- SRAM4: 16KB, 2 elements, shared (NS) --- */
  MPCBB_Area_Desc.SecureRWIllegalMode = GTZC_MPCBB_SRWILADIS_DISABLE;
  for (int i = 0; i < 2; i++) {
    MPCBB_Area_Desc.AttributeConfig.MPCBB_SecConfig_array  = 0x00000000;
    MPCBB_Area_Desc.AttributeConfig.MPCBB_PrivConfig_array = 0xFFFFFFFF;
  }
  MPCBB_Area_Desc.AttributeConfig.MPCBB_LockConfig_array[0] = 0x00000000;
  if (HAL_GTZC_MPCBB_ConfigMem(SRAM4_BASE, &MPCBB_Area_Desc) != HAL_OK)
  {
    Error_Handler();
  }



定一个共享内存结构体
#ifndef SHARED_MEM_H
#define SHARED_MEM_H

#include <stdint.h>

#define UART_RX_BUF_SIZE  64
#define SHARED_MEM_ADDR   0x28000000UL  /* SRAM4 physical address, S/NS accessible */

typedef struct {
    uint16_t len;
    uint8_t  data[UART_RX_BUF_SIZE];
} __attribute__((packed)) SharedUartRx_t;

#define SHARED_UART_RX  ((volatile SharedUartRx_t *)SHARED_MEM_ADDR)

#endif /* SHARED_MEM_H */



串口接收到数据之后,通过注册回调通知NS,然后NS去读取共享内存数据处理,在secure_nsc.h中添加事件ID
typedef enum
{
  SECURE_FAULT_CB_ID       = 0x00U,
  GTZC_ERROR_CB_ID         = 0x01U,
  UART_RX_NOTIFY_CB_ID     = 0x02U  /*!< Secure UART RX notify (no args) callback ID */
} SECURE_CallbackIDTypeDef;



在secure_nsc.c中添加注册回调事件入口
void *pUartRxNotifyCallback = NULL;

CMSE_NS_ENTRY void SECURE_RegisterCallback(SECURE_CallbackIDTypeDef CallbackId, void *func)
{
  if(func != NULL)
  {
        switch(CallbackId)
        {
          case SECURE_FAULT_CB_ID:           /* SecureFault Interrupt occurred */
          pSecureFaultCallback = func;
          break;
          case GTZC_ERROR_CB_ID:             /* GTZC Interrupt occurred */
          pSecureErrorCallback = func;
          break;
          case UART_RX_NOTIFY_CB_ID:
          pUartRxNotifyCallback = func;
          break;
          default:
          /* unknown */
          break;
        }
  }
}



S串口收到数据,调用NS回调通知NS,这里简化列出相关代码,调用pUartRxNotifyCallback完成通知动作
typedef void CMSE_NS_CALL (*funcptr)(void);

typedef funcptr funcptr_NS;

extern funcptr_NS pUartRxNotifyCallback;

      if (pUartRxNotifyCallback != NULL)
        pUartRxNotifyCallback();




在NS中添加注册和处理,这里举例子就是将接收到的数据直接打印出来了。
SECURE_RegisterCallback(UART_RX_NOTIFY_CB_ID, (void *)NS_UartRxNotify);

static void NS_UartRxNotify(void)
{
  printf("[NS] RX(%u): %.*s\r\n",
         SHARED_UART_RX->len,
         SHARED_UART_RX->len,
         (const char *)SHARED_UART_RX->data);
}



直接回调方式
S可以直接传输4个参数给NS,超过 4 个参数后,会走栈(Stack)。走栈就会涉及到地址合法性了,第五个及以后参数所在的栈,必须是调用方(NS)的栈,并且 Secure 侧访问时会涉及地址权限检查。
这里我只采用前4个参数,这四个参数走的是寄存器方式,不会涉及到RAM地址是否合法问题,每个寄存器32bit,一共16个字节,其实也可以传递很多信息了。

在secure_nsc.h添加事件ID FOUR_DATA_CB_ID
typedef enum
{
  SECURE_FAULT_CB_ID       = 0x00U,
  GTZC_ERROR_CB_ID         = 0x01U,
  UART_RX_NOTIFY_CB_ID     = 0x02U,  /*!< Secure UART RX notify (no args) callback ID */
  FOUR_DATA_CB_ID          = 0x03U
} SECURE_CallbackIDTypeDef;



在secure_nsc.c添加注册回调
void *pFourDataCallback = NULL;

CMSE_NS_ENTRY void SECURE_RegisterCallback(SECURE_CallbackIDTypeDef CallbackId, void *func)
{
  if(func != NULL)
  {
        switch(CallbackId)
        {
          case SECURE_FAULT_CB_ID:           /* SecureFault Interrupt occurred */
          pSecureFaultCallback = func;
          break;
          case GTZC_ERROR_CB_ID:             /* GTZC Interrupt occurred */
          pSecureErrorCallback = func;
          break;
          case UART_RX_NOTIFY_CB_ID:
          pUartRxNotifyCallback = func;
          break;
          case FOUR_DATA_CB_ID:
          pFourDataCallback = func;
          break;
          default:
          /* unknown */
          break;
        }
  }
}



S调用通知传递4个参数,这里同样简化代码,需要传递的时候,调用pFourDataCallback接口
typedef void CMSE_NS_CALL (*funcptr_data)(uint32_t r0, uint32_t r1, uint32_t r2, uint32_t r3);
typedef funcptr_data funcptr_data_NS;
extern funcptr_data_NS pFourDataCallback;
      if (pFourDataCallback != NULL)
        pFourDataCallback(0x01, 0x02, 0x03, 0x04);



NS注册回调,处理数据
SECURE_RegisterCallback(FOUR_DATA_CB_ID, (void *)NS_FourDataRx);
static void NS_FourDataRx(uint32_t r0, uint32_t r1, uint32_t r2, uint32_t r3)
{
  printf("[NS] r0:0x%08lx r1:0x%08lx r2:0x%08lx r3:0x%08lx \r\n",
         r0, r1, r2, r3);
}



NS直接读取S数据
NS还可以通过API直接读取S数据,读取一个uint32数据,也是走寄存器方式,不会走栈,没有地址合法性问题

在secure_nsc.c定义一个API,直接返回一个uint32_t 类型数据
CMSE_NS_ENTRY uint32_t SECURE_GetEvent(void)
{
  return 0xFF55AAEE;
}


在secure_nsc.h中添加声明
uint32_t SECURE_GetEvent(void);


在NS中直接调用
  printf("SECURE_GetEvent: 0x%08lx\r\n", SECURE_GetEvent());


所以S和NS分区通信方式有多种,可以根据实际需要去选择合适的方式。

常见问题
若 NS 区中断不响应,检查以下三点:

TZSC 外设属性 — 需配置为 GTZC_TZSC_PERIPH_NSEC
NVIC ITNS 路由 — NVIC_INIT_ITNS1_VAL 对应的 bit 需置 1
GPIO 安全属性 — 相关引脚需通过 HAL_GPIO_ConfigPinAttributes() 设为 GPIO_PIN_NSEC
三者缺一不可,否则 NS 区无法正常收发串口中断。
————————————————
版权声明:本文为CSDN博主「IIIIllllIII」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012988644/article/details/162826504

您需要登录后才可以回帖 登录 | 注册

本版积分规则

141

主题

4598

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部
0