<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://kuebelrobert-dev.space/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kuebelrobert-dev.space/" rel="alternate" type="text/html" /><updated>2026-10-21T15:29:02+00:00</updated><id>https://kuebelrobert-dev.space/feed.xml</id><title type="html">Embedded Architecture Notes</title><subtitle>Notes on software architecture for embedded systems — security, flashing, communication, RTOS, AUTOSAR, firmware, debugging, AI, and engineering processes — drawn from professional work and self-study.</subtitle><author><name>Robert</name></author><entry><title type="html">Running an AUTOSAR Project on ThreadX, Part 2: RTE Communication and Basic Software</title><link href="https://kuebelrobert-dev.space/system%20&%20sw%20architecture/2026/10/21/running-autosar-on-threadx-part-2-rte-communication-and-bsw/" rel="alternate" type="text/html" title="Running an AUTOSAR Project on ThreadX, Part 2: RTE Communication and Basic Software" /><published>2026-10-21T00:00:00+00:00</published><updated>2026-10-21T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/10/21/running-autosar-on-threadx-part-2-rte-communication-and-bsw</id><content type="html" xml:base="https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/10/21/running-autosar-on-threadx-part-2-rte-communication-and-bsw/"><![CDATA[<p><em>This is Part 2 of a two-part post on running AUTOSAR components on ThreadX. <a href="/system%20&amp;%20sw%20architecture/2026/10/20/running-autosar-on-threadx-part-1-os-and-execution-model/">Part 1</a> covered the OS side: runnables and RTE events, tasks, resources, alarms, interrupts and multicore. This part covers RTE communication, basic software, and the bill at the end.</em></p>

<p>Quick recap of the idea from Part 1: the components stay exactly as they are, including every <code class="language-plaintext highlighter-rouge">Rte_Read</code> and <code class="language-plaintext highlighter-rouge">Rte_Call</code>. The generated RTE API headers stay the contract; underneath, a generated shim fulfills that contract with ThreadX primitives. Every mechanism gets a rating:</p>

<ul>
  <li><strong>Native</strong> — ThreadX has a primitive that does the job.</li>
  <li><strong>Shim</strong> — a thin layer of your code on top of ThreadX primitives.</li>
  <li><strong>Build</strong> — ThreadX has nothing close. You design and implement it, and for safety-relevant parts, you also argue it.</li>
</ul>

<p>In Part 1, we gave every OS task a thread and a dispatcher loop that wakes up on event flags. Now we fill in what the runnables actually do through their ports. The example project is still the BCU from the <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">foundation series</a>: six Cortex-M7 cores, one ASIL B core, five QM cores.</p>

<h2 id="whats-inside">What’s inside</h2>

<ul>
  <li>Sender-receiver communication, unqueued and queued, on ThreadX primitives</li>
  <li>Implicit vs. explicit communication: when a runnable gets a snapshot and when it sees the writer’s new value</li>
  <li>Client-server, synchronous and asynchronous, and mode management</li>
  <li>Basic software on ThreadX</li>
  <li>The summary table: native, shim or build for every mechanism, and what you lose (and how to win some of it back)</li>
</ul>

<section id="unlock" class="premium-unlock">
  <h2>Read the full article</h2>
  <p>The rest of this article is free, but not anonymous: tell me who you are and you get access to all members-only articles on this blog. No newsletter, no spam, no selling your data.</p>

  <p class="premium-unlock-msg" data-reason="required" hidden="">This article needs a quick unlock first.</p>
  <p class="premium-unlock-msg" data-reason="invalid" hidden="">Please enter a valid email address and accept the privacy policy.</p>
  <p class="premium-unlock-msg" data-reason="denied" hidden="">Access isn't available for this email address. If you think that's a mistake, <a href="mailto:kuebelroberttech@googlemail.com">send me a mail</a>.</p>

  <form method="post" action="https://kuebelrobert-dev.space/api/unlock">
    <input type="hidden" name="slug" value="running-autosar-on-threadx-part-2-rte-communication-and-bsw" />
    <label>Email <span aria-hidden="true">*</span>
      <input type="email" name="email" required="" maxlength="254" autocomplete="email" />
    </label>
    <label>Name
      <input type="text" name="name" maxlength="100" autocomplete="name" />
    </label>
    <label>Company
      <input type="text" name="company" maxlength="100" autocomplete="organization" />
    </label>
    <label>Which topic interests you most?
      <textarea name="interest" rows="3" maxlength="500"></textarea>
    </label>
    <label class="premium-unlock-hp" aria-hidden="true">Website
      <input type="text" name="website" tabindex="-1" autocomplete="off" />
    </label>
    <label class="premium-unlock-consent">
      <input type="checkbox" name="consent" value="yes" required="" />
      I agree that my details are stored to grant me access, as described in the <a href="/privacy.html">privacy policy</a>. I can have them deleted at any time.
    </label>
    <button type="submit">Unlock</button>
  </form>
</section>

<style>
  .premium-unlock { margin-top: 2.5em; padding: 1.25em 1.5em; border: 1px solid currentColor; border-radius: 6px; }
  .premium-unlock form { display: grid; gap: 0.9em; }
  .premium-unlock label { display: grid; gap: 0.3em; }
  .premium-unlock input[type=email], .premium-unlock input[type=text], .premium-unlock textarea {
    font: inherit; padding: 0.45em 0.6em; border-radius: 4px; border: 1px solid #888; width: 100%; box-sizing: border-box;
  }
  .premium-unlock .premium-unlock-consent { display: flex; gap: 0.6em; align-items: flex-start; }
  .premium-unlock .premium-unlock-consent input { margin-top: 0.35em; }
  .premium-unlock .premium-unlock-hp { position: absolute; left: -10000px; }
  .premium-unlock button { justify-self: start; font: inherit; padding: 0.5em 1.4em; border-radius: 4px; cursor: pointer; }
  .premium-unlock-msg { font-weight: bold; }
</style>

<script>
  (function () {
    var reason = new URLSearchParams(location.search).get("unlock");
    var msg = reason && document.querySelector('.premium-unlock-msg[data-reason="' + reason + '"]');
    if (msg) msg.hidden = false;
  })();
</script>]]></content><author><name>Robert</name></author><category term="System &amp; SW Architecture" /><category term="architecture" /><category term="autosar" /><category term="rte" /><category term="threadx" /><category term="rtos" /><category term="migration" /><category term="asil" /><summary type="html"><![CDATA[This is Part 2 of a two-part post on running AUTOSAR components on ThreadX. Part 1 covered the OS side: runnables and RTE events, tasks, resources, alarms, interrupts and multicore. This part covers RTE communication, basic software, and the bill at the end.]]></summary></entry><entry><title type="html">Securing the Foundation, Part 1: Threat Model, Root of Trust and Boot</title><link href="https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-1-threat-model-root-of-trust-and-boot/" rel="alternate" type="text/html" title="Securing the Foundation, Part 1: Threat Model, Root of Trust and Boot" /><published>2026-10-21T00:00:00+00:00</published><updated>2026-10-21T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-1-threat-model-root-of-trust-and-boot</id><content type="html" xml:base="https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-1-threat-model-root-of-trust-and-boot/"><![CDATA[<p><em>This is Part 1 of a three-part series on embedded security for our BCU. This part covers the threat model, the root of trust and the boot concept; <a href="/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics/">Part 2</a> covers updates, the bus and diagnostics; <a href="/security/2026/10/21/securing-the-foundation-part-3-code-debug-immobilizer-and-detection/">Part 3</a> covers the code, the debug port, the immobilizer, detection and privacy.</em></p>

<p><em>Quick recap of the BCU from the foundation series: <a href="/system%20&amp;%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking/">Part 1</a> (requirements), <a href="/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/">Part 2</a> (memory map), <a href="/system%20&amp;%20sw%20architecture/2026/09/04/create-the-foundation-part-3-protection-allocation-and-cpu-load/">Part 3</a> (memory protection) and <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">Part 4</a> (SWC allocation and tasks). If you haven’t read them, the short version: six Cortex-M7 cores, one simplex ASIL B core for exterior lighting, five QM cores for everything else, an HSM with its own firewalled flash, and no A/B swap.</em></p>

<p>In foundation Part 3, I hid a side note in the middle of the MPU chapter: <em>safety is not security, that’s a post of its own.</em> Side notes like that are promises. This is me keeping one.</p>

<p>The difference fits in one sentence. Safety protects the car from its own mistakes. Security protects it from somebody who makes mistakes happen on purpose. A bit flip doesn’t read your source code, doesn’t wait for the one millisecond your check is skipped, and doesn’t come back next week with a better tool. An attacker does all three. So everything we built in the foundation series (default-deny, E2E, mailboxes, watchdogs) is a good start, but it was designed against faults that are random and don’t try to get past you.</p>

<p>The good news: if you followed the series, a lot of security is already there, just not labelled as such. The bad news: the rest touches nearly every part of the ECU, from the production line to the scrapyard. So let’s go through it door by door, using our BCU. Every door gets the same three questions: what’s behind it, who wants in, and what keeps them out. There are ten doors, which is too many for one post, so this is a three-part series. This part sets up the threat model and covers the two doors everything else depends on: the root of trust and the boot.</p>

<h2 id="step-zero-the-tara-or-who-actually-wants-in">Step zero: the TARA, or “who actually wants in?”</h2>

<p>In safety you start with a HARA. In security you start with a TARA (threat analysis and risk assessment), as defined by ISO/SAE 21434. Since UN R155 came in, it’s not optional anymore: no cybersecurity management system, no type approval, no car. That gets a project manager’s attention very quickly.</p>

<p>A TARA works in five steps:</p>

<ol>
  <li><strong>Assets.</strong> What’s worth protecting, and which property of it: confidentiality, integrity or availability.</li>
  <li><strong>Damage scenarios.</strong> What happens if that property is lost, rated in four impact categories: safety, financial, operational and privacy.</li>
  <li><strong>Threat scenarios and attack paths.</strong> How someone gets there, step by step.</li>
  <li><strong>Attack feasibility.</strong> How hard that is: time, expertise, knowledge of the target, window of opportunity, equipment.</li>
  <li><strong>Risk and treatment.</strong> Impact × feasibility gives a risk value. For each risk you decide: reduce it, accept it, share it (insurance, contracts) or avoid it (drop the feature).</li>
</ol>

<p>The output is a set of <strong>cybersecurity goals</strong>, and from those you derive requirements. Without a TARA, every security discussion turns into “but what if the attacker…” and never ends, because an attacker with enough budget can always do one more thing. The TARA is how you decide where to stop. It turns paranoia into a documented, reviewable risk acceptance. Paranoia is cheap, the documented version costs effort.</p>

<p>Here’s a heavily trimmed first TARA for our BCU. As always in this series, the ratings are assumptions with a date on them, not results. If you want the untrimmed version — 14 assets, 13 damage scenarios, 19 threat scenarios with attack-potential ratings, risk treatment and eight cybersecurity goals with their CAL — it’s in the repo as <a href="https://github.com/Kuebelrobert/kuebelrobert.github.io/blob/main/examples/TARA_BCU_example.xlsx"><code class="language-plaintext highlighter-rouge">examples/TARA_BCU_example.xlsx</code></a>. Same idea as the memory map: the source of truth is a YAML file, <a href="https://github.com/Kuebelrobert/kuebelrobert.github.io/blob/main/_data/tara_bcu.yml"><code class="language-plaintext highlighter-rouge">_data/tara_bcu.yml</code></a>, and <a href="https://github.com/Kuebelrobert/kuebelrobert.github.io/blob/main/scripts/build_tara.py"><code class="language-plaintext highlighter-rouge">scripts/build_tara.py</code></a> refuses to generate the workbook if a risk above the acceptance threshold is left untreated.</p>

<table>
  <thead>
    <tr>
      <th>Asset</th>
      <th>Property</th>
      <th>Damage scenario</th>
      <th>Impact</th>
      <th>Main attack path</th>
      <th>Feasibility</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Central locking / unlock command</td>
      <td>Integrity</td>
      <td>Car opened and stolen</td>
      <td>Severe (financial)</td>
      <td>Injecting frames on a body CAN or LIN reachable from outside (door, mirror, headlight harness)</td>
      <td>High</td>
    </tr>
    <tr>
      <td>Immobilizer secret</td>
      <td>Confidentiality</td>
      <td>Keys cloned, car started without an authorized key</td>
      <td>Severe (financial)</td>
      <td>Debug port, side-channel on the ECU, sniffing the immobilizer protocol</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td>Brake / hazard light request</td>
      <td>Integrity</td>
      <td>Phantom brake lights, confused following traffic</td>
      <td>Moderate (safety)</td>
      <td>Spoofed CAN FD frames via OBD port or a compromised ECU</td>
      <td>High without SecOC</td>
    </tr>
    <tr>
      <td>Application firmware</td>
      <td>Integrity, authenticity</td>
      <td>Persistent malicious code in the ECU</td>
      <td>Severe</td>
      <td>Manipulated update package, debugger, rollback to a vulnerable version</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td>SecOC and diagnostic keys</td>
      <td>Confidentiality</td>
      <td>Attacker can authenticate as any ECU on the bus</td>
      <td>Severe</td>
      <td>Shared keys extracted from one ECU and reused in the whole fleet</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td>Diagnostic functions (IO control, write DIDs)</td>
      <td>Integrity</td>
      <td>Lamps switched, configuration changed, variant coding manipulated</td>
      <td>Major</td>
      <td>Weak UDS SecurityAccess, brute force</td>
      <td>High</td>
    </tr>
    <tr>
      <td>ECU availability</td>
      <td>Availability</td>
      <td>No lights, no doors</td>
      <td>Major (safety, operational)</td>
      <td>Bus flooding, update that bricks the ECU</td>
      <td>High</td>
    </tr>
    <tr>
      <td>Stored data (DTC snapshots, lock/unlock history)</td>
      <td>Confidentiality</td>
      <td>Movement and usage profile of the driver</td>
      <td>Moderate (privacy)</td>
      <td>Reading event memory over diagnostics</td>
      <td>High</td>
    </tr>
  </tbody>
</table>

<p>The first row is my favorite, because it’s not hypothetical. For a while, a popular way to steal cars was to pull out the headlight connector, plug a device into the CAN wires behind it and send “unlock” and “immobilizer OK”. No hacking of the radio, no stolen key. Just the wiring harness, which runs to places on the outside of the car where anyone can reach it. For a BCU, which is the ECU attached to almost every one of those wires, this is the threat model in one picture.</p>

<p>Here’s the whole attack surface of our BCU on one page: every way in from the outside, where it lands, and which door of this series deals with it. Keep it open in a second tab, all three parts refer back to it.</p>

<style>
.mpu-matrix {
  --surface-1: #fdfdfd;
  --text-primary: #111111;
  --text-secondary: #52514e;
  --good: #0ca30c;
  --on-good: #ffffff;
  --caution: #f2a900;
  --on-caution: #1a1a19;
  --critical: #d03b3b;
  --critical-tint: #fbeaea;
  --wrap: #2a78d6;
  --on-wrap: #ffffff;
  margin: 1.5rem 0;
  position: relative;
  color: var(--text-primary);
  background: var(--surface-1);
}
:root[data-theme="dark"] .mpu-matrix {
  color-scheme: dark;
  --surface-1: #1a1a19;
  --text-primary: #ffffff;
  --text-secondary: #c3c2b7;
  --critical-tint: #3a2020;
  --wrap: #3987e5;
}
.mpu-matrix .mpu-legend { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; margin: 0 0 0.5rem; font-size: 0.9rem; color: var(--text-secondary); }
.mpu-matrix .mpu-key { display: inline-flex; align-items: center; gap: 0.4rem; }
.mpu-matrix .mpu-swatch { width: 14px; height: 14px; border-radius: 3px; display: inline-block; }
.mpu-matrix .mpu-swatch.granted { background: var(--good); }
.mpu-matrix .mpu-swatch.caution { background: var(--caution); }
.mpu-matrix .mpu-swatch.wrap { width: auto; height: auto; padding: 0 0.45rem; border-radius: 999px; background: var(--wrap); color: var(--on-wrap); font-size: 0.75rem; font-weight: 700; line-height: 1.4; }
.mpu-matrix .mpu-swatch.denied { background: repeating-linear-gradient(45deg, var(--critical) 0 2px, var(--critical-tint) 2px 5px); box-shadow: inset 0 0 0 1.5px var(--critical); }
.mpu-matrix svg { display: block; width: 100%; max-width: 560px; height: auto; font-family: inherit; }
.mpu-matrix .axis-title { fill: var(--text-secondary); font-size: 13px; }
.mpu-matrix .tick { fill: var(--text-primary); font-size: 15px; font-weight: 600; }
.mpu-matrix .cell { cursor: default; outline: none; }
.mpu-matrix .cell .bg.granted { fill: var(--good); }
.mpu-matrix .cell .bg.caution { fill: var(--caution); }
.mpu-matrix .cell .bg.denied { fill: url(#mpu-hatch); stroke: var(--critical); stroke-width: 1.5; }
.mpu-matrix .cell .icon { font-size: 28px; font-weight: 700; text-anchor: middle; dominant-baseline: central; pointer-events: none; }
.mpu-matrix .cell .half { font-size: 15px; font-weight: 700; text-anchor: middle; dominant-baseline: central; pointer-events: none; }
.mpu-matrix .cell .granted:is(.icon, .half) { fill: var(--on-good); }
.mpu-matrix .cell .caution:is(.icon, .half) { fill: var(--on-caution); }
.mpu-matrix .cell .denied:is(.icon, .half) { fill: var(--critical); }
.mpu-matrix .cell .pill { fill: var(--wrap); }
.mpu-matrix .cell .pill-text { fill: var(--on-wrap); font-size: 12px; font-weight: 700; text-anchor: middle; dominant-baseline: central; pointer-events: none; }
.mpu-matrix .cell .frame { fill: none; stroke: var(--text-secondary); stroke-width: 1.5; opacity: 0.55; }
.mpu-matrix .cell .ring { fill: none; stroke: var(--text-primary); stroke-width: 2; opacity: 0; }
.mpu-matrix .cell:hover .ring, .mpu-matrix .cell:focus .ring { opacity: 1; }
.mpu-hatch-defs .hatch-bg { fill: var(--critical-tint, #fbeaea); }
.mpu-hatch-defs .hatch-line { stroke: var(--critical, #d03b3b); stroke-width: 1.5; opacity: 0.45; }
.mpu-matrix .mpu-tip { position: absolute; pointer-events: none; padding: 0.35rem 0.6rem; border-radius: 4px; font-size: 0.85rem; line-height: 1.3; max-width: 260px; background: var(--text-primary); color: var(--surface-1); box-shadow: 0 2px 8px rgba(0,0,0,0.2); opacity: 0; transition: opacity 80ms; }
.mpu-matrix .mpu-tip.show { opacity: 1; }
.mpu-matrix figcaption { font-size: 0.9rem; color: var(--text-secondary); margin-top: 0.5rem; }
.mpu-matrix details { margin-top: 0.5rem; font-size: 0.9rem; }
.mpu-matrix table { margin-top: 0.5rem; }
.mpu-matrix th, .mpu-matrix td { text-align: center; padding: 0.25rem 0.6rem; }
</style>

<svg class="mpu-hatch-defs mpu-matrix" width="0" height="0" style="position:absolute;margin:0" aria-hidden="true" focusable="false">
<defs>
<pattern id="mpu-hatch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<rect class="hatch-bg" width="6" height="6" />
<line class="hatch-line" x1="0" y1="0" x2="0" y2="6" />
</pattern>
</defs>
</svg>
<script>
(function () {
  function cellOf(e) { return e.target.closest && e.target.closest(".mpu-matrix .cell"); }
  function show(cell) {
    var fig = cell.closest(".mpu-matrix"), tip = fig.querySelector(".mpu-tip");
    tip.innerHTML = cell.getAttribute("data-tip");
    var f = fig.getBoundingClientRect(), b = cell.getBoundingClientRect();
    tip.classList.add("show");
    var left = b.left - f.left + b.width / 2 - tip.offsetWidth / 2;
    left = Math.max(0, Math.min(left, fig.clientWidth - tip.offsetWidth));
    var top = b.top - f.top - tip.offsetHeight - 6;
    if (top < 0) top = b.bottom - f.top + 6;
    tip.style.left = left + "px";
    tip.style.top = top + "px";
  }
  function hide(cell) { cell.closest(".mpu-matrix").querySelector(".mpu-tip").classList.remove("show"); }
  ["mouseover", "focusin"].forEach(function (t) {
    document.addEventListener(t, function (e) { var c = cellOf(e); if (c) show(c); });
  });
  ["mouseout", "focusout"].forEach(function (t) {
    document.addEventListener(t, function (e) {
      var c = cellOf(e);
      if (c && !(e.relatedTarget && c.contains(e.relatedTarget))) hide(c);
    });
  });
})();
</script>

<figure class="viz-root mpu-matrix sec-surface" id="security-attack-surface">
<style>
.sec-surface svg { max-width: 760px; }
.sec-surface .box { fill: var(--surface-1); stroke: var(--text-secondary); stroke-width: 1.2; }
.sec-surface .box.ext { stroke-dasharray: 4 3; }
.sec-surface .box.asil { stroke: var(--good); stroke-width: 2; }
.sec-surface .ecu { fill: none; stroke: var(--text-secondary); stroke-width: 1.5; }
.sec-surface .hsm { fill: url(#mpu-hatch); stroke: var(--critical); stroke-width: 1.5; }
.sec-surface .hsm-in { fill: var(--surface-1); stroke: none; }
.sec-surface .ttl { fill: var(--text-primary); font-size: 14px; font-weight: 600; }
.sec-surface .sub { fill: var(--text-secondary); font-size: 11.5px; }
.sec-surface .hdr { fill: var(--text-secondary); font-size: 12px; text-anchor: middle; }
.sec-surface .p { fill: none; stroke: var(--critical); stroke-width: 2; }
.sec-surface .p.ok { stroke: var(--good); }
.sec-surface .p.int { stroke: var(--text-secondary); stroke-dasharray: 4 3; }
.sec-surface .ah { fill: var(--critical); }
.sec-surface .ah.ok { fill: var(--good); }
.sec-surface .ah.int { fill: var(--text-secondary); }
.sec-surface .badge { fill: var(--text-primary); }
.sec-surface .badge-t { fill: var(--surface-1); font-size: 11px; font-weight: 700; text-anchor: middle; dominant-baseline: central; }
.sec-surface .lbl { fill: var(--text-secondary); font-size: 11px; }
.sec-surface ul { margin: 0.5rem 0 0 1.25rem; font-size: 0.9rem; color: var(--text-secondary); }
</style>
<div class="mpu-legend" aria-hidden="true">
<span class="mpu-key"><span class="mpu-swatch denied"></span>Untrusted input from outside the ECU</span>
<span class="mpu-key"><span class="mpu-swatch granted"></span>Diverse safety path, no bus involved</span>
<span class="mpu-key"><span class="mpu-swatch wrap">4</span> Door (section) that covers it</span>
</div>
<svg viewBox="0 0 760 440" role="img" aria-labelledby="sec-surface-title sec-surface-desc">
<title id="sec-surface-title">Attack surface of the BCU</title>
<desc id="sec-surface-desc">Six ways into the BCU from outside. The OBD port and the four CAN FD channels, and Ethernet with DoIP, both end at Core 1, which runs the gateway, SecOC and a rule-based IDS. The eight LIN channels, some running to doors and mirrors, end at Core 2. The key transponder ends at Core 5, which runs diagnostics, the immobilizer and the update manager. Update packages end at Stage-2. The debug port is controlled by the HSM and the lifecycle state. Core 1 passes ASIL-relevant signals to the ASIL B Core 0 through the mailbox with E2E protection. The hardwired brake switch reaches Core 0 directly, without any bus. The HSM holds all keys behind a firewall.</desc>
<defs>
<marker id="sec-ah" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path class="ah" d="M0,0 L8,4 L0,8 z" /></marker>
<marker id="sec-ah-ok" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path class="ah ok" d="M0,0 L8,4 L0,8 z" /></marker>
<marker id="sec-ah-int" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path class="ah int" d="M0,0 L8,4 L0,8 z" /></marker>
</defs>
<text class="hdr" x="100" y="22">Outside world</text>
<text class="hdr" x="475" y="22">BCU</text>
<rect class="ecu" x="250" y="30" width="450" height="400" rx="10" />
<!-- entry points -->
<rect class="box ext" x="10" y="40" width="180" height="46" rx="6" /><text class="ttl" x="22" y="60">OBD port · CAN FD ×4</text><text class="sub" x="22" y="77">anyone with a €20 adapter</text>
<rect class="box ext" x="10" y="100" width="180" height="46" rx="6" /><text class="ttl" x="22" y="120">Ethernet · DoIP</text><text class="sub" x="22" y="137">telematics, diagnostics</text>
<rect class="box ext" x="10" y="160" width="180" height="46" rx="6" /><text class="ttl" x="22" y="180">LIN ×8</text><text class="sub" x="22" y="197">doors, mirrors: outside the car</text>
<rect class="box ext" x="10" y="220" width="180" height="46" rx="6" /><text class="ttl" x="22" y="240">Key / transponder</text><text class="sub" x="22" y="257">RF, relay attacks</text>
<rect class="box ext" x="10" y="280" width="180" height="46" rx="6" /><text class="ttl" x="22" y="300">Update package</text><text class="sub" x="22" y="317">OTA or workshop tester</text>
<rect class="box ext" x="10" y="350" width="180" height="46" rx="6" /><text class="ttl" x="22" y="370">Debug port</text><text class="sub" x="22" y="387">JTAG/SWD, physical access</text>
<!-- receivers -->
<rect class="box" x="290" y="45" width="170" height="70" rx="6" /><text class="ttl" x="302" y="68">Core 1 · QM</text><text class="sub" x="302" y="86">gateway · SecOC</text><text class="sub" x="302" y="102">rule-based IDS</text>
<rect class="box" x="290" y="155" width="170" height="56" rx="6" /><text class="ttl" x="302" y="178">Core 2 · QM</text><text class="sub" x="302" y="196">LIN master · body</text>
<rect class="box" x="290" y="220" width="170" height="56" rx="6" /><text class="ttl" x="302" y="243">Core 5 · QM</text><text class="sub" x="302" y="261">diag · immo · update</text>
<rect class="box" x="290" y="285" width="170" height="50" rx="6" /><text class="ttl" x="302" y="306">Stage-1 / Stage-2</text><text class="sub" x="302" y="323">boot · flashing</text>
<rect class="box asil" x="500" y="45" width="170" height="100" rx="6" /><text class="ttl" x="512" y="68">Core 0 · ASIL B</text><text class="sub" x="512" y="86">exterior lights</text><text class="sub" x="512" y="102">E2E check</text><text class="sub" x="512" y="118">no direct outside input</text>
<rect class="hsm" x="500" y="200" width="170" height="215" rx="6" />
<rect class="hsm-in" x="508" y="208" width="154" height="199" rx="4" />
<text class="ttl" x="518" y="232">HSM</text><text class="sub" x="518" y="252">all keys, behind a firewall</text><text class="sub" x="518" y="270">crypto jobs, TRNG</text><text class="sub" x="518" y="288">monotonic counters</text><text class="sub" x="518" y="306">boot verification</text><text class="sub" x="518" y="324">lifecycle · debug unlock</text>
<!-- external paths -->
<path class="p" d="M190,63 H287" marker-end="url(#sec-ah)" />
<path class="p" d="M190,123 H240 V100 H287" marker-end="url(#sec-ah)" />
<path class="p" d="M190,183 H287" marker-end="url(#sec-ah)" />
<path class="p" d="M190,243 H287" marker-end="url(#sec-ah)" />
<path class="p" d="M190,303 H287" marker-end="url(#sec-ah)" />
<path class="p" d="M190,373 H497" marker-end="url(#sec-ah)" />
<!-- internal paths -->
<path class="p int" d="M460,80 H497" marker-end="url(#sec-ah-int)" />
<text class="lbl" x="463" y="74">mailbox</text>
<path class="p int" d="M460,248 H497" marker-end="url(#sec-ah-int)" />
<path class="p int" d="M460,310 H497" marker-end="url(#sec-ah-int)" />
<path class="p ok" d="M755,95 H673" marker-end="url(#sec-ah-ok)" />
<text class="lbl" x="706" y="86">brake</text><text class="lbl" x="706" y="112">switch</text>
<!-- door badges -->
<rect class="badge" x="203" y="54" width="30" height="18" rx="9" /><text class="badge-t" x="218" y="63">4·5</text>
<rect class="badge" x="198" y="114" width="30" height="18" rx="9" /><text class="badge-t" x="213" y="123">4·6</text>
<circle class="badge" cx="218" cy="183" r="9" /><text class="badge-t" x="218" y="183">4</text>
<circle class="badge" cx="218" cy="243" r="9" /><text class="badge-t" x="218" y="243">8</text>
<circle class="badge" cx="218" cy="303" r="9" /><text class="badge-t" x="218" y="303">3</text>
<circle class="badge" cx="218" cy="373" r="9" /><text class="badge-t" x="218" y="373">7</text>
<rect class="badge" x="625" y="215" width="30" height="18" rx="9" /><text class="badge-t" x="640" y="224">1·2</text>
<circle class="badge" cx="445" cy="62" r="9" /><text class="badge-t" x="445" y="62">9</text>
</svg>
<figcaption>
Every way into the BCU, and where it lands. The badges are the doors of this series: 1–2 in Part 1, 3–5 in Part 2, 6–10 in Part 3.
<ul>
<li>All external input lands on a QM core or on the bootloader, never directly on the ASIL B core. ASIL-relevant data reaches Core 0 only through the mailbox, and Core 0 checks it with E2E.</li>
<li>The brake light has a second path that no bus can reach: the hardwired brake switch.</li>
<li>Every key is in the HSM. The cores send jobs to it (dashed lines) and only get results back.</li>
</ul>
</figcaption>
</figure>

<p>Two more points before we go door by door:</p>

<ul>
  <li><strong>Security doesn’t replace safety goals, it adds new ones.</strong> The brake light row is both a safety topic (HARA, ASIL B, foundation Part 4) and a security topic (TARA, SecOC). They need to agree. A security reaction that turns the brake light off “because the message was tampered with” is unsafe. Write down how the two interact, the same way foundation Part 3 wrote down the QM/ASIL failure behavior.</li>
  <li><strong>The TARA rates attackers with physical access too.</strong> Someone who bought the same BCU at a scrapyard has unlimited time, a lab, and no reason to hurry. Whether you defend against them (fault injection, side channels) is a decision the TARA makes, not your gut feeling. Usually the answer is “protect the secrets that are shared across many vehicles, accept the rest”. More on that later.</li>
</ul>

<h2 id="door-1-the-root-of-trust--who-checks-the-checker">Door 1: the root of trust — who checks the checker?</h2>

<p>Every security mechanism in the ECU eventually rests on the question: who checked that the checker is genuine? At some point that chain has to stop at something you trust because it <em>can’t</em> be changed. That is the root of trust.</p>

<p>In our BCU it’s three things, and foundation Part 2 already gave each of them a place:</p>

<ul>
  <li><strong>The HSM’s boot ROM.</strong> Mask ROM in silicon, burned at the chip factory. It verifies the HSM firmware before the HSM runs it.</li>
  <li><strong>The HSM firmware and its keys</strong> in the firewalled HSM Flash (256 KB) and HSM Data Flash (32 KB). No application core can read them, not even the ASIL B one. That’s not the MPU, that’s the hardware firewall, and that’s important: if a QM core gets compromised and runs privileged, the MPU is gone. The firewall isn’t.</li>
  <li><strong>Stage-1</strong>, the 64 KB bootloader that’s <code class="language-plaintext highlighter-rouge">read-only</code> after production and has no update path. Paired with the HSM, it decides what runs next.</li>
</ul>

<h3 id="what-the-hsm-is-actually-for">What the HSM is actually for</h3>

<p>The HSM is a separate core with its own memory, its own crypto accelerators and its own firewall. The application cores talk to it through a job interface: “compute a CMAC over this buffer with key slot 7” — and they get the result, never the key. The key never leaves the HSM in plaintext. That’s the whole idea: <strong>an application compromise must not mean a key compromise.</strong> If an attacker gets code running on QM Core 5, they can ask the HSM for MACs as long as they’re in, which is bad. But when they’re out, they don’t take the key with them, and the next update closes the hole. Once a key is extracted, no update helps.</p>

<figure class="anim" style="margin:1.5rem 0">
<video src="/assets/videos/embedded-security/hsm-job-interface.mp4" autoplay="" loop="" muted="" playsinline="" controls="" preload="metadata" aria-label="Animation: Core 5 sends a CMAC job with key slot 7 to the HSM and gets a MAC back. A compromised core then tries to read key slot 7 directly and is blocked at the firewall. It can still request MACs while it is in, but after an update fixes it, the key is still inside the HSM." style="display:block;width:100%;height:auto;border-radius:6px;border:1px solid #e1e0dc"></video>
<figcaption style="font-size:0.9rem;color:#52514e;margin-top:0.4rem">Jobs go in, results come out. A direct read of the key hits the firewall, and after the fix the attacker leaves with nothing.</figcaption>
</figure>
<script>
(function () {
  if (!window.matchMedia || !matchMedia("(prefers-reduced-motion: reduce)").matches) return;
  function stop() {
    document.querySelectorAll("figure.anim video").forEach(function (v) { v.removeAttribute("autoplay"); v.pause(); });
  }
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", stop); else stop();
})();
</script>

<p>What our HSM provides, and who uses it in the BCU:</p>

<table>
  <thead>
    <tr>
      <th>HSM service</th>
      <th>Used for</th>
      <th>Called from</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>AES-128 CMAC generate/verify</td>
      <td>SecOC, boot verification, immobilizer protocol</td>
      <td>Core 1 (SecOC), Core 5 (immobilizer), Stage-1</td>
    </tr>
    <tr>
      <td>Signature verification (ECDSA, later post-quantum)</td>
      <td>Update packages, diagnostic certificates</td>
      <td>Stage-2, Core 5 (update manager, UDS 0x29)</td>
    </tr>
    <tr>
      <td>Hash (SHA-256)</td>
      <td>Update package hashing, boot verification</td>
      <td>Stage-2, Stage-1</td>
    </tr>
    <tr>
      <td>True random number generator</td>
      <td>Challenges (UDS, immobilizer), nonces, key generation</td>
      <td>Core 5</td>
    </tr>
    <tr>
      <td>Monotonic counters</td>
      <td>Anti-rollback, SecOC freshness</td>
      <td>Stage-2, Core 1</td>
    </tr>
    <tr>
      <td>Secure key storage and key update</td>
      <td>All of the above</td>
      <td>HSM-internal</td>
    </tr>
    <tr>
      <td>Secure debug unlock</td>
      <td>Debug access in the field</td>
      <td>HSM-internal</td>
    </tr>
  </tbody>
</table>

<p>The 32 KB “placeholder” in HSM Data Flash from foundation Part 2 now gets a first sizing: around 20 key slots with metadata, a few monotonic counters (wear-leveled, because counters that increment on every update or every ignition cycle <em>will</em> wear out a sector), two or three certificates of around 1 KB each, and the lifecycle state. 32 KB is enough, with room to spare. The placeholder just became a decision.</p>

<h3 id="the-key-inventory">The key inventory</h3>

<p>You can’t protect keys you didn’t write down. This is the list I’d put into the architecture document, and the moment where most projects discover they have more keys than they thought:</p>

<table>
  <thead>
    <tr>
      <th>Key</th>
      <th>Type</th>
      <th>Scope</th>
      <th>Stored in</th>
      <th>Provisioned</th>
      <th>Replaced by</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Boot MAC key</td>
      <td>AES-128</td>
      <td>Per ECU</td>
      <td>HSM Data Flash</td>
      <td>Generated in the HSM on first boot</td>
      <td>Never (re-generated after an update)</td>
    </tr>
    <tr>
      <td>Software signing key (OEM)</td>
      <td>ECDSA public key</td>
      <td>Per vehicle line</td>
      <td>HSM Data Flash, or Stage-1 as a fallback</td>
      <td>Production</td>
      <td>Key update with a key-signing key</td>
    </tr>
    <tr>
      <td>SecOC keys</td>
      <td>AES-128</td>
      <td>Per bus cluster, per vehicle</td>
      <td>HSM Data Flash</td>
      <td>End of line, via the vehicle’s key master</td>
      <td>Authenticated key update protocol</td>
    </tr>
    <tr>
      <td>Immobilizer secret</td>
      <td>AES-128</td>
      <td>Per vehicle (pairing BCU ↔ keys ↔ engine ECU)</td>
      <td>HSM Data Flash</td>
      <td>End of line / workshop pairing</td>
      <td>Workshop re-pairing (authenticated)</td>
    </tr>
    <tr>
      <td>Diagnostic ECU identity</td>
      <td>ECDSA key pair + certificate</td>
      <td>Per ECU</td>
      <td>HSM Data Flash</td>
      <td>End of line</td>
      <td>Certificate renewal over UDS 0x29</td>
    </tr>
    <tr>
      <td>Diagnostic root CA</td>
      <td>ECDSA public key</td>
      <td>Per OEM</td>
      <td>HSM Data Flash</td>
      <td>Production</td>
      <td>Key update</td>
    </tr>
    <tr>
      <td>Debug unlock key</td>
      <td>AES-128</td>
      <td>Per ECU, derived</td>
      <td>HSM Data Flash</td>
      <td>Production</td>
      <td>Never</td>
    </tr>
    <tr>
      <td>TLS / DoIP keys</td>
      <td>ECDSA key pair</td>
      <td>Per ECU</td>
      <td>HSM Data Flash</td>
      <td>End of line</td>
      <td>Certificate renewal</td>
    </tr>
  </tbody>
</table>

<p>The two rules behind that table:</p>

<ul>
  <li><strong>No secret is shared across the fleet.</strong> If a key is the same in every BCU, one BCU from a scrapyard, one side-channel attack and one weekend give you every car. Per-ECU keys are derived from a master key and the ECU serial number (from the ECU OTP block in foundation Part 2) in the key management backend. The master key never sees an ECU. The only things that are the same in every ECU are <em>public</em> keys, and those only need integrity, not confidentiality.</li>
  <li><strong>Every key has a way out.</strong> Algorithms get broken, keys leak, certificates expire. For every key in the table, “how do we replace it in a car that’s already in the field?” needs an answer before production starts. “We’ll do a recall” is an answer, just a very expensive one.</li>
</ul>

<h2 id="door-2-boot--secure-authenticated-or-just-fast">Door 2: boot — secure, authenticated, or just fast?</h2>

<p>Foundation Part 1 already warned about this conflict: secure boot that checks megabytes of code before anything starts is great for security and terrible for “the bus expects an answer in 150 ms”. Now we have the memory map, so we can do the math instead of worrying about it.</p>

<p>First, the terms, because people use them interchangeably and they aren’t:</p>

<ul>
  <li><strong>Secure boot:</strong> each stage verifies the next one <em>before</em> it runs. If the check fails, the next stage doesn’t run at all.</li>
  <li><strong>Authenticated boot:</strong> the next stage starts immediately, and the check runs in parallel. If it fails, the system loses its privileges: the HSM refuses to hand out keys for this boot cycle, and the event gets reported.</li>
  <li><strong>Measured boot:</strong> each stage records a hash of the next one, and someone else (a backend, a remote verifier) decides later whether to trust it. Common on PCs, rare on MCUs. I mention it so you’ve heard of it.</li>
</ul>

<p>Now the numbers. Let’s assume our HSM computes a CMAC at around 40 MB/s. That’s an illustrative value — check your datasheet, the range across chips is huge. What we’d need to verify, using foundation Part 2’s regions with realistic fill levels (only the image, not the reserved space):</p>

<table>
  <thead>
    <tr>
      <th>What</th>
      <th>Size (actual image)</th>
      <th>Verification time at 40 MB/s</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Stage-1</td>
      <td>~50 KB</td>
      <td>~1.3 ms</td>
    </tr>
    <tr>
      <td>Stage-2</td>
      <td>~200 KB</td>
      <td>~5 ms</td>
    </tr>
    <tr>
      <td>ASIL B code + calibration</td>
      <td>~1.9 MB + ~0.4 MB</td>
      <td>~58 ms</td>
    </tr>
    <tr>
      <td>QM code + calibration</td>
      <td>~6 MB + ~1.5 MB</td>
      <td>~190 ms</td>
    </tr>
    <tr>
      <td><strong>Everything, strictly before start</strong></td>
      <td><strong>~10 MB</strong></td>
      <td><strong>~255 ms</strong></td>
    </tr>
  </tbody>
</table>

<p>255 ms just for verification, before any core has initialized a single CAN controller. With a 150 ms budget for the first answer on the bus, full secure boot simply doesn’t fit. So here’s what I’d do:</p>

<ol>
  <li><strong>The HSM boots first and verifies its own firmware</strong> with its ROM. Strict, no exceptions.</li>
  <li><strong>The HSM verifies Stage-1 before it releases the ASIL B core from reset.</strong> Strict secure boot. 1.3 ms, nobody will notice.</li>
  <li><strong>Stage-2 is verified strictly, but only when we actually enter it</strong> (flashing session, or an invalid application). Normal startups don’t pay for it.</li>
  <li><strong>The applications use authenticated boot.</strong> Stage-1 does the fast safety integrity check (more on that in a moment) and starts the cores. The HSM verifies the application images in the background. The SecOC keys, the immobilizer key and the diagnostic identity are <strong>bound to the boot result</strong>: the HSM only unlocks them for this power cycle after the verification succeeds, typically within 250–300 ms of reset.</li>
</ol>

<figure class="anim" style="margin:1.5rem 0">
<video src="/assets/videos/embedded-security/boot-timeline.mp4" autoplay="" loop="" muted="" playsinline="" controls="" preload="metadata" aria-label="Animation: two timelines from 0 to 300 ms with a bus deadline at 150 ms. Top: the HSM verifies about 10 MB before anything starts, the cores start at 255 ms and miss the deadline. Bottom: the HSM verifies Stage-1 in 1.3 ms, the cores start immediately and answer on the bus after about 80 ms, while the HSM verifies the applications in the background and unlocks the keys at about 258 ms." style="display:block;width:100%;height:auto;border-radius:6px;border:1px solid #e1e0dc"></video>
<figcaption style="font-size:0.9rem;color:#52514e;margin-top:0.4rem">Strict secure boot for everything misses the 150 ms deadline. Strict Stage-1 plus authenticated applications makes it, and the keys stay locked until the verification is done.</figcaption>
</figure>

<p>What an attacker gets from a manipulated application under this scheme: an ECU that runs, but can’t send a single authenticated message, can’t authorize the engine start and can’t authenticate to a diagnostic tester. For the TARA, that’s the trade-off to argue: for about a quarter of a second after each reset, unverified code runs, but it has no keys. And to get modified code into the flash at all, the attacker must already have defeated Stage-2’s signature check or the debug lock. Those are doors 3 and 7, in <a href="/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics/">Part 2</a> and <a href="/security/2026/10/21/securing-the-foundation-part-3-code-debug-immobilizer-and-detection/">Part 3</a>. I’d accept that risk and write down why. If your TARA disagrees (for example, because the application controls an actuator that’s dangerous even in that quarter second), the answer is strict verification for that part and a different startup concept, not “let’s hope nobody tries”.</p>

<p>Two things that make this cheaper than it looks:</p>

<ul>
  <li><strong>Sign the image, not the region.</strong> The 2 MB ASIL B region holds 1.9 MB of code. The signature covers the image length stored in the header, not the padding. Foundation Part 2’s reserves cost memory, but not boot time.</li>
  <li><strong>Asymmetric at update time, symmetric at boot time.</strong> Verifying an ECDSA signature takes a few milliseconds no matter the image size. Hashing the image is what costs time. So the update path (Stage-2) verifies the OEM signature once, and then the HSM computes a CMAC with the ECU’s own boot MAC key and stores it. Every following boot checks only this CMAC, which is faster than re-checking the full signature chain and doesn’t need the OEM’s public key at runtime.</li>
</ul>

<h3 id="integrity-for-safety-authenticity-for-security">Integrity for safety, authenticity for security</h3>

<p>Foundation Part 2 left a caveat in the YAML: the check that stops a corrupted ASIL B image from starting is a <em>safety</em> mechanism against random hardware faults and probably needs ASIL B. The HSM’s CMAC is a <em>security</em> mechanism, usually developed as QM.</p>

<p>It’s tempting to say “the CMAC catches every bit flip anyway, so we just need one check”. Technically true, but the safety case then rests on HSM firmware developed to a standard that doesn’t fit. So in our BCU, Stage-1 runs a fast CRC over the ASIL B image, developed to ASIL B, <em>and</em> the HSM runs its CMAC. Two checks over the same bytes for two different reasons. It looks redundant, but it means neither the safety assessor nor the security assessor has to accept evidence from the other team’s process. If you want to merge them, talk to both assessors first. Together, ideally — that’s a meeting worth bringing popcorn to.</p>

<h3 id="fault-injection-the-attack-on-the-if">Fault injection: the attack on the <code class="language-plaintext highlighter-rouge">if</code></h3>

<p>The most efficient attack on secure boot doesn’t break the crypto. It skips the comparison. A short voltage glitch or an electromagnetic pulse at the right moment, and <code class="language-plaintext highlighter-rouge">if (result != OK)</code> takes the wrong branch. There’s equipment for this that costs less than a decent oscilloscope.</p>

<p>If the TARA puts this attack in scope (for the immobilizer, it probably does), the code that evaluates the boot result looks a bit paranoid:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define BOOT_OK    0x3CA5965Au   </span><span class="cm">/* no 0/1: a single glitched bit must not produce "OK" */</span><span class="cp">
#define BOOT_FAIL  0xC35A69A5u
</span>
<span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">res</span> <span class="o">=</span> <span class="n">hsm_boot_status</span><span class="p">(</span><span class="n">IMG_APP_ASILB</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">!=</span> <span class="n">BOOT_OK</span><span class="p">)</span> <span class="p">{</span> <span class="n">boot_fail</span><span class="p">();</span> <span class="p">}</span>
<span class="n">delay_random_cycles</span><span class="p">();</span>                         <span class="cm">/* the attacker has to hit twice, at an unknown time */</span>
<span class="k">if</span> <span class="p">(</span><span class="n">hsm_boot_status</span><span class="p">(</span><span class="n">IMG_APP_ASILB</span><span class="p">)</span> <span class="o">!=</span> <span class="n">BOOT_OK</span><span class="p">)</span> <span class="p">{</span> <span class="n">boot_fail</span><span class="p">();</span> <span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">!=</span> <span class="n">BOOT_OK</span><span class="p">)</span> <span class="p">{</span> <span class="n">boot_fail</span><span class="p">();</span> <span class="p">}</span>            <span class="cm">/* re-check: the compiler keeps it because of volatile */</span>
<span class="n">start_core</span><span class="p">(</span><span class="n">CORE_ASILB</span><span class="p">,</span> <span class="n">img_entry</span><span class="p">(</span><span class="n">IMG_APP_ASILB</span><span class="p">));</span>
</code></pre></div></div>

<p>Non-trivial constants, double checks, random delays, <code class="language-plaintext highlighter-rouge">volatile</code> so the compiler doesn’t “optimize” the paranoia away. Plus the hardware: many automotive MCUs have voltage and clock monitors that reset the chip on a glitch, and the HSM runs its own checks with its own countermeasures. It doesn’t make fault injection impossible, it makes it slower and less reliable. Usually that’s enough to make the attack not worth it.</p>

<h2 id="where-this-leaves-us">Where this leaves us</h2>

<p>Two doors in, we have the base everything else stands on. A TARA tells us what’s worth protecting and where to stop. The HSM keeps every key behind a firewall that even a compromised, privileged core can’t get through. The key inventory says where each key comes from and how it gets replaced. And the boot concept fits into foundation Part 1’s startup budget by making the keys, not the start, depend on the verification.</p>

<p>What we haven’t covered yet is everything that tries to get <em>into</em> the ECU while it’s running: new firmware, frames on the bus and a diagnostic tester asking for more rights than it should have. That’s <a href="/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics/">Part 2</a>. The code itself, the debug port, the immobilizer and noticing an attack follow in <a href="/security/2026/10/21/securing-the-foundation-part-3-code-debug-immobilizer-and-detection/">Part 3</a>.</p>

<h2 id="sources-and-further-reading">Sources and further reading</h2>

<h3 id="standards-and-specifications">Standards and specifications</h3>

<ul>
  <li>ISO/SAE 21434:2021 — Road vehicles — Cybersecurity engineering — the TARA method used in step zero.</li>
  <li><a href="https://unece.org/transport/documents/2021/03/standards/un-regulation-no-155-cyber-security-and-cyber-security">UN Regulation No. 155 — Cyber security and cyber security management system</a> — Annex 5 lists the threats a type approval expects you to have thought about.</li>
  <li><a href="https://csrc.nist.gov/pubs/sp/800/38/b/upd1/final">NIST SP 800-38B: The CMAC Mode for Authentication</a> — the MAC behind fast authenticated boot.</li>
  <li><a href="https://csrc.nist.gov/pubs/fips/186-5/final">NIST FIPS 186-5: Digital Signature Standard</a> — ECDSA for signed images.</li>
  <li><a href="https://csrc.nist.gov/pubs/sp/800/193/final">NIST SP 800-193: Platform Firmware Resiliency Guidelines</a> — root of trust, protection, detection and recovery.</li>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_CryptoServiceManager.pdf">AUTOSAR Classic Platform: Crypto Service Manager, R23-11</a> and <a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_KeyManager.pdf">Key Manager, R23-11</a> — how application cores talk to the HSM and keys.</li>
  <li><a href="https://www.evita-project.org/">EVITA project</a> — the research project behind the full/medium/light HSM classes still used today.</li>
  <li><a href="https://docs.mcuboot.com/">MCUboot</a> and <a href="https://www.trustedfirmware.org/projects/tf-m/">Trusted Firmware-M</a> — open-source references for secure boot and a root of trust on Cortex-M.</li>
</ul>

<h3 id="papers">Papers</h3>

<ul>
  <li>Hagai Bar-El et al.: <a href="https://doi.org/10.1109/JPROC.2005.862424"><em>The Sorcerer’s Apprentice Guide to Fault Attacks</em></a>, Proceedings of the IEEE 94(2), 2006 — the classic overview of glitching and why one <code class="language-plaintext highlighter-rouge">if</code> is not enough.</li>
</ul>

<h3 id="books">Books</h3>

<ul>
  <li>David Ward, Paul Wooderson: <em>Automotive Cybersecurity: An Introduction to ISO/SAE 21434</em>, SAE International, 2021</li>
  <li>Catherine H. Gebotys: <em>Security in Embedded Devices</em>, Springer, 2010</li>
  <li>Jasper van Woudenberg, Colin O’Flynn: <em>The Hardware Hacking Handbook</em>, No Starch Press, 2021 — fault injection and side channels from the attacker’s side.</li>
  <li>Christof Paar, Jan Pelzl: <em>Understanding Cryptography</em>, Springer, 2010 — AES, CMAC and ECC explained for engineers.</li>
  <li>Adam Shostack: <em>Threat Modeling: Designing for Security</em>, Wiley, 2014</li>
  <li>Ross Anderson: <em>Security Engineering</em>, 3rd ed., Wiley, 2020</li>
</ul>

<p><em>The animations in this post are rendered with <a href="https://www.manim.community/">manim</a> from <a href="https://github.com/Kuebelrobert/kuebelrobert.github.io/blob/main/scripts/manim/security_scenes.py"><code class="language-plaintext highlighter-rouge">scripts/manim/security_scenes.py</code></a>.</em></p>]]></content><author><name>Robert</name></author><category term="Security" /><category term="security" /><category term="tara" /><category term="iso-21434" /><category term="hsm" /><category term="key-management" /><category term="secure-boot" /><category term="fault-injection" /><category term="cortex-m7" /><summary type="html"><![CDATA[This is Part 1 of a three-part series on embedded security for our BCU. This part covers the threat model, the root of trust and the boot concept; Part 2 covers updates, the bus and diagnostics; Part 3 covers the code, the debug port, the immobilizer, detection and privacy.]]></summary></entry><entry><title type="html">Securing the Foundation, Part 2: Updates, the Bus and Diagnostics</title><link href="https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics/" rel="alternate" type="text/html" title="Securing the Foundation, Part 2: Updates, the Bus and Diagnostics" /><published>2026-10-21T00:00:00+00:00</published><updated>2026-10-21T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics</id><content type="html" xml:base="https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics/"><![CDATA[<p><em>This is Part 2 of a three-part series on embedded security for our BCU. <a href="/security/2026/10/21/securing-the-foundation-part-1-threat-model-root-of-trust-and-boot/">Part 1</a> covers the threat model, the root of trust and the boot concept; this part covers updates, the bus and diagnostics; <a href="/security/2026/10/21/securing-the-foundation-part-3-code-debug-immobilizer-and-detection/">Part 3</a> covers the code, the debug port, the immobilizer, detection and privacy. The example is the BCU from the <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">foundation series</a>.</em></p>

<p>In <a href="/security/2026/10/21/securing-the-foundation-part-1-threat-model-root-of-trust-and-boot/">Part 1</a>, we built the base: a TARA for the BCU, an HSM that keeps every key behind its firewall, a key inventory, and a boot concept that fits into the 150 ms startup budget because the keys, not the start, depend on the verification. If you skipped it, at least look at the attack surface map there. This part uses its door numbers.</p>

<p>This part covers the three doors that are open while the ECU is running, by design: the update path, which is <em>supposed</em> to put new code into the ECU; the bus, where every ECU is supposed to talk; and diagnostics, which is supposed to give a tester control over the ECU. The question isn’t how to close them. It’s how to make sure only the right people get through.</p>

<h2 id="whats-inside">What’s inside</h2>

<ul>
  <li>Door 3, updates: check before you erase, anti-rollback, and the problem with “immutable”</li>
  <li>Door 4, the bus: SecOC on CAN FD, the brake light with security, LIN, Ethernet and the gateway</li>
  <li>Door 5, diagnostics: the door with a key under the mat</li>
</ul>

<section id="unlock" class="premium-unlock">
  <h2>Read the full article</h2>
  <p>The rest of this article is free, but not anonymous: tell me who you are and you get access to all members-only articles on this blog. No newsletter, no spam, no selling your data.</p>

  <p class="premium-unlock-msg" data-reason="required" hidden="">This article needs a quick unlock first.</p>
  <p class="premium-unlock-msg" data-reason="invalid" hidden="">Please enter a valid email address and accept the privacy policy.</p>
  <p class="premium-unlock-msg" data-reason="denied" hidden="">Access isn't available for this email address. If you think that's a mistake, <a href="mailto:kuebelroberttech@googlemail.com">send me a mail</a>.</p>

  <form method="post" action="https://kuebelrobert-dev.space/api/unlock">
    <input type="hidden" name="slug" value="securing-the-foundation-part-2-updates-bus-and-diagnostics" />
    <label>Email <span aria-hidden="true">*</span>
      <input type="email" name="email" required="" maxlength="254" autocomplete="email" />
    </label>
    <label>Name
      <input type="text" name="name" maxlength="100" autocomplete="name" />
    </label>
    <label>Company
      <input type="text" name="company" maxlength="100" autocomplete="organization" />
    </label>
    <label>Which topic interests you most?
      <textarea name="interest" rows="3" maxlength="500"></textarea>
    </label>
    <label class="premium-unlock-hp" aria-hidden="true">Website
      <input type="text" name="website" tabindex="-1" autocomplete="off" />
    </label>
    <label class="premium-unlock-consent">
      <input type="checkbox" name="consent" value="yes" required="" />
      I agree that my details are stored to grant me access, as described in the <a href="/privacy.html">privacy policy</a>. I can have them deleted at any time.
    </label>
    <button type="submit">Unlock</button>
  </form>
</section>

<style>
  .premium-unlock { margin-top: 2.5em; padding: 1.25em 1.5em; border: 1px solid currentColor; border-radius: 6px; }
  .premium-unlock form { display: grid; gap: 0.9em; }
  .premium-unlock label { display: grid; gap: 0.3em; }
  .premium-unlock input[type=email], .premium-unlock input[type=text], .premium-unlock textarea {
    font: inherit; padding: 0.45em 0.6em; border-radius: 4px; border: 1px solid #888; width: 100%; box-sizing: border-box;
  }
  .premium-unlock .premium-unlock-consent { display: flex; gap: 0.6em; align-items: flex-start; }
  .premium-unlock .premium-unlock-consent input { margin-top: 0.35em; }
  .premium-unlock .premium-unlock-hp { position: absolute; left: -10000px; }
  .premium-unlock button { justify-self: start; font: inherit; padding: 0.5em 1.4em; border-radius: 4px; cursor: pointer; }
  .premium-unlock-msg { font-weight: bold; }
</style>

<script>
  (function () {
    var reason = new URLSearchParams(location.search).get("unlock");
    var msg = reason && document.querySelector('.premium-unlock-msg[data-reason="' + reason + '"]');
    if (msg) msg.hidden = false;
  })();
</script>]]></content><author><name>Robert</name></author><category term="Security" /><category term="security" /><category term="secure-update" /><category term="anti-rollback" /><category term="post-quantum" /><category term="secoc" /><category term="can-fd" /><category term="lin" /><category term="gateway" /><category term="uds" /><category term="cortex-m7" /><summary type="html"><![CDATA[This is Part 2 of a three-part series on embedded security for our BCU. Part 1 covers the threat model, the root of trust and the boot concept; this part covers updates, the bus and diagnostics; Part 3 covers the code, the debug port, the immobilizer, detection and privacy. The example is the BCU from the foundation series.]]></summary></entry><entry><title type="html">Securing the Foundation, Part 3: Code, Debug, Immobilizer and Detection</title><link href="https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-3-code-debug-immobilizer-and-detection/" rel="alternate" type="text/html" title="Securing the Foundation, Part 3: Code, Debug, Immobilizer and Detection" /><published>2026-10-21T00:00:00+00:00</published><updated>2026-10-21T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-3-code-debug-immobilizer-and-detection</id><content type="html" xml:base="https://kuebelrobert-dev.space/security/2026/10/21/securing-the-foundation-part-3-code-debug-immobilizer-and-detection/"><![CDATA[<p><em>This is Part 3 of a three-part series on embedded security for our BCU. <a href="/security/2026/10/21/securing-the-foundation-part-1-threat-model-root-of-trust-and-boot/">Part 1</a> covers the threat model, the root of trust and the boot concept; <a href="/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics/">Part 2</a> covers updates, the bus and diagnostics; this part covers the code, the debug port, the immobilizer, detection and privacy. The example is the BCU from the <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">foundation series</a>.</em></p>

<p><a href="/security/2026/10/21/securing-the-foundation-part-1-threat-model-root-of-trust-and-boot/">Part 1</a> built the base: TARA, HSM, keys and boot. <a href="/security/2026/10/21/securing-the-foundation-part-2-updates-bus-and-diagnostics/">Part 2</a> put locks on the three doors that are open by design: updates, the bus and diagnostics. This last part is about the doors that are easy to forget: the code behind all those locks, the debug port, the immobilizer (where security isn’t a protection, it’s the function), noticing an attack, and the data the BCU collects without anybody asking. At the end, everything comes together in one table: which security mechanism lives where in the BCU, and what it costs.</p>

<h2 id="whats-inside">What’s inside</h2>

<ul>
  <li>Door 6, the code itself: parsers, pointers and other gifts</li>
  <li>Door 7, the debug port and the ECU lifecycle</li>
  <li>Door 8, the immobilizer: security as the actual function</li>
  <li>Door 9, detection and response, and Door 10, privacy</li>
  <li>Where security lives in the BCU: one table for every mechanism, and the process around it</li>
</ul>

<section id="unlock" class="premium-unlock">
  <h2>Read the full article</h2>
  <p>The rest of this article is free, but not anonymous: tell me who you are and you get access to all members-only articles on this blog. No newsletter, no spam, no selling your data.</p>

  <p class="premium-unlock-msg" data-reason="required" hidden="">This article needs a quick unlock first.</p>
  <p class="premium-unlock-msg" data-reason="invalid" hidden="">Please enter a valid email address and accept the privacy policy.</p>
  <p class="premium-unlock-msg" data-reason="denied" hidden="">Access isn't available for this email address. If you think that's a mistake, <a href="mailto:kuebelroberttech@googlemail.com">send me a mail</a>.</p>

  <form method="post" action="https://kuebelrobert-dev.space/api/unlock">
    <input type="hidden" name="slug" value="securing-the-foundation-part-3-code-debug-immobilizer-and-detection" />
    <label>Email <span aria-hidden="true">*</span>
      <input type="email" name="email" required="" maxlength="254" autocomplete="email" />
    </label>
    <label>Name
      <input type="text" name="name" maxlength="100" autocomplete="name" />
    </label>
    <label>Company
      <input type="text" name="company" maxlength="100" autocomplete="organization" />
    </label>
    <label>Which topic interests you most?
      <textarea name="interest" rows="3" maxlength="500"></textarea>
    </label>
    <label class="premium-unlock-hp" aria-hidden="true">Website
      <input type="text" name="website" tabindex="-1" autocomplete="off" />
    </label>
    <label class="premium-unlock-consent">
      <input type="checkbox" name="consent" value="yes" required="" />
      I agree that my details are stored to grant me access, as described in the <a href="/privacy.html">privacy policy</a>. I can have them deleted at any time.
    </label>
    <button type="submit">Unlock</button>
  </form>
</section>

<style>
  .premium-unlock { margin-top: 2.5em; padding: 1.25em 1.5em; border: 1px solid currentColor; border-radius: 6px; }
  .premium-unlock form { display: grid; gap: 0.9em; }
  .premium-unlock label { display: grid; gap: 0.3em; }
  .premium-unlock input[type=email], .premium-unlock input[type=text], .premium-unlock textarea {
    font: inherit; padding: 0.45em 0.6em; border-radius: 4px; border: 1px solid #888; width: 100%; box-sizing: border-box;
  }
  .premium-unlock .premium-unlock-consent { display: flex; gap: 0.6em; align-items: flex-start; }
  .premium-unlock .premium-unlock-consent input { margin-top: 0.35em; }
  .premium-unlock .premium-unlock-hp { position: absolute; left: -10000px; }
  .premium-unlock button { justify-self: start; font: inherit; padding: 0.5em 1.4em; border-radius: 4px; cursor: pointer; }
  .premium-unlock-msg { font-weight: bold; }
</style>

<script>
  (function () {
    var reason = new URLSearchParams(location.search).get("unlock");
    var msg = reason && document.querySelector('.premium-unlock-msg[data-reason="' + reason + '"]');
    if (msg) msg.hidden = false;
  })();
</script>]]></content><author><name>Robert</name></author><category term="Security" /><category term="security" /><category term="secure-coding" /><category term="fuzzing" /><category term="sbom" /><category term="debug-protection" /><category term="lifecycle" /><category term="immobilizer" /><category term="ids" /><category term="privacy" /><category term="iso-21434" /><category term="cortex-m7" /><summary type="html"><![CDATA[This is Part 3 of a three-part series on embedded security for our BCU. Part 1 covers the threat model, the root of trust and the boot concept; Part 2 covers updates, the bus and diagnostics; this part covers the code, the debug port, the immobilizer, detection and privacy. The example is the BCU from the foundation series.]]></summary></entry><entry><title type="html">Running an AUTOSAR Project on ThreadX, Part 1: The OS and the Execution Model</title><link href="https://kuebelrobert-dev.space/system%20&%20sw%20architecture/2026/10/20/running-autosar-on-threadx-part-1-os-and-execution-model/" rel="alternate" type="text/html" title="Running an AUTOSAR Project on ThreadX, Part 1: The OS and the Execution Model" /><published>2026-10-20T00:00:00+00:00</published><updated>2026-10-20T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/10/20/running-autosar-on-threadx-part-1-os-and-execution-model</id><content type="html" xml:base="https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/10/20/running-autosar-on-threadx-part-1-os-and-execution-model/"><![CDATA[<p><em>This is Part 1 of a two-part post on running AUTOSAR components on ThreadX. This part covers the OS side: runnables, tasks, resources, alarms, interrupts, hooks, and everything multicore. <a href="/system%20&amp;%20sw%20architecture/2026/10/21/running-autosar-on-threadx-part-2-rte-communication-and-bsw/">Part 2</a> covers RTE communication, basic software, and what the whole exercise costs you.</em></p>

<p>Sooner or later, every embedded architect hears some version of this sentence: “We have all these AUTOSAR components already — can’t we just run them on ThreadX?” Sometimes it’s a QM core that doesn’t justify another AUTOSAR license, sometimes a prototype that has to run next week, sometimes a customer who wants an AUTOSAR <em>architecture</em> but a different OS underneath. The question is always the same: how much of AUTOSAR can you rebuild on a plain RTOS, and what does it cost?</p>

<p>Short answer: almost all of it. Long answer: this post and the next one. I went through the AUTOSAR Classic mechanisms one by one — runnables and RTE events, OS tasks and resources, alarms, IOC, partitions, timing protection in this part; sender-receiver in all its flavors, client-server and modes in Part 2 — and mapped each one onto ThreadX. Every mapping gets a rating:</p>

<ul>
  <li><strong>Native</strong> — ThreadX has a primitive that does the job.</li>
  <li><strong>Shim</strong> — a thin layer of your code on top of ThreadX primitives.</li>
  <li><strong>Build</strong> — ThreadX has nothing close. You design and implement it, and for safety-relevant parts, you also argue it.</li>
</ul>

<p>The example project is the BCU from the <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">foundation series</a>: six Cortex-M7 cores, one ASIL B core, five QM cores.</p>

<h2 id="the-idea-keep-the-contract-swap-the-engine">The idea: keep the contract, swap the engine</h2>

<p>The trick that makes this work is not to port the components. You keep them exactly as they are, including every <code class="language-plaintext highlighter-rouge">Rte_Read</code> and <code class="language-plaintext highlighter-rouge">Rte_Call</code>, and replace what sits underneath:</p>

<table>
  <thead>
    <tr>
      <th>Layer</th>
      <th>In AUTOSAR</th>
      <th>On ThreadX</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Software components</td>
      <td>Runnables calling <code class="language-plaintext highlighter-rouge">Rte_*</code></td>
      <td><strong>Unchanged</strong></td>
    </tr>
    <tr>
      <td>RTE API</td>
      <td>Generated <code class="language-plaintext highlighter-rouge">Rte_&lt;Swc&gt;.h</code> headers</td>
      <td><strong>Unchanged</strong> — the contract stays</td>
    </tr>
    <tr>
      <td>RTE implementation</td>
      <td>Generated by the RTE generator</td>
      <td><strong>The shim</strong>: buffers, queues, flags, dispatchers</td>
    </tr>
    <tr>
      <td>Basic software scheduling</td>
      <td>SchM, generated</td>
      <td>Periodic threads calling the BSW main functions</td>
    </tr>
    <tr>
      <td>OS</td>
      <td>AUTOSAR OS (OSEK-based)</td>
      <td>ThreadX, plus whatever the shim has to add</td>
    </tr>
    <tr>
      <td>Memory protection</td>
      <td>OS-Applications, generated MPU tables</td>
      <td>ThreadX Modules or your own MPU handling</td>
    </tr>
  </tbody>
</table>

<p>The RTE API is the contract between the components and the platform. As long as that contract holds, components don’t care what fulfills it. That’s also the first rule of doing this in practice: <strong>generate the shim, don’t write it by hand.</strong> An ECU has hundreds of ports. Nobody writes 300 <code class="language-plaintext highlighter-rouge">Rte_Read</code> functions by hand and gets all of them right — but a small script that reads your port description (the ARXML you already have, or a YAML file like the <a href="/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/">memory map from Part 2 of the foundation series</a>) gets them right every time, or fails loudly.</p>

<h2 id="the-execution-model">The execution model</h2>

<h3 id="runnables-and-rte-events">Runnables and RTE events</h3>

<p>In AUTOSAR, you don’t write tasks. You write runnables, and RTE events decide when they run: every 10 ms, when data arrives, when someone calls an operation. The RTE generator then maps runnables into OS tasks. On ThreadX, the shim does the same with one thread per OS task and one event flag per RTE event:</p>

<figure class="anim" style="margin:1.5rem 0">
<video src="/assets/videos/autosar-threadx/events-to-threads.mp4" autoplay="" loop="" muted="" playsinline="" controls="" preload="metadata" aria-label="Animation: three RTE events set bits in an event flag group; the task thread wakes up and runs the runnables mapped to the set bits." style="display:block;width:100%;height:auto;border-radius:6px;border:1px solid #e1e0dc"></video>
<figcaption style="font-size:0.9rem;color:#52514e;margin-top:0.4rem">Every RTE event becomes a flag. The thread waits for any of its flags, wakes up and runs the mapped runnables in the configured order.</figcaption>
</figure>
<script>
(function () {
  if (!window.matchMedia || !matchMedia("(prefers-reduced-motion: reduce)").matches) return;
  function stop() {
    document.querySelectorAll("figure.anim video").forEach(function (v) { v.removeAttribute("autoplay"); v.pause(); });
  }
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", stop); else stop();
})();
</script>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define EV_10MS      (1u &lt;&lt; 0)   </span><span class="cm">/* TimingEvent 10 ms        */</span><span class="cp">
#define EV_SPEED_RX  (1u &lt;&lt; 1)   </span><span class="cm">/* DataReceivedEvent         */</span><span class="cp">
#define EV_SERVE_OP  (1u &lt;&lt; 2)   </span><span class="cm">/* OperationInvokedEvent     */</span><span class="cp">
</span>
<span class="k">static</span> <span class="n">TX_EVENT_FLAGS_GROUP</span> <span class="n">task10ms_ev</span><span class="p">;</span>
<span class="k">static</span> <span class="n">TX_TIMER</span>             <span class="n">task10ms_tmr</span><span class="p">;</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">tick_10ms</span><span class="p">(</span><span class="n">ULONG</span> <span class="n">arg</span><span class="p">)</span>                 <span class="cm">/* runs in the ThreadX timer thread */</span>
<span class="p">{</span>
    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
    <span class="n">tx_event_flags_set</span><span class="p">(</span><span class="o">&amp;</span><span class="n">task10ms_ev</span><span class="p">,</span> <span class="n">EV_10MS</span><span class="p">,</span> <span class="n">TX_OR</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">Task_10ms_entry</span><span class="p">(</span><span class="n">ULONG</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">ULONG</span> <span class="n">ev</span><span class="p">;</span>
    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
    <span class="n">LightCtrl_Init</span><span class="p">();</span>                                       <span class="cm">/* InitEvent */</span>
    <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
        <span class="n">tx_event_flags_get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">task10ms_ev</span><span class="p">,</span> <span class="n">EV_10MS</span> <span class="o">|</span> <span class="n">EV_SPEED_RX</span> <span class="o">|</span> <span class="n">EV_SERVE_OP</span><span class="p">,</span>
                           <span class="n">TX_OR_CLEAR</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ev</span><span class="p">,</span> <span class="n">TX_WAIT_FOREVER</span><span class="p">);</span>
        <span class="n">rte_task10ms_copy_in</span><span class="p">();</span>                             <span class="cm">/* implicit inputs, see Part 2 */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">ev</span> <span class="o">&amp;</span> <span class="n">EV_10MS</span><span class="p">)</span>     <span class="p">{</span> <span class="n">R_Control</span><span class="p">();</span> <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">ev</span> <span class="o">&amp;</span> <span class="n">EV_SPEED_RX</span><span class="p">)</span> <span class="p">{</span> <span class="n">R_OnSpeed</span><span class="p">();</span> <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">ev</span> <span class="o">&amp;</span> <span class="n">EV_SERVE_OP</span><span class="p">)</span> <span class="p">{</span> <span class="n">R_ServeOp</span><span class="p">();</span> <span class="p">}</span>
        <span class="n">rte_task10ms_copy_out</span><span class="p">();</span>                            <span class="cm">/* implicit outputs */</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cm">/* at init, with a 1 ms tick: */</span>
<span class="cm">/* tx_timer_create(&amp;task10ms_tmr, "10ms", tick_10ms, 0, 10, 10, TX_AUTO_ACTIVATE); */</span>
</code></pre></div></div>

<p>Two catches. First, event flags don’t count. If the 10 ms timer fires twice before the thread gets to run, the thread sees one flag, not two — the activation is gone, silently. AUTOSAR basic tasks can count multiple activations and report <code class="language-plaintext highlighter-rouge">E_OS_LIMIT</code> when they overflow. If you care (and for anything timing-supervised, you do), use a counting semaphore for the timing event, or at least count overruns in the timer callback. Second, ThreadX timer callbacks run in the timer thread (or in the tick interrupt, depending on configuration), so they set a flag and leave. Nothing else.</p>

<p>The full list of RTE events, what they mean in AUTOSAR, and where they come from on ThreadX:</p>

<table>
  <thead>
    <tr>
      <th>RTE event</th>
      <th>When it fires in AUTOSAR</th>
      <th>Triggered on ThreadX by</th>
      <th>Watch out for</th>
      <th>Rating</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TimingEvent</code></td>
      <td>Periodically, with the configured period (and an offset, if the RTE generator staggers runnables)</td>
      <td><code class="language-plaintext highlighter-rouge">tx_timer</code> expiration function setting the task’s flag; offsets via the timer’s initial ticks</td>
      <td>Flags don’t count missed activations (see above). Period must be a multiple of the tick. Two timers with the same period drift apart only if they are created at different ticks — create them together</td>
      <td>Native</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">InitEvent</code></td>
      <td>Once, after <code class="language-plaintext highlighter-rouge">Rte_Start</code>, before any other runnable of the component</td>
      <td>Call before the thread’s <code class="language-plaintext highlighter-rouge">for (;;)</code> loop</td>
      <td>Order across threads: a component’s init must not read data that another thread hasn’t initialized yet. Either init everything in <code class="language-plaintext highlighter-rouge">tx_application_define</code> or hold all threads behind a start barrier</td>
      <td>Native</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BackgroundEvent</code></td>
      <td>Whenever nothing else runs, in the lowest-priority (background) task</td>
      <td>Lowest-priority thread calling the runnables in an endless loop</td>
      <td>Never blocks, so it must be the only thread on that priority, and below the idle-time measurement if you have one. Keep it preemptible: no interrupt locks inside</td>
      <td>Native</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DataReceivedEvent</code></td>
      <td>When a new value arrives on an R-port data element (queued or unqueued)</td>
      <td>The shim’s <code class="language-plaintext highlighter-rouge">Rte_Write</code>/<code class="language-plaintext highlighter-rouge">Rte_Send</code> (or the COM Rx callback for bus data) setting the receiver’s flag</td>
      <td>For queued data, one flag for several messages: the runnable must drain the queue in a loop, not read one element. For unqueued data, several writes collapse into one activation — that’s last-is-best, and correct</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DataReceiveErrorEvent</code></td>
      <td>When a received value is invalidated, or its alive timeout expires (unqueued data)</td>
      <td>Shim: <code class="language-plaintext highlighter-rouge">Rte_Invalidate</code> path, or a supervision timer that checks the timestamp of the buffer</td>
      <td>Needs a periodic check, because a timeout is the <em>absence</em> of an event — some thread has to look at the clock</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DataSendCompletedEvent</code></td>
      <td>When an explicit <code class="language-plaintext highlighter-rouge">Rte_Write</code>/<code class="language-plaintext highlighter-rouge">Rte_Send</code> has been transmitted (or failed)</td>
      <td>Shim: immediately for local data, from the COM Tx confirmation callback for bus data</td>
      <td>The confirmation callback runs in interrupt or BSW context: set a flag and leave</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DataWriteCompletedEvent</code></td>
      <td>When an implicit <code class="language-plaintext highlighter-rouge">Rte_IWrite</code> has been published, after the task ends</td>
      <td>Shim: in <code class="language-plaintext highlighter-rouge">copy_out</code>, after the task-level copies are written back</td>
      <td>Fires once per task activation, not once per <code class="language-plaintext highlighter-rouge">Rte_IWrite</code></td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">OperationInvokedEvent</code></td>
      <td>When a client calls the server’s operation (client-server)</td>
      <td>Server thread waiting on its request queue (or a flag set by the shim next to the queue)</td>
      <td>Several clients share one queue: the request needs a client ID and a sequence number, so the reply finds its way back (see Part 2)</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">AsynchronousServerCallReturnsEvent</code></td>
      <td>When the result of an asynchronous <code class="language-plaintext highlighter-rouge">Rte_Call</code> is available</td>
      <td>Server setting the client’s flag after writing the reply slot</td>
      <td>The flag only says “something is ready” — <code class="language-plaintext highlighter-rouge">Rte_Result</code> must check the sequence number and drop stale answers</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SwcModeSwitchEvent</code></td>
      <td>On entry to, exit from or transition between modes of a mode-switch port</td>
      <td>The mode shim: a flag per mode user, the transition in the dispatcher (see Part 2)</td>
      <td>Mode-disabled runnables must not start during the transition — the dispatcher checks the mode before every runnable, not once per loop</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ModeSwitchedAckEvent</code></td>
      <td>At the mode manager, when all mode users have completed the switch</td>
      <td>The mode shim, after the last user has finished its transition</td>
      <td>“All users” across several threads or cores needs a counter or a barrier, not a single flag</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SwcModeManagerErrorEvent</code></td>
      <td>At the mode manager, when a mode switch could not be performed (e.g. the switch queue overflowed)</td>
      <td>The mode shim, from <code class="language-plaintext highlighter-rouge">Rte_Switch</code></td>
      <td>Rarely used — and therefore rarely tested. Decide whether you need it at all</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ExternalTriggerOccurredEvent</code></td>
      <td>When another component (or the BSW) raises a trigger on a trigger port</td>
      <td><code class="language-plaintext highlighter-rouge">Rte_Trigger</code> → <code class="language-plaintext highlighter-rouge">tx_event_flags_set</code> on every triggered task</td>
      <td>One trigger can activate runnables in several tasks: the shim sets one flag per task. Queued triggers (with a queue length) need a counter</td>
      <td>Native</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">InternalTriggerOccurredEvent</code></td>
      <td>When a runnable triggers another runnable of the same component (<code class="language-plaintext highlighter-rouge">Rte_IrTrigger</code>)</td>
      <td><code class="language-plaintext highlighter-rouge">tx_event_flags_set</code>, or a direct call if both runnables are in the same task</td>
      <td>Same as above; the direct call is only allowed if the configuration doesn’t require a separate activation</td>
      <td>Native</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TransformerErrorEvent</code></td>
      <td>When a transformer (E2E, SOME/IP serializer) reports a hard error on the server side of a call</td>
      <td>E2E/transformer wrapper in the shim setting the server’s flag</td>
      <td>The error has to be stored next to the flag, so the runnable can tell <em>which</em> request failed and why</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">OsTaskExecutionEvent</code></td>
      <td>Every time the OS task the runnable is mapped to executes, independent of why</td>
      <td>Runnable called on every pass of the thread loop</td>
      <td>Runs whenever <em>any</em> flag of the thread is set — including the ones you didn’t think of</td>
      <td>Native</td>
    </tr>
  </tbody>
</table>

<h3 id="os-tasks-basic-extended-activation">OS tasks: basic, extended, activation</h3>

<p>AUTOSAR OS is OSEK underneath, and OSEK knows two kinds of tasks. <strong>Basic tasks</strong> run to completion: <code class="language-plaintext highlighter-rouge">ActivateTask</code> starts them, they end with <code class="language-plaintext highlighter-rouge">TerminateTask</code>. <strong>Extended tasks</strong> can also wait for events. On ThreadX, both become threads in an endless loop:</p>

<ul>
  <li><strong>Basic task</strong> → thread blocking on a counting semaphore. <code class="language-plaintext highlighter-rouge">ActivateTask</code> is <code class="language-plaintext highlighter-rouge">tx_semaphore_put</code>, the semaphore count is the number of pending activations, and the shim returns <code class="language-plaintext highlighter-rouge">E_OS_LIMIT</code> when it exceeds the configured maximum. <code class="language-plaintext highlighter-rouge">TerminateTask</code> and <code class="language-plaintext highlighter-rouge">ChainTask</code> become “return to the top of the loop” (plus an activation of the next task for <code class="language-plaintext highlighter-rouge">ChainTask</code>). Rating: <strong>Shim</strong>.</li>
  <li><strong>Extended task</strong> → thread with an event flag group. <code class="language-plaintext highlighter-rouge">SetEvent</code>, <code class="language-plaintext highlighter-rouge">WaitEvent</code>, <code class="language-plaintext highlighter-rouge">ClearEvent</code> and <code class="language-plaintext highlighter-rouge">GetEvent</code> map almost one to one onto <code class="language-plaintext highlighter-rouge">tx_event_flags_set</code> and <code class="language-plaintext highlighter-rouge">tx_event_flags_get</code>, with 32 flags per group. Rating: <strong>Native</strong>.</li>
  <li><strong>Priorities</strong> → careful: in OSEK, a <em>higher</em> number means a higher priority. In ThreadX, 0 is the highest. The shim generator inverts the numbers exactly once, in one place, and nobody ever touches priorities by hand again. This is not a hypothetical bug. It is <em>the</em> bug.</li>
  <li><strong>Non-preemptive tasks and internal resources</strong> → ThreadX’s <strong>preemption-threshold</strong>. A thread with priority 10 and a preemption threshold of 5 can only be preempted by threads above priority 5 — exactly the “group of tasks that don’t preempt each other” that OSEK builds with internal resources. Rating: <strong>Native</strong>, and one of the nicest matches in this whole post.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Schedule()</code></strong> (a non-preemptive task offering a rescheduling point) → temporarily lowering the preemption threshold. <code class="language-plaintext highlighter-rouge">tx_thread_relinquish</code> only yields to threads of the <em>same</em> priority, so it is not the same thing. Rating: <strong>Shim</strong>.</li>
</ul>

<h3 id="resources-and-exclusive-areas">Resources and exclusive areas</h3>

<p>OSEK resources use the <strong>immediate priority ceiling protocol</strong>: when a task takes a resource, it immediately runs at the highest priority of all tasks that use that resource. Nobody else who needs the resource can run, so nobody can block on it, and deadlocks are impossible by design. ThreadX mutexes use <strong>priority inheritance</strong> instead: the holder is boosted only once a higher-priority thread actually blocks. Both bound the blocking time, but they behave differently:</p>

<figure class="anim" style="margin:1.5rem 0">
<video src="/assets/videos/autosar-threadx/resource-vs-mutex.mp4" autoplay="" loop="" muted="" playsinline="" controls="" preload="metadata" aria-label="Animation: timelines of three threads H, M and L. With a priority ceiling, M never runs while L holds the resource. With priority inheritance, M runs while L holds the mutex, and L is only boosted when H blocks." style="display:block;width:100%;height:auto;border-radius:6px;border:1px solid #e1e0dc"></video>
<figcaption style="font-size:0.9rem;color:#52514e;margin-top:0.4rem">With a ceiling, nothing between L and the ceiling runs while L holds the resource. With inheritance, M can run in between, and nested mutexes can deadlock.</figcaption>
</figure>

<p>The good news: preemption-threshold gives you the ceiling protocol on ThreadX almost for free.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* GetResource(RES_SPEED) -- ceiling = highest-priority user of RES_SPEED */</span>
<span class="n">UINT</span> <span class="n">old</span><span class="p">;</span>
<span class="n">tx_thread_preemption_change</span><span class="p">(</span><span class="n">tx_thread_identify</span><span class="p">(),</span> <span class="n">CEILING_RES_SPEED</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">old</span><span class="p">);</span>

<span class="cm">/* ... critical section ... */</span>

<span class="cm">/* ReleaseResource(RES_SPEED) */</span>
<span class="n">tx_thread_preemption_change</span><span class="p">(</span><span class="n">tx_thread_identify</span><span class="p">(),</span> <span class="n">old</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">old</span><span class="p">);</span>
</code></pre></div></div>

<p>For nested resources, the shim keeps a small stack of old thresholds. For resources shared with interrupts (OSEK allows Category 2 ISRs to use resources), preemption-threshold isn’t enough, because it only affects threads — the shim has to mask the interrupt instead. Rating: <strong>Shim</strong>.</p>

<p><strong>Exclusive areas</strong> (<code class="language-plaintext highlighter-rouge">Rte_Enter</code>/<code class="language-plaintext highlighter-rouge">Rte_Exit</code> for components, <code class="language-plaintext highlighter-rouge">SchM_Enter</code>/<code class="language-plaintext highlighter-rouge">SchM_Exit</code> for basic software) are the same story with one more twist: AUTOSAR lets you configure <em>how</em> each exclusive area is implemented — interrupt lock, OS resource, or no protection at all if the configuration proves it’s unnecessary. The shim needs the same switch per exclusive area: <code class="language-plaintext highlighter-rouge">tx_interrupt_control(TX_INT_DISABLE)</code> for short copies, preemption-threshold for longer sections, a mutex only where you really want inheritance. Rating: <strong>Shim</strong>.</p>

<h3 id="alarms-counters-and-schedule-tables">Alarms, counters and schedule tables</h3>

<ul>
  <li><strong>Alarms</strong> (<code class="language-plaintext highlighter-rouge">SetRelAlarm</code>, <code class="language-plaintext highlighter-rouge">SetAbsAlarm</code>, <code class="language-plaintext highlighter-rouge">CancelAlarm</code>, with the actions “activate task”, “set event” or “callback”) → <code class="language-plaintext highlighter-rouge">tx_timer</code> with an expiration function that performs the action. Rating: <strong>Native</strong>.</li>
  <li><strong>Counters</strong> → the ThreadX tick (<code class="language-plaintext highlighter-rouge">tx_time_get</code>) covers the system counter. Additional counters driven by hardware (an angle counter, a second time base) are yours. Rating: <strong>Shim</strong>.</li>
  <li><strong>Schedule tables</strong> (expiry points that activate tasks and set events at fixed offsets, optionally synchronized to a global time) → a table plus one timer that walks through the expiry points is easy. Synchronization to an external time base, with drift correction, is not. Rating: <strong>Shim</strong> without synchronization, <strong>Build</strong> with it.</li>
</ul>

<h3 id="interrupts">Interrupts</h3>

<ul>
  <li><strong>Category 1 vs. Category 2 ISRs</strong> → ThreadX doesn’t make that distinction; on the Cortex-M ports, an ISR can call the ThreadX services that are allowed from interrupt context (with <code class="language-plaintext highlighter-rouge">TX_NO_WAIT</code>). “This ISR never calls the OS” is a rule you enforce in reviews. Rating: <strong>Shim</strong> (a naming convention and a review rule).</li>
  <li><strong><code class="language-plaintext highlighter-rouge">DisableAllInterrupts</code>, <code class="language-plaintext highlighter-rouge">SuspendAllInterrupts</code></strong> → <code class="language-plaintext highlighter-rouge">tx_interrupt_control(TX_INT_DISABLE)</code> and restore. Nesting counters for <code class="language-plaintext highlighter-rouge">Suspend</code>/<code class="language-plaintext highlighter-rouge">Resume</code> are a few lines in the shim. Rating: <strong>Native</strong>.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">SuspendOSInterrupts</code></strong> (block only interrupts that may call the OS, let the fast Category 1 ISRs through) → on Cortex-M that’s a BASEPRI-based lock instead of PRIMASK. Check whether your ThreadX port can be configured that way; if not, it’s a port change. Rating: <strong>Shim</strong> or <strong>Build</strong>.</li>
</ul>

<h3 id="hooks-and-error-handling">Hooks and error handling</h3>

<ul>
  <li><strong>StartupHook, ShutdownHook</strong> → <code class="language-plaintext highlighter-rouge">tx_application_define</code> and your own shutdown path. Rating: <strong>Native</strong>.</li>
  <li><strong>ErrorHook</strong> → ThreadX returns error codes from every service (with error checking enabled). The shim wraps the calls and funnels errors into one handler. Rating: <strong>Shim</strong>.</li>
  <li><strong>Stack monitoring</strong> → <code class="language-plaintext highlighter-rouge">tx_thread_stack_error_notify</code>, with stack checking enabled. Rating: <strong>Native</strong>.</li>
  <li><strong>ProtectionHook</strong> → the MemManage fault handler, plus the module manager’s fault handling if you use ThreadX Modules. Rating: <strong>Shim</strong>.</li>
</ul>

<h2 id="multicore-and-partitioning">Multicore and partitioning</h2>

<h3 id="cross-core-communication">Cross-core communication</h3>

<p>AUTOSAR OS generates the <strong>IOC</strong> (Inter-OS-Application Communicator) for anything that crosses cores or partitions: <code class="language-plaintext highlighter-rouge">IocWrite</code>/<code class="language-plaintext highlighter-rouge">IocRead</code> for last-is-best, <code class="language-plaintext highlighter-rouge">IocSend</code>/<code class="language-plaintext highlighter-rouge">IocReceive</code> for queued data. Cross-core <code class="language-plaintext highlighter-rouge">ActivateTask</code> and <code class="language-plaintext highlighter-rouge">SetEvent</code> work too.</p>

<p>ThreadX on a multicore microcontroller like ours usually runs as one independent instance per core (AMP). There is no shared kernel, so no queue, flag or semaphore spans two cores. The bridge is shared memory and a protocol you define:</p>

<figure class="anim" style="margin:1.5rem 0">
<video src="/assets/videos/autosar-threadx/ioc-cross-core.mp4" autoplay="" loop="" muted="" playsinline="" controls="" preload="metadata" aria-label="Animation: Core 1 writes a mailbox in shared non-cacheable SRAM using a sequence counter; Core 0 reads it, retries when the counter is odd or changed, and accepts a consistent copy." style="display:block;width:100%;height:auto;border-radius:6px;border:1px solid #e1e0dc"></video>
<figcaption style="font-size:0.9rem;color:#52514e;margin-top:0.4rem">A seqlock mailbox between two ThreadX instances: the writer makes the sequence counter odd while it writes, the reader retries a bounded number of times.</figcaption>
</figure>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span> <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">seq</span><span class="p">;</span> <span class="n">Speed_T</span> <span class="n">data</span><span class="p">;</span> <span class="p">}</span> <span class="n">mbox_speed_t</span><span class="p">;</span>
<span class="k">extern</span> <span class="n">mbox_speed_t</span> <span class="n">mbox_speed</span><span class="p">;</span>                 <span class="cm">/* shared SRAM, mapped non-cacheable */</span>

<span class="kt">void</span> <span class="nf">mbox_speed_write</span><span class="p">(</span><span class="n">Speed_T</span> <span class="n">v</span><span class="p">)</span>                <span class="cm">/* the single writer, Core 1 */</span>
<span class="p">{</span>
    <span class="n">mbox_speed</span><span class="p">.</span><span class="n">seq</span><span class="o">++</span><span class="p">;</span>  <span class="n">__DMB</span><span class="p">();</span>                 <span class="cm">/* odd: writing */</span>
    <span class="n">mbox_speed</span><span class="p">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span>
    <span class="n">__DMB</span><span class="p">();</span>  <span class="n">mbox_speed</span><span class="p">.</span><span class="n">seq</span><span class="o">++</span><span class="p">;</span>                 <span class="cm">/* even: done */</span>
<span class="p">}</span>

<span class="n">bool</span> <span class="nf">mbox_speed_read</span><span class="p">(</span><span class="n">Speed_T</span> <span class="o">*</span><span class="n">out</span><span class="p">)</span>              <span class="cm">/* reader, Core 0: bounded, never waits on the writer */</span>
<span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">tries</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">tries</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="n">tries</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">uint32_t</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">mbox_speed</span><span class="p">.</span><span class="n">seq</span><span class="p">;</span>  <span class="n">__DMB</span><span class="p">();</span>
        <span class="o">*</span><span class="n">out</span> <span class="o">=</span> <span class="n">mbox_speed</span><span class="p">.</span><span class="n">data</span><span class="p">;</span>        <span class="n">__DMB</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">((</span><span class="n">s1</span> <span class="o">==</span> <span class="n">mbox_speed</span><span class="p">.</span><span class="n">seq</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">((</span><span class="n">s1</span> <span class="o">&amp;</span> <span class="mi">1u</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0u</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="nb">true</span><span class="p">;</span> <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>                               <span class="cm">/* stale: use the fallback */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Three things carry this, and the IOC would have handled every one of them:</p>

<ul>
  <li><strong>Caches.</strong> A Cortex-M7 has a data cache, and the caches of different cores are not coherent. Shared data lives in a region the MPU marks non-cacheable, or both sides do explicit cache maintenance. Forget this and the reader processes a value that’s been stale for minutes, with a perfectly consistent sequence counter.</li>
  <li><strong>Bounded retries.</strong> An unbounded seqlock loop lets a misbehaving QM writer stall the ASIL reader — interference through time.</li>
  <li><strong>One writer per mailbox.</strong> Two writers need two mailboxes.</li>
</ul>

<p>For queued data, the same idea becomes a single-producer, single-consumer ring buffer, plus an inter-core interrupt so the receiving core can wake its thread with <code class="language-plaintext highlighter-rouge">tx_semaphore_put</code> instead of polling. Cross-core <code class="language-plaintext highlighter-rouge">ActivateTask</code>/<code class="language-plaintext highlighter-rouge">SetEvent</code> work the same way: an inter-core interrupt, and a handler on the target core that sets the flag. Rating: <strong>Build</strong>.</p>

<h3 id="spinlocks">Spinlocks</h3>

<p><code class="language-plaintext highlighter-rouge">GetSpinlock</code>/<code class="language-plaintext highlighter-rouge">ReleaseSpinlock</code> protect data that several cores modify. On ThreadX, you build them from the chip’s hardware semaphore unit if it has one, or from exclusive load/store instructions — but only if the interconnect actually supports exclusive accesses to the shared SRAM across cores. Many don’t, and an <code class="language-plaintext highlighter-rouge">LDREX</code>/<code class="language-plaintext highlighter-rouge">STREX</code> spinlock on memory without a global exclusive monitor locks nothing at all. Check the reference manual before you trust it. And the rule from Part 4 of the foundation series still applies: no spinlock is ever shared between ASIL and QM. Rating: <strong>Build</strong>.</p>

<h3 id="os-applications-memory-protection-and-trusted-functions">OS-Applications, memory protection and trusted functions</h3>

<p>AUTOSAR groups tasks, ISRs and data into <strong>OS-Applications</strong>, marks them trusted or non-trusted, and generates the MPU configuration per partition. On ThreadX, the closest thing is <strong>ThreadX Modules</strong>: separately linked application modules that the module manager loads, starts and stops, each running unprivileged in its own MPU-protected memory. It’s a different model — modules are separate binaries — but the result is the same: QM code that can’t touch ASIL memory.</p>

<ul>
  <li><strong>Partitioning and the MPU</strong> → ThreadX Modules, or a hand-made scheme that reprograms the MPU on every context switch. Rating: <strong>Shim</strong> with Modules, <strong>Build</strong> without.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">CallTrustedFunction</code></strong> (a QM partition asks the ASIL partition for a service) → a request queue to a thread inside the ASIL partition, exactly like a client-server call. Rating: <strong>Shim</strong>.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">CallNonTrustedFunction</code></strong> (the call gate from the ASIL partition into QM code) → a request to a thread in the QM partition, with a timeout, and validation of the result. Rating: <strong>Shim</strong>.</li>
  <li><strong>Terminating and restarting a partition</strong> (<code class="language-plaintext highlighter-rouge">TerminateApplication</code>) → the module manager can stop and restart a module. Rating: <strong>Native</strong> with Modules.</li>
</ul>

<h3 id="timing-protection">Timing protection</h3>

<p>AUTOSAR OS can monitor an <strong>execution budget</strong> per task, a <strong>time frame</strong> between activations, and <strong>lock budgets</strong> for resources and interrupt locks. When a task exceeds them, the ProtectionHook decides what happens. ThreadX has nothing equivalent built in. Its execution profiling can measure how long threads run, and from there you build a monitor: per-activation runtime accounting in the dispatcher, a deadline check, and a watchdog supervisor that escalates. For ASIL software, that monitor is a safety mechanism — and has to be developed and argued like one. Rating: <strong>Build</strong>.</p>

<h3 id="service-protection">Service protection</h3>

<p>AUTOSAR OS rejects calls from the wrong context (<code class="language-plaintext highlighter-rouge">WaitEvent</code> from an ISR, <code class="language-plaintext highlighter-rouge">TerminateTask</code> with a resource still held). ThreadX’s error checking catches the first kind (<code class="language-plaintext highlighter-rouge">TX_CALLER_ERROR</code>); the second kind is the shim’s job. Rating: <strong>Native</strong> plus <strong>Shim</strong>.</p>

<h2 id="the-summary-table-part-1">The summary table, part 1</h2>

<table>
  <thead>
    <tr>
      <th>Mechanism</th>
      <th>ThreadX building blocks</th>
      <th>Rating</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Runnables + TimingEvent</td>
      <td>Thread, <code class="language-plaintext highlighter-rouge">tx_timer</code>, event flags</td>
      <td>Native</td>
    </tr>
    <tr>
      <td>Other RTE events</td>
      <td>Event flags set by the shim</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td>Basic tasks, <code class="language-plaintext highlighter-rouge">ActivateTask</code>, multiple activation</td>
      <td>Thread + counting semaphore</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td>Extended tasks, <code class="language-plaintext highlighter-rouge">SetEvent</code>/<code class="language-plaintext highlighter-rouge">WaitEvent</code></td>
      <td>Thread + event flags</td>
      <td>Native</td>
    </tr>
    <tr>
      <td>Non-preemptive tasks, internal resources</td>
      <td>Preemption-threshold</td>
      <td>Native</td>
    </tr>
    <tr>
      <td>OS resources (priority ceiling)</td>
      <td><code class="language-plaintext highlighter-rouge">tx_thread_preemption_change</code></td>
      <td>Shim</td>
    </tr>
    <tr>
      <td>Exclusive areas (<code class="language-plaintext highlighter-rouge">Rte_</code>/<code class="language-plaintext highlighter-rouge">SchM_Enter</code>)</td>
      <td>Interrupt lock, preemption-threshold or mutex</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td>Alarms</td>
      <td><code class="language-plaintext highlighter-rouge">tx_timer</code></td>
      <td>Native</td>
    </tr>
    <tr>
      <td>Schedule tables</td>
      <td>Table + timer; synchronization</td>
      <td>Shim / Build</td>
    </tr>
    <tr>
      <td>Interrupt locks</td>
      <td><code class="language-plaintext highlighter-rouge">tx_interrupt_control</code></td>
      <td>Native</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SuspendOSInterrupts</code> (Category 1 stays alive)</td>
      <td>BASEPRI-based port</td>
      <td>Shim / Build</td>
    </tr>
    <tr>
      <td>Hooks, stack monitoring</td>
      <td><code class="language-plaintext highlighter-rouge">tx_application_define</code>, <code class="language-plaintext highlighter-rouge">tx_thread_stack_error_notify</code></td>
      <td>Native</td>
    </tr>
    <tr>
      <td>IOC, cross-core events</td>
      <td>Shared memory protocol, inter-core interrupts</td>
      <td>Build</td>
    </tr>
    <tr>
      <td>Spinlocks</td>
      <td>Hardware semaphores or exclusive accesses</td>
      <td>Build</td>
    </tr>
    <tr>
      <td>OS-Applications, MPU partitioning</td>
      <td>ThreadX Modules</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td>Trusted / non-trusted function calls</td>
      <td>Request queues across partitions</td>
      <td>Shim</td>
    </tr>
    <tr>
      <td>Timing protection</td>
      <td>Runtime accounting + monitor + watchdog</td>
      <td>Build</td>
    </tr>
  </tbody>
</table>

<p>The OS side holds up well: most of OSEK maps onto ThreadX natively or with a thin shim, and preemption-threshold turns out to be the secret weapon for internal resources and the priority ceiling. What’s left — cross-core communication, spinlocks, timing protection — is exactly the part that’s hard everywhere.</p>

<h2 id="next-up">Next up</h2>

<p>So far, the components have a place to run and a reason to wake up. What they don’t have yet is anybody to talk to. <a href="/system%20&amp;%20sw%20architecture/2026/10/21/running-autosar-on-threadx-part-2-rte-communication-and-bsw/">Part 2</a> builds the RTE communication on top of this execution model — sender-receiver, implicit access, client-server, modes — brings in the basic software, and ends with the honest bill: what you lose without the AUTOSAR toolchain, and how to win some of it back.</p>

<h2 id="sources-and-further-reading">Sources and further reading</h2>

<h3 id="standards-and-specifications">Standards and specifications</h3>

<ul>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_OS.pdf">AUTOSAR Classic Platform: Specification of Operating System, R23-11</a> — tasks, resources, alarms, schedule tables, spinlocks, OS-Applications, timing and service protection: the contract this post rebuilds.</li>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_RTE.pdf">AUTOSAR Classic Platform: Specification of RTE, R23-11</a> — runnables, RTE events and exclusive areas.</li>
  <li>ISO 17356-3:2005 — Road vehicles — Open interface for embedded automotive applications — Part 3: OSEK/VDX Operating System — the OSEK base AUTOSAR OS is built on.</li>
  <li><a href="https://github.com/eclipse-threadx/threadx">Eclipse ThreadX source code</a> and <a href="https://github.com/eclipse-threadx/rtos-docs">ThreadX documentation</a> — the API (threads, event flags, mutexes with priority inheritance, timers) used for the mapping.</li>
</ul>

<h3 id="books">Books</h3>

<ul>
  <li>Edward L. Lamie: <em>Real-Time Embedded Multithreading Using ThreadX</em>, 2nd ed., Newnes, 2009 — the ThreadX book, written by the people who built it.</li>
  <li>Edward Row: <em>Mastering Modern RTOS: Zephyr and ThreadX Internals, APIs, and Advanced Development</em> — ThreadX next to Zephyr, good for seeing which design choices are ThreadX-specific.</li>
  <li>Olaf Kindel, Mario Friedrich: <em>Softwareentwicklung mit AUTOSAR</em>, dpunkt.verlag, 2009 (German) — a practical walkthrough of the AUTOSAR OS and RTE concepts.</li>
  <li>Giorgio C. Buttazzo: <em>Hard Real-Time Computing Systems</em>, 3rd ed., Springer, 2011 — priority ceiling and priority inheritance, the theory behind resources and mutexes.</li>
</ul>

<p><em>The animations in this post are rendered with <a href="https://www.manim.community/">manim</a> from <a href="https://github.com/Kuebelrobert/kuebelrobert.github.io/blob/main/scripts/manim/autosar_threadx_scenes.py"><code class="language-plaintext highlighter-rouge">scripts/manim/autosar_threadx_scenes.py</code></a>.</em></p>]]></content><author><name>Robert</name></author><category term="System &amp; SW Architecture" /><category term="architecture" /><category term="autosar" /><category term="rte" /><category term="threadx" /><category term="rtos" /><category term="multicore" /><category term="migration" /><category term="asil" /><summary type="html"><![CDATA[This is Part 1 of a two-part post on running AUTOSAR components on ThreadX. This part covers the OS side: runnables, tasks, resources, alarms, interrupts, hooks, and everything multicore. Part 2 covers RTE communication, basic software, and what the whole exercise costs you.]]></summary></entry><entry><title type="html">Create the Foundation, Part 4: SWC Allocation and a First Task Setup</title><link href="https://kuebelrobert-dev.space/system%20&%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/" rel="alternate" type="text/html" title="Create the Foundation, Part 4: SWC Allocation and a First Task Setup" /><published>2026-10-19T00:00:00+00:00</published><updated>2026-10-19T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup</id><content type="html" xml:base="https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/"><![CDATA[<p><em>This is Part 4, the closing part of a series on laying the foundation for a new ECU project. <a href="/system%20&amp;%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking/">Part 1</a> covered requirements research and the thinking process; <a href="/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/">Part 2</a> covered memory mapping and partitioning; <a href="/system%20&amp;%20sw%20architecture/2026/09/04/create-the-foundation-part-3-protection-allocation-and-cpu-load/">Part 3</a> covered memory protection; this part covers SWC allocation, a first CPU budget and a first task setup.</em></p>

<p>So far, our BCU has a memory map the hardware can actually enforce and a protection concept that keeps QM away from ASIL B — cores, DMA, peripherals and all. What it doesn’t have yet is software in it. One simplex ASIL B core and five QM cores, all of them empty, all of them waiting for somebody to decide what goes where — and, just as important, <em>when</em> it runs.</p>

<p>That’s three decisions: which software component lives on which core, whether the CPU can actually carry it, and how each core’s work is cut into tasks. Let’s make all three, at least as a first draft you can defend in a review.</p>

<h2 id="swc-allocation-deciding-what-actually-lives-where">SWC allocation: deciding what actually lives where</h2>

<p>With the regions protected, the next question is which software component (SWC, in AUTOSAR terms) actually runs on which core, in which memory partition. This is a genuinely separate decision from the memory map — the map tells you what’s possible, allocation tells you what you’re actually doing.</p>

<p>The criteria I use, roughly in this order:</p>

<ol>
  <li><strong>ASIL classification first.</strong> An ASIL B SWC goes on the ASIL B core, full stop. This isn’t negotiable, and it’s the one criterion that overrides all the others below.</li>
  <li><strong>Communication locality second.</strong> SWCs that talk to each other constantly are cheaper to run on the same core (or at least the same trust domain) than SWCs that rarely interact — every cross-core call through the shared region has overhead, and every one of them is a chance to hit the MPU boundary we spent a whole part locking down. This applies within QM too: two QM SWCs that chat constantly are still cheaper on the same QM core than split across two different QM cores, even though there’s no MPU boundary between them — the inter-core overhead doesn’t care whether both sides are QM.</li>
  <li><strong>Timing criticality third.</strong> A SWC with a hard real-time deadline benefits from sharing a core with as few other time-sensitive SWCs as possible, to keep scheduling analysis tractable. Mixing five different periodic tasks with tight deadlines onto the same core “because there’s room” is how you end up debugging priority inversion at 2am.</li>
  <li><strong>Load balancing last, not first — but now it’s a real decision, not a formality.</strong> With a single ASIL B core, there was never a choice about where ASIL work goes. With five QM cores, there genuinely is a choice about how QM work splits across Core 1 through Core 5, and that choice is worth making deliberately. Resist flattening load evenly before the first three criteria are satisfied — a perfectly balanced allocation that violates ASIL separation, or splits a tightly coupled QM communication chain across two QM cores for no reason other than a nicer-looking spreadsheet, is not actually a good allocation.</li>
</ol>

<p>Write the allocation down as an actual table (SWC, ASIL level, core, rationale) rather than keeping it as tribal knowledge. You will revisit it more times than you expect, and “why is this here” is a much better question to answer from a table than from git blame on a config file from fourteen months ago.</p>

<h3 id="a-first-allocation-for-our-bcu">A first allocation for our BCU</h3>

<p>Time to follow my own advice. Part 1 gave us the function list (immobilizer, lighting, climate &amp; thermal, parking assist and the usual body stuff), four CAN FD channels, eight LIN channels, one Ethernet, diagnostics including OBD, and an update requirement. Let’s assume the HARA left us with exactly one function that needs ASIL B: the exterior lighting that other road users depend on — brake lights, hazard lights and low beam. Everything else is QM. (That’s an assumption, and like every assumption in this series it gets written down with a date. If the HARA comes back with a second ASIL function, this table is the first thing that changes.)</p>

<table>
  <thead>
    <tr>
      <th>Core</th>
      <th>Role</th>
      <th>SWCs</th>
      <th>ASIL</th>
      <th>Rationale</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Core 0</td>
      <td>Safety</td>
      <td>Exterior light safety (brake, hazard, low beam), hardwired brake pedal switch input, lamp output drivers and diagnosis, safety manager (fault reaction, safe state), E2E checks for ASIL-relevant signals, watchdog manager, self-tests, a small NVM handler for the ASIL B NVM block</td>
      <td>ASIL B</td>
      <td>Criterion 1. Also everything that <em>checks</em> the ASIL function lives here, not just the function itself.</td>
    </tr>
    <tr>
      <td>Core 1</td>
      <td>Communication</td>
      <td>CAN FD ×4, Ethernet, PduR/COM, SecOC, network management, gateway routing</td>
      <td>QM</td>
      <td>Criterion 2: every other core talks to the outside world through here, so routing stays on one core.</td>
    </tr>
    <tr>
      <td>Core 2</td>
      <td>LIN &amp; body</td>
      <td>LIN master ×8 with schedule tables, window lifters, mirrors, wipers, seats</td>
      <td>QM</td>
      <td>Criteria 2 and 3: the body functions mostly <em>are</em> LIN slaves, and LIN schedule tables want a quiet, predictable core.</td>
    </tr>
    <tr>
      <td>Core 3</td>
      <td>Comfort</td>
      <td>Climate &amp; thermal control, interior lighting, comfort functions</td>
      <td>QM</td>
      <td>Slow control loops with lots of calibration. Nobody here is in a hurry.</td>
    </tr>
    <tr>
      <td>Core 4</td>
      <td>Parking</td>
      <td>Parking assist: ultrasonic sensor timing, echo processing, distance warning</td>
      <td>QM</td>
      <td>Criterion 3: the one QM function with tight sensor timing gets a core mostly to itself.</td>
    </tr>
    <tr>
      <td>Core 5</td>
      <td>Services</td>
      <td>Diagnostics (UDS, DTC handling, OBD), NVM manager for the QM NVM block, immobilizer and HSM client, update manager</td>
      <td>QM</td>
      <td>Request-driven services with bursty load. Grouping them keeps their worst cases from colliding with anybody’s control loop.</td>
    </tr>
  </tbody>
</table>

<p>Two things worth pointing out. First, the ASIL B core is deliberately not “the core with the lighting on it”. It also hosts everything that supervises the rest: the safety manager, the watchdog manager and the self-tests from Part 3 (including the periodic readback of the MPU and firewall configuration). Second, the light <em>request</em> still arrives on Core 1, because that’s where the CAN FD stack lives. So the most safety-relevant signal in the whole ECU crosses the ASIL/QM boundary on its way in. We’ll come back to that below, because it decides a lot about the task setup.</p>

<h2 id="cpu-load-and-memory-consumption-the-reality-check-nobody-scheduled">CPU load and memory consumption: the reality check nobody scheduled</h2>

<p>Remember from Part 1 that hardware sized this microcontroller without knowing the actual CPU load? This is where that comes back to haunt you — or doesn’t, if you plan for it properly.</p>

<p>Before we cut anything into tasks, one sober look at the budget. At this stage you don’t have real measurements, because there’s no running SW yet to measure. What you do have is the ability to make an honest first assumption instead of an optimistic one:</p>

<ul>
  <li><strong>Base your first CPU budget on comparable past projects, not on this project’s wishful thinking.</strong> If a similar BCU generation ran at 60% load with a similar function set, “this one will surely be more efficient” is not a plan, it’s a hope. Start from the closest real data point you have.</li>
  <li><strong>Reserve real headroom, not token headroom.</strong> A common rule of thumb is to plan for 30-40% free CPU budget at this early stage, specifically because requirements will grow (they always do) and because interrupt load, communication overhead, and diagnostics tend to be underestimated across the board, not just by you.</li>
  <li><strong>Size stacks from worst-case call depth, not typical call depth.</strong> This one is memory, not CPU, but the same discipline applies: a stack sized for the common case and blown by the one deep, rare, interrupt-nested call path is a bug that shows up in the field, not in your unit tests.</li>
  <li><strong>Write the assumption down as an assumption, with a date.</strong> “We assumed X% load based on Y” is a sentence you want to be able to find again in six months, when the real measurements come in and either confirm you were reasonable or tell you exactly how wrong you were — and by how much.</li>
  <li><strong>Plan the first real measurement point explicitly.</strong> Don’t let “we’ll measure it once bring-up is further along” be the whole plan. Decide now which milestone will produce your first real CPU load number, so the assumption above has a scheduled expiration date instead of quietly becoming permanent.</li>
</ul>

<h2 id="a-first-task-setup">A first task setup</h2>

<p>An allocation tells you where things run. A task setup tells you when, and in which order, and what happens when something takes longer than planned. Get this one wrong and you can build a perfect spatial separation (Part 3) and still have QM interfere with ASIL — just through time instead of memory.</p>

<h3 id="the-rules-first">The rules first</h3>

<p>Before the tables, the rules the tables follow. These are the part I’d actually put into the architecture document:</p>

<ul>
  <li><strong>One partition per task, never mixed.</strong> A task runs either ASIL B code or QM code, never both. Part 3’s second experiment showed why: the MPU protects per task, not per function, so a QM runnable inside an ASIL task runs with ASIL permissions.</li>
  <li><strong>A small, harmonic set of periods: 1, 5, 10, 20 and 100 ms.</strong> Every period divides the next one, which keeps the scheduling analysis simple and lets cross-core data exchange line up on common boundaries. Somebody will ask for 50 ms. Somebody always does. It doesn’t divide 20, so it gets 20 or 100.</li>
  <li><strong>Rate-monotonic priorities.</strong> Shorter period, higher priority. The exceptions are event tasks for incoming communication, which sit just above the periodic task that consumes their data.</li>
  <li><strong>ISRs stay short and belong to the partition of their driver.</strong> An ISR does the minimum and hands over to a task. QM drivers’ ISRs run on QM cores only. Anything that needs microsecond timing (ultrasonic echoes, PWM edges) belongs in timer-capture hardware plus a short ISR, never in a task, not even a 1 ms one.</li>
  <li><strong>Hot code and stacks go into TCM.</strong> Each core’s tasks get their stacks and their most frequently executed code in that core’s own TCM (Part 2): single-cycle access, no crossbar contention, and much easier timing analysis. Shared SRAM is for data that has to be shared, not for convenience.</li>
  <li><strong>Cross-core data moves at task boundaries, without locks.</strong> The producer writes at the end of its task, the consumer reads at the start of its own, through the mailbox pattern from Part 3: single writer, sequence counter, CRC. No spinlock is ever shared between an ASIL and a QM task, because a QM task that holds a lock is a QM task that can stall the ASIL core.</li>
  <li><strong>Every task gets an execution budget, and somebody checks it.</strong> The OS’s timing protection (or a watchdog manager’s deadline supervision) checks that each ASIL task finishes within its budget and on time. The watchdog manager on Core 0 also watches a heartbeat from each QM core. A dead QM core means degraded function, not a safe state, and that difference is a written-down decision, as Part 3 demanded.</li>
  <li><strong>Load per core stays at or below about 60% for now, interrupts included.</strong> That’s the 30–40% headroom from above, applied per core instead of averaged across the chip. Averaging is how you hide one overloaded core behind five relaxed ones. The budgets in the tables below are for task bodies, so the interrupt load (CAN FD receive interrupts on Core 1 can be a lot) has to fit into the gap between the table total and 60%.</li>
  <li><strong>Utilization isn’t schedulability.</strong> 60% load says nothing about whether the 5 ms task still makes its deadline when everything above it fires at once. Once real execution times exist, a worst-case response-time analysis per core replaces the percentages. Until then, the percentages are a sanity check, not a proof.</li>
</ul>

<h3 id="core-0--asil-b">Core 0 — ASIL B</h3>

<table>
  <thead>
    <tr>
      <th>Task</th>
      <th>Trigger</th>
      <th>Priority</th>
      <th>Content</th>
      <th>Budget</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Init_Asil</code></td>
      <td>startup</td>
      <td>—</td>
      <td>MPU and firewall setup, startup self-tests, configuration CRCs</td>
      <td>—</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_Asil_1ms</code></td>
      <td>1 ms</td>
      <td>highest</td>
      <td>Lamp output monitoring (current feedback, open/short detection)</td>
      <td>8%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_Asil_5ms</code></td>
      <td>5 ms</td>
      <td>high</td>
      <td>Read brake pedal switch (debounced), read and E2E-check light requests, light arbitration, output commands</td>
      <td>15%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_Asil_10ms</code></td>
      <td>10 ms</td>
      <td>medium</td>
      <td>Safety manager, fault reaction, watchdog manager (alive, deadline and logical supervision)</td>
      <td>10%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_Asil_100ms</code></td>
      <td>100 ms</td>
      <td>low</td>
      <td>Periodic self-tests in slices: RAM test, MPU and firewall configuration readback</td>
      <td>12%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Idle</code></td>
      <td>background</td>
      <td>lowest</td>
      <td>Flash CRC in slices</td>
      <td>rest</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td> </td>
      <td><strong>Planned load</strong></td>
      <td><strong>45%</strong></td>
    </tr>
  </tbody>
</table>

<h3 id="core-1--communication-qm">Core 1 — Communication (QM)</h3>

<table>
  <thead>
    <tr>
      <th>Task</th>
      <th>Trigger</th>
      <th>Priority</th>
      <th>Content</th>
      <th>Budget</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_ComRx</code></td>
      <td>event (CAN FD / Ethernet Rx)</td>
      <td>highest</td>
      <td>Rx processing, SecOC verification, routing; writes ASIL-relevant signals for Core 0</td>
      <td>18%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_Com_5ms</code></td>
      <td>5 ms</td>
      <td>high</td>
      <td>COM and PduR main functions (Rx/Tx), transport protocol</td>
      <td>15%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_Com_10ms</code></td>
      <td>10 ms</td>
      <td>medium</td>
      <td>SecOC main function, Ethernet stack</td>
      <td>12%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Task_Com_100ms</code></td>
      <td>100 ms</td>
      <td>low</td>
      <td>Network management, gateway housekeeping</td>
      <td>5%</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td> </td>
      <td><strong>Planned load</strong></td>
      <td><strong>50%</strong></td>
    </tr>
  </tbody>
</table>

<h3 id="cores-2-to-5--the-other-qm-cores">Cores 2 to 5 — the other QM cores</h3>

<table>
  <thead>
    <tr>
      <th>Core</th>
      <th>Task</th>
      <th>Trigger</th>
      <th>Content</th>
      <th>Budget</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Core 2</td>
      <td><code class="language-plaintext highlighter-rouge">Task_Lin_5ms</code></td>
      <td>5 ms</td>
      <td>LIN schedule tables, all eight channels</td>
      <td>25%</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Task_Body_10ms</code></td>
      <td>10 ms</td>
      <td>Window lifters (incl. anti-pinch), mirrors, wipers</td>
      <td>15%</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Task_Body_100ms</code></td>
      <td>100 ms</td>
      <td>Seats, slow body functions</td>
      <td>5%</td>
    </tr>
    <tr>
      <td>Core 3</td>
      <td><code class="language-plaintext highlighter-rouge">Task_Comfort_20ms</code></td>
      <td>20 ms</td>
      <td>Interior lighting, comfort functions</td>
      <td>10%</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Task_Climate_100ms</code></td>
      <td>100 ms</td>
      <td>Climate &amp; thermal control loops</td>
      <td>25%</td>
    </tr>
    <tr>
      <td>Core 4</td>
      <td><code class="language-plaintext highlighter-rouge">Task_Park_1ms</code></td>
      <td>1 ms</td>
      <td>Ultrasonic burst sequencing (echo timing itself: timer capture + ISR)</td>
      <td>15%</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Task_Park_20ms</code></td>
      <td>20 ms</td>
      <td>Echo processing, distance calculation, warning output</td>
      <td>30%</td>
    </tr>
    <tr>
      <td>Core 5</td>
      <td><code class="language-plaintext highlighter-rouge">Task_Diag_10ms</code></td>
      <td>10 ms</td>
      <td>Diagnostics (UDS, DTC handling, OBD)</td>
      <td>20%</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Task_Svc_20ms</code></td>
      <td>20 ms</td>
      <td>Immobilizer, HSM jobs</td>
      <td>10%</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Task_Svc_100ms</code></td>
      <td>100 ms</td>
      <td>Update manager, NVM manager</td>
      <td>15%</td>
    </tr>
  </tbody>
</table>

<p>Planned load, Core 2: 45%. Core 3: 35%. Core 4: 45%. Core 5: 45%. Every number in these tables is an assumption from comparable projects, not a measurement — exactly the kind the CPU budget section asked you to write down with a date, so it can be proven wrong on schedule.</p>

<h3 id="one-signal-end-to-end-the-brake-light">One signal, end to end: the brake light</h3>

<p>Tables are nice, but a task setup only proves itself on a real signal path. Take the brake light. The request arrives from another ECU over CAN FD, and the lamp has to be on within (let’s assume) 50 ms of it. On top of that, the BCU reads the brake pedal switch directly, through a hardwired input on the ASIL B core. That second source isn’t decoration: it’s diverse (different sensor, different path, no software from another ECU involved) and it never crosses the ASIL/QM boundary at all.</p>

<ol>
  <li><strong>Core 1, <code class="language-plaintext highlighter-rouge">Task_ComRx</code>:</strong> the frame arrives, SecOC verifies it, and the signal is written into the shared workspace in SRAM Block 0, together with its E2E protection (sequence counter and CRC from the sending ECU). This is QM software writing data that ASIL will use — the amber cell from Part 3.</li>
  <li><strong>Core 0, <code class="language-plaintext highlighter-rouge">Task_Asil_5ms</code>:</strong> at the start of its next cycle, the ASIL task copies the signal, <em>then</em> checks the copy: E2E CRC, sequence counter, timeout. Only a valid copy goes into the light arbitration. An invalid or stale one is simply ignored, and the lamp follows the hardwired brake switch alone. Only if <em>both</em> sources are lost does the defined fallback kick in (for brake lights: assume braking, i.e. lamp on, which is the safe direction — and a DTC, so somebody actually finds out).</li>
  <li><strong>Core 0, <code class="language-plaintext highlighter-rouge">Task_Asil_5ms</code>:</strong> arbitration decides, and the output driver switches the lamp.</li>
  <li><strong>Core 0, <code class="language-plaintext highlighter-rouge">Task_Asil_1ms</code>:</strong> current monitoring confirms the lamp is actually on, or reports a defect.</li>
</ol>

<p>Worst case from “frame received” to “lamp commanded”: the Rx task’s response time, plus up to one 5 ms period of waiting for Core 0’s next cycle, plus the 5 ms task’s own execution — call it about 10 ms on the ECU, well inside the 50 ms. More importantly, the latency is <em>bounded by design</em>: it depends on two task periods, not on how busy the QM cores happen to be. If Core 1 hangs completely, the ASIL side doesn’t wait for it. It sees the timeout, keeps following the brake switch, and the watchdog manager reports the missing heartbeat. The car behind you never notices that half of your BCU just died, which is exactly the point.</p>

<p>That’s what the rules above buy you. The safety-relevant path crosses the ASIL/QM boundary exactly once, at a fixed point in time, through a buffer slot only Core 1 writes, and the ASIL side validates everything it reads. And for the one function that really matters, there’s a second path that doesn’t cross the boundary at all.</p>

<h3 id="what-this-setup-deliberately-leaves-open">What this setup deliberately leaves open</h3>

<p>A first draft that pretends to be final is worse than one that admits its gaps. Open for now:</p>

<ul>
  <li><strong>Real execution times.</strong> Every budget above is an estimate until the first measurement milestone from the CPU budget section.</li>
  <li><strong>Shared hardware.</strong> The flash controller, the DMA controllers and the crossbar are shared by all six cores. Their contention shows up as timing jitter, not as a fault, and it needs measuring, not guessing.</li>
  <li><strong>Startup order.</strong> Which core comes up first, who waits for whom, and how the ASIL core behaves while Core 1 is still initializing its communication stack. That deserves its own post.</li>
  <li><strong>The degradation concept.</strong> What exactly happens to each function when a QM core is lost. The table of “dead core X → what still works” is a safety and a product decision, not just an architecture one.</li>
</ul>

<h2 id="closing-out-the-foundation">Closing out the foundation</h2>

<p>Four parts ago, we started with nothing but a BCU, an ASIL B requirement, and a microcontroller whose CPU budget was decided by hardware without asking anyone. We turned that into a set of researched requirements, a memory map that traces back to every one of those requirements, an enforced protection scheme, a documented SWC allocation, an honest (if uncomfortable) first CPU budget, and a first task setup with one signal path traced end to end.</p>

<p>Is this foundation perfect? No — some of these assumptions will turn out wrong the moment real measurements show up. But it’s a foundation you can explain, defend, and deliberately revise, instead of one that quietly accumulated by accident while everyone was busy getting the first sample to blink an LED. That difference is the whole point of this series.</p>

<h2 id="sources-and-further-reading">Sources and further reading</h2>

<h3 id="standards-and-specifications">Standards and specifications</h3>

<ul>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_EXP_VFB.pdf">AUTOSAR Classic Platform: Virtual Functional Bus, R23-11</a> — why SWCs can be allocated to cores late in the project, at least on paper.</li>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_OS.pdf">AUTOSAR Classic Platform: Specification of Operating System, R23-11</a> — tasks, alarms, schedule tables and multicore.</li>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_RTE.pdf">AUTOSAR Classic Platform: Specification of RTE, R23-11</a> — runnables and how they are mapped onto tasks.</li>
  <li><a href="https://www.iso.org/standard/68388.html">ISO 26262-6:2018 — Product development at the software level</a> — freedom from interference in the timing domain.</li>
</ul>

<h3 id="papers">Papers</h3>

<ul>
  <li>C. L. Liu, James W. Layland: <a href="https://doi.org/10.1145/321738.321743"><em>Scheduling Algorithms for Multiprogramming in a Hard-Real-Time Environment</em></a>, Journal of the ACM 20(1), 1973 — the origin of rate-monotonic scheduling and its utilization bound.</li>
  <li>M. Joseph, P. Pandya: <a href="https://doi.org/10.1093/comjnl/29.5.390"><em>Finding Response Times in a Real-Time System</em></a>, The Computer Journal 29(5), 1986 — response-time analysis, the tool to check the task setup against its deadlines.</li>
</ul>

<h3 id="books">Books</h3>

<ul>
  <li>Giorgio C. Buttazzo: <em>Hard Real-Time Computing Systems</em>, 3rd ed., Springer, 2011 — the standard textbook for priority assignment, schedulability and resource sharing.</li>
  <li>Jane W. S. Liu: <em>Real-Time Systems</em>, Prentice Hall, 2000</li>
  <li>Hermann Kopetz: <em>Real-Time Systems: Design Principles for Distributed Embedded Applications</em>, 2nd ed., Springer, 2011 — time-triggered thinking, useful for the brake-light path end to end.</li>
  <li>Edward A. Lee, Sanjit A. Seshia: <a href="https://ptolemy.berkeley.edu/books/leeseshia/"><em>Introduction to Embedded Systems</em></a>, 2nd ed., MIT Press, 2017 (free online) — scheduling and multitasking chapters.</li>
</ul>]]></content><author><name>Robert</name></author><category term="System &amp; SW Architecture" /><category term="architecture" /><category term="project-foundation" /><category term="swc-allocation" /><category term="task-design" /><category term="rtos" /><category term="autosar" /><category term="asil" /><category term="cortex-m7" /><summary type="html"><![CDATA[This is Part 4, the closing part of a series on laying the foundation for a new ECU project. Part 1 covered requirements research and the thinking process; Part 2 covered memory mapping and partitioning; Part 3 covered memory protection; this part covers SWC allocation, a first CPU budget and a first task setup.]]></summary></entry><entry><title type="html">Create the Foundation, Part 3: Memory Protection — Turning the Map into an Enforced Boundary</title><link href="https://kuebelrobert-dev.space/system%20&%20sw%20architecture/2026/09/04/create-the-foundation-part-3-protection-allocation-and-cpu-load/" rel="alternate" type="text/html" title="Create the Foundation, Part 3: Memory Protection — Turning the Map into an Enforced Boundary" /><published>2026-09-04T00:00:00+00:00</published><updated>2026-09-04T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/09/04/create-the-foundation-part-3-protection-allocation-and-cpu-load</id><content type="html" xml:base="https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/09/04/create-the-foundation-part-3-protection-allocation-and-cpu-load/"><![CDATA[<p><em>This is Part 3 of a series on laying the foundation for a new ECU project. <a href="/system%20&amp;%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking/">Part 1</a> covered requirements research and the thinking process; <a href="/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/">Part 2</a> covered memory mapping and partitioning; this part covers memory protection — MPU, bus masters and peripherals; <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">Part 4</a> covers SWC allocation, a first CPU budget and a first task setup.</em></p>

<p>In <a href="/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/">Part 2</a>, we drew a memory map for our ASIL B BCU on our ARM Cortex-M7-based automotive MCU: flash split by criticality and update strategy, RAM split across one simplex ASIL B core and five independent QM cores, plus a standby region and a shared region for the things that genuinely need to cross the boundary. A memory map on its own is a diagram. What turns it into something you can actually put your safety case behind is three more decisions: how it’s enforced, whether the hardware can actually carry the load we’re about to put on it, and what runs where. This part covers the first one, and it’s big enough to deserve a part of its own. What runs where, and whether the hardware can carry it, follow in <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">Part 4</a>.</p>

<h2 id="whats-inside">What’s inside</h2>

<ul>
  <li>Who may execute whose code, and who may read and write whose data, as access matrices per core</li>
  <li>A worked example, ASIL B flash vs. the shared SRAM block, down to the MPU registers</li>
  <li>Why you run out of MPU regions faster than you expect, and the AUTOSAR version of the same problem</li>
  <li>Two experiments on an RTOS MPU port</li>
  <li>The doors the MPU doesn’t guard: DMA, other bus masters and peripherals</li>
</ul>

<section id="unlock" class="premium-unlock">
  <h2>Read the full article</h2>
  <p>The rest of this article is free, but not anonymous: tell me who you are and you get access to all members-only articles on this blog. No newsletter, no spam, no selling your data.</p>

  <p class="premium-unlock-msg" data-reason="required" hidden="">This article needs a quick unlock first.</p>
  <p class="premium-unlock-msg" data-reason="invalid" hidden="">Please enter a valid email address and accept the privacy policy.</p>
  <p class="premium-unlock-msg" data-reason="denied" hidden="">Access isn't available for this email address. If you think that's a mistake, <a href="mailto:kuebelroberttech@googlemail.com">send me a mail</a>.</p>

  <form method="post" action="https://kuebelrobert-dev.space/api/unlock">
    <input type="hidden" name="slug" value="create-the-foundation-part-3-protection-allocation-and-cpu-load" />
    <label>Email <span aria-hidden="true">*</span>
      <input type="email" name="email" required="" maxlength="254" autocomplete="email" />
    </label>
    <label>Name
      <input type="text" name="name" maxlength="100" autocomplete="name" />
    </label>
    <label>Company
      <input type="text" name="company" maxlength="100" autocomplete="organization" />
    </label>
    <label>Which topic interests you most?
      <textarea name="interest" rows="3" maxlength="500"></textarea>
    </label>
    <label class="premium-unlock-hp" aria-hidden="true">Website
      <input type="text" name="website" tabindex="-1" autocomplete="off" />
    </label>
    <label class="premium-unlock-consent">
      <input type="checkbox" name="consent" value="yes" required="" />
      I agree that my details are stored to grant me access, as described in the <a href="/privacy.html">privacy policy</a>. I can have them deleted at any time.
    </label>
    <button type="submit">Unlock</button>
  </form>
</section>

<style>
  .premium-unlock { margin-top: 2.5em; padding: 1.25em 1.5em; border: 1px solid currentColor; border-radius: 6px; }
  .premium-unlock form { display: grid; gap: 0.9em; }
  .premium-unlock label { display: grid; gap: 0.3em; }
  .premium-unlock input[type=email], .premium-unlock input[type=text], .premium-unlock textarea {
    font: inherit; padding: 0.45em 0.6em; border-radius: 4px; border: 1px solid #888; width: 100%; box-sizing: border-box;
  }
  .premium-unlock .premium-unlock-consent { display: flex; gap: 0.6em; align-items: flex-start; }
  .premium-unlock .premium-unlock-consent input { margin-top: 0.35em; }
  .premium-unlock .premium-unlock-hp { position: absolute; left: -10000px; }
  .premium-unlock button { justify-self: start; font: inherit; padding: 0.5em 1.4em; border-radius: 4px; cursor: pointer; }
  .premium-unlock-msg { font-weight: bold; }
</style>

<script>
  (function () {
    var reason = new URLSearchParams(location.search).get("unlock");
    var msg = reason && document.querySelector('.premium-unlock-msg[data-reason="' + reason + '"]');
    if (msg) msg.hidden = false;
  })();
</script>]]></content><author><name>Robert</name></author><category term="System &amp; SW Architecture" /><category term="architecture" /><category term="project-foundation" /><category term="memory-protection" /><category term="mpu" /><category term="dma" /><category term="freedom-from-interference" /><category term="autosar" /><category term="asil" /><category term="cortex-m7" /><summary type="html"><![CDATA[This is Part 3 of a series on laying the foundation for a new ECU project. Part 1 covered requirements research and the thinking process; Part 2 covered memory mapping and partitioning; this part covers memory protection — MPU, bus masters and peripherals; Part 4 covers SWC allocation, a first CPU budget and a first task setup.]]></summary></entry><entry><title type="html">Create the Foundation, Part 2: Memory Mapping and Partitioning</title><link href="https://kuebelrobert-dev.space/system%20&%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/" rel="alternate" type="text/html" title="Create the Foundation, Part 2: Memory Mapping and Partitioning" /><published>2026-09-03T00:00:00+00:00</published><updated>2026-09-03T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning</id><content type="html" xml:base="https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/"><![CDATA[<p><em>This is Part 2 of a series on laying the foundation for a new ECU project. <a href="/system%20&amp;%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking/">Part 1</a> covered requirements research and the thinking process; this part covers memory mapping and partitioning; <a href="/system%20&amp;%20sw%20architecture/2026/09/04/create-the-foundation-part-3-protection-allocation-and-cpu-load/">Part 3</a> covers memory protection — MPU, bus masters and peripherals; <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">Part 4</a> covers SWC allocation, a first CPU budget and a first task setup.</em></p>

<p>In <a href="/system%20&amp;%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking/">Part 1</a>, we picked our microcontroller: an ARM Cortex-M7-based automotive MCU, six cores at around 300 MHz, configured for our ASIL B project as one simplex ASIL B core plus five independent QM cores — instead of burning a core on lockstep redundancy we don’t need. We also gathered security (a root of trust), updateability (independently updatable blocks), communication (a ComStack reachable from multiple components), diagnostics (somewhere persistent to live), low power and startup time (an always-on ECU that has to talk early), and the rules of our memory protection hardware (8 or 16 MPU regions per core, each one a power of two, aligned to its own size) as requirements. None of that is a memory map yet. This is where we turn it into one.</p>

<h2 id="whats-inside">What’s inside</h2>

<ul>
  <li>How the flash gets split: immutable blocks first, then the updatable ones, with OTP, bootloaders, HSM partitions and reserved blocks that only exist so the MPU can do its job</li>
  <li>The full address-based flash map with sector sizes, generated from a YAML file that a script checks before it draws anything</li>
  <li>RAM for one simplex ASIL B core and five QM cores, plus the memory that has to survive a reset</li>
  <li>Where security, diagnostics, communication and startup time from Part 1 ended up in the map</li>
  <li>The requirements nobody writes down, and what’s still open for the next parts</li>
</ul>

<section id="unlock" class="premium-unlock">
  <h2>Read the full article</h2>
  <p>The rest of this article is free, but not anonymous: tell me who you are and you get access to all members-only articles on this blog. No newsletter, no spam, no selling your data.</p>

  <p class="premium-unlock-msg" data-reason="required" hidden="">This article needs a quick unlock first.</p>
  <p class="premium-unlock-msg" data-reason="invalid" hidden="">Please enter a valid email address and accept the privacy policy.</p>
  <p class="premium-unlock-msg" data-reason="denied" hidden="">Access isn't available for this email address. If you think that's a mistake, <a href="mailto:kuebelroberttech@googlemail.com">send me a mail</a>.</p>

  <form method="post" action="https://kuebelrobert-dev.space/api/unlock">
    <input type="hidden" name="slug" value="create-the-foundation-part-2-memory-mapping-and-partitioning" />
    <label>Email <span aria-hidden="true">*</span>
      <input type="email" name="email" required="" maxlength="254" autocomplete="email" />
    </label>
    <label>Name
      <input type="text" name="name" maxlength="100" autocomplete="name" />
    </label>
    <label>Company
      <input type="text" name="company" maxlength="100" autocomplete="organization" />
    </label>
    <label>Which topic interests you most?
      <textarea name="interest" rows="3" maxlength="500"></textarea>
    </label>
    <label class="premium-unlock-hp" aria-hidden="true">Website
      <input type="text" name="website" tabindex="-1" autocomplete="off" />
    </label>
    <label class="premium-unlock-consent">
      <input type="checkbox" name="consent" value="yes" required="" />
      I agree that my details are stored to grant me access, as described in the <a href="/privacy.html">privacy policy</a>. I can have them deleted at any time.
    </label>
    <button type="submit">Unlock</button>
  </form>
</section>

<style>
  .premium-unlock { margin-top: 2.5em; padding: 1.25em 1.5em; border: 1px solid currentColor; border-radius: 6px; }
  .premium-unlock form { display: grid; gap: 0.9em; }
  .premium-unlock label { display: grid; gap: 0.3em; }
  .premium-unlock input[type=email], .premium-unlock input[type=text], .premium-unlock textarea {
    font: inherit; padding: 0.45em 0.6em; border-radius: 4px; border: 1px solid #888; width: 100%; box-sizing: border-box;
  }
  .premium-unlock .premium-unlock-consent { display: flex; gap: 0.6em; align-items: flex-start; }
  .premium-unlock .premium-unlock-consent input { margin-top: 0.35em; }
  .premium-unlock .premium-unlock-hp { position: absolute; left: -10000px; }
  .premium-unlock button { justify-self: start; font: inherit; padding: 0.5em 1.4em; border-radius: 4px; cursor: pointer; }
  .premium-unlock-msg { font-weight: bold; }
</style>

<script>
  (function () {
    var reason = new URLSearchParams(location.search).get("unlock");
    var msg = reason && document.querySelector('.premium-unlock-msg[data-reason="' + reason + '"]');
    if (msg) msg.hidden = false;
  })();
</script>]]></content><author><name>Robert</name></author><category term="System &amp; SW Architecture" /><category term="architecture" /><category term="project-foundation" /><category term="memory-mapping" /><category term="partitioning" /><category term="flash-memory" /><category term="autosar" /><category term="asil" /><category term="cortex-m7" /><summary type="html"><![CDATA[This is Part 2 of a series on laying the foundation for a new ECU project. Part 1 covered requirements research and the thinking process; this part covers memory mapping and partitioning; Part 3 covers memory protection — MPU, bus masters and peripherals; Part 4 covers SWC allocation, a first CPU budget and a first task setup.]]></summary></entry><entry><title type="html">Create the Foundation, Part 1: Requirements Research and the Thinking Process</title><link href="https://kuebelrobert-dev.space/system%20&%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking/" rel="alternate" type="text/html" title="Create the Foundation, Part 1: Requirements Research and the Thinking Process" /><published>2026-08-30T00:00:00+00:00</published><updated>2026-08-30T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking</id><content type="html" xml:base="https://kuebelrobert-dev.space/system%20&amp;%20sw%20architecture/2026/08/30/create-the-foundation-part-1-requirements-and-thinking/"><![CDATA[<p><em>This is Part 1 of a series on laying the foundation for a new ECU project. Part 1 covers requirements research and the thinking process; <a href="/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/">Part 2</a> covers memory mapping and partitioning; <a href="/system%20&amp;%20sw%20architecture/2026/09/04/create-the-foundation-part-3-protection-allocation-and-cpu-load/">Part 3</a> covers memory protection — MPU, bus masters and peripherals; <a href="/system%20&amp;%20sw%20architecture/2026/10/19/create-the-foundation-part-4-swc-allocation-and-task-setup/">Part 4</a> covers SWC allocation, a first CPU budget and a first task setup.</em></p>

<p>Starting a new ECU project typically begins with compiling code for an A-Sample without much in the way of requirements — usually just the purpose of the ECU (BCU, Gateway, BMS, etc.) and, if you’re lucky, a derived ASIL (the automotive safety integrity level).</p>

<p>The first priority is getting a first commissioning of the individual parts — the microcontroller, the safety companion chip, sensors and actuators — via IO testing to confirm the PCB wiring is correct, setting up a test environment like a HIL, and taking first measurements on how the sensors and actuators behave in real life.</p>

<p>This means the top priority goes to hitting those goals, which often means neglecting the vital parts — memory layout, partitioning, and design patterns — that a project actually needs to succeed in the long run. That’s the first step into technical-debt hell: debt that, in reality, never gets paid back, because no customer is going to pay you to fix something they can’t see.</p>

<p>To use an analogy: imagine someone building a house without a detailed plan, knowing only that it will be a house — sometimes without even a stable foundation, just so they can install windows and doors first.</p>

<p>Every homeowner would go after the architect and construction company with a shovel if their house were built that way.</p>

<p>Especially in these modern days, where AI is spitting out more code than a human can read in a day!</p>

<p>So a solid foundation — even when the requirements aren’t 100% ready — might not be such a bad idea after all.</p>

<p>In most of the projects I’ve had the luck of contributing to, the SW stack was customizable, or built around something called a “platform”, so you were already able to start from a “solid” foundation. You take a project you (hopefully) already pushed somewhere safe, and start by forking or branching it for the upcoming project.</p>

<p>You start by removing everything that’s no longer needed and aligning the old project to the new hardware.</p>

<p>Modify the bring-up, change some IOs, remove the unwanted runnables, deactivate most of the safety features, and so on.</p>

<p>This approach speeds up getting a running SW on the new sample — as long as the foundation is robust and the architecture actually lets you do it (a topic for another post: clean architecture :)). The memory mapping usually gets adapted slightly, while the key principles behind the partitioning and memory protection concept get carried over as-is.</p>

<p>Wait a minute — didn’t I just say technical debt never gets paid back? Right. So yes, that debt is about to become part of your new architecture too. Yippee-ki-yay, brothertrucker, if you know what I mean.</p>

<p>Writing all this down, I’m starting to think SW projects work a lot like the real-world financial system :O.</p>

<p>So let’s forget that approach for the rest of this series, since not everyone has the luxury of just copying a disastrous project and trying to make it work :D. Let’s build one from scratch instead — properly, starting with the thinking, not the flashing.</p>

<h2 id="lay-out-the-foundation">Lay out the foundation</h2>

<p>Let’s assume you’re lucky enough to be the SW architect for a BCU (Body Control Unit) on an A-Sample, with a safety requirement to support ASIL B.</p>

<p>Typical functions include things like immobilizer, lighting, climate &amp; thermal, parking assist, and so on.</p>

<p>It’s an ARM Cortex-M7-based automotive MCU, in the same vein as the safety-oriented multi-core families several vendors now offer: six Cortex-M7 cores running at around 300 MHz, with a chip-level safety story that supports ISO 26262 up to ASIL D — <em>if</em> you pair two of the cores up in lockstep. That pairing isn’t fixed in silicon, it’s a configuration choice: you decide whether to sacrifice cores in pairs to buy hardware-checked redundancy, or run all six independently for more raw compute.</p>

<p>Our project only needs ASIL B. Lockstep buys you ASIL D-grade confidence at the cost of a whole core’s worth of throughput — and for ASIL B, that’s usually more insurance than you need. So the plan for this project is: one core runs simplex as our ASIL B core (software-diagnosed — periodic self-tests, ECC-protected memory, the usual toolbox — instead of a hardware-compared twin), and the other five run as independent QM cores. Same chip that could give us one ASIL-D-grade core and four spare, now gives us one ASIL-B-grade core and five QM cores instead. Over-qualified hardware for the software requirement, deliberately spent on throughput instead of on safety margin we don’t need — which, again, is a very normal, very common outcome of hardware being locked in before requirements are.</p>

<p>One warning before you celebrate the extra throughput: “simplex ASIL B with software diagnostics” is a much bigger decision than it sounds. ASIL B still demands real fault coverage for the CPU itself, and on a core without a lockstep twin, that coverage comes from a software test library that periodically exercises the core and costs CPU time on exactly the core you care most about. Worse, many MCU safety manuals simply <em>assume</em> lockstep for any ASIL use. So the first document to read isn’t the datasheet, it’s the chip’s safety manual and its assumptions of use: does the vendor support ASIL B on a simplex core at all, with which test library, and at what CPU cost? If the answer is “no”, your architecture just changed before it started.</p>

<p>Projected memory usage is sufficient, so no bottleneck there. Projected CPU load, however, is unknown — hardware, system or most likely the customer’s purchasing department made that decision on its own, without knowing the actual load (and let me assure you, that happens a lot more often than you’d think — we’ll come back to that particular gift later in this series).</p>

<p>Besides ASIL B as a safety goal, you have the usual standard requirements: security, communication (let’s assume for the sake of the example: four CAN FD, eight LIN and one Ethernet channel), OBD relevance, updateability, and some diagnostics. Nothing fancy, but still painful if you don’t consider it in the foundation.</p>

<p>Any further discussion about hardware changes is pointless — the schematic is frozen, the B-Sample is already in the making. That’s automotive for you.</p>

<p>So what’s the first thing you need to do, besides cancelling all vacation for the next three years, since “taskforce” is written all over this project?</p>

<p>You start by trying to get as much information as possible about further requirements, or details on the ones that already exist — but don’t waste time just waiting for answers.</p>

<p>You need to be bold: assume requirements where you have to, or just decide to design the project the way you think is right. I know that’s easier said than done, and experience helps, but if you don’t decide, deadlines will decide for you. I can assure you deadlines and technical debt are siblings in the project world.</p>

<p>So: gather as much information as you can within a defined time window, and then set the foundation. There’s usually no truly wrong decision, though of course some solutions work better than others. Either way, your team and your customer will thank you for it — and honestly, it’s also just your job!</p>

<p>Let’s take a closer look at what you actually need to research and decide before you can draw a single line of your memory map — because every one of these decisions ends up carved into it.</p>

<h2 id="security--secure-as-fort-knox-but-slow-and-resource-hungry">Security — secure as Fort Knox, but slow and resource-hungry</h2>

<p>Security is important, and every ECU has security goals. Clarify which security mechanisms you actually need to implement. Secure boot and authenticated boot have completely different impacts on startup behavior. Secure boot requires a root of trust, which means a handshake between the boot manager (or first-stage bootloader) and the security module. Think carefully about what’s really needed — the impact on your foundation can be severe, affecting memory mapping and startup time, just to name a couple.</p>

<h2 id="communication--the-first-hello-world-that-actually-matters">Communication — the first “hello world” that actually matters</h2>

<p>Communication is crucial for an ECU — without it, it’s just a piece of electronics. Clarify the security and safety requirements on your communication channels. SecOC and E2E protection are standard by now, so you already need to think about where to allocate your ComStack. You should also consider whether it’s worth splitting up your Com main functions to reduce CPU load. Keep in mind that you’ll need to route received signals into various components, so your memory protection scheme needs to be capable of handling that. Most ECUs need communication up and running very early during startup, so plan for that in your init sequence.</p>

<h2 id="updateability--ota-as-your-life-insurance">Updateability — OTA as your life insurance</h2>

<p>Let me start with the most important point: you need a solid plan for updating your SW throughout the development lifecycle. This is one of the biggest lessons I’ve taken away from the last few years. Of course, OTA isn’t the only way to update your SW, especially not in the early phase — but at some point, and usually earlier than you’d think, you’ll need to be updateable through the official update tools: OTA, an offboard tester, or similar. At the very beginning you might get away with a debugger, or maybe XCP or something proprietary — but plan this thoroughly, or you’ll be in real trouble. Cars are meant to drive, and once they’re driving, nobody wants to carry a debugger around just to update them.</p>

<p>Furthermore, updateability has a huge impact on your SW architecture. You need to consider integrity and authenticity mechanisms in the memory layout — a CRC check for integrity, or a proper signature for authenticity, which are two different things (a CRC won’t catch someone intentionally handing you the wrong image). You also have to consider which targets need to be updateable, like the application and the flash bootloader. If the application contains calibration data — which it usually does — you might need to split code from calibration. One hint I’ll already give you: think about how to divide your targets into updateable blocks. Depending on the microcontroller’s memory architecture, it can make sense to have more than two blocks that can be updated separately — just be aware that each block adds the overhead of being validated, and the risk of failing during the update.</p>

<p>And first and foremost, check whether you need to support an A/B swap mechanism, because that’s a completely different approach from simply invalidating the application and flashing the new one over the old one.</p>

<h2 id="diagnostics--your-debugger-once-the-hardware-is-out-in-the-wild">Diagnostics — your debugger once the hardware is out in the wild</h2>

<p>By diagnostics I mean both the OBD standard — determined by the legislation of whichever countries the car is sold in — and sensor or general ECU diagnostics that determine the system’s specific state. OBD can be quite CPU- and memory-intensive depending on the used OBD fault classes, since it demands a lot of features to be implemented. So always find out whether you really need OBD, which OBD fault classes you need to implement, or whether you have room to negotiate the requirements down. Sensor diagnostics, and especially general diagnostics, can be a lifesaver once you have problems in the field. Always consider diagnostic services, or at least DTCs, that let you capture important information about the ECU’s internal state before the problem occurred — you’ll need them if the problem is a sporadic one. OBD lets you define your own trouble codes, stored in a format that helps you pinpoint the error source. Clarify how you want to recover from diagnostic abnormalities — this is often already defined by the customer, but it’s still worth considering early, while you’re designing your internal state machine.</p>

<h2 id="low-power-and-startup-time--the-always-on-ecu">Low power and startup time — the always-on ECU</h2>

<p>A BCU is the ECU that never really sleeps. It has to wake up on a door handle, a key fob or a CAN message, it has to stay within a quiescent current budget measured in milliamps (sometimes less) while the car is parked for weeks, and it usually has to be talking on the bus within a few hundred milliseconds of waking up. Clarify early which wake sources you need, what has to survive a sleep (wake reason, reset reason, the last DTC), which core wakes up first, and whether partial networking is required. And put the startup time next to the security requirement: secure boot that verifies megabytes of application code before anything runs is great for security and terrible for “the bus expects an answer in 150 ms”. You want to discover that conflict on paper, not on the first wake-up measurement.</p>

<h2 id="memory-protection-hardware--the-requirement-hiding-in-the-datasheet">Memory protection hardware — the requirement hiding in the datasheet</h2>

<p>This one is on nobody’s requirement list, which is exactly why it belongs on yours. Every other topic above eventually ends up as a region in your memory map, and every one of those regions will eventually need to be <em>enforced</em> — by the MPU (or MMU) of each core that can reach it. So before you draw a single line, find out what shape of region your protection hardware actually accepts:</p>

<ul>
  <li><strong>How many regions per core?</strong> A Cortex-M7 typically gives you 8 or 16 MPU regions per core. That’s not per memory type, that’s <em>total</em> — flash, TCM, SRAM, standby RAM and peripherals all come out of the same handful of slots.</li>
  <li><strong>What does a region have to look like?</strong> On the classic ARMv7-M MPU — the one a Cortex-M7 has — a region’s size must be a power of two, and its base address must be aligned to that size. A 2 MB region can start at 0x01200000, but not at 0x01098000. A 1984 KB block is not one region at all; at an unlucky address it’s six. (The newer Armv8-M MPU uses base/limit registers with 32-byte granularity and is far more forgiving — know which one you’ve got before you plan around either.)</li>
  <li><strong>What doesn’t the MPU see?</strong> A core’s MPU only checks that core’s own loads, stores and instruction fetches. DMA controllers, the HSM and other bus masters go around it, so those boundaries need a firewall at the crossbar or memory-controller level instead. Find out whether your chip has one, which masters it can tell apart (per DMA channel or only per DMA controller?), and its own granularity, alignment rules and number of rule slots, which are often coarser and fewer than the MPU’s.</li>
  <li><strong>What are the flash sector sizes, and which flash blocks can work in parallel?</strong> A boundary that doesn’t sit on a sector boundary can’t be erased on its own, and that breaks the independently updatable blocks you just asked for under updateability. One level up, check read-while-write: most flash can only be erased or programmed while you execute from a <em>different</em> physical block. If you want to update in the background while the car keeps working, your memory map has to follow those block boundaries too.</li>
</ul>

<p>These are cheap constraints to respect while the memory map is still a blank page, and very expensive ones to retrofit: once the first software is out in the field, moving a region boundary means breaking compatibility with every update that’s already out there. Put them on your list now, next to security and updateability, and treat them as requirements like everything else here.</p>

<p>These are only some of the considerations worth thinking about. A real ECU project is, of course, far more complex — I just wanted to share some thoughts based on what I’ve experienced so far.</p>

<h2 id="where-this-leaves-us">Where this leaves us</h2>

<p>By now you should have a rough, honestly-assumed-where-necessary picture of your security approach, your communication needs, your updateability strategy, your diagnostic requirements, your power and startup budget, and the rules your memory protection hardware plays by. That’s the input. None of it is memory layout yet — but every one of those six topics is about to become a constraint on it: security wants a protected root of trust region, updateability wants your flash split into independently updatable blocks, communication wants buffer space it can reach from multiple components, diagnostics wants somewhere persistent to write down what went wrong, low power wants memory that survives a sleep and a boot path short enough to wake up fast, and the MPU wants every region it will ever enforce to be a power of two, aligned to its own size.</p>

<p>In <a href="/system%20&amp;%20sw%20architecture/2026/09/03/create-the-foundation-part-2-memory-mapping-and-partitioning/">Part 2</a>, we’ll take exactly this BCU example and actually draw the memory map — flash layout, RAM partitioning across the ASIL B core and the five QM cores, and the reasoning behind where each of today’s requirements ends up living.</p>

<p>Meanwhile, your colleagues are already fighting their first battles with bring-up SW — because getting the chance to debug on real hardware is a game changer, and nobody’s going to wait for your memory map to be perfect before they start.</p>

<h2 id="sources-and-further-reading">Sources and further reading</h2>

<h3 id="standards-and-specifications">Standards and specifications</h3>

<ul>
  <li><a href="https://www.iso.org/standard/68383.html">ISO 26262-1:2018 — Road vehicles — Functional safety — Part 1: Vocabulary</a> — where ASIL, QM and “freedom from interference” are defined.</li>
  <li>ISO/SAE 21434:2021 — Road vehicles — Cybersecurity engineering — the frame for the security requirements and the TARA.</li>
  <li><a href="https://unece.org/transport/documents/2021/03/standards/un-regulation-no-156-software-update-and-software-update">UN Regulation No. 156 — Software update and software update management system</a> — why updateability is no longer optional.</li>
  <li>ISO 14229-1:2020 — Unified diagnostic services (UDS) — the diagnostic protocol behind the “debugger in the wild”.</li>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_EXP_LayeredSoftwareArchitecture.pdf">AUTOSAR Classic Platform: Layered Software Architecture, R23-11</a> — a good map of which basic software module ends up owning which of the requirements above.</li>
  <li><a href="https://developer.arm.com/documentation/ddi0403/latest">Arm: Armv7-M Architecture Reference Manual (DDI 0403)</a> and <a href="https://developer.arm.com/documentation/ddi0553/latest">Armv8-M Architecture Reference Manual (DDI 0553)</a> — the MPU rules that become memory-map requirements.</li>
  <li><a href="https://developer.arm.com/documentation/ddi0489/latest">Arm: Cortex-M7 Technical Reference Manual (DDI 0489)</a></li>
</ul>

<h3 id="books">Books</h3>

<ul>
  <li>Len Bass, Paul Clements, Rick Kazman: <em>Software Architecture in Practice</em>, 4th ed., Addison-Wesley, 2021 — quality attributes and how to turn vague wishes into requirements you can design against.</li>
  <li>Jörg Schäuffele, Thomas Zurawka: <em>Automotive Software Engineering</em>, 2nd ed., Springer Vieweg, 2016 — the automotive development process from requirements to series production.</li>
  <li>Hans-Leo Ross: <em>Functional Safety for Road Vehicles</em>, Springer, 2016 — ISO 26262 explained by someone who has to apply it.</li>
  <li>Joseph Yiu: <em>The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors</em>, 3rd ed., Newnes, 2013 — the most readable introduction to the Cortex-M MPU, exceptions and low-power modes.</li>
  <li>Elecia White: <em>Making Embedded Systems</em>, 2nd ed., O’Reilly, 2024</li>
</ul>]]></content><author><name>Robert</name></author><category term="System &amp; SW Architecture" /><category term="architecture" /><category term="project-foundation" /><category term="technical-debt" /><category term="security" /><category term="communication" /><category term="updateability" /><category term="diagnostics" /><category term="autosar" /><category term="cortex-m7" /><summary type="html"><![CDATA[This is Part 1 of a series on laying the foundation for a new ECU project. Part 1 covers requirements research and the thinking process; Part 2 covers memory mapping and partitioning; Part 3 covers memory protection — MPU, bus masters and peripherals; Part 4 covers SWC allocation, a first CPU budget and a first task setup.]]></summary></entry><entry><title type="html">Garbage Collection - friend or enemy</title><link href="https://kuebelrobert-dev.space/flashing/2026/08/29/garbage-collection-friend-or-enemy/" rel="alternate" type="text/html" title="Garbage Collection - friend or enemy" /><published>2026-08-29T00:00:00+00:00</published><updated>2026-08-29T00:00:00+00:00</updated><id>https://kuebelrobert-dev.space/flashing/2026/08/29/garbage-collection-friend-or-enemy</id><content type="html" xml:base="https://kuebelrobert-dev.space/flashing/2026/08/29/garbage-collection-friend-or-enemy/"><![CDATA[<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>

<script>mermaid.initialize({ startOnLoad: true, theme: "dark" });</script>

<p>Garbage collection or in short GC is a mechanism that takes care of “cleaning up” old, invalidated flash data so new data can be written. Before garbage collection can step into action, I need to explain why we need this clean-up at all.</p>

<p>A car stores values like operating-hour counters, mileage, or calibration offsets for sensors inside its data flash. Over the lifecycle of the car, these values change from time to time — due to firmware updates or the recalibration of sensor data. They are typically required to be non-volatile, meaning they need to survive the ECU going into a power-down.</p>

<p>RAM cells normally lose their content once power is removed, while flash is able to retain it even after the whole microcontroller is without supply. There are several types of non-volatile memory, but I’ll focus on NOR flash here, since it’s the type most commonly built directly into automotive microcontrollers — as opposed to NAND flash, which you’ll more commonly find in mass-storage devices like SD cards or SSDs.</p>

<p>To change a value in flash, you can’t just overwrite it: you always have to erase the content first and then write the actual value. Erasing happens at the granularity of a whole sector, not a single byte, and it’s this erase-before-write property that causes most of the fun described below.</p>

<p>Repeated erase/write cycles wear a flash cell out over time — every cell has a limited number of program/erase cycles it can survive (its <em>endurance</em>) before it becomes unreliable, which in turn affects how long it can still reliably hold data (its <em>data retention</em>). To keep this wear evenly distributed across the memory, software needs to make sure each cell is written roughly the same number of times. This is called wear leveling, and it’s typically done by not always overwriting the same cell but instead writing consecutively into the next available cells.</p>

<p>Since this strategy leaves behind a trail of stale, invalidated data every time it writes to a new cell, someone eventually needs to clean up that mess. That someone is garbage collection: it waits until the amount of garbage reaches a certain threshold, and then reclaims space by copying the still-valid content into a new, clean sector before erasing the old one.</p>

<div class="mermaid">
flowchart LR
    subgraph Before["1. Before GC"]
        direction TB
        A1["Sector A (active)<br />Valid + invalid data mixed"]
        A2["Sector B (clean)<br />Empty, standby"]
    end
    subgraph During["2. During GC"]
        direction TB
        B1["Sector A (active)<br />Still readable, not yet erased"]
        B2["Sector B (filling)<br />Valid entries copied over"]
    end
    subgraph After["3. After GC"]
        direction TB
        C1["Sector A (erased)<br />New clean standby"]
        C2["Sector B (active)<br />Only valid data remains"]
    end
    Before --&gt; During --&gt; After
</div>

<p><em>Figure 1: A garbage collection pass doesn’t clean a sector in place — it copies the still-valid data into the clean standby sector, flips which sector is “active,” and only then erases the old one.</em></p>

<p>And just like cleaning up your own room, garbage collection takes time — and needs to be scheduled with some sense of priority. You also really don’t want to get interrupted in the middle of that cleaning process, since an interruption during an erase can leave you with neither a valid old copy nor a complete new one.</p>

<p>Now we know the why, and so far it sounds like a pretty favorable service. So where’s the problem?</p>

<p>Updating software is a crucial part of cars and their ECUs — the same goes for IoT devices, mobile phones, and so on. The problem can occur when you’re in the middle of a flashing sequence and you hit the garbage collection threshold at the same time.</p>

<p>Do you want to wait until garbage collection is finished? Is your system stable enough to deal with that? And aren’t you violating some timing requirement by waiting on garbage collection in the first place?</p>

<p>Now here is where good architecture needs to earn its paycheck.</p>

<h2 id="a-concrete-example">A concrete example</h2>

<p>Imagine an application that’s split into independently updatable blocks — say, one block that’s purely code (functions and constants), and another that holds only the calibration data for those functions. The overall system consists of a boot manager, a flash bootloader, and the application itself. A typical flashing sequence then looks like this:</p>

<ol>
  <li>The boot manager notices a pending flash request.</li>
  <li>It jumps into the flash bootloader.</li>
  <li>The flash bootloader flashes the application block by block.</li>
  <li>After each block, it writes a progress marker into NVM — so that if a supply glitch causes a reset mid-sequence, the system knows exactly where it left off instead of guessing.</li>
</ol>

<div class="mermaid">
sequenceDiagram
    participant BM as Boot Manager
    participant BL as Flash Bootloader
    participant APP as Application Blocks
    participant NVM as NVM / Flash

    BM-&gt;&gt;BM: Detect pending flash request
    BM-&gt;&gt;BL: Jump to Flash Bootloader
    loop For each application block
        BL-&gt;&gt;APP: Flash block
        BL-&gt;&gt;NVM: Write progress marker
        NVM--&gt;&gt;BL: GC trigger? (maybe)
    end
    BL-&gt;&gt;BM: Flashing complete, jump back
</div>

<p><em>Figure 2: Every block write ends with a progress marker in NVM — and every one of those writes is a place where garbage collection can decide to join the party.</em></p>

<p>Every single actor in that chain — boot manager, bootloader, application — can write to NVM, and every one of those writes is a potential GC trigger. Which means the one operation you added specifically to make flashing <em>safer</em> (the progress marker) is also the one most likely to invite the process that can make flashing <em>slower</em>, right when you can least afford it.</p>

<h2 id="what-you-can-actually-do-about-it">What you can actually do about it</h2>

<p>“Just suppress GC during flashing” is the answer everybody reaches for first, and it’s not wrong — but it’s not free either. If you defer every GC trigger for the full duration of a flash sequence, the garbage keeps piling up right up until the moment you release the brakes again. Do that often enough, or make your flash sequence long enough, and you can walk straight into the situation GC was invented to prevent: no clean sector left to write into.</p>

<p>A pattern that works better in practice is to give GC a <em>budget</em> instead of a blanket ban. Between block writes — at defined, predictable points in the flash sequence — check how full your garbage backlog is. If you’re comfortably below your threshold, defer and keep flashing. If you’re getting close to running out of clean sectors, spend a bounded amount of time letting GC catch up right there, before it becomes an emergency. That way GC still runs during flashing, just on your terms and inside a time slot you’ve already accounted for in your timing budget — instead of showing up uninvited in the middle of the block you least expected.</p>

<p>Whatever strategy you pick, don’t rely on the happy path holding up in the field. Always keep timeouts and fail-safe mechanisms in place, so that even if GC is allowed to run during flashing and the flashing itself fails, you can still fall back to a safe, previously validated backup image.</p>

<h2 id="the-decision-written-down-properly">The decision, written down properly</h2>

<p>Everything above is the reasoning. If you’d rather hand the actual decision to a coding agent instead of re-deriving it from paragraphs — or just want something you can skim in thirty seconds instead of read — I’ve written it up as a compact Architecture Decision Record: <a href="https://github.com/Kuebelrobert/kuebelrobert.github.io/blob/main/_adr/0001-gc-budget-during-flash-sequences.md"><code class="language-plaintext highlighter-rouge">ADR-0001: Budget garbage collection during flash sequences</code></a>. It also answers a question that comes up the moment you’re building this inside a platform instead of a single product: does GC budgeting even belong at the platform level, or is it product-specific plumbing? (Short version: platform — every derivative on the same NVM/FEE stack hits the identical problem, and nothing about the fix is product business logic.) The ADR stays short on purpose — a decision flowchart plus one fenced, structured block (interface, tunables, acceptance criteria) that an AI coding agent can work from directly, instead of paragraphs it would have to reverse-engineer first. Notice what it still doesn’t do: hand you a specific threshold percentage or budget-in-milliseconds as if I’d already decided it for you. Those stay <code class="language-plaintext highlighter-rouge">calibration_required: true</code> — they depend on your sector size, your GC cost, and your actual target.</p>

<h2 id="weak-spots-to-check-in-your-own-architecture">Weak spots to check in your own architecture</h2>

<p>If you’re starting a project, these are the questions worth answering before GC and flashing collide on you for real, and not on a whiteboard:</p>

<ul>
  <li>Timing budget: can you afford a GC run happening during flashing at all, or does it blow your update time budget?</li>
  <li>Field vs. production: field devices need to survive a worst case gracefully, but don’t forget production — the line wants deterministic, repeatable flash timing just as much, if not more.</li>
  <li>Suppression: can you actually suppress garbage collection during flashing, and if so, for how long before the backlog becomes a problem?</li>
  <li>Fallback: do you have timeouts and fail-safe mechanisms so that a failed flash — with or without GC involved — still leaves you on a validated backup image, not a brick?</li>
  <li>Lifecycle: have you planned GC handling early enough to cover different target versions, especially the ones that may no longer be field-updatable after they’ve shipped?</li>
</ul>

<p>There are many more things to consider, but I hope this gives you some awareness of the dangers — and the potential — of designing your system around the health of your data flash, so you can be bulletproof the day GC decides it wants to compete with your delivery date.</p>

<h2 id="sources-and-further-reading">Sources and further reading</h2>

<h3 id="specifications">Specifications</h3>

<ul>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_FlashEEPROMEmulation.pdf">AUTOSAR Classic Platform: Specification of Flash EEPROM Emulation (Fee), R23-11</a> — the module that owns garbage collection in an AUTOSAR stack, and where the “when is GC allowed to run” question officially lives.</li>
  <li><a href="https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_NVRAMManager.pdf">AUTOSAR Classic Platform: Specification of NVRAM Manager (NvM), R23-11</a> — the layer above Fee: block management, write-all on shutdown, and why your shutdown time suddenly depends on flash.</li>
</ul>

<h3 id="articles-and-papers">Articles and papers</h3>

<p>A few sources on flash garbage collection, wear leveling, and AUTOSAR’s NVM stack that go deeper into the mechanics touched on above:</p>

<ul>
  <li><a href="https://www.swissbit.com/en/blog/post/Explained-Garbage-Collection/">Explained: Garbage Collection — Swissbit</a></li>
  <li><a href="https://dev.to/kumar001repo/fundamentals-of-nvm-in-autosar-part-1-1620">Fundamentals of NVM in AutoSAR Part-1 — dev.to</a></li>
  <li><a href="https://www.embeddedtutor.com/2020/05/flash-eeprom-emulation-fee-in-autosar.html">Flash EEPROM Emulation (FEE in AUTOSAR) — embeddedtutor.com</a></li>
  <li><a href="https://community.infineon.com/t5/Knowledge-Base-Articles/AURIX-MCU-Avoiding-GC-during-FEE-boot-up-in-TC3xx-MCAL-KBA236204/ta-p/369740">AURIX MCU: Avoiding GC during FEE boot up in TC3xx MCAL — Infineon Developer Community</a></li>
  <li><a href="https://community.infineon.com/t5/Knowledge-Base-Articles/AURIX-MCU-Garbage-collection-in-FEE-MCAL-driver/ta-p/371131">AURIX MCU: Garbage collection in FEE MCAL driver — Infineon Developer Community</a></li>
  <li><a href="https://www.futurescope.co/wear-leveling-and-garbage-collection-in-ssd/">What is Wear Leveling and Garbage Collection in SSDs? — FutureScope</a></li>
  <li><a href="https://ieeexplore.ieee.org/document/7043841/">Garbage collection and wear leveling for flash memory: Past and future — IEEE Xplore</a></li>
  <li>Eran Gal, Sivan Toledo: <a href="https://doi.org/10.1145/1089733.1089735"><em>Algorithms and Data Structures for Flash Memories</em></a>, ACM Computing Surveys 37(2), 2005 — the classic survey on log-structured flash storage, wear leveling and reclamation.</li>
  <li>Michael Nygard: <a href="https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions"><em>Documenting Architecture Decisions</em></a>, 2011 — the blog post that started the ADR format used for ADR-0001 above. More templates at <a href="https://adr.github.io/">adr.github.io</a>.</li>
</ul>

<h3 id="books">Books</h3>

<ul>
  <li>Rino Micheloni, Luca Crippa, Alessia Marelli: <em>Inside NAND Flash Memories</em>, Springer, 2010 — NAND rather than NOR, but the best explanation of why cells wear out, what retention really means and why erase is so expensive.</li>
  <li>Elecia White: <em>Making Embedded Systems</em>, 2nd ed., O’Reilly, 2024 — has a solid chapter on memory and on designing for updates in the field.</li>
  <li>Jörg Schäuffele, Thomas Zurawka: <em>Automotive Software Engineering</em>, 2nd ed., Springer Vieweg, 2016 — the big picture of where flashing, production and the vehicle lifecycle meet.</li>
</ul>]]></content><author><name>Robert</name></author><category term="Flashing" /><category term="garbage-collection" /><category term="flash-memory" /><category term="wear-leveling" /><category term="nvm" /><category term="firmware-update" /><summary type="html"><![CDATA[]]></summary></entry></feed>