开发板调试真正耗时的部分,往往不是某一条命令,而是电源、串口、
U-Boot、网络启动、驱动加载和操作记录分散在不同工具中。任何一个环节
失去上下文,都可能让调试重新从“设备现在处于什么状态”开始。
本文展示一条已在 Hi3095 开发板实机跑通的 openUBMC 调试链路:
Agent 读取文件化 SOP,通过智能插座 API 完成断电与上电,经共享串口
进入 U-Boot,使用 TFTP 临时启动 RAMFS,再在纯内存维护环境中验证网络、
驱动和 eMMC。需要部署固件时,可以在同一维护面进入独立的受控烧录
流程,而不依赖板上原有 rootfs。
本文公开的是方法和脱敏后的实机证据,不包含实验室地址、账号、
私有制品名、板级偏移或设备唯一标识。
1. 先看最终效果
这条链路分成四个平面:
| 平面 | 作用 | 关键边界 |
|---|---|---|
| 可见控制面 | Agent 执行 SOP,终端同步展示状态 | 只有一个串口写者 |
| 控制主机 | 电源 API、ser2net、TFTP 和状态合同 | 所有入口固定并可记录 |
| Hi3095 开发板 | 上电、U-Boot、内核/DTB/RAMFS 启动 | U-Boot 变量仅本次会话生效 |
| RAMFS 维护面 | 网络、驱动、存储检查和后续部署 | 默认不挂载、不写持久化介质 |
可见桌面同时保留四个窗口:Agent 控制端、智能插座状态、RAMFS 能力
合同和串口观察端。模型名称、内部路径和串口地址已局部打码,但实际界面
和终端输出均来自同一次实机操作。
2. 电源与串口为什么要联动
智能插座不是为了替代板级复位,而是让“冷启动”成为一个有明确结果的
自动化动作。控制器先读取当前状态,再执行有界的断电等待和重新上电;
旁路终端则持续观察同一个串口。
这样可以把两个事实关联起来:
- API 确实完成了
on → off → on。 - 串口在重新上电后重新出现 DDR、U-Boot 和内核输出。
ser2net 可以开放多个观察连接,但这不等于多个连接可以同时输入。实践中
采用的纪律是:一个控制连接负责写串口,其余连接只观察。同时关闭
“新连接踢掉旧连接”的行为,避免演示终端或第二位开发者打断正在进行的
启动流程。
高速串口日志应直接输出到终端。曾尝试在实时链路上增加文本过滤,结果
stdout 反压导致观察进程提前退出。正确做法是保留原始观察链路,在截图
副本上做脱敏,不让公开材料处理影响硬件控制。
3. U-Boot 负责把 RAMFS 串起来
RAMFS 并不是单独的一份压缩包。要形成可用的维护环境,需要 U-Boot
在同一次会话中串起:
断电 / 上电
-> 抢占 U-Boot
-> 设置本次会话的网络参数
-> TFTP 获取设备树、内核和 RAMFS
-> 校验镜像
-> bootm
-> 等待 RAMFS 明确的 ready 标志
这里不执行 saveenv。网络参数、加载地址和启动参数都只服务于当前
会话,断电后不会覆盖开发板原有的持久化 U-Boot 配置。
这一步的意义是把“板上系统是否还能启动”和“维护环境是否可用”解耦。
即使原有 rootfs 无法登录、网络服务未启动,仍可以从 U-Boot 临时进入
一套已知状态的 RAMFS。
4. RAMFS 才是维护闭环的核心
进入 RAMFS 后,目标不是只看到一个 shell,而是确认它具有完成后续工作
的能力。本次实测合同包含:
| 合同项 | 实测标志 | 含义 |
|---|---|---|
| RAMFS 启动 | RAMFS_BOOT_READY=true |
已进入纯内存维护环境 |
| 网络接口 | NETWORK_INTERFACE_UP |
板端接口已拉起 |
| 网络路径 | NETWORK_PATH_OK |
板端可以访问文件服务端 |
| eMMC 驱动 | EMMC_DRIVER_READY |
块设备已由驱动暴露 |
| 挂载工具 | MOUNT_TOOL_READY |
具备受控检查文件系统的工具 |
| 驱动加载 | DRIVER_DYNAMIC_LOAD_OK |
匹配当前内核 ABI 的模块可加载 |
| 驱动卸载 | DRIVER_DYNAMIC_UNLOAD_OK |
验证后可恢复模块基线 |
| 持久化边界 | PERSISTENT_ENV_CHANGED=false |
未修改持久化 U-Boot 环境 |
| 写盘边界 | EXPLICIT_EMMC_WRITE=false |
本次演示没有请求 eMMC 写入 |
这里的“动态驱动”不是指任意 .ko 都能加载。模块必须与当前运行内核的
ABI、符号版本和依赖关系匹配。演示选用一个不会访问设备的文件系统模块,
完成加载和卸载,用来证明 RAMFS 具备动态扩展能力,同时避免对硬件状态
产生额外影响。
有了网络和匹配的驱动,RAMFS 可以承担三类任务:
- 通过网络传入诊断工具、模块或待部署镜像。
- 临时加载存储、网络或板级维护所需驱动。
- 在明确授权后挂载分区、采集数据,或进入独立烧录流程。
5. 从 RAMFS 到受控烧录
“RAMFS 能看到 eMMC”不等于“可以直接写”。写入必须是另一个显式动作,
至少经过以下守卫:
- 确认目标设备身份、容量和当前布局。
- 备份分区表和将被覆盖的数据,并保存哈希。
- 校验源镜像的大小、版本和哈希。
- 只允许写入预先声明的目标,不接受临时拼接的设备名或偏移。
- 写入后按精确字节数回读,并与源文件哈希比较。
- 默认不修改
boot0、boot1和持久化 U-Boot 环境。 - 任一检查失败立即停止,不把“命令已发送”当成成功。
因此,完整闭环是:
U-Boot 临时启动 RAMFS
-> 拉起网络
-> 加载匹配驱动
-> 暴露并识别目标存储
-> 网络传入制品
-> 备份与哈希校验
-> 显式授权写入
-> 回读校验
-> 选择临时启动或恢复原启动链
本文截图停在“写入前能力合同全部通过”,这是有意设计的安全边界;同一
RAMFS 维护面已经具备继续部署和烧录的条件,但公开演示不需要为了证明
能力而改写持久化介质。
6. 实践中修正的三个问题
这次实测也说明,硬件自动化不能只依赖“命令退出码为 0”:
- RAMFS 通常是 BusyBox 环境,不能假定存在完整 GNU 工具。网络检查必须
使用目标环境实际具备的命令。 - 串口新连接可能丢失最前面的输出。应先完成检查,再集中输出结果合同,
不把首行日志当作唯一成功判据。 - 串口执行器应限制单条命令长度。能力检查按网络、存储和驱动拆分成短
命令,再由控制端汇总结论,比拼接一条超长命令更可靠。
这些问题都没有通过“忽略失败”解决,而是收紧了合同:网络接口与网络
路径必须同时出现,驱动必须同时证明加载成功和卸载成功,缺少任一标志
都判定整轮失败。
7. 时间与复现边界
在本次环境中:
- 首次冷启动 Agent、读取 SOP 并完成交互授权,用时约 2 分 44 秒。
- 控制面和观察端已经就绪后,重复完成电源循环、U-Boot/TFTP、RAMFS
启动和合同检查,用时约 1 分 33 秒。
这是单块开发板和当前网络环境下的实测值,不是对其他硬件的性能承诺。
更重要的是,整条链路每个阶段都有可见状态、明确成功标志和可停止边界。
当电源、串口、U-Boot、TFTP、RAMFS 和写入守卫都被文件化后,AI 的
价值不再是“自由操作硬件”,而是稳定执行经过审查的步骤,并把一次性的
人工经验沉淀为可复现、可观察、可回退的 openUBMC 实板开发闭环。




