Series Secure boot on an i.MX8M gateway Part 5 of 10 · all parts

Getting the U-Boot build environment exactly right

With backups done and the keys generated, the next real milestone is building an actual bootloader from source — first completely unsigned, just to prove the…

With backups done and the keys generated, the next real milestone is building an actual bootloader from source — first completely unsigned, just to prove the build itself works, before adding any signing on top. This post is about everything that had to be in place before that build could even start, and why I spent more time on version-pinning than I originally expected to.

The toolchain, the boring but necessary part

Building bootloader code for an ARM64 chip from an ordinary x86_64 Linux machine needs a cross-compiler — basically a version of the normal build tools that knows how to produce code for a different kind of processor than the one actually running the build. That part was simple, one package install away.

Why "just clone the bootloader repo" isn't enough

Here's the part I didn't fully appreciate until I was in the middle of it. The final bootloader image isn't just one project's source code — it's an assembly of several separate pieces:

  • The bootloader source itself.
  • A separate, lower-level firmware component called ARM Trusted Firmware, which runs even before the bootloader properly starts.
  • A vendor tool that stitches everything together into one final bootable image.
  • A set of binary firmware blobs used to train the board's RAM at boot — this board reads DRAM-training data straight from an on-board EEPROM chip, which is specific to this hardware vendor's design, not something you'd find on a generic reference board.

Getting any one of these at the wrong version doesn't necessarily fail loudly. In the worst case, it could silently produce a bootloader that looks fine, builds without error, but doesn't train the memory correctly on this specific board — and that's a genuinely nasty thing to debug later, so I wanted to get it right before even attempting a first build, not after.

How I actually pinned the right versions

The board's own boot log, from a plain serial console session, includes exact version strings and even a build timestamp down to the second for the low-level firmware component. That timestamp turned out to be the key: rather than trying to match "roughly the right time period," I went looking for the exact upstream commit whose own recorded commit time matched that boot-log timestamp to the second. Out of that project's entire history, exactly one commit matched — which is about as strong a confirmation as you can realistically get without literally having the vendor's own build logs in front of you.

For the vendor's image-assembly tool, there wasn't a timestamp to match against directly, but there was a different, still solid clue: this vendor tends to tag matching versions of their different repositories with the exact same version suffix, consistently, across many releases. Once I found that same suffix existed on the assembly tool's repository too, that was a strong (if slightly less ironclad than the timestamp match) signal that it was the right pairing.

And for the RAM-training firmware blobs, the board vendor's own official build configuration names the exact package version needed, and — nice cross-check — a completely unrelated upstream project's own documentation independently names the very same package version and lists the very same file names inside it. Two unrelated sources landing on the same answer, from two different angles, is exactly the kind of confirmation that lets me stop second-guessing a version number and just move on.

The one dead end that turned out to be genuinely useful

While looking into all this, I found a second copy of this vendor's build system floating around online, which looked at first glance like it might be the authoritative source for exactly these version pins. Digging into it a little further showed it was actually pinning a different set of versions than what the board's own boot log demonstrably came from — a separate, newer track, not this specific unit's actual factory build. I'm glad I checked rather than just trusting the first plausible-looking source I found, since using its version pins instead would have quietly meant building against the wrong baseline entirely, while looking completely reasonable the whole time.

Where things stand

With all four pieces identified down to an exact version and actually downloaded onto my build machine, I'm at the point where the next real step is attempting an actual build — unsigned, on purpose, so any problems there are clearly build problems and not signing problems. I haven't done that yet as I'm writing this, so I'll cover how it actually goes, mistakes included, in the next post — which will also be where signing and, eventually, the first genuinely irreversible step of this whole project finally comes into the story.