随着越来越多的嵌入式产品连接到网络,嵌入式产品的安全(Security)需求越来越被关注。为此Arm推出了Armv8-M架构,与Armv6-M和Armv7-M相比,Armv8-M架构最关键的增强是支持安全(Security)扩展:TrustZone,来帮助满足嵌入式产品的安全需求。
本文主要介绍如何在IAR Embedded Workbench for Arm中开发和调试Armv8-M TrustZone。
Armv8-M TrustZone介绍
TrustZone是Armv8-M架构的一个安全扩展,用于将系统划分为安全(Secure)状态和非安全(Non-secure)状态:
-
安全状态:系统在安全状态下可以同时访问安全区域和非安全区域

-
非安全状态:系统在非安全状态下只能访问非安全区域

安全状态和非安全状态与现有的处理模式(Handler mode)和线程模式(Thread mode)正交,从而在安全状态和非安全状态下都支持处理模式和线程模式:

如果实施了TrustZone安全扩展,系统默认以安全状态启动。如果没有实施TrustZone安全扩展,系统始终处于非安全状态。
TrustZone将内存空间划分为安全区域和非安全(NS: Non-secure)区域,安全区域进一步划分为安全(S: Secure)区域和非安全可调用(NSC: Non-secure Callable)区域:
-
非安全区域:非安全区域对应的是安全状态和非安全状态都能访问的内存空间
-
安全区域:安全区域对应的是只有安全状态下才能访问的内存空间
-
非安全可调用区域:非安全可调用区域是一种特殊的安全区域,专门用来存储安全网关(SG: Secure Gateway)指令,用于非安全状态下的软件调用安全状态下的函数。非安全状态下的软件调用安全状态下的函数必须同时满足:
-
第一条指令是安全网关指令
-
安全网关指令放到非安全可调用区域
-
内存空间的安全状态由内部安全属性单元 (SAU: Secure Attribution Unit) 或外部实现定义属性单元 (IDAU: Implementation Defined Attribution Unit) 共同控制:

安全属性单元在安全状态下可编程,其程序员模型类似于内存保护单元 (MPU: Memory Protection Unit):

TrustZone支持软件在安全状态和非安全状态之间相互调用:
-
安全网关(SG: Secure Gateway)指令:用于非安全状态下的软件调用安全状态下的函数
-
BXNS(Branch with exchange to Non-secure state)指令:用于安全状态下的软件跳转或者返回到非安全状态
-
BLXNS(Branch with link and exchange to Non-secure state)指令:用于安全状态下的软件调用非安全状态下的函数
非安全状态下的软件通过SG指令调用安全状态下的函数,安全状态下的函数执行完对应操作后通过BXNS指令返回到非安全状态:

安全状态下的软件通过BLXNS指令调用非安全状态下的函数,同时将对应LR上保存的返回地址设置为特殊值FNC_RETURN,非安全状态下的函数执行完对应操作后通过跳转到特殊值FNC_RETURN地址返回到安全状态:

当然异常和中断也会导致安全状态切换:

如果对应异常和中断跟当前处理器安全状态相同,对应异常和中断处理几乎跟当前Armv6-M和Armv7-M相同。主要区别是:当处理器在安全状态运行时发生了非安全异常和中断,处理器会自动将所有安全信息压入安全栈,并将对应寄存器里面的内容清除,从而避免信息泄露。
测试目标(TT: Test Target)指令用于查询内存的安全状态和访问权限:

Armv8-M TrustZone软件开发
带有TrustZone的应用程序被分成安全软件和非安全软件:复位之后,系统启动在安全状态,运行对应安全软件,进行安全软件的初始化,然后跳转到非安全软件的入口,进行非安全软件的初始化。非安全软件可以通过安全API请求安全服务,安全API可以调用非安全函数作为回调函数。

安全软件和非安全软件通常是分开开发的:安全软件设置安全栈指针,执行系统安全配置,初始化安全外设和安全固件框架,并实现安全服务;非安全软件有自己的启动代码和非安全初始化,非安全软件实现用户应用程序,调用安全API并实现非安全回调函数。
下面是安全软件和非安全软件开发的相关步骤:
-
内存分区:划分好对应内存空间和中断的安全状态
-
添加CMSIS启动和初始化代码
-
startup_<device>.c包含对应启动代码和向量表
-
system_<device>.c包含对应系统和时钟配置
-
partition_<device>.h包含对应内存安全状态配置
-

-
配置链接文件:
-
非安全软件需要放到非安全区域
-
安全软件需要放到安全区域
-
安全网关指令需要放到非安全可调用区域
-
-
开发安全软件
-
编程安全属性单元来配置内存空间的安全状态
-
编程Interrupt Target Non-secure Register (NVIC->ITNS)来配置对应中断的安全状态
-
编程System Handler Control and State Register (SHCSR)来配置SecureFault, MemManage, UsageFault
-
编程Application Interrupt and Reset Control Register (AIRCR)
-
定义非安全软件可用的功能
-
添加对应安全API实现并添加包含对应安全API的头文件
-
-
构建安全软件
-
使用对应cmse(CMSE: Cortex-M Security Extension)编译选项进行构建
-
生成用于非安全软件需要的库文件(包含对应安全API)
-

-
开发和构建非安全软件
-
包含对应安全API的头文件
-
包含对应安全API的库文件
-
在IAR Embedded Workbench for Arm中开发和调试Armv8-M TrustZone
下面通过具体的例子来介绍如何在IAR Embedded Workbench for Arm中开发和调试Armv8-M TrustZone。
最快捷的方式是打开对应开发板自带的Armv8-M TrustZone示例工程,比如ST NUCLEO-H563ZI开发板的TrustZone示例工程:
NXP FRDM-MCXN947开发板的TrustZone示例工程:
https://github.com/nxp-mcuxpresso/mcuxsdk-examples/tree/main/_boards/frdmmcxn947/trustzone_examples
还有IAR Embedded Workbench for Arm安装目录下的arm\src\ARMv8M_Secure\Hello_Secure示例工程等。
下面详细介绍如何从零开始新建对应的Armv8-M TrustZone工程(这里只列出跟TrustZone相关的步骤):
创建对应的Armv8-M TrustZone Secure工程,配置TrustZone Mode为Secure(对应使能--cmse编译选项):



对应Secure库文件(包含非安全软件可以调用的安全API)会自动生成,用于Non-secure工程:


创建对应的Armv8-M TrustZone Non-secure工程,配置TrustZone Mode为Non-secure:

同时Non-secure工程需要导入对应Secure工程生成的库文件:

下面的例子中Secure工程提供对应的安全API NSC_ToggleLED1()让Non-secure工程调用:
__cmse_nonsecure_entry void NSC_ToggleLED1(void)
{
/* Toggle PB.00 (LED1) */
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
同时提供对应的安全API NSC_SecureCallback()用于Non-secure工程调用注册对应的回调函数:
__cmse_nonsecure_entry void NSC_SecureCallback(funcptr_NSCallback func)
{
SecureCallback_ToggleLED = func;
}
对应关键字__cmse_nonsecure_entry用于声明对应Secure工程的函数可以被Non-secure工程调用:

Veneer$$CMSE section会自动为每个使用关键字__cmse_nonsecure_entry声明的函数创建,包含对应的安全网关指令,需要放到非安全可调用区域:


对应头文件如下:
#include <arm_cmse.h>
void NSC_ToggleLED1(void);
typedef __cmse_nonsecure_call void (*funcptr_NSCallback)(void);
void NSC_SecureCallback(funcptr_NSCallback func);
对应关键字__cmse_nonsecure_call用于声明对应函数指针调用将进入非安全状态:

Non-secure工程调用安全API NSC_SecureCallback()注册对应Secure工程需要Non-secure工程提供的回调函数SecureCallback_ToggleLED2():
void SecureCallback_ToggleLED2(void)
{
/* Toggle PF.04 (LED2) */
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
}
NSC_SecureCallback((void *)SecureCallback_ToggleLED2);
对应头文件如下:
void NSC_ToggleLED1(void);
void NSC_SecureCallback(void *func);
最终实现Secure工程调用Non-secure工程里面的回调函数SecureCallback_ToggleLED2()来翻转LED2,Non-secure工程调用Secure工程里面的安全API NSC_ToggleLED1()来翻转LED1。
下载调试的时候需要在安全工程里面加载非安全工程的调试文件来同时下载调试安全工程和非安全工程:

然后下载调试的时候就能同时下载调试安全工程和非安全工程,View > Images会显示安全工程和非安全工程的调试文件:

非安全工程调用安全工程里面使用关键字__cmse_nonsecure_entry声明的函数,第一条指令对应的是安全网关指令:


安全工程里面的函数执行完对应操作之后,清除对应的寄存器,然后通过BXNS指令返回非安全工程:

安全工程调用使用关键字__cmse_nonsecure_call声明然后非安全工程提供对应实现的回调函数,首先会清除对应的寄存器,然后通过BLXNS指令跳转到非安全工程:


非安全工程函数执行对应操作后返回安全工程:


总结
本文主要介绍了如何在IAR Embedded Workbench for Arm中开发和调试Armv8-M TrustZone,更多关于Armv8-M TrustZone的信息,可以参考Arm的文档。
参考文献:
-
Joseph Yiu ARMv8-M Architecture Technical Overview
-
Introduction to the Armv8-M Architecture and its Programmers Model
-
Arm® TrustZone Technology for the Armv8-M Architecture
-
https://arm-software.github.io/CMSIS_6/main/Core/using_TrustZone_pg.html
-
Armv8-M Security Extension User Guide
-
Secure software guidelines for Armv8-M
