← Back · part of NextBSD Research · sub-plan of the Pi 500+ native-boot plan
NextBSD’s installer writes GPT with an EFI System Partition, unconditionally. That is unbootable on a Raspberry Pi, whose firmware reads config.txt 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.
Why this page exists 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 machdep.efi_map (does not exist on arm64); treating “has a device tree” as “firmware booted us” (loader.efi installs a DTB too); and mounting candidate filesystems to look for config.txt (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.
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.
| Case | What starts the OS | What must be on disk |
|---|---|---|
| Legacy BIOS (x86) | MBR bootstrap → gptboot → loader | GPT + a freebsd-boot partition holding pmbr/gptboot |
| UEFI (x86, arm64) | firmware → BOOTAA64.EFI → loader | GPT + an ESP (type c12a7328-…) |
| Firmware direct (Pi) | EEPROM bootloader reads config.txt, enters the kernel at EL2 | MBR + FAT32 type 0x0c holding config.txt, a DTB, and the kernel |
| UEFI on a Pi (EDK2) | EEPROM loads RPI_EFI.fd as an ARM stub, then UEFI | both: MBR + FAT32 0x0c for the EEPROM, carrying an EFI firmware image |
The trap 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 yes 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: “Do not try to use GPT for the partition scheme … as these are unsupported by the CPU-embedded bootloader.”
Verified in source FreeBSD’s own Raspberry Pi image does not use firmware-direct boot. release/arm64/RPI.conf installs sysutils/u-boot-rpi-arm64 and ships a config.txt whose kernel= line names u-boot.bin. The chain is:
Pi EEPROM → u-boot.bin → U-Boot's built-in EFI → BOOTAA64.EFI → loader → kernel
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 kernel8.img directly, no loader at all — is a genuinely different design, and the installer gap is a direct consequence of choosing it.
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. No project surveyed probes for “the firmware entered the kernel directly”; the ones that genuinely support it resolve it by board identity or by declaring it at build time.
Each of these was checked against FreeBSD source at releng/15.1, and the ones marked measured were read off a Pi 500+ running NextBSD.
| Signal | Proves | Verdict |
|---|---|---|
machdep.bootmethod | BIOS / UEFI / PVH. What bsdinstall itself uses. | x86 only Declared in sys/x86/x86/cpu_machdep.c. Absent on arm64. |
machdep.efi_map | The EFI memory map. | amd64 only One declaration in the tree, sys/amd64/amd64/machdep.c:1724. This is the bug that would have bricked arm64 VM installs. |
/dev/efi | EFI runtime services present and working — efidev(4) declines to attach otherwise. | Good Arch-neutral. Absence does not prove BIOS (RT can be broken or disabled). |
kenv efi-version | loader.efi ran. Set nowhere else (stand/efi/libefi/env.c). | Good Arch-neutral, cheap. |
hw.fdt.compatible | The root node’s compatible, NULs already joined into spaces by the kernel. | Best board ID World-readable; gives the whole property. Measured raspberrypi,500 brcm,bcm2712 |
ofwdump -P compatible / | Same, via /dev/openfirm. | Avoid Needs root (mode 0600); prints raw hex without -S, and -S stops at the first NUL — so brcm,bcm2712 is invisible. -R does return the whole property. |
/chosen/bootloader in the FDT | The Raspberry Pi firmware booted this system. It adds the whole subtree at handover — boot-mode, partition, pm_rsts, tryboot, version — and it is absent from the DTB on disk. | Positive evidence Measured Present in the blob NextBSD received (bootloader, rpi-boardrev-ext, os_prefix, tryboot, boot-mode). Better in kind than inferring a firmware boot from the absence of UEFI — though reading it needs the DTB blob, since ofwdump does not enumerate the subnode. |
| “an FDT exists” | Nothing about the boot method. | Misleading loader.efi installs the DTB from the EFI configuration table — “Using DTB provided by EFI at %p”. Every UEFI arm64 VM has /dev/openfirm. |
debug.dump_modinfo | Which metadata the loader passed. No MODINFOMD_ENVP ⇒ no FreeBSD loader ran. | Strongest arm64 evidence Arch-neutral, but requires parsing text. |
machdep.acpi_root | On arm64, non-zero only if a loader passed acpi.rsdp — there is no memory-scan fallback. | Corroborating Measured 0 on the Pi. |
hw.efi.*, machdep.efi_rt_handle_faults | That the kernel was compiled with options EFIRT. | Not evidence Present on BIOS amd64 and DTB arm64 alike. |
kenv -l | — | Returns ENOENT Needs PRESERVE_EARLY_KENV, which no GENERIC sets. |
gpart show types | Partition scheme and type names, straight from disk metadata. | No mount needed Measured da0s1 fat32lba, da0s2 freebsd |
/dev/msdosfs/<label> | A FAT volume with that label exists. | Conditional Needs GEOM_LABEL and a non-empty label; unlabelled volumes produce no node. |
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.
| Claim | Status |
|---|---|
| MBR, not GPT | Depends on generation and media 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 USB mass storage: GPT works. Pi 4 and 5: GPT since firmware 2020-09-14 (“Add support GPT and Hybrid MBR partition tables”), with GPT FAT16 added 2021-10-04 and 4K-native-sector support on 2712. Raspberry Pi’s own rpi-image-gen ships image/gpt/ and image/mbr/ side by side. So MBR is the only scheme that works everywhere — a decision, not a constraint. |
FAT partition type 0x0c (fat32lba) | Folklore The documented rule is about the filesystem, not the type byte. autoboot.adoc: “Bootable partitions must be formatted as FAT12, FAT16 or FAT32 and contain a start.elf file (or config.txt file on Raspberry Pi 5) in order to be classed as be bootable by the bootloader.” NOOBS — Raspberry Pi’s own installer — shipped its bootable partition as 0x0E; pftf’s Pi 4 port recommends 0xef. 0x0c is the conventional choice and a perfectly good one, but it is not the requirement. |
| Some type bytes are actively dangerous | Yes 0x07 exFAT as the first partition hangs the BCM2711 B0 mask ROM — 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: 0x0F extended containers were unbootable before firmware 2024-05, and MBR slot 4 was broken before 2025-05. |
| The boot partition must be marked active | Convention only Every Raspberry Pi OS image decoded, 2012–2026, has status byte 0x00. FreeBSD’s arm.subr sets it anyway; rpi-imager writes 0x80. It evidently does not matter. |
| FAT32 specifically | Not strictly Images were FAT16 until 2017-01-11, and FreeBSD’s RPI image still formats FAT16 while labelling the partition 0x0c. The firmware tolerates the mismatch. |
config.txt must exist on a Pi 5 | Required boot.adoc:135: “Raspberry Pi 5 requires a config.txt file to be present to indicate that the partition is bootable.” On earlier models that role is played by start.elf. 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. |
| A partition table at all | Required The EEPROM looks for a FAT partition; it does not read a bare filesystem. |
Corrected after publication 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.
Test: hw.fdt.compatible contains raspberrypi,.
For: direct, cheap, world-readable, and the whole property is available.
Against: answers “which board”, not “which boot method”. Fails on an EDK2 Pi, which is a Pi but wants the EFI payload. Needs a list that grows with the hardware.
Test: /dev/efi or kenv efi-version.
For: arch-neutral; correctly sends an EDK2 Pi down the UEFI path; no board list.
Against: “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 partitioning wrong, since that needs MBR regardless.
config.txt on the boot mediumTest: mount candidate FAT partitions, look for config.txt plus the kernel it names.
For: proof rather than inference — that file is the boot method, and finding it also locates the files to copy.
Against: Mounts media the operator did not nominate. Intrusive, and in practice it produced three separate bugs: probing the install target, leaking mounts, and failing silently on stale state.
Test: gpart reports an MBR disk with a fat32lba slice.
For: no mounting; structural rather than by convention, so it recognises a stock Raspberry Pi OS card or a hand-made stick.
Against: describes a layout, not a capability. A machine could have such a disk attached and still be a PC.
Not a detection method — a way of acting on one. Rather than generating a config.txt 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.
The gap that reordered this Test UEFI first and a real case breaks. Stock FreeBSD’s Pi image boots through a UEFI layer — U-Boot’s, not EDK2’s — so on that system /dev/efi 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. A UEFI layer on a Pi does not change who reads the partition table.
The fix is not a different test but a different question. Two decisions were being conflated:
| Decision | Determined by | Because |
|---|---|---|
| Partition scheme and boot filesystem | the board | whatever reads the partition table first is fixed in silicon or EEPROM. On a Pi that is always the GPU firmware, whatever runs afterwards. |
| What goes on the boot partition | the boot method | kernel8.img + config.txt for firmware-direct; u-boot.bin or RPI_EFI.fd plus an EFI tree where a UEFI layer is in play. |
Chosen Board decides the layout. Boot method decides the payload. Always reproduce the medium’s boot setup rather than generating one.
. # 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
For NextBSD today that yields mbr-fat + firmware on the Pi and gpt + efi everywhere else — the same two outcomes as before. The difference is that the two remaining combinations are now expressible instead of silently wrong.
A refinement not yet taken. The payload test infers a firmware boot from the absence of UEFI, which is weaker than it could be. /chosen/bootloader 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 hw.fdt.dtb rather than asking a sysctl; ofwdump does not enumerate the subnode. Worth doing if the absence-based test ever proves fragile.
Why the board test can come first without an EDK2 exception. 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 and it is UEFI, and both facts have consequences that do not conflict once they are asked separately.
Why not mount to detect (option C). 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.
Why E regardless. Every generated config.txt 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 bootcode.bin and start.elf that a Pi 5 image has never heard of — without a per-generation file manifest.
Why MBR, given that GPT would work on a Pi 4 or 5. 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.
Resolved by the research UEFI on a Pi 5 is not a case worth building for. There is no pftf/RPi5, and edk2-platforms contains only RPi3/ and RPi4/ — searching it for BCM2712 returns nothing. The only port, worproject/rpi5-uefi, was archived in February 2025; 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 “that would require a sizeable support commitment”. 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.
Open Root label collision. The generic path labels the installed root NEXTBSD so a leftover install medium cannot hijack the boot, and overrides the kernel’s baked-in default through loader.conf. On a Pi there is no loader to do the overriding, so the target must be labelled ROOTFS — 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 ROOTDEVNAME names something distinct.
Do not rely on it efibootmgr at install time. U-Boot cannot set UEFI variables at runtime at all — “we don’t allow SetVariableRT, since the OS doesn’t know how to write that file” — and pftf’s Pi 4 firmware stores variables inside RPI_EFI.fd and flushes them only during boot services, so entries written from a running OS silently vanish on reboot. EBBR makes SetVariable optional after ExitBootServices(). The installer already writes the removable-media fallback path \EFI\BOOT\BOOTAA64.EFI and treats the NVRAM entry as best-effort, which is the correct contract; this is a note not to tighten that later.
Reasoned 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.
Not verified The FAT cluster and reserved-sector constraints for the Broadcom firmware specifically. Documented for TI’s ROM, absent for this one. mkfs defaults have always worked here, so this is a latent unknown rather than a live problem.
A Pi 500+ running NextBSD from USB, 2026-08-24. Everything in the decision above is consistent with these readings.
/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