| 名称 | jetson-validate-image |
| 描述 | >- 在 jetson-flash-image 之后使用,对已刷写的 DUT(目标设备)执行静态 BSP 检查、 目标板冒烟/回归测试,或两者都执行。不用于构建或刷写步骤。 触发词:validate bsp、on-target validation。 |
| 版本 | 0.0.1 |
| 开源协议 | “Apache-2.0” metadata: data-classification: public |
| 作者 | “Jetson Team” tags: - bsp - validation - test - deploy domain: meta |
验证 BSP 镜像
状态: DUT 访问契约已稳定;验证过程的其余部分仍为骨架。
目的
确认新定制的 BSP 正确落地——既作为磁盘上的静态工件,也作为目标上运行的系统——而无需重新提升或重新刷写。构成 Setup → Customize → Build → Deploy 流水线中 Deploy 的验证尾部(流水线视图见 ../../context/bsp-customization-workflow.md),并且可独立重复运行。
前置条件
- 活跃的目标平台 profile,其中已解析
bsp_image:(先运行/jetson-init-image)。 - 仅静态范围:无需其他;技能直接读取
<bsp_image.root_path>。 - 目标板范围:
/jetson-flash-image已将暂存的 BSP 推送到 DUT。- 在活跃 profile 中配置了
dut_access:块(或在运行时交互填充——参见## DUT 访问)。 - 各传输方式的主机工具:SSH 使用
ssh+sshpass;UART 使用带pyserial的 Python 3.6+。 - 当
auth=password或sudo.method=password时,所需环境变量可在主机上解析(password_env/sudo.password_env命名环境变量;切勿将秘密内联在 YAML 中)。
何时调用
- 在
jetson-flash-image已将 BSP 放到目标之后。 - 用户明确要求对已刷写设备进行验证、测试或冒烟/回归检查。
- 作为 CI 门禁,在声明定制批次可发布之前。
操作说明
下面的流程是骨架。
- 按前置契约读取活跃目标。
- 选择验证范围 — 静态(针对磁盘上的
bsp_image,无需 DUT)和/或目标板(DUT 必须从刚刷写的镜像启动)。 - 静态检查(如在此范围内):
- 必需工件存在于
<bsp_image.root_path>/Linux_for_Tegra/中的预期路径。 - DTB / 模块校验和或签名验证。
- 分区布局与单板
.conf的 XML 比对的合理性。 - 将 overlay 暂存的输出与
bsp_image交叉检查(提升后应完全相同)。 - Initramfs ↔ 内核 + rootfs 模块一致性。 解压
<bsp_image>/Linux_for_Tegra/bootloader/l4t_initrd.img和<bsp_image>/Linux_for_Tegra/rootfs/boot/initrd;对照 提升后的bsp_image状态验证三个不变量: (a)<bsp_image>/Linux_for_Tegra/kernel/Image与<bsp_image>/Linux_for_Tegra/rootfs/boot/Image逐字节一致——此处漂移意味着“将内核 Image 镜像到 rootfs”步骤被跳过,任何后续 initramfs 刷新都是针对之前的内核构建的。 (b) 对两个 initrd 镜像提供的每个模块路径,其字节 / md5 与<bsp_image>/Linux_for_Tegra/rootfs/lib/modules/<ver>/下的文件一致——任何漂移都意味着模块在早期启动时会被遮蔽。 © 每个 initramfs 中的*.ko上盖的 vermagic 与从<bsp_image>/Linux_for_Tegra/kernel/Image可达的UTS_RELEASE匹配(例如用strings解析Linux version …字符串)——vermagic 偏移意味着内核Image已被刷新,但没有重新运行l4t_update_initrd.sh,模块加载将失败并提示 “disagrees about version of symbol …”。这三种故障模式均由/jetson-promote-image的 gate 关闭,该 gate 要么检查kernel/Image或rootfs/lib/modules/,加上内核 Image 镜像;在此处报告漂移,并将用户引导回干净的提升流程。参见../jetson-promote-image/SKILL.md。
- 必需工件存在于
- 目标板检查(如在此范围内):
- 按下方“## DUT 访问”小节连接 DUT — 从活跃 profile 的
dut_access:块解析传输方式(ssh / uart)、凭据和 sudo 方法(当字段缺失 / 标记为prompt时交互式回退),然后运行连接探测,若失败则拒绝。 - 确认启动已到达用户空间。
- 运行选定的测试套件(冒烟、回归、按定制聚焦、临时)。
- 已加载模块的 srcversion 漂移。 对于定制已知重建的模块,比较 DUT 上的
cat /sys/module/<name>/srcversion与modinfo /lib/modules/$(uname -r)/.../<name>.ko | awk '/srcversion/ {print $2}'的结果。不匹配意味着内核运行的副本比 rootfs 附带的旧——几乎总是过期的 initramfs(bootloader 侧的 initrd 传入了定制前模块,它先加载,而 rootfs 副本无法替换活动模块)。建议重新运行/jetson-promote-image并重新刷写。注意:某些模块不发出srcversion;可回退到 md5 检查,另一种方式是提取/sys/module/<name>/sections/下的区域,或通过行为比较(printk / sysfs 节点 / 新版本已知暴露的 DT 属性)检查内核加载的二进制。 - 运行中的内核 vs rootfs 内核
Image。 将 DUT 上的cat /proc/version(或uname -v)与从/boot/Image提取的Linux version …字符串(strings /boot/Image | grep -m1 'Linux version')比较。不匹配(通常是构建时间戳 /LOCALVERSION)意味着 bootloader 运行的内核Image比 rootfs 持有的旧,几乎可以肯定是因为新Image已被提升,但 initramfs /extlinux.conf/ QSPI 启动分区没有刷新。rootfs 中的模块将有不同 vermagic,因此后续对“针对新内核构建的 .ko”执行modprobe将失败。建议重新运行/jetson-promote-image并重新刷写。 - 用户空间 dmesg 可读性。 Ubuntu 22.04 设置
kernel.dmesg_restrict=1;非 rootdmesg读取返回 “Operation not permitted” 并静默产生零结果。每个基于dmesg的检查必须使用sudo(或通过sudo sysctl kernel.dmesg_restrict=0临时降低限制)。在验证层表面化这个问题,可让基于 printk 的定制检查保持诚实。 - 收集结果、日志、产物。
- 按下方“## DUT 访问”小节连接 DUT — 从活跃 profile 的
- 总结:每项检查通过/失败、总体结论、日志和产物存放位置。
DUT 访问
目标板执行部分需要一种到达刚刷写 DUT 的方式。两种传输方式作为对等体受支持:ssh(首选)和 uart(用于没有网络的 DUT 的回退)。
该契约已锁定,但存放在 references/dut-access.md 中,以使本 SKILL.md 保持在代理路由预算内。该参考文件涵盖:
dut_access:profile 模式(ssh、uart、sudo、workdir)。- 解析顺序(profile → 环境变量 → 交互提示)及完整的拒绝触发表。
- 强制连接探测(
uname -r+cat /etc/nv_tegra_release)及其输出验证规则。 - 按传输方式传输文件(
scpvs 基于 tty 的 base64,并带有 >100 KB 警告)。 - 按
sudo.method× 传输方式的 sudo 调用矩阵。 scripts/uart_session.py所遵循的 UART 实现契约(登录 / 执行 / 推送 / 拉取的状态机、退出码、健壮性说明)。- 安全说明(密码处理、host-key 固定)。
可用脚本
| 脚本 | 用途 | 参数 |
|---|---|---|
scripts/uart_session.py |
目标板执行部分的 UART 传输黑盒:登录、命令执行(可选 sudo),以及基于 tty 的 base64 文件传输。当 DUT 没有网络或 ssh 损坏时替代 ssh。 | --tty <dev> [--baud <n>] --user <name> --password-env <ENVVAR> [--sudo-password-env <ENVVAR>] [--shell-prompt <regex>] [--lock-strategy refuse|wait] <probe|exec|push|pull> [...] |
调用(技能将脚本作为子进程调用——以 agent-runtime 术语称为 run_script()):
# run_script: 通过 UART 探测 DUT
DUT_UART_PASSWORD_ENV=DUT_UART_PWD \
DUT_UART_PWD="$(read -rs -p 'UART 登录密码: '; echo "$REPLY")" \
scripts/uart_session.py \
--tty /dev/ttyACM0 --baud 115200 \
--user ubuntu --password-env DUT_UART_PASSWORD_ENV \
probe
# run_script: 执行 sudo 命令并捕获退出码
scripts/uart_session.py --tty /dev/ttyACM0 --user ubuntu \
--password-env DUT_UART_PASSWORD_ENV \
--sudo-password-env DUT_SUDO_PWD \
exec --use-sudo 'dmesg | tail -200'
脚本的退出码即契约——参见 UART 实现契约 中的退出码表。
示例
仅静态验证(无需 DUT):
/jetson-validate-image
> 仅对暂存的 BSP 执行静态检查
SSH 目标板验证(刚刷写 DUT 后):
/jetson-flash-image
↓
/jetson-validate-image
> 通过 dut_access.ssh 执行目标板检查
UART 目标板验证(DUT 无网络):
/jetson-validate-image
> 使用 /dev/ttyACM0 处的 uart 传输;profile 中的 dut_access.uart 块
已配置好 tty 和 login_password_env
限制
- 占位技能——只有 DUT 访问契约和
uart_session.py辅助工具已锁定。静态检查列表、测试套件选择、结果宿布局和通过/失败策略在“## 待办事项”下跟踪,可能变更。 - UART 文件传输是在 115200 波特率下基于 base64 字节敲打,约 10 KB/s——对于 >100 KB 的源会发出警告但仍继续。对于大容量传输,请切换到 SSH 传输。
uart_session.py每次子命令调用都会打开和关闭 tty(每次登录约 1–2 秒)。在 UART 上运行 >10 条命令的验证会摊销不佳;建议使用 SSH。- SSH 使用
StrictHostKeyChecking=accept-new,并带按 profile 的known_hosts文件。指纹变化将拒绝——通常意味着 DUT 被重新刷写(host key 重新生成)或 IP 被重新分配。手动移除按 profile 的条目,而不是自动接受。 - 密码从不内联在 profile YAML 中——只持久化
password_env(环境变量名)。auth=prompt/sudo.method=prompt会在对话日志中暴露密码,这由用户负责管理。 transport=uart搭配lock_strategy=steal未实现(需要发送控制字符,可能破坏持有者的状态)。
故障排查
| 错误 | 原因 | 解决方案 |
|---|---|---|
no DUT transport configured(拒绝) |
请求了目标板范围,但 dut_access.transport 在 profile / 环境 / 交互提示之间无法解析 |
在活跃 profile 中编写 dut_access:,或改用仅静态范围重新运行。 |
$PASSWORD_ENV is unset(拒绝) |
auth=password(或 sudo.method=password)指定的环境变量未在主机上导出 |
在调用技能前 export <ENVVAR>=...;切勿在 YAML 中内联密码。 |
tty held by another process(拒绝,退出码 3) |
lock_strategy=refuse 且 fuser 报告有其他持有者(minicom、picocom、getty) |
关闭持有进程(只有在你确定里面是什么时才能用 sudo fuser -k <tty>),或改用 lock_strategy=wait 重新运行。 |
pyserial import failed(退出码 4) |
选择了 UART 传输,但技能 Python 环境未安装 pyserial |
apt install python3-serial 或 pip install pyserial。 |
| SSH 指纹被拒绝 | 自按 profile 的 known_hosts 固定以来,DUT 的 host key 已更改(通常是重新刷写) |
从 <workspace>/target-platform/<profile-stem>.known_hosts 中删除匹配行并重新运行——第一次连接将接受新 key。 |
login failed(退出码 128) |
UART 探测在超时内无法匹配登录或 shell 提示符 | 确认 DUT 已上电并启动到用户空间;如果 DUT 有不同寻常的 PS1,检查 --shell-prompt 覆盖;确认 --user 匹配真实账户。 |
DUT not booted from the just-flashed BSP(警告 / 拒绝) |
DUT 上的 /etc/nv_tegra_release 与 bsp_image.version 不匹配 |
重新运行 /jetson-flash-image 并确认 DUT 实际重新上电进入了新 BSP,而不是之前的 BSP。 |
sudo prompt detected but no sudo password configured(退出码 130) |
向 uart_session.py exec 传递了 --use-sudo,但未设置 --sudo-password-env,且 DUT 用户没有 NOPASSWD |
在 profile 中设置 sudo.method=password + sudo.password_env,或在 DUT 上通过 /etc/sudoers.d/ 授予 NOPASSWD。 |
command timed out(退出码 129) |
长时间运行的 DUT 命令超过脚本超时 | 通过 ssh 传输直接运行命令,或拆分为更短的步骤。 |
参考
references/dut-access.md— 完整的 DUT 访问契约:profile 模式、解析顺序、探测、文件传输、sudo、UART 实现、安全。../../context/target-platform-contract.md— 目标平台契约。../../context/bsp-customization-workflow.md— Workspace 编辑协议(本技能是 Deploy 尾部)。../jetson-build-source/SKILL.md— 构建器;产生本技能验证的工件。