-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnextbsd-boot-method-detection.html
More file actions
251 lines (198 loc) · 28.7 KB
/
Copy pathnextbsd-boot-method-detection.html
File metadata and controls
251 lines (198 loc) · 28.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NextBSD — how an installer should decide what kind of boot to install</title>
<style>
:root { --fg:#1a1a1a; --fg-muted:#555; --bg:#fafaf7; --accent:#b8472a; --accent-soft:#f3e7df; --border:#d8d4c8; --code-bg:#f0ece2; --table-stripe:#f4efe5; --warn:#8a5a00; --good:#2d6f3b; --bad:#a23030; }
* { box-sizing:border-box; }
body { font-family:-apple-system,BlinkMacSystemFont,"Helvetica Neue",Helvetica,sans-serif; color:var(--fg); background:var(--bg); line-height:1.55; margin:0; padding:0; }
.wrap { max-width:880px; margin:0 auto; padding:48px 32px 96px; }
h1 { font-size:2.05rem; line-height:1.2; margin:0 0 8px; letter-spacing:-0.01em; }
h2 { font-size:1.4rem; margin:54px 0 12px; padding-top:18px; border-top:2px solid var(--border); }
h3 { font-size:1.12rem; margin:30px 0 10px; color:var(--accent); }
h4 { font-size:0.98rem; margin:20px 0 8px; }
p { margin:0 0 14px; }
ul,ol { margin:0 0 14px 22px; padding:0; } li { margin:0 0 6px; }
code { font-family:"SF Mono",Menlo,Consolas,monospace; font-size:0.9em; background:var(--code-bg); padding:1px 5px; border-radius:3px; }
pre { font-family:"SF Mono",Menlo,Consolas,monospace; font-size:0.84em; line-height:1.5; background:var(--code-bg); border:1px solid var(--border); border-radius:4px; padding:14px 16px; overflow-x:auto; margin:0 0 14px; }
pre code { background:none; padding:0; }
.lede { font-size:1rem; color:var(--fg-muted); margin:0 0 28px; }
.meta { font-size:0.85rem; color:var(--fg-muted); margin:0 0 24px; }
table { border-collapse:collapse; width:100%; margin:12px 0 22px; font-size:0.92rem; }
th,td { text-align:left; padding:9px 12px; border:1px solid var(--border); vertical-align:top; }
th { background:var(--accent-soft); font-weight:600; }
tr:nth-child(even) td { background:var(--table-stripe); }
.scroll { overflow-x:auto; }
.callout { border-left:3px solid var(--accent); background:var(--accent-soft); padding:14px 18px; margin:18px 0 22px; border-radius:0 4px 4px 0; }
.callout p:last-child { margin-bottom:0; }
.callout-warn { border-left-color:var(--warn); background:#fbf3df; }
.callout-good { border-left-color:var(--good); background:#e8f1e3; }
.callout-bad { border-left-color:var(--bad); background:#f7e6e6; }
.pill { display:inline-block; font-size:0.78rem; font-weight:600; text-transform:uppercase; letter-spacing:0.04em; padding:2px 8px; border-radius:10px; margin-right:8px; }
.pill-good { background:#d6ead0; color:var(--good); } .pill-warn { background:#f4dfbf; color:var(--warn); } .pill-bad { background:#f0c8c8; color:var(--bad); } .pill-neutral { background:#ddd; color:#333; }
.toc { background:white; border:1px solid var(--border); border-radius:4px; padding:18px 24px 14px 36px; margin:0 0 36px; font-size:0.95rem; }
.toc h2 { margin:0 0 8px; padding-top:0; border-top:none; font-size:1rem; text-transform:uppercase; letter-spacing:0.04em; color:var(--fg-muted); margin-left:-14px; }
.toc ol { margin:0 0 0 6px; } .toc li { margin-bottom:4px; }
.toc a { color:var(--fg); text-decoration:none; } .toc a:hover { text-decoration:underline; }
.back { font-size:0.9rem; margin-bottom:18px; } .back a { color:var(--accent); text-decoration:none; }
.footnote { font-size:0.85rem; color:var(--fg-muted); border-top:1px solid var(--border); margin-top:48px; padding-top:16px; }
.cite { font-size:0.82em; color:var(--fg-muted); }
.opt { border:1px solid var(--border); border-radius:4px; padding:2px 22px 6px; margin:0 0 26px; background:white; }
.opt h3 { margin-top:22px; }
.verdict { font-weight:600; }
.pill-meas { background:#cfe3f0; color:#1d5a7a; }
</style>
</head>
<body>
<div class="wrap">
<p class="back"><a href="index.html">← Back</a> · part of <a href="nextbsd-research.html">NextBSD Research</a> · sub-plan of <a href="nextbsd-rpi500-native-boot-plan.html">the Pi 500+ native-boot plan</a></p>
<h1>How an installer should decide what kind of boot to install</h1>
<p class="lede">NextBSD’s installer writes GPT with an EFI System Partition, unconditionally. That is unbootable on a Raspberry Pi, whose firmware reads <code>config.txt</code> off a FAT partition and enters the kernel directly — no UEFI, no BIOS, no loader of any kind. This page surveys every way of telling those cases apart, says what each signal actually proves, and picks one. Written after four attempts at the detection got it wrong in four different ways, then revised twice more when the research contradicted the result.</p>
<div class="callout callout-warn">
<p><span class="pill pill-warn">Why this page exists</span> The first four attempts were each plausible and each broken: matching a device-tree compatible string (the tool prints hex unless asked for text, and then truncates at the first NUL); testing <code>machdep.efi_map</code> (<strong>does not exist on arm64</strong>); treating “has a device tree” as “firmware booted us” (<code>loader.efi</code> installs a DTB too); and mounting candidate filesystems to look for <code>config.txt</code> (intrusive, and it broke three ways). Picking from a survey costs an afternoon. Picking without one cost a day and would have shipped a bug that bricks every arm64 VM install.</p>
</div>
<h2 id="cases">1. The cases, and why there are more than two</h2>
<p>An installer has to produce a disk the machine’s firmware can actually start. There are three distinct arrangements, not two, and a fourth hybrid that catches people out.</p>
<table>
<thead><tr><th>Case</th><th>What starts the OS</th><th>What must be on disk</th></tr></thead>
<tbody>
<tr><td><strong>Legacy BIOS</strong> (x86)</td><td>MBR bootstrap → <code>gptboot</code> → loader</td><td>GPT + a <code>freebsd-boot</code> partition holding <code>pmbr</code>/<code>gptboot</code></td></tr>
<tr><td><strong>UEFI</strong> (x86, arm64)</td><td>firmware → <code>BOOTAA64.EFI</code> → loader</td><td>GPT + an ESP (type <code>c12a7328-…</code>)</td></tr>
<tr><td><strong>Firmware direct</strong> (Pi)</td><td>EEPROM bootloader reads <code>config.txt</code>, enters the kernel at EL2</td><td>MBR + FAT32 type <code>0x0c</code> holding <code>config.txt</code>, a DTB, and the kernel</td></tr>
<tr><td><strong>UEFI on a Pi</strong> (EDK2)</td><td>EEPROM loads <code>RPI_EFI.fd</code> as an ARM stub, <em>then</em> UEFI</td><td><strong>both</strong>: MBR + FAT32 <code>0x0c</code> for the EEPROM, carrying an EFI firmware image</td></tr>
</tbody>
</table>
<div class="callout callout-warn">
<p><span class="pill pill-warn">The trap</span> The fourth row is why “is this a Pi?” and “does this machine have UEFI?” are both insufficient on their own. An EDK2 Pi answers <em>yes</em> to UEFI, but the EEPROM — not EDK2 — is still what reads the partition table, so it cannot use a GPT ESP. The pftf RPi3 documentation says so outright: <em>“Do not try to use GPT for the partition scheme … as these are unsupported by the CPU-embedded bootloader.”</em></p>
</div>
<h2 id="notus">2. What NextBSD is doing is not what FreeBSD does</h2>
<p><span class="pill pill-meas">Verified in source</span> FreeBSD’s own Raspberry Pi image does <strong>not</strong> use firmware-direct boot. <code>release/arm64/RPI.conf</code> installs <code>sysutils/u-boot-rpi-arm64</code> and ships a <code>config.txt</code> whose <code>kernel=</code> line names <code>u-boot.bin</code>. The chain is:</p>
<pre><code>Pi EEPROM → u-boot.bin → U-Boot's built-in EFI → BOOTAA64.EFI → loader → kernel</code></pre>
<p>U-Boot manufactures a UEFI environment so the ordinary arm64 path works. That is why nothing in FreeBSD’s installer has a case for us: upstream never needs one. NextBSD’s route (a) — firmware enters <code>kernel8.img</code> directly, no loader at all — is a genuinely different design, and the installer gap is a direct consequence of choosing it.</p>
<p>The same pattern holds almost everywhere. Fedora, openSUSE and Debian all chainload U-Boot in front of the kernel and thereby dodge the question entirely. <strong>No project surveyed probes for “the firmware entered the kernel directly”</strong>; the ones that genuinely support it resolve it by board identity or by declaring it at build time.</p>
<h2 id="signals">3. Every signal, and what it actually proves</h2>
<p>Each of these was checked against FreeBSD source at <code>releng/15.1</code>, and the ones marked measured were read off a Pi 500+ running NextBSD.</p>
<table>
<thead><tr><th>Signal</th><th>Proves</th><th>Verdict</th></tr></thead>
<tbody>
<tr><td><code>machdep.bootmethod</code></td><td><code>BIOS</code> / <code>UEFI</code> / <code>PVH</code>. What bsdinstall itself uses.</td><td><span class="pill pill-bad">x86 only</span> Declared in <code>sys/x86/x86/cpu_machdep.c</code>. Absent on arm64.</td></tr>
<tr><td><code>machdep.efi_map</code></td><td>The EFI memory map.</td><td><span class="pill pill-bad">amd64 only</span> One declaration in the tree, <code>sys/amd64/amd64/machdep.c:1724</code>. <strong>This is the bug that would have bricked arm64 VM installs.</strong></td></tr>
<tr><td><code>/dev/efi</code></td><td>EFI runtime services present <em>and working</em> — <code>efidev(4)</code> declines to attach otherwise.</td><td><span class="pill pill-good">Good</span> Arch-neutral. Absence does not prove BIOS (RT can be broken or disabled).</td></tr>
<tr><td><code>kenv efi-version</code></td><td><code>loader.efi</code> ran. Set nowhere else (<code>stand/efi/libefi/env.c</code>).</td><td><span class="pill pill-good">Good</span> Arch-neutral, cheap.</td></tr>
<tr><td><code>hw.fdt.compatible</code></td><td>The root node’s compatible, <strong>NULs already joined into spaces by the kernel</strong>.</td><td><span class="pill pill-good">Best board ID</span> World-readable; gives the whole property. <span class="pill pill-meas">Measured</span> <code>raspberrypi,500 brcm,bcm2712</code></td></tr>
<tr><td><code>ofwdump -P compatible /</code></td><td>Same, via <code>/dev/openfirm</code>.</td><td><span class="pill pill-warn">Avoid</span> Needs root (mode 0600); prints raw hex without <code>-S</code>, and <code>-S</code> stops at the first NUL — so <code>brcm,bcm2712</code> is invisible. <code>-R</code> does return the whole property.</td></tr>
<tr><td><code>/chosen/bootloader</code> in the FDT</td><td>The Raspberry Pi firmware booted this system. It adds the whole subtree at handover — <code>boot-mode</code>, <code>partition</code>, <code>pm_rsts</code>, <code>tryboot</code>, <code>version</code> — and it is absent from the DTB on disk.</td><td><span class="pill pill-good">Positive evidence</span> <span class="pill pill-meas">Measured</span> Present in the blob NextBSD received (<code>bootloader</code>, <code>rpi-boardrev-ext</code>, <code>os_prefix</code>, <code>tryboot</code>, <code>boot-mode</code>). Better in kind than inferring a firmware boot from the <em>absence</em> of UEFI — though reading it needs the DTB blob, since <code>ofwdump</code> does not enumerate the subnode.</td></tr>
<tr><td>“an FDT exists”</td><td>Nothing about the boot method.</td><td><span class="pill pill-bad">Misleading</span> <code>loader.efi</code> installs the DTB from the EFI configuration table — <code>“Using DTB provided by EFI at %p”</code>. Every UEFI arm64 VM has <code>/dev/openfirm</code>.</td></tr>
<tr><td><code>debug.dump_modinfo</code></td><td>Which metadata the loader passed. No <code>MODINFOMD_ENVP</code> ⇒ no FreeBSD loader ran.</td><td><span class="pill pill-good">Strongest arm64 evidence</span> Arch-neutral, but requires parsing text.</td></tr>
<tr><td><code>machdep.acpi_root</code></td><td>On arm64, non-zero only if a loader passed <code>acpi.rsdp</code> — there is no memory-scan fallback.</td><td><span class="pill pill-good">Corroborating</span> <span class="pill pill-meas">Measured</span> <code>0</code> on the Pi.</td></tr>
<tr><td><code>hw.efi.*</code>, <code>machdep.efi_rt_handle_faults</code></td><td>That the kernel was <em>compiled</em> with <code>options EFIRT</code>.</td><td><span class="pill pill-bad">Not evidence</span> Present on BIOS amd64 and DTB arm64 alike.</td></tr>
<tr><td><code>kenv -l</code></td><td>—</td><td><span class="pill pill-bad">Returns ENOENT</span> Needs <code>PRESERVE_EARLY_KENV</code>, which no GENERIC sets.</td></tr>
<tr><td><code>gpart show</code> types</td><td>Partition scheme and type names, straight from disk metadata.</td><td><span class="pill pill-good">No mount needed</span> <span class="pill pill-meas">Measured</span> <code>da0s1 fat32lba</code>, <code>da0s2 freebsd</code></td></tr>
<tr><td><code>/dev/msdosfs/<label></code></td><td>A FAT volume with that label exists.</td><td><span class="pill pill-warn">Conditional</span> Needs <code>GEOM_LABEL</code> <em>and</em> a non-empty label; unlabelled volumes produce no node.</td></tr>
</tbody>
</table>
<h2 id="required">4. What is actually required, versus merely conventional</h2>
<p>A surprising amount of Pi boot lore turns out to be convention. Separating the two matters, because every genuine requirement is a constraint on the installer and every convention is a free choice.</p>
<table>
<thead><tr><th>Claim</th><th>Status</th></tr></thead>
<tbody>
<tr><td>MBR, not GPT</td><td><span class="pill pill-warn">Depends on generation <em>and</em> media</span> Pi 1/2/3 from SD: MBR only, and that is silicon — the mask ROM cannot be updated. Pi 2B v1.2/3B/3B+ over <strong>USB mass storage</strong>: GPT works. Pi 4 and 5: GPT since firmware 2020-09-14 (<em>“Add support GPT and Hybrid MBR partition tables”</em>), with GPT FAT16 added 2021-10-04 and 4K-native-sector support on 2712. Raspberry Pi’s own <code>rpi-image-gen</code> ships <code>image/gpt/</code> and <code>image/mbr/</code> side by side. So MBR is the only scheme that works <em>everywhere</em> — a decision, not a constraint.</td></tr>
<tr><td>FAT partition type <code>0x0c</code> (fat32lba)</td><td><span class="pill pill-bad">Folklore</span> The documented rule is about the <em>filesystem</em>, not the type byte. <code>autoboot.adoc</code>: <em>“Bootable partitions must be formatted as FAT12, FAT16 or FAT32 and contain a <code>start.elf</code> file (or <code>config.txt</code> file on Raspberry Pi 5) in order to be classed as be bootable by the bootloader.”</em> NOOBS — Raspberry Pi’s own installer — shipped its bootable partition as <code>0x0E</code>; pftf’s Pi 4 port recommends <code>0xef</code>. <code>0x0c</code> is the conventional choice and a perfectly good one, but it is not the requirement.</td></tr>
<tr><td>Some type bytes are actively dangerous</td><td><span class="pill pill-meas">Yes</span> <strong><code>0x07</code> exFAT as the first partition hangs the BCM2711 B0 mask ROM</strong> — a brief ACT flash, then nothing at all: no UART, no HDMI. This is the classic “brand-new 64 GB card will not boot” report. Also: <code>0x0F</code> extended containers were unbootable before firmware 2024-05, and MBR slot 4 was broken before 2025-05.</td></tr>
<tr><td>The boot partition must be marked <em>active</em></td><td><span class="pill pill-bad">Convention only</span> Every Raspberry Pi OS image decoded, 2012–2026, has status byte <code>0x00</code>. FreeBSD’s <code>arm.subr</code> sets it anyway; <code>rpi-imager</code> writes <code>0x80</code>. It evidently does not matter.</td></tr>
<tr><td>FAT32 specifically</td><td><span class="pill pill-warn">Not strictly</span> Images were FAT16 until 2017-01-11, and FreeBSD’s RPI image still formats FAT16 while labelling the partition <code>0x0c</code>. The firmware tolerates the mismatch.</td></tr>
<tr><td><code>config.txt</code> must exist on a Pi 5</td><td><span class="pill pill-meas">Required</span> <code>boot.adoc:135</code>: <em>“Raspberry Pi 5 requires a <code>config.txt</code> file to be present to indicate that the partition is bootable.”</em> On earlier models that role is played by <code>start.elf</code>. So the file is not merely configuration — on this generation it is the marker that makes a partition a boot candidate at all. An installer that wrote a boot partition without one would produce a disk the firmware skips silently.</td></tr>
<tr><td>A partition table at all</td><td><span class="pill pill-meas">Required</span> The EEPROM looks for a FAT partition; it does not read a bare filesystem.</td></tr>
</tbody>
</table>
<div class="callout callout-warn">
<p><span class="pill pill-warn">Corrected after publication</span> This section first listed “MBR, not GPT” as a hard requirement, on the strength of Raspberry Pi OS shipping MBR for fourteen years and the pftf UEFI ports saying GPT was unsupported. Both facts are real; the conclusion drawn from them was not. The firmware gained GPT support in 2020, and the pftf statement describes the Pi 3. Left visible rather than quietly edited, because mistaking a convention for a constraint is precisely the failure this page exists to prevent — and it happened here, in the section written to prevent it.</p>
</div>
<h2 id="options">5. The options</h2>
<h3>A. Match the board’s compatible string</h3>
<p><strong>Test:</strong> <code>hw.fdt.compatible</code> contains <code>raspberrypi,</code>.<br>
<strong>For:</strong> direct, cheap, world-readable, and the whole property is available.<br>
<strong>Against:</strong> answers “which board”, not “which boot method”. <span class="pill pill-bad">Fails</span> on an EDK2 Pi, which is a Pi but wants the EFI payload. Needs a list that grows with the hardware.</p>
<h3>B. Test for UEFI, everything else is firmware boot</h3>
<p><strong>Test:</strong> <code>/dev/efi</code> or <code>kenv efi-version</code>.<br>
<strong>For:</strong> arch-neutral; correctly sends an EDK2 Pi down the UEFI path; no board list.<br>
<strong>Against:</strong> “not UEFI” is not the same as “Pi firmware” — a U-Boot board without EFI payload support would be misread. And it still gets the EDK2 Pi’s <em>partitioning</em> wrong, since that needs MBR regardless.</p>
<h3>C. Look for <code>config.txt</code> on the boot medium</h3>
<p><strong>Test:</strong> mount candidate FAT partitions, look for <code>config.txt</code> plus the kernel it names.<br>
<strong>For:</strong> proof rather than inference — that file <em>is</em> the boot method, and finding it also locates the files to copy.<br>
<strong>Against:</strong> <span class="pill pill-bad">Mounts media the operator did not nominate.</span> Intrusive, and in practice it produced three separate bugs: probing the install target, leaking mounts, and failing silently on stale state.</p>
<h3>D. Read the partition table only</h3>
<p><strong>Test:</strong> <code>gpart</code> reports an MBR disk with a <code>fat32lba</code> slice.<br>
<strong>For:</strong> no mounting; structural rather than by convention, so it recognises a stock Raspberry Pi OS card or a hand-made stick.<br>
<strong>Against:</strong> describes a <em>layout</em>, not a capability. A machine could have such a disk attached and still be a PC.</p>
<h3>E. Reproduce the medium’s own boot setup</h3>
<p><strong>Not a detection method — a way of acting on one.</strong> Rather than generating a <code>config.txt</code> from a template, copy the boot partition that started this machine. It demonstrably works on this hardware, which no amount of detection can promise, and it needs no per-board knowledge at all — a Pi 4, a Pi 5 or a board nobody has thought of yet all work unchanged.</p>
<h2 id="decision">6. The decision</h2>
<div class="callout callout-warn">
<p><span class="pill pill-warn">The gap that reordered this</span> Test UEFI first and a real case breaks. <strong>Stock FreeBSD’s Pi image boots through a UEFI layer</strong> — U-Boot’s, not EDK2’s — so on that system <code>/dev/efi</code> exists and a UEFI-first rule answers “generic” and writes GPT+ESP. That disk will not boot, because the EEPROM is still what reads the partition table and it wants MBR+FAT. The same is true of an EDK2 Pi. <strong>A UEFI layer on a Pi does not change who reads the partition table.</strong></p>
</div>
<p>The fix is not a different test but a different question. Two decisions were being conflated:</p>
<table>
<thead><tr><th>Decision</th><th>Determined by</th><th>Because</th></tr></thead>
<tbody>
<tr><td><strong>Partition scheme and boot filesystem</strong></td><td>the <strong>board</strong></td><td>whatever reads the partition table first is fixed in silicon or EEPROM. On a Pi that is always the GPU firmware, whatever runs afterwards.</td></tr>
<tr><td><strong>What goes on the boot partition</strong></td><td>the <strong>boot method</strong></td><td><code>kernel8.img</code> + <code>config.txt</code> for firmware-direct; <code>u-boot.bin</code> or <code>RPI_EFI.fd</code> plus an EFI tree where a UEFI layer is in play.</td></tr>
</tbody>
</table>
<div class="callout callout-good">
<p><span class="pill pill-good">Chosen</span> <strong>Board decides the layout. Boot method decides the payload. Always reproduce the medium’s boot setup rather than generating one.</strong></p>
</div>
<pre><code>. # 1. LAYOUT -- who reads the partition table?
# A Pi is a Pi whether or not something UEFI-shaped runs later.
case "$(sysctl -n hw.fdt.compatible 2>/dev/null)" in
*raspberrypi,*|*brcm,bcm2*) LAYOUT=mbr-fat ;;
*) LAYOUT=gpt ;;
esac
# 2. PAYLOAD -- did a UEFI loader run?
# NOT machdep.efi_map: that does not exist on arm64.
if [ -e /dev/efi ] || [ -n "$(kenv -q efi-version)" ]; then
PAYLOAD=efi # loader.efi into an EFI/BOOT tree
else
PAYLOAD=firmware # config.txt + DTB + kernel8.img
fi</code></pre>
<p>For NextBSD today that yields <code>mbr-fat</code> + <code>firmware</code> on the Pi and <code>gpt</code> + <code>efi</code> everywhere else — the same two outcomes as before. The difference is that the two remaining combinations are now expressible instead of silently wrong.</p>
<p><strong>A refinement not yet taken.</strong> The payload test infers a firmware boot from the <em>absence</em> of UEFI, which is weaker than it could be. <code>/chosen/bootloader</code> is positive evidence — the Pi firmware adds that subtree at handover and it is not in the DTB on disk — and it is measurably present in the blob NextBSD receives. It is not used yet only because reading it means parsing <code>hw.fdt.dtb</code> rather than asking a sysctl; <code>ofwdump</code> does not enumerate the subnode. Worth doing if the absence-based test ever proves fragile.</p>
<p><strong>Why the board test can come first without an EDK2 exception.</strong> Because the layout it selects is correct for an EDK2 Pi too. That was the case the earlier ordering could not represent: it is a Pi <em>and</em> it is UEFI, and both facts have consequences that do not conflict once they are asked separately.</p>
<p><strong>Why not mount to detect (option C).</strong> Mounting during detection is intrusive and was the direct cause of three bugs — probing the install target, leaking mounts, and failing silently on stale state. The information it yields is needed only when copying, at which point mounting one identified device read-only is proportionate.</p>
<p><strong>Why E regardless.</strong> Every generated <code>config.txt</code> encodes an assumption about the board. A copied one encodes what actually booted. It is also the only approach that handles a Pi 3 — whose boot partition needs <code>bootcode.bin</code> and <code>start.elf</code> that a Pi 5 image has never heard of — without a per-generation file manifest.</p>
<p><strong>Why MBR, given that GPT would work on a Pi 4 or 5.</strong> Reach. MBR is the only scheme a Pi 3 can boot from SD, it is what Raspberry Pi OS has shipped for fourteen years, and it costs nothing here. GPT would buy A/B partitioning, which nothing needs yet.</p>
<h2 id="open">7. What is still unresolved</h2>
<div class="callout callout-good">
<p><span class="pill pill-good">Resolved by the research</span> <strong>UEFI on a Pi 5 is not a case worth building for.</strong> There is no <code>pftf/RPi5</code>, and <code>edk2-platforms</code> contains only <code>RPi3/</code> and <code>RPi4/</code> — searching it for BCM2712 returns nothing. The only port, <code>worproject/rpi5-uefi</code>, was <strong>archived in February 2025</strong>; RP1 has no ACPI description, so Ethernet, GPIO and fan control do not work under it, and EEPROM access is broken so UEFI variables cannot persist. Raspberry Pi have declined to help twice, most recently <em>“that would require a sizeable support commitment”</em>. Nobody has reported booting it on a Pi 500 at all. The layout/payload split above handles it correctly if it ever appears; nothing needs to be built for it now.</p>
</div>
<div class="callout callout-warn">
<p><span class="pill pill-warn">Open</span> <strong>Root label collision.</strong> The generic path labels the installed root <code>NEXTBSD</code> so a leftover install medium cannot hijack the boot, and overrides the kernel’s baked-in default through <code>loader.conf</code>. On a Pi there is no loader to do the overriding, so the target must be labelled <code>ROOTFS</code> — the same label the install medium carries. The current mitigation is “do not leave the stick plugged in”, which is weak. The real fix is a board kernel whose <code>ROOTDEVNAME</code> names something distinct.</p>
</div>
<div class="callout callout-warn">
<p><span class="pill pill-warn">Do not rely on it</span> <strong><code>efibootmgr</code> at install time.</strong> U-Boot cannot set UEFI variables at runtime at all — <em>“we don’t allow SetVariableRT, since the OS doesn’t know how to write that file”</em> — and pftf’s Pi 4 firmware stores variables inside <code>RPI_EFI.fd</code> and flushes them only during boot services, so entries written from a running OS silently vanish on reboot. EBBR makes <code>SetVariable</code> optional after <code>ExitBootServices()</code>. The installer already writes the removable-media fallback path <code>\EFI\BOOT\BOOTAA64.EFI</code> and treats the NVRAM entry as best-effort, which is the correct contract; this is a note not to tighten that later.</p>
</div>
<p><span class="pill pill-warn">Reasoned</span> Beyond the Pi, the layout question does not generalise. Allwinner wants U-Boot at raw sector 16 or 256, Rockchip at sector 64, Amlogic at sector 1 — none of them read a partition table at that stage, and the offsets collide with a primary GPT. FreeBSD, Fedora and Armbian have each independently converged on a per-SoC-family install hook, which is strong evidence no filesystem-only model exists. If NextBSD ever targets a non-Pi board, budget for that shape rather than extending this one.</p>
<p><span class="pill pill-warn">Not verified</span> The FAT cluster and reserved-sector constraints for the Broadcom firmware specifically. Documented for TI’s ROM, absent for this one. <code>mkfs</code> defaults have always worked here, so this is a latent unknown rather than a live problem.</p>
<h2 id="measured">8. Measured on the hardware</h2>
<p>A Pi 500+ running NextBSD from USB, 2026-08-24. Everything in the decision above is consistent with these readings.</p>
<pre><code>/dev/efi absent
kenv efi-version unset
machdep.acpi_root 0
dev.efirtc.0 absent
hw.fdt.compatible raspberrypi,500 brcm,bcm2712
hw.fdt.model Raspberry Pi 500 Rev 1.0
ofwdump -S -P compatible raspberrypi,500 <- truncated at the first NUL
kern.disks da0 nda0
gpart show -p da0
=> 1 30871551 da0 MBR (15G)
1 204800 da0s1 fat32lba (100M)
204801 4500096 da0s2 freebsd (2.1G)
mount
/dev/ufs/ROOTFS on / (ufs, local, noatime, soft-updates)
devfs on /dev (devfs) <- the boot partition is NOT mounted at runtime</code></pre>
<p class="meta">2026-08-24. Detection logic verified against <code>freebsd/freebsd-src</code> at <code>releng/15.1</code>; Raspberry Pi behaviour against the <code>raspberrypi/firmware</code>, <code>pi-gen</code> and <code>pftf</code> repositories; everything in §8 read off the board itself. Implemented in <a href="https://github.com/nextbsd-redux/nextbsd-userland/pull/60">nextbsd-userland#60</a>, tracking <a href="https://github.com/nextbsd-redux/nextbsd/issues/420">nextbsd#420</a>.</p>
</div>
</body>
</html>