Week 5 · Coding Period

Building the AGL image, and bringing up the Jetson

This week I built a custom AGL image for the Jetson Orin Nano from source and tried to boot it on the real board. Here's everything I did, in order — and every wall I hit along the way, and how I got past it.

The plan for the week was simple to state: build AGL for the Jetson, put it on the board, boot it. Each of those three steps turned into its own fight. This is the log.

1. Building the AGL image

What I did: AGL is assembled with Yocto/BitBake from stacked layers — poky (core) + meta-agl (the AGL bits) + meta-tegra (the Jetson BSP). AGL doesn't officially support Jetson, so there was no ready-made target; I wrote a small machine template to name the board and pull meta-tegra into the build, then kicked off bitbake agl-image-minimal (5,356 build tasks) inside a tmux session so it would survive a dropped SSH connection.

It did not go cleanly. I hit four separate walls before it built.

Problem — host Python too new

The build server's system Python (3.13) is newer than this Yocto release supports, and BitBake bailed out on a sanity check. I have no root on that server, so I couldn't just install an older Python.

Fix

Sourced Yocto's buildtools tarball — a self-contained toolchain (Python 3.12) that runs entirely from my home directory, no root needed. BitBake was happy after that.

Problem — circular dependency

First build died with tegra-minimal-initramfs ... has circular dependency on do_image_tegraflash. I'd added the Jetson flash-image output globally, so it also landed on the init-ramdisk, which tried to wrap itself in a flash bundle.

Fix

Scoped the flash output to my image only:

IMAGE_FSTYPES:append:pn-agl-image-minimal = " tegraflash"

Problem — missing init-ramdisk format

Next failure: the bootloader recipe tried to copy a cpio.gz init-ramdisk that didn't exist. AGL hard-sets the format to ext4.gz and drops the plain cpio.gz that meta-tegra needs.

Fix

INITRAMFS_FSTYPES:append = " cpio.gz"

Problem — a partition layout that doesn't exist

Then do_image_wic died looking for a .wks partition file. AGL forces a wic.xz disk image, but the Jetson boots through meta-tegra's own path and has no wic layout.

Fix

IMAGE_FSTYPES:remove = "wic.xz wic.bmap wic.xz.sha256sum"

Result: all 5,356 tasks finished, producing a ~149 MB Jetson flash bundle (rootfs + bootloader + device tree + flash scripts). All four fixes live in the machine template, so the build is reproducible — re-running it doesn't undo them.

2. Flashing it to an SD card

What I did: the bundle includes a script that builds a single SD-card image with the right partitions. I wrote that to the card with bmaptool.

Problem

The default image is sized for a full 32 GB, but a "32 GB" card is really ~29.7 GiB — no margin, didn't fit.

Fix

Regenerated the image a bit smaller so it fits with room to spare, then flashed:

sudo bmaptool copy --bmap agl-orin-sd.img.bmap agl-orin-sd.img /dev/sdX
bmaptool: info: 100% copied

3. First boot — it's alive

What I did: card into the Jetson, USB-to-serial adapter on the debug UART to watch the boot, power on. The console immediately scrolled my image:

L4TLauncher: Attempting Direct Boot
Linux version 5.15.185-l4t-r36.5 ... (oe-user@)
Machine model: NVIDIA Jetson Orin Nano Developer Kit

The oe-user@ tag proves it's my Yocto build, not stock NVIDIA. And I confirmed it's really AGL by reading the rootfs identity off the card:

$ cat /etc/os-release
ID=agl
PRETTY_NAME="Automotive Grade Linux 21.0.1 (unagi)"

The build works. The firmware loads and runs my AGL kernel on the real Jetson, and the root filesystem is genuine Automotive Grade Linux. That alone validated the whole build effort.

4. Problem — the board couldn't read the SD card

Then the boot stalled. Right after loading the kernel, the log filled with the same error, forever:

blk_update_request: I/O error, dev mmcblk0, sector 0 (READ)
mmcblk0: unable to read partition table

Every read from the card failed — it couldn't even read sector 0, so it could never mount the root filesystem.

What I tried: my first guess was a bad card or bad write. I ruled that out fast — I put the same card in my laptop and it read perfectly, every partition mounted. The Jetson's own firmware had also just read it (that's how it loaded the kernel). So the card and the image were fine; only the running kernel couldn't talk to the card. That narrowed it down a lot.

5. The root cause — a firmware a whole generation too new

The clue was in the first line of the boot log. I lined up the two version numbers the board reported:

PieceVersionGeneration
Board firmware39.2.0 (L4T r39.2)JetPack 7.2
My AGL kernel5.15-l4t-r36.5JetPack 6.2

The board's firmware had been updated to JetPack 7.2, but the AGL branch I build against targets JetPack 6.2 — a full generation apart.

Why it breaks the SD card  On the Jetson, the firmware sets up the SoC hardware — including the SD/MMC controller's timing — before handing off to Linux. The JetPack 7 firmware configures it one way; my JetPack 6 kernel expects another. They disagree on how to drive the card, so the kernel's reads fail. The firmware can still read it in a slow safe mode (so it boots the kernel), but the kernel inherits a controller set up for the wrong generation. The card and cable were never the problem — the version mismatch was.

6. The fix — realign firmware and OS

The plan: make the firmware and the OS the same generation again. The cleanest way is a full flash over USB recovery mode — the meta-tegra bundle ships its own matching firmware, so a recovery flash rewrites the board's firmware down to JetPack 6 and writes the OS together, in one coherent system. SD-only boot leans on whatever firmware is already there; the recovery flash replaces it, which is exactly what's needed here.

7. Problem — actually getting into recovery mode

Which dropped me straight into the next fight. To flash over USB, the board has to enter Force Recovery Mode, where it stops booting and shows up on the laptop as a special USB device (0955 NVIDIA Corp. APX). Simple on paper — jumper two pins, apply power — and it refused to work for hours. What I worked through:

The serial console was the key tool the whole time — it's what let me tell whether the board went silent because it entered recovery, or just because it reset.

8. Where it stands, and what's next

Status: the image is proven correct and the board boots my AGL kernel on real hardware — the headline result for the week. What's left is mechanical: finish the USB-recovery flash to put the firmware and OS on the same generation, which clears the SD read errors and takes the board all the way to a login prompt.

Next: finish the recovery flash and reach a full AGL userspace on the Jetson, then — back on the main plan — add meta-ros so the board can subscribe to the CARLA LiDAR stream over the network, exactly as the emulated VM already does.

Bare-metal weeks are humbling — a build that compiled cleanly still took days of cables, header pins and a single version number to coax toward life. But understanding why a JetPack 6 image and JetPack 7 firmware can't share a board is the kind of thing you only learn by hitting it.

← Week 4
Home →