Updating NVIDIA Jetson devices with Memfault OTA
NVIDIA offers one of the most comprehensive SDKs for developers of AI-heavy products. It includes a development kit that can emulate other devices in the lineup (Jetson AGX Orin DK), a simpler development kit for “entry-level” products (Jetson Orin Nano DK), a ton of exciting software libraries, AI models and even more examples of how to use them. It’s truly outstanding and out of the box shows up as a Ubuntu workstation which will feel very familiar.
However, it can be a bit daunting to figure out how to take this workstation experience and turn it into a headless unit that you can ship to customers far away and update remotely.
In this article, I will walk you through building a customized image of NVIDIA Jetson Linux and updating it via Memfault OTA service.
Pre-requisites
To use NVIDIA SDK, you need a Ubuntu machine running Ubuntu 20.04 or 22.04. You will need one NVIDIA developer kit, we use the Jetson Orin Nano Developer Kit because it is widely available. You will also need to install an NVMe drive on the bottom of the kit because the OTA system requires it for image-based OTA (an SDCard will not work).
Testing and validation for this article is based on NVIDIA Jetson Linux 36.3 (latest at the time of publication) and specifically the following files:
If your developer kit is brand new, you may need to update your firmware to support Jetson 36. We found these instructions easy to follow.
Preparing the image
NVIDIA provides a pre-built root filesystem which includes a graphical filesystem and lots of examples and tools. It’s great for initial experimentation but before going into production you will want to reduce the size of the image and only include software that is used.
Generating your root filesystem is very easy.
Generate a minimal root filesystem with the packages you need
First, download and decompress the BSP:
$ mkdir ~/nvidia && cd ~/nvidia
$ wget https://developer.nvidia.com/downloads/embedded/l4t/r36_release_v3.0/release/jetson_linux_r36.3.0_aarch64.tbz2
$ tar xpf jetson_linux_r36.3.0_aarch64.tbz2
Then, customize the list of packages that are included in the image:
$ cd ~/nvidia/Linux_for_Tegra/tools/samplefs
$ cp nvubuntu-jammy-minimal-aarch64-packages nvubuntu-jammy-interrupt-aarch64-packages
$ vi nvubuntu-jammy-interrupt-aarch64-packages
# ... customize the list of packages as needed
# For OTA, we will use curl and jq
$ echo "curl" >> nvubuntu-jammy-interrupt-aarch64-packages
$ echo "jq" >> nvubuntu-jammy-interrupt-aarch64-packages
Finally, use the nv_build_samplefs.sh
script to generate a tarball of the root filesystem with all the packages installed:
$ sudo ./nv_build_samplefs.sh --abi aarch64 --distro ubuntu --flavor interrupt --version jammy
This script only works on Ubuntu 22.04. If you are using a more recent version, NVIDIA provides a Docker image that you can use to generate the root filesystem.
At this point, you have a sample_fs.tbz2
file. You need to extract it in the rootfs
folder of the BSP and run the NVIDIA apply_binaries.sh
script.
# Decompress in the `rootfs` folder
$ cd ~/nvidia/Linux_for_Tegra/rootfs
$ sudo tar xpf ../tools/samplefs/sample_fs.tbz2
$ cd ..
# Install NVIDIA binary packages into the rootfs
$ ./apply_binaries.sh
Skip oem-config
and add a default user
For a production image, you will need to add a user account. This will also skip
the oem-config
program that is otherwise run automatically by Jetson Linux.
$ sudo tools/l4t_create_default_user.sh -u mf -n orin --accept-license
Creating: Username - mf, Password - OTk5MGE0, Autologin - false
Creating: Hostname - orin
A password is automatically generated for you by default. You can also set one with the
-p
option. Either way, it would not be a good idea to ship an image with a password that someone could possibly find and compromise all your devices.For a better alternative, look at our list of EOSS talks recap which includes “Engineering Secure SSH Access for Engineers”.
Install Memfault monitoring
Now would also be a great time to add Memfault monitoring to your image (make sure to replace <YOUR_PROJECT_KEY>
with a valid Memfault Project Key).
curl -sSf https://app.memfault.com/static/scripts/linux-quickstart.sh | sudo MEMFAULT_PROJECT_KEY="<YOUR PROJECT KEY>" chroot rootfs/ sh
This script will show some errors because it is not designed to run in a chroot environment. However, it will install the necessary packages and configuration files.
We want the version number reported to Memfault to include the NVIDIA /etc/user_release_version
file. So we adjust /usr/bin/memfault-device-info
:
#!/bin/bash
source /etc/os-release
USER_VERSION=$(awk '{print $NF}' /etc/user_release_version)
MACHINE_ID=$(cat /etc/machine-id)
echo "MEMFAULT_DEVICE_ID=$MACHINE_ID"
echo "MEMFAULT_HARDWARE_VERSION=orin"
echo "MEMFAULT_SOFTWARE_TYPE=ubuntu"
echo "MEMFAULT_SOFTWARE_VERSION=${VERSION_ID}.${USER_VERSION}"
Add Memfault OTA Agent
We use the agent described in the Generic OTA Guide. Let’s add it to our image:
Add /usr/bin/memfault-ota
to the image with the following content (and make sure it is executable):
#!/bin/sh
PROJECT_KEY=$(cat /etc/memfaultd.conf | jq -r '.project_key')
MEMFAULT_SOFTWARE_TYPE=$(cat /etc/memfaultd.conf | jq -r '.software_type')
MEMFAULT_SOFTWARE_VERSION=$(cat /etc/memfaultd.conf | jq -r '.software_version')
eval "$(memfault-device-info)"
url=$(curl -f -s --get \
--data-urlencode "hardware_version=${MEMFAULT_HARDWARE_VERSION}" \
--data-urlencode "software_type=${MEMFAULT_SOFTWARE_TYPE}" \
--data-urlencode "software_version=${MEMFAULT_SOFTWARE_VERSION}" \
--data-urlencode "device_serial=${MEMFAULT_DEVICE_ID}" \
--header "Memfault-Project-Key: ${PROJECT_KEY}" \
"https://api.memfault.com/api/v0/releases/latest/url")
exit_code=$?
if [ "$exit_code" -eq 0 ] && [ -n "$url" ]; then
# The following line will need to be updated for the specific update installer
# you are using.
update-installer $url
if [ $? -eq 0 ]; then
memfaultctl reboot --reason 3
else
logger -p user.err "OTA install failed"
fi
else
if [ "$exit_code" -ne 0 ]; then
logger -p user.err "OTA status check request failed"
logger -p user.err "curl returned exit code: ${exit_code}"
fi
fi
memfault-ota
will call the command update-installer
with the URL of the OTA package to install. We have to provide this command.
In the rootfs
, create a file usr/bin/update-installer
with the following content (and make sure it is executable):
#!/bin/sh
set -ex
rm -fr /ota
mkdir -p /ota/package
echo "Downloading..."
wget -O /ota/package.tar $1
echo "Preparing..."
tar -C /ota/package -xf /ota/package.tar
tar -C /ota/package -pxf /ota/package/ota_tools.tar.tbz2
echo "Installing..."
cd /ota/package/Linux_for_Tegra/tools/ota_tools/version_upgrade/
./nv_ota_start.sh /ota/package/ota_payload_package.tar.gz
Finally, to automatically look for OTA updates at regular intervals, we use a SystemD unit and timer. Create the following files in the rootfs:
/lib/systemd/system/memfault-ota.service
[Unit]
Description=Memfault OTA Service
Requires=network.target
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/sh /usr/bin/memfault-ota
[Install]
WantedBy=multi-user.target
/lib/systemd/system/memfault-ota.timer
[Unit]
Description=Launch %i service every 6 hours
[Timer]
OnBootSec=3min
OnUnitInactiveSec=6h
Persistent=true
[Install]
WantedBy=timers.target
Configure your image
Now is a great time to apply further customization to the image. You can add your programs, configuration files, etc. Hopefully, the examples above will provide some good inspiration.
Flash your device (first-time)
At this point, our image is ready to be packaged and flashed via a direct USB link to a device.
This is the initial preparation of the device, the one you would do at the factory:
# Generate images for QSPI
$ sudo ROOTFS_AB=1 ./tools/kernel_flash/l4t_initrd_flash.sh --showlogs -p "-c bootloader/generic/cfg/flash_t234_qspi.xml" --no-flash --network usb0 jetson-orin-nano-devkit internal
# Generate images for external storage device
$ sudo ROOTFS_AB=1 ./tools/kernel_flash/l4t_initrd_flash.sh --showlogs --no-flash --external-device nvme0n1p1 -c ./tools/kernel_flash/flash_l4t_t234_nvme_rootfs_ab.xml --external-only --append --network usb0 jetson-orin-nano-devkit external
# Flash images into the both storage devices
$ sudo ./tools/kernel_flash/l4t_initrd_flash.sh --showlogs --network usb0 --flash-only
Your system will reboot after the last command, and is ready to be used!
Prepare an update
Now without changing anything else (but feel free to edit the configuration or add other programs on the system), let’s increment the version in the nv_tegra/user_version
file and build an OTA update payload:
$ cd ~/nvidia/Linux_for_Tegra
$ sudo -E ROOTFS_AB=1 ./tools/ota_tools/version_upgrade/l4t_generate_ota_package.sh --external-device nvme0n1 jetson-orin-nano-devkit R36-3
This will generate the bootloader/jetson-orin-nano-devkit/ota_payload_package.tar.gz
file.
Our update-instaler
also needs the OTA tools so we will include them in each release.
$ cd ~/nvidia/Linux_for_Tegra/bootloader/jetson-orin-nano-devkit
$ cp ~/nvidia/ota_tools_R36.3.0_aarch64.tbz2 ./ota_tools.tar.bz2
$ source ../../nv_tegra/user_version
$ tar -cf update-22.04.$USER_VERSION.tar ota_tools.tar.tbz2 ota_payload_package.tar.gz
This update-0.2.tar
file is our complete update payload. It’s what will be distributed by Memfault OTA service. We are not compressing it because it’s content is already compressed (it’s one tar containing two compressed files).
Deploy the update
To upload the update you could use the Memfault web interface but it’s generally easier to do it from the command line:
$ pip install memfault-cli
$ memfault --org $MEMFAULT_ORG --org-token $MEMFAULT_OAT --project $MEMFAULT_PROJECT upload-ota-payload --hardware-version orin --software-type ubuntu --software-version 22.04.$USER_VERSION update-22.04.$USER_VERSION.tar
To activate this update, navigate to Memfault OTA and activate the release.
Watch the update!
You can wait for the device to check for updates every 6 hours or you can manually trigger the update by running the memfault-ota
command:
$ sudo memfault-ota
Conclusion
We have built a custom image for an NVIDIA Orin Nano Developer Kit with Memfault OTA and Memfault Monitoring. We are now able to update this device (and all its replicas!) over the air reliably.
This is a great starting point for building a production-ready device.
We left out signing and verifying the image. From an OTA distribution perspective, it makes no difference if the image is signed and encrypted. For a real deployment, you should do a threat analysis and implement appropriate security measures. NVIDIA includes a suite of tools to sign and verify updates.
If you have any questions or need help, feel free to reach out to us!