Jetson Orin 强行 apt upgrade 翻车,从黑屏光标到 QSPI 归位的完整硬核复盘
前言
给 NVIDIA Jetson 执行系统升级永远是一场赌博。L4T 的内核、驱动与 QSPI 引导固件深度绑定,一旦升级中断或依赖不齐,黑屏、引导链损坏、QSPI 与 rootfs 版本断层便接踵而来。
本文记录 Jetson Orin Nano Super(L4T 36.4.3 → 36.4.7)升级失败后的完整救援过程:SD 卡跳板救数据 → GRUB 命令行手工引导 → 修复脚本重生引导链 → UEFI 强制 Normal 续命 → 深挖 Capsule update status: 0 死锁 → 找到 NVIDIA 自家密道一次刷齐 QSPI。全程无主机重刷、无 dd 到 mtd、无手工写 efivar——从”强撑 hybrid”到”版本完全对齐”,每一步都建立在真实日志与源码之上。
一、灾难降临:cpio 缺位,initrd 打包失败
起因:在真系统里跑了一次看似普通的 sudo apt upgrade,L4T 全家桶从 36.4.3 拉到 36.4.7。
致命错误(升级中途弹出,被大量下载进度条淹没):
nv-update-initrd: 行 127: cpio: 未找到命令dpkg: 处理软件包 nvidia-l4t-initrd (--configure) 时出错cpio 是 initramfs 打包的核心工具,缺了它 → 新的 36.4.7 initrd 压根没生成成功 → 里面 NVMe 驱动没打进去 → 重启后内核起来但根挂载不上。
表象:UEFI 试图引导、Linux 内核静默死在 Attempted to kill init,几次启动尝试过后 boot-guard 把 OS chain A 标为 Unbootable——再次重启,屏幕左上角只剩一个孤零零的光标,键盘无响应。
二、SD 卡救援跳板:守住数据底线,但治不了本
黑屏当下没有主机可以 SDK Manager 刷,唯一办法是先造一个能进的环境。
- 烧一张通用 Ubuntu ARM64 SD 卡(非 NVIDIA 原版镜像,手头没有);改 EEPROM/串口进 UEFI 把 SD 卡提到 BootOrder 首位。
- 挂载 NVMe 全部 15 个分区,
lsblk认出nvme0n1p1是 117 GB 根(ext4)、p2是 A_kernel(vfat)、p10是 ESP;mount /dev/nvme0n1p1 /mnt后第一时间把home下核心代码 rsync 到外接 U 盘。 - 局限:普通 Ubuntu 里
nvbootctrl/tegra-mccompiler/nvidia-l4t-*全都没有,apt源也接不上 NVIDIA 的t234 r36.4feed——SD 卡里做任何操作都碰不到 Jetson 的 QSPI 与引导链。它只能救数据,救不了系统。
结论:必须想办法把 NVMe 里的真系统拉起来。
三、GRUB 命令行手工引导 + nomodeset 破冰
真系统重启掉进了 GRUB grub> 命令行(因为 /boot/grub/grubenv 保存的 boot 项在 apt 中途被截断)。在命令行敲下救命四连:
set root=(hd0,gpt1)linux /boot/Image root=UUID=bf28224b-a16a-4cdf-8017-8b04bd637f0f ro rootwait nomodesetinitrd /boot/initrdboot几个关键点:
- UUID 必须是原系统根分区的真实物理 UUID(在 SD 卡救援环境里
sudo blkid /dev/nvme0n1p1查出来抄进去),绝不能混用 SD 卡自己那个p1的 UUID。 nomodeset是必需项——这台板子通过 DP 转 HDMI 转接头输出,Linux 内核在加载nvidia-drm时读不到 EDID 会静默死屏(不是黑屏,是光标停在刷新的假活状态)。加nomodeset强制走 UEFI 帧缓冲,屏幕才终于亮起。rootwait是保险:NVMe 枚举有时序抖动,等 3 秒再挂根。
进入桌面后,第一件正事不是修引导,是 sudo apt install cpio——把根子上缺的工具补齐。
四、第一轮修复:fix-boot-official.sh 与它的三个坑
进真系统后跑精心准备的 fix-boot-official.sh,脚本设计覆盖三处风险:
- 不让
apt-get update失败拖死——去掉set -e,关键步显式判错; - 找回被
nvidia-l4t-bootloader重装覆盖的自定义 extlinux 项——暂存JetsonIO段(含双 IMX219 CSI 摄像头的FDT与OVERLAYS)并回贴;顺便把 FDT 从已不存在的p3767-0005-nv.dtb自动重定向到-nv-super.dtb; - 收尾禁用 p2 里的 GRUB 劫持件、把当前 NVMe 启动项置顶。
4.1 一个”看似理所当然”的致命误判
网上的一手经验都说:ESP 里 /boot/efi/EFI/BOOT/BOOTAA64.efi 是 GRUB 的 removable-media 兜底路径,必须改名禁掉。我们照做了,系统立刻不能启动。
事后用 strings + efibootmgr -v 做指纹验证才搞清楚:这个 BOOTAA64.efi 里全都是 L4TLauncher、ExtLinuxBoot、ApplyTegraDeviceTreeOverlay 等字符串,压根没有 GRUB 字样——它是 NVIDIA 原生的 L4TLauncher,是这台板子真正读 extlinux.conf 引导内核的东西。禁掉它 = 断引导。
真正的 GRUB 劫持件在另一个地方:p2 (A_kernel 分区) 上的 \EFI\BOOT\{bootaa64.efi, grubaa64.efi, grub.cfg} 三件套。Boot0007(Image 直连 p2)与 Boot0009(\EFI\BOOT\grubaa64.efi)才是元凶。
修正后的脚本动作:只 .disabled 掉 p2 那三件套;ESP 的 BOOTAA64.efi 反过来加校验、若被误禁则自动恢复;BootOrder 只重排不删除,把 0008(NVMe 磁盘级路径)顶到首位,把 0007 排出去。
4.2 跑完,还是黑屏
引导链修好了、extlinux 正确、BootOrder 干净,sudo reboot 之后——又是孤零零的光标。
五、UEFI 的”神来之笔”:OS chain A status = Normal 强权续命
黑屏说明 UEFI 的 boot-guard 还没被安抚——它把 OS chain A 记录成了 Unbootable,任你怎么换引导文件,UEFI 一看到这条就直接跳过 A 链、什么也不做。
于是走进最后一道门:
- 开机狂按
Esc进 UEFI 设置; Device Manager → NVIDIA Configuration → L4T Configuration → OS chain A status;- 从
Unbootable强改成Normal,F10保存退出。
奇迹发生了:UEFI 忽略版本判定,强行加载 \EFI\BOOT\BOOTAA64.efi(L4TLauncher),L4TLauncher 读 extlinux.conf,内核起来、rootfs 挂上、桌面出来。
这是整个救援的关键转折点,也是这台板子第一次以”混合状态”活下来。
六、“强撑的 hybrid”:QSPI 36.4.3 × rootfs 36.4.7
进系统后 nvbootctrl dump-slots-info:
Current version: 36.4.3 ← QSPI 引导固件版本Capsule update status: 0Current bootloader slot: AActive bootloader slot: Anum_slots: 2slot: 0, status: normalslot: 1, status: normal/etc/nv_tegra_release 与 dpkg -l nvidia-l4t-* 则显示 rootfs 完全是 36.4.7。QSPI 与 rootfs 劈叉 4 个小版本,靠 UEFI 手动 override 才能起来。
journalctl -b 看到 nv-l4t-bootloader-config.service 每次开机都在演:
Info: System version(deb version): 2360327, BSP version(QSPI version): 2360323Info: Starting bootloader post-install procedure.INFO. Trigger Capsule update is done. ← 装作已暂存INFO. Copy L4tlauncher to /boot/efi/EFI/BOOT/ done.Reboot the target system for updates to take effect. ← 让你重启Info: variable BootChainFwStatus is not found. ← 但其实没挂起做了一次 shutdown -h now + 拔电源 + 冷启动——QSPI 仍 36.4.3。做错了什么? 什么都没做错,只是根本没有 capsule 被暂存,UEFI 没东西可读。
七、假成功的真相:两个致命的软件 bug
顺着”为什么没暂存”往下挖,读 /var/lib/dpkg/info/nvidia-l4t-bootloader.postinst:
trigger_capsule_update () { ... create_capsule_uefi_variable device_id=$(fwupdmgr get-devices | awk '/System Firmware/{getline; print}' | ...) yes N | fwupdtool install-blob "${payload_dir}/${capsule_payload}" "${device_id}" \ > /dev/null 2>&1 # ← 错误被吞光! echo "INFO. Trigger Capsule update is done." # ← 无论成败都打这行}把 > /dev/null 2>&1 拆掉,手工跑一次 sudo fwupdtool install-blob ...,真相暴露:
/boot/efi does not have sufficient space, required 102.6 MB, need additional 39.6 MBBug #1:ESP 空间物理不足
- Jetson 出厂 ESP 只有 64 MB(
/dev/nvme0n1p10); - 我们的 capsule 文件
/opt/ota_package/t23x/TEGRA_BL_3767_super.Cap48.4 MB; - fwupd 的 capsule-on-disk 流程要双份(工作副本 + 处理后镜像),需要 ~103 MB。永远装不下。
Bug #2:OsIndications 变量位写错
同脚本里 create_capsule_uefi_variable() 写的是:
printf "\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"前 4 字节是 UEFI 属性 (NV+BS+RUNTIME),后 8 字节是变量值——全 0。UEFI 标准要求的是 EFI_OS_INDications_FIRMWARE_UPDATES 的 bit 2 = 1,全 0 意味着”OS 不需要 UEFI 做任何固件更新”。就算 fwupd 侥幸成功,UEFI 也不会去处理。
换句话说:apt install nvidia-l4t-bootloader 走的 fwupd 路径在这块板子上从设计上就不可能成功——ESP 尺寸没跟上 capsule 膨胀。这才是 Capsule update status: 0 死循环的根因。
八、NVIDIA 自家的密道:nv_bootloader_capsule_updater.sh
既然 fwupd 那条路是死的,就在 /usr/sbin 里翻——find 出来三个工具:
/usr/sbin/nv_update_engine/usr/sbin/nv_bootloader_capsule_updater.sh ← 名字就叫 capsule updater/usr/sbin/nv_bootloader_payload_updater打开 nv_bootloader_capsule_updater.sh,只有两行核心动作:
ESP_CAPSULE_DIR="EFI/UpdateCapsule/"cp "${payload_file}" "${capsule_dir}" # 单份复制, 只要 1× 尺寸printf "\x07\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00" > ... # 关键: bit2=0x04dd if="${TMP_FILE}" of="${var_path}" > /dev/null 2>&1对比 fwupd 路径的两个致命差异:
fwupd install-blob | NVIDIA 自家 capsule_updater | |
|---|---|---|
| 落地位置 | EFI/BOOT/fwupd/ | EFI/UpdateCapsule/ |
| ESP 空间需求 | 双份 ≈ 103 MB(装不下) | 单份 48 MB(够!) |
| OsIndications 值 | 全 0(UEFI 忽略) | bit 2 = 1(UEFI 认这个位) |
一次执行收工:
$ sudo /usr/sbin/nv_bootloader_capsule_updater.sh -q \ /opt/ota_package/t23x/TEGRA_BL_3767_super.Cap
Info: Copy capsule payload to /boot/efi/EFI/UpdateCapsule/ done.Info: Set capsule UEFI variable .../OsIndications-8be4df61-... done.Info: Firmware on QSPI will be updated on the next boot.Info: Reboot the target system for updates to take effect.验证:xxd /sys/firmware/efi/efivars/OsIndications-* 显示 0700 0000 0400 0000 0000 0000,bit 2 正确置位;ls /boot/efi/EFI/UpdateCapsule/ 里 .Cap 就位;ESP 剩 12 MB。
九、一次普通 sudo reboot 之后
不需要冷启动、不需要拔电源、不需要再进 UEFI 改任何东西——普通 sudo reboot 就够了,因为这次是真的有 pending 状态。
起来后:
$ sudo nvbootctrl dump-slots-infoCurrent version: 36.4.7 ← QSPI 已升上来Capsule update status: 1 ← capsule 已成功应用Current bootloader slot: B ← 从 A 切到 B (双 slot 轮转)Active bootloader slot: Bnum_slots: 2slot: 0, status: normal ← slot A 仍是 36.4.3 兜底slot: 1, status: normal再看环境细节:
/boot/efi/EFI/UpdateCapsule/已被 UEFI 清空(消费完成);OsIndicationsbit 2 被 UEFI 归零(正常收尾);df -h /boot/efi回到 61 MB 可用;- 服务日志:
System version(deb version): 2360327, BSP version(QSPI version): 2360327——版本劈叉彻底消失,Reboot the target system假 log 也不再出现。
QSPI 与 rootfs 完全对齐到 36.4.7,从”强撑 hybrid”到”稳态生产机”。
最后跑一下 sudo nvbootctrl verify:
Info: variable BootChainFwStatus is not found.这次这条不再是故障信号——UEFI 已经直接把启动判成 good,压根没有 try 状态要转正。退出码 0,无害。
至于之前上的 apt-mark hold(49 个 nvidia-l4t-* 包),保留。它现在的作用不是”锁死在旧版”,而是”防止未来某次 apt upgrade 又一次把 nvidia-l4t-bootloader 拉上去,然后再次掉进 fwupd 那个 ESP 装不下的死循环”。真要再升 36.4.8+,正确姿势是 unhold → apt upgrade → 立即手动跑 nv_bootloader_capsule_updater.sh -q <新 .Cap 路径> → reboot。
十、经验总结与避坑指南
10.1 关于 apt upgrade 与 Jetson
- Jetson 不要盲目
apt upgrade——L4T 内核、驱动、QSPI 固件、initrd 深度绑定,跨版本升级要么走 SDK Manager 整机刷,要么一次做完、别中途断。 - 升级前先装齐
cpio/initramfs-tools等最底层的打包依赖——nv-update-initrd依赖cpio却不在自身依赖里,缺了就静默失败。 - 升级后如果引导包要重装,apt 完成 ≠ 引导链就绪,还需要
nvidia-l4t-bootloader-config.service跑完、capsule 应用;这套自动化并不牢靠。
10.2 关于引导链的假坑
- ESP 的
BOOTAA64.efi不是 GRUB,是 NVIDIA L4TLauncher。任何”看到BOOTAA64.efi就改名”的操作都会直接断引导。用strings提指纹(L4TLauncher/ExtLinuxBoot有 = 原生;有grub字样 = 是劫持件)先确认再动。 - GRUB 劫持件真实位置:p2 (A_kernel) 的
\EFI\BOOT\{bootaa64.efi, grubaa64.efi, grub.cfg},以及 Boot0007/Boot0009 这类直接指到 p2 的 Image/EFI 文件的 UEFI 入口。禁它们要临时 mount p2 才能改文件名。 efibootmgr永远用-o重排序、不要用-B删条目,删错一个就没了。
10.3 关于 UEFI boot-guard
OS chain A status = Unbootable是内核能起来但 init 挂不上/根挂不上 时 UEFI 的判定;此时任你怎么改引导文件都不生效,只能进 UEFI 手动改回 Normal。- 这是最后一道人工通道——不是解决方案,只是续命。真正的解决方案必须消除版本劈叉。
10.4 关于 Capsule update status: 0 假成功
这是全文最硬核的一节:
nv-l4t-bootloader-config.service每次开机都会打Trigger Capsule update is done.——但这是假 log,postinst里fwupdtool install-blob用> /dev/null 2>&1吞掉了所有错误。- Jetson Orin Nano 出厂 ESP 只有 64 MB,L4T 36.4.7 的
.Cap是 48.4 MB,fwupd 的 capsule-on-disk 要 2× 尺寸 → 物理上装不下; - 而且
create_capsule_uefi_variable()写的OsIndications = 全 0,UEFI 标准要求的 bit 2 根本没置位; - 因此热重启、冷启动、拔电源都不可能让 QSPI 升上来——不是”再等等就好”,是这个路径永远不会通。
- 正确的系统内更新通道:
/usr/sbin/nv_bootloader_capsule_updater.sh -q <.Cap 路径>——单份复制到EFI/UpdateCapsule/,正确置 OsIndications bit 2。一次sudo reboot生效。 - 判断真挂起 vs 假 log:
xxd /sys/firmware/efi/efivars/OsIndications-*第 5 字节应为04;ls /boot/efi/EFI/UpdateCapsule/应有.Cap。二者缺一 = 更新根本没挂起。
10.5 什么情况下还是得走主机重刷
- 单一物理引导链彻底损毁(比如 p2 里 L4TLauncher 和 GRUB 都被清掉,ESP 上 BOOTAA64.efi 也没了);
- UEFI 变量集损坏(
BootChainFwCurrent/TegraPlatformCompatSpec值本身不对,不是”没挂起”而是”底账烂了”); .Cap文件本身缺失或校验不过(/opt/ota_package/t23x/目录被误删)。- 这三种情况下,只能进 RCM 用主机 SDK Manager /
flash.sh整机刷。
10.6 单砖环境的心态守则
- 只有一块板、无主机可刷——那就绝不做
dd of=/dev/mtd*、绝不手工echo > efivars。任何”看起来能一步到位”的野路子都可能是不可逆砖板。 - A/B slot 是 Jetson 仅存的自动化兜底:任何 capsule 更新只写非活动 slot,启动失败 UEFI 会自动回滚——但也仅止于此。
- 能维持”强撑 hybrid 状态稳定运行”就是一种合格工程结局——不必强求一次到位;把真相挖到底,找一条正确的路,等维护窗口再来一次。
结语
从黑屏时只剩光标的绝望,到 SD 卡里 blkid 抄 UUID 的冷静;从 GRUB 命令行敲 nomodeset 那一击的兴奋,到脚本跑完仍黑屏的崩溃;从 UEFI 里把 Unbootable 强改成 Normal 的孤注一掷,到 apt-mark hold 锁 49 个包的”就这么过吧”;再到翻开 postinst 看到 > /dev/null 2>&1 时的会心一笑——“我就说这是软件问题。”
底层逻辑永远不会骗人,只会把真相藏在你没看的那一行。fwupdtool 需要 103 MB、OsIndications 差一个 bit、NVIDIA 自家还有另一条密道——这三步每一步都不神秘,只是需要有人把静默吞掉的错误重新打印出来。
保持冷静,sudo reboot 之前多问一句 “log 里那句 Trigger Capsule update is done.,它真的做了什么吗?”