<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Production Readiness on Hoa Nguyen — Embedded Software Architect</title><link>https://hoanguyen.de/categories/production-readiness/</link><description>Recent content in Production Readiness on Hoa Nguyen — Embedded Software Architect</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 10 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://hoanguyen.de/categories/production-readiness/index.xml" rel="self" type="application/rss+xml"/><item><title>From Board Bring-Up to Production: 5 Lessons from an i.MX RT Control Unit</title><link>https://hoanguyen.de/posts/bringup-to-production-imx-rt-lessons/</link><pubDate>Tue, 10 Nov 2026 00:00:00 +0000</pubDate><guid>https://hoanguyen.de/posts/bringup-to-production-imx-rt-lessons/</guid><description>Five lessons from taking an i.MX RT control unit from first boot toward production: the bootloader that hides your SDRAM setup, the flash size nobody measured, writing the flash you execute from, firmware updates as a protocol, and surviving the power cut.</description><content:encoded><![CDATA[<p>Getting a board to blink is the easy 10 %. The hard part is everything between &ldquo;it boots on
my desk&rdquo; and &ldquo;it can leave the building&rdquo;: a bootloader in front of the application, a flash
layout that still fits next year, field updates that never leave a device half-updated, and
a power cut at the worst possible moment.</p>
<p>None of the five problems below is exotic. Each one costs days when it goes unnoticed,
because a hidden assumption looks true on the bench until the moment it isn&rsquo;t.</p>
<p>In this article I walk through five lessons from taking an i.MX RT (Cortex-M7) control unit
along that path, in the order a product meets them: the mechanism behind each one, and the
rule that keeps it from coming back.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Five lessons from bring-up to production, each with the hidden assumption and the rule that replaces it: SDRAM is set up by the boot ROM&#39;s DCD, so the bootloader must take over; the flash size is a measurement, so one header owns the map and CI checks it; writing XIP flash makes the flash vanish, so the write path runs from RAM; an update is a protocol, so only the new version number counts as success; hold-up time is a measurement, so the power-fail path never starts a flash operation"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 610">
<defs><marker id="m_cur" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker></defs>
<text x="66" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor" fill-opacity="0.7">Phase</text>
<text x="232" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#f59e0b">The hidden assumption</text>
<text x="492" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#10b981">The rule instead</text>
<rect x="10" y="42" width="740" height="104" rx="10" fill="#94a3b8" fill-opacity="0.06"/>
<circle cx="36" cy="94.0" r="15" fill="#3b82f6"/>
<text x="36" y="99.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">1</text>
<text x="62" y="90.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="start" fill="currentColor">Bring-up</text>
<text x="62" y="110.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">behind a bootloader</text>
<rect x="222" y="54" width="236" height="80" rx="8" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-width="1.5"/>
<text x="234" y="89.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">The application sets up</text>
<text x="234" y="108.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">its own SDRAM</text>
<line x1="462" y1="94.0" x2="478" y2="94.0" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.6" marker-end="url(#m_cur)"/>
<rect x="482" y="54" width="262" height="80" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="494" y="80.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">The boot ROM did it via the DCD.</text>
<text x="494" y="99.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">The bootloader must take over</text>
<text x="494" y="118.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">SDRAM and clocks</text>
<rect x="10" y="154" width="740" height="104" rx="10" fill="#94a3b8" fill-opacity="0.06"/>
<circle cx="36" cy="206.0" r="15" fill="#8b5cf6"/>
<text x="36" y="211.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">2</text>
<text x="62" y="202.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="start" fill="currentColor">Flash layout</text>
<text x="62" y="222.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">partition map</text>
<rect x="222" y="166" width="236" height="80" rx="8" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-width="1.5"/>
<text x="234" y="192.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">The size in the linker</text>
<text x="234" y="211.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">script and the ticket</text>
<text x="234" y="230.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">is right</text>
<line x1="462" y1="206.0" x2="478" y2="206.0" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.6" marker-end="url(#m_cur)"/>
<rect x="482" y="166" width="262" height="80" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="494" y="192.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Probe the part. One header owns</text>
<text x="494" y="211.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">the map; an assert and a CI</text>
<text x="494" y="230.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">test keep it true</text>
<rect x="10" y="266" width="740" height="104" rx="10" fill="#94a3b8" fill-opacity="0.06"/>
<circle cx="36" cy="318.0" r="15" fill="#14b8a6"/>
<text x="36" y="323.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">3</text>
<text x="62" y="314.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="start" fill="currentColor">Field update</text>
<text x="62" y="334.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">writing the XIP flash</text>
<rect x="222" y="278" width="236" height="80" rx="8" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-width="1.5"/>
<text x="234" y="313.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Writing the flash we run</text>
<text x="234" y="332.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">from is just slow</text>
<line x1="462" y1="318.0" x2="478" y2="318.0" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.6" marker-end="url(#m_cur)"/>
<rect x="482" y="278" width="262" height="80" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="494" y="304.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">While busy, the flash is gone:</text>
<text x="494" y="323.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">write path in RAM, IRQs masked,</text>
<text x="494" y="342.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">I-cache invalidated after</text>
<rect x="10" y="378" width="740" height="104" rx="10" fill="#94a3b8" fill-opacity="0.06"/>
<circle cx="36" cy="430.0" r="15" fill="#f59e0b"/>
<text x="36" y="435.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">4</text>
<text x="62" y="426.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="start" fill="currentColor">Update protocol</text>
<text x="62" y="446.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">proof of success</text>
<rect x="222" y="390" width="236" height="80" rx="8" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-width="1.5"/>
<text x="234" y="425.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">"Update done" means</text>
<text x="234" y="444.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">the update worked</text>
<line x1="462" y1="430.0" x2="478" y2="430.0" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.6" marker-end="url(#m_cur)"/>
<rect x="482" y="390" width="262" height="80" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="494" y="416.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">One outcome per request; only</text>
<text x="494" y="435.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">the new version number counts</text>
<text x="494" y="454.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">as success</text>
<rect x="10" y="490" width="740" height="104" rx="10" fill="#94a3b8" fill-opacity="0.06"/>
<circle cx="36" cy="542.0" r="15" fill="#ef4444"/>
<text x="36" y="547.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">5</text>
<text x="62" y="538.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="start" fill="currentColor">Power cut</text>
<text x="62" y="558.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">hold-up time</text>
<rect x="222" y="502" width="236" height="80" rx="8" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-width="1.5"/>
<text x="234" y="537.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">The capacitors keep the</text>
<text x="234" y="556.5" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">board alive long enough</text>
<line x1="462" y1="542.0" x2="478" y2="542.0" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.6" marker-end="url(#m_cur)"/>
<rect x="482" y="502" width="262" height="80" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="494" y="528.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Measure hold-up first. If an erase</text>
<text x="494" y="547.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">can outlast it, the power-fail</text>
<text x="494" y="566.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">path never starts one</text>
</svg>
</div>
  <figcaption>Five hidden assumptions, and the rule that replaces each one.</figcaption>
</figure>

<h2 id="1-the-board-boots-on-its-own-then-dies-behind-the-bootloader">1. The board boots on its own, then dies behind the bootloader</h2>
<p><strong>The mental model: the boot ROM was quietly doing your setup for you.</strong></p>
<p>A stand-alone application image on the i.MX RT works because the boot ROM reads the image&rsquo;s
boot header and runs its DCD (Device Configuration Data). That is where external SDRAM gets
configured. You never have to think about it.</p>
<p>Then you put a bootloader such as MCUboot in front of the application. A signed application
image behind a bootloader has no boot header of its own, so nobody runs its DCD. The first
stack push or the first <code>.bss</code> clear in SDRAM ends in a bus fault, long before <code>main()</code>.</p>
<p>The fix has two parts:</p>
<ol>
<li>Move the SDRAM setup (the DCD) into the <strong>bootloader</strong>, which the ROM still boots
normally.</li>
<li>Make the application <strong>skip the clock (PLL) re-initialisation</strong>, because the
bootloader has already done it.</li>
</ol>
<p>A related trap: in MCUboot&rsquo;s direct-XIP-with-revert mode, a slot without the image
trailer&rsquo;s magic value is erased at reset. The symptom is <em>the application disappears after
a power cycle</em>. Sign the image with the trailer included.</p>
<div class="callout callout--tip">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">💡</span>What to check on your own board</p>
  <div class="callout__body">
List everything the boot ROM does for a stand-alone image (DCD, clocks, memory) and ask who
does it once a bootloader sits in front. Anything nobody owns will fault before <code>main()</code>.
  </div>
</div>

<h2 id="2-measure-the-chip-not-the-documents">2. Measure the chip, not the documents</h2>
<p><strong>The mental model: the flash size is a measurement, not a setting.</strong></p>
<p>The flash size in a linker script, a ticket or a code comment is often copied from an
earlier board. Several documents agreeing with each other proves nothing if they all come
from the same assumption. Read the part number on the board, and probe the memory with a
debugger before you plan a partition map around it.</p>
<p>Then make the answer structural, so it cannot drift again:</p>
<ol>
<li><strong>One header</strong> owns the flash partition map.</li>
<li><strong>A start-up assert</strong> checks it against the linker symbols.</li>
<li><strong>A host test</strong> parses the linker scripts, so any drift fails in CI rather than on a
customer&rsquo;s device.</li>
</ol>
<p>The map matters most once you plan field updates. A dual-slot layout needs room for <strong>two
complete images</strong>, next to the bootloader and the data partition, and images grow over a
product&rsquo;s life.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Flash budget for dual-slot updates: bootloader, slot 1 with the running image, slot 2 for the incoming update, and a data partition must all fit in the part; each slot is sized for the worst-case build plus room to grow; above 16 MiB the flash needs 4-byte addressing; the layout is owned by one header and checked in CI"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 372">
<text x="20" y="72" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">At design time</text>
<text x="20" y="90" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">everything fits</text>
<rect x="170" y="52" width="50" height="44" rx="4" fill="#94a3b8" fill-opacity="0.3" stroke="#94a3b8" stroke-width="1.5"/>
<text x="195.0" y="79" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">boot</text>
<rect x="220" y="52" width="170" height="44" rx="4" fill="#3b82f6" fill-opacity="0.2" stroke="#3b82f6" stroke-width="1.5"/>
<text x="305.0" y="72" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">slot 1</text>
<text x="305.0" y="88" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.8">running image</text>
<rect x="390" y="52" width="170" height="44" rx="4" fill="#3b82f6" fill-opacity="0.2" stroke="#3b82f6" stroke-width="1.5"/>
<text x="475.0" y="72" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">slot 2</text>
<text x="475.0" y="88" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.8">incoming update</text>
<rect x="560" y="52" width="90" height="44" rx="4" fill="#14b8a6" fill-opacity="0.2" stroke="#14b8a6" stroke-width="1.5"/>
<text x="605.0" y="72" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">data</text>
<text x="605.0" y="88" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.8">file system</text>
<text x="20" y="162" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Images grow</text>
<text x="20" y="180" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">both slots grow</text>
<rect x="170" y="142" width="50" height="44" rx="4" fill="#94a3b8" fill-opacity="0.3" stroke="#94a3b8" stroke-width="1.5"/>
<text x="195.0" y="169" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">boot</text>
<rect x="220" y="142" width="215" height="44" rx="4" fill="#3b82f6" fill-opacity="0.2" stroke="#3b82f6" stroke-width="1.5"/>
<text x="327.5" y="162" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">slot 1</text>
<text x="327.5" y="178" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.8">bigger image</text>
<rect x="435" y="142" width="215" height="44" rx="4" fill="#3b82f6" fill-opacity="0.2" stroke="#3b82f6" stroke-width="1.5"/>
<text x="542.5" y="162" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">slot 2</text>
<text x="542.5" y="178" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.8">same size again</text>
<rect x="650" y="142" width="90" height="44" rx="4" fill="#ef4444" fill-opacity="0.22" stroke="#ef4444" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="695.0" y="162" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">data</text>
<text x="695.0" y="178" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.8">no room</text>
<line x1="650" y1="38" x2="650" y2="200" stroke="currentColor" stroke-width="1.6" stroke-dasharray="5 4" stroke-opacity="0.7"/>
<text x="650" y="30" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.8">end of the part</text>
<text x="170" y="30" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">start of flash</text>
<rect x="20" y="222" width="720" height="136" rx="8" fill="#8b5cf6" fill-opacity="0.08" stroke="#8b5cf6" stroke-width="1"/>
<text x="36" y="248" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Rules for the partition map</text>
<text x="36" y="274" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Size each slot for the worst-case build (debug, with tracing), plus room to grow.</text>
<text x="36" y="295" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Two slots mean two complete images: an image that grows costs twice its growth.</text>
<text x="36" y="316" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Above 16 MiB the flash needs 4-byte addressing: boot config, driver commands, size limit.</text>
<text x="36" y="337" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">One header owns the map; a start-up assert and a CI test check it against the linker.</text>
</svg>
</div>
  <figcaption>What a dual-slot layout has to fit, and the rules for sizing it.</figcaption>
</figure>

<table>
  <thead>
      <tr>
          <th>Budget question</th>
          <th>What to do</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Which build do I size against?</td>
          <td>The worst case: the debug build with tracing, not the release build.</td>
      </tr>
      <tr>
          <td>Does reflashing prove the layout?</td>
          <td>No. Debug probes often skip unchanged sectors, so a reflash test can prove nothing.</td>
      </tr>
      <tr>
          <td>What changes above 16 MiB?</td>
          <td>4-byte addressing. On the i.MX RT it has to be set consistently in the FlexSPI configuration block, the flash driver&rsquo;s commands (also used for XIP reads) and the driver&rsquo;s size limit.</td>
      </tr>
  </tbody>
</table>
<h2 id="3-writing-the-flash-you-execute-from">3. Writing the flash you execute from</h2>
<p><strong>The mental model: while the flash is busy, it does not exist.</strong></p>
<p>On the i.MX RT the code usually runs straight out of QSPI NOR (execute in place, XIP). A
firmware update writes to the same chip. From the moment you send a program or erase
command until the flash clears its busy bit (WIP), <strong>nothing</strong> may be fetched from it: not
the interrupt handler, not the vector table, not a helper function the compiler decided
not to inline.</p>
<p>The failure is intermittent, which makes it nasty. The update crashes at a different block
on every run, and every bench session looks like a different bug.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Timeline of one flash write while executing in place: running from flash, then a busy window where nothing may be fetched from flash and the ISR, vectors and helpers run from RAM with interrupts masked, then invalidate the instruction cache, then resume; a page program is short, a sector erase takes tens of milliseconds and many times longer in the worst case"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 340">
<defs><marker id="m_cur" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker></defs>
<rect x="20" y="70" width="170" height="92" rx="8" fill="#10b981" fill-opacity="0.15" stroke="#10b981" stroke-width="1.5"/>
<text x="34" y="108" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Running from</text>
<text x="34" y="127" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">flash (XIP)</text>
<rect x="196" y="70" width="330" height="92" rx="8" fill="#ef4444" fill-opacity="0.15" stroke="#ef4444" stroke-width="2"/>
<text x="210" y="96" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="#ef4444">Flash busy (WIP = 1): it does not exist</text>
<text x="210" y="120" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">No fetch from flash at all: ISR, vector</text>
<text x="210" y="138" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">table and helpers must run from RAM;</text>
<text x="210" y="156" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">interrupts masked</text>
<rect x="532" y="70" width="102" height="92" rx="8" fill="#f59e0b" fill-opacity="0.15" stroke="#f59e0b" stroke-width="1.5"/>
<text x="544" y="108" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Invalidate</text>
<text x="544" y="127" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">I-cache</text>
<rect x="640" y="70" width="100" height="92" rx="8" fill="#10b981" fill-opacity="0.15" stroke="#10b981" stroke-width="1.5"/>
<text x="652" y="108" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Resume</text>
<text x="652" y="127" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">XIP</text>
<line x1="196" y1="40" x2="196" y2="68" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.7" marker-end="url(#m_cur)"/>
<text x="196" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">program / erase command</text>
<line x1="526" y1="40" x2="526" y2="68" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.7" marker-end="url(#m_cur)"/>
<text x="560" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">busy bit clears</text>
<text x="210" y="192" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">How long is the window? Check your datasheet:</text>
<text x="210" y="212" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">page program</text>
<text x="310" y="212" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">short</text>
<text x="210" y="230" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">sector erase</text>
<text x="310" y="230" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">tens of ms, typical</text>
<text x="210" y="248" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">worst case</text>
<text x="310" y="248" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">many times the typical value</text>
<text x="540" y="192" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">Cortex-M7 speculative</text>
<text x="540" y="209" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">fetches may have pulled</text>
<text x="540" y="226" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">stale lines in the cache</text>
<rect x="20" y="286" width="720" height="38" rx="8" fill="#ef4444" fill-opacity="0.12" stroke="#ef4444" stroke-width="1.5"/>
<text x="36" y="310" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Without this: a crash at a random block, different on every run</text>
</svg>
</div>
  <figcaption>One write to the flash you execute from. While the busy bit is set, the flash does not exist for the CPU.</figcaption>
</figure>

<p>What fixes it:</p>
<ol>
<li>Run the erase and program routines, and everything they call, from RAM.</li>
<li>Keep interrupts masked while the flash is busy. Check the datasheet for how long that
is: a page program is short, but a sector erase takes tens of milliseconds, and the
worst case is many times the typical value.</li>
<li><strong>Invalidate the instruction cache</strong> afterwards. The Cortex-M7 fetches speculatively and
can have pulled in stale or garbage lines during the write.</li>
</ol>
<div class="callout callout--warn">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">⚠️</span>An unknown opcode fails silently</p>
  <div class="callout__body">
A NOR flash ignores a command it does not support. Send the wrong erase opcode and the erase
&ldquo;succeeds&rdquo; instantly. If an erase finishes far faster than the datasheet&rsquo;s typical time,
suspect that the command never ran.
  </div>
</div>

<h2 id="4-treat-the-field-update-as-a-protocol">4. Treat the field update as a protocol</h2>
<p><strong>The mental model: every update request ends in exactly one outcome, and the new version
number is the only proof.</strong></p>
<p>A device that reports &ldquo;update done&rdquo; has proven nothing. It might reboot into the old image,
or not reboot at all. Update logic only gets robust once you treat it as a protocol with
explicit rules:</p>
<ul>
<li>Every request ends in <strong>exactly one</strong> outcome: success, a classified failure, or a
timeout. Never silence, never two answers.</li>
<li>After the restart, poll the device for its version with a fixed interval and a hard
limit. Only the new version string counts as success.</li>
<li>Retries must fit the device&rsquo;s own limits. If the target closes an idle session after a
timeout, all your retries together must finish inside it, or the retries themselves
break the session.</li>
</ul>
<p>The bench catches what unit tests cannot: a bootloader step on the target that takes far
longer than you assumed, or a frame buffer that does not fit on the task stack you gave it.
Measure step times on hardware and check stack high-water marks before you trust the
timeouts.</p>
<h2 id="5-production-ready-means-surviving-the-power-cut">5. Production-ready means surviving the power cut</h2>
<p><strong>The mental model: measure the hold-up time before you design the software around it.</strong></p>
<p>When the supply drops, the board runs a little longer on its capacitors. That hold-up time
is a hardware property: measure it on a real board, in the worst-case load state, before
you design the power-fail path. One wrong part in the hold-up circuit can bring it close
to zero.</p>
<p>That number decides the software design. If the worst-case flash erase (lesson 3) is
longer than the hold-up time, <strong>the emergency path must never start a flash operation</strong>. It
only finishes or abandons what is already running, and closes the file system.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Power-fail timeline: the supply drops, the power-fail interrupt fires and the measured hold-up window starts; closing the file system and a typical erase fit in the window, a worst-case erase can outlast it; so the emergency path never starts a flash operation"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 372">
<line x1="230" y1="40" x2="230" y2="254" stroke="#f59e0b" stroke-width="2"/>
<text x="230" y="22" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="bold" text-anchor="middle" fill="currentColor">power-fail interrupt</text>
<line x1="590" y1="40" x2="590" y2="254" stroke="#ef4444" stroke-width="2" stroke-dasharray="5 4"/>
<text x="590" y="22" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="bold" text-anchor="middle" fill="currentColor">brown-out: CPU stops</text>
<rect x="230" y="48" width="360" height="34" rx="8" fill="#10b981" fill-opacity="0.18" stroke="#10b981" stroke-width="1.5"/>
<text x="410.0" y="70" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">hold-up time: measure it on the real board</text>
<text x="20" y="70" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Capacitors</text>
<text x="20" y="124" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Close the file system</text>
<rect x="230" y="108" width="30" height="24" rx="3" fill="#3b82f6" fill-opacity="0.35" stroke="#3b82f6" stroke-width="1.5"/>
<text x="270" y="125" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="bold" text-anchor="start" fill="#10b981">fits</text>
<text x="20" y="172" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">Typical sector erase</text>
<rect x="230" y="156" width="120" height="24" rx="3" fill="#f59e0b" fill-opacity="0.35" stroke="#f59e0b" stroke-width="1.5"/>
<text x="360" y="173" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="bold" text-anchor="start" fill="#10b981">fits</text>
<text x="20" y="220" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Worst-case sector erase</text>
<rect x="230" y="204" width="470" height="24" rx="3" fill="#ef4444" fill-opacity="0.35" stroke="#ef4444" stroke-width="1.5"/>
<text x="690" y="221" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="bold" text-anchor="end" fill="currentColor">outlasts the hold-up</text>
<text x="745" y="254" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="11.5" font-weight="normal" text-anchor="end" fill="currentColor" fill-opacity="0.6" font-style="italic">not to scale</text>
<rect x="20" y="270" width="720" height="88" rx="8" fill="#ef4444" fill-opacity="0.08" stroke="#ef4444" stroke-width="1"/>
<text x="36" y="294" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">If a worst-case erase can outlast the hold-up time:</text>
<text x="36" y="318" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">the power-fail path never starts a flash operation. It only finishes or abandons</text>
<text x="36" y="338" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">what is already running, and closes the file system.</text>
</svg>
</div>
  <figcaption>The hold-up window against flash timing. A worst-case erase may not fit, so the power-fail path never starts one.</figcaption>
</figure>

<p>Then test it properly. Cut power repeatedly while the firmware writes continuously, and
check after each cut that the file system is consistent and the last committed version of
each file survived.</p>
<div class="callout callout--check">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">✅</span>Test the test</p>
  <div class="callout__body">
A power-cut test that never cuts during a write proves nothing. Count how many cuts actually
landed mid-write before you trust the pass rate.
  </div>
</div>

<h2 id="summary">Summary</h2>
<ol>
<li><strong>Own what the ROM did for you.</strong> Once a bootloader sits in front, it has to set up
memory and clocks.</li>
<li><strong>Measure, then make it structural.</strong> Flash size, step timings and hold-up time are
measurements; a header, an assert and a CI test keep them true.</li>
<li><strong>Design for the worst case.</strong> The flash write path runs from RAM, updates end in exactly
one outcome, and the power-fail path never starts an erase.</li>
</ol>
<div class="callout callout--note">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">📝</span>Sources</p>
  <div class="callout__body">
<ul>
<li>NXP, <em>i.MX RT1060 Processor Reference Manual</em>: boot ROM, IVT/DCD, FlexSPI.</li>
<li>MCUboot documentation: image trailer and the direct-XIP / revert modes.</li>
<li>The datasheet of your QSPI NOR, for its typical and maximum program and erase times.</li>
</ul>

  </div>
</div>

<p>💬 Questions, corrections, or your own war story? Leave a comment below.</p>
]]></content:encoded></item><item><title>HABv4 Secure Boot on i.MX RT: A Production Checklist (Fuses, Keys, Encrypted XIP)</title><link>https://hoanguyen.de/posts/habv4-secure-boot-production-checklist-imx-rt/</link><pubDate>Tue, 10 Nov 2026 00:00:00 +0000</pubDate><guid>https://hoanguyen.de/posts/habv4-secure-boot-production-checklist-imx-rt/</guid><description>Secure boot on the i.MX RT10xx is a sequence of one-way steps. A production checklist in the order that avoids bricking boards: trust boundary, keys, signing, testing on an open part, fusing the SRK hash, encrypted XIP, closing the part, and locking debug.</description><content:encoded><![CDATA[<p>Secure boot means the chip only runs firmware you signed. On the NXP i.MX RT10xx this is
HAB (High Assurance Boot, version 4): the boot ROM checks a signature on your image against
a hash of your public keys that is burned into the chip&rsquo;s fuses.</p>
<p>The concept is simple. Production is not, because almost every step is <strong>one-way</strong>. A fuse
bit can go from 0 to 1, never back. Burn the wrong value, or burn the right values in the
wrong order, and a board on the line becomes scrap. For a product team, the order of the
steps matters more than any single step.</p>
<p>In this article I give the checklist I use, in the order that keeps every step recoverable
for as long as possible, with the reason behind each one.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Comparison of the three HAB states OPEN, OPEN plus SRK fused, and CLOSED: how you get there (factory, burn SRK_HASH one-way, burn SEC_CONFIG one-way), what happens to an image with a bad signature (logged and boots, logged and boots, does not run and falls back to the serial downloader), whether keys are tied to the chip, and what each state is used for"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 420">
<defs><marker id="m_cur" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker></defs>
<rect x="170" y="14" width="190" height="44" rx="8" fill="#10b981" fill-opacity="0.25" stroke="#10b981" stroke-width="2"/>
<text x="265.0" y="42" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="middle" fill="currentColor">OPEN</text>
<rect x="375" y="14" width="190" height="44" rx="8" fill="#f59e0b" fill-opacity="0.25" stroke="#f59e0b" stroke-width="2"/>
<text x="470.0" y="42" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="middle" fill="currentColor">OPEN + SRK fused</text>
<rect x="580" y="14" width="190" height="44" rx="8" fill="#ef4444" fill-opacity="0.25" stroke="#ef4444" stroke-width="2"/>
<text x="675.0" y="42" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="middle" fill="currentColor">CLOSED</text>
<line x1="362" y1="36" x2="373" y2="36" stroke="currentColor" stroke-width="1.6" marker-end="url(#m_cur)"/>
<line x1="567" y1="36" x2="578" y2="36" stroke="currentColor" stroke-width="1.6" marker-end="url(#m_cur)"/>
<rect x="10" y="72" width="760" height="78" rx="8" fill="#94a3b8" fill-opacity="0.06"/>
<text x="20" y="116" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">How you get here</text>
<text x="180" y="116.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">factory state</text>
<text x="385" y="107.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">burn SRK_HASH</text>
<text x="385" y="125.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#ef4444">one-way</text>
<text x="590" y="107.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">burn SEC_CONFIG[1]</text>
<text x="590" y="125.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#ef4444">one-way</text>
<rect x="10" y="156" width="760" height="78" rx="8" fill="#94a3b8" fill-opacity="0.06"/>
<text x="20" y="190" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Image with a</text>
<text x="20" y="208" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">bad signature</text>
<text x="180" y="191.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">logged in the HAB</text>
<text x="180" y="209.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">event log, still boots</text>
<text x="385" y="200.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">logged, still boots</text>
<text x="590" y="182.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">does not run:</text>
<text x="590" y="200.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">ROM falls back to</text>
<text x="590" y="218.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">the serial downloader</text>
<rect x="10" y="240" width="760" height="78" rx="8" fill="#94a3b8" fill-opacity="0.06"/>
<text x="20" y="284" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Your keys</text>
<text x="180" y="284.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">not tied to the chip</text>
<text x="385" y="284.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">tied to your SRK hash</text>
<text x="590" y="284.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">tied to your SRK hash</text>
<rect x="10" y="324" width="760" height="78" rx="8" fill="#94a3b8" fill-opacity="0.06"/>
<text x="20" y="368" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Use it for</text>
<text x="180" y="359.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">development; testing the</text>
<text x="180" y="377.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">final signed image</text>
<text x="385" y="359.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">the first production</text>
<text x="385" y="377.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">batch: still recoverable</text>
<text x="590" y="359.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">volume units, after the</text>
<text x="590" y="377.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">full end-of-line test</text>
</svg>
</div>
  <figcaption>HAB is not a switch. It is a ratchet with three positions, and it only turns one way.</figcaption>
</figure>

<h2 id="the-checklist-at-a-glance">The checklist at a glance</h2>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Production sequence in two lanes: the contract manufacturer holds no keys, assembles and tests with an unsigned image and ships boards open; in-house, steps 1 to 3 (key tree, sign, boot on an open part) are reversible, steps 4 to 7 (burn SRK_HASH, encrypted XIP, close with SEC_CONFIG, lock debug then lock fuses) are one-way"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 540">
<defs><marker id="m_cur" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker></defs>
<rect x="10" y="40" width="230" height="490" rx="8" fill="#94a3b8" fill-opacity="0.07" stroke="#94a3b8" stroke-width="1" stroke-dasharray="6 4"/>
<text x="24" y="66" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Contract manufacturer</text>
<text x="24" y="84" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">holds no keys</text>
<rect x="256" y="40" width="514" height="490" rx="8" fill="#3b82f6" fill-opacity="0.05" stroke="#3b82f6" stroke-width="1"/>
<text x="270" y="66" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">In-house: keys live here</text>
<text x="24" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">0 · Trust boundary first: decide who may ever hold a secret</text>
<rect x="26" y="104" width="198" height="92" rx="8" fill="#94a3b8" fill-opacity="0.18" stroke="#94a3b8" stroke-width="1.5"/>
<text x="38" y="128" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Assembly + board test</text>
<text x="38" y="146" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">with an unsigned</text>
<text x="38" y="164" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">test image</text>
<rect x="26" y="212" width="198" height="74" rx="8" fill="#94a3b8" fill-opacity="0.18" stroke="#94a3b8" stroke-width="1.5"/>
<text x="38" y="236" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Boards leave open,</text>
<text x="38" y="254" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">with no secrets</text>
<text x="38" y="272" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">on them</text>
<line x1="226" y1="250" x2="268" y2="250" stroke="currentColor" stroke-width="1.6" marker-end="url(#m_cur)"/>
<rect x="276" y="98" width="476" height="182" rx="8" fill="#10b981" fill-opacity="0.08" stroke="#10b981" stroke-width="1"/>
<rect x="276" y="286" width="476" height="238" rx="8" fill="#ef4444" fill-opacity="0.08" stroke="#ef4444" stroke-width="1"/>
<rect x="290" y="104" width="330" height="50" rx="8" fill="#10b981" fill-opacity="0.14" stroke="#10b981" stroke-width="1.5"/>
<circle cx="310" cy="129.0" r="13" fill="#10b981"/>
<text x="310" y="134.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">1</text>
<text x="332" y="125" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Key tree SRK → CSF → IMG</text>
<text x="332" y="143" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">private keys in an HSM</text>
<rect x="290" y="164" width="330" height="50" rx="8" fill="#10b981" fill-opacity="0.14" stroke="#10b981" stroke-width="1.5"/>
<circle cx="310" cy="189.0" r="13" fill="#10b981"/>
<text x="310" y="194.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">2</text>
<text x="332" y="185" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Sign the image</text>
<text x="332" y="203" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">nxpimage hab export, family mimxrt1060</text>
<rect x="290" y="224" width="330" height="50" rx="8" fill="#10b981" fill-opacity="0.14" stroke="#10b981" stroke-width="1.5"/>
<circle cx="310" cy="249.0" r="13" fill="#10b981"/>
<text x="310" y="254.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">3</text>
<text x="332" y="245" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Boot it on an OPEN part</text>
<text x="332" y="263" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">HAB event log must be clean</text>
<rect x="290" y="284" width="330" height="50" rx="8" fill="#ef4444" fill-opacity="0.14" stroke="#ef4444" stroke-width="1.5"/>
<circle cx="310" cy="309.0" r="13" fill="#ef4444"/>
<text x="310" y="314.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">4</text>
<text x="332" y="305" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Burn SRK_HASH</text>
<text x="332" y="323" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">reboot, check the log again</text>
<rect x="290" y="344" width="330" height="50" rx="8" fill="#ef4444" fill-opacity="0.14" stroke="#ef4444" stroke-width="1.5"/>
<circle cx="310" cy="369.0" r="13" fill="#ef4444"/>
<text x="310" y="374.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">5</text>
<text x="332" y="365" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Encrypted XIP (BEE)</text>
<text x="332" y="383" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">BOOT_CFG1[1] + key-select fuses</text>
<rect x="290" y="404" width="330" height="50" rx="8" fill="#ef4444" fill-opacity="0.14" stroke="#ef4444" stroke-width="1.5"/>
<circle cx="310" cy="429.0" r="13" fill="#ef4444"/>
<text x="310" y="434.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">6</text>
<text x="332" y="425" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Close: SEC_CONFIG[1]</text>
<text x="332" y="443" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">first batch stays open</text>
<rect x="290" y="464" width="330" height="50" rx="8" fill="#ef4444" fill-opacity="0.14" stroke="#ef4444" stroke-width="1.5"/>
<circle cx="310" cy="489.0" r="13" fill="#ef4444"/>
<text x="310" y="494.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">7</text>
<text x="332" y="485" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Lock debug, then lock fuses</text>
<text x="332" y="503" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">JTAG_SMODE + KTE; lock fuses last</text>
<text x="634" y="164" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#10b981">Reversible</text>
<text x="634" y="182" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">repeat as often</text>
<text x="634" y="200" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">as you like</text>
<text x="634" y="364" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#ef4444">One-way</text>
<text x="634" y="382" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">eFuse bits only</text>
<text x="634" y="400" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">go 0 → 1</text>
</svg>
</div>
  <figcaption>The whole flow on one page. Green steps can be repeated; red steps burn fuses.</figcaption>
</figure>

<p>The first four steps can be repeated as often as you like. That is where all the testing
belongs.</p>
<h2 id="0-draw-the-trust-boundary-first">0. Draw the trust boundary first</h2>
<p>Decide who is allowed to hold secrets before you generate a single key. My split: the
contract manufacturer (EMS) assembles and tests boards with an <strong>unsigned test image</strong> and
<strong>no keys</strong>. Boards leave the EMS open, with no secrets on them. Signing, fusing and device
identity happen in-house.</p>
<p>That one decision keeps the private keys inside one building and makes every later step
auditable.</p>
<h2 id="1-keys-only-a-hash-goes-into-the-chip">1. Keys: only a hash goes into the chip</h2>
<p>HAB uses a small tree of keys. The SRK (Super Root Key) sits at the top. It certifies a
CSF key, which signs the command sequence, and an IMG key, which signs the image. The chip
stores only a <strong>hash</strong> of the SRK public keys, in the <code>SRK_HASH</code> fuses.</p>
<p>With NXP&rsquo;s SPSDK the key tree is one command. Without <code>-ca</code> you only get SRKs, which is not
enough to sign:</p>
<div class="codeblock">
  <div class="codeblock-bar">
    <span class="codeblock-label"><span class="codeblock-lang">bash</span></span><button class="codeblock-copy" type="button" hidden aria-label="Copy code to clipboard">copy</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nxpcrypto pki-tree hab -k rsa2048 -o keys -ca</span></span></code></pre></div>
</div>
<p>Two rules matter more than the tool:</p>
<ol>
<li><strong>The private keys never live on a laptop that leaves the building.</strong> An HSM is the right
home.</li>
<li><strong>Choose the key size for the product&rsquo;s lifetime, not the tutorial.</strong> HABv4 supports RSA
from 1024 to 4096 bits. Most examples, including mine above, use RSA-2048. NXP&rsquo;s
AN13023 notes that BSI guidance does not accept RSA-2048 for new designs, while
RSA-3072 is fine. If your product goes through that kind of review, decide before you
fuse anything.</li>
</ol>
<h2 id="2-sign-the-image">2. Sign the image</h2>
<p>In SPSDK 2.0 (October 2023) the old tool names changed: <code>elftosb</code> became <code>nxpimage</code>, and
<code>nxpkeygen</code>/<code>nxpcertgen</code> became <code>nxpcrypto</code>. Old tutorials fail quietly because of it.</p>
<div class="codeblock">
  <div class="codeblock-bar">
    <span class="codeblock-label"><span class="codeblock-lang">bash</span></span><button class="codeblock-copy" type="button" hidden aria-label="Copy code to clipboard">copy</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nxpimage hab get-template -f mimxrt1060 -o hab_template.yaml
</span></span><span class="line"><span class="cl">nxpimage hab <span class="nb">export</span> -c hab_template.yaml -o signed_image.bin</span></span></code></pre></div>
</div>
<div class="callout callout--warn">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">⚠️</span>The family flag is mimxrt1060, not mimxrt1062</p>
  <div class="callout__body">
<code>-f mimxrt1062</code> is rejected. The family string <code>mimxrt1060</code> covers the RT1060, RT1062 and
RT1064.
  </div>
</div>

<p>HAB signs a digest of the image. It does <strong>not</strong> encrypt it. Encryption is a separate step
(step 5).</p>
<h2 id="3-boot-the-signed-image-on-an-open-part">3. Boot the signed image on an open part</h2>
<p>This is the step that prevents the most common production brick. Before any fuse is
burned, erase the flash, program the <strong>final signed image</strong>, and boot it on a part that is
still open.</p>
<p>In the open configuration HAB checks the signature but only <strong>logs</strong> a failure and boots
anyway. That makes it the perfect test bench: you see every problem in the HAB event log,
and the board still works. Do not move on until the log is clean.</p>
<h2 id="4-burn-the-srk-hash-and-check-again">4. Burn the SRK hash and check again</h2>
<p>Now burn <code>SRK_HASH</code>, reboot, and check the HAB log a second time, this time against the
fused hash. The part is still open, so a mistake here still boots. It just means this chip
can never be closed with your keys.</p>
<div class="callout callout--danger">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">🐛</span>One-way from here</p>
  <div class="callout__body">
eFuse bits only go from 0 to 1. Every step from here on cannot be undone. Script the fuse
values, review them, and burn them from a tool, never by hand.
  </div>
</div>

<h2 id="5-encrypted-xip-decide-early">5. Encrypted XIP: decide early</h2>
<p>Signing proves <em>who</em> wrote the firmware. Anyone with a flash programmer can still read it.
If the code itself is worth protecting, the i.MX RT106x can run encrypted code straight from
flash with its BEE (Bus Encryption Engine): AES-128, two regions, decrypted on the fly while
executing. Other RT10xx parts use OTFAD instead, and the RT1010 has neither.</p>
<p>The fuses involved are <code>BOOT_CFG1[1]</code>, which enables encrypted XIP, and the key-select fuses
<code>BEE_KEY0_SEL</code> / <code>BEE_KEY1_SEL</code>. These pick either a key derived from the chip-unique OTPMK
or a key you burn yourself into <code>SW_GP2</code>.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Vertical flash map of an encrypted XIP image on FlexSPI NOR: FCB at 0x0000, EKIB0 at 0x0400, EPRDB0 at 0x0480, EKIB1 at 0x0800, EPRDB1 at 0x0880, IVT plus boot data plus image from 0x1000; next to it the key choice per region: an OTPMK-derived key means no OTA for that region, a key in SW_GP2 allows OTA but is one secret for the fleet, and the hybrid of OTPMK for the bootloader and SW_GP2 for the application"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 520">
<text x="30" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">FlexSPI NOR at 0x6000_0000: offsets, read top to bottom (not to scale)</text>
<rect x="30" y="44" width="300" height="36" rx="4" fill="#94a3b8" fill-opacity="0.2" stroke="#94a3b8" stroke-width="1.5"/>
<text x="42" y="66.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">FCB: FlexSPI configuration block</text>
<text x="342" y="59" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">0x0000</text>
<rect x="30" y="84" width="300" height="36" rx="4" fill="#f59e0b" fill-opacity="0.2" stroke="#f59e0b" stroke-width="1.5"/>
<text x="42" y="106.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">EKIB0: encrypted key info, region 0</text>
<text x="342" y="99" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">0x0400</text>
<rect x="30" y="124" width="300" height="36" rx="4" fill="#f59e0b" fill-opacity="0.2" stroke="#f59e0b" stroke-width="1.5"/>
<text x="42" y="146.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">EPRDB0: region 0 descriptor</text>
<text x="342" y="139" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">0x0480</text>
<rect x="30" y="164" width="300" height="36" rx="4" fill="#f59e0b" fill-opacity="0.2" stroke="#f59e0b" stroke-width="1.5"/>
<text x="42" y="186.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">EKIB1: encrypted key info, region 1</text>
<text x="342" y="179" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">0x0800</text>
<rect x="30" y="204" width="300" height="36" rx="4" fill="#f59e0b" fill-opacity="0.2" stroke="#f59e0b" stroke-width="1.5"/>
<text x="42" y="226.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">EPRDB1: region 1 descriptor</text>
<text x="342" y="219" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">0x0880</text>
<rect x="30" y="244" width="300" height="146" rx="4" fill="#3b82f6" fill-opacity="0.2" stroke="#3b82f6" stroke-width="1.5"/>
<text x="42" y="270" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">IVT + boot data + image</text>
<text x="342" y="259" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">0x1000</text>
<text x="42" y="300" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.85">runs encrypted, decrypted</text>
<text x="42" y="317" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.85">on the fly by BEE</text>
<text x="42" y="334" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.85">(AES-128, two regions)</text>
<text x="440" y="58" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Each region's key: BEE_KEYn_SEL fuses</text>
<rect x="440" y="72" width="330" height="104" rx="8" fill="#8b5cf6" fill-opacity="0.12" stroke="#8b5cf6" stroke-width="1.5"/>
<text x="454" y="96" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">OTPMK-derived key</text>
<text x="454" y="115" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">unique per chip, never readable</text>
<text x="454" y="134" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">nobody can produce new ciphertext:</text>
<text x="454" y="153" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#ef4444">no OTA for that region</text>
<rect x="440" y="188" width="330" height="104" rx="8" fill="#14b8a6" fill-opacity="0.12" stroke="#14b8a6" stroke-width="1.5"/>
<text x="454" y="212" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">Your key in SW_GP2</text>
<text x="454" y="231" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">you can produce new ciphertext:</text>
<text x="454" y="250" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#10b981">OTA works</text>
<text x="454" y="269" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">but one secret for the whole fleet</text>
<rect x="440" y="304" width="330" height="64" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="2"/>
<text x="454" y="330" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">Hybrid: bootloader → OTPMK,</text>
<text x="454" y="350" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">application → SW_GP2</text>
<text x="440" y="400" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Enable encrypted XIP: BOOT_CFG1[1].</text>
<text x="440" y="420" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">BEE only decrypts: it cannot re-encrypt.</text>
<text x="440" y="440" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Encrypted XIP rules out flash remap, so</text>
<text x="440" y="460" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">MCUboot falls back to overwrite-only.</text>
</svg>
</div>
  <figcaption>Where BEE&rsquo;s key blocks live in flash, and what each key choice costs you.</figcaption>
</figure>

<div class="callout callout--warn">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">⚠️</span>BEE on the RT106x is decrypt-only</p>
  <div class="callout__body">
The engine only decrypts. With the chip-unique key, nobody outside the chip can produce
new ciphertext for that region, which makes it effectively write-once and kills
over-the-air updates. A key of your own in <code>SW_GP2</code> restores updates but costs per-device
secrecy. My answer is a hybrid: the chip-unique key for the bootloader, your own key for the
application.
  </div>
</div>

<p>One more trade-off: encrypted XIP does not work together with flash remapping. If you use
MCUboot, that rules out its swap-by-remap mode and leaves overwrite-only updates, without a
rollback slot.</p>
<h2 id="6-close-the-part">6. Close the part</h2>
<p>Burning <code>SEC_CONFIG[1]</code> closes the part. From then on, an image that fails authentication
does not run: the boot ROM drops into the serial downloader instead.</p>
<p>My policy: the <strong>first batch stays open with the SRK fused</strong>, so it can still be recovered.
Volume units are closed only after the full end-of-line test has passed on them.</p>
<p>Two details are easy to miss:</p>
<ul>
<li>Closing does <strong>not</strong> disable the USB serial downloader (SDP). A separate fuse,
<code>SDP_DISABLE</code>, does that. I leave SDP enabled on purpose, as a recovery path.</li>
<li>On a closed part the boot ROM enables the MPU. A debug or flash tool that worked before
may need to disable it first.</li>
</ul>
<h2 id="7-lock-the-debug-port-and-the-fuses-last">7. Lock the debug port and the fuses, last</h2>
<p>An open JTAG port bypasses everything above. The i.MX RT1060 Security Reference Manual
describes three modes in the <code>JTAG_SMODE</code> fuses: enabled, Secure JTAG (challenge-response),
or no debug. <code>SJC_DISABLE</code> gives the strongest protection. The manual also requires the
<code>KTE</code> fuse to be burned, or the JTAG mode does not take effect.</p>
<p>The very last step is the <strong>lock fuses</strong> (<code>BOOT_CFG_LOCK</code>, <code>SJC_RESP_LOCK</code>, the GP locks and
others), which stop anyone from burning more bits later. And never burn a reserved fuse.</p>
<div class="callout callout--note">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">📝</span>Where this step comes from</p>
  <div class="callout__body">
Steps 0–6 are from my own production flow. The debug-lock details in step 7 come from
NXP&rsquo;s Security Reference Manual and application notes, not from my bench. Check them
against the manual for your exact part before you burn anything.
  </div>
</div>

<h2 id="who-checks-what-at-runtime">Who checks what at runtime</h2>
<p>A common question once MCUboot is in the picture: does HAB verify every application update?
No. HAB verifies the <strong>bootloader</strong> at every reset. The bootloader then verifies the
<strong>application</strong> with its own signature (ECDSA P-256 in my setup). That means two sets of
signing keys, and both belong behind the same trust boundary from step 0.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Chain of trust from the SRK_HASH eFuses through the SRK certificate and the CSF and IMG certificates to the bootloader, then from the bootloader to the application; key set 1 is HAB with RSA, checked by the boot ROM at every reset; key set 2 is MCUboot with ECDSA P-256, checked by the bootloader including for every OTA update"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 330">
<defs><marker id="m_cur" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker></defs>
<rect x="10" y="10" width="760" height="34" rx="8" fill="#3b82f6" fill-opacity="0.08" stroke="#3b82f6" stroke-width="1" stroke-dasharray="6 4"/>
<text x="390" y="32" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="middle" fill="currentColor">Trust boundary: both private key sets stay in-house</text>
<rect x="14" y="74" width="120" height="84" rx="8" fill="#ef4444" fill-opacity="0.16" stroke="#ef4444" stroke-width="1.5"/>
<text x="74.0" y="106" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14.5" font-weight="bold" text-anchor="middle" fill="currentColor">eFuses</text>
<text x="74.0" y="126" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.85">SRK_HASH</text>
<rect x="164" y="74" width="110" height="84" rx="8" fill="#f59e0b" fill-opacity="0.16" stroke="#f59e0b" stroke-width="1.5"/>
<text x="219.0" y="106" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14.5" font-weight="bold" text-anchor="middle" fill="currentColor">SRK cert</text>
<text x="219.0" y="126" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.85">root key</text>
<rect x="304" y="74" width="110" height="84" rx="8" fill="#f59e0b" fill-opacity="0.16" stroke="#f59e0b" stroke-width="1.5"/>
<text x="359.0" y="106" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14.5" font-weight="bold" text-anchor="middle" fill="currentColor">CSF + IMG</text>
<text x="359.0" y="126" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.85">certs</text>
<rect x="444" y="74" width="140" height="84" rx="8" fill="#3b82f6" fill-opacity="0.16" stroke="#3b82f6" stroke-width="1.5"/>
<text x="514.0" y="106" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14.5" font-weight="bold" text-anchor="middle" fill="currentColor">Bootloader</text>
<text x="514.0" y="126" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.85">MCUboot</text>
<rect x="614" y="74" width="156" height="84" rx="8" fill="#10b981" fill-opacity="0.16" stroke="#10b981" stroke-width="1.5"/>
<text x="692.0" y="106" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14.5" font-weight="bold" text-anchor="middle" fill="currentColor">Application</text>
<text x="692.0" y="126" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.85">updated over</text>
<text x="692.0" y="142" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.85">the air</text>
<line x1="134" y1="116" x2="162" y2="116" stroke="currentColor" stroke-width="1.6" marker-end="url(#m_cur)"/>
<line x1="274" y1="116" x2="302" y2="116" stroke="currentColor" stroke-width="1.6" marker-end="url(#m_cur)"/>
<line x1="414" y1="116" x2="442" y2="116" stroke="currentColor" stroke-width="1.6" marker-end="url(#m_cur)"/>
<line x1="584" y1="116" x2="612" y2="116" stroke="currentColor" stroke-width="1.6" marker-end="url(#m_cur)"/>
<path d="M14,178 L14,188 L584,188 L584,178" fill="none" stroke="#f59e0b" stroke-width="2"/>
<text x="20" y="212" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#f59e0b">Key set 1: HAB (RSA)</text>
<text x="20" y="231" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">The boot ROM checks the bootloader at every reset.</text>
<path d="M444,252 L444,262 L770,262 L770,252" fill="none" stroke="#10b981" stroke-width="2"/>
<text x="450" y="286" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#10b981">Key set 2: MCUboot (ECDSA P-256)</text>
<text x="450" y="305" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">The bootloader checks the app,</text>
<text x="450" y="324" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">including every OTA update.</text>
</svg>
</div>
  <figcaption>Two signing key sets, one trust boundary.</figcaption>
</figure>

<h2 id="summary">Summary</h2>
<ol>
<li><strong>The order is the safety net.</strong> Everything reversible (keys, signing, testing on an open
part) happens before anything one-way.</li>
<li><strong>Signing is not encryption.</strong> HAB proves origin. BEE hides the code, and on the RT106x it
comes with a decrypt-only catch for updates.</li>
<li><strong>Close late and in stages.</strong> First batch open with the SRK fused, volume units closed
after their full test, debug and fuse locks last.</li>
</ol>
<div class="callout callout--note">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">📝</span>Sources</p>
  <div class="callout__body">
<ul>
<li>NXP AN12681, HABv4 secure boot on i.MX RT10xx.</li>
<li>NXP, <em>i.MX RT1060 Security Reference Manual</em>: security configurations, fuse map, BEE,
Secure JTAG.</li>
<li>NXP AN12852, on-the-fly decryption on i.MX RT.</li>
<li>NXP AN13023, crypto algorithm recommendations (the RSA key-size note).</li>
<li>SPSDK documentation: <code>nxpcrypto</code> and <code>nxpimage hab</code>.</li>
</ul>

  </div>
</div>

<p>💬 Questions, corrections, or your own war story? Leave a comment below.</p>
]]></content:encoded></item></channel></rss>