44. TrustZone安全框架应用

本章相关术语:
  • IDAU——Implementation Defined Attribution Unit

本章参考文档:

注:本章所介绍内容适用于启明6M5和启明4M2开发板,不适用于启明2L1开发板。

已经对 TrustZone 技术有初步了解的朋友,可以直接移步到第三小节——“TrustZone 项目开发的分类”

44.1. Arm ® TrustZone ® 及其安全功能简介

44.1.1. TrustZone 技术概述

Arm ® TrustZone ® 技术是针对 MCU 功能的一种硬件强制隔离技术。 Arm TrustZone 技术将系统和应用程序划分为安全域和非安全域。 安全应用程序可以访问安全和非安全的内存和资源;非安全应用程序只能访问非安全内存和资源。 这些安全状态与现有的线程和处理程序模式正交,可在安全和非安全状态下使能线程和处理程序模式。 系统默认以安全状态启动。CPU 的安全状态包括“安全”和“不安全”。

图

图1 处理器状态

带有安全扩展的 Armv8-M 架构是一个可选的架构扩展。如果实现了安全扩展,则系统默认以安全状态启动。 如果未实现安全扩展,则系统始终处于非安全状态。Arm TrustZone 技术并未涵盖安全的所有方面。例如,它 不包括加密。

在 Armv8-M 架构具有安全扩展的设计中,对系统安全至关重要的组件可置于安全区域中。 这些关键组件包括:

  • 安全自举程序

  • 密钥

  • 闪存编程支持

  • 高价值资产

其余的应用程序置于非安全区域中。

图

图2 安全和非安全区域

如简介部分中所述,有关 TrustZone 的定义和使用的更多详细信息, 请参见 Arm 文档 《适用于 Armv8-M 架构的 Arm® TrustZone® 技术》

44.1.2. 采用 TrustZone 的 RA MCU 硬件强制安全

要构建安全硬件平台,安全注意事项需要超越处理器级别。 支持 Arm ® TrustZone ® 的瑞萨 RA MCU 将安全措施扩展到整个系统,包括:

  • 存储器系统

  • 总线系统

  • 对安全和非安全外设的访问控制

  • 调试系统

注:支持 TrustZone 技术的瑞萨 RA MCU 在硬件功能的细节方面可能会有一些变化。

44.1.2.1. 存储器分离

支持 RA TrustZone 技术的 RA MCU 上的代码闪存、数据闪存和 SRAM 通过 IDAU (实施定义属性单元) 分为安全 (S)、非安全 (NS) 和非安全可调用 (NSC) 区域。当器件生命周期为安全软件开发 (SSD) 状态时, 可通过使用串行编程命令将这些存储器安全属性编程到非易失性存储器中。

图

图3 安全区域划分示意图

注:RA4M2 的 Code Flash 没有上图中所示的 Dual Mode。

图

图4 安全区域划分大小

注:CFS1,CFS2,SRS1,SRS2的值由相关寄存器决定,详见 《RA6M5 Group User’s Manual: Hardware》

44.1.2.2. 总线系统分离

CPU、DMAC 和 DTC 的 IDAU 区域设置是一致的。DMAC 和 DTC 也使用主 TrustZone 过滤器。

44.1.2.2.1. DMA 控制器和 DTC 控制器的主 TrustZone 过滤器

直接存储器访问控制器 (DMAC) 和数据传输控制器 (DTC) 由主 TrustZone 过滤器监控。 在访问总线之前,会提前检测闪存和 SRAM 的 TrustZone 违例区域。 DMAC 和 DTC 中的主 TrustZone 过滤器可以检测 IDAU 定义的 闪存区域(代码闪存和数据闪存)和 SRAM 区域(ECC/奇偶校验 RAM)的安全区域。 当非安全通道访问这些地址时,主 TrustZone 过滤器会检测到安全违例,不允许访问违例地址。 对于 DMA 和 DTC,检测到的访问违例将作为“主 TrustZone 过滤器错误”处理, 产生 DMA_TRANSERR 中断以响应“主 TrustZone 过滤器错误”。

以下是有关 DMAC 安全属性的一些附加注释:

  • 可以为每个通道单独配置安全属性。每个 DMA 通道可以设为安全或非安全属性。

  • 只有安全代码可以配置 DMAC 的启动方式,可以由安全代码启动,还是由非安全代码启动。

    • 如果在安全项目中使用 DMAC,FSP 将以安全模式启动 DMA, 并通过设置相应的寄存器来防止非安全项目意外停止 DMAC。

44.1.2.2.2. 以太网 DMA 控制器

RA6M5 要求将 EDMAC RAM 缓冲区置于 TrustZone 的非安全 RAM 中。EDMAC 被硬编码为 TrustZone 非安 全总线主器件。这些硬件特性允许以下两个以太网程序分区选项:

  • 在安全区域中运行以太网程序,在非安全区域中放置 EDMAC RAM 缓冲区

  • 在非安全区域中运行以太网程序 EDMAC RAM 缓冲区

FSP 支持客户使用这两种选项实现。

44.1.2.2.3. 总线主 MPU TrustZone 功能

总线主 MPU 可用于除 CPU 外的每个总线主器件的存储器保护功能。安全软件可以设置总线主 MPU 的安全属性。

有关总线系统安全属性控制的更多详细信息,请参见《RA6M5 用户硬件手册》和《FSP 用户手册》。

44.1.2.3. IO 与外设分离

MCU 中的大多数外设都可以配置为安全或非安全,但有几个例外。

外设分为两种类型:

  • 类型 1 外设具有一种安全属性,对所有寄存器的访问由一种安全属性控制。 安全应用程序将类型 1 外设安全属性设置到外设安全属性寄存器(PSARx:x = B 至 E)。

    • e2 studio 和 FSP 提供了简便的方式来分配 PSARx。

    • 外设的不同通道可以采用不同的安全属性。例如,UART 通道 0 和通道 1 可以具有不同的安全或非安全属性。

  • 类型 2 外设具有每个寄存器或每个位的安全属性,根据这些安全属性控制对每个寄存器或位域的访问。 安全应用程序将类型 2 外设安全属性设置到每个模块中的安全属性寄存器。 对于安全属性寄存器,请参见每个外设的用户手册中的相关部分。

    • e 2 studio 和 FSP 可配置其中大多数外设,有几个例外的外设已采用合理的默认设置以提供更好的开发体验。

    • 有关每个外设的详细信息,请参见最新的《FSP 用户手册》。

表 3. 类型 1 和类型 2 外设列表

类型

外设

类型 1

SCI、SPI、USBFS、CAN、IIC、SCE9、DOC、SDHI、SSIE、CTSU、CRC、

CAC、TSN、ADC12、DAC12、POEG、AGT、GPT、RTC、IWDT 和 WDT

类型 2

系统控制(复位、LVD、时钟发生电路、低功耗模式和电池备份功能)、闪存高速缓存、

SRAM 控制器、CPU 高速缓存、DMAC、DTC、ICU、MPU、总线、安全设置、ELC 和 I/O 端口

始终非安全

CS 区域控制器、QSPI、OSPI、ETHERC 和 EDMAC

注:类型 2 外设的访问权限因外设而异。请参见每个外设的寄存器说明部分。

表 4. 基于 Arm ® TrustZone ® 的外设访问控制

权限

安全访问

非安全访问

外设配置为安全

允许

忽略写入/忽略读取,生成 TrustZone 访问错误

外设配置为非安全

允许

允许

时钟生成电路 (CGC) 注意事项

时钟生成电路针对每个时钟树控制都有单独的安全属性。当前版本的工具和 FSP 为用户提供以下的灵活时钟控制方案。

  • 整个时钟树仅通过安全项目进行控制,在非安全项目中被锁定。

  • 整个时钟树可通过非安全项目和安全项目进行控制。

默认情况下,FSP 将时钟生成电路 (CGC) 的所有安全属性设置为非安全,如图所示。因此,安全和非安全项目都可以更改时钟设置。 用户可以选择将 CGC 的所有安全属性设置为安全,因此非安全项目开发人员无法覆写安全项目设置

44.1.2.4. 调试接口

对于支持 Arm ® TrustZone ® 技术的 RA MCU 产品家族,调试功能分为 DBG0、DBG1 和 DBG2 三个级别, 用于调试支持TrustZone技术的产品,并为开发、生产和部署的产品提供安全性。

  • DBG2:允许调试器连接,无限制访问存储器和外设

  • DBG1:允许调试器连接,但仅限访问非安全存储区域和外设

  • DBG0:不允许调试器连接

调试级别根据产品的器件生命周期状态确定。

更多详细细节可以参考 第43章 DLM——内部Flash读写保护实验。

44.1.3. TrustZone 用例示例

44.1.3.1. 知识产权 (IP) 保护

IP 保护是专有软件算法或数据保护的常见需求。 TrustZone 技术为 IP 保护提供了良好的硬件隔离。 TrustZone 技术在安全(“受信任”)和非安全(“非受信任”)代码/数据区域之间形成隔离。创建供他人集成的构建模 块的客户可以利用 TrustZone 技术功能, 将其软件 IP 存储在安全(“受信任”)区域中。

商业模式

并非所有软件开发人员都会创建最终产品。一些开发人员创建构建模块(例如算法),供其他人集成以创建最 终产品。他们面临的一个困难是保护其软件 IP。他们的最终客户更愿意接收源代码,但源代码很容易被复制和 重新分发。

即使是二进制库也不受完全保护,因为有些工具可以将二进制文件反汇编为汇编代码、甚至 C 源代码。 TrustZone 技术为这些开发人员提供了新的商业模式,开发人员可以将其算法编程到支持 TrustZone 的 MCU 的 安全区域并销售增值 MCU,其 IP 受到 TrustZone 和 RA MCU 的器件生命周期管理 (DLM) 系统的保护。

用于 IP 保护的 RA MCU 器件生命周期管理功能

在开发过程中, DLM 状态回退将擦除受保护区域的闪存内容(除非永久锁定)。这样可以防止读取受保护区域 的闪存,从而保护 IP 并在需要修改算法的情况下避免器件报废。

在生产期间,如果算法开发人员希望保留在应用程序已经烧写好的情况下调试其算法的能力,可以安 装 DLM 密钥来实现 NSECSD 到 SSD 以及 DPL 到 NSECSD 的切换。

用于 IP 保护的 RA MCU 闪存块锁定功能

RA MCU 支持临时和永久闪存块保护。这样便可保护客户 IP 和信任根免遭意外擦除和更改。

IP 保护开发、生产和部署流程

图

图5 IP 保护开发、生产和部署流程

44.1.3.2. 信任根保护

信任根 (RoT) 是产品的安全基础。所有更高级别的安全都建立在 RoT 之上。 RoT 还针对更高级别的安全漏洞 实现了恢复功能。当信任根遭到破坏时,无法进行恢复,并可能导致严重的后果。对于物联网应用,信任根是 经过身份验证的固件更新的基础,也可以实现安全的互联网通信。

为了减少攻击面, RoT 中包含的功能应该尽可能少。

图

图6 信任根保护-尽可能减少安全区域中存储的内容

应考虑将所有其他应用程序代码和设备驱动程序分配给非安全区域。

44.2. 支持 Arm® TrustZone® 应用程序设计的软件功能

44.2.1. IDAU 区域设置

出厂时,RA MCU 以 CM(芯片制造)生命周期状态交付给开发人员。 在设置 IDAU 区域之前,MCU 必须切换到 SSD(安全软件开发)生命周期状态。

从 CM 到 SSD 状态的切换方法可参考本教程第3章,第43章。

44.2.2. 非安全可调用模块

非安全分区可能需要访问安全项目中的一些驱动程序和中间件堆叠。要使能生成 NSC 跳板,请从配置器中所 选模块的右键单击上下文菜单中设置 “Non-Secure Callable” (非安全可调用)。

注解

注:只能将堆叠的顶层模块配置为 NSC。

图

图7 生成 NSC 跳板

44.2.3. 非安全可调用的保护函数

可以通过保护 API 从非安全项目访问 NSC 驱动程序。 FSP 将自动为安全项目中配置为非安全可调用的所有堆叠顶部模块/驱动程序 API 生成保护函数。

用户在使用保护函数时应遵循的一些最佳实践和指南,如下所示。

限制对选定配置和控制的访问

生成的默认保护函数将忽略从 NS 端传入的 p_ctrl 和 p_cfg 参数。 作为替代,保护函数将基于模块实例提供这些数据结构的静态安全区域实例。

代码清单:保护函数示例
 BSP_CMSE_NONSECURE_ENTRY fsp_err_t g_uart0_open_guard(
         uart_ctrl_t *const p_api_ctrl, uart_cfg_t const *const p_cfg)
 {
     /* TODO: add your own security checks here */

     FSP_PARAMETER_NOT_USED(p_api_ctrl);
     FSP_PARAMETER_NOT_USED(p_cfg);

     return R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
 }

非安全缓冲区位置的测试

  • 如果非安全区域提供输入(例如通过调用带数据缓冲区的 write () 函数),则保护函数应检查数据缓 冲区是否完全在 NS 区域内。

  • 如果非安全区域提供用于存储输出的指针(例如通过调用带存储位置的指针的 read () 函数),则保护 函数应检查数据缓冲区是否完全在 NS 区域内。

使用 CMSE 库来处理此要求。

将非安全数据输入结构处理为易失性结构

如果非安全区域提供数据结构作为输入(例如,带 3 个成员的 typedef’d 结构), 则保护函数应先在安全区域中复制数据结构,然后再传送给安全函数。 这样做是因为非安全数据结构应视为易失性结构,非安全区域在调用 NSC 函数后可能会更改其内容。

处理此要求,参考: 避免对当前处理的数据进行异步修改

限制 NSC 函数中的参数数量

编译器使用寄存器 R0 到 R3 来传送参数和返回值。寄存器 R4 到 R12 在函数执行期间使用。 被调用的函数将恢复寄存器 R4 到 R12。因此,如果 NSC API 用于具有 4 个以上参数的安全函数, 则保护函数应定义一个具有不同原型的函数,该函数将成为处理所有参数的漏斗。 新的函数原型应该采用一个数据结构,该结构的成员覆盖安全函数中的所有参数。 这意味着非安全代码需要将函数参数放入结构中。然后,保护函数会将数据结构扩展为单独的参数并传送给安全函数。

下图给出了一个 FSP 示例,用于将 R_SPI_WriteRead 函数中的 5 个参数缩减为 NSC API 保护函数中的 4 个 参数。

图

图8 处理超过 4 个参数的安全函数

44.2.4. 创建由用户定义的非安全可调用函数

出于 IP 保护目的,用户可以在安全项目中创建自定义 NSC API, 仅公开其算法的顶级控制并将 IP 存储在安全 Arm ® TrustZone ® 区域中。 在创建用户定义的 NSC API 期间应采取上述预防措施。

创建自定义 NSC API 的步骤如下:

  1. 通过声明带 BSP_CMSE_NONSECURE_ENTRY 的函数来创建非安全可调用自定义函数。

  2. 创建一个包含所有自定义 NSC 函数原型的头文件,例如 my_nsc_api.h。

  3. 使用 “Build Variable”**(构建变量)包含 **NSC 头文件的路径,如下所示。

  4. 编译安全项目以创建安全捆绑包。NSC 头文件会自动提取到非安全项目中使用。

图

图9 链接用户定义的非安全可调用 API 头文件

44.2.5. 编写支持 TrustZone 技术的软件

44.2.5.1. 利用 CMSE 函数增强系统级安全性

本小节讨论如何利用 CMSE 库改进安全软件设计。CMSE 函数的一些示例是:

  • cmse_check_address_range:例如,该函数可用于确认地址范围是否完全在非安全区域

  • cmse_check_pointed_object:例如,该函数可用于确认指针指向的存储器是否完全在非安全区域

代码清单:非安全缓冲区地址范围检查
 BSP_CMSE_NONSECURE_ENTRY fsp_err_t g_uart0_read_guard(uart_ctrl_t *const p_api_ctrl,
               uint8_t *const p_dest,
               uint32_t const bytes)
 {
     /* Verify all pointers are in non-secure memory.*/
     uint8_t *const p_dest_checked = cmse_check_address_range ((void*) p_dest, bytes, CMSE_AU_NONSECURE);
     FSP_ASSERT (p_dest == p_dest_checked);

     /* TODO: add your own security checks here */
     FSP_PARAMETER_NOT_USED (p_api_ctrl);
     return R_SCI_UART_Read (&g_uart0_ctrl, p_dest_checked, bytes);
 }

44.2.5.2. 避免对当前处理的数据进行异步修改

如下所示,当指针 p 指向非安全存储器时,在执行数组边界检查的存储器访问操作之后, 但在对数组进行索引的存储器访问操作之前这段时间内,其值可能会更改。 对非安全存储器的这种异步更改将导致此数组边界检查无效。

代码清单:在安全代码中将非安全数据视为易失性数据
 int array[N];
 void foo(volatile int *p)
 {
   int i = *p;
   if (i >= 0 && i < N) { array[i] = 0; }
 }

44.2.5.3. 利用 Armv8-M 堆栈指针堆栈限制功能

Armv8-M 架构引入了堆栈限制寄存器,可在堆栈溢出时触发异常。采用 Arm ® TrustZone ® 技术的 CM23 有两 个处于安全状态的堆栈限制寄存器:

  • 主堆栈的堆栈限制寄存器:MSPLIM_S

  • 进程堆栈的堆栈限制寄存器:PSPLIM_S

采用 TrustZone 技术的 CM33 有两个处于安全状态的堆栈限制寄存器和两个处于非安全状态的堆栈限制寄存器:

  • 处于安全状态的主堆栈的堆栈限制寄存器:MSPLIM_S

  • 处于安全状态的进程堆栈的堆栈限制寄存器:PSPLIM_S

  • 处于非安全状态的主堆栈的堆栈限制寄存器:MSPLIM_NS

  • 处于非安全状态的进程堆栈的堆栈限制寄存器:PSPLIM_NS

用户可以采用自定义的故障处理程序来捕捉堆栈限制溢出错误。

有关堆栈限制寄存器功能的更多信息,用户可以参见 《Arm ® v8-M 架构参考手册》 的“Armv8-M 架构配置文件”部分。

44.3. TrustZone 项目开发的分类

44.3.1. 联合式项目开发

使用联合式项目开发模型时,安全和非安全项目由同一个受信任的团队开发。 安全项目必须与非安全项目位于同一工作区中,并且通常在设计工程师有权访问安全和非安全项目源时使用。

此外,将引用一个安全的 .elf 文件并将其包含在调试编译的调试配置中,以便下载到目标器件。开发工程师 将能够查看安全和非安全项目的源代码和配置。

对于联合式项目开发模型,可以在一个工作区中调试安全和非安全项目开发。

44.3.2. 分离式项目开发

以下是分离式项目开发模型的特点:

  • 安全项目和非安全项目由两个不同的团队单独开发。

  • 安全项目将首先由 IP 提供商开发。IP 提供商创建一个安全捆绑包。

  • 在非安全开发人员开始开发之前,安全捆绑包已预先烧录到 MCU 里。 非安全开发人员只能看到非安全项目和非安全分区。

44.4. 新建 TrustZone 工程

接下来结合本教程第5和第6章所述的步骤,新建 TrustZone 工程。

本小节将介绍建立以下三个工程的方法:

  • 44_TrustZone_S

  • 44_TrustZone_Dummy_NS

  • 44_TrustZone_NS

44.4.1. 新建安全工程模板

44.4.1.1. 步骤 1:使用 RA 项目生成器模板创建新项目

该步骤请参考本教程第5和第6章的内容。

请注意,最好在项目名称的末尾附上“_s”,以此提示该项目为安全项目。

44.4.1.2. 步骤 2:选择安全项目作为项目类型

选择“Secure Project”(安全项目)作为项目类型并花点时间阅读有关此项目类型的说明。在此项目中初始 化的所有外设都将被假定为具有安全属性,但表 3 中列出的例外情况为始终非安全。置于此项目中的所有代码 和数据将由 FSP BSP 初始化为安全,并且控制权将在安全项目执行结束时交由非安全项目复位处理程序。

图

图10 选择项目模板

单击“Next”(下一步),选择项目模板。

44.4.1.3. 步骤 3:选择项目模板

因为我们本章实验都不需要用到 RTOS ,所以这里选择第一个最小项目即可。

图

图11 选择安全项目类型

如上图所示,共有三个安全项目模板:

  • “Bare Metal – Minimal”(裸机 – 最小化)
    • 具有 MCU 初始化功能的安全项目,支持切换到非安全分区。 本文使用此项目模板作为示例来说明创建安全项目的一般步骤。

  • “TrustZone Secure RTOS - Minimal”(TrustZone 安全 RTOS - 最小化)
    • 安全项目将在安全区域中为 RTOS 项目中需要访问 NSC API 的线程添加相应的 RTOS 上下文。 选择此项目类型后,将添加 Arm TrustZone 上下文 RA 端口,如下图所示。

    • RTOS 内核和用户任务将驻留在非安全分区中。

图

图12 添加 TrustZone 上下文 RA 端口

时钟控制注意事项

时钟将在安全项目中初始化以允许更快启动。 默认情况下,FSP 将时钟生成电路 (CGC) 的所有安全属性设置为非安全,如下图所示。 因此,安全和非安全项目都可以更改时钟设置。 用户可以选择将 CGC 的所有安全属性设置为安全, 从而,非安全项目开发人员无法覆写安全项目设置,如下图所示。

图

图13 安全项目将时钟设置为安全

44.4.1.4. 步骤 4:生成项目内容并编译项目模板

打开 FSP 配置器。单击“Generate Project Content”(生成项目内容),如图所示。

图

图14 生成项目内容

右键单击项目,选择“Build Project”(编译项目)。

图

图15 编译模板项目

小技巧

请注意,默认情况下,模板中包含用于控制安全 GPIO 引脚的 GPIO 驱动程序。 如果不需要,用户可以将其删除,以减少项目占用空间。

44.4.1.5. 步骤 5:查看生成的初始安全捆绑包

编译成功后,将生成安全捆绑包 <project_name>.sbd,如图所示。

图

图16 生成的安全捆绑包

44.4.1.6. 步骤 6:开发安全应用程序

在产品开发过程中,用户在完成开发之前,很可能会反复经历以下步骤:

  • 添加所需的 FSP 模块

    • 如果需要,定义 NSC 模块。有关详细方法,会在后面进行说明。

    • 注:以太网不能用于安全项目,只能用于非安全项目。

  • 如有需要,创建用户定义的非安全可调用函数。有关详细方法,会在后面进行说明。

  • 开发安全应用程序

    • 设计代码流,在开始非安全项目执行之前运行不需要非安全可调用功能的安全应用程序: 在调用函数 R_BSP_NonSecureEnter(); 之前

  • 重新编译并测试应用程序

44.4.1.7. 调试安全项目

  • 对于联合式项目开发模型,可以在一个工作区中调试安全和非安全项目开发。

  • 对于联合式项目开发模型,安全项目的调试通常不会以隔离方式进行。

采用联合式项目开发时,安全项目的调试通常不会与非安全项目的调试进行隔离。

采用分离式项目开发时,要单独调试安全项目,用户可以使用以下两个方案:

  • 方法一:准备一个“虚拟/测试用的”非安全项目,如将在本章创建的项目——44_TrustZone_Dummy_NS。这种方法的优势在于可以在测试非安全项目中调试非安全可调用 API。

  • 方法二:在安全项目的 hal_entry.c 中将 R_BSP_NonSecureEnter(); 替换为 while(1);,然后单独调试安全项目。

    • 重要提示:在进行非安全项目调试之前,需要恢复 R_BSP_NonSecureEnter(); ,写入该恢复后的安全项目,这样后面才能对MUC的非安全项目进行开发。

44.4.2. 新建虚拟非安全工程

本工程为虚拟(dummy)非安全工程,一般是用于在分离式项目开发的情况下对安全工程进行调试。

采用联合式项目开发时,安全项目的调试通常不会与非安全项目的调试进行隔离。 一般可以直接新建对应的非安全工程,而不需要新建这个虚拟非安全工程。

建立虚拟非安全工程与建立非安全工程的方法基本一致,请读者往下继续阅读“新建非安全工程模板”小节。

44.4.3. 新建非安全工程模板

44.4.3.1. 步骤 1:使用 RA 项目生成器模板创建新项目

对于联合式项目开发,建立并编译安全模板项目后,用户便可在安全项目所在的同一工作区中开始创建非安全模板项目。 即安全项目 44_TrustZone_S 和 44_TrustZone_NS 位于同一个工作区。

而对于分离式项目开发,安全项目 44_TrustZone_S 和 44_TrustZone_Dummy_NS 位于同一个工作区。

请注意,如果读者想自行创建其他TrustZone项目时,最好在项目名称的末尾也附上“_ns”,以此提示该项目为非安全项目。

44.4.3.2. 步骤 2:选择非安全项目作为项目类型

选择“Non-secure Project”(非安全项目)作为项目类型。

图

图17 选择非安全项目作为项目类型

单击“Next”(下一步),选择项目模板。

44.4.3.3. 步骤 3:与安全项目建立链接

对于联合式项目开发中的 非安全项目 与分离式项目开发中的 虚拟非安全项目,选择当前工作区内的安全项目即可。

请注意,安全项目必须位于同一工作区中,并且必须处于打开状态才能在选择框中引用。 此外,还必须编译安全项目以创建用于设置非安全项目的信息。

图

图18 建立到安全项目的链接

而对于分离式项目开发中的 非安全项目,单独在一个工作区即可,并且是绑定安全项目生成的 Bundle 文件。

图

图19 建立到安全捆绑包的链接

如果安全项目没有被编译,则会报错,此时应返回进行编译。

No smart bundle found in project, active configuration may need building

单击“Next”(下一步)继续。

44.4.3.4. 步骤 4:按照下图提示选择非安全项目是否支持 RTOS

本章实验无需使用到 RTOS ,所以我们选择 No RTOS 即可

图

图20 选择是否在非安全项目中使用 RTOS

单击“Next”(下一步)继续。

44.4.3.5. 步骤 5:选择项目模板以完成创建非安全模板项目

选择最小模板即可

图

图21 非 RTOS 应用的模板选项

单击“Finish”(完成)创建相应的模板项目。

提示

即使允许在 BSP“Properties”(属性)页面中配置某些安全属性,在当前的 IDE 支持下也不会将其使能。无法通过非安全项目配置以下属性:

图

图22 不可通过非安全项目配置的属性

44.4.3.6. 步骤 6:生成项目内容并建立项目引用,编译非安全项目

为避免安全项目的意外更新被遗漏,用户还可以将安全项目定义为非安全项目的引用, 这样在编译非安全项目时就会自动触发编译安全项目。 打开非安全项目的“Properties”(属性)页面,单击“Project References”(项目引用)并选择相应的安全项目作为引用项目。 设置完成后,编译非安全项目时将始终触发重新编译安全项目。

图

图23 建立项目引用

单击 “应用并关闭”,随后编译非安全项目。

请注意,每次更改安全项目时都必须对其进行编译,以确保与非安全项目保持连接。当安全捆绑包更改时,将 弹出一个窗口,要求用户获取最新的安全捆绑包。单击“Yes”(是),然后重新编译非安全项目,以便使 用更新后的 <project_name>.sbd。

图

图24 安全捆绑包更新通知

44.4.4. 联合式项目开发与分离式项目开发之间的区别

  • 使用分离式项目开发模型调试安全项目时,不会与产品的非安全项目同时进行。

  • 用户可以创建一个虚拟非安全项目用于安全项目测试,例如测试非安全可调用 API。

  • 在分离式项目开发的非安全项目开发之前,需要为MCU 配置与安全捆绑包相关联的安全二进制文件。

  • 安全捆绑包包含二进制格式的安全项目 IP 和安全项目的NSC API 接口。

  • 此外,在安装完安全捆绑包后 ,需要将MCU 器件生命周期状态从 SSD 切换为 NSECSD 以保护安全内容。

44.5. IP 保护实验——电位器控制LED的亮度

在本章实验中,我们需要实现通过调节开发板上的电位器,来控制开发板上LED的亮度的功能,但是这次实验与以往的开发不同, 在本次实验里,我们将控制代码在安全项目中实现,然后在非安全项目中调用安全项目中实现的API,达到安全保护IP的目的。

本章实验所需要的硬件外设,我们在前面的章节已经学习过了,如果有些许遗忘,可以回到之前的章节重新温习一下。

44.5.1. 硬件设计

连接引脚分配

开发板

调试串口引脚

ADC采样引脚

PWM输出引脚兼LED输出引脚

LED状态灯

启明6M5

  • P511:SCI4 RXD

  • P512:SCI4 TXD

P000—-ADC0

GPT6A—P400—-LED1

P403—-LED2

启明4M2

  • P100:SCI0 RXD

  • P101:SCI0 TXD

P000—-ADC0

GPT4B—P608—-LED2

P609—-LED2

44.5.2. 软件设计

下图所示为此示例项目中的安全、非安全和非安全可调用硬件和软件分区方案。

图

图25 软件架构框图

44.5.2.1. 新建工程

对于 e2 studio 开发环境:

拷贝一份在上一节创建的 TrustZone 工程模板,包括 44_TrustZone_S,44_TrustZone_NS,44_TrustZone_Dummy_NS,最后再将它们导入到 e2 studio 工作空间中,保证他们在同一个工作空间下面。

对于 Keil 开发环境:

拷贝一份在上一节创建的 TrustZone 工程模板,包括 44_TrustZone_S,44_TrustZone_NS,44_TrustZone_Dummy_NS, 接着打开非安全项目工程中生成的工作空间文件,如 “TrustZone_NS.uvmpw”。

安全项目工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “my_nsc_api” 文件夹, 再进入 “my_nsc_api” 文件夹里面新建源文件和头文件:“my_nsc_api.c” 和 “my_nsc_api.h”。

再将之前的工程 25_ADC 中 src 文件夹中的 adc 文件夹拷贝到当前工程下面的 src 文件夹下,同样的, 将之前的工程 27_GPT_PWM_Output 中 src 文件夹中的 gpt 文件夹拷贝到当前工程下面的 src 文件夹下。

整个安全项目工程文件结构如下:

44_TrustZone_S 文件结构
44_TrustZone_S
├─ ......
└─ src
   ├─ adc
   │  ├─ bsp_adc.c
   │  └─ bsp_adc.h
   ├─ gpt
   │  ├─ bsp_gpt_pwm_output.c
   │  └─ bsp_gpt_pwm_output.h
   ├─ my_nsc_api
   │  ├─ my_nsc_api.c
   │  └─ my_nsc_api.h
   └─ hal_entry.c

非安全项目工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “uart” 文件夹, 再进入 “uart” 文件夹里面新建源文件和头文件:“nsc_bsp_uart.c” 和 “nsc_bsp_uart.h”, 将之前的工程 11_GPIO_LED 中 src 文件夹中的 led 文件夹拷贝到当前工程下面的 src 文件夹下。

整个非安全项目工程文件结构如下:

44_TrustZone_NS 文件结构
44_TrustZone_NS
├─ ......
└─ src
   ├─ uart
   │  ├─ nsc_bsp_uart.c
   │  └─ nsc_bsp_uart.h
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   └─ hal_entry.c

虚拟非安全项目就是一个空的非安全项目, 所以虚拟非安全项目工程文件结构如下:

44_TrustZone_Dummy_NS 文件结构
44_TrustZone_Dummy_NS
├─ ......
└─ src
   └─ hal_entry.c

44.5.2.2. FSP配置

接下来我们先来配置安全项目 44_TrustZone_S

GPT 的配置与 “GPT——通用PWM定时器” 章节中 “实验2:PWM输出” 的FSP配置方法基本一致, 不过需要注意的是配置的GPT6的A组输出PWM,如下图:

图

图27 gpt的fsp配置

ADC 的配置也与 “ADC——电压采集” 章节中的 “实验:电位器电压采集” 中FSP配置相同

图

图28 adc的fsp配置

因为我们需要用 Uart 来作为非安全api调试接口,所以我们还需要配置 Uart ,这里不需要配置串口中断函数名字

图

图29 uart的fsp配置

这里我们还需要 uart 模块设置成 Non-secure Callable

图

图30 将 uart 设置成非安全可调用api

配置完成之后可以按下快捷键“Ctrl + S”保存, 最后点右上角的 “Generate Project Content” 按钮,让软件自动生成配置代码即可。

接下来我们再来配置非安全项目 44_TrustZone_NS

配置非安全项目前,需要先将建立起链接的安全项目进行编译,更新.sbd文件

打开 Stacks 选项卡,添加非安全可调模块

图

图31 添加非安全可调用模块

配置完成之后,同样的,保存,点击右上角的 “Generate Project Content” 按钮,让软件自动生成配置代码即可。

44.5.2.3. 安全项目中的保护函数源文件

当我们配置完成生成项目后,可以看到在安全项目的 src 文件夹下生成了一个C语言源文件 “g_uart4_guard.c”, 里面是fsp对与 Uart 模块生成的保护函数,打开该文件,我们可以看到此文件下的保护函数。

例如 Uart4的部分保护函数如下

代码清单:Uart4模块的保护函数
BSP_CMSE_NONSECURE_ENTRY fsp_err_t g_uart4_open_guard(uart_ctrl_t *const p_api_ctrl, uart_cfg_t const *const p_cfg)
{
   /* TODO: add your own security checks here */

   FSP_PARAMETER_NOT_USED (p_api_ctrl);
   FSP_PARAMETER_NOT_USED (p_cfg);

   return R_SCI_UART_Open (&g_uart4_ctrl, &g_uart4_cfg);
}
BSP_CMSE_NONSECURE_ENTRY fsp_err_t g_uart4_read_guard(uart_ctrl_t *const p_api_ctrl, uint8_t *const p_dest,
      uint32_t const bytes)
{
   /* Verify all pointers are in non-secure memory. */
   uint8_t *const p_dest_checked = cmse_check_address_range ((void*) p_dest, bytes, CMSE_AU_NONSECURE);
   FSP_ASSERT (p_dest_checked != NULL);

   /* TODO: add your own security checks here */

   FSP_PARAMETER_NOT_USED (p_api_ctrl);

   return R_SCI_UART_Read (&g_uart4_ctrl, p_dest_checked, bytes);
}

我们可以看到,生成的默认保护函数将忽略从 NS 端传入的 p_ctrl 和 p_cfg 参数。 并且会验证所有读操作的指针是否都在非安全内存中,以防止安全数据的泄露。

44.5.2.4. 安全项目中创建自定义的非安全可调用函数

创建的自定义的非安全可调用函数需要在函数头前添加 BSP_CMSE_NONSECURE_ENTRY 来修饰, 下面是自定义函数的源文件代码:

代码清单:my_nsc_api.c
#include "my_nsc_api.h"
#include "gpt/bsp_gpt_pwm_output.h"
#include "adc/bsp_adc.h"
/*初始化 GPT,ADC模块*/
BSP_CMSE_NONSECURE_ENTRY void My_nsc_api_Init(void)
{
   GPT_PWM_Init();
   ADC_Init();
}

BSP_CMSE_NONSECURE_ENTRY uint8_t My_nsc_api_apply(void)
{
   uint8_t temp;
   /*将读到的电压值进行处理,使其不能超过GPT_PWM_SetDuty()输入PWM占空比范围*/
   temp = (uint8_t)(Read_ADC_Voltage_Value()*100/3.3);
   GPT_PWM_SetDuty(temp);
   /*返回输出PWM占空比*/
   return temp;
}

下面是自定义函数的头文件代码:

代码清单:my_nsc_api.h
#ifndef MY_NSC_API_MY_NSC_API_H_
#define MY_NSC_API_MY_NSC_API_H_
#include "hal_data.h"

/*自定义的非安全可调用函数*/
BSP_CMSE_NONSECURE_ENTRY void My_nsc_api_Init(void);
BSP_CMSE_NONSECURE_ENTRY uint8_t My_nsc_api_apply(void);

#endif /* MY_NSC_API_MY_NSC_API_H_ */

44.5.2.5. 安全项目链接自定义的非安全可调用 API 头文件

打开安全项目的属性设置,添加包含所有自定义 NSC 函数原型的头文件路径

图

图33 添加路径

44.5.2.6. 非安全项目的串口初始化函数

编译成功完安全项目后,我们调用 FSP_GUARD_g_uart4_R_SCI_UART_Open 函数打开 SCI4 UART 模块, 并且使用 FSP_GUARD_g_uart4_R_SCI_UART_CallbackSet 函数来设置串口中断函数。

代码清单:文件 bsp_uart.c 串口初始化
uart_callback_args_t g_uart_callback_args;
/* 调试串口 UART4 初始化 */
void Debug_UART4_Init(void)
{
   fsp_err_t err;
   err = FSP_GUARD_g_uart4_R_SCI_UART_Open();

   err = FSP_GUARD_g_uart4_R_SCI_UART_CallbackSet(debug_uart4_callback, NULL, &g_uart_callback_args);

   assert(FSP_SUCCESS == err);
}

44.5.2.7. 非安全项目的串口中断回调函数

与正常的串口中断回调函数相同,并且定义了一个额外的标志变量 uart_send_complete_flag, 来表示串口发送数据已完成。

代码清单:文件 bsp_uart.c 串口初始化
/* 发送完成标志 */
volatile bool uart_send_complete_flag = false;


/* 串口中断回调 */
void debug_uart4_callback (uart_callback_args_t * p_args)
{
   switch (p_args->event)
   {
      case UART_EVENT_RX_CHAR:
      {
            FSP_GUARD_g_uart4_R_SCI_UART_Write((uint8_t *)&(p_args->data), 1);//回显功能
            break;
      }
      case UART_EVENT_TX_DATA_EMPTY:
      {
            uart_send_complete_flag = true;
            break;
      }
      default:
            break;
   }
}

值得注意一下的是,串口函数我们需要声明一下,因为在配置安全项目的Uart模块时,并没有对串口中断回调函数进行命名。

44.5.2.8. 非安全项目重定向printf输出到串口

我们将以下的代码添加到源文件“bsp_uart.c”里面。

代码清单 :重定向printf输出到串口
/* 重定向 printf 输出 */
#if defined __GNUC__ && !defined __clang__
int _write(int fd, char *pBuffer, int size); //防止编译警告
int _write(int fd, char *pBuffer, int size)
{
   (void)fd;
   FSP_GUARD_g_uart4_R_SCI_UART_Write((uint8_t *)pBuffer, (uint32_t)size);
   while(uart_send_complete_flag == false);
   uart_send_complete_flag = false;

   return size;
}
#else
int fputc(int ch, FILE *f)
{
   (void)f;
   FSP_GUARD_g_uart4_R_SCI_UART_Write((uint8_t *)&ch, 1);
   while(uart_send_complete_flag == false);
   uart_send_complete_flag = false;

   return ch;
}
#endif

在使用 e2 stdio 的时候,需要修改C语言项目设置,按下图步骤修改即可。

图

图34 打开C/C++项目设置串口

图

图35 修改项目工具设置

44.5.2.9. 非安全项目的hal_entry入口函数

代码清单 :hal_entry.c
#include "hal_data.h"
#include "my_nsc_api.h"
#include "uart/bsp_uart.h"
#include "led/bsp_led.h"
FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER

/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used.  This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
   /* TODO: add your own code here */
   My_nsc_api_Init();//用户自定义api函数初始化
   Debug_UART4_Init(); // SCI4 UART 调试串口初始化

   printf("这是一个读取电位器电压来控制LED的亮度的例程\r\n");
   printf("打开串口助手查看LED的PWM的占空比\r\n");
   printf("旋钮电位器,可以看到输出的PWM值在一定范围之内发生变化,LED灯的亮度也在变化\r\n");


   while(1)
   {
      printf("Light_PWM = %d\r\n",My_nsc_api_apply());//打印PWM输出的占空比
      R_BSP_SoftwareDelay(500, BSP_DELAY_UNITS_MILLISECONDS); //大概0.5秒钟读取一次
      LED2_TOGGLE;//大概0.5秒翻转一次电平
   }
#if BSP_TZ_SECURE_BUILD
   /* Enter non-secure code */
   R_BSP_NonSecureEnter();
#endif
}

44.5.2.10. 调试与下载验证

在进入下载调试前,还需要设置器件的 IDAU 分区,以保证安全项目的代码下载到对应Flash的分区。

1、保证器件处于SSD状态,可以通过初始化该器件,或者在已经注入密钥的情况下将器件转化到SSD状态,具体方法可以参考上一章 DLM——内部Flash读写保护实验

2、打开 RFP 软件,在 “Flash Options” 选项卡下,打开分区设置,并选择编译好的安全项目中的 .rpd 文件。该文件其实是瑞萨分区数据文件(Renesas Partition Data File)。

图

图36 打开分区设置

图

图37 选择分区文件

3、设置好下载命令,点击开始

图

图38 下载 IDAU 分区文件

联合式项目开发

编译非安全项目后,确定器件状态为SSD,如果不是,则需要先将器件转换到SSD状态,才能打开调试设置, 之后确定将安全和非安全 .elf 文件写入到 MCU

图

图39 选择调试文件

分离式项目开发

对于安全开发人员的开发调试:

情况一:如果是通过虚拟非安全项目来调试安全项目,则在编译虚拟非安全项目后,在SSD状态下,将安全和虚拟非安全 .elf 文件写入到 MCU

图

图40 选择安全和虚拟非安全项目 .elf 文件

情况二:如果是通过更改安全项目 hal_entry.c 中的 R_BSP_NonSecureEnter(); 来调试安全项目,则选择调试文件时只需要选择安全项目的 .elf 文件

图

图41 选择安全项目 .elf 文件

下载好安全项目到MCU后,需要工具将器件转换到 NSECSD 状态,最后把 MCU 交付给非安全开发人员进行非安全项目开发

对于非安全开发人员的开发调试:

将最新的安全捆绑包文件(.sbd)绑定到非安全项目,之后进入到调试设置界面,选择非安全项目 .elf 文件。

图

图42 选择非安全项目.elf 文件

Keil环境的下载调试

在设置完 IDAU 分区后,需要先下载安全项目的 .axf 文件,之后才能调试/下载非安全项目。

注解

Keil 环境下暂不支持 TrustZone 分离式开发

44.5.2.11. 实验现象

下载程序后,程序工作大概会以0.5秒为间隔,闪烁LED2,我们打开串口助手,可以看到输出到LED1的PWM占空比发生变化,开发板上的LED1亮度也会发生变化, 可以跟旁边的电源指示相比较,能看到明显的变化。

图

图43 串口调试输出信息