Zephyr TF-M User Guide

TF-M 概述

Trusted Firmware-M(TF-M)是平台安全架构(PSA)物联网安全框架 的参考实现 。它定义并实现了一个架构和一组软件组件,旨在解决物联网产品中的一些主要安全问题。

此表描述了根文件夹下的结构以及部分可能的文件夹。

文件夹名称

描述

bl1

第一阶段不可变引导加载程序

bl2

基于 MCUBoot 的第二阶段引导加载程序

cmake

编译系统的 CMake 文件

config

配置文件

docs

文档

interface

RoT 服务 API 用于客户端调用

lib

第三方库

platform

平台文件

secure_fw

安全固件

tools

脚本中用于编译的工具

架构概述

下图概述了 Trusted Firmware-M(TF-M)的功能和原理:

../_images/zephyr_tfm_architecture.svg

以下各节简要描述了图中提到的不同概念。

SPE 和 NSPE

TF-M 的关键概念是通过将安全处理环境(SPE,可信世界)和非安全处理环境(NSPE,非可信世界)分离来实现安全性。

  • SPE(安全处理环境):运行在 Cortex-M TrustZone 的 Secure World 中。

    SPE 是 TF-M 的核心运行域,负责管理密钥、加密引擎、可信存储等关键安全资产,并通过 PSA API 向 NSPE 提供安全服务。 SPE 中的代码和数据受硬件隔离保护,NSPE 无法直接访问,确保安全边界的物理隔离。

  • NSPE(非安全处理环境):运行在 TrustZone 的 Non-Secure World 中。

    NSPE 是应用程序和 RTOS(如 Zephyr、FreeRTOS)的运行域,负责处理常规业务逻辑、外设驱动和网络通信等功能。 NSPE 通过 PSA API 调用 SPE 提供的安全服务,无法直接操作安全资源,所有敏感操作必须委托给 SPE 完成。

PRoT 和 ARoT

  • PRoT(Platform Root of Trust)平台信任根,是 PSA 安全架构中最核心的信任基础。

    PRoT 包含 TF-M 的 SPM (Secure Partition Manager) 及平台初始化代码,负责系统启动时的安全配置(SAU/MPC/PPC)、固件验证和安全分区管理。 PRoT 拥有最高权限,运行在最底层隔离级别,是整个系统安全的”信任锚点”,一旦被破坏将导致全局安全失效。

  • ARoT(Application Root of Trust)应用信任根,是运行在 Secure World 中的安全服务分区集合。

    ARoT 包含 PSA Crypto、Trusted Storage、Attestation 等具体安全服务实现,为 NSPE 提供标准化的安全功能接口。 ARoT 受 PRoT 管理和隔离保护,各服务分区之间可配置不同的隔离级别。ARoT 可在不影响 PRoT 的前提下进行更新升级。

Isolation level

  • 隔离等级1:安全和非安全处理环境部分中描述的安全/非安全分离。

  • 隔离等级2:ARoT 服务无法访问 SPE 的其他部分。

  • 隔离等级3:ARoT 服务无法访问 SPE 的其他部分和其他 ARoT 服务。

SPM

FF-M (Firmware-Framework-M)是 Arm 定义的架构规范,定义了安全固件应该如何组织与隔离、模型与调用机制,其中定义的服务访问流程如下:

../_images/zephyr_tfm_service_access.svg

Note

FF-M 指的是两个规范的累积结果: FF-M v1.1 更新FF-M v1.0

SPM(Secure Partition Manager)安全分区管理是符合 FF-M 标准的实现的核心,它建立并维护一个固件框架,该框架遵循 FF-M 管理分区运行时,实现了 Client API 和 Secure Partition API。

每个服务都会公开其服务 ID(SID)和句柄,供客户端访问使用。客户端通过 SID 或句柄,经由 Client API 访问服务。分区在需要操作客户端数据或响应客户端请求时,会使用 Secure Partition API。

分区运行时模型

一个分区必须以以下运行时模型 之一运行:进程间通信(IPC)模型或安全函数(SFN)模型。

仅包含安全函数(SFN)分区的实现更适合资源受限的设备,这种实现称为 SFN 模型实现。而当系统中存在进程间通信(IPC)分区时,则称为 IPC 模型实现。

IPC 模型

运行在进程间通信(IPC)模型下的分区看起来像一个经典进程。分区内只有一个线程,它不断等待信号。服务进程管理(SPM)将客户端 API调用中获取的信息转换为消息, 并向分区发出信号。分区调用信号及其绑定消息所指示的相应服务函数,并将服务返回的结果回复给客户端。该模型的优点:

  • 它通过限制数据交互接口来提供更好的隔离性。数据最好在本地缓冲区中进行处理。

  • 它提供了一种处理多服务访问的机制。MCU 系统中没有内存映射机制,因此,如果服务访问采用基于函数调用的机制,则在服务多线程客户端时很难提供多个函数调用上下文。该模型将多个服务访问转换为消息,并由分区逐个处理消息中的服务访问。

SFN 模型

安全函数(SFN)模型分区类似于一个库。每个服务都以函数入口的形式存在于分区内。SPM 在找到服务后启动目标服务函数。整个过程(从客户端到服务函数)就是一个函数调用。该模型节省了用于进程间通信调度的工作负载。

同时,它放宽了数据交互机制,例如允许直接内存访问(MMIOVEC)。但由于多重调用上下文维护的困难,难以实现多线程客户端服务访问。

PSA 认证的 API

PSA(平台安全架构)是 ARM 推出的物联网设备全栈安全架构标准体系,目标是统一 MCU 安全架构与 API 规范,解决物联网安全碎片化问题。

PSA 体系的核心内容包括三个维度:

  • 安全架构规范:定义基于硬件隔离的物联网安全架构模型,明确安全启动、隔离、认证等核心能力的设计要求。

  • 标准化API接口:定义统一的 PSA Functional API 规范,覆盖密码学、可信存储、设备证明等核心安全服务的接口标准。

  • 安全评估与认证体系:定义分级的 PSA Certified 认证体系,为物联网设备安全能力提供标准化的评估与认证依据。

PSA 认证的 API 通过非安全可调用接口(NSC)向非安全端公开。NSC 可调用接口是一种安全机制,可实现 NSPE 和 SPE 之间的受控通信。 利用此机制,运行在 NSPE 中的应用程序可以与 SPE 中的 RoT 服务进行安全通信。

TF-M 服务

安全服务(简称 Service )是 SPE 中提供安全功能的组件,而客户端则是 Service 的用户。当 Service 访问其依赖的服务时,它充当客户端的角色。 服务被分组到安全分区(也称为分区)中。一个分区:

  • 包含用途相同的服务。

  • 提供实现所需的隔离边界。

  • 是一个软件开发单元。

每个服务都会公开其服务 ID(SID)和句柄,供客户端访问使用。客户端通过 SID 或句柄,经由 Client API 访问服务。 分区在需要操作客户端数据或响应客户端请求时,会使用 Secure Partition API。

受保护存储服务

TF-M 受保护存储(PS)服务是一项 ARoT 服务,实现了 PSA 受保护存储 API。 该服务通常依靠 flash 访问域的硬件隔离,当前版本也依赖硬件将 flash 区域与非安全访问隔离。即使没有硬件隔离,数据的机密性和完整性仍然能够得到保障。

PS 服务采用基于 AES-GCM 的 AEAD (Authenticated Encryption with Associated Data‌,带关联数据的认证加密)加密策略作为参考,以保护数据的完整性和真实性。 PS 重用 TF-M 内部可信存储服务提供的非分层文件系统来存储加密的、经过身份验证的对象。

PS 服务公开以下强制性 PSA PS 接口,版本 1.0:

psa_status_t psa_ps_set(psa_storage_uid_t uid, size_t data_length, const void *p_data, psa_storage_create_flags_t create_flags);
psa_status_t psa_ps_get(psa_storage_uid_t uid, size_t data_offset, size_t data_size, void *p_data, size_t *p_data_length);
psa_status_t psa_ps_get_info(psa_storage_uid_t uid, struct psa_storage_info_t *p_info);
psa_status_t psa_ps_remove(psa_storage_uid_t uid);
uint32_t psa_ps_get_support(void);

这些 PSA PS 接口和 PS TF-M 类型在 interface/include/psa/protected_storage.hinterface/include/psa/storage_common.hinterface/include/tfm_ps_defs.h 中定义和记录,可以在其中查看接口函数详细注释及使用方法。

在下面 zephyr 示例工程 章节的 psa_protected_storage 示例工程中可以查看使用示例,tfm_psa_test 和 tfm_regression_test 两个示例工程的测试源码在测试代码仓库中,也可以在测试代码仓库中查看使用方法。

内部可信存储服务

PSA 内部可信存储(ITS)是一项 PRoT 服务,用于将最关键的安全设备数据(例如加密密钥)存储在内部存储中,该内部存储可确保数据的机密性和真实性。 这与受保护存储形成对比,后者是一项 ARoT 服务,允许将更大的数据集安全地存储在外部 flash 中,并提供加密、身份验证和回滚保护选项,以保护静态数据。

TF-M 内部可信存储(ITS)服务实现了 PSA 内部可信存储 API。 该服务由 flash 访问域的硬件隔离提供支持,并依靠硬件将 flash 区域与非安全处理环境的访问以及更高隔离级别的应用程序信任根隔离。 当前的 ITS 服务设计依赖于 TF-M 提供的硬件抽象。ITS 服务提供了一种非分层存储模型(文件系统),其中所有资产都通过线性索引的元数据列表进行管理。

TF-M ITS 服务公开以下强制性 PSA ITS 接口版本 1.0:

psa_status_t psa_its_set(psa_storage_uid_t uid, size_t data_length, const void *p_data, psa_storage_create_flags_t create_flags);
psa_status_t psa_its_get(psa_storage_uid_t uid, size_t data_offset, size_t data_size, void *p_data, size_t *p_data_length);
psa_status_t psa_its_get_info(psa_storage_uid_t uid, struct psa_storage_info_t *p_info);
psa_status_t psa_its_remove(psa_storage_uid_t uid);

这些 PSA PS 接口和 PS TF-M 类型在 interface/include/psa/storage_common.hinterface/include/psa/internal_trusted_storage.h 中定义和记录,可以在其中查看接口函数详细注释及使用方法。

初始认证服务

初始认证服务是一项 PRoT 服务,允许设备在验证过程中向远程验证实体证明自身的真实性和完整性。该服务基于 PSA 认证规范,生成包含设备唯一标识、固件度量值、安全配置等信息的认证令牌(Attestation Token), 并使用设备私钥签名。验证方通过公钥验证令牌,确认设备未被篡改且运行可信固件,是零信任架构和设备入网认证的核心组件。

下图展示了一个典型的初始证明流程:

../_images/zephyr_tfm_initial_attestation.svg

验证实体(Validation Entity,VE)使用对象记录 VEOR中的元数据向证明端点(Attestation End Point,AEP)发起 challenge,该对象记录的内容和用途取决于所采用的证明方案。为了确保数据的新鲜性,建议在每次 challenge 中都使用一次性随机数(nonce)。 AEP 向初始证明服务(Initial Attestation Service)请求一个初始证明令牌(Initial Attestation Token,IAT),并至少提供在该证明方案下需要由 VE 进行验证的元数据。通常,这些元数据会是对象记录 AEPOR中 AEP 特定数据以及 VEOR的密码散列值(cryptographic hash)。

初始证明服务构造对象记录 IASOR,其内容通常包括:

  • 来自初始启动状态(Initial Boot State)的启动种子(boot seed),以及在安全启动过程中加载的每个可更新组件的启动状态;

  • 系统当前的安全生命周期状态;

  • 实例 ID(Instance ID)和实现 ID(Implementation ID),以及调用分区 ID(calling Partition ID)。

初始证明密钥(Initial Attestation Key,IAK)用于对来自 AEP 和 IAS 的数据的密码散列值进行签名。使用 IAK 完成签名后,结果会被返回给 AEP,这个签名结果就是初始证明令牌(Initial Attestation Token)。

TF-M 初始认证服务公开以下 PSA 接口:

psa_status_t
psa_initial_attest_get_token(const uint8_t *auth_challenge,
                             size_t         challenge_size,
                             uint8_t       *token_buf,
                             size_t         token_buf_size,
                             size_t        *token_size);

psa_status_t
psa_initial_attest_get_token_size(size_t challenge_size,
                                  size_t *token_size);

这些 PSA PS 接口和 PS TF-M 类型在 interface/include/psa/initial_attestation.h.ininterface/include/tfm_attest_defs.h 中定义和记录,可以在其中查看接口函数详细注释及使用方法。

加密服务

TF-M 加密服务是一项 PRoT 服务,在 TF-M 的 PSA RoT 安全分区中实现了 PSA 认证的加密 API。它基于 Mbed TLS 项目,该项目提供了一个 PSA 加密 API 的 C 软件库参考实现。 SPE 中运行的其他服务或 NSPE 中运行的应用程序可以请求该服务,其目的是以安全高效的方式提供加密原语,可以通过软件或将调用路由到平台可能提供的任何底层加密硬件加速器或安全元件来实现。

TF-M Crypto 服务公开了 PSA 接口,这些接口在头文件 interface/include/psa/crypto.h 中进行了详细说明。 该头文件本身又包含其他几个头文件,这些头文件不应由用户应用程序直接包含。

TF-M 配置

TF-M 将在后台自动编译和链接,作为标准 Zephyr 编译过程的一部分。此编译过程对 TF-M 的使用方式做出了一些假设,并对 Zephyr 应用程序固件的功能产生了一定的影响:

  • 首先启动安全处理环境(安全启动和TF-M)。

  • Zephyr 的资源分配取决于安全映像中做出的选择。

要将 TF-M 添加到编译中,需要将 CONFIG_BUILD_WITH_TFM 添加到 prj.conf 文件中来启用配置选项。可以参考 zephyr 示例工程 相关代码。

Note

zephyr 中关于 TF-M 的配置选项,会在 TF-M module 中处理,转化成编译选项传递给 TF-M 安全固件或非安全固件。

kconfig 配置

profile 选择

CONFIG_TFM_PROFILE_TYPE_NOT_SET:

不会自动启用任何预设服务,需要手动显式配置每个安全分区、指定隔离等级和运行时模型。

CONFIG_TFM_PROFILE_TYPE_SMALL:

由轻量级的 TF-M 框架和基本安全服务组成,以保持最小的内存占用,支持资源极其受限的设备上的基本安全功能。

CONFIG_TFM_PROFILE_TYPE_MEDIUM:

通过非对称加密支持,安全地将设备连接到云服务。Profile Medium 的目标设备需要更多资源来支持更多加密算法和更高的隔离级别。

CONFIG_TFM_PROFILE_TYPE_AROTLESS:

仅适用于不支持应用路由(ARoT)服务的设备。该平台仅包含 SPE 中的 PSA RoT 域,因此无需实现 ARoT 和 PSA RoT 之间的隔离。因此,此配置文件选择隔离级别 1,以简化实现并优化内存占用和性能。

CONFIG_TFM_PROFILE_TYPE_LARGE:

使用隔离级别 3 可实现 应用程序 RoT(App RoT)服务之间的额外隔离,选择更多加密算法和密码套件,可以启用针对物理攻击的基本软件防御措施。

Note

如果选择了 CONFIG_TFM_PROFILE_TYPE_NOT_SET,需要手动配置隔离等级、运行时模型和安全分区。

隔离等级

CONFIG_TFM_ISOLATION_LEVEL:

三个隔离等级,可选值:1,2,3。

Note

取值说明:

  • 手动设置所需的 TF-M 隔离级别。可选值为 1、2 或 3;默认值由编译配置决定。

  • 当使用 TF-M Profile 选项时,不允许手动设置隔离级别,因为它由 Profile 设置自动决定。

  • 由于隔离级别 2 和 3 需要 PSA API(TFM IPC)支持,当 TFM IPC 未启用时,将强制使用级别 1。

运行时模型

CONFIG_TFM_IPC:

使用 IPC 模型作为 PSA API 的 SPM 后端。IPC 模型支持 IPC 和 SFN 分区模型,以及隔离级别 1、2 和 3。在此模型中,每个安全分区以任意顺序处理信号,并且可以在继续处理其他信号的同时延迟响应消息。

CONFIG_TFM_SFN:

使用 SFN 模型作为 PSA API 的 SPM 后端。SFN 模型支持 SFN 分区模型,以及隔离级别 1。在此模型中,每个安全分区由一组回调函数组成,这些回调函数实现安全服务。

安全分区

CONFIG_TFM_PARTITION_PROTECTED_STORAGE:

受保护存储服务。

CONFIG_TFM_PARTITION_INTERNAL_TRUSTED_STORAGE:

内部可信存储服务。

CONFIG_TFM_PARTITION_CRYPTO:

加密服务。

CONFIG_TFM_PARTITION_INITIAL_ATTESTATION:

初始认证服务。

CONFIG_TFM_PARTITION_PLATFORM:

平台服务。

CONFIG_TFM_PARTITION_FIRMWARE_UPDATE:

固件更新。

测试相关配置

CONFIG_TFM_USE_NS_APP:

使用 TF-M 的非安全固件。

CONFIG_TFM_REGRESSION_S:

开启 TF-M 安全回归测试。

CONFIG_TFM_REGRESSION_NS:

开启 TF-M 非安全回归测试。

CONFIG_TFM_PSA_TEST_PROTECTED_STORAGE:

开启受保护存储安全分区的 psa api 测试。

CONFIG_TFM_PSA_TEST_INTERNAL_TRUSTED_STORAGE:

开启内部可信存储安全分区的 psa api 测试。

CONFIG_TFM_PSA_TEST_CRYPTO:

开启加密安全分区的 psa api 测试。

CONFIG_TFM_PSA_TEST_INITIAL_ATTESTATION:

开启初始证明服务安全分区的 psa api 测试。

CONFIG_TFM_PSA_TEST_STORAGE:

开启存储相关的 psa api 测试,与 CONFIG_TFM_PSA_TEST_PROTECTED_STORAGE 运行的测试用例相同。

dts 配置

文件 zephyr/dts/arm/realtek/amebag2/amebag2.dtsi 中增加了 sram0_ns 条目,用于存放非安全固件内容,即 zephyr.bin

sram0_ns: memory@20016020 {
    compatible = "mmio-sram";
    reg = <0x20016020 DT_SIZE_K(232)>;
};

变体目标 rtl8721f_evb//ns 编译时使用 zephyr/boards/realtek/rtl8721f_evb/rtl8721f_evb_ns.dts 文件配置,其中 zephyr,sram 使用的是 sram0_ns, 并且其中包含 flash 布局划分,和 flash 布局 部分介绍的 flash 布局是关联的,注意不能冲突。

#include "rtl8721f_evb_common.dts"

/ {
     chosen {
             zephyr,sram = &sram0_ns;
     };
};

&spic {
     status = "okay";
};

/* 4MB flash */
&flash0 {
     reg = <0x04000020 DT_SIZE_M(4)>;

     partitions {
             compatible = "fixed-partitions";
             #address-cells = <1>;
             #size-cells = <1>;

             /* Reserve 80kB for the bootloader */
             boot_partition: partition@0 {
                     label = "bootloader";
                     reg = <0x00000000 0x00014000>;
                     read-only;
             };

             /* Reserve 1968kB for the application in slot 0 */
             slot0_partition: partition@14000 {
                     label = "image-0";
                     reg = <0x00014000 0x001EC000>;
             };

             storage_partition: partition@250000 {
                     label = "storage";
                     reg = <0x00250000 0x00006000>;
             };
             /* tfm PS area       0x256000-0x25b000 */
             /* tfm ITS area      0x25b000-0x25f000 */
             /* tfm OTP/NV area   0x25f000-0x261000 */
     };
 };

flash 布局

flash 布局相关配置主要在文件 modules/tee/tf-m/trusted-firmware-m/platform/ext/target/realtek/rtl8721f_evb/partition/flash_layout.h 中, 主要配置内容如下,配置含义如注释所示。

/* Size of a Secure and of a Non-secure image */
#define FLASH_S_PARTITION_SIZE          (256 * 1024) /* S partition: 256 KB */
#define FLASH_NS_PARTITION_SIZE         (0x80000) /* NS partition: 512 KB */

/* Sector size of the flash hardware; same as FLASH0_SECTOR_SIZE */
#define FLASH_AREA_IMAGE_SECTOR_SIZE    (0x1000)     /* 4 KB */
/* Same as FLASH0_SIZE */
#define FLASH_TOTAL_SIZE                (0x00400000) /* 4 MB */

/* RTL8721F Flash base address */
#define FLASH_BASE_ADDRESS              (0x08000000)

/* Protected Storage (PS) Service definitions */
#define FLASH_PS_AREA_OFFSET            (0x00256000)
#define FLASH_PS_AREA_SIZE              (0x5000)   /* 20 KB */

/* Internal Trusted Storage (ITS) Service definitions */
#define FLASH_ITS_AREA_OFFSET           (FLASH_PS_AREA_OFFSET + \
                                      FLASH_PS_AREA_SIZE)
#define FLASH_ITS_AREA_SIZE             (0x4000)   /* 16 KB */

/* OTP_definitions */
#define FLASH_OTP_NV_COUNTERS_AREA_OFFSET (FLASH_ITS_AREA_OFFSET + \
                                        FLASH_ITS_AREA_SIZE)
#define FLASH_OTP_NV_COUNTERS_AREA_SIZE   (FLASH_AREA_IMAGE_SECTOR_SIZE * 2)
#define FLASH_OTP_NV_COUNTERS_SECTOR_SIZE FLASH_AREA_IMAGE_SECTOR_SIZE

/* RTL8721F (AmebaG2) memory aliases */
#define S_ROM_ALIAS_BASE  (0x10C00020)  /* Secure Flash base */
#define NS_ROM_ALIAS_BASE (0x04000020)  /* Non-Secure Flash base (same physical flash) */

#define S_RAM_ALIAS_BASE  (0x20007000)  /* Secure SRAM base */

#ifndef TFM_NS_REG_TEST
#define NS_RAM_ALIAS_BASE (0x20016020)  /* Non-Secure SRAM base */
#else
#define NS_RAM_ALIAS_BASE (0x2002a020)  /* Non-Secure SRAM base */
#endif

Note

关于上面的 flash 布局的配置,需要注意以下几点:

  • FLASH_TOTAL_SIZEFLASH_BASE_ADDRESS 是根据芯片硬件规格固定配置的,不能修改,其他配置以此为基础,不能超过 flash 的最大容量。

  • FLASH_S_PARTITION_SIZEFLASH_NS_PARTITION_SIZE 是分配给安全固件和非安全固件的空间,空间不够编译时可能会报错。

  • PS、ITS、OTP_NV_COUNTERS 三组配置是给安全服务使用的,注意不能冲突,以免覆盖踩踏。并且配置地址是相对于 flash 起始地址的偏移量。

  • NS_RAM_ALIAS_BASE 地址是使用 tfm_ns 作为非安全固件的场景下,安全固件跳转非安全固件的入口地址,配置错误会导致跳转错误运行报错。由于回归测试时安全固件占用 RAM 较大, NS_RAM_ALIAS_BASE 需要后移, 但正常场景安全固件不会使用这么大空间,因此使用回归测试宏区分,注意由于非安全固件无法获取安全固件是否是回归测试,这里绑定安全固件和非安全固件回归测试一起测试。

TF-M 编译

编译命令

如果支持 TrustZone 并期望 zephyr 运行在非安全环境,则编译时使用带有 */ns 相应变体的开发板目标,*/ns 变体会启用 Cortex-M 安全扩展 (CMSE)。 编译不带 */ns 的开发板目标时,TF-M 未被使用。

Note

zephyr/boards/realtek/rtl8721f_evb/rtl8721f_evb_ns_defconfig 中配置了 CONFIG_TRUSTED_EXECUTION_NONSECURE,在编译 rtl8721f_evb//ns 时使用此配置文件。 编译出的 zephy.bin 作为非安全固件,编译 TF-M 得到的 tfm_s.bin 作为安全固件。

编译命令示例如下:

west build -b rtl8721f_evb//ns zephyr/samples/tfm_integration/tfm_ipc

Note

编译时会调用脚本做后处理,合并 tfm_s.binzephyr.bin 等固件输出一个供烧录使用的 amebagreen2_app.bin,烧录使用的 iamge 在 build/images。 下面中间产物如果有的话,安全固件编译产物 tfm_s.bin 等可以在 build/tfm/bin 查看,安全固件编译产物 tfm_ns.bin 等可以在 build/tfm_ns/bin 查看, zephyr.bin 编译产物在 build/zephyr 查看。

zephyr 示例工程

zephyr中集成了几个示例工程,这些 TF-M 集成示例可以与受支持的 Armv8-M 板一起使用,并演示如何将 TF-M API 与 Zephyr 一起使用。 TF-M 示例与演示工程目录在 zephyr/samples/tfm_integration,其中包含以下几个工程:

  • config_build :测试 ns 变体编译配置

  • tfm_ipc :使用 IPC 实现安全图像和非安全图像之间的通信。

  • psa_crypto :加密使用 PSA Crypto API 进行加密和设备证书签名请求。

  • psa_protected_storage :受保护存储使用受保护存储(PS)API 存储加密数据。

  • tfm_secure_partition :创建一个安全分区,以公开安全服务。

  • tfm_psa_test :测试 psa api,每次只能打开一个安全分区进行测试。测试代码实现在 modules/tee/tf-m/psa-arch-tests

  • tfm_regression_test :TF-M 安全固件和非安全固件回归测试。测试代码实现在 modules/tee/tf-m/tf-m-tests

前面五个示例工程中使用 zephyr 作为非安全固件,最后两个示例工程中使用 tfm_ns 作为非安全固件,虽然也编译了 zephyr,但由于配置了 CONFIG_TFM_USE_NS_APP, 最后会把 tfm_ns 作为非安全固件合并进最终烧录使用的 bin 文件中。下面以 tfm_psa_test 为例介绍示例工程使用方法。

tfm_psa_test

此工程使用 Zephyr 和 TF-M 运行 PSA(Platform Security Architecture,平台安全架构)测试套件。

PSA 测试在 psa-arch-tests 仓库中实现,此示例仅支持在 psa-arch-tests 中有移植的平台,rtl8721f_evb 已移植。

一次只能运行一个测试套件,通过以下 Kconfig 选项之一设置:

  • CONFIG_TFM_PSA_TEST_CRYPTO — 密码学测试

  • CONFIG_TFM_PSA_TEST_PROTECTED_STORAGE — 受保护存储测试

  • CONFIG_TFM_PSA_TEST_INTERNAL_TRUSTED_STORAGE — 内部可信存储测试

  • CONFIG_TFM_PSA_TEST_STORAGE — 通用存储测试

  • CONFIG_TFM_PSA_TEST_INITIAL_ATTESTATION — 初始认证测试

CONFIG_TFM_PSA_TEST_STORAGE 为例,可以在 prj.conf 中配置 CONFIG_TFM_PSA_TEST_STORAGE=y,也可以在编译命令中添加编译选项如下:

west build -b rtl8721f_evb//ns zephyr/samples/tfm_integration/tfm_psa_test/ -- -DCONFIG_TFM_PSA_TEST_STORAGE=y

示例输出

10:18:56.394  [BOOT-I] ROM:[V1.0]
10:18:56.396  [BOOT-I] FLASH RATE:1, Pinmux:0
10:18:56.398  [BOOT-I] BOOT FROM NOR
10:18:56.398  [BOOT-I] Boot from Flash
10:18:56.399  [BOOT-I] IMG1(OTA1) Version: 1.1
10:18:56.399  [BOOT-I] IMG1 ENTRY [104005ad:0]
10:18:56.399  [BOOT-I] AP BOOT REASON 0:
10:18:56.399  Initial Power on
10:18:56.399  [BOOT-I] IMG1 ENTER MSP:[30000fc0]
10:18:56.399  [BOOT-I] Build Time: Mar 13 2026 11:46:15
10:18:56.399  [BOOT-I] PMC_CORE_ROLE: KM4NS
10:18:56.399  [PSRAM-I] PSRAM CLK: 400MHz, DQ16, Size: 16MB
10:18:56.399  [BOOT-I] Init APM PSRAM
10:18:56.399  [PSRAM-I] Cal win size 21
10:18:56.399  [PSRAM-I] Cal win size 20
10:18:56.399  [FLASH-I] Flash ID: 85-20-18 (Capacity: 128M-bit)
10:18:56.414  [FLASH-I] Flash Read 4IO
10:18:56.415  [FLASH-I] FLASH CLK: 100000000 Hz
10:18:56.415  [FLASH-I] FLASH HandShake OK
10:18:56.415  [BOOT-I] NP XIP IMG[02000000:33100]
10:18:56.417  [BOOT-I] NP SRAM[20068000:2800]
10:18:56.417  [BOOT-I] NP PSRAM[02035900:20]
10:18:56.417  [BOOT-I] AP XIP IMG[04000000:9ca0]
10:18:56.419  [BOOT-I] AP SRAM[20027000:680]
10:18:56.419  [BOOT-I] AP PSRAM[0400a320:20]
10:18:56.421  [BOOT-I] AP IMG3[10c00000:1a760]
10:18:56.421  [BOOT-I] AP NSC[20006000:23c0]
10:18:56.421  [BOOT-W] IMG3 not encrypted! Enable RDP for MP!
10:18:56.421  [BOOT-W] IMG3 not encrypted! Enable RDP for MP!
10:18:56.423  [BOOT-W] IMG3 not encrypted! Enable RDP for MP!
10:18:56.423  [BOOT-W] IMG3 not encrypted! Enable RDP for MP!
10:18:56.423  [BOOT-W] IMG3 not encrypted! Enable RDP for MP!
10:18:56.425  [BOOT-I] IMG2 BOOT from OTA 1, Version: 1.1
10:18:56.425  [CHIPINFO-W] PSRAM or DRAM End in layout is 0x60400000, but actually is 0x61000000
10:18:56.425  set MPC using actually value
10:18:56.427  [BOOT-I] Start NonSecure @ 0x4cc9b3bb ...
10:18:56.427  [APP-I] NP CPU CLK: 240000000 Hz
10:18:56.427  Booting TF-M v2.2.0+g47f6dda
10:18:56.429  [WRN] This device was provisioned with dummy keys. This device is NOT SECURE
10:18:56.429  [MAIN-I] IWDG refresh thread Started!
10:18:56.429  [Sec Thread] Secure image initializing!
10:18:56.430  [MAIN-I] NP OS START
10:18:56.434  [INF][PS] Encryption alg: 0x5500100
10:18:56.434  [INF][Crypto] Provision entropy seed...
10:18:56.434  [INF][Crypto] Provision entropy seed... complete.
10:18:56.489  Non-Secure system starting...
10:18:56.493  ***** PSA Architecture Test Suite - Version 1.4 *****
10:18:56.493
10:18:56.493  Running.. Storage Suite
10:18:56.493  ******************************************
10:18:56.493
10:18:56.493  TEST: 401 | DESCRIPTION: UID not found check | UT: STORAGE
10:18:56.493  [Info] Executing tests from non-secure
10:18:56.502
10:18:56.502  [Info] Executing ITS tests
10:18:56.502  [Check 1] Call get API for UID 6 which is not set
10:18:56.502  [Check 2] Call get_info API for UID 6 which is not set
10:18:56.502  [Check 3] Call remove API for UID 6 which is not set
10:18:56.621  [Check 4] Call get API for UID 6 which is removed
10:18:56.621  [Check 5] Call get_info API for UID 6 which is removed
10:18:56.623  [Check 6] Call remove API for UID 6 which is removed
10:18:56.623  Set storage for UID 6
10:18:56.662  [Check 7] Call get API for different UID 5
10:18:56.662  [Check 8] Call get_info API for different UID 5
10:18:56.662  [Check 9] Call remove API for different UID 5
10:18:56.744
10:18:56.746  [Info] Executing PS tests
10:18:56.747  [Check 1] Call get API for UID 6 which is not set
10:18:56.747  [Check 2] Call get_info API for UID 6 which is not set
10:18:56.747  [Check 3] Call remove API for UID 6 which is not set
10:18:57.951  [Check 4] Call get API for UID 6 which is removed
10:18:57.964  [Check 5] Call get_info API for UID 6 which is removed
10:18:57.964  [Check 6] Call remove API for UID 6 which is removed
10:18:57.967  Set storage for UID 6
10:18:58.364  [Check 7] Call get API for different UID 5
10:18:58.364  [Check 8] Call get_info API for different UID 5
10:18:58.364  [Check 9] Call remove API for different UID 5
10:18:59.175
10:18:59.175  TEST RESULT: PASSED
10:18:59.175
10:18:59.175  ******************************************
...
10:19:21.898  TEST: 417 | DESCRIPTION: Storage assest capacity modification check | UT: STORAGE
10:19:21.898  [Info] Executing tests from non-secure
10:19:21.901
10:19:21.901  [Info] Executing PS tests
10:19:21.905  Test Case skipped as Optional PS APIs not are supported.
10:19:21.905
10:19:21.905  TEST RESULT: SKIPPED (Skip Code=0x0000002B)
10:19:21.905
10:19:21.905  ******************************************
10:19:21.914
10:19:21.916  ************ Storage Suite Report **********
10:19:21.916  TOTAL TESTS     : 17
10:19:21.921  TOTAL PASSED    : 11
10:19:21.923  TOTAL SIM ERROR : 0
10:19:21.925  TOTAL FAILED    : 0
10:19:21.927  TOTAL SKIPPED   : 6
10:19:21.929  ******************************************
10:19:21.931
10:19:21.935  Entering standby..

Note

  • tfm_psa_test 示例工程中的 prj.conf 中虽然设置了 CONFIG_TFM_PROFILE_TYPE_LARGE=y,但 CMakeLists.txt 又添加了 -DTFM_ISOLATION_LEVEL=2 编译选项,实际上安全固件还是运行在隔离等级 2 的。

  • tfm_psa_test 和 tfm_regression_test 示例工程在 CMakeLists.txt 的最后都编译了 tfm_ns,需要使用 HAL_REALTEK,编译时会自动下载,但下载的仓库内容可能不是最新。也可以通过添加 -DHAL_REALTEK_PATH=${ZEPHYR_HAL_REALTEK_MODULE_DIR} 编译选项使用已有路径。

  • tfm_regression_test 示例工程中安全回归测试和非安全回归测试要同时打开,原因是回归测试中占用 sram 较大,为其提供了一种单独的 flash 布局。

  • tfm_regression_test 示例工程中不开启 TFM_BL2 的情况下需要在工程目录下的 CMakeLists.txt 中去掉 -DQCBOR_PATH${QCBOR_PATH_TYPE}=${CONFIG_TFM_QCBOR_PATH} 才能自动下载 qcbor,否则传入空值不会下载。TFM_QCBOR_PATHTFM_BL2 包含,不开启 TFM_BL2,配置不生效,TFM_QCBOR_PATH 值为空。

  • tfm_ipc 示例工程配置了 CONFIG_TFM_PROFILE_TYPE_NOT_SET=yCONFIG_TFM_IPC=y 会按隔离等级 1 运行。需要配置 CONFIG_TFM_PROFILE_TYPE_MEDIUM=yCONFIG_TFM_ISOLATION_LEVEL=2 才能跑在隔离等级 2。

  • ITSPS 安全分区服务会使用 flash,相关测试可能需要先擦除 flash,以免有残留的值影响运行过程中的使用。

添加安全分区

安全分区是一个执行环境,它为信任根(RoT)服务提供以下功能:

  • 获取资源、保护自身代码和数据。

  • 与系统中其他组件交互的机制。

每个安全分区都是一个独立的执行线程,也是最小的隔离单元。

本章节主要介绍如何在 TF-M 中添加安全分区,重点在于配置、清单和实现规则。实际的源代码级实现不包含在本文档中。

过程

添加安全分区的主要步骤如下:

添加源文件夹

<TF-M base folder>/secure_fw/partitions 目录下为新安全分区添加一个源文件夹(假设文件夹名称为 example ):

此文件夹应包含以下部分:

  • 清单文件

  • CMake 配置文件

  • 源代码文件

添加清单

每个安全分区都必须在清单文件中声明资源需求。安全分区管理器(SPM)使用清单文件在安全分区(SPE)内组装和分配资源。清单文件包含以下内容:

  • 安全分区名称

  • 已实现的 RoT 服务列表

  • 访问其他 RoT 服务

  • 内存需求

  • 调度提示

  • 外围存储器映射 I/O 区域和中断

Note

用户可以在安全分区清单中设置 priority 属性为 LOW、NORMAL 或 HIGH。

清单工具 tools/tfm_parse_manifest_list.py 会根据安全分区的 priority 值及其依赖关系计算其加载优先级。在 TF-M 初始化期间,SPM 会根据安全分区的加载优先级值确定其加载和初始化顺序。

  • priority 较高的安全分区会在 priority 的安全分区之前加载和初始化。

  • 安全分区是在其依赖项加载和初始化之后才加载和初始化的。

以下是 IPC 模型的清单参考示例:

{
  "psa_framework_version": 1.1,
  "name": "TFM_SP_EXAMPLE",
  "type": "APPLICATION-ROT",
  "priority": "NORMAL",
  "model": "IPC",
  "entry_point": "tfm_example_main",
  "stack_size": "0x0200",
  "services" : [
    {
      "name": "ROT_A",
      "sid": "0x000000E0",
      "non_secure_clients": true,
      "connection_based": true,
      "version": 1,
      "version_policy": "STRICT"
      "mm_iovec": "disable"
    }
  ],
  "mmio_regions": [
    {
      "name": "TFM_PERIPHERAL_A",
      "permission": "READ-WRITE"
    }
  ],
  "irqs": [
    {
      "source": "TFM_A_IRQ",
      "name": "A_IRQ",
      "handling": "SLIH"
    }
  ]
  "dependencies": [
    "TFM_CRYPTO",
    "TFM_INTERNAL_TRUSTED_STORAGE_SERVICE"
  ]
}

Note

要使用 SFN 模型,用户需要将 "model": "IPC" 替换为 "model": "SFN"。用户还需要删除 "entry_point" 属性 ,并可选择将其替换为 "entry_init"

更新清单列表

<TF-M base folder>/tools/tfm_manifest_list.yamltools/tfm_parse_manifest_list.py` 用于收集安全分区所需的必要信息。清单工具会处理这些信息,并在编译过程中生成必要的文件。

参考配置示例:

{
  "description": "TFM Example Partition",
  "manifest": "secure_fw/partitions/example/tfm_example_partition.yaml",
  "conditional": "@TFM_PARTITION_EXAMPLE@",
  "output_path": "partitions/example",
  "version_major": 0,
  "version_minor": 1,
  "pid": 290,
  "linker_pattern": {
    "library_list": [
      "*tfm_*partition_example*"
     ]
  }
}

TF-M 还支持外部安全分区编译,可以在其中拥有自己的清单列表。详细信息请参阅 树外安全分区编译

安全分区 ID 分发

每个安全分区都有一个标识符(ID)。TF-M 将生成一个包含安全分区 ID 定义的头文件。该头文件位于 <TF-M build folder>generated/interface/include/psa_manifest/pid.h。 每个定义都使用清单中的 name 属性作为其名称,其值由 SPM 分配。

分区 ID 可以设置为固定值,也可以省略以自动分配。

#define name id-value

安全分区

PID 范围

TF-M 内部分区

0 - 255

PSA 和用户分区

256 - 2999

TF-M 测试分区

3000 - 4999

固件框架测试分区

5000 - 5999

保留的

6000 -

请参考 <TF-M base folder>/tools/tfm_manifest_list.yaml“<TF-M extras repo>/partitions/*/*_manifest_list.yaml”<TF-M test repo>/tests_reg/test/secure_fw/tfm_test_manifest_list.yaml<TF-M test repo>/tests_psa_arch/spe/tfm_psa_ff_test_manifest_list.yaml 文件, 以获取详细的 PID 分配信息。

关于在哪里添加定义,请参阅 更新清单列表 章节。

RoT 服务 ID(SID)分发

RoT 服务通过其 RoT 服务 ID(SID)进行标识。SID 是一个 32 位数字,与安全分区清单中的一个符号名称相关联。第 31 位到第 12 位唯一标识 RoT 服务的供应商。 其余第 11 位到第 0 位可由供应商自行决定如何使用。

以下是 TF-M 中使用的 RoT 服务 ID 表。

安全分区

供应商 ID(20 位)

功能 ID(12 位)

initial_attestation

0x00000

0x020-0x03F

platform

0x00000

0x040-0x05F

protected_storage

0x00000

0x060-0x06F

internal_trusted_storage

0x00000

0x070-0x07F

crypto

0x00000

0x080-0x09F

firmware_update

0x00000

0x0A0-0x0BF

tfm_secure_client

0x0000F

0x000-0x01F

tfm_ipc_client

0x0000F

0x060-0x07F

tfm_ipc_service

0x0000F

0x080-0x09F

tfm_slih_test_service

0x0000F

0x0A0-0x0AF

tfm_flih_test_service

0x0000F

0x0B0-0x0BF

tfm_ps_test_service

0x0000F

0x0C0-0x0DF

tfm_secure_client_2

0x0000F

0x0E0-0x0FF

tfm_sfn_test_service_1

0x0000F

0x100-0x11F

tfm_sfn_test_service_2

0x0000F

0x120-0x13F

tfm_attest_test_service

0x0000F

0x140-0x15F

RoT 服务无状态句柄分发

安全分区可以包含无状态服务。这些服务通过无状态句柄进行区分和引用。在清单文件中,会设置一个 stateless_handle 属性来索引无状态服务。在当前实现中, 该属性必须是 "auto" 或 [1, 32] 范围内的数字,并且可以扩展。此外,对于无状态服务, connection-based 属性必须设置为 false

无状态句柄的索引分为两个范围,用于不同的用途。索引 [1, 16] 分配给 TF-M 安全分区。其余索引 [17, 32] 保留给任何其他安全分区,例如 tf-m-teststf-m-extras 中的安全分区。

下表总结了 TF-M 安全分区的无状态句柄分配。

分区名称

无状态句柄

TFM_SP_CRYPTO

1

TFM_SP_PS

2

TFM_SP_ITS

3

TFM_SP_INITIAL_ATTESTATION

4

TFM_SP_FWU

5

TFM_SP_PLATFORM

6

stack_size

stack_size 属性用于指示安全分区的堆栈内存使用情况。该属性的值必须是以字节为单位的十进制或十六进制值。它也可以是一个编译可配置项, 其默认值在配置中定义 config_base.cmake。可以覆盖此配置值以适应不同的使用场景。

heap_size

此属性为可选属性,默认值为 0。它表示安全分区的堆内存使用情况。允许的值与 stack_size 相同.

mmio_regions

此属性是安全分区需要访问的 MMIO 区域对象列表。TF-M 当前仅支持 named_region。用户需要提供一个名称宏来指示内存区域的变量。

TF-M 使用以下结构来表示外围存储器。

struct platform_data_t {
  uint32_t periph_start;
  uint32_t periph_limit;
  int16_t periph_ppc_bank;
  int16_t periph_ppc_loc;
};

Note

TF-M 协议并不期望使用这种结构,这只是当前实现所采用的。其他需要不同信息来实现隔离的外设需要定义一个同名但不同的结构。

以下是一个例子:

struct platform_data_t tfm_peripheral_A;
#define TFM_PERIPHERAL_A                 (&tfm_peripheral_A)

mm_iovec

内存映射 IOVEC(MM-IOVEC)提供客户端输入输出向量到安全分区的直接映射。如果此属性设置为 enable,则当框架支持 MM-IOVEC 时,安全分区可以使用 MM-IOVEC API。

使用 MM-IOVEC 可以针对更大的缓冲区提供内存和运行时优化,但会降低对常见安全漏洞的缓解能力。是否使用 MM-IOVEC 取决于内存和运行时优化以及安全性方面的要求。

更新编译系统

为了适应新添加的安全分区,需要对编译系统进行以下更改。

添加 CMakeLists.txt 文件

每个安全分区都必须有一个对应的 CMakeLists.txt,在本例中是 <TF-M base folder>/secure_fw/partitions/example/CMakeLists.txt,它是该安全分区的编译配置。

以下是 CMakeLists.txt 的参考示例

CMake 文件应包含以下内容

  • 添加库文件 tfm_app_rot_partition_example 和相关源文件。

    add_library(tfm_app_rot_partition_example STATIC)
    
    target_sources(tfm_app_rot_partition_example
        PRIVATE
            tfm_example_partition.c
    )
    

    Note

    安全分区必须编译为独立的静态库,并且库的名称必须遵循以下模式,因为它会影响链接器脚本如何在内存中放置分区:

    • 在 PSA RoT 分区的情况下使用 tfm_psa_rot_partition*

    • 在 ARoT 分区的情况下使用 tfm_app_rot_partition*

  • 添加清单工具生成的源文件。

    # The intermedia file defines the partition stack.
    target_sources(tfm_app_rot_partition_example
        PRIVATE
            ${CMAKE_BINARY_DIR}/generated/example_partition/auto_generated/intermedia_tfm_example_partition.c
    )
    
    # The load info file includes the static data of the partition.
    target_sources(tfm_partitions
        INTERFACE
            ${CMAKE_BINARY_DIR}/generated/example_partition/auto_generated/load_info_tfm_example_partition.c
    )
    
  • 使用清单工具添加依赖项。

    为确保在编译安全分区库时上述生成的文件是最新的,应设置库和清单工具目标之间的依赖关系。

    add_dependencies(tfm_app_rot_partition_example manifest_tool)
    
  • 为 PSA API 链接 tfm_sprt 接口。

    target_link_libraries(tfm_app_rot_partition_example
        PRIVATE
            tfm_sprt
    )
    
  • 将安全分区库链接到 tfm_partitions 以便于被包含在最终映像中。

    target_link_libraries(tfm_partitions
        INTERFACE
            tfm_app_rot_partition_example
    )
    

最后,应将此安全分区的编译添加到 <TF-M base folder>/secure_fw/partitions/CMakeLists.txt

add_subdirectory(example)

更新配置系统

如果安全分区具有启用或禁用它的编译配置,则应将该配置选项添加到配置系统中。

CMake 配置

应该将配置选项的默认值添加到 <TF-M base folder>/config/config_base.cmake

set(TFM_PARTITION_EXAMPLE  OFF  CACHE BOOL  "Enable the example partition")

Kconfig 配置

应该添加一个 menuconfig<TF-M base folder>/secure_fw/partitions/example/Kconfig

menuconfig TFM_PARTITION_EXAMPLE
    bool "Enable the Example Partition"
    default n

并将其添加到 <TF-M base folder>/secure_fw/partitions/Kconfig

rsource ``example/Kconfig``

Note

如果未启用安全分区,则应跳过安全分区编译过程。这可以通过在其 CMakeLists.txt 开头添加以下代码来实现。

if (NOT TFM_PARTITION_EXAMPLE)
    return()
endif()

实现 RoT 服务

要实现 RoT 服务,分区需要一个源文件,其中包含服务的实现以及分区入口点。用户可以在 <TF-M base folder>/secure_fw/partitions/example/tfm_example_partition.c 目录下创建此源文件。

下面的例子中将实现一个带 SID 的 RoT 服务 ROT_A。

IPC 模型分区入口点

在安全分区初始化之后,该函数必须有一个循环,该循环会反复等待输入信号,然后处理这些信号。

#include "psa_manifest/tfm_example.h"
#include "psa/service.h"

void tfm_example_main(void)
{
    psa_signal_t signals = 0;

    /* Secure Partition initialization */
    example_init();

    /*
     * Continually wait for one or more of the partition's RoT Service or
     * interrupt signals to be asserted and then handle the asserted
     * signal(s).
     */
    while (1) {
        signals = psa_wait(PSA_WAIT_ANY, PSA_BLOCK);
        if (signals & ROT_A_SIGNAL) {
            rot_A();
        } else {
            /* Should not come here */
            psa_panic();
        }
    }
}

SFN 模型分区的入口初始化

在 SFN 模型中,安全分区包含一个可选的初始化函数,该函数声明为 添加清单 部分中提到的 entry_init``符号。初始化后, ``entry_init 函数返回以下值:

  • 如果初始化成功则返回 PSA_SUCCESS

  • 如果初始化部分成功,并且希望某些 SFN 接收消息,则返回 PSA_SUCCESS。非运行状态的 RoT 服务必须使用 PSA_ERROR_CONNECTION_REFUSED 响应连接请求 。

  • 如果初始化失败,则返回错误状态,并且不得调用安全分区内的任何 SFN。

IPC 模型的服务实现

该服务由一个函数实现 rot_A(),该函数会在收到传入信号时被调用。具体的实现方式由用户自行决定,但我们提供了一个示例服务供参考。 以下示例在被调用时会发送消息“Hello World”。

#include "psa_manifest/tfm_example.h"
#include "psa/service.h"

/* Some other type of services. */
#define SOME_ROT_A_SERVICE_TYPE                (1)

static void rot_A(void)
{
    const int BUFFER_LEN = 32;
    psa_msg_t msg;
    int i;
    uint8_t rec_buf[BUFFER_LEN];
    uint8_t send_buf[BUFFER_LEN] = "Hello World";

    psa_get(ROT_A_SIGNAL, &msg);
    switch (msg.type) {
    case PSA_IPC_CONNECT:
    case PSA_IPC_DISCONNECT:
        /*
         * This service does not require any setup or teardown on connect
         * or disconnect, so just reply with success.
         */
        psa_reply(msg.handle, PSA_SUCCESS);
        break;
    default:
        /* Handling services requested by psa_call. */
        if (msg.type == PSA_IPC_CALL) {
            for (i = 0; i < PSA_MAX_IOVEC; i++) {
                if (msg.in_size[i] != 0) {
                    psa_read(msg.handle, i, rec_buf, BUFFER_LEN);
                }
                if (msg.out_size[i] != 0) {
                    psa_write(msg.handle, i, send_buf, BUFFER_LEN);
                }
            }
            psa_reply(msg.handle, PSA_SUCCESS);
        } else if (msg.type == SOME_ROT_A_SERVICE_TYPE) {
            /* Operations for SOME_ROT_A_SERVICE_TYPE */
        } else {
            /* Invalid type for this Secure Partition. */
            return PSA_ERROR_PROGRAMMER_ERROR;
        }
    }
}

SFN 模型的服务实现

SFN 模型由一组安全函数(SFN)组成,每个 RoT 服务对应一个 SFN。连接、断开连接和请求消息不会导致 SFN 安全分区发出安全分区信号。 相反,RoT 服务对应的安全函数(SFN)由框架调用,消息详情作为参数传递给该 SFN。要为每个 RoT 服务添加一个用于处理消息的安全函数(SFN),每个 SFN 都将具有以下原型。

psa_status_t <<name>>_sfn(const psa_msg_t *msg);

其中包含一个基于连接的示例服务供参考,该服务在被调用时会发送消息“Hello World”。

#include "psa_manifest/tfm_example.h"
#include "psa/service.h"

/* Some other type of services. */
#define SOME_ROT_A_SERVICE_TYPE                (1)

psa_status_t rot_a_sfn(const psa_msg_t *msg)
{
    const int BUFFER_LEN = 32;
    int i;
    uint8_t rec_buf[BUFFER_LEN];
    uint8_t send_buf[BUFFER_LEN] = "Hello World";

    switch (msg->type) {
    case PSA_IPC_CONNECT:
    case PSA_IPC_DISCONNECT:
        /*
         * This service does not require any setup or teardown on connect
         * or disconnect, so just reply with success.
         */
        return PSA_SUCCESS;
    default:
        /* Handling services requested by psa_call. */
        if (msg->type == PSA_IPC_CALL) {
            for (i = 0; i < PSA_MAX_IOVEC; i++) {
                if (msg->in_size[i] != 0) {
                    psa_read(msg->handle, i, rec_buf, BUFFER_LEN);
                }
                if (msg.->out_size[i] != 0) {
                    psa_write(msg->handle, i, send_buf, BUFFER_LEN);
                }
            }
            return PSA_SUCCESS;
        } else if (msg->type == SOME_ROT_A_SERVICE_TYPE) {
            /* Operations for SOME_ROT_A_SERVICE_TYPE */
        } else {
            /* Invalid type for this Secure Partition. */
            return PSA_ERROR_PROGRAMMER_ERROR;
        }
    }
}

树外安全分区编译

TF-M 支持编译外部安全分区,其源代码文件夹维护在 TF-M 代码库之外。开发人员可以在编译命令行中配置 TFM_EXTRA_MANIFEST_LIST_FILESTFM_EXTRA_PARTITION_PATHS 以包含外部安全分区。

  • TFM_EXTRA_MANIFEST_LIST_FILES

    外部安全分区提供的清单列表的绝对路径列表。 多个清单列表之间用分号 ; 分隔。多个清单列表用双引号括起来。

  • TFM_EXTRA_PARTITION_PATHS

    外部安全分区源代码文件夹的绝对目录列表。TF-M 编译系统会在分区源代码文件夹中搜索 CMakeLists.txt。多个外部安全分区目录之间用分号 ; 分隔。多个目录之间用双引号括起来。

单个外部安全分区文件夹可以按如下方式组织。

secure partition folder
      ├── CMakeLists.txt
      ├── manifest_list.yaml
      ├── out_of_tree_partition_manifest.yaml
      └── source code

在上面的示例中,编译命令中的 TFM_EXTRA_MANIFEST_LIST_FILESTFM_EXTRA_PARTITION_PATHS 按如下方式配置。

-DTFM_EXTRA_MANIFEST_LIST_FILES=<Absolute-path-sp-folder/manifest_list.yaml>
-DTFM_EXTRA_PARTITION_PATHS=<Absolute-path-sp-folder>

多个外部安全分区可以采用不同的组织结构。例如,多个安全分区可以维护在同一目录下,如下所示。

top-level folder
      ├── Partition 1
      │       ├── CMakeLists.txt
      │       ├── partition_1_manifest.yaml
      │       └── source code
      ├── Partition 2
      │       └── ...
      ├── Partition 3
      │       └── ...
      ├── manifest_list.yaml
      └── Root CMakeLists.txt

在上面的示例中,根 CMakeLists.txt 文件包含了所有分区的 CMakeLists.txt 文件,例如通过 add_subdirectory()manifest_list.yaml 列出了所有分区的清单文件。 编译命令行中 TFM_EXTRA_MANIFEST_LIST_FILESTFM_EXTRA_PARTITION_PATHS 配置如下所示。

-DTFM_EXTRA_MANIFEST_LIST_FILES=<Absolute-path-top-level-folder/manifest_list.yaml>
-DTFM_EXTRA_PARTITION_PATHS=<Absolute-path-top-level-folder>

或者,可以将树外安全分区放在不同的文件夹中。

partition 1 folder                    partition 2 folder
    ├── CMakeLists.txt                    ├── CMakeLists.txt
    ├── manifest_list.yaml                ├── manifest_list.yaml
    ├── partition_1_manifest.yaml         ├── partition_2_manifest.yaml
    └── source code                       └── source code

在上面的示例中,每个安全分区都管理着自己的清单文件和 CMakeLists.txt 文件,编译命令行中的 TFM_EXTRA_MANIFEST_LIST_FILESTFM_EXTRA_PARTITION_PATHS 可以按如下配置。请注意,这些输入内容需要用双引号括起来。

-DTFM_EXTRA_MANIFEST_LIST_FILES="<Absolute-path-part-1-folder/manifest_list.yaml>;<Absolute-path-part-2-folder/manifest_list.yaml>"
-DTFM_EXTRA_PARTITION_PATHS="<Absolute-path-part-1-folder>;<Absolute-path-part-2-folder>"

Note

TFM_EXTRA_MANIFEST_LIST_FILES 路径中的清单列表不必与 TFM_EXTRA_PARTITION_PATHS 中的安全分区目录一一对应。顺序也无关紧要。

TFM_EXTRA_MANIFEST_LIST_FILESTFM_EXTRA_PARTITION_PATHS 可以在多个额外的源文件中进行配置。建议使用 CMake 列表 APPEND 方法以避免意外覆盖。

补充说明

  • 在进程间通信(IPC)模型中,使用 PSA FF 提出的内存访问机制。SPM 提供 API 和隔离边界检查,对内存的自由访问可能导致程序崩溃。

  • 在进程间通信(IPC)模型中,分区运行时内部的内存检查是不必要的。SPM 会在调用内存访问 API 时处理内存检查。

  • 在 IPC 模型中,客户端 ID 已包含在消息结构中,安全分区可以通过调用 psa_get() 函数获取该 ID。因此,安全分区不再需要手动调用 tfm_core_get_caller_client_id() 获取调用方客户端 ID。

  • 在进程间通信(IPC)模型中,安全策略管理(SPM)会检查客户端和服务之间的安全策略和分区依赖关系。因此,服务不再需要验证安全调用者的身份。