找回密码
立即注册
搜索
发回帖 发新帖

6599

积分

0

好友

837

主题
发表于 12 小时前 | 查看: 0| 回复: 0

本文以瑞萨 RZ/T2H 平台为例,介绍基于 OP-TEE(Open Portable Trusted Execution Environment)的 OTP(One-Time Programmable Memory)访问方案实现思路。通过 OP-TEE 框架,Linux 用户空间应用可以安全地读取或写入 OTP 中的数据,用于存放存储设备密钥、产品序列号、安全配置参数等敏感信息,而无需将底层 OTP 控制逻辑暴露给普通 Linux 环境。

同时,本文也会概要梳理 OP-TEE 的基本架构,以及 TrustZone、CA(Client Application)、TA(Trusted Application)和 OTP 驱动之间的协作关系,帮助开发人员从整体上理解软件架构与代码实现路径。

什么是 OP-TEE?

OP-TEE 是 ARM TrustZone 技术上的开源 TEE(Trusted Execution Environment)实现。TrustZone 将系统划分为两个相互隔离的执行世界:

  • Normal World(普通世界)
  • Secure World(安全世界)

CPU 通过 NS(Non-Secure bit)和 SMC(Secure Monitor Call)指令在两个世界之间切换。在 OP-TEE 架构中,应用通常由两部分组成:CA(Client Application)与 TA(Trusted Application)。

RZ/T2H 采用 ARM Cortex-A55 多核架构,并支持 ARM TrustZone 安全扩展。借助TrustZone 提供的硬件隔离机制,OP-TEE OS 可以运行在 Secure World 中,与 Linux 系统形成物理隔离,从而为敏感数据和安全功能提供可信运行环境。

1 可信应用程序(TA)和客户端应用程序(CA)

OP-TEE 的 TA/CA 采用遵循 GlobalPlatform TEE API 的客户端-服务器模型。客户端应用程序(CA)使用 TEE Client API 调用安全世界中的可信应用程序(TA)服务;可信应用程序(TA)实现的是 TEE Internal Core API Specification 中定义的功能。

OP-TEE CA与TA交互架构图:Normal World与Secure World通过Shared memory和SMC call通信

上图展示了整体调用流程:

  1. 客户端应用程序(CA)通过调用 TEE Client API 中的函数,向可信应用程序(TA)发送请求。
  2. TEE Client API 库将该请求路由到 OP-TEE Linux Kernel Driver。
  3. OP-TEE Linux Driver 再将客户端应用程序的请求路由到 Arm Trusted Firmware(ATF)。
  4. OP-TEE OS 框架判断应由哪个可信应用程序(TA)来处理该请求。
  5. OP-TEE OS 框架将控制权传递给对应的 TA,由其处理请求。
  6. 请求处理完成后,执行控制返回给客户端应用程序,客户端应用程序收到返回值以及处理后的数据。

2 PTA 和 UTA 的区别

伪可信应用程序(Pseudo Trusted Application,PTA):

  • 内置在 TEE 操作系统中的高权限安全服务。
  • PTA 不是独立的 .ta 文件,而是注册在 OP-TEE Core 中的内建服务。
  • 适合封装只有 Secure World 才能访问的寄存器、安全外设或底层平台能力。

用户模式可信应用程序(User Mode Trusted Application,UTA),是运行在 TEE 中、受内核保护的安全 App:

  • UTA 是标准意义上的用户模式 TA,在 OP-TEE 的 Secure World 用户态运行。
  • UTA 更适合承载客户业务逻辑,因为可以独立开发、签名和部署,并具有更好的隔离性。

OTP 读写功能需要直接访问 OP-TEE Core 内部接口和安全硬件驱动,并作为其他 TA 共用的底层安全服务,因此本方案采用 PTA。如下图所示,OTP 组件与 FLASH、SPI、SCE 的驱动处于同一层级。

OP-TEE系统架构图:Secure World内的OP-TEE OS包含OTP、Flash、SCE等Pseudo TAs

在 RZ/T2H 的 YOCTO 编译路径 /build/tmp/work/rzt2h_dev-poky-linux/ 下,optee-ta-sample 不是通过 git 管理的,而是解压配方中的 tar.gz 文件,当前与配方一起发布。optee-example 提供的每一个样例工程都包含两个子目录 host 和 ta,它们分别是 Host Application(CA)和 UTA。optee-example 主要是关于 hello_world 以及加密调用相关的例程。

3 OTP CA 的实现

基于以上两点,我们在配方中(路径为 /meta-rz-features/meta-rz-security/recipes-bsp/optee/files/optee-ta-sample)增加 otp:

optee-ta-sample项目目录结构:包含flash、otp、sce等目录

该 otp 路径下包含 otp.c、pta_otp.h 及对应的 makefile。otp.c 的目的是作为 OTP CA 例子,在这里注入参数,调用 TEEC_InitializeContext、TEEC_OpenSession、TEEC_InvokeCommand、TEEC_CloseSession 等函数,通过 OTP_UUID 找到 OPTEE-OS 下的对应接口。UUID 可以在 uuidgenerator.net 生成。

如前面所述,由于 optee-ta-sample 不是通过 git 管理,而是放在配方文件里,所以整体流程是:先解压 optee-ta-sample_rsip_v2.00.tar.gz,把 otp 部分的改动加入到解压文件夹里,然后再压缩成同名文件夹:

tar -czvf optee-ta-sample_rsip_v2.00.tar.gz optee-ta-sample

执行后会生成新的 .tar.gz 文件。

optee-ta-sample压缩包文件列表

4 OTP PTA 实现

我们希望在 Linux 应用程序中实现如下 OTP 读写目标:

  • 读:rz_optee_ta_otp read 0x0204 1——读取地址为 0x0204 的 OTP 地址,1 个 word;
  • 写:rz_optee_ta_otp write 0x0204 0x01——向地址为 0x0204 的 OTP 地址写入 1 个 word。

4.1 PTA 部分的修改

增加和修改的代码包括如下:

user@rz:~/otp_vlp5/build/tmp/work/rzt2h_dev-poky-linux/optee-os/4.8.0+git/git$ git status
On branch 4.8.0/rz
Your branch is behind 'origin/4.8.0/rz' by 2713 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   core/arch/arm/plat-rz/common/drivers/otp/puccini/otp_drv.c
        modified:   core/arch/arm/plat-rz/common/drivers/otp/puccini/otp_drv.h
        modified:   core/arch/arm/plat-rz/common/drivers/otp/puccini/otp_regs.h
        modified:   core/arch/arm/plat-rz/common/pta/sub.mk

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        core/arch/arm/plat-rz/common/pta/include/pta_otp.h
        core/arch/arm/plat-rz/common/pta/pta_otp.c

修改点说明:

(1)由于 otp_drv.c 下已经有了 r_otp_read 函数,我们照葫芦画瓢,增加 bool r_otp_write(uint32_t addr, uint32_t val):

r_otp_write函数代码截图:包含地址范围校验与r_otp_open调用

OTP write 的流程按照 RZ/T2H 用户手册 45.5.1 章节执行:

步骤 操作内容
A 假定 OTP 当前处于掉电状态。请检查 OTPPWR 寄存器中的 PWR 位为 0,并且 OTPSTR 寄存器中的 CMD_RDY 位为 0,以确认掉电处理已经完成。
B 将 OTPPWR 寄存器中的 PWR 位和 ACCL 位设置为 1。设置后,确认 OTPSTR 寄存器中的 CMD_RDY 位是否为 1。
C 将写入地址设置到 OTPADRWR 寄存器。
D 将要写入的数据设置到 OTPDATWR 寄存器。
E 将 OTPSTAWR 寄存器中的 STAWR 位设置为 1。
F OTP 控制器开始执行 OTP 写入处理。CMD_RDY 位从 1 变为 0。如果在 OTPADRWR 寄存器中设置了禁止写入区域的地址,则 STAWR 位不会被置为 1,并且 CMD_RDY 位保持为 1。在这种情况下,OTPSTR 寄存器中的 ERR_WP 位将被置为 1。
G OTP 控制器将 OTPDATWR 寄存器中的值写入 OTP。注:在本流程完成之前,必须保持 OTPADRWR 和 OTPDATWR 寄存器中的值不变。
H 轮询 STAWR 位,直到该位变为 0,以检测写入命令接收过程是否完成。
I 轮询 CMD_RDY 位,直到该位变为 1,以检测写入操作是否完成。
J 检测到 CMD_RDY 位为 1 后,确认 OTPSTR 寄存器中的 ERR_WP 位和 ERR_WR 位均未被置为 1。注:如果其中任一位被置为 1,则表示发生了 OTP 写入错误。
K 如果要继续写入 OTP,请从步骤 C 开始重复执行后续步骤。如果要发出 OTP 读取操作,请参阅用户手册表 45.6 的步骤 C 及其后续步骤。完成 OTP 访问时:解除掉电状态(PWR=1)后执行过 OTP 读取时,需要执行虚拟读取(dummy read),对未使用的用户区域执行一次 OTP 虚拟读取后,将 OTPPWR 寄存器中的 PWR 位和 ACCL 位设置为 0,然后等待 CMD_RDY 位变为 0;解除掉电状态(PWR=1)后未执行 OTP 读取时,不需要执行虚拟读取,直接将 OTPPWR 寄存器中的 PWR 位和 ACCL 位设置为 0,然后等待 CMD_RDY 位变为 0。

注:某些区域可以应用 WORDLOCK。对已应用 WORDLOCK 的区域,不能执行 OTP 写入。对于已经写入的位,即使再次写入 0 或 1,这些位也会保持为 1。禁止在 OTP 写入过程中复位,对于这种操作,无法保证其正常工作。

注:如果不再需要访问 OTP,务必按照上述流程使 OTP 进入掉电状态。

(2)在 otp_regs.h 中增加对应的宏;

(3)pta_otp.c 中是 TA 世界注册对应的 PTA 响应函数,包含读写操作:

invoke_command函数代码截图:通过switch分发OTP_CMD_WRITE与OTP_CMD_READ命令

(4)sub.mk 增加:

srcs-y += pta_otp.c

sub.mk配置文件修改截图

在 YOCTO 中,我们一般不直接在源码上修改,而是通过打 patch 的方式。

4.2 如何制作 patch 和使用 patch

(1)optee-os 的源码采用 git 管理,将修改和增加的文件创建 patch:

git add .
git diff --cached > otp.patch

终端中执行git add与git diff --cached生成patch的命令截图

(2)将 otp.patch 放到 meta-rz-features/meta-rz-security/recipes-bsp/optee/files/ 下。

(3)在 ../ 下增加一个 optee-os_git.bbappend 文件,内容为:

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI:append = " \
file://otp.patch \
"

这样就在配方中增加了 patch。

4.3 编译

(1)编译 OPTEE-OS

用 MACHINE=rzt2h-dev bitbake -c cleansstate optee-os 来清除临时改动,此时会将 optee-os 下的 git 仓库清除掉。再用 MACHINE=rzt2h-dev bitbake optee-os 来下载 git 仓库并打入 patch,完成编译。

完成后可以看到 pta_otp.c 的路径:

OP-TEE OS源码目录树:展示plat-rz下的pta_otp.c位置

OP-TEE OS 是在 secure boot 下,与 BL31、BL33 一起绑定到 FIP。如下图所示:

瑞萨RZ/T2H安全启动流程图:ROM Code加载BL2、BL31、BL32与BL33

随后需依次执行:

MACHINE=rzt2h-dev bitbake -c cleanall firmware-signing
MACHINE=rzt2h-dev bitbake -c cleansstate firmware-pack
MACHINE=rzt2h-dev bitbake firmware-pack

FIP 在生成后会进行签名,公钥放到 BL2 里面,因此要重新编译 firmware-signing。

(2)编译 OPTEE-TA-SAMPLE

optee相关源码目录列表:optee-client、optee-example、optee-os、optee-ta-sample

在 /build/tmp/work/rzt2h_dev-poky-linux/ 路径下,会看到 optee-client、optee-example、optee-os、optee-ta-sample 四个目录:

  • optee-client:普通世界侧的客户端库和守护进程,例如 libteec、tee-supplicant、TEE Client API 实现。
  • optee-example:官方示例程序,通常包含 Normal World 的 host app 和 Secure World 的 TA 示例,例如 hello world、secure storage、random 等。
  • optee-os:OP-TEE 的安全世界操作系统本体,包括 Trusted OS 内核、TEE Core、TA 加载与调度、加密服务、Secure Monitor 调用接口等。
  • optee-ta-sample:瑞萨提供,可以直接访问 OP-TEE Core 内部接口、安全硬件驱动,并作为其他 TA 共用的底层安全服务的 PTA,包括 SCE、SPI、FLASH 等。PTA 调用例程输出路径即 /build/tmp/work/rzt2h_dev-poky-linux/optee-ta-sample,编译方法为:
MACHINE=rzt2h-dev bitbake -c cleansstate optee-ta-sample  # 清理之前编译的文件

MACHINE=rzt2h-dev bitbake optee-ta-sample  # 编译PTA例程,从配方中解压并编译

最终会生成名为 rz_optee_ta_otp 的应用程序。

5 验证

将重新生成的 bl2-tbb_bp_xspi0-rzt2h-dev.srec 和 fip-tbb-rzt2h-dev.srec 通过 SCI 烧写到 xspi0 里,再拷贝 otp/host 中的 rz_optee_ta_otp 到 RZ/T2H 的 /home/root 路径下,就可以直接运行了。

5.1 读取功能验证

根据 RZ/T2H 的用户手册,0x0E5 是 unique ID 的前 32 位。我们先读 0x00e5 的默认数据:

root@rzt2h-dev:~# rz_optee_ta_otp read 0x00e5 1

在 TA 中是如下调用关系:rz_optee_ta_otp 应用程序运行 -> 捕捉到用户输入 read -> read_otp_value(如下截屏)。

TEEC_InvokeCommand读取OTP的代码截图

prepare_tee_session 里,调用 TEEC_OpenSession 时通过 UUID 找到 OTP 的实例。在打印实例中,可以看到对应的调用打印:

I/TC: create entry point for pseudo TA "otp.ta"
I/TC: open entry point for pseudo ta "otp.ta"
I/TC: close entry point for pseudo ta "otp.ta"
I/TC: destroy entry point for pseudo ta "otp.ta"

static TEE_Result create_ta(void)
{
    IMSG("create entry point for pseudo TA \"%s\"", TA_NAME);
    return TEE_SUCCESS;
}

static void destroy_ta(void)
{
    IMSG("destroy entry point for pseudo ta \"%s\"", TA_NAME);
}

static TEE_Result open_session(uint32_t nParamTypes __unused,
                   TEE_Param pParams[TEE_NUM_PARAMS] __unused,
                   void **ppSessionContext __unused)
{
    IMSG("open entry point for pseudo ta \"%s\"", TA_NAME);
    return TEE_SUCCESS;
}

static void close_session(void *pSessionContext __unused)
{
    IMSG("close entry point for pseudo ta \"%s\"", TA_NAME);
}

调用 TEEC_InvokeCommand 时通过 OTP_CMD_READ 找到 otp_read:

static TEE_Result otp_read(uint32_t param_types, TEE_Param p[TEE_NUM_PARAMS])
{
...
    IMSG("otp read has been called");
...
    if (!r_otp_read(p[0].value.a, &p[1].value.a, 1))
        return TEE_ERROR_GENERIC;
    return TEE_SUCCESS;
}

对应打印 log 为 I/TC: otp_read has been called。完整 log 如下:

root@rzt2h-dev:~# rz_optee_ta_otp read 0x00e5 1
I/TC: create entry point for pseudo TA "otp.ta"
I/TC: open entry point for pseudo ta "otp.ta"
I/TC: otp_read has been called
I/TC: close entry point for pseudo ta "otp.ta"
0xE5: 0x3802120B

读取正常。

5.2 写功能验证

测试往 user area 写数据。User area 是从 0x204 到 0x3FF 的数据区域,每个地址是 4 字节。

写入命令:

root@rzt2h-dev:~# rz_optee_ta_otp write 0x0204 0x1

rz_optee_ta_otp 应用程序运行 -> 捕捉到用户输入 write -> write_otp_value(如下截屏)。

TEEC_InvokeCommand写入OTP的代码截图

与 read_otp_value 一样的逻辑,会看到 otp.ta 的 create 和 entry:

I/TC: create entry point for pseudo TA "otp.ta"
I/TC: open entry point for pseudo ta "otp.ta"

因为传递给 invoke command 的命令是 OTP_CMD_WRITE,在 invoke_command_entry_point 注册的函数中,会通过 OTP_CMD_WRITE 分发到 otp_write:

otp_write函数代码截图:参数类型校验与底层写入调用

对应 log 为:

I/TC: otp_write has been called

I/TC: r_otp_write called.

I/TC: otp_write: parameters correctly aligned, addr=0x204, value=0x1

最终调用底层的 r_otp_write:

r_otp_write完整实现代码截图:包含轮询等待、寄存器写入等完整流程

对应 log 为:

I/TC: r_otp_write called.

I/TC: step E completed, line 126

log 显示完整的写流程全部成功执行:

root@rzt2h-dev:~# rz_optee_ta_otp write 0x0204 0x1
I/TC: create entry point for pseudo TA "otp.ta"
I/TC: open entry point for pseudo ta "otp.ta"
I/TC: otp_write has been called
I/TC: otp_write: parameters correctly aligned, addr=0x204, value=0x1
I/TC: r_otp_write called.
I/TC: step E completed, line 126
I/TC: otp_read has been called
I/TC: close entry point for pseudo ta "otp.ta"
Write successful at address 0x204 with value 0x00000001

5.3 回读验证写功能是否成功

root@rzt2h-dev:~# rz_optee_ta_otp read 0x0204 1
I/TC: create entry point for pseudo TA "otp.ta"
I/TC: open entry point for pseudo ta "otp.ta"
I/TC: otp_read has been called
I/TC: close entry point for pseudo ta "otp.ta"
I/TC: destroy entry point for pseudo ta "otp.ta"
0x204: 0x00000001

回读的数据与 5.2 写入的数据一致,表示写入成功。

注意,由于 OTP 是一次性编程区域,并且有些区域已用于 key 的存放,在使用时请使用 user area 区域。

源代码请向瑞萨获取。RZ/T2H 的 VLP5.0(Linux 6.12 内核)所有代码均可在 GitHub 上找到。为便于管理,在瑞萨官网有 YOCTO 工程,使用 YOCTO 编译时会自动从 GitHub 上拉取对应的版本。完整的软硬件设计规范、参考例程、工具等资料,可访问 RZ/T2H 产品页面 获取更多信息。

如果你在嵌入式安全、TrustZone 或 OP-TEE 驱动开发中遇到问题,欢迎到 云栈社区 与更多开发者交流讨论。




上一篇:Servo 0.7.0 收进 527 个提交,Rust 浏览器引擎更新多项 Web 功能
下一篇:瑞萨RA MCU FSP开发避坑:防止自动生成代码覆盖用户逻辑
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-11-13 18:15 , Processed in 0.863070 second(s), 46 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表