十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

dw-mshc硬件哈希加速:从寄存器布局到Linux Crypto驱动实战

dw-mshc硬件哈希加速:从寄存器布局到Linux Crypto驱动实战 简介Synopsys DW-MSHC 控制器参考包面向嵌入式系统与 SoC 开发者旨在帮助理解高速存储接口中多标准主机控制器的架构以及 DW 双倍数据速率数据通路的实现。压缩包共 2 个文件包含一份 txt 规格说明文本与一份 C 源码示例整体仅约 4KB轻量紧凑、便于快速查阅。文档明确该控制器遵循 Synopsys DW-MSHC 规范常用于 SD、MMC 等存储设备的高速通信场景源码则覆盖时钟管理、双倍速率数据路径、CRC/ECC 错误检测与校正、电源管理、中断处理等关键模块的 C 语言实现。读者可对照文档与代码掌握控制器的寄存器配置、多时钟域同步、错误恢复与数据吞吐提升机制理解从协议规范到实际驱动的落地链路并将其用于项目移植或驱动开发参考。已有 769 人学习浏览是一份高信息密度的嵌入式存储控制器参考资料。1. dw-mshc 在 SoC 里的正确位置补上硬件哈希而不是抢 CPU“synopsys-dw-mshc”这类交付包标题拆开看就是两个经常弄混的关键词“dw”指 Synopsys DesignWare IP 系列“mshc”在它的资料体系里通常指向 Multi-Standard Hash Controller也就是支持 SHA-1/SHA-2/SM3 等算法的一套可综合哈希引擎。和你在 Linux 里见过的 dw_mmc、dw_apb_uart 这类外设性质不同dw-mshc 大多数时候不接存储也不接 console而是做一件透明但高频的事把摘要计算的 64 轮迭代挪进 RTL 状态机让 CPU 只负责给消息分片和回收结果。遇到这个标题的人通常不是只用 OpenSSL 的软件工程师而是三类角色给 SoC 做 IP 集成的设计/验证工程师、给板卡裁剪 BSP 的内核开发以及在 FPGA 原型上调试 crypto 加速的软件人员。对这三类人真正值得动手复现的部分是一致的先建立一套寄存器 view 和流状态机的对应关系再把它接进 Linux crypto 框架最后用测试向量和软硬对照把结果钉死。2. DW_MSHC 的多流工作方式与寄存器布址哈希加速背后的状态机边界2.1 为什么叫“MSHC”一个哈希引擎里维护多条流哈希加速最容易被低估的一点是单独把 SHA-256 的 64 轮迭代做成状态机并不难难的是同时服务多个上下文。Secure Boot、文件系统校验、网络签章往往同时发起摘要请求如果每个请求都独占一个硬件引擎面积和功耗都不划算。Synopsys 在 dw-mshc 上采取的设计思路是在 IP 内部放一个真正的计算引擎外加一组上下文槽位每个槽位保存算法类型、长度计数和中间摘要值驱动用流 IDstream id写寄存器来切换上下文。这里把流理解成“带保存点的计算过程”就够了。CPU 往 FIFO 里送一个 64B 消息块引擎算完会更新内部摘要状态切到另一条流时新的摘要值被写到另一组保存寄存器原来的值继续留在这个上下文里。多核系统两个线程各自提交 SHA-256如果驱动把流 ID 隔离做对了它们可以轮流占住同一个引擎而不互相踩踏。这个设计直接关系到驱动怎么写你不需要在软件里维护几十个 midstate只需要在请求提交时把上下文选好并在 DONE 之后及时取走摘要。省下来的内存访问和 memcpy往往是嵌入式场景下比算力更值钱的资源。2.2 dw_mshc 常用寄存器布局一份够看的地址底图dw-mshc 的寄存器偏移在不同 IP 配置下会有差别但基本物理分区是稳定的。下面这张表按我常见到的交付风格整理适合先拿来画驱动骨架真正 tapeout 前以你们签核的 DesignWare 手册为准偏移寄存器名作用0x000MSHC_CTRL算法选择、流 ID 选择、START 触发0x004MSHC_CFG大小端、DMA 使能、完成中断使能0x008MSHC_STATidle/busy、FIFO 空满、错误标志0x00CMSHC_SRST软复位自清零0x010MSHC_BLOCK_CNT已提交的数据块数量0x014–0x04FMSHC_FIFO消息块写入区按 512 bit 对齐0x080–0x09FMSHC_DIGEST摘要输出区0x0C0MSHC_STREAM_ID多流上下文切换从驱动视角看真正需要反复打交道的只有 CTRL、STAT、FIFO、DIGEST 四个区域。CFG 在初始化时配置一次日常请求尽量少动它SRST 只在驱动加载或出错恢复时写。BLOCK_CNT 对调试很有用可以确认驱动认为发了多少块、硬件实际收了多少块两边不一致说明 FIFO 写入丢了数据。寄存器位宽一般按 AHB/APB 总线位宽定大多数设计是 32 位。写 FIFO 时不能把整个消息块塞成一个很长的 write因为 DW-mshc 的 FIFO 口往往固定 32 位宽需要每次写一个 word。块缓冲和位宽限制是后面性能优化的主要矛盾源。2.3 一次 SHA-256 提交的最小代码路径不考虑 DMA 的情况下提交一个 64B 数据块给 dw-mshc 的软件路径非常短。下面的函数是一次轮询式写入的骨架static void dw_mshc_write_block(struct dw_mshc_dev *dw, const u8 *data, u32 len) { u32 tmp; int i; /* 先把本次会话的算法和消息块数固定下来 */ writel(DW_MSHC_CFG_BE, dw-regs MSHC_CFG); writel((len 63) 6, dw-regs MSHC_BLOCK_CNT); /* 大端序数据拆成 32 位 word 写入 FIFOIP 内部按 64B 对齐取用 */ for (i 0; i 16; i) { tmp get_unaligned_be32(data i * 4); writel(tmp, dw-regs MSHC_FIFO i * 4); } /* 置 START 位引擎开始消化这一块 */ writel(DW_MSHC_CTRL_START | DW_MSHC_CTRL_SHA256, dw-regs MSHC_CTRL); /* DONE 置位前不能读摘要否则大概率读到旧值 */ while (!(readl(dw-regs MSHC_STAT) DW_MSHC_STAT_DONE)) cpu_relax(); }这里最值得注意的参数是BLOCK_CNT。它决定了硬件在哪一个 block 做填充SHA-256 规范要求在消息后补 0x80、补零、再补 64 位原始长度dw-mshc 的做法一般是由驱动告知总块数引擎自行判断在最后一个块里做填充。如果你每次都把实际长度除 64 向上取整填进去但最后一块硬件又不负责补长度字段结果会和 OpenSSL 完全对不上。上板调试时先拿一个恰好 64B 倍数的消息测通过再增加尾部长度是最稳的推进路径。3. 把 dw-mshc 接进 Linux crypto 框架从 platform 驱动到 ahash 实例3.1 probe 里的四件套地址、中断、时钟和复位dw-mshc 在 Linux 里通常以 platform device 形式出现设备树 compatible 常用“snps,dw-mshc”。probe 阶段不需要做任何哈希计算只要把运行环境准备好。一个典型的 probe 骨架如下static const struct of_device_id dw_mshc_of_match[] { { .compatible snps,dw-mshc, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dw_mshc_of_match); static int dw_mshc_probe(struct platform_device *pdev) { struct dw_mshc_dev *hw; struct resource *res; int ret; hw devm_kzalloc(pdev-dev, sizeof(*hw), GFP_KERNEL); if (!hw) return -ENOMEM; platform_set_drvdata(pdev, hw); res platform_get_resource(pdev, IORESOURCE_MEM, 0); hw-regs devm_ioremap_resource(pdev-dev, res); if (IS_ERR(hw-regs)) return PTR_ERR(hw-regs); hw-irq platform_get_irq(pdev, 0); if (hw-irq 0) return hw-irq; /* 时钟未开时读写寄存器会让总线 hang务必先 clk_prepare_enable */ hw-clk devm_clk_get(pdev-dev, NULL); if (IS_ERR(hw-clk)) return PTR_ERR(hw-clk); clk_prepare_enable(hw-clk); /* 软复位并等待自清零 */ writel(DW_MSHC_SRST_ASSERT, hw-regs MSHC_SRST); while (readl(hw-regs MSHC_SRST)) cpu_relax(); ret dw_mshc_init_streams(hw); if (ret) goto err_clk; return crypto_register_ahash(hw-alg); err_clk: clk_disable_unprepare(hw-clk); return ret; }这里每一行都对应一个经典故障点。ioremap 失败通常不是驱动问题而是设备树 reg 属性没对上地址时钟没开就访问寄存器在部分 ARM SoC 上表现为读回全 0而不是总线错误软复位等待不能加超时退出的版本最坑硬件一旦挂死驱动就永远 spin 在 while 里单看 log 根本看不出是复位没完成。3.2 用 ahash 接口做请求分发init/update/final 三件套Linux Crypto API 里哈希驱动最常用的是ahash接口。它比老的shash更适合 dw-mshc因为 ahash 请求天然带异步语义后续接 DMA 时不必改接口。注册时只需要填充alg里的算法名、摘要长度和回调函数static struct ahash_alg dw_mshc_sha256_alg { .init dw_mshc_hash_init, .update dw_mshc_hash_update, .final dw_mshc_hash_final, .digest dw_mshc_hash_digest, .halg.digestsize SHA256_DIGEST_SIZE, .halg.base.cra_name sha256, .halg.base.cra_driver_name dw-mshc-sha256, .halg.base.cra_priority 300, .halg.base.cra_flags CRYPTO_ALG_ASYNC, .halg.base.cra_blocksize SHA256_BLOCK_SIZE, };cra_priority是这里最容易被忽略的参数。它决定内核在同时存在软件 sha256 和硬件 sha256 时优先选谁。Synopsys 硬件通常应该比通用软件实现快但并发场景下如果中断处理和 DMA 分配成本太高priority 设得太大会适得其反。先用 300 这类中等值拿到实测吞吐后再调比一开始抢到最高优先级更稳妥。update回调的核心工作是把散落的消息分片送给 dw-mshc 的上下文缓冲攒满 64B 就执行一次真正的硬件块提交。这个缓冲必须放在请求上下文dw_mshc_reqctx里不能放到设备全局结构里否则两个并发请求会互相覆盖消息内容。3.3 HMAC 场景下别绕过 setkey密钥块的 4 种形态dw-mshc 的 HMAC 加速依赖 IP 内部的 ipad/opad 预处理驱动要在setkey回调里把密钥先裁剪成目录块大小。SHA-256 的块大小是 64B处理规则如下密钥长度处理方式key_len 64直接复制到 key_pad剩余补 0x00key_len 64先用 SHA-256 压缩成 32B再补零到 64BHMAC 算法选择硬件通过 CTRL 里算法位选择 HMAC-SHA256setkey 里最容易犯的错误是直接把长密钥原样写进寄存器。超过块长的密钥必须先做一次摘要否则 HMAC 的第一次内层哈希一开始就是错的而且这种错误在测试向量上非常隐蔽因为只有长密钥用例会失败短密钥用例全绿。提交代码前把 RFC 4231 里的 64B 及以上密钥用例都跑一遍比多写十个调试打印都有用。4. 把 DW_MSHC 吞吐跑上去轮询、中断和 DMA 描述符的参数取舍4.1 先回答“要不要走中断”小消息轮询更快dw-mshc 的 DONE 状态可以在轮询里被捕获也可以触发中断。很多人默认中断比轮询好但实际上哈希请求的消息块长短直接决定两者差异。下面是一组在常见嵌入式时钟下的经验值单次消息长度推荐模式理由 1KB轮询中断响应和调度开销接近计算时间1KB ~ 64KB中断CPU 可以在硬件计算期间做别的活 64KB中断 DMA减少 CPU 逐 word 写 FIFO 的时间判断标准不是“计算快不快”而是“CPU 等待时间占比高不高”。消息短时CPU 提交几个块之后马上就能得到结果轮询的 while 循环只转几圈如果坚持走中断一次上下文切换可能比硬件算完整个 SHA-256 还贵。对于几十 KB 的长消息驱动可以把请求切成长度合适的 chunk一次申请 DMA 描述符链然后等最后一个 DONE。chunk 太大DMA 描述符占用内存多太小硬件流水线被频繁打断。常见设计里每描述符承载 8~16 个 block也就是 512B~1KB折中效果最好。4.2 DMA 描述符要做成环字段和参数一张表说清如果 dw-mshc 集成了 AXI DMA 写通道驱动只需要维护一组描述符。常见描述符项的格式接近下面这样struct dw_mshc_desc { u32 src_addr; /* 消息所在物理地址要求 4B 对齐 */ u32 len; /* 本段有效字节数硬件会向上取整到 64B */ u32 cfg; /* burst、中断使能、最后一段标记 */ u32 next_addr; /* 下一描述符地址0 表示结束 */ };参数含义和配套约束src_addr必须是物理地址不能直接塞内核虚拟地址DMA 引擎不经过 MMU。len字段的向上取整是坑点硬件按块消化最后不足 64B 的部分会被当作一个完整块发起读操作所以 DMA buffer 尾部必须留足 zero padding否则可能读越界。cfg里推荐把 burst 设为 8 或 16取值太小会拉低 AXI 总线效率太大在部分 SoC 上触发带宽争抢。描述符环的 head/tail 指针要放在 uncached 内存里否则 CPU 更新 tail 后 DMA 可能读到 cache 里的旧值。这些参数在验证阶段全设成最小值能跑通但性能测试时一定要按实际总线位宽和 SoC 带宽重新调直接用默认值上线的项目八成会来一次“为什么硬件加速比软件还慢”的复盘。4.3 dw-mshc 常见的三个寄存器级坑第一个坑是大小端配置。dw-mshc 的 FIFO 按 32 位 word 输入消息本身可能是大端也可能是小端。SHA-256 标准里消息字节序和 word 内字节序都需要一致很多驱动只在 CFG 里选了 big-endian却没有对消息做get_unaligned_be32转换结果摘要前 8 字节是对的后面全乱。第二个坑是“上一个请求的摘要没取完就提交下一个请求”。部分 IP 版本不会自动清 DIGEST 区而是累加或者保留旧值。驱动必须在 DONE 后立刻读取摘要并把上下文重置到初始 IV否则同一个算法的连续请求会得到第二次结果等于第一次结果或者中间态。第三个坑和综合工具有关。如果是在 Design Compiler 2024.03 这类新版本里做时序收敛dw-mshc 的异步复位和 FIFO 满标志经常被综合工具优化出问题。驱动侧的自保手段是每次写 FIFO 前检查 STAT 的 FIFO_FULL 位而不是假设写入一定成功。多一次 readl 对性能影响不大但能挡掉大部分仿真和上板不一致的问题。5. 用三组对照向量验证 dw_mshcSHA-256 标准用例加软硬交叉回归5.1 先跑最短的“abc”和“空串”拿到一块能跑通的 dw-mshc 驱动第一步不是性能测试而是把算法正确性钉死。SHA-256 最经典的向量是输入“abc”得到ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad。这个用例还覆盖了不足 64B 消息的填充路径“abc”只有 24 bit硬件必须把 0x80 填充到 56B并在最后 8B 写入原始长度 24。第二个标准用例是空串摘要固定为e3b0c442 98fc1c14 9afbf4c8 996fb924 27ae41e4 649b934c a495991b 7852b855。空串最容易暴露驱动的边界处理问题很多驱动在长度为零时会直接跳过 update但 final 阶段必须让硬件至少走完一次填充。5.2 用长消息暴露填充和 DMA 对齐问题短向量通过后构造一个 1000000 个字符a的长消息测回归标准结果是cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0。这种长度能覆盖多 block 连续提交、长度字段跨 word 写入、以及 DMA 描述符链接等路径。在 Linux 里做软硬对照最直接的方式是写一个内核模块把同一段数据分别交给 dw-mshc 和内核自带的sha256_generic对比结果# 把驱动编译进内核后用 cryptomgr 自测触发算法注册验证 modprobe dw-mshc cryptomgr_test -m sha256cryptomgr_test是内核 crypto 自测框架会跑一批预置向量任何一位不匹配都会在 dmesg 里留下失败记录。再用 user space 工具做一轮长消息吞吐采样把每次请求的 start/end 时间记下来看批量提交时每 KB 耗时是否呈线性下降。5.3 最后的收尾技巧把 wrap 风险变成回归用例多流驱动的最后一道验证是把同一算法、不同长度的消息并发提交例如 8 个线程同时做 sha256长度分别从 1B 到 64KB 错开。此时如果STREAM_ID切换和摘要读取竞争处理得不好会出现极少数请求返回错误摘要且复现概率不稳定。我的做法是在驱动里保留一个 debugfs 节点每次 DONE 后打印 stream id、block count 和摘要前 8 字节再把这组数据喂给脚本和软件 sha256sum 做逐行比对连续跑一万次没有一条 diff才认为多流逻辑真正可信。本文还有配套的精品资源点击获取
返回列表