主机边界:默认使用标准库模型、协议向量、配置数据和故障矩阵;没有目标板也能完成状态、帧、校验、兼容和交付清单测试。
目标机边界:真实寄存器副作用、总线电气、IRQ/DMA、时钟/复位、供电、器件行为、工具链和回滚路径必须回到当前芯片、板卡、器件和官方资料。
01 · DRIVER LAYERS
驱动分层:谁拥有哪一个事实
驱动不是一组把寄存器写成某个数的函数,而是把硬件事实转换成稳定软件能力的边界。寄存器层知道位域和时序,HAL 组织通用外设操作,设备驱动管理一个具体器件,服务层解释单位和业务语义,应用层只依赖合同。本单元建立分层坐标,避免抽象泄漏和责任重叠。
9.1.1 从寄存器事实到软件能力
一个可维护的驱动栈先回答‘谁拥有哪一个事实’:寄存器层拥有位域和清除语义,HAL 拥有一组外设的通用操作,设备驱动拥有器件初始化与状态,服务层拥有单位、范围和业务转换,应用拥有策略和用户目标。若每层都可以改同一个寄存器,故障就无法定位;若没有一层负责状态机,初始化、复位和恢复会散落在调用者之间。
9.1.2 位域、读改写与副作用
读写寄存器并不等同于完成动作。某些状态位写 1 清除,某些配置必须按顺序写入,某些字段只能在禁用状态修改,某些读操作会弹出 FIFO 或清除中断。把这些副作用藏在一个经过命名的低层函数里,才能让上层看到‘配置成功’或‘事件已取走’这样的可解释语义。
C · LAYERED DEVICE APItypedef struct { int (*read_raw)(void *ctx, uint8_t *buf, size_t n); int (*reset)(void *ctx); bool (*ready)(void *ctx); } device_ops_t; /* application sees the contract, not peripheral registers */
接口只暴露设备能力;寄存器、时钟、复位和总线副作用留在实现层。
PYTHON · LAYER CONTRACTdef call_read(ops, context, size): if size <= 0: raise ValueError("size must be positive") status, payload = ops(context, size) if status != "OK": raise RuntimeError(status) return payload
主机模型验证前后置条件;真实驱动仍需核对所有权、完成点和目标机资源。
9.1.3 LL、HAL 与 BSP 的不同职责
LL 常提供贴近寄存器的轻量入口,HAL 则以功能为中心组织初始化、轮询、中断和 DMA;BSP 再把时钟、引脚、供电和板级连接接起来。它们不是从低到高的同义词。迁移到另一颗 MCU 时,通用层可以保留,设备特有的时钟、复位、DMA 请求和寄存器布局必须重新核对。
| 层次 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 寄存器 | 位域/副作用/时钟 | 应用可直接写寄存器 |
| 驱动 | 状态/资源/错误 | HAL 成功就是设备可用 |
| 服务 | 单位/策略/质量 | 最终值覆盖原始事实 |
9.1.4 设备驱动与服务层的分界
设备驱动应该返回设备能证明的事实,例如识别号、ready、busy、CRC 错误、FIFO 深度和原始计数;服务层才把这些事实解释为摄氏度、帕斯卡、文件或网络消息。若驱动直接返回‘正常温度’,应用会失去 raw、时间戳、量程和质量标志,异常也会被悄悄转换成一个看似合理的值。
9.1.5 句柄、实例和资源所有权
句柄不只是一个指针,它应表达实例、总线、配置、锁、状态和回调上下文。两个任务同时改变同一个设备的片选、寄存器指针或工作模式,哪怕每次调用都返回成功,组合结果也可能错误。所有权可以是单线程、互斥锁、消息队列或专用任务,但必须在接口和证据中可见。
从左到右是思考顺序,不是把代码、配置、协议和目标机现象压缩成一个“通过”。
9.1.6 同步、异步与完成语义
轮询函数返回通常只证明寄存器满足某个条件;启动 DMA 的函数只证明传输已提交;回调或事件才可能证明缓冲区完成。中断到达、DMA 写完和器件内部转换完成也不是同一时刻。接口应明确 completion_level、数据所有权、取消行为和超时单位,避免上层过早读取或重复消费。
9.1.7 抽象泄漏与性能证据
抽象有成本:函数跳转、锁、复制、日志和状态检查会消耗时间和内存,但直接下沉寄存器又会扩大耦合。应测量 API 调用耗时、最坏延迟、栈深度和缓冲占用,并把必须暴露的硬件选项作为能力查询或配置结构,而不是让应用偷偷包含芯片头文件。
9.1.8 本节交付:驱动分层卡
驱动分层卡至少写出 layer、owner、input、output、completion_level、error_space、resource、side_effect、timeout、concurrency、evidence 和 unverified_scope。它不是漂亮的架构图,而是后续设备合同、协议解析、模拟测试和目标机交付可以共同引用的边界。
请围绕“驱动分层:谁拥有哪一个事实”写出对象、资源/输入条件、状态或协议机制、输出和完成语义、一个时间/安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,也要补齐芯片、板卡、总线、供电、工具链、仪器、配置和观察条件。不要用一个 API 返回值、一帧成功报文、一个 mock 结果或一张截图覆盖没有做过的验证。
02 · DEVICE CONTRACT
设备合同:生命周期、状态与错误
设备合同把初始化、配置、运行、停止、复位和故障恢复写成状态机,并把每个 API 的前置条件、后置条件、超时、并发、功耗和错误分类固定下来。这样上层不必猜测‘返回 0 是不是已经有数据’,驱动也不必为每个调用者重复发明重试和恢复。
9.2.1 探测、绑定与身份确认
探测成功可以只说明设备在总线上回应了一个地址或 ID,不说明固件所需功能已经可用。绑定前应确认身份、修订、能力和资源;例如同一总线上有响应的芯片可能是不同型号,某个寄存器可读也不代表它的供电、时钟或中断线路已经正确。
9.2.2 生命周期状态机
生命周期最少应区分 absent、bound、reset、configured、running、stopping、fault 和 recovering。每个转移要写触发条件、动作、输出和失败去向。把所有状态压缩成一个布尔值,会让调用者在设备忙、设备故障和设备根本不存在时做出相同的危险动作。
C · DEVICE LIFECYCLEtypedef enum { DEV_ABSENT, DEV_BOUND, DEV_CONFIGURED, DEV_RUNNING, DEV_FAULT } device_state_t; int device_start(device_t *dev) { if (dev->state != DEV_CONFIGURED) return ERR_STATE; dev->state = DEV_RUNNING; return 0; }
状态转移必须与复位、资源、并发和错误恢复合同一致。
PYTHON · RETRY POLICYdef retry_allowed(error, idempotent, attempt, limit): transient = {"timeout", "busy", "nack"} return error in transient and idempotent and attempt < limit
重试资格不等于动作已经安全;必须保存副作用和设备状态。
9.2.3 前置条件与后置条件
前置条件是调用者在函数前必须完成的事实,例如句柄有效、状态为 configured、缓冲区可写、锁已持有;后置条件是函数返回时可以相信的事实,例如数据长度、设备状态、错误阶段和所有权。没有后置条件的 void 函数只能靠日志猜测是否做完。
| 层次 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 状态 | 生命周期/所有权 | 任意状态都能调用 |
| 时间 | deadline/cancel/retry | 超时可以无限延长 |
| 恢复 | 清理/复位/健康 | 再次 init 一定修好 |
9.2.4 忙、就绪、故障与未知
busy 表示动作正在进行,ready 表示可接受下一动作,health 表示最近一次自检或通信仍在合同时间内,unknown 表示证据不足。它们互相不能替换。电源刚恢复、总线刚释放或驱动刚加载时,设备可能可达但还未 ready;超时后也不应直接把它标成正常。
9.2.5 超时、取消与等待者
超时必须从同一个时基计算,并在合同中说明是否包含排队、总线占用、器件转换和数据搬运。取消一个等待不一定取消了器件内部动作;如果硬件没有 cancel,驱动应进入 draining 或 fault,等待状态安全后再交付缓冲。无限延长 deadline 只是把故障推迟到更危险的位置。
从左到右是思考顺序,不是把代码、配置、协议和目标机现象压缩成一个“通过”。
9.2.6 重试、幂等与重复副作用
重试前先问操作是否幂等。读取状态通常可以重试,写入配置可能需要读回验证,启动电机或提交不可逆命令则可能不能盲目重放。重试记录应包含 error_phase、attempt、backoff、device_state 和是否发生副作用;只在日志里写‘retry’无法让别人判断是否安全。
9.2.7 复位、重新初始化与资源释放
恢复不是再次调用 init 就结束。应先停止新请求、让 DMA/中断缓冲归还、清除锁和片选、复位器件或总线、重新读取身份与能力,再按依赖顺序配置,并做独立健康检查。恢复失败要进入明确的 fault/disabled 状态,不能把半配置设备暴露给应用。
9.2.8 错误码、日志和健康快照
错误码要能定位阶段:bind、power、reset、configure、transfer、decode、timeout、integrity、range 或 recovery。日志保留设备 ID、配置版本、请求序号、时间戳、原始响应和重试次数;健康快照则记录当前状态、最近错误、丢失计数和恢复结果,方便现场只采一份数据也能复盘。
9.2.9 本节交付:设备合同卡
设备合同卡包括 identity、states、preconditions、postconditions、completion_level、timeout、cancel、retry_policy、ownership、power、error_phase、recovery、health_snapshot 和 stop_conditions。它将协议层的帧、配置层的资源和应用层的策略连接起来,而不是把责任推给‘使用者自行注意’。
请围绕“设备合同:生命周期、状态与错误”写出对象、资源/输入条件、状态或协议机制、输出和完成语义、一个时间/安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,也要补齐芯片、板卡、总线、供电、工具链、仪器、配置和观察条件。不要用一个 API 返回值、一帧成功报文、一个 mock 结果或一张截图覆盖没有做过的验证。
03 · DEVICE PROTOCOL
设备协议:帧、版本与兼容性
设备协议的困难不在于把几个字节拼起来,而在于定义边界、长度、字节序、版本、能力、完整性、序号、确认、超时、重试、背压和未知字段的处理。协议层应让错误可定位、重复可判断、升级有路径,并且不把 CRC 当作身份认证。
9.3.1 帧边界与解析状态
帧解析器不应把‘读到几个字节’当成消息。一个健壮解析器有明确状态,例如 WAIT_SYNC、READ_HEADER、CHECK_LENGTH、READ_PAYLOAD、CHECK_INTEGRITY 和 EMIT_RESULT;长度异常、超大帧、超时或 CRC 错误都有丢弃和重新同步路径。解析器日志要记录状态和偏移,才能解释为何后续好帧又恢复。
9.3.2 长度、字节序与字段编码
长度和字节序属于协议语法,不是编译器默认行为。固定宽度字段要明确有符号性、缩放、保留值和最大值;乘法、移位和长度加法要在目标整数宽度下检查溢出。主机端通过一个 Python 编码器不代表 8 位 MCU 的整数提升、对齐和端序实现也正确。
PYTHON · FRAME ENCODERdef encode(version, kind, sequence, payload, crc16): if len(payload) > 255: raise ValueError("payload too large") header = bytes((0xA5, version, kind, sequence, len(payload))) body = header + payload return body + crc16(body).to_bytes(2, "big")
帧语法要同时定义同步、长度、端序、CRC 参数、版本和最大值。
C · PARSER STATEswitch (parser->state) { case WAIT_SYNC: if (byte == 0xA5) parser->state = READ_HEADER; break; case READ_HEADER: parser_header_push(parser, byte); break; case READ_PAYLOAD: parser_payload_push(parser, byte); break; case CHECK_FRAME: return verify_frame(parser); }
解析器必须为截断、坏长度、坏 CRC、超时和重新同步保留可观察状态。
9.3.3 转义、同步与坏帧丢弃
转义是为了让同步字节、长度或控制字符可以出现在 payload 中;它必须有成对规则和截断处理。遇到非法转义、长度超限或中途超时时,解析器应丢弃当前候选帧并回到可证明的同步点,而不是把残留字节拼到下一条命令。
| 层次 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 帧 | 边界/长度/端序 | 读到字节就是一帧 |
| 可靠 | CRC/ACK/序号 | CRC 等于认证 |
| 演进 | 版本/能力/迁移 | 删除旧字段无影响 |
9.3.4 CRC、校验和与完整性边界
CRC 或校验和可以在给定参数、范围和传输方向下检测部分随机或突发错误,但不能证明消息来自可信设备,也不能证明字段语义正确。安全身份需要认证、密钥和重放防护;完整性测试则要保存多项式、初值、反射、终值和覆盖范围。
9.3.5 请求、响应、ACK 与 NACK
ACK 只证明某一层收到了请求,NACK 可能表示格式错、忙、权限不足、范围错或内部故障,TIMEOUT 则是证据不足。响应结构要带结果码、错误阶段、已执行副作用和可重试提示,不能用一个 0/1 把这些情况压扁。
从左到右是思考顺序,不是把代码、配置、协议和目标机现象压缩成一个“通过”。
9.3.6 序号、去重与幂等
序号让接收端识别重复和丢失,但去重窗口、重启后的持久化和序号回绕都要定义。幂等命令可以按序号返回缓存结果;非幂等命令则应要求事务 ID、状态查询或明确的‘可能已执行’错误。客户端重试必须记录 attempt 和原始响应。
9.3.7 背压、窗口与超时
发送方必须受到接收 FIFO、处理时间和总线带宽约束;窗口、credit、忙响应和节流都属于协议的一部分。超时要覆盖排队、传输、处理和响应,而不是从 write() 返回开始盲猜。背压缺失会把可恢复的慢设备变成持续丢帧和复位风暴。
9.3.8 版本、能力与未知字段
版本字段说明语法,能力字段说明当前端点支持什么;二者不能互相替代。新增字段应允许旧端跳过或按长度停止,删除字段要有迁移期,错误码和默认值也要保持兼容。保存一张版本×能力矩阵,比一句‘向后兼容’更可复查。
9.3.9 本节交付:设备协议卡
设备协议卡包括 framing、field_schema、endianness、limits、integrity、auth_scope、sequence、dedup、ack_semantics、timeout、backpressure、version、capabilities、compatibility 和 replay_vectors。它让下一层可以用同样的样例测试驱动,而不是每次靠抓包猜协议。
请围绕“设备协议:帧、版本与兼容性”写出对象、资源/输入条件、状态或协议机制、输出和完成语义、一个时间/安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,也要补齐芯片、板卡、总线、供电、工具链、仪器、配置和观察条件。不要用一个 API 返回值、一帧成功报文、一个 mock 结果或一张截图覆盖没有做过的验证。
04 · HARDWARE DESCRIPTION
设备描述与配置:让硬件成为数据
当硬件拓扑写在设备树、Kconfig、板级配置或生成文件里,驱动代码可以复用于多个板卡,但数据描述也会成为合同的一部分。compatible、寄存器、IRQ、时钟、复位、供电、总线父节点和默认参数必须描述硬件事实,不能为了让驱动 probe 成功而编造节点。
9.4.1 硬件描述不是代码注释
设备树、板级配置或 Kconfig 是硬件描述数据,不是为绕过编译错误而填的参数表。它告诉系统设备在哪里、挂在哪个总线、需要哪些资源;驱动再根据这些资源实现行为。把策略、默认超时和当前驱动限制全部写进节点,会让同一硬件无法被另一种软件正确理解。
9.4.2 compatible 与能力匹配
compatible 应尽量具体,并按真实软件可支持的范围列 fallback;‘看起来兼容’不等于驱动能安全使用。新增修订若改变寄存器布局、复位时序或错误语义,就需要新的 compatible 或显式能力分支。绑定文件和示例必须一起评审,避免字符串变成无人负责的 ABI。
DTS · RESOURCE SHAPEsensor@48 { compatible = "demo,pressure-v2"; reg = <0x48>; interrupts = <5>; reset-gpios = <&gpio0 12 0>; status = "okay"; };
节点描述硬件事实;具体地址、IRQ、GPIO 和兼容字符串必须按板卡资料核对。
PYTHON · CONFIG CHECKdef required_fields(node): required = ("compatible", "reg", "status") missing = [name for name in required if name not in node] if missing: raise ValueError("missing: " + ",".join(missing)) return node["compatible"]
主机校验能发现数据缺失,不能证明地址译码、IRQ 线、供电和 pinctrl 已连接。
9.4.3 reg、IRQ、clock 与 reset 资源
reg、interrupts、clocks、resets、power supplies、pinctrl 和 DMA 通道任何一项错误,都可能表现成驱动超时或数据错乱。静态字段存在只证明文本完整,仍要将编译后的 DTB、内核/RTOS 解析结果和板卡原理图、实测引脚逐项对账。
| 层次 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 描述 | 节点/compatible | 为驱动创建节点 |
| 资源 | reg/IRQ/clock/reset | 字段存在就是连接正确 |
| 验证 | schema/DTB/probe | 默认值可以猜硬件 |
9.4.4 总线父子关系与依赖
设备节点的父子关系表达控制器与从设备的拓扑,依赖关系则决定 probe 是否可以开始。一个 SPI 子设备的驱动不应自己假定 SPI 控制器时钟已开;它应该通过资源和框架获得依赖,并在延迟探测时区分‘依赖尚未 ready’与‘身份不匹配’。
9.4.5 overlay、多实例与板级变体
overlay 适合表达板卡变体,但多层 overlay 可能覆盖同一属性、重复注册地址或改变 pinctrl。多实例要有稳定的实例标识、独立资源和并发所有权;不要用一个全局设备指针和一个模糊默认值把两块器件混成一块。
从左到右是思考顺序,不是把代码、配置、协议和目标机现象压缩成一个“通过”。
9.4.6 binding schema 与静态校验
binding schema 的价值是把必填字段、枚举、数组长度、单位和互斥关系在构建期检查。即使当前驱动只用轮询,也应描述实际存在的中断资源;绑定描述硬件,不应随着某个驱动暂时不支持某功能而删掉事实。
9.4.7 probe 顺序、延迟探测与默认值
probe 失败日志要区分缺资源、依赖 busy、设备 NACK、身份不匹配和配置非法。默认值只能用于明确安全且有文档依据的字段,不能拿一个常见时钟、地址或 IRQ 猜测硬件。晚到的 regulator、reset 或总线控制器应触发可观察的 deferred 状态而不是静默跳过。
9.4.8 本节交付:硬件配置卡
硬件配置卡包括 source、generated_artifact、compatible、parent_bus、reg、irq、clock、reset、power、pinctrl、dma、required、defaults、schema_version、board_revision 和 probe_evidence。它让 Linux/Zephyr 的描述方式可以迁移,但不抹平板卡真实差异。
请围绕“设备描述与配置:让硬件成为数据”写出对象、资源/输入条件、状态或协议机制、输出和完成语义、一个时间/安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,也要补齐芯片、板卡、总线、供电、工具链、仪器、配置和观察条件。不要用一个 API 返回值、一帧成功报文、一个 mock 结果或一张截图覆盖没有做过的验证。
05 · SIMULATION AND FAULTS
模拟与故障注入:在没有目标机时验证边界
主机模拟不是目标机的替身,而是把协议、状态机、超时、错误和恢复从寄存器依赖中拆出来。用可注入的总线、寄存器、时钟和时间源,可以在读者没有硬件时重复运行故障矩阵;再用明确的 HIL/目标机清单说明哪些事实仍需现场测量。
9.5.1 依赖注入与最小接口
依赖注入不是把硬件函数随便替换成 lambda,而是定义驱动真正需要的最小能力:读写寄存器、提交事务、取时间、等待事件、记录日志和报告电源状态。真实 HAL 和 fake 实现都遵守同一接口,测试才可以判断驱动策略,而不是判断一个特制 mock 的内部捷径。
9.5.2 伪寄存器与副作用模型
伪寄存器模型应记录地址、宽度、写入顺序和副作用。例如写 1 清除、读后弹出、锁定位、写保护和状态自动变化都要显式建模;如果 fake 把所有寄存器都当成普通字典,主机测试会给出虚假的安全感。
PYTHON · VIRTUAL DEVICEclass FakeDevice: def __init__(self, responses): self.responses = iter(responses) self.now = 0 def transfer(self, request): response = next(self.responses) if response == "TIMEOUT": self.now += 100 return response
确定性假件让超时、重试和恢复可重复;它不模拟真实电平或器件模拟行为。
C · FAULT VECTORtypedef struct { const char *id; int expected_error; bool retryable; bool enters_fault; } fault_case_t; static const fault_case_t cases[] = { {"nack", ERR_NACK, true, false}, {"bad_crc", ERR_INTEGRITY, false, true} };
故障矩阵应覆盖阶段、状态、重试和停止条件,而不是只记录返回码。
9.5.3 确定性虚拟时间
虚拟时间让测试把 now、deadline、backoff 和器件转换完成作为可控事件推进。相比真实 sleep,它能在毫秒级时间窗内重放长时间故障,并精确断言‘超时前再试一次、超时后进入 fault’。时间源本身也是依赖,不能在函数内偷偷调用系统时钟。
| 层次 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 假件 | 总线/寄存器/时间 | mock 返回成功就够 |
| 故障 | 阶段/重试/状态 | 只测 happy path |
| 迁移 | HIL/波形/电气 | 主机通过即目标通过 |
9.5.4 故障矩阵:忙、NACK、坏帧与掉电
故障矩阵应按阶段列出:bind 身份不匹配、reset 不完成、configure 被拒绝、transfer NACK、response 坏帧、timeout、DMA 覆盖、power loss 和 recovery 失败。每格要写预期错误、是否重试、设备状态和必须留下的日志,避免只测一个异常返回。
9.5.5 属性、变形与边界测试
属性测试检查规则而非固定样例,例如编码后再解码得到同一消息、长度变化不会越界、增加一次可忽略的空操作不改变状态、序号重复不会增加副作用。变形测试特别适合协议和配置,因为它可以一次覆盖大量边界,但仍要保留失败输入供重放。
从左到右是思考顺序,不是把代码、配置、协议和目标机现象压缩成一个“通过”。
9.5.6 trace、计数器和最小日志
trace 至少包含单调 tick、设备实例、状态前后、操作、请求序号、输入摘要、错误阶段、attempt、deadline 和恢复动作。日志要有长度上限和敏感字段策略;主机测试的 trace 可作为目标机字段模板,但不能假装它包含真实电气波形。
9.5.7 HIL、目标机和主机证据边界
HIL 或目标机测试负责真实寄存器副作用、总线电平、IRQ/DMA 竞争、器件转换时间、上电和温度。主机测试通过只证明软件模型中的断言;迁移时要把同一个输入、预期输出、失败动作和证据字段带到实机,而不是重新写一套不可比的演示。
9.5.8 本节交付:故障矩阵卡
故障矩阵卡包括 dependency_contract、virtual_clock、register_side_effects、fault_id、input_vector、expected_phase、retry、state_after、trace_ref、host_result、target_required 和 stop_condition。它使‘没有板也能开始’与‘没有板不能声称完成’同时成立。
请围绕“模拟与故障注入:在没有目标机时验证边界”写出对象、资源/输入条件、状态或协议机制、输出和完成语义、一个时间/安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,也要补齐芯片、板卡、总线、供电、工具链、仪器、配置和观察条件。不要用一个 API 返回值、一帧成功报文、一个 mock 结果或一张截图覆盖没有做过的验证。
06 · DRIVER DELIVERY
驱动交付:从接口到可回滚产品
驱动交付是可重建、可诊断、可升级和可回滚的证据包:源码、配置、接口、构建版本、测试向量、日志、故障矩阵、目标机条件、已知限制和恢复动作必须一起出现。最后一个函数返回成功不是交付完成;读者要能知道运行什么、看到什么、何时停止以及结论不能越过哪里。
9.6.1 最小可重建交付包
最小交付包不是‘把源码压缩一下’,而是让没有上下文的读者在干净目录完成构建或主机测试,并知道每个结果依赖的版本和输入。清单应覆盖源文件、头文件、配置、协议向量、测试、脚本、工具版本、许可证、示例日志和未验证字段;任何靠开发者电脑上的隐藏文件才能工作的步骤都不算可重建。
9.6.2 接口文档与示例输入
接口文档要写前置条件、后置条件、单位、生命周期、并发、超时、错误阶段和所有权,并给出一个成功、一个边界和一个失败调用序列。示例输入不能只展示最短 happy path;它应包含截断、重复、忙、范围错误或版本不匹配,让读者看到合同如何保护系统。
SHELL · DELIVERY MANIFESTsha256sum src/* examples/* host_tests/* python run_all_tests.py # record toolchain, config hash, target result and unverified scope
清单、命令、哈希和未验证范围一起交付;一个 ELF 不能替代重建证据。
PYTHON · REPLAYdef replay(records, handler): results = [] for record in records: results.append(handler(record["request"], record["fault"])) return results
主机重放能复核状态和协议规则;目标机仍需补充资源、时序、电气和恢复证据。
9.6.3 构建版本、哈希与配置快照
源码提交、配置快照、编译器版本、链接脚本、依赖锁、构建命令、ELF/map 和哈希共同组成 provenance。二进制名称和时间戳不能代替提交哈希;如果配置由设备树或生成器产生,还要保存源文件、生成版本和生成输出,才能解释一次升级为何改变资源。
| 层次 | 应留下的证据 | 不能直接推出 |
|---|---|---|
| 重建 | 源码/配置/工具 | 一个二进制足够 |
| 证据 | 测试/日志/哈希 | 覆盖率代表安全 |
| 演进 | 兼容/回滚/停止 | 升级失败再重试 |
9.6.4 主机测试、目标测试与覆盖边界
主机测试验证纯软件模型、解析器、状态机和故障策略;目标测试验证真实寄存器、总线、IRQ/DMA、电气和器件行为。报告应分栏并列出覆盖边界。主机 100% 分支覆盖不等于目标机安全,目标机一次通过也不能替代长期、异常和回滚证据。
9.6.5 升级、迁移与兼容矩阵
兼容矩阵把旧固件、新驱动、协议版本、配置版本和能力组合列出来,说明支持、降级、拒绝或迁移脚本。字段改名、单位改变、默认值改变和错误码重用都可能是破坏性变化;保留一个短暂的双读/双写或显式版本迁移,通常比静默猜测更可靠。
从左到右是思考顺序,不是把代码、配置、协议和目标机现象压缩成一个“通过”。
9.6.6 回滚、复位和不可逆操作
更新、复位和回滚路径要把不可逆动作单独标出:写入 Flash、启动电机、修改电源、清除配对或提交计数器都不能靠无限重试。失败时先进入安全态、保存原因和版本,再执行有界的恢复;更新过程被切断后,仍应有可验证的旧镜像或恢复模式。
9.6.7 安全、权限与敏感日志
协议令牌、密钥、个人数据、设备序列号和内部地址不应原样写入公开日志。调试信息要区分开发与生产级别,错误摘要可以哈希或截断,但必须保留定位所需的阶段和计数。权限边界也属于驱动合同:谁能配置、谁能读 raw、谁能触发 reset,都要明确。
9.6.8 评审、维护和已知限制
维护记录要包含已知限制、未验证假设、目标机差异、开放问题、复现命令和下一次只改一个变量的建议。‘在我的板上正常’不是可迁移结论;好的发布说明会写明支持的芯片/修订/配置范围、停止条件和不承诺的性能或安全属性。
9.6.9 本节交付:驱动发布卡
驱动发布卡包括 manifest、source_hash、config_hash、toolchain、api_version、protocol_version、test_commands、host_result、target_result、compatibility、migration、rollback、security_scope、known_limits、stop_conditions 和 next_owner。它把本章的分层、合同、协议、配置和模拟证据汇聚成可以交接的产品。
请围绕“驱动交付:从接口到可回滚产品”写出对象、资源/输入条件、状态或协议机制、输出和完成语义、一个时间/安全约束、一个失败路径和对应证据。没有目标板时,运行主机路径并把未执行字段标为未执行;拥有目标机时,也要补齐芯片、板卡、总线、供电、工具链、仪器、配置和观察条件。不要用一个 API 返回值、一帧成功报文、一个 mock 结果或一张截图覆盖没有做过的验证。
资料卡(访问日期:2026-09-04):ST HAL/LL、CMSIS-Driver、Zephyr 设备模型/Devicetree 和 Linux 设备模型/绑定规范展示不同生态的分层与配置入口。通用框架不能替代当前芯片、板卡、器件、总线、电气、供电和恢复条件。