Setup
- Get a workstation running Ubuntu 20.04 LTS (this was used for this tutorial) or Ubuntu 18.04 LTS (according to Nvidia docs).
- For JetPack 5.1.2+
- Download from Jetson Linux:
- For JetPack 6.X
- Download from Jetson Linux:
- Driver Package (BSP)
- Sample Root Filesystem
- OTA tools
- Download from Jetson Linux:
- For this tutorial we will be using a Jetson AGX Orin devkit, it was tested in a Jetson Orin Nano devkit as well
Pre flash steps
- Extract the BSP (will create
Linux_for_Tegra
folder) - Extract the Sample Root Filesystem with sudo into
Linux_for_Tegra/rootfs
(see below) - Extract the OTA tools in the
Linux_for_Tegra
folder. - Populate the L4T with Nvidia provided binaries for the hardware:
sudo ./apply_binaries.sh
- Optional: Create user/password so we can ssh into Jetson after flash:
sudo ./tools/l4t_create_default_user.sh -u mender -p changeme -n jetson --accept-license
Flashing
From this point all the commands are executed in a Workstation running Ubuntu
Make sure the device is in force recovery mode
We must use the l4t_initrd_flash.sh
for Orin NX and Nano or flash.sh
for the others. Please visit for JetPack 5 and for JetPack 6 for more information.
Ensure to use the ROOTFS_AB=1
flag to enable the A/B partitioning.
For example:
sudo ROOTFS_AB=1 ROOTFS_RETRY_COUNT_MAX=1 ./flash.sh jetson-agx-orin-devkit mmcblk0p1
After flashing
After the flashing, boot the device and perform the following actions
Partitioning schema
From this point all the commands are executed the Jetson Device, you can connect to it by Serial connection, SSH or by using a monitor and keyboard.
We will need a place where to store the /data
directory for persistent data. In this example, we are using the UDA
partition for storing the Mender’s persistent /data
directory.
Assuming the UDA
partition is the XX, then, lets format it for storing files in there. We tested it with ext4.
sudo mkfs.ext4 /dev/mmcblk1pXX
And use it as the /data partition as required by Mender:
sudo mkdir -p /data
sudo su -c "echo '/dev/mmcblk1pXX /data ext4 defaults 0 0' >> /etc/fstab"
sudo mount -a
Install Mender
Download the Mender’s application. In this tutorial we are using the Express installation, but you can choose a different method from here. If you follow this one, you must install curl
first or use wget
instead.
curl -fLsS https://get.mender.io -o get-mender.sh
# Review the content of the get-mender.sh file
sudo bash get-mender.sh
# The device type is set as jetson-orin for this example
As it was installed as a userspace application, let’s move some configuration files to the /data
partition
sudo systemctl stop mender-client
sudo mv /var/lib/mender /data
sudo ln -s /data/mender /var/lib/mender
Let’s create the rootfs-image-jetson Update Module
This Update Module will handle Jetson updates and interact with the Nvidia’s OTA tools and the bootloader interface cli nvbootctrl.
Please read all the comments inside the following code (Update Module), you may need to tweak it
Create the file /usr/share/mender/modules/v3/rootfs-image-jetson
with the following content:
#!/bin/sh
set -ue
STATE="$1"
FILES="$2"
# Notice *ota_tools_aarch64.tbz2* and *ota_payload_package.tar.gz*
# names should match with the name when creating the `mender-artifact`
ota_payload_package="$FILES"/files/ota_payload_package.tar.gz
nvidia_tools="$FILES"/files/ota_tools_aarch64.tbz2
# Nvidia uses the concept of "slots", they will
# be always 0 and 1 values for A and B partitions
MENDER_ROOTFS_PART_A_NUMBER="0"
MENDER_ROOTFS_PART_B_NUMBER="1"
# Some useful information for integrity check
mkdir -p "/data/fs_update/"
mender_boot_part="/data/fs_update/mender_boot_part"
active_num="$(nvbootctrl get-current-slot)"
if test $active_num -eq $MENDER_ROOTFS_PART_A_NUMBER; then
passive_num=$MENDER_ROOTFS_PART_B_NUMBER
else
passive_num=$MENDER_ROOTFS_PART_A_NUMBER
fi
case "$STATE" in
Download)
if [ "$(nvbootctrl get-number-slots)" != "2" ]; then
echo "Your device need to be configured to use A/B partitioning"
exit 1
fi
;;
ArtifactInstall)
# Let's record what slot we expect to boot next time
echo $passive_num > $mender_boot_part
# You need a big enough DATA partition to fit the rootfs image,
# you can change it for any tmp folder as well
DATA="/data"
# Let's enable Nvidia's OTA tools to our workdir
mkdir -p "${DATA}/workdir"
WORKDIR="${DATA}/workdir"
tar -jxvf $nvidia_tools -C $WORKDIR
# Let's move the OTA payload
mkdir -p "${DATA}/ota/"
ln -sfn "${DATA}/ota" "/ota"
mv $ota_payload_package "${DATA}/ota/"
# And this folder is required for the updater
mkdir -p "${DATA}/ota_work"
ln -sfn "${DATA}/ota_work" "/ota_work"
# Let's trigger the upgrade process from Nvidia's OTA tools
cd ${WORKDIR}/Linux_for_Tegra/tools/ota_tools/version_upgrade
# Uncomment the following one for the DevKit using an SD card. **
# ln -sfn /ota_work/external_device/ /ota_work/internal_device
# For Orin Nano DevKit
./nv_ota_start.sh /dev/mmcblk1 "/ota/ota_payload_package.tar.gz"
# nv_ota_start.sh set the unused slot to active for next reboot
>&2 echo "Next boot will load Slot $passive_num"
# Cleaning up the disk
rm -rf "${DATA}/ota_work"
rm -rf "${DATA}/ota"
rm -rf "${DATA}/workdir"
unlink /ota
# The following one just if uncommented **
#unlink /ota_work/external_device/
unlink /ota_work
;;
PerformsFullUpdate)
echo "Yes"
;;
NeedsArtifactReboot)
echo "Automatic"
;;
SupportsRollback)
echo "Yes"
;;
ArtifactVerifyReboot)
# We use stderr for logging as Mender protocol uses stdout for exchanging messages with the server.
>&2 echo "ArtifactVerifyReboot: The active partition is $active_num while the last passive was $(cat $mender_boot_part)"
if test "$(cat $mender_boot_part)" != "$active_num"; then
exit 1
fi
# Recommend calling sync at the end here as well
sync
;;
ArtifactVerifyRollbackReboot)
>&2 echo "ArtifactVerifyRollbackReboot: The active partition is $active_num while the last passive was $(cat $mender_boot_part)"
if test "$(cat $mender_boot_part)" = "$active_num"; then
exit 1
fi
# Recommend calling sync at the end here as well
sync
;;
ArtifactCommit)
>&2 echo "ArtifactCommit: The active partition is $active_num while the last passive was $(cat $mender_boot_part)"
if test "$(cat $mender_boot_part)" = "$active_num"; then
>&2 echo "Flagging the deployment as successful"
else
# If we get here, an upgrade in standalone mode failed to
# boot and the user is trying to commit from the old OS.
# This communicates to the user that the upgrade failed.
echo "Upgrade failed and was reverted: refusing to commit!"
exit 1
fi
;;
ArtifactRollback)
>&2 echo "ArtifactRollback: The active partition is $active_num while the last passive was $(cat $mender_boot_part)"
if test "$(cat $mender_boot_part)" = "$active_num"; then
nvbootctrl set-active-boot-slot $passive_num
fi
sync
;;
esac
exit 0
And make it executable
sudo chmod +x /usr/share/mender/modules/v3/rootfs-image-jetson
You can start the Mender service at this point
sudo systemctl start mender-client
Golden image generation
From now just modify this image as you like. We are going to take a snapshot to create a OTA package with a Mender artifact.
Snapshot creation
When you are happy with your “new image” we create a snapshot in another workstation from the Jetson device.
USER="user" #host username
HOST="host-ip" #check it is reachable from the Jetson
sudo mender snapshot dump | ssh $USER@$HOST /bin/sh -c 'cat > $HOME/my-jetson-fs.raw'
Nvidia’s OTA package creation
From this point all the commands are executed in a Workstation running Ubuntu 20.04
Assuming now you have the image dump from the previous command in your workstation in $HOME/my-jetson-fs.raw
# Let's create a temp file to mount the previous raw image
mkdir -p tmp-fs
sudo mount -o loop my-jetson-fs.raw tmp-fs
cd tmp-fs
# And we create a tarball with its content
sudo tar -cvpzf ../image_fs.tar.gz --exclude=./data --exclude=./image_fs.tar.gz --one-file-system ./
cd ..
# At this point we don't need the raw image generated by mender snapshot. Clean the folder a little bit if you want
sudo umount tmp-fs
rm my-jetson-fs.raw
Now we can create the ota_payload_package
from Nvidia.
Please be aware
rootfs_updater.sh
istools/ota_tools/version_upgrade/nv_ota_update.sh
renamed, you can modify this file if needed.
cp ./Linux_for_Tegra/tools/ota_tools/version_upgrade/nv_ota_update.sh rootfs_updater.sh
In the following command,
-
-s
Skip generating the rootfs image. We are providing one from the golden image by setting the-f
option. -
-r
Generates an OTA payload package to only update rootfs. This option is meaningful only for an update without layout (re-partitioning) change.
- Replace the
/path/to
from the following command.- You may need to use
--external-device nvme0n1
if using a SD card, and that is the reason we may need theln -sfn /ota_work/external_device/ /ota_work/internal_device
in the Update Module.image_fs.tar.gz
is the tarball we generated at the begining of this section.
cd ./Linux_for_Tegra/tools/ota_tools/version_upgrade/
sudo l4t_generate_ota_package.sh -sr \
-f /path/to/image_fs.tar.gz \
-o /path/to/rootfs_updater.sh \
jetson-agx-orin-devkit R36-3
For now, R35-5
is the current latest L4T release for JetPack 5, R36-3 for JetPack 6, you may need to change it when generating an Upgrade Image for the following releases.
Mender artifact generation
Now we can generate a Mender artifact. Previously we created a tarball that the Nvidia toolset knows how to handle, now we are adding metadata so Mender will know how to trigger all the Nvidia’s OTA tools by using the Update Module we created before
Replace the
/path/to
from the following command.
You can add your own artifact names. Also, modify the jetson-orin
name for another one that better fits your device. Also replace the following Xs with the actual name of your device:
ARTIFACT_NAME="my-update-1.0"
DEVICE_TYPE="jetson-orin"
OUTPUT_PATH="my-update-1.0.mender"
# Make sure the names are consistent with the Update Module,
# you may need to rename ota_tools_R3X.X.X_aarch64.tbz2 to
# ota_tools_aarch64.tbz2 for example
IMAGE="./path/to/Linux_for_Tegra/bootloader/jetson-XXXX-devkit/ota_payload_package.tar.gz"
OTA_TOOLS="./path/to/ota_tools_R3X.X.X_aarch64.tbz2"
mender-artifact write module-image -T rootfs-image-jetson -n ${ARTIFACT_NAME} -t ${DEVICE_TYPE} -o ${OUTPUT_PATH} -f ${IMAGE} -f ${OTA_TOOLS}
And now you can upload to the Mender Server the new my-update-1.0.mender
or however you named OUTPUT_PATH
in the step above.
Before deployment
From this point all the commands are executed in the Jetson Device, you can connect to it by Serial connection, SSH or by using a monitor and keyboard.
Check in what SLOT you are right now:
sudo nvbootctrl -t rootfs dump-slots-info
Current rootfs slot: A
Active rootfs slot: A
num_slots: 2
slot: 0, retry_count: 3, status: normal
slot: 1, retry_count: 3, status: normal
Create a deployment
From this point all the commands are in the Mender Web UI at https://hosted.mender.io
Upload the artifact
You will need to click on RELEASES on the left panel and then the purple UPLOAD button, there you can upload your my-update-1.0.mender
file.
Deploy the artifact
Then, click on DEVICES, look for the Jetson device and click on it. Then on the + button at the right bottom corner, and then to “Create Deployment for this Device”.
You can now click on DEPLOYMENTS section from the left panel and check its progress.
The Jetson was updated
From this point all the commands are executed back in the Jetson device by using a monitor, SSH or a Serial connection.
First, check in what SLOT you are right now, as you can see, it switched to B:
Current rootfs slot: B
Active rootfs slot: B
num_slots: 2
slot: 0, retry_count: 3, status: normal
slot: 1, retry_count: 3, status: normal
And finally, you should be able to see anything new that was added as part of the golden image.