jetson-validate-imageSkill jetson-validate-image

该技能用于在Jetson设备刷写自定义BSP镜像后,执行静态BSP检查以及目标板上的冒烟/回归测试,验证镜像是否正确部署。支持SSH和UART两种DUT访问方式,覆盖内核与initramfs一致性、模块srcversion校验、运行内核与rootfs镜像比对等关键检查,并可在CI中作为发布门禁。关键词:Jetson、BSP验证、镜像验证、冒烟测试、回归测试、DUT访问、initramfs、内核一致性、Ubuntu 22.04 dmesg限制。

BSP与板级支持 0 次安装 0 次浏览 更新于 9/7/2026
名称 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=passwordsudo.method=password 时,所需环境变量可在主机上解析(password_env / sudo.password_env 命名环境变量;切勿将秘密内联在 YAML 中)。

何时调用

  • jetson-flash-image 已将 BSP 放到目标之后。
  • 用户明确要求对已刷写设备进行验证、测试或冒烟/回归检查。
  • 作为 CI 门禁,在声明定制批次可发布之前。

操作说明

下面的流程是骨架。

  1. 按前置契约读取活跃目标
  2. 选择验证范围 — 静态(针对磁盘上的 bsp_image,无需 DUT)和/或目标板(DUT 必须从刚刷写的镜像启动)。
  3. 静态检查(如在此范围内):
    • 必需工件存在于 <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/Imagerootfs/lib/modules/,加上内核 Image 镜像;在此处报告漂移,并将用户引导回干净的提升流程。参见 ../jetson-promote-image/SKILL.md
  4. 目标板检查(如在此范围内):
    • 按下方“## DUT 访问”小节连接 DUT — 从活跃 profile 的 dut_access: 块解析传输方式(ssh / uart)、凭据和 sudo 方法(当字段缺失 / 标记为 prompt 时交互式回退),然后运行连接探测,若失败则拒绝。
    • 确认启动已到达用户空间。
    • 运行选定的测试套件(冒烟、回归、按定制聚焦、临时)。
    • 已加载模块的 srcversion 漂移。 对于定制已知重建的模块,比较 DUT 上的 cat /sys/module/<name>/srcversionmodinfo /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;非 root dmesg 读取返回 “Operation not permitted” 并静默产生零结果。每个基于 dmesg 的检查必须使用 sudo(或通过 sudo sysctl kernel.dmesg_restrict=0 临时降低限制)。在验证层表面化这个问题,可让基于 printk 的定制检查保持诚实。
    • 收集结果、日志、产物。
  5. 总结:每项检查通过/失败、总体结论、日志和产物存放位置。

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)及其输出验证规则。
  • 按传输方式传输文件(scp vs 基于 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=refusefuser 报告有其他持有者(minicom、picocom、getty) 关闭持有进程(只有在你确定里面是什么时才能用 sudo fuser -k <tty>),或改用 lock_strategy=wait 重新运行。
pyserial import failed(退出码 4) 选择了 UART 传输,但技能 Python 环境未安装 pyserial apt install python3-serialpip 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_releasebsp_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 传输直接运行命令,或拆分为更短的步骤。

参考