尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

嵌入式Linux下ATSHA204A加密芯片驱动开发与防抄板实战

嵌入式Linux下ATSHA204A加密芯片驱动开发与防抄板实战 简介面向嵌入式安全与Linux驱动开发场景这份资源提供ATSHA204A加密芯片的驱动源码可帮助开发者完成芯片初始化、I2C通信与SHA-256认证等功能的集成。压缩包共12个文件含6个头文件、4个C源文件、1个Makefile和1个Kconfig整体仅32KB。头文件定义硬件接口与数据结构C源码实现模块注册、I2C读写、命令封装等核心逻辑Makefile与Kconfig方便直接编译进内核或作为模块加载。当前已有485人学习下载适合正在做安全芯片适配或内核驱动开发的工程师参考。阅读源码可快速理解ATSHA204A在Linux下的驱动框架、命令发送流程及用户态交互方式驱动中的ioctl接口和调试辅助函数为安全启动、密钥管理或Android HAL层对接提供了可复用基础也能借鉴其代码风格编写其他I2C类加密芯片驱动对入门和进阶开发者均有参考价值。 这两年做嵌入式 Linux 项目我越来越觉得安全这块不能等产品被抄了才后悔。很多产品就是一块主控加一片 Flash固件被人直接读出来、逆向、克隆正品还没卖几台仿品已经满大街了。后来我在几个项目里用了一颗 Microchip 的 ATSHA204A 加密芯片配套自己写 Linux 驱动才把防抄板和配件认证的问题真正按住。今天就把整个方案从选型、硬件接线、驱动源码实现到调试验证的过程完整拆一遍给想搞加密认证但还没找到门路的朋友一个可以直接参考的路径。ATSHA204A 不是一颗功能很花哨的芯片它的核心能力就是做 SHA-256 和 HMAC 运算内部带有随机数发生器密钥一旦烧进去之后谁都没法再读出来。在 Linux 系统里我们需要做的就是在主控和这颗芯片之间把 I2C 通信打通并给上层提供调用接口。这个工作说难不难但坑也不少尤其是时序和锁配置这两块搞不好能把人耗上一周。下面直接进入正题。1. 项目背景与整体设计思路1.1 为什么选 ATSHA204A而不是 ATECC608A 这类芯片选芯片之前先要想清楚你到底要防什么。ATSHA204A 走的是对称认证路线芯片里存一把密钥主机端也有一把一样的密钥通过 MAC、CheckMAC 这类命令完成认证。ATECC608A 则支持 ECC 非对称算法私钥可以完全不出芯片安全性上限更高但配置也更复杂一颗的价格差不多是 ATSHA204A 的好几倍。我的判断标准很简单如果是产品固件防抄板、防第三方配件伪装原厂ATSHA204A 的成本和开发量都很合适。如果是要做云端证书签发、设备身份需要更硬核的密码学保证那再考虑 ATECC608A。ATSHA204A 我现在的项目里主要用在三个地方固件防抄板开机时主控发起认证只有合法芯片能算出正确的 MAC否则系统拒绝继续运行。配件/耗材认证电池、墨盒、传感器这类可更换部件里放一颗主机通过认证来判断是不是原装。设备与云端绑定芯片生成的随机数参与会话密钥协商防止设备身份被克隆。说白了它就像给产品装了一把独立的锁锁芯不在主控里哪怕你把我 Flash 里的固件全部抄走也复制不出这把锁。1.2 驱动方案选型主线内核驱动还是自研字符设备驱动拿到 ATSHA204A 之后Linux 侧有两条路可以走。一条是启用内核主线自带的驱动对应内核配置项是 CONFIG_CRYPTO_DEV_ATSHA204A该驱动基于内核 crypto API 工作配合 Microchip 官方用户态库 cryptoauthlib 使用。另一条是自研一个 I2C 字符设备驱动自己实现 Wake、Nonce、MAC、Read 这些命令的收发再以 ioctl 的形式暴露给应用层。我在第一批样机上是直接用主线驱动做的验证毕竟不需要写代码配置一下设备树就能跑起来。但用到量产阶段我开始偏向自研驱动原因比较现实对比项主线 crypto API 驱动自研字符设备驱动开发成本低配置即可中等核心代码几百行定制能力受内核框架限制完全可控内核版本依赖依赖较新的内核几乎无依赖上层配合必须配 cryptoauthlib自定 ioctl 协议排障难度框架层帮你消化了一部分细节出事不好查所有逻辑自己掌握容易定位如果你的产品内核版本比较老或者你要的命令流程和官方库不完全一致自研反而更省心。我最终采用的方案就是自研驱动驱动层只负责和芯片打交道上层再封装一层认证逻辑库这样后续换芯片或者调整算法内核侧基本不用动。2. 底层协议与硬件连接解析2.1 I2C 地址、引脚和外围电路ATSHA204A 的 I2C 从机地址是 0x647 位地址。换算成 8 位写地址是 0xC8读地址是 0xC9。这里第一步就很容易踩坑设备树里 reg 属性必须写 7 位地址 0x64但有的驱动代码习惯用 8 位地址一旦混了I2C 核心就匹配不上。硬件上除了标准的 SDA、SCL 两根线还有一个细节需要注意就是唤醒时序。芯片在空闲时会进入低功耗状态发送命令前必须先把 SDA 线拉低至少 60 微秒释放后再等一段时间芯片才会进入可接收命令的状态。普通的 I2C 控制器不会帮你产生这么长的低电平脉冲所以实际电路上常常要把芯片的 SDA 引脚额外接一路 GPIO或者把 GPIO 并联到 SDA 线上专门用来做 Wake 操作。外围电路方面I2C 上拉电阻一般选 2.2k 到 10k 之间。有些开发板的 I2C 总线上已经带上了上拉再接一颗就容易造成信号上升沿过缓通信不稳定。电平匹配也要注意ATSHA204A 工作电压范围比较宽但和主控 I2C 电平不一致时还是要加电平转换不能硬接。2.2 数据包格式与命令交互流程和 ATSHA204A 通信的所有数据都是固定格式的包理解了这个包结构驱动就完成了一半。命令包的格式如下控制字节0x03 表示这是一个完整包。长度字段2 字节小端序表示从控制字节开始到结尾 CRC 为止的总字节数。命令字比如 Wake 之后经常用的 Read、Nonce、MAC、DeriveKey 等具体命令码以芯片手册为准。参数区不同命令带不同参数。CRC 字段2 字节CRC-16 校验注意字节序。响应包的控制字节是 0x04后面跟着长度、状态/数据区和 CRC。在芯片还在处理命令时读取响应会得到一个 0x00 的忙标志这时候需要等一会儿再继续读。如果收到的值和 CRC 对不上就要检查是不是时序太紧导致命令包没发完整。驱动里的命令流程一般是这样的拉低 SDA 至少 60 微秒实现 Wake。等待芯片唤醒完成一般建议等 2 到 3 毫秒宁多勿少。发送命令包。根据命令类型等待执行完成像 Nonce、MAC 这类运算命令通常要等几十毫秒。轮询读取响应包先读到 0x04 开头才算真正拿到响应。校验 CRC。最开始我自己写时序的时候为了快把延时压得很紧结果时不时就出现偶发失败。后来把延时放宽到手册推荐值的 1.5 倍左右通信就稳了。这类芯片对时序的要求虽然不算苛刻但余量留得太少量产时不同批次的芯片就会开始出幺蛾子。3. Linux 驱动源码核心实现3.1 设备树配置与驱动框架注册我是在 i2c1 总线上挂这颗芯片的设备树节点写法如下i2c1 { clock-frequency 100000; status okay; atsha204a64 { compatible atmel,atsha204a; reg 0x64; wake-gpios gpio4 3 GPIO_ACTIVE_HIGH; }; };reg 字段是 0x64不是 0xC8这个前面说过了。wake-gpios 就是用来产生唤醒脉冲的 GPIO实际电平极性要根据你的电路连接来定有的是低电平唤醒有的电路反相后变成高电平唤醒这里需要根据实际硬件来配置。驱动侧的核心是一个标准的 i2c_driver 结构重点是 probe 函数里做三件事申请设备对象结构体、初始化互斥锁、把 misc 设备注册上去。struct atsha_dev { struct i2c_client *client; struct gpio_desc *wake_gpio; struct mutex lock; struct miscdevice misc; }; static int atsha_probe(struct i2c_client *client) { struct atsha_dev *dev; dev devm_kzalloc(client-dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; dev-client client; dev-wake_gpio devm_gpiod_get(client-dev, wake, GPIOD_OUT_LOW); if (IS_ERR(dev-wake_gpio)) return PTR_ERR(dev-wake_gpio); mutex_init(dev-lock); i2c_set_clientdata(client, dev); /* misc 设备注册部分省略主设备号使用 MISC_DYNAMIC_MINOR */ return 0; }为什么这里选择 mutex 而不是 spinlock因为 I2C 传输本身可能需要睡眠等待mutex 允许在持有锁的时候睡眠spinlock 则不允许拿 spinlock 包着 i2c_transfer 很容易出问题。另外这把锁不只是保护一次 read/write还要保证一个完整的“Wake 发命令 读响应”流程不被其他线程插进来打断。因为 ATSHA204A 是个状态机如果两个线程的命令交错执行芯片状态直接就乱了。3.2 驱动核心唤醒、命令收发与 IOCTL 实现Wake 操作本质就是拉低 GPIO 一段时间。这里有个小细节GPIO 拉低的时间必须保证在 60 微秒以上但也不能太久否则芯片可能进入别的异常状态。我一般拉到 100 微秒左右释放之后再等 2 毫秒以上才开始发命令。static int atsha_wake(struct atsha_dev *dev) { gpiod_set_value(dev-wake_gpio, 1); usleep_range(100, 150); gpiod_set_value(dev-wake_gpio, 0); usleep_range(2000, 2500); return 0; }命令发送和响应读取是驱动里最基本的两个函数。发送命令就是构造一个 i2c_msg 发出去这里没什么玄机核心在于读响应前的等待和忙轮询。static int atsha_send_cmd(struct atsha_dev *dev, const u8 *cmd, size_t len) { struct i2c_msg msg { .addr dev-client-addr, .flags 0, .len len, .buf (u8 *)cmd, }; return i2c_transfer(dev-client-adapter, msg, 1) 1 ? 0 : -EIO; } static int atsha_read_resp(struct atsha_dev *dev, u8 *buf, size_t maxlen) { u8 head; int ret, i; for (i 0; i 20; i) { ret i2c_master_recv(dev-client, head, 1); if (ret ! 1) return -EIO; if (head 0x04) break; if (head ! 0x00) return -EBADMSG; msleep(5); } if (head ! 0x04) return -ETIMEDOUT; /* 这里再根据响应包长度字段读取完整数据并做 CRC 校验 */ return atsha_recv_full(dev, buf, maxlen); }在等待时间内芯片没有准备好时读到的第一个字节是 0x00所以读取函数要循环重试。循环次数和间隔根据命令的执行时间调整我常用的策略是循环 20 次每次间隔 5 毫秒这样最多等 100 毫秒覆盖大多数命令的执行时间绰绰有余。接下来是 ioctl 接口设计。为什么不用简单的 read/write 操作因为 ATSHA204A 的命令类型很多每种命令的参数和返回数据格式都不一样read/write 很难表达这种变化。ioctl 的每个命令对应一个具体的操作结构清晰也不容易被应用层误用。struct atsha_msg { void __user *data; size_t len; }; #define ATSHA_IOCTL_WAKE _IO(A, 0x01) #define ATSHA_IOCTL_READ _IOWR(A, 0x02, struct atsha_msg) #define ATSHA_IOCTL_MAC _IOWR(A, 0x03, struct atsha_msg) static long atsha_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct atsha_dev *dev file-private_data; struct atsha_msg msg; int ret; if (cmd ATSHA_IOCTL_WAKE) { mutex_lock(dev-lock); ret atsha_wake(dev); mutex_unlock(dev-lock); return ret; } if (cmd ATSHA_IOCTL_MAC) { if (copy_from_user(msg, (void __user *)arg, sizeof(msg))) return -EFAULT; mutex_lock(dev-lock); /* 组装 MAC 命令包、发送、读响应再把结果 copy_to_user */ ret atsha_do_mac(dev, msg); mutex_unlock(dev-lock); return ret; } return -ENOTTY; }在正式实现里MAC 命令的组装涉及 Nonce、Write 等前置流程这里不展开所有代码但设计思想是一样的驱动只做收发和基本状态管理业务的认证逻辑放到用户态库里去拼装。这样驱动可以保持简单后续要支持新的衍生命令也不用频繁改动内核代码。3.3 用户空间验证程序驱动写完第一个要跑通的用例是读取芯片的 9 字节 Serial Number它能直接证明 I2C 通信和驱动框架都没问题。简单示例static int read_serial(int fd, unsigned char *serial) { struct atsha_msg msg; msg.data serial; msg.len 9; if (ioctl(fd, ATSHA_IOCTL_READ, msg) 0) return -1; return 0; } int main(void) { int fd; unsigned char serial[9]; int i; fd open(/dev/atsha204a, O_RDWR); if (fd 0) { perror(open); return 1; } if (ioctl(fd, ATSHA_IOCTL_WAKE, 0) 0) { perror(wake); return 1; } if (read_serial(fd, serial) 0) { perror(read serial); return 1; } printf(serial:); for (i 0; i 9; i) printf( %02X, serial[i]); printf(\n); close(fd); return 0; }如果你用的是主线内核驱动对应的用户态校验工具是官方仓库里带的小程序它会自动枚举 /dev 下的设备然后走完整握手流程适合做产线功能验证。自研驱动的话就需要自己写类似的测试程序把 MAC 认证流程完整跑一遍再交付。4. 常见问题与排查技巧实录4.1 I2C 通信失败时的排查思路ATSHA204A 和主控之间最常见的故障就是 I2C 探测不到设备。先用 i2cdetect 扫描总线理论上能在 0x64 位置看到设备。如果扫描不到第一步不是去查驱动而是确认芯片是否被唤醒。因为芯片空闲时处于低功耗状态直接扫描是扫不到的必须先拉低 SDA 线产生 Wake 脉冲。我遇到过一次很隐蔽的问题板子的 I2C 控制器不支持某种特殊的时钟延展而 ATSHA204A 在事务处理期间会把 SCL 拉低一段时间控制器如果不能正确处理这个状态通信就会卡死。当时查了很久最后用示波器对比 SCL 波形才定位到是控制器兼容性问题换了一个 I2C 控制器口就好了。总线频率也值得检查。ATSHA204A 手册里最高支持 1MHz但很多主控的 I2C 控制器在 400kHz 下波形质量并不好尤其是上拉电阻和走线阻抗不匹配时。我量产项目里统一跑 100kHz速度完全够用稳定性却提高了不少。4.2 配置区和数据区的锁死问题这是整个项目里最让人肉疼的坑。ATSHA204A 内部有配置区、OTP 区和数据区数据区又分成 16 个 slot密钥只能写在 slot 里。芯片提供了 Lock 功能可以把这些区域锁定锁定之后配置就不可修改外界也读不出密钥。但 Lock 是一次性操作锁错了或者写错配置这颗芯片就废了只能换新的。开发调试阶段我强烈建议不要执行任何 Lock 操作。你一开始可能觉得“我肯定不会写错”但真的有人会手滑把 Data Zone 提前锁了然后又发现密钥没写完整结果整片芯片报废。产线量产的时候也不要一上来就锁建议流程是在产测工装上把密钥写入指定 slot。做一次完整功能测试确保芯片能正常完成 MAC 认证。确认无误后再执行 Lock。Lock 之后再跑一遍全功能测试通过才出货。4.3 上层业务该用官方库还是自己封装最后聊一下用户态方案的取舍。Microchip 官方的 cryptoauthlib 功能很全支持多款芯片抽象层也做得不错。主线内核驱动配合它确实能用但如果你的产品对代码体积敏感或者你只需要其中两三个命令流程我建议在自研驱动的用户态库里只实现需要的那几个流程。在我现在的项目里用户态库只实现了三个接口设备认证、固件校验、配件校验。核心就是 Nonce MAC 的组合。每次认证主机端生成随机数发给芯片芯片用内部密钥算一个 MAC 回来主机端再用同样的密钥和随机数算一遍两边一致才放行。这套逻辑用官方库当然能写但自己实现也就百来行代码还省掉了与驱动框架之间的磨合成本。另外如果你真的要用官方库注意内核必须打开 CONFIG_CRYPTO_DEV_ATSHA204A 之外还需要确认 AF_ALG 或相关 crypto 接口在内核里可用否则主线和用户态库之间的衔接会比较别扭。最后再分享一点实操心得ATSH204A 这套东西用熟了之后你会觉得它非常简单但第一次接触的时候我也被时序问题折磨过。后来总结下来这类芯片驱动说白了就是三件事时序、I2C、加锁。时序保的是芯片状态机的正确推进I2C 是通信的物理基础加锁保证多进程环境下操作不交错。把这三件事想清楚驱动代码写起来就顺了。给新上手的朋友一个最实际的建议开发阶段把密钥相关流程拆开做先保证 Wake 和读 Serial Number 稳定再去做 Nonce 和 MAC。不要一上来就想着把整个认证流程拼完再去调试那样出了问题根本不知道是驱动问题、时序问题还是密钥配置问题。芯片锁配置这种事宁可晚一步绝不能早一步等所有功能验证完再锁能帮你省掉一大堆返工时间。本文还有配套的精品资源点击获取
返回列表