手撕ARM64启动栈(九): OP-TEE Linux Driver——加载、SMC 握手与设备节点创建

发布时间:2026/8/17 19:34:19
手撕ARM64启动栈(九): OP-TEE Linux Driver——加载、SMC 握手与设备节点创建 系列文章目录手撕ARM64启动栈一QEMU TF-A → OP-TEE → U-Boot → Linux 全链路总览手撕ARM64启动栈二QEMU TF-A(ATF) BL1 执行上下文——GDB 实测复位到 BL2手撕ARM64启动栈三QEMU TF-A(ATF) BL1 核心职责——bl1_main()逐函数拆解与 BL2 加载手撕ARM64启动栈四QEMU TF-A(ATF) BL2 详解手撕ARM64启动栈五QEMU TF-A(ATF) BL32/OP-TEE 镜像头解析——三模式判定与入口改写手撕ARM64启动栈六QEMU TF-A(ATF) BL31 运行时服务框架与 opteed 注册手撕ARM64启动栈七QEMU TF-A(ATF) BL31 世界切换手撕ARM64启动栈八QEMU TF-A(ATF) OP-TEE 初始化详解手撕ARM64启动栈九OP-TEE Linux Driver——加载、SMC 握手与设备节点创建文章目录系列文章目录1. 引言2. 驱动目录结构与文件职责3. 从 TEE core 初始化到 optee_probe()3.1 tee_coresubsys_initcall3.2 optee_core_initmodule_init3.3 optee_smc_abi_register → platform_driver_register4. optee_probe 详解4.1 conduit 检测与 UID 验证4.2 版本、线程数与能力协商4.3 SHM 池建立4.4 tee_device_alloc / tee_device_register4.5 驱动内部子系统初始化5. 共享内存管理静态、动态与 arg cache5.1 静态 SHM保留内存5.2 动态 SHM5.3 TEE core 的用户缓冲区入口5.4 arg struct cache6. 小结1. 引言上一篇末尾OP-TEE 通过smc #0把thread_vector_table交给 BL31自身进入休眠状态等待 Normal World 调用。控制权随后经 BL31 → U-Boot → Linux 传递下来。本篇关注 Linux kernel 侧optee驱动相关初始化optee驱动位于哪里、如何加载、如何通过 SMC 与 OP-TEE 握手以及如何建立/dev/tee0和/dev/teepriv0两个字符设备。2. 驱动目录结构与文件职责linux/drivers/tee/ ├── tee_core.c # TEE 子系统核心字符设备注册、ioctl 分发 ├── tee_shm.c # struct tee_shm 分配、注册与释放 ├── tee_shm_pool.c # SHM 池静态 memremap 池与动态页池 ├── tee_heap.c # 受保护内存 DMA-BUF heap ├── tee_private.h # TEE core 内部数据结构 ├── optee/ # OP-TEE 驱动 │ ├── core.c # module_init/exit、RPMB、公用辅助函数 │ ├── smc_abi.c # SMC ABI │ ├── ffa_abi.c # FF-A ABI │ ├── call.c # call queue、SHM arg cache、session open/invoke 分发 │ ├── supp.c # tee-supplicant 请求队列SUPPL_RECV/SEND │ ├── notif.c # 异步通知 │ ├── rpc.c # OP-TEE RPC 回调处理 │ ├── device.c # TEE bus 设备枚举 │ ├── protmem.c │ ├── optee_private.h # 驱动内部数据结构 │ ├── optee_smc.h # SMC Function ID 定义 │ ├── optee_msg.h # optee_msg_arg 消息格式定义 │ └── optee_rpc_cmd.h # RPC 命令码定义tee_core.c提供tee_device_alloc()、tee_device_register()和tee_init()等公共基础设施。optee/目录下的文件才是optee特有驱动。smc_abi.c是 OP-TEE SMC 路径的主体包含optee_probe()、probe 阶段的握手 SMC 调用以及运行时的optee_smc_do_call_with_arg()。3. 从 TEE core 初始化到optee_probe()本节说明 TEE core 初始化、OP-TEE 驱动注册和 DT 匹配之间的先后关系3.1 tee_coresubsys_initcalltee_core.c底部subsys_initcall(tee_init);tee_init()完成三件事注册tee_class、用alloc_chrdev_region()申请TEE_NUM_DEVICES32个字符设备号主设备号动态分配次设备号 0–31、注册tee_bus_type。这一步在驱动 probe 之前完成确保后续tee_device_alloc()时tee_devt已经有效。3.2 optee_core_initmodule_initcore.c底部module_init(optee_core_init);optee_core_init()依次调用smc_abi_rcoptee_smc_abi_register();ffa_abi_rcoptee_ffa_abi_register();本节使用 SMC ABI 路径ffa abi后续文章单独介绍3.3 optee_smc_abi_register → platform_driver_registersmc_abi.cstaticconststructof_device_idoptee_dt_match[]{{.compatiblelinaro,optee-tz},{},};staticstructplatform_driveroptee_driver{.probeoptee_probe,.driver{.nameoptee,.of_match_tableoptee_dt_match,},};intoptee_smc_abi_register(void){returnplatform_driver_register(optee_driver);}platform_driver_register()把optee_driver挂到 platform busplatform bus 匹配后立即调用optee_probe()。4. optee_probe 详解optee_probe()在smc_abi.c中整体流程如下图本节按图中各阶段展开4.1 conduit 检测与 UID 验证invoke_fnget_invoke_func(pdev-dev);get_invoke_func()读取 DT 的method属性pr_info(probing for conduit method.\n);if(!strcmp(hvc,method))returnoptee_smccc_hvc;// 包装 arm_smccc_hvc()elseif(!strcmp(smc,method))returnoptee_smccc_smc;// 包装 arm_smccc_smc()返回的函数指针存入invoke_fn后续所有握手和运行时 SMC 都通过它发出。本篇例子返回optee_smccc_smc最终调用arm_smccc_smc()陷入 EL3。4.2 版本、线程数与能力协商UID 通过后连续发四个 Fast SMC确定初始化参数optee_msg_get_os_revision(invoke_fn);if(!optee_msg_api_revision_is_compatible(invoke_fn)){pr_warn(api revision mismatch\n);return-EINVAL;}thread_countoptee_msg_get_thread_count(invoke_fn);optee_msg_exchange_capabilities(invoke_fn,sec_caps,max_notif_value,rpc_param_count);SMC作用OPTEE_SMC_CALL_GET_OS_REVISION读取 OP-TEE OS 版本打印revision X.Y (build_id)OPTEE_SMC_CALLS_REVISION检查消息 API 的 major/minor 是否满足OPTEE_MSG_REVISION_MAJOR/MINOROPTEE_SMC_GET_THREAD_COUNT读取 OP-TEE 线程池大小供 call queue 初始化使用OPTEE_SMC_EXCHANGE_CAPABILITIES协商能力位sec_caps同时返回max_notif_value、rpc_param_countsec_caps中重要的标志位标志位含义OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM支持静态SHMoptee_shm42000000OPTEE_SMC_SEC_CAP_DYNAMIC_SHM支持动态SHM运行时注册页OPTEE_SMC_SEC_CAP_ASYNC_NOTIF支持异步通知中断驱动而非轮询OPTEE_SMC_SEC_CAP_MEMREF_NULL支持 NULL memref 参数OPTEE_SMC_SEC_CAP_RPMB_PROBE支持由内核直接处理 RPMB 路由还需 Linux 可达CONFIG_RPMBOP-TEE 在处理OPTEE_SMC_EXCHANGE_CAPABILITIES时打印Reserved shared memory is enabled和Dynamic shared memory is enabled。本例的 Secure World 日志中这两行位于 Primary CPU 已切换到 Normal World 之后对应 Linux probe 发起的能力协商Normal World 侧对应的日志见 4.5 节末尾。4.3 SHM 池建立probe 根据sec_caps决定使用哪种 SHM 池两种能力可以同时存在但只为设备选择一个主 poolif(sec_capsOPTEE_SMC_SEC_CAP_DYNAMIC_SHM){if(sec_capsOPTEE_SMC_SEC_CAP_RPC_ARG)arg_cache_flagsOPTEE_SHM_ARG_SHARED;elsearg_cache_flagsOPTEE_SHM_ARG_ALLOC_PRIV;pooloptee_shm_pool_alloc_pages();}if(IS_ERR(pool)(sec_capsOPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM)){arg_cache_flagsOPTEE_SHM_ARG_SHARED|OPTEE_SHM_ARG_ALLOC_PRIV;pooloptee_config_shm_memremap(invoke_fn,memremaped_shm);}优先建立动态 SHM pooloptee_shm_pool_alloc_pages()动态方式不可用或建立失败时才回退到静态 reserved SHM pooloptee_config_shm_memremap()。两种 pool 各自的建立方式和注册/注销机制见第 5 节。4.4 tee_device_alloc / tee_device_registerteedevtee_device_alloc(optee_clnt_desc,NULL,pool,optee);optee-teedevteedev;teedevtee_device_alloc(optee_supp_desc,NULL,pool,optee);optee-supp_teedevteedev;optee_set_dev_group(optee);// 设置两个设备的 sysfs 属性组tee_device_register(optee-teedev);tee_device_register(optee-supp_teedev);主设备号已经由前面的tee_init()统一动态申请alloc_chrdev_region(tee_devt,0,TEE_NUM_DEVICES,tee);这次调用申请一个主设备号以及该主设备号下从 0 开始的 32 个次设备号。tee_device_alloc()从这 32 个次设备号中选择一个并用下面的代码组成完整的dev_tteedev-dev.devtMKDEV(MAJOR(tee_devt),teedev-id);tee_device_alloc()同时构造struct tee_device。普通设备optee_clnt_desc无TEE_DESC_PRIVILEGED标志使用次设备号 015特权设备optee_supp_desc带TEE_DESC_PRIVILEGED使用次设备号 1631。设备名称中的编号不是原始次设备号而是各自区间内从 0 开始的编号snprintf(teedev-name,sizeof(teedev-name),tee%s%d,teedesc-flagsTEE_DESC_PRIVILEGED?priv:,teedev-id-offs);因此本例中的设备号关系为设备节点主设备号次设备号名称中的编号/dev/tee0MAJOR(tee_devt)00/dev/teepriv0MAJOR(tee_devt)16016 - offs其中offs 16两个设备共享 TEE core 动态申请的主设备号但使用不同的次设备号。两个设备在功能上也不同/dev/tee0对应optee_clnt_desc注册optee_ops向 TEE core 提供open_session、invoke_func、cancel_req、close_session和 SHM register/unregister 等回调多个进程可以同时打开每次open()由 TEE core 分配独立的struct tee_context。/dev/teepriv0对应optee_supp_desc注册optee_supp_ops除 context 和 SHM 回调外还提供supp_recv/supp_send用于把 OP-TEE 的 Reverse RPC 请求转交给用户空间服务进程。4.5 驱动内部子系统初始化设备注册后probe 继续初始化运行时所需的本地状态optee_cq_init(optee-call_queue,thread_count);// yielding call 等待队列optee_supp_init(optee-supp);// supplicant RPC 请求队列optee_shm_arg_cache_init(optee,arg_cache_flags);// optee_msg_arg 缓存ctxteedev_open(optee-teedev);optee-ctxctx;// 驱动内部操作使用的持久 contextrcoptee_notif_init(optee,max_notif_value);// 通知状态if(sec_capsOPTEE_SMC_SEC_CAP_ASYNC_NOTIF){rcoptee_smc_notif_init_irq(optee,irq);// 中断和 bottom halfenable_async_notif(optee-smc.invoke_fn);// 通知 OP-TEE 启用异步通知}optee_protmem_pool_init(optee);// 受保护内存服务可选optee_disable_unmapped_shm_cache(optee);// 清理 OP-TEE 端遗留 RPC SHM cacheif(!optee-rpc_param_count)optee_enable_shm_cache(optee);// 重新启用 RPC SHM cacheoptee_enumerate_devices(PTA_CMD_GET_DEVICES);// 初始 TEE bus 枚举// 初始化 RPMB 扫描 work 和 notifiernormal-world.logLog打印optee: probing for conduit method. optee: revision 4.10 (c02b12933697e2e5) optee: Asynchronous notifications enabled optee: dynamic shared memory is enabled optee: initialized driver5. 共享内存管理静态、动态与 arg cache5.1 静态 SHM保留内存静态 SHM 对应optee os初始化篇写入 DT 的optee_shm42000000no-map2MB。Linux 在memblock_mark_nomap()阶段把这段物理内存排除在 buddy allocator 之外probe 阶段optee_config_shm_memremap()把它映射为内核虚拟地址并建立gen_pool分配池。5.2 动态 SHM动态 SHM 用alloc_pages()按需分配普通物理页。创建普通 SHM 对象时pool 的pool_op_alloc()调用tee_dyn_shm_alloc_helper(..., optee_shm_register)Linux 分配页并以OPTEE_MSG_CMD_REGISTER_SHM注册给 OP-TEE。释放对象时pool_op_free()以OPTEE_MSG_CMD_UNREGISTER_SHM注销再释放页。动态 SHM 不依赖启动时固定的预留区但要求双方协商出OPTEE_SMC_SEC_CAP_DYNAMIC_SHM。OP-TEE 仍会校验这些页属于允许共享的 Non-secure DDR 范围。5.3 TEE core 的用户缓冲区入口TEE core 为用户缓冲区提供两种 kernel 入口tee_shm_alloc_user_buf()从设备 SHM pool 分配对象tee_shm_register_user_buf()pin 用户页面并调用 OP-TEE backend 的shm_register回调。释放时由对应 pool 或 unregister 回调完成清理。用户空间库如何在这两条入口之间选择、何时使用 shadow buffer后续章节将会展开说明5.4 arg struct cache每次 yielding call 都要在 SHM 里准备一个struct optee_msg_arg用于存放命令码、session id 和参数数组。这类调用生命周期很短如果每次都从 pool 申请再释放开销不小动态 SHM 路径下还要额外向 OP-TEE 注册和注销。arg cache 用来省掉这部分开销用完的页不还给 pool而是留在链表里复用structoptee_shm_arg_entry{structlist_headlist_node;structtee_shm*shm;DECLARE_BITMAP(map,MAX_ARG_COUNT_PER_ENTRY);};每个节点管理一页 SHM单个optee_msg_arg只有一两百字节一页能放多个map位图记录页内哪些 slot 已占用。optee_get_msg_arg()先找有空闲 slot 的页set_bit()后按offs bit * sz返回页内偏移所有页都满了才新分配一页。optee_free_msg_arg()只做clear_bit()页本身保留到驱动卸载时才释放。probe 末尾调用optee_shm_arg_cache_init(optee, arg_cache_flags)建立这个 per-driver 缓存。两个标志各自控制一件事标志置位时清零时OPTEE_SHM_ARG_SHARED一页放多个 arg用非零 offset 区分一页只放一个 argoffset 恒为 0OPTEE_SHM_ARG_ALLOC_PRIV用tee_shm_alloc_priv_buf()分配不向 OP-TEE 注册用tee_shm_alloc_kernel_buf()分配页面预注册给 OP-TEEarg_cache_flags在选择 SHM pool 时同步确定本例走动态 SHM 且 OP-TEE 报告了OPTEE_SMC_SEC_CAP_RPC_ARG因此只置OPTEE_SHM_ARG_SHAREDarg page 预注册给 OP-TEE一页可放多个 argoptee_smc_do_call_with_arg()相应使用OPTEE_SMC_CALL_WITH_REGD_ARG传tee_shm句柄和 offsetOP-TEE 用 cookie 查已注册的 mobj不必再解析物理地址。三种组合与运行时实际使用的 yielding SMC Function IDSHM poolRPC_ARGarg_cache_flagsyielding SMC Function ID动态支持SHAREDCALL_WITH_REGD_ARG动态不支持ALLOC_PRIVCALL_WITH_ARG静态回退支持SHARED | ALLOC_PRIVCALL_WITH_RPC_ARG静态回退不支持SHARED | ALLOC_PRIVCALL_WITH_ARG这里的 Function ID 只决定 OP-TEE 如何定位 arg物理地址还是 SHM cookie、后面是否跟着一段预留的 RPC arg具体请求做什么由optee_msg_arg.cmd表示与 Function ID 无关。静态 pool 本身就在一段已经 memremap 的连续内存里OP-TEE 侧早已知道这段地址范围所以既允许非零 offset又不需要逐页注册两个标志同时置位。可见这两个标志描述的是不同维度并非互斥选项。6. 小结OP-TEE Linux 驱动probe 完成后kernel 已经准备好两个独立接口/dev/tee0面向普通 TEE client/dev/teepriv0面向特权 supplicant。普通 CA 的libteec调用以及Reverse RPC 和 TA 文件加载将会在后续章节继续展开。