ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

QSEE平台指纹TA移植完整指南:从源码修改到调试排障

QSEE平台指纹TA移植完整指南:从源码修改到调试排障 做Qualcomm平台指纹方案移植的同事里十有八九会在TA这一层卡住。前面几篇如果把驱动、GPIO、SPI通信都理顺了你会觉得指纹马上就能点亮了结果发现TA要么加载不进去要么加载进去了但抓图、比对全套跑不起来。这篇文章把我个人在基于QSEE的指纹识别方案移植过程中关于TA移植的完整思路和踩坑记录整理出来重点说清楚三件事TA源码应该怎么改、Normal World和Secure World的通信接口怎么对齐、编译打包和上线排障怎么做。如果你正在往骁龙平台移植指纹方案或者只是想把TEE侧的东西搞明白这篇能帮你少走不少弯路。1. 移植前的边界确认TA到底是哪一层的活1.1 QSEE里的TA和REE侧驱动职责边界在哪在动手移植TA之前必须先想清楚一个问题指纹方案里哪些活必须在Secure World做哪些活留在REE侧做就行。这个边界如果不清楚后面你会面对大量无意义的debug。从我接触过的FPC、Goodix、思立微等方案看典型的职责切割大致如下功能模块放置位置原因SPI/GPIO寄存器级访问REE驱动为主TA可接管关键时序传感器初始化频率高REE侧方便调试指纹图像采集通常REE驱动采集后共享给TADMA/中断链路复杂放REE调试成本低指纹特征提取TA内完成算法和模板安全相关必须受保护指纹模板存储/加密TA内完成模板是敏感数据不能明文出Secure World指纹比对1:1/1:NTA内完成比对逻辑和密钥校验不能被篡改指纹密钥派生TA内完成涉及解锁/支付等场景的密钥体系这个表格不是绝对的。有些方案为了防SPI总线嗅探会要求TA直接接管传感器读图REE侧根本不接触图像数据有些低成本方案又把特征提取也放到REE侧TA只做一个模板加密存储。但一个真正过认证的方案基本都会把特征提取、模板加密、比对这三件事锁在Secure World里。所以你在移植之前一定要跟算法/安全团队确认清楚你们的安全边界画在哪这决定了TA代码量的多少。1.2 移植前需要核对的三张清单我自己的经验是把移植这件事拆成三张清单来核对能避免中途返工。第一张是平台清单。确认目标SoC的QSEE版本/安全OS版本。不同版本之间的TEE Internal API有些差异比如GlobalPlatform接口的某些结构体定义在较新版本里变了。另外Secure Boot默认使能状态也要确认它会直接影响后面TA签名策略。很多人在老平台上习惯关掉Secure Boot跑裸TA到了新平台发现启动校验过不去直接卡在黑屏或ABL阶段。第二张是外设清单。指纹sensor的型号、供电要求、SPI工作频率、中断GPIO、复位GPIO、复位时序要求。这些在你改TA之前就要确认因为TA里如果有一段直接接管SPI读sensor的逻辑它对时序和GPIO的依赖比你想象的强得多。第三张是代码清单。指纹厂商提供的算法库是.a还是预编译.so有没有对应的TA参考源码参考源码是基于哪个高通的BSP版本裁剪出来的直接照着老的参考代码套新平台最常见的报错是TZ加载TA时找不到符号。这三张清单核对完你才适合打开源码开始动手。不要急着往代码里塞功能。1.3 一个容易忽略的前提TA的运行环境和Linux完全不同很多从普通Linux驱动转过来做TA的同事刚开始特别不适应。TA不是跑在Linux内核里的一个进程而是运行在高通QSEE安全世界里的一段独立代码。这意味着没有标准libcmalloc/free、printf这些都不能直接用。QSEE SDK里自有的一套运行库函数名接近但行为要查文档。TA异常时会触发TZ panic调试手段非常有限。TA的内存是被隔离的它和REE侧通信必须走显式的共享内存/消息机制。这个认知如果不建立起来后面会觉得这也不能用那也不能用其实不是限制而是安全世界的规则本来就不一样。2. TA源码改造从厂商SDK到QSEE工程的融合2.1 算法库链接静态库还是共享库这里头有讲究指纹传感器厂商一般会提供一个算法库里面封装了特征提取、模板生成、比对的整套逻辑。这个库有两种给法一种是.a静态库一种是.so共享库加上一堆头文件。我的建议是只要能拿到静态库就优先用静态库。原因很简单QSEE TA的加载机制对共享库的依赖链非常敏感。TA加载时如果找不到依赖的动态库直接加载失败而且日志并不会把依赖缺失的原因打得很清楚只会告诉你load TA失败。静态库链接进去省去一大堆运行时的符号解析问题。如果厂商只给.so那你需要把.so一同打包进TZ image并且确保TA的DT_REQUIRED里录入的依赖库名字和实际加载路径一致。这一步如果版本对不上坑会埋得很深。链接阶段还有一个常见错误链接器链接到了REE侧的其他安全库导致TA镜像里出现了一批不属于这个世界的符号。高通QSEE的编译工具链一般是qsee专用的不要图省事直接用REE侧aarch64工具链。我见过同事拿aarch64-linux-gnu-gcc编译TA链出来也通过了但一加载就panic查了一整天发现是指令集/浮点ABI不匹配。2.2 TA入口和命令分发指纹命令集合怎么设计QSEE TA的标准入口是GlobalPlatform风格的核心是这几个函数TA_CreateEntryPoint、TA_OpenSession、TA_InvokeCommand、TA_CloseSession、TA_DestroyEntryPoint。实际大部分指纹商都不会直接用原始接口而是封装一层自己的命令分发。我在移植时习惯把命令ID定义集中在头文件里例如#define TZA_CMD_GET_INFO 0x0001 #define TZA_CMD_SET_SENSOR_CONFIG 0x0002 #define TZA_CMD_ACQUIRE_IMAGE 0x0003 #define TZA_CMD_GENERATE_TEMPLATE 0x0004 #define TZA_CMD_AUTHENTICATE 0x0005 #define TZA_CMD_DELETE_TEMPLATE 0x0006TA_InvokeCommand里做switch/case分发。注意一个细节命令ID不要用枚举类型用宏定义固定数值。因为REE侧驱动和TA都引用这套ID两边如果出现编译宏差异枚举值可能错位排查起来非常痛苦。定成宏后两边一起编译头文件一致几乎不会因为枚举顺序导致错命令。在命令里传输参数时一定要明确这块内存到底是什么格式。QSEE侧拿到的是一块原始内存指针和长度它本身不具备反序列化能力。所以你要么用固定struct要么用TLV结构。个人更推荐固定struct方便固定安全边界。typedef struct { uint32_t cmd_id; uint32_t in_len; uint32_t out_len; uint32_t reserved; } tza_msg_header_t;协议头解析完再根据cmd_id去读实际payload。不要试图直接用用户传进来的长度去memcpy一定要做越界检查。在Secure World里越界访问不会像REE那样给你一个优雅的segment fault而是直接触发TrustZone panic整机可能重启。2.3 TA里的日志、随机数和时间三件小事三个坑先说日志。QSEE里不能用printf你得用高通SDK提供的日志宏常见的是TZ_UTILS或qsee_log级别的接口。这些日志会打到TZ的log buffer里最终通过QXDM或者TZLog工具拉出来。如果你发现TZ侧日志打不出来先检查两个地方一是Secure Boot/日志等级配置是不是把DLOG关了二是你拉日志的工具链是不是连对了端口。再说随机数。指纹模板的加密、密钥派生都需要随机数。在REE侧你可能随手就bs /dev/urandom了但在QSEE里必须用安全随机数接口通常对应高通的CSPRNG硬件。有的工程师贪方便用系统时间戳加一些固定salt当随机源这在安全评审时会被直接打回。然后是时间。QSEE环境里有时候获取时间戳的接口调用成本很高不要在热路径比如每帧图像比对前反复取时间戳。实测下来会影响指纹解锁的体验本来应该是毫秒级的比对可能被debug日志和取时间戳拖到几百毫秒。3. Normal World侧的QSEE通信接口与指纹驱动协作3.1 通信链路怎么建立QSEEClient与TA UUIDTA移植不是只有TA侧改完就完事REE侧驱动需要能跟TA对上话。在高通平台这个通道通常是qseecom驱动提供的用户态封装成QSEEClientAPI。你在驱动里需要先按TA的UUID启动TA然后再发命令。int32_t qsee_app_start(const char *app_name, uint32_t *app_id); int32_t qsee_app_send_command(uint32_t app_id, void *req_ptr, uint32_t req_len, void *rsp_ptr, uint32_t rsp_len); int32_t qsee_app_shutdown(uint32_t app_id);这段代码里app_name对应的就是TA镜像里定义的名字。移植时最常见的问题是指纹TA的UUID或名字写错了导致qsee_app_start返回错误。我排查过很多次发现都是REE驱动和TA两侧的UUID不统一驱动里写的是参考代码的旧UUIDTA编译时又是另一个UUID。3.2 共享内存与Cache一致性图像数据传递的细节指纹图像数据怎么从REE侧驱动传到TA侧一般做法是先分配一块共享内存ion buffer或QSEE shared bufferREE驱动把图像数据写进去然后通过qsee_app_send_command把buffer的物理地址和长度作为参数传给TA让TA直接访问这块地址。这个环节最典型的坑是Cache一致性问题。REE侧驱动往buffer里写数据时CPU Cache可能还没刷进去TA访问到的可能是旧数据表现就是抓到的图像花屏或全黑。解决方式是在写入之后刷新Cache或直接通过DMA的coherent buffer分配。有的工程师一开始没注意company大半天以为是数据格式问题后来加了一个flush_cache_all就好了。反过来TA侧往共享内存里写返回数据时同样存在TA侧Cache没刷回来的问题。REE侧驱动读到的可能是不完整的响应。这在高通不同SoC上行为还不一样所以不要靠运气。3.3 指纹中断和GPIO的Secure归属指纹方案里如果TA需要直接接管传感器那中断和GPIO的归属就要认真规划。正常世界里Linux驱动可以申请一个GPIO作为中断如果中断需要直达Secure World那这个GPIO的配置必须被标记为secure否则TZ侧没有对应的中断路由。具体配置位置在TLMMTop Level Mode Multiplexer的secure配置里通常由boot阶段的tables描述。如果你发现REE侧申请中断成功但TA里始终等不到中断回调先别怀疑TA逻辑去查GPIO是不是在Secure侧被正确路由了。这块移植还涉及一个概念指纹方案的状态机到底由谁主导。主流做法是REE侧驱动做状态机TA做被动执行者传感器中断先到REE侧REE驱动收到后再发命令通知TA去处理。因为REE侧调试日志、性能剖析工具都更成熟状态机放REE侧效率最高。只有对安全要求极高的方案才把状态机整体放进TA这也是导致中断必须走Secure路由的原因。4. 编译、签名与镜像打包把TA真正跑进TEE4.1 QSEE TA编译工程怎么组织QSEE TA的编译工程和高通BSP里的其他模块不太一样一般会有一个专门存放TA源码的目录比如vendor/qcom/proprietary/securemsm/trustzone/...下。工程里需要配置的通常包括TA名称和UUID依赖的库路径算法库、QSEE SDK某些公共库编译选项目标平台、优化等级、调试符号链接脚本/加载地址我在实际移植中喜欢先把厂商给的TA工程当作模板用diff的方式对比参考平台和目标平台的差异而不是直接从头新建一个。因为QSEE的编译脚本版本差异很敏感你手工建的工程很容易漏掉某些隐藏依赖。比如有的工程需要在编译时生成一个ta_entry配置这个配置来自安全OS启动信息漏掉之后TA跑不起来但编译不报错。4.2 签名与Secure Boot测试策略TA编译出来之后不能直接往分区里怼需要签名。高通Secure Boot正常开启的情况下TA镜像必须由合法的OEM key签名否则加载时会被TZ校验拒绝。开发阶段通常会做两件事之一要么在测试分区/刷了非安全boot的机器上跑未签名TA要么直接用测试key给TA签名。我个人的习惯是维护两个签名的产物一个test-signed版本用于日常调试一个OEM-signed版本用于认证和量产验证。两边千万别混不然你可能测了很久发现行为差异是因为签名的key角色不同导致的。这里有个教训同一块开发板上如果你之前烧的是debug boot image后来误刷了user imageTA的加载策略可能完全不同。遇到昨天还好好的今天TA就加载不了先查Secure Boot状态和烧进去的image是不是同一个类型。4.3 镜像打包与烧写流程TA编译签名完成后要打进TZTrustZoneimage里。高通的TZ image一般通过pil或boot chain加载烧写时可能涉及aboot、tz、hyp等多个分区。指纹TA通常是打包进trustzone.img或单独的QRDK分区具体要看BSP里的配置。烧写之后的第一步验证不要直接跑完整指纹业务流程先做最小验证用QSEEClientAPI写一个小测试程序直接qsee_app_start 发一个GET_INFO命令看TA能不能正常响应。能响应该初步说明加载没问题。然后再逐步测试抓图、模板、比对。如果这个最小测试程序都起不来后边全流程debug就无从谈起。这个最小可验证闭环的思路我觉得怎么强调都不过分。5. 最典型的三种TA移植失败场景与完整排查链路5.1 场景一TA加载失败日志提示找不到或签名校验失败表现是qsee_app_start返回失败TZ日志里能看到类似Error loading TA或Failed to verify TA signature的信息。排查链路我会按这个顺序走先从TZ侧确认TA有没有被识别到。查看UUID和TA名称是否与加载请求一致。检查TA镜像的签名状态。先确认设备当前的Secure Boot策略再用对应key签一个test版本验证。检查TA链接的依赖库是否存在。可以加载libc库列表看看TA依赖的每一个.so是否都在TZ image里。缺一个就是这个表现。检查TA的编译架构是否匹配。用readelf看so的machine typeELF头和TZ预期不符也会加载失败。这个场景里我见过最隐蔽的坑是TA源码编译根本没报错但因为某个公共库版本过旧TA里调用了一个新内核上才有的API导致运行时符号解析失败。编译期查不出来只能在设备上看TZ panic。5.2 场景二TA正常加载但指纹图像抓不到/全黑/花屏先判断图像通路到底断在哪。我会做一个试验手动让REE驱动抓一帧原图直接输出到节点里看。如果REE侧图像正常那问题就在共享内存或TA侧处理如果REE侧图像就不正常那是sensor驱动/供电/时序问题跟TA无关。共享内存环节重点检查Cache一致性和物理地址映射。我之前遇到过一次全花屏从log看DMA buffer地址也传过去了长度也对但TA访问的时候就是花。后来发现是buffer在REE侧的虚拟地址和物理地址搞混了给TA传成了虚拟地址TA当然访问不到真实的物理内存。如果是TA直接接管SPI的模式全黑图大概率是sensor的GPIO没配好、SPI速率不对或复位时序没拉够。不要从算法角度去猜先把sensor的寄存器read ID读出来ID都对才有资格讨论图像。5.3 场景三比对始终不通过或返回神秘错误码这是最折磨人的场景TA跑起来了图像也抓到了模板也生成了但每次解锁都报错。排查这类问题首先要拿到TA内部算法返回的具体错误码。很多指纹算法库的错误码非常详细比如模板特征点太少活体检测失败模板数据CRC错误。如果你只在外层看到比对失败四个字完全没法定位。所以移植TA时一定要把错误码逐级透传出来不要自己吞掉。如果显示模板数据CRC错误或解密失败多数是绑定密钥的问题模板是用旧密钥加密产生的换了一个TA版本后密钥派生逻辑变化导致模板无法解密。这种问题通常需要升级兼容策略让TA能识别旧密钥版本并重新迁移模板。如果错误码显示特征点太少那要回头检查图像质量如果活体检测失败先看是不是sensor型号与算法库支持的型号不匹配。我见过有些算法库绑定了特定的传感器型号换sensor后能出图但算法认为这不是它认识的硬件直接拒绝出活体结果这种情况要从算法库配置层面去适配不是改TA调用顺序能解决的。实际做到这里一套QSEE指纹TA移植的主干链路就已经通了。我在多款骁龙平台上跑过类似的移植工作每次都发现真正花时间的不是写代码本身而是前面梳理安全边界、确认共享内存角色、对齐REE侧与TA侧协议这三个环节。只要这三个环节没偷懒后面编译打包和排查基本都是有迹可循的。最后再分享一个习惯我会在TA和REE驱动里同时打印同一个命令的执行耗时用时间戳对齐两边日志很多诡异的偶发失败都是这样发现的比如某个命令在REE侧耗时正常但TA侧执行时间极不稳定一查是安全OS的调度问题。这种问题不看运行轨迹根本想不到。
返回列表