掌握嵌入式开发的关键工具,解锁高效设备部署新姿势
在NVIDIA Jetson嵌入式开发领域,l4t_initrd_flash.sh 脚本是开发者进行设备刷写的核心工具之一。
与传统flash.sh不同,它采用initrd(initial ramdisk)技术,通过USB引导设备进入RAM中的Linux环境,实现对eMMC或NVMe等存储器的直接写入操作。
本文将深入解析该脚本的各项参数及其应用场景。
一、脚本核心工作机制与工作流程
l4t_initrd_flash.sh属于L4T(Linux for Tegra)工具链的一部分,其工作流程分为两个关键阶段:
- 初始化阶段:通过
--no-flash参数生成刷写包 - 刷写阶段:使用
--flash-only将映像刷入设备
这种分离设计允许开发者预先生成刷写包,然后在多台设备上重复使用,显著提高批量生产效率。
1.1 与传统flash.sh的本质区别
# 传统刷写流程(依赖U-Boot)
sudo ./flash.sh jetson-agx-orin-devkit internal
# initrd刷写流程(RAM环境操作)
sudo ./tools/kernel_flash/l4t_initrd_flash.sh --external-device nvme0n1p1 -c flash_l4t_t234_nvme.xml jetson-orin-nano-devkit nvme0n1p1
- 无Bootloader依赖:通过USB将设备引导至RAM中的Linux环境(initrd),直接在内存中操作存储设备
- 双阶段分离:生成镜像包(
--no-flash)与刷写设备(--flash-only)解耦,支持预编译和批量部署 - 硬件识别机制:通过USB VendorID(0955)和ProductID精准识别Jetson设备
1.2 核心工作流程
二、参数详解与使用场景
设备与存储相关参数
| 参数 | 缩写 | 说明 | 应用场景 |
|---|---|---|---|
--external-device | 无 | 指定外部存储设备标识符 | 刷写NVMe SSD:--external-device nvme0n1p1 |
-c <config_file> | -c | 外部存储设备的分区布局配置文件 | 定义NVMe分区结构:-c flash_l4t_t234_nvme.xml |
-S <size> | -S | 设置APP分区大小(支持KiB/MiB/GiB) | 限制应用分区空间:-S 8GiB |
-k <partition> | -k | 仅刷写指定分区 | 快速更新内核分区:-k kernel |
2.1 设备控制参数
| 参数 | 作用 | 源码关键逻辑 |
|---|---|---|
--usb-instance | 指定物理设备标识(如1-2.4) | busnum-devpath组合标识设备 |
--direct | 直连模式(非USB恢复模式) | 跳过USB设备扫描,直接操作/dev/mmcblk0等设备节点 |
--massflash [N] | 批量刷写模式(默认上限10台) | 由${target_board}.conf中的MAX_MASSFLASH定义上限 |
--device-instance | 设备实例编号(多设备时自动递增) | instance=$((instance + 1))循环分配ID |
2.2 存储配置参数
# 典型NVMe刷机命令
sudo ./l4t_initrd_flash.sh \
--external-device nvme0n1p1 \ # 指定NVMe分区
-c flash_l4t_t234_nvme.xml \ # 分区布局配置文件
-S 32GiB \ # 限制APP分区大小
-k kernel \ # 仅刷写内核分区
jetson-agx-orin-devkit external
-c <config_file>:
外部存储分区配置文件(XML格式),定义rootfs/APP等分区布局-S <size>:
设置APP分区容量(支持KiB/MiB/GiB单位),如-S 16GiB-k <partition>:
选择性刷写指定分区(如-k kernel仅更新内核)
2.3 流程控制参数
| 参数 | 作用 | 组合使用场景 |
|---|---|---|
--no-flash | 仅生成刷写包不执行刷写 | 预编译阶段:生成基础镜像包 |
--flash-only | 使用预生成镜像直接刷写 | 生产环境:配合--no-flash生成的镜像包 |
--append | 追加外部存储配置到现有镜像包 | 分步操作:先内部存储后追加NVMe配置 |
--reuse | 复用之前生成的工作目录 | 快速迭代:避免重复解压基础文件 |
--keep | 刷写后保留临时文件 | 调试:检查生成的initrd和flash布局 |
2.4 高级功能参数
网络刷写模式(适用于无物理接触场景)
# USB网络刷写(通过usb0虚拟网卡)
--network usb0
# 以太网刷写(指定IP和网关)
--network eth0:192.168.0.17/24:192.168.0.21
- 原理:在主机上配置NFS服务,设备通过网络加载刷写文件
安全验证参数
-u <key_file>:PKC密钥文件(ODM融合设备)-v <key_file>:SBK密钥(存储加密)-i <key>:根文件系统加密密钥
调试辅助参数
--showlogs # 实时显示刷写日志
--initrd # 停留在initrd环境(不执行刷写)
- 日志路径:
Linux_for_Tegra/initrdlog/flash_${devpath}_${instance}_${ts}.log
三、关键位置参数解析
脚本需要两个必需的位置参数:
-
<board-name>:目标设备型号标识符- 例如:
jetson-orin-nano-devkit、jetson-xavier-nx-devkit-emmc
- 例如:
-
<rootdev>:根文件系统目标设备- eMMC/SD卡:
mmcblk0p1 - NVMe SSD:
nvme0n1p1 - 外部存储:
external
- eMMC/SD卡:
四、实用场景示例
场景1:刷写Jetson Orin Nano到NVMe SSD
sudo ./l4t_initrd_flash.sh --external-device nvme0n1p1 \
-p "-c ./bootloader/t186ref/cfg/flash_t234_qspi.xml" \
-c ./tools/kernel_flash/flash_l4t_t234_nvme.xml \
--showlogs --network usb0 \
jetson-orin-nano-devkit nvme0n1p1
场景2:修复文件系统挂载失败问题
当出现 “ERROR: mmcblk0p1 mount fail” 错误时:
- 在initrd中添加e2fsck工具
- 重建initrd映像
- 仅刷写更新后的initrd:
./tools/kernel_flash/l4t_initrd_flash.sh --flash-only \
--initrd --showlogs \
jetson-xavier-nx-devkit-emmc mmcblk0p1
场景3:批量生产环境刷写
# 生成刷写包
sudo ./l4t_initrd_flash.sh --no-flash --external-device nvme0n1p1 \
-c flash_l4t_t234_nvme.xml jetson-agx-orin-devkit external
# 同时刷写多台设备
sudo ./l4t_initrd_flash.sh --massflash 5 --showlogs \
--flash-only jetson-agx-orin-devkit external
五、注意事项与排错技巧
-
恢复模式准备:执行刷写前需将设备置于Force Recovery Mode
- 长按恢复按钮 → 按下电源键 → 释放恢复按钮
-
文件系统兼容性:
# 刷写前检查文件系统 e2fsck -f /dev/mmcblk0p1 resize2fs /dev/mmcblk0p1 -
日志分析:刷写日志保存在
Linux_for_Tegra/initrdlog/目录- 文件名格式:
flash_<devpath>_<instance>_<timestamp>.log
- 文件名格式:
-
设备识别失败处理:当脚本无法识别设备时
- 检查udev规则:
/etc/udev/rules.d/99-l4t-host.rules - 确认USB连接状态:
lsusb | grep 0955
- 检查udev规则:
-
设备无法识别
# 检查udev规则
ls /etc/udev/rules.d/99-l4t-host.rules
# 验证设备进入恢复模式
lsusb | grep 0955:7[ef]23 # Orin系列设备ID
- 文件系统挂载失败
# 在initrd中添加e2fsck工具
cp $(which e2fsck) ${INITRD_DIR}/bin
# 重建initrd后单独刷写
./l4t_initrd_flash.sh --flash-only --initrd --showlogs \
jetson-xavier-nx-devkit-emmc mmcblk0p1
六、与传统flash.sh的对比优势
| 特性 | l4t_initrd_flash.sh | 传统flash.sh |
|---|---|---|
| 刷写机制 | 通过initrd在RAM中操作 | 直接写入存储设备 |
| 速度 | ⭐⭐⭐⭐ (较快) | ⭐⭐⭐ (较慢) |
| 外部存储支持 | ✅ 原生支持NVMe/UFS/SSD | ⚠️ 有限支持 |
| 安全启动 | ✅ SBK/PKC密钥注入 | ⚠️ 基础支持 |
| 批量刷写 | ✅ 支持多设备并行 | ❌仅单设备 |
| 网络刷写 | ✅ NFS/TFTP支持 | ❌ 不支持 |
| 依赖项 | 不依赖U-Boot | 需要bootloader |
| 资源占用 | 较高(需解压initrd) | 较低 |
结语
掌握l4t_initrd_flash.sh的深入用法,能够显著提升Jetson设备部署效率,特别是在大规模生产环境和定制化存储方案中。该脚本的模块化设计(生成与刷写分离)和网络刷写支持,使其成为工业级应用的首选工具。建议结合具体硬件型号和JetPack版本,通过小规模测试验证后再部署到生产环境。
建议
-
环境隔离原则
使用物理Ubuntu主机(非虚拟机)执行刷写,避免USB连接不稳定 -
版本匹配策略
JetPack版本需与L4T版本严格对应(如JetPack 6.0对应L4T 36.3.x) -
日志管理规范
启用--showlogs并归档initrdlog/目录,建立设备刷写档案 -
安全操作警告
慎用--erase-all:会完全擦除存储设备所有分区
附录:Linux_for_Tegra/tools/kernel_flash/l4t_initrd_flash.sh
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
# Usage: ./l4t_initrd_flash.sh <options> [ --external-device <ext> -c <cfg> -S <SIZE> ] <target> <rootfs_dir>
# This script flashes the target using initrd
set -eo pipefail
trap cleanup EXIT
clean_up_network_flash()
{
if [ -n "${network}" ]; then
if [ -f /etc/exports ]; then
sed -i -e '/^# Entry added by NVIDIA initrd flash tool/,+1d' /etc/exports
fi
if command -v exportfs &> /dev/null; then
exportfs -ra
fi
fi
}
cleanup()
{
remove_udev_rules
clean_up_network_flash
}
install_udev_rules()
{
ln -s "$(realpath "${UDEV_L4T_DIR}/99-l4t-host.rules")" /etc/udev/rules.d/
ln -s "$(realpath "${UDEV_L4T_DIR}/10-l4t-usb-msd.rules")" /etc/udev/rules.d/
}
remove_udev_rules()
{
rm -f /etc/udev/rules.d/99-l4t-host.rules
rm -f /etc/udev/rules.d/10-l4t-usb-msd.rules
}
fill_devpaths()
{
# Find devices to flash
devpaths=($(find /sys/bus/usb/devices/usb*/ -name devnum -print0 | {
found=()
while read -r -d "" fn_devnum; do
dir="$(dirname "${fn_devnum}")"
vendor="$(cat "${dir}/idVendor")"
if [ "${vendor}" != "0955" ]; then
continue
fi
product="$(cat "${dir}/idProduct")"
case "${product}" in
"7018") ;; # TX2i
"7418") ;; # TX2 4GB
"7c18") ;; # TX2, TX2 NX
"7019") ;; # AGX Xavier
"7819") ;; # AGXi
"7919") ;; # AGXi
"7023") ;; # AGX Orin
"7223") ;; # AGX Orin 32GB
"7323") ;; # Orin NX 16GB (p3767-0000)
"7423") ;; # Orin NX 8GB (p3767-0001)
"7523") ;; # Orin Nano 8GB (p3767-0003)
"7623") ;; # Orin Nano 4GB (p3767-0004)
"7e19") ;; # NX
*)
continue
;;
esac
fn_busnum="${dir}/busnum"
if [ ! -f "${fn_busnum}" ]; then
continue
fi
fn_devpath="${dir}/devpath"
if [ ! -f "${fn_devpath}" ]; then
continue
fi
# Only include devices for which the DEVNAME exists. In a container
# environment, the DEVNAME for this device may not have been mapped
# in, which is the case for when a device is in recovery mode, but
# that device is not mapped into the current container.
devname=$(udevadm info --query=property "$dir" | grep DEVNAME | cut -d= -f2)
if [ ! -e "$devname" ]; then
continue
fi
busnum="$(cat "${fn_busnum}")"
devpath="$(cat "${fn_devpath}")"
if [ -n "${usb_instance}" ] && [ "${usb_instance}" != "${busnum}-${devpath}" ]; then
continue
else
found+=("${busnum}-${devpath}")
fi
done
echo "${found[@]}"
}))
# Handle the "direct" device logically no difference than a device that is connected on Jetson.
# So add the "direct" device to the Jetson device list.
if [ -n "${direct}" ]; then
devpaths+=("direct")
fi
}
L4T_INITRD_FLASH_DIR="$(cd "$(dirname "${0}")" && pwd)"
L4T_TOOLS_DIR="${L4T_INITRD_FLASH_DIR%/*}"
LINUX_BASE_DIR="${L4T_TOOLS_DIR%/*}"
showlogs=0
flash_only="0"
initrd_only=""
reuse_package=""
external_only=""
no_flash="0"
target_partname=""
max_massflash=""
keep=""
network=""
direct=""
reuse=""
export initrd_flash_step=1
UDEV_L4T_DIR="${L4T_INITRD_FLASH_DIR}/host_udev"
flash_cmd="${L4T_INITRD_FLASH_DIR}/l4t_initrd_flash_internal.sh"
source "${L4T_INITRD_FLASH_DIR}"/l4t_initrd_flash.func
parse_param "$@"
remove_udev_rules
install_udev_rules
fill_devpaths
if [ "${flash_only}" = "0" ]; then
# Generate flash images for both single flash and massflash
echo "${flash_cmd} --no-flash $*"
"${flash_cmd}" --no-flash "$@"
echo "Finish generating flash package."
fi
if [ "${no_flash}" = "1" ]; then
echo "Put device in recovery mode, run with option --flash-only to flash device."
exit 0
fi
# Exit if no devices to flash
if [ "${#devpaths[@]}" -eq 0 ]; then
echo "No devices to flash"
exit 1
fi
# If we got here, that means user is doing flash in a single command. Therefore,
# check this condition.
if [ "${flash_only}" = "0" ] && [ -z "${direct}" ]; then
if [ ${#devpaths[@]} -gt "1" ]; then
echo "Error, too many devices in RCM mode"
echo "For signing and flashing, only one device is supported"
exit 1
fi
fi
flash_param="$(cat "${L4T_INITRD_FLASH_DIR}/${INITRD_FLASHPARAM}")"
target_board=$(awk -F" " '{print $(NF-1)}' "${L4T_INITRD_FLASH_DIR}/${INITRD_FLASHPARAM}")
CHIPID=$(LDK_DIR=${LINUX_BASE_DIR}; source "${LDK_DIR}/${target_board}.conf";echo "${CHIPID}")
ts=$(date +%Y%m%d-%H%M%S);
instance=0
get_max_flash
if [ ${#devpaths[@]} -gt "${max_massflash}" ] && [ -z "${direct}" ]; then
echo "Too many devices in RCM mode"
exit 1
fi
mkdir -p "${LINUX_BASE_DIR}/initrdlog/"
for devpath in "${devpaths[@]}"; do
fn_log="${LINUX_BASE_DIR}/initrdlog/flash_${devpath}_${instance}_${ts}.log"
cmdarg=()
[ -n "${keep}" ] && cmdarg+=("--keep")
[ -n "${reuse}" ] && cmdarg+=("--reuse")
[ -n "${initrd_only}" ] && cmdarg+=("--initrd")
[ -n "${reuse_package}" ] && cmdarg+=("--use-backup-image")
[ -n "${external_only}" ] && cmdarg+=("${external_only}")
[ -n "${target_partname}" ] && cmdarg+=("-k" "${target_partname}")
[ -n "${network}" ] && cmdarg+=("--network" "${network}")
[ -n "${direct}" ] && cmdarg+=("--direct" "${direct}")
[ -n "${EKB_PAIR}" ] && cmdarg+=("--ekb-pair")
[ -n "${erase_all}" ] && cmdarg+=("--erase-all")
if [[ "${CHIPID}" = "0x19" && "${flash_only}" = "0" && -n "${FAB}" ]]; then
# For T194, if FAB has already been defined, T194 must not read
# uid again otherwise, it will hang.
cmdarg+=("--skipuid")
fi
cmd="${flash_cmd} ${cmdarg[*]} --usb-instance ${devpath} --device-instance ${instance} --flash-only ${flash_param}";
echo "${cmd}"
if [ "${max_massflash}" -eq 1 ] || [ -n "${direct}" ]; then
eval "${cmd}" 2>&1 | tee "${fn_log}"
echo "Log is saved to Linux_for_Tegra/initrdlog/flash_${devpath}_${instance}_${ts}.log "
exit
else
eval "${cmd}" > "${fn_log}" 2>&1 &
fi
flash_pid="$!";
flash_pids+=("${flash_pid}")
echo "Start flashing device: ${devpath}, rcm instance: ${instance}, PID: ${flash_pid}";
echo "Log will be saved to Linux_for_Tegra/initrdlog/flash_${devpath}_${instance}_${ts}.log "
if [ "${showlogs}" -eq 1 ]; then
gnome-terminal -- /bin/bash -c "tail -f ${fn_log}" -t "${fn_log}" > /dev/null 2>&1 &
fi;
if [ "${max_massflash}" -eq 1 ]; then
break
fi
instance=$((instance + 1))
done
# Wait until all flash processes done
failure=0
while true; do
running=0
if [ ${showlogs} -ne 1 ]; then
echo -n "Ongoing processes:"
fi;
new_flash_pids=()
for flash_pid in "${flash_pids[@]}"; do
if [ -e "/proc/${flash_pid}" ]; then
if [ ${showlogs} -ne 1 ]; then
echo -n " ${flash_pid}"
fi;
running=$((running + 1))
new_flash_pids+=("${flash_pid}")
else
wait "${flash_pid}" || failure=1
fi
done
if [ "${showlogs}" -ne 1 ]; then
echo
fi;
if [ "${running}" -eq 0 ]; then
break
fi
flash_pids=("${new_flash_pids[@]}")
sleep 5
done
if [ ${failure} -ne 0 ]; then
echo "Flash complete (WITH FAILURES)";
exit 1
fi
984

被折叠的 条评论
为什么被折叠?



