
1. 项目缘起与整体设计思路AM62L这颗处理器在工业控制和边缘计算场景里铺得越来越广很多做安全启动、固件签名、TLS加速的朋友都会盯上它片上的DTHE_V2模块。DTHE是Data Transform and Hashing Engine的缩写V2是这个引擎的第二代架构它把对称加密、哈希、消息认证码这些运算从主核手里接过来让Cortex-A53去干别的事。标题里点名的SHA-512和HMAC恰好是安全启动链里用得最重的两个算法——SHA-512负责度量HMAC负责带密钥的完整性校验。问题在于TI的TRM动辄几千页寄存器位域散落在不同章节真要动手写裸机驱动或者调Linux内核里的crypto节点光靠翻手册很容易在某个bit上卡一整天。我这次做的项目就是把这套DTHE_V2的SHA-512/HMAC寄存器配置从头到尾捋一遍目标很明确让一个刚拿到AM62L EVM、手里只有TRM和SDK的工程师能在半天内把哈希和HMAC跑通并且知道每个关键位为什么这么配。适合的读者有三类一是写BootROM之后第一阶段固件的底层工程师二是给Linux内核crypto子系统写平台驱动的人三是做安全审计、需要确认硬件加速是否真正生效的验证人员。这三类人关注的点不一样但底层寄存器操作是共通的所以我会把寄存器层面的东西讲透再往上延伸到驱动集成。整体设计上我没有一上来就贴寄存器表而是先讲清楚DTHE_V2的数据通路。这个引擎内部有一条DMA通道、一个算法核、一组上下文寄存器和一个结果FIFO。SHA-512和HMAC共用同一套哈希核区别在于HMAC在哈希运算前后各加了一次密钥异或和一次外层哈希。理解这一点很关键因为寄存器配置里有一半的坑都来自“HMAC不是独立算法而是SHA-512的两次调用”这个事实。方案选型上我坚持用寄存器直接操作而不是依赖SDK封装原因是SDK的封装层在早期版本里对SHA-512的块大小处理有偏差而且调试时看不到中间状态。直接怼寄存器虽然累但每一步都可控出问题能定位到具体bit。提示DTHE_V2的寄存器基地址在不同封装和不同TRM修订版里可能不同动手前务必用你手上芯片对应的TRM确认基址本文以常见映射为例不替代官方文档。2. DTHE_V2硬件加速器SHA-512/HMAC核心细节解析2.1 SHA-512在DTHE_V2里的数据流与块处理逻辑SHA-512的块大小是1024位也就是128字节这个数字必须刻在脑子里。DTHE_V2的哈希核一次处理一个块处理完把中间状态写回上下文寄存器再喂下一个块。很多人第一次配的时候会疑惑为什么我写了64字节数据引擎没反应因为SHA-512要求至少一个完整块才能触发一次压缩函数不足128字节的尾部需要走padding流程。padding规则是先在消息末尾补一个0x80然后补0直到长度满足(消息长度 1 16) % 128 0最后16字节放原始消息的比特长度大端序。这个padding你可以软件做也可以让引擎做但DTHE_V2的自动padding只在特定模式下可用我建议软件做padding把完整块喂给引擎逻辑最清晰。数据通路上CPU把数据写进DTHE的输入FIFO或者配置DMA让数据从内存搬进来。FIFO深度有限写的时候要查状态寄存器的FIFO满标志满了就等。这里有个实操细节连续写FIFO时如果不等标志位硬写数据会丢而且丢得悄无声息最后哈希结果不对你还找不到原因。我一般用轮询方式读DTHE_STS寄存器的IN_FIFO_FULL位为0才写下一个字。DMA方式效率高但配置描述符麻烦调试阶段先用轮询把算法跑通再换DMA优化。2.2 HMAC的双重哈希结构与密钥预处理HMAC的公式是HMAC(K, m) H((K xor opad) || H((K xor ipad) || m))。K是把密钥补齐或哈希到块大小后的结果。对SHA-512来说块大小128字节如果密钥超过128字节先对密钥做一次SHA-512得到64字节再补零到128字节如果密钥不足128字节直接补零。ipad是0x36重复128次opad是0x5c重复128次。DTHE_V2不会自动帮你做这些你得自己算好K xor ipad和K xor opad然后分两次调用哈希核。第一次调用把K xor ipad作为第一个块喂进去然后喂消息m最后做padding得到内层哈希结果。第二次调用把K xor opad作为第一个块然后喂内层哈希结果64字节再做padding得到最终HMAC。注意第二次调用的消息长度是64字节padding后是128字节的一个块。这里最容易错的是内层哈希结果直接当消息喂忘了它也需要padding。我见过有人把64字节内层结果直接写FIFO就等结果引擎因为没收到完整块一直不输出卡死。注意两次调用之间必须复位哈希核或者重新初始化上下文否则第二次调用会接着第一次的中间状态算结果完全错。复位方式看DTHE_CTRL寄存器的SW_RESET位写1再写0。2.3 关键寄存器位域与配置顺序DTHE_V2的寄存器不多但每个都有讲究。核心的几个DTHE_CTRL控制启停和复位DTHE_CFG选算法和模式DTHE_STS看状态DTHE_IN_FIFO写数据DTHE_OUT_FIFO读结果DTHE_CTX系列存上下文。配置顺序我总结成一句话先复位再配算法再灌上下文再喂数据最后读结果。顺序错了轻则结果不对重则引擎挂起。DTHE_CFG里选SHA-512的编码通常是0x4具体值查TRMHMAC模式位要置1。有个坑是HMAC模式位在有些TRM修订版里叫HMAC_EN有些叫MAC_MODE功能一样。上下文寄存器DTHE_CTX0到DTHE_CTX15存的是SHA-512的8个64位初始向量每个向量占两个32位寄存器。初始向量是固定的但如果你做的是续算比如分片哈希就要把上次的上下文读出来再写回去。读上下文要在引擎空闲时读运行中读会拿到脏数据。3. 实操过程与核心环节实现3.1 环境准备与寄存器基址确认我用的环境是AM62L EVM加TI的SDK调试手段是JTAG加内存映射读写。第一步是确认DTHE_V2的基址在TRM的存储器映射章节找DTHE条目通常是0x40900000这类地址。确认后用调试器读DTHE_STS寄存器如果读到全0或者全F说明时钟没开或者电源域没使能。AM62L的DTHE在某个电源域里需要在PSCPower Sleep Controller里使能对应模块。这一步不做后面所有寄存器读写都是无效的而且不会报错只是读回0。我踩过这个坑查了两小时才发现是电源域没开。时钟方面DTHE的工作时钟来自某个PLL分频频率影响吞吐但不影响功能。调试阶段用默认时钟就行不用折腾。中断可以先不配用轮询方式跑通再说。中断配置涉及DTHE_INT_EN和系统中断控制器等算法通了再补。3.2 SHA-512单块哈希的完整寄存器操作序列下面是我实测跑通的一段序列用伪代码表示你可以直接翻译成C。假设基址是BASE每个寄存器偏移按TRM。// 1. 软件复位 write32(BASE DTHE_CTRL, 0x1); // SW_RESET置1 while (read32(BASE DTHE_STS) RESET_BUSY); // 等复位完成 write32(BASE DTHE_CTRL, 0x0); // 清复位 // 2. 配置算法SHA-512非HMAC write32(BASE DTHE_CFG, SHA512_MODE); // 3. 灌初始向量8个64位共16个32位寄存器 for (int i 0; i 16; i) { write32(BASE DTHE_CTX0 i*4, sha512_iv[i]); } // 4. 喂一个128字节块 for (int i 0; i 32; i) { // 32个32位字 while (read32(BASE DTHE_STS) IN_FIFO_FULL); write32(BASE DTHE_IN_FIFO, block[i]); } // 5. 触发处理 write32(BASE DTHE_CTRL, START); // 6. 等完成 while (!(read32(BASE DTHE_STS) DONE)); // 7. 读结果8个64位 for (int i 0; i 16; i) { result[i] read32(BASE DTHE_OUT_FIFO); }这段序列里第3步的初始向量是SHA-512标准里定义的网上能查到不用自己算。第4步的块必须是padding后的完整块如果你只有一条短消息先软件padding成128字节再喂。第7步读结果时OUT_FIFO里是8个64位哈希值按大端序排列读出来直接就是标准哈希输出。3.3 HMAC的两次调用实现与密钥处理HMAC的代码是在SHA-512基础上包两层。先算K xor ipad和K xor opad各128字节。然后// 内层哈希 sha512_init(); sha512_update(K_ipad, 128); sha512_update(message, msg_len); sha512_final(inner_hash); // 得到64字节 // 外层哈希 sha512_init(); sha512_update(K_opad, 128); sha512_update(inner_hash, 64); sha512_final(hmac_result); // 得到最终HMAC这里的sha512_init就是上面那段寄存器序列的1到3步sha512_update是4到6步sha512_final是padding加最后一次update加读结果。注意sha512_final里的padding要按当前已处理的消息长度算不能固定。我写了个小函数算padding输入是累计消息长度输出是padding字节实测没问题。密钥处理有个细节如果密钥正好128字节K就是密钥本身不用补零。如果密钥是0字节空密钥K就是128个0这时候K xor ipad就是128个0x36。空密钥在测试时很有用因为标准HMAC测试向量里有空密钥的用例拿来验证实现对不对。3.4 性能实测与DMA模式切换轮询模式下我实测SHA-512处理1MB数据大概耗时几十毫秒具体数字取决于时钟频率和FIFO等待开销。这个性能对安全启动够用但对大流量TLS场景偏慢。切到DMA模式后吞吐能提升数倍因为CPU不用在FIFO上自旋。DMA配置涉及DTHE的DMA请求线和系统DMA控制器步骤是配DMA源地址为内存缓冲区目的地址为DTHE_IN_FIFO传输长度设为数据字节数然后启动DMA同时启动DTHE。DTHE每消费一个FIFO字就发一个DMA请求DMA自动搬下一个字。DMA模式的坑在于对齐和长度。源地址最好128字节对齐长度最好是128的整数倍否则尾部处理麻烦。我一般把数据padding到128整数倍再交给DMA这样DMA搬完DTHE也正好处理完最后一个块。如果长度不是整数倍DMA搬完最后一个不完整块DTHE会等更多数据你得手动补padding容易乱。所以宁可多搬几个字节的padding也别让DMA搬不完整块。4. 常见问题与排查技巧实录4.1 哈希结果不对的排查路径结果不对是最常见的问题排查要按顺序来。先确认电源和时钟读DTHE_STS看是否全0。再确认复位是否完成复位没完成就配算法配置会被忽略。然后确认算法编码写对了SHA-512和SHA-256的编码不同写错会得到另一个算法的结果。接着确认初始向量写对了写错一个字节结果就全变。最后确认paddingpadding错是最隐蔽的因为短消息可能碰巧对长消息就错。我整理了一个排查表按现象查原因现象可能原因排查方法读结果全0电源域未使能读PSC寄存器确认模块状态结果与标准不符初始向量错对照标准IV逐字节核对引擎挂起不输出块不完整确认喂了128整数倍字节HMAC结果错但SHA对密钥异或错检查ipad/opad是否0x36/0x5c第二次哈希结果错未复位两次调用间加SW_RESET4.2 上下文续算的注意事项分片哈希时第一片算完不要读最终结果而是读上下文寄存器把8个64位中间状态存下来。第二片开始时先复位配算法然后把存下的上下文写回DTHE_CTX再喂第二片数据。这里的关键是上下文写回后不要重新灌初始向量否则第一片的计算白费。我见过有人在续算时既灌IV又灌上下文结果上下文被覆盖等于从头算。读上下文要在DONE置位后、下一次START之前读这个窗口里上下文是稳定的。如果引擎还在跑读出来的是中间态不能用。另外上下文寄存器的读写顺序要和IV一致都是CTX0到CTX15顺序错了状态就乱了。4.3 中断与轮询的取舍经验调试阶段强烈建议用轮询因为中断涉及中断控制器配置多一层就多一个出错点。等轮询跑通结果和标准向量对上再切中断。切中断时注意清中断标志DTHE的中断是电平触发还是边沿触发要看TRM清标志的时机不对会丢中断或者重复进中断。我一般在中断服务程序里先读DTHE_STS确认是DONE中断处理完读结果再写DTHE_INT_CLR清标志。如果不清中断会一直触发CPU卡在ISR里出不来。提示DTHE_V2的中断和DMA可以同时用DMA完成中断和DTHE完成中断要分开处理别混在一个ISR里否则逻辑纠缠不清。4.4 与其他AM62L安全模块的协同DTHE_V2不是孤立的它和SA2UL另一个安全加速器、ROM代码、防火墙都有交互。安全启动时ROM先用DTHE算度量然后跳转到你的固件。你的固件如果也要用DTHE必须等ROM释放DTHE否则寄存器被ROM占着你写不进去。释放的标志通常是某个状态位或者ROM跳转前会复位DTHE。我建议固件启动后先读DTHE_STS确认空闲再开始配置。防火墙方面DTHE的寄存器区域可能被防火墙保护只有特定特权级能访问。如果你在非安全态访问被拦读回0或者触发异常。确认防火墙配置在FWL相关寄存器里调试阶段可以先关防火墙跑通再开。SA2UL和DTHE功能有重叠选哪个看场景DTHE适合哈希和HMACSA2UL适合批量对称加密。两者可以并行但共享内存带宽大流量时要注意带宽瓶颈。5. 寄存器配置速查与参数计算补充5.1 SHA-512初始向量与常量表SHA-512的8个初始向量是固定的我列出来方便你核对寄存器高32位低32位CTX00x6a09e6670xf3bcc908CTX10xbb67ae850x84caa73bCTX20x3c6ef3720xfe94f82bCTX30xa54ff53a0x5f1d36f1CTX40x510e527f0xade682d1CTX50x9b05688c0x2b3e6c1fCTX60x1f83d9ab0xfb41bd6bCTX70x5be0cd190x137e2179写的时候注意大小端DTHE_V2的寄存器是小端但SHA-512的IV在标准里是大端表示写进去之前要转成小端。我一般用htonl或者手动字节交换。这个转换错了结果会完全不对而且很难发现因为IV看起来“差不多”。5.2 Padding长度计算与边界情况Padding的长度计算是pad_len (128 - (msg_len 1 16) % 128) % 128然后总padding是1 pad_len 16字节。边界情况是(msg_len 1 16) % 128 0时pad_len为0但还是要补一个0x80和16字节长度总共17字节这时候会多出一个块。这个边界我专门测过如果漏了消息长度正好是111字节128-17时结果会错。111字节的消息加0x80和16字节长度正好128字节不需要额外块但112字节的消息加0x80后是113加16是129超过128需要两个块。这个分界点要记牢。5.3 HMAC密钥长度对配置的影响密钥长度分三种情况配置不同密钥长度K处理配置要点 128字节补零到128直接异或ipad/opad 128字节不处理直接异或 128字节先SHA-512得64字节再补零到128多一次哈希调用超过128字节的密钥先做一次SHA-512得到64字节摘要再补零到128字节当K。这一步容易忘忘了的话K就是原密钥截断或补零结果和标准HMAC不符。标准测试向量里有长密钥用例拿来验证这个分支最合适。6. 驱动集成与上层调用建议6.1 Linux内核crypto框架下的注册要点如果你要在Linux里用DTHE_V2得写一个crypto算法驱动注册sha512和hmac(sha512)两个算法。注册时cra_name填sha512cra_driver_name填dthe-sha512cra_blocksize填128cra_ctxsize填上下文大小。HMAC算法的cra_name填hmac(sha512)cra_blocksize也是128。注册完用crypto_alloc_ahash测试跑标准向量。驱动里init、update、final三个回调对应我前面说的三段。update里处理不完整块把不足128字节的数据缓存起来等凑够一块再喂DTHE。final里做padding喂最后一块读结果。这个缓存逻辑是驱动里最容易出bug的地方因为update可能被调用多次每次数据长度不定。我建议用一个128字节的缓冲区加一个长度计数器每次update先填缓冲区满了就喂DTHE并清空最后final处理剩余。6.2 用户态直接访问的可行性有些场景不想写内核驱动想在用户态直接怼寄存器。这需要把DTHE的寄存器区域通过/dev/mem映射到用户空间然后按前面的序列操作。可行性是有的但有两个限制一是需要root权限二是和内核里的其他DTHE用户可能冲突。如果系统里已经有内核驱动在用DTHE用户态再访问会打架。所以用户态方案适合裸机或者DTHE独占的场景不适合通用Linux发行版。用户态访问的另一个问题是缓存一致性。DTHE通过DMA读内存时如果内存被CPU缓存了DTHE可能读到旧数据。解决办法是用非缓存内存或者手动flush缓存。/dev/mem映射默认是非缓存的所以这个问题不大但如果你用的是mmap的缓存内存就要小心。6.3 安全启动链中的度量集成安全启动时DTHE用来算固件度量。典型流程是ROM算第一级度量和HMAC存到安全存储你的固件算第二级度量和第一级拼接再算HMAC逐级往上。这里的关键是度量顺序和拼接方式要和验证端一致否则验签失败。我建议把每级度量的长度和顺序写进文档别靠记忆。HMAC的密钥在安全启动里通常是设备唯一密钥存在OTP或者安全存储里DTHE配置时从那里读不要硬编码在固件里。度量集成还有个性能考量安全启动对时间敏感DTHE的轮询模式可能拖慢启动。如果启动时间超标切DMA模式或者把度量并行化——DTHE算哈希的同时CPU做其他初始化。但并行要注意依赖关系度量结果没出来之前不能跳转到下一级。7. 实操心得与后续扩展方向我在AM62L上折腾DTHE_V2这段时间最大的体会是寄存器配置本身不难难的是把“为什么这么配”想清楚。SHA-512的IV、padding规则、HMAC的双重结构这些在教科书里都有但落到具体寄存器上每个bit都要自己确认。我建议你第一次跑的时候拿一个已知的标准向量比如空字符串的SHA-512一步步跟每写一个寄存器就核对一次跑通了再换复杂输入。这样出问题能快速定位到是哪一步引入的。后续扩展的话DTHE_V2还支持SHA-256、SHA-1、MD5配置思路和SHA-512一样只是IV和块大小不同。对称加密方面DTHE支持AES的多种模式如果你要做TLS加速可以把AES和SHA-512串起来一个负责加密一个负责完整性。再往上可以研究DTHE和SA2UL的分工把哈希和加密分到两个引擎上并行吞吐还能再提。这些我还在试等跑通了再分享。最后分享一个小技巧DTHE的DTHE_STS寄存器里有个BUSY位任何寄存器操作前先读它为0再操作。这个习惯能避免大部分“配置不生效”的问题因为引擎忙的时候写配置会被忽略而且不报错。我一开始没注意这个配了半天没反应后来加了BUSY检查一次就通了。