Skip to content

Repository files navigation

MiSTer Linux Modernization

A complete, reproducible operating system for the MiSTer DE10-Nano — kernel, root filesystem, a real-time kernel variant, a flashable SD-card image, and the update channel that delivers them. Built in the open from a modern Buildroot and a mainline LTS kernel, with every MiSTer kernel patch carried in-tree as a plain .patch file applied to a pristine, hash-verified kernel.org tarball.

It is a drop-in replacement: the unmodified, stock MiSTer binary and every existing core run on it unchanged.

What ships

Artifact What it is
linux.img + zImage_dtb The OS itself — rootfs and kernel, delivered as a normal update through the stock on-device Downloader
zImage_dtb-rt A PREEMPT_RT kernel variant, built by CI. Its modules ride inside the same linux.img, so switching kernels is a one-line u-boot.txt edit in either direction — no rootfs flash
sdcard.img.xz A complete, flashable SD card that self-expands to the card's real size on first boot — no mr-fusion, no Windows SD installer
release_YYYYMMDD.7z The stock-layout release archive, byte-compatible with the Downloader's expectations
legal-info.tar.gz A full SBOM — every package, version, license, and upstream source tarball
SHA256SUMS + attestations Build provenance for the image assets
db.json The opt-in update channel, served from GitHub Pages — drop one script into Scripts/, run it, and releases arrive through the standard on-device Downloader
An exportable kernel tree The carried series, rendered deterministically into Linux-Kernel_MiSTer layout so upstream can consume the work without a second fork

Status — personal use only. Phases 0–3 are complete with hardware validation on a real DE10-Nano; Phase 4 (release engineering & sustainability) is in progress. Nothing here will be offered publicly until the sustainability commitment in ADR 0014 is signed by a named maintainer. If you are trying it anyway, read docs/user/beta-testing.md first.


Contents


The one-paragraph version

MiSTer's operating system ships as an opaque archive containing a 375 MiB ext4 image (93% full) built from Buildroot 2021.02.4 with glibc 2.31, running Linux 5.15.1 — a kernel forked in November 2021 that has never merged a single 5.15.y stable release. There is no public build recipe, no CI, no SBOM, and no update path for any of it. This project rebuilds the whole thing from Buildroot 2026.05.2 and a mainline 6.18 LTS kernel in a public repository, with reproducible builds, a signed-hash supply chain, a nine-workflow CI pipeline, and a per-commit reconciliation of the entire kernel fork — then ships it through the same update channel users already have, alongside a real-time kernel variant and a flashable card image that stock has no equivalent of. All of it boots the unmodified, stock MiSTer binary and every existing core.

The core posture: prove parity first, then improve. Carry the smallest possible delta against a pristine kernel.org tree, and hand each subsystem back to mainline as soon as mainline can hold it.


Stock vs. this image, at a glance

Stock MiSTer This project
Kernel 5.15.1, forked Nov 2021, zero 5.15.y stable updates ever merged; 5.15 EOL Oct 2026 6.18 LTS, on a live .y line with security backports
Kernel delta 110 commits on a squashed-import fork with no shared ancestry with mainline — so no merge-base, and no per-commit disposition 37 patch files against a pristine tarball, each with provenance, upstream status, and an evidence-backed record
Buildroot 2021.02.4 2026.05.2 (~5 years of upstream work)
glibc / gcc 2.31 / gcc 10-era 2.43 / 14.4.0
OpenSSL 1.1.1 — EOL since 2023-09-11, no upstream fixes since 3.6.3
OpenSSH 8.6p1 10.5p1
Samba ~4.14 4.24.5
Python 3.9 3.14.7
SSH host keys Identical on every MiSTer on Earth, baked into the public download, dated 2016 Generated per device on first boot, persisted to the FAT card (ADR 0015)
Timezone on a fresh card Unset — /media/fat/linux/timezone does not exist, so glibc falls back to UTC and stays there until the user finds the community timezone.sh script Detected once, from the box's public IP, using the same provider and the same destination file as timezone.sh so the two are interchangeable. It is one dhcpcd hook, firing on the first connection the box makes — so a card that was offline on its first boot still gets it once Wi-Fi is set up, and a box with no network does nothing at all. Never overwrites a timezone you set, and it is opt-out-able before it ever runs (ADR 0025)
Wi-Fi chipset coverage Six out-of-tree vendor forks, plus in-kernel mac80211 USB drivers for MediaTek/Ralink/Marvell/older Realtek; no Broadcom, no Wi-Fi 6/6E, no Atheros USB Wi-Fi, no Redpine; several chips can't do WPA3 at all Mainline-first: in-kernel mac80211 (rtl8xxxu/rtw88/rtw89/mt76/ath9k_htc/rsi…) for every chip mainline can drive that is worth building here — six symbols are deliberately left off (one upstream itself calls non-working, four 2000s-era 802.11b/g parts, one that is optical LiFi rather than Wi-Fi; wifi-parity §7) — plus Broadcom/Cypress and Wi-Fi 6/6E support stock never had. One out-of-tree driver remains, for a Wi-Fi 6E chip mainline has no USB driver for at all. WPA3/SAE hardware-verified; see the hardware-support table for the full build- vs. hardware-verified breakdown (ADR 0016)
Bluetooth firmware ath3k shipped with no ath3k-1.fw and no ar3k/*.dfu at all; no MediaTek MT79xx and no Qualcomm QCA firmware (its Realtek combos were covered — stock-fw.txt carries rtl8761bu, rtl8822b, rtl8822cu, rtl8821c) Firmware audited driver-by-driver against what each one actually requests at runtime, gaps closed (MediaTek combo, Qualcomm QCA Rome USB, one Broadcom .hcd, Atheros AR3011/AR3012) — including one asymmetry this project itself introduced: we shipped the MT7961 Wi-Fi blob without its Bluetooth sibling, so that combo dongle's Wi-Fi worked and its BT did not. Found and closed. Two built drivers still ship with no firmware, deliberately and on the record (bluetooth-parity §9)
The .7z extractor that unpacks every OS update p7zip 16.02 — dated 2016-05-21, in two places: usr/bin/7zr in the rootfs, and /media/fat/linux/7za, which the Downloader downloads off the internet the first time it updates and then reuses forever 7-Zip 26.02, built from source in-tree. The /media/fat/linux/7za copy is shipped by us — statically linked, so it survives a rollback to an older or even stock rootfs — so that download never happens (ADR 0023)
NTFS Not supported at all ntfs3 in-kernel module + ntfs-3g automount
Image size 375 MiB, 13.6% free ("93% full") 512 MiB, 39.5% free — 310 MiB used / 202 MiB free, temporary debug block included, measured by ./scripts/check-size-budget.sh output/images/linux.img on the image built 2026-07-27 with this branch's full package set (the T5 utilities, 7-Zip, and the rtl8852cu driver all included). Floor is 15%. ⚠️ docs/size-budget.md still reports 60.6% free: it is a P3.3-vintage report and needs regenerating
Rootfs build recipe Not published. MiSTer-devel/Linux_Image_creator_MiSTer exists, but it is an assembler, not a builder: create_img.sh runs mkfs.ext4 and untars a prebuilt 82 MiB rootfs.tar.bz2 committed to git. Nothing public turns source into that tarball, so the image can be re-packed but its contents cannot be changed, patched or audited beyond ls This repository
Reproducible No published recipe, so unverifiable Byte-identical across independent builds, proven by CI (docs/reproducibility.md)
SBOM / license manifest None published Full legal-info bundle published with every release
Dependency updates Manual, ad-hoc Renovate, covering kernel pins, firmware, and every package hash
Real-time kernel No PREEMPT_RT variant built by CI, booted on hardware

Every number in that table is sourced. The versions come from the shipped artifacts on both sides — stock from the extracted stock linux.img, ours read off the built tree.

On version drift, since this table cites documents that can lag it. The ground truth for "ours" is always the build pins — BUILDROOT_VERSION in the Makefile and configs/mister_de10nano_defconfig — plus whatever Buildroot's own .mk files resolve to at that pin. The documents below are dated analyses, not a live mirror of those pins: a Renovate bump or a Buildroot line bump moves a package without rewriting the prose that reasoned about it. Where a document is behind the pin it now says so at the top, and lists what has and has not been re-checked. Nothing in CI asserts package versions, so that gap is closed by reading, not by a test — see the honest list.

Full detail with citations: docs/version-delta.md (the stack, re-read most recently), docs/package-manifest.md (the 251-SONAME mapping, established against Buildroot 2026.02.3), docs/verification/stock-release-20250402.md (the stock side, fixed forever — stock does not move).


Project status

Phase State What that means
0 — Recon & decisions ✅ Complete Patch triage, ABI-contract verification, five open questions decided (ADRs 0010–0014)
1 — Kernel & initramfs ✅ Complete 6.18 LTS pinned; all 37 patches apply cleanly; zImage_dtb builds warning-free, boots under QEMU and on real hardware — from the CI-built artifact, not a local build
2 — Rootfs & testing ✅ Complete Buildroot 2026.05.2, glibc 2.43, reproducible ext4 image with full SBOM; menu and cores load on hardware — the ABI contract holds in practice, not just on paper
3 — Module packages & HW matrix ✅ Complete Wi-Fi, Bluetooth, controllers and special devices packaged; hardware-validated for the chips actually present on the one test board. The v10/v10.1/v10.2 driver + firmware expansion (Broadcom, Wi-Fi 6/6E, MediaTek, Atheros USB, Redpine) is packaged and mostly CI-asserted but not hardware-validated — see the ledger and the chipset table. The remaining matrix rows (Samba, MIDI) are build/CI-verified only
4 — Release & sustainability 🔄 In progress CI/CD, db.json distribution, beta program, governance, publication gate
5 — Full SD image & U-Boot 🔄 Partially landed sdcard.img builds, and release.yml verifies it with scripts/check-sdcard.sh (ADR 0020); U-Boot-from-source is planned but not started — now targeting mainline U-Boot, not the 2017.03 fork (ADR 0024, plan, tasks) — and the SD image has not been flashed to a fresh card on hardware (P5.4)

Hardware validation ledger

Validated on one real DE10-Nano, booting CI-built artifacts:

Subsystem Status
Boot to MiSTer menu, cores load ✅ Confirmed
All out-of-tree modules present, no BUG/Oops/panic ✅ Confirmed
Bluetooth — firmware load + controller pairing ✅ Confirmed
Wi-Fi — WPA3/SAE (PMF required) 5 GHz auto-connect via mainline rtw88 ✅ Confirmed
Downloader over HTTPS ✅ Confirmed
PREEMPT_RT kernel boots and runs MiSTer ✅ Confirmed on 7.2-rc4 (2026-07-20) and 7.2-rc7 (2026-08-14). The pin has since moved to 7.2 final, which builds green (make rt, 2026-08-17) but has not been booted — boot is a per-version claim
Samba, MIDI ⚠️ Build/CI-verified only — not exercised on hardware
RT latency measurement ⏳ Not yet taken

Test logs live in docs/testlogs/. Anything not listed above should be treated as unverified in practice — that includes most of the Wi-Fi/Bluetooth chipset table below: this ledger's "all out-of-tree modules present" row is a snapshot from before the current branch's driver/firmware expansion (Broadcom, Wi-Fi 6/6E, and the rest), which has not yet had its own hardware pass.


Install it on a real MiSTer

One command, on the MiSTer itself, over SSH. This converts an ordinary install — including one flashed with Mr. Fusion — to this image. It works on a stock card; you do not need to reflash anything.

curl -fsSL https://raw.githubusercontent.com/mcfbytes/Buildroot_MiSTer/master/install.sh | bash

On a stock card the one-liner above will fail, and fail quietly. Verified on real hardware: stock's curl cannot verify GitHub (curl: (60)), bash gets an empty pipe, and the pipeline still exits 0 — so it looks like nothing happened at all.

The cause is not an expired bundle. Stock ships exactly one file in /etc/ssl/certs/, cacert.pem, and it works fine — it simply is not in the hashed-symlink layout curl searches by default, so curl finds no issuer. Passing --cacert explicitly fixes it, which is what Scripts/update.sh has always done.

So fetch the installer with -k for that one request and let it take over — it detects this, verifies the card's own bundle actually works before adopting it, and uses it for everything afterwards:

curl -fsSLk https://raw.githubusercontent.com/mcfbytes/Buildroot_MiSTer/master/install.sh -o /tmp/mlm.sh \
  && sh /tmp/mlm.sh

If you would rather not use -k at all, download install.sh on a machine whose TLS works, check its sha256sum against the repo, and copy it onto the card. And if a card ever turns up whose bundle is genuinely broken rather than merely mislocated, install.sh --bootstrap-ca fetches a fresh one to /tmp for a single run — it explains that trade-off in full before doing anything.

You only need any of this once: this image ships a current CA bundle in the layout curl expects, and refreshes it on every release.

wget is not an alternative. The wget on a MiSTer is the BusyBox applet with no TLS support at all — it answers every https:// URL with not an http or ftp url. Stock ships a real curl, and MiSTer's own update.sh uses it exclusively.

Prefer to read before you run — the better habit, and this script is written to be read:

curl -fsSL https://raw.githubusercontent.com/mcfbytes/Buildroot_MiSTer/master/install.sh -o mlm.sh
less mlm.sh
sh mlm.sh --dry-run     # prints exactly what it would change, touches nothing

--dry-run prints the full plan and exits. --yes skips the 10-second countdown, --no-reboot leaves the reboot to you.

Read docs/user/beta-testing.md first. This is a personal project with an unmet sustainability gate (ADR 0014), validated on one board. The ledger directly above is the honest account of what has and has not been tested on hardware.

What it changes

The installer prints all of this and pauses before doing anything.

linux/linux.img, linux/zImage_dtb Replaced — the root filesystem and kernel. This is the point
linux/7za Replaced — 7-Zip 26.02 instead of the 2016 p7zip
downloader.ini One key changed: [MiSTer] update_linux = false. A surgical edit; your comments, sections and databases are left alone, and the original is saved to linux/.mlm-backup/downloader.ini.orig. Created (declaring the official database) only if you don't have one
Scripts/update_linux_modernization.sh Installed — this is what updates the image from now on
Scripts/check_storage.sh, Scripts/pair_logitech.sh Installed — two small launchers for tools that live in the image (exFAT check, Logitech pairing)

Everything else under linux/ is rewritten with byte-identical content. Our release archive is the stock archive with those three files swapped in, so MidiLink.INI, ppp_options, uboot.img, updateboot, the _-prefixed templates, mt32-rom-data/ and soundfonts/ come back exactly as they were. It is a no-op unless you had customised one — and the small ones are copied to linux/.mlm-backup/ first in case you had.

Two side effects, both caused by any Linux update rather than by this one: the saved U-Boot environment (first 512 bytes of the card) is wiped by updateboot, and SSH host keys change, because this image generates them per device instead of shipping one set to everybody (ADR 0015) — expect a one-time host-key warning.

Afterwards, routine updates stop touching /media/fat/linux/ altogether. update_linux = false means no Linux update runs unless you run the updater, so these files end up more stable than on stock, where every official Linux update rewrites them.

What it leaves alone

  • MiSTer.ini and every core .ini — not touched. Nor games/, ROMs, saves, states, config/, cores or _Arcade/.
  • linux/gamecontrollerdb/ — explicitly excluded from the sync.
  • Your live u-boot.txt (holds this card's MAC), wpa_supplicant.conf, user-startup.sh, samba.sh — only the _-prefixed templates ship, so the real ones survive.
  • linux/{hostname,hosts,interfaces,resolv.conf,dhcpcd.conf,fstab} — copied into the new image before it goes live, so your network identity carries over.

Afterwards

Your cores keep updating normally — update_all.sh and Scripts/update.sh simply stop touching the Linux image, in either direction. That is what stops the official image overwriting this one; see ADR 0025. Updating this image is one deliberate action:

/media/fat/Scripts/update_linux_modernization.sh            # update
/media/fat/Scripts/update_linux_modernization.sh --status   # where am I?

To undo, at any time: update_linux_modernization.sh --restore-stock, then run your normal update. Full procedure in docs/user/rollback.md.

The other two ways in

  • A fresh card: flash sdcard.img.xz from a release. It self-expands on first boot and arrives already configured, with Jotego's cores enabled. See docs/user/sdcard-flashing.md.
  • By hand: docs/user/onboarding.md walks through the same steps individually, if you would rather not run an installer at all.

On curl | bash

You are piping a remote script into a shell as root, which is worth a moment's thought. It is the same trust model MiSTer's own Scripts/update.sh already uses — it fetches dont_download.sh from raw.githubusercontent.com and executes it — and the anchor is HTTPS to GitHub. The installer does not pin a hash of the updater it downloads: both come from the same repository over the same TLS connection, so a pin would add no real assurance while guaranteeing the file goes stale. If that trade isn't one you want to make, use the download-and-read form above, or the by-hand route.


What this improves

1. The kernel: five years of stable releases, and a way back to mainline

Stock forked Linux 5.15.1 in November 2021 and never took a single subsequent 5.15.y stable release. 5.15 itself reaches end-of-life in October 2026. This project tracks 6.18 LTS — the exact patch level is BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE in configs/mister_de10nano_defconfig, and it is deliberately not repeated in prose here because stable .y releases land weekly. Pinned by version and SHA-256 against kernel.org, with Renovate opening a PR on every .y bump.

The interesting part is not the version number — it's the shape of the delta. The fork's 126 reconciled commits (110 on the shipped MiSTer-v5.15 branch, 1 on upstream's own MiSTer-v6.18 branch, plus 15 residue commits that existed only on the older v5.14/v5.13.12 branches) are down to 36 carried patch files. Every remaining drop is either verifiably in mainline 6.18, replaced by a maintained package, or recorded as a deliberate decision. A 37th file, 0047, is not part of that delta at all — it backports a mainline commit (ce21a5cf3d1f, first released in 7.2) that the 6.18.y line never received.

Every commit in the fork was independently reconciled, each with a machine-readable, evidence-backed disposition record, 100% of them verified by a second independent review pass (docs/kernel-recon/). That reconciliation caught and closed several silent gaps the original triage had misclassified — Joy-Con combining, the DualSense player-ID and mic-mute/BTN_Z interfaces, stock NES/Famicom A/B button mapping, Pro-Controller-clone tolerance, fake-CSR Bluetooth dongle detection, and two config drifts (macvlan, xpad-as-module) — now carried as patches 00320037 plus config parity.

Because the kernel is stored as {pinned tarball + ordered series} rather than as a materialized tree, scripts/export-kernel-tree.sh can render it back into a Linux-Kernel_MiSTer-style git tree deterministically — the export is a build output, not a second source of truth, so upstream can consume the work without this repo forking the kernel a second time. A separate linux-patches-upstream/ series carries what that exported tree needs but our image deliberately does not (the loop= boot patch — see below).

2. Six latent bugs found and fixed

These are not forward-porting regressions. Every one is present verbatim in the 5.15 fork, and therefore in every MiSTer image shipped to date. They surfaced only because forward-porting forces you to actually read code that has been copied forward untouched since 2021.

Where The bug Status
B1 GameCube adapter Use-after-free on unplug. Teardown cancels the rumble work item but never the four per-port work_connect items, then kfree()s the adapter the handler container_of()s back into. Same defect on the probe error path. ✅ Fixed
B2 MiSTer_fb memremap() returns NULL on failure, not an ERR_PTR — the code tests IS_ERR(), which is false for NULL, so a failed mapping falls through with screen_base unset. Oops on the first fbcon draw. ✅ Fixed
B3 MiSTer audio SPI class_create()/device_create() return ERR_PTR, never NULL — so the == NULL checks are dead code and a failed device_create() was treated as success. ✅ Fixed
B4 MiSTer audio SPI Bogus diagnostics on SPI failure: the ring length is computed from a -1 read, so the status string you read to diagnose a broken SPI link is itself wrong — precisely when you need it. ✅ Fixed
B5 Logitech K400 Fn The fork's own patch reuses SetFeature at the wrong feature index, writing the wrong HID++ feature. ✅ Fixed
B6 Cyclone V cpufreq wait_for_fsm() passes a mask where wait_on_bit() wants a bit number, so it polls bit 1 instead of bit 0 — and does a plain test_bit() on __iomem. Harmless today only because the call returns immediately. ⚠️ Carried verbatim, deliberately

B6 is not fixed on purpose: making it actually wait changes the timing of a live PLL reprogramming sequence, and a forward-port is the wrong place to smuggle in an untested change to clock sequencing. It is recorded, not hidden.

Notably, all six live in MiSTer-original or fork-modified code. None is in mainline code. That is the sharpest argument for the small-delta posture this project takes.

Full write-up with the reasoning for each: docs/patch-provenance.md §10.

3. Security posture

  • OpenSSL 1.1.1 → 3.6.3. Stock ships a TLS library that has been end-of-life since 2023-09-11 and has received no upstream fixes since. This is the single strongest security argument for the whole project, and it is a plain, checkable fact.
  • Per-device SSH host keys. Every stock MiSTer ships the same host keys, baked into the public release archive, dated 2016-12-31, on a read-only root that makes ssh-keygen -A a no-op. The private host key of every MiSTer on Earth is public and identical — so SSH server impersonation is trivial and produces no host-key warning. This image generates unique keys on first boot and persists them to an ext4 image on the FAT partition, reusing stock's own proven mechanism for Bluetooth pairing keys. (ADR 0015)
  • OpenSSH 8.6p1 → 10.5p1, Samba ~4.14 → 4.24.5, BlueZ → 5.79, wpa_supplicant 2.9 → 2.12 — the network-facing surface, several release cycles of hardening each.
  • Python 3.9 → 3.14.7 — the on-device interpreter that runs the Downloader and community scripts (compatibility tested; see docs/python-compat.md).
  • An actual update path. Renovate tracks the kernel, firmware, and every pinned package; CI proves each bump still builds and still passes the parity suite. Stock's security model is "the image is frozen."

What has not changed: the root password is still 1, deliberately, for stock parity. That is publicly known and always has been. See the FAQ for the plain-language version of what that means on an untrusted network.

4. Storage, filesystems, and networking

Capability Stock Here
exFAT Out-of-tree Samsung driver (also handled FAT12/16/32) Mainline exfat, with the Samsung symlink extension carried as patch 0031 (ADR 0010, ADR 0019)
exFAT repair None. exFAT has no journal, the card is never cleanly unmounted (even the OSD's "Reboot" is a raw HPS reset-controller write), so lost clusters accumulate and nothing ever checks Scripts > check_storage.sh — a read-only scan while running, and, only if it finds something and you confirm, a one-shot fsck.exfat on the next boot from the initramfs, the only place a repair can run (ADR 0026)
Logitech pairing None. hid-logitech-dj drives every already-paired device, but the pairing handshake has no kernel interface, so a replacement or second-hand Unifying receiver cannot be bound to a device on-box at all Scripts > pair_logitech.sh — enumerates every receiver, refuses the families it cannot drive (Bolt, Lightspeed) by name, and confirms the result from the receiver's own slot table (docs/logitech-pairing.md)
NTFS Not supported ntfs3 module + ntfs-3g automount via util-linux mount (ADR 0013)
USB automount Debian usbmount 0.0.24 Buildroot usbmount, functionally identical, plus NTFS (docs/usb-automount-parity.md)
Archive extraction 7zr = p7zip 16.02 (2016); the updater additionally fetches the same 2016 build over the network as /media/fat/linux/7za 7-Zip 26.02 as 7zz (+7za), and a static copy shipped to /media/fat/linux/7za so nothing is fetched (ADR 0023)
CIFS kernel mounts No mount.cifs shipped cifs-utils included — a deliberate, documented beyond-parity addition for community storage scripts (docs/netfs-parity.md)
Wi-Fi Six out-of-tree vendor forks plus in-kernel mac80211 USB drivers for MediaTek/Ralink/Marvell/older Realtek; no Broadcom, no Wi-Fi 6/6E, no Atheros USB Wi-Fi, no Redpine Mainline drivers for every chip mainline can drive that is worth building here (six symbols deliberately left off — see below), plus chipset families stock never supported at all. One out-of-tree driver left — see below

Wi-Fi and Bluetooth hardware support

The short version: if your dongle is Realtek, Broadcom/Cypress, MediaTek, Atheros/Qualcomm, or Redpine, a driver for it is almost certainly built into this image — with two named exceptions, both called out below: the newest Realtek Wi-Fi 6E chip (RTL8852CU — a driver is in the tree, but it is not even build-verified yet) and Qualcomm/Atheros QCA9377 over USB (no driver built at all, deliberately, because upstream says its own driver does not work).

Stock's out-of-tree set was six Realtek chips, each via its own vendor fork — but that was not all it had: it also carried in-kernel mac80211 USB drivers for MediaTek (mt7601u, mt76x0u, mt76x2u, mt7663u), Ralink (rt2500usb, rt73usb, rt2800usb), Marvell (mwifiex_usb, libertas/usb8xxx) and older Realtek parts (rtl8xxxu, rtl8192cu, rtl8187), per stock-mods.txt. What it had none of was Broadcom, Wi-Fi 6/6E, Atheros USB Wi-Fi, or Redpine — and for at least one chip it did have a driver for, it shipped no firmware, so the driver probed and then failed (MT7663U; same story for ath3k on the Bluetooth side).

Getting there was a from-scratch, 36-symbol audit of every USB Wi-Fi driver Linux 6.18 offers, cross-checked against every USB device-ID table in drivers/net/wireless/ so the answer is evidence, not recollection (docs/wifi-parity.md §7), plus the same method applied a second time to Bluetooth firmware (docs/bluetooth-parity.md §9).

Read the last column before the rest — it says how far each row was actually checked, and most of this table is newly added, unreleased hardware support that has not yet had a device plugged into it:

If your dongle uses… Stock MiSTer This image Checked how far?
Realtek 802.11n/ac — RTL8188EU/8188FU, RTL8710BU, RTL8811AU/8821AU, RTL8812AU, RTL8814AU, RTL8821CU, RTL8822BU/8822CU, RTL8723DU Out-of-tree vendor fork for six of them (RTL8188EU, RTL8188FU, RTL8811AU/8821AU, RTL8812AU, RTL8821CU, RTL8822BU — ADR 0016, stock-mods.txt); several of those can't do WPA3 at all. No driver at all for RTL8710BU, RTL8814AU, RTL8822CU, RTL8723DU: none of the six forks covers them, stock-mods.txt carries no rtw88 module, and stock's own kernel branch (MiSTer-v5.15) has an rtl8xxxu/ with only the 8192c/8192e/8723a/8723b chip files — no 8710b In-kernel rtl8xxxu / rtw88 (mac80211) Hardware-verified: an RTL8822BU auto-connects to a WPA3-only 5 GHz network at boot. (The fork it replaced advertised SAE but failed WPA3-only association with status_code=1 — hardware-verified regression the switch fixed, ADR 0016.) Every other chip in this row builds the same way but has not been tested against real hardware.
Realtek Wi-Fi 6 — RTL8851BU, RTL8852BU Not supported In-kernel rtw89 Build-verified only
Realtek Wi-Fi 6E — RTL8852CU / RTL8832CU Not supported Out-of-tree rtl8852cu-morrownr — the only driver that exists for this chip on USB anywhere, in or out of tree: mainline's rtw89 has the chip's radio HAL but only a PCIe bus file, and this board has no PCIe Not yet built, let alone tested — added by reading the driver's source, not compiled (docs/wifi-parity.md §8)
Broadcom / Cypress — BCM43xx, CYW43xx USB adapters Not supported at all In-kernel brcmfmac Build-verified only
MediaTek Wi-Fi 5 — MT7663U (802.11ac) Driver was shipped (mt7663u, mt7663-usb-sdio-common, mt7615-common are all in stock-mods.txt) — but none of the four firmware files it requests (stock-fw.txt has no mt7663* at all), so it probed and then failed at request_firmware() Same in-kernel mt76 driver, plus the four mediatek/mt7663* blobs it actually loads (wifi-parity §6.2) Build-verified only
MediaTek Wi-Fi 6 / Wi-Fi 7 — MT7921U (802.11ax), MT7925U (802.11be, 6 GHz-capable) Not supported In-kernel mt76 (mt7921u / mt7925u) Build-verified only
Atheros — AR6003/AR6004, AR9271/AR9287 (ath9k_htc), AR9170 (carl9170) Not supported In-kernel Build-verified only
Redpine RS9113 / RS9116 Not supported In-kernel rsi Build-verified only
RTL8192DU (dual-band 802.11n) Not supported In-kernel rtlwifi Build-verified only
Bluetooth — MediaTek MT7921/MT7925 combo, Qualcomm QCA Rome 6174A (USB), one Broadcom .hcd, Atheros AR3011/AR3012, plus Realtek RTL8761B/BU and every Wi-Fi+BT combo chip above ath3k built with no ath3k-1.fw, no ar3k/*.dfu; no MediaTek MT79xx or Qualcomm QCA blobs. Its Realtek combos were covered Firmware shipped for every Bluetooth driver here where an upstream blob exists and the hardware is reachable on this board. Two built drivers deliberately ship without it: Intel btintel (30 MiB of intel/ibt-* for silicon that only comes on M.2 cards this board cannot host — CONFIG_BT_HCIBTUSB selects the driver unconditionally, so it cannot be turned off) and bcm203x (a ~2003 BCM2033 whose BCM2033-MD.hex/BCM2033-FW.bin upstream linux-firmware does not carry at all). Both are recorded in bluetooth-parity §9's "Deliberately NOT shipped" General Bluetooth pairing is hardware-verified; these specific chipset gaps were closed by reading each driver's firmware-request code, not by plugging one in

One deliberate exclusion, not an oversight. ATH10K_USB (Qualcomm/Atheros QCA9377 over USB — e.g. the Linksys WUSB6100M) is the one modern 802.11ac USB chip this image does not build a driver for. Upstream's own kernel Kconfig calls it out: "Currently work in progress and will not fully work." Binding a dongle to a driver that then fails is worse than binding it to nothing, so it stays off. Every other driver family the audit found — 30 of 36 — is built. Of the six that are not, this is one; four are 2000s-era 802.11b/g parts (AR5523 ~2004, AT76C50X_USB ~2001, P54_USB ~2004, ZD1211RW ~2005) with no realistic install base today; and the sixth, PLFXLC, is pureLiFi optical hardware that happens to live under drivers/net/wireless/ without being Wi-Fi at all (docs/wifi-parity.md §7).

Why "build-verified" and "hardware-verified" are called out separately, and not glossed over: this table represents a large, single-branch expansion — several new chip vendors, a new Wi-Fi generation, and a Bluetooth firmware pass — landed and documented before a second device could be sourced and tested against most of it. The driver source and firmware request paths were read line-by-line for every entry above (file:line citations in docs/wifi-parity.md and docs/bluetooth-parity.md), and scripts/ci-tests.sh asserts that the newly-added modules and firmware land in the built image: the seven rtw88 USB modules, brcmfmac+brcmutil, rtl8192du/ath6kl_usb/ rsi_usb, the one permitted out-of-tree 8852cu.ko, and a fixed blob list (MT7663's four files, ath3k-1.fw, the four brcmfmac*.bin, rtl8192dufw.bin, two rsi/*.rps, the three MediaTek BT ram-codes, the QCA USB pair, both .hcds, rtl8761b/rtl8761bu). It does not yet assert rtw89_8851bu/ rtw89_8852bu, mt7663u/mt7921u/mt7925u, ath9k_htc or carl9170, nor the rtw89/*.bin, htc_9271.fw/htc_7010.fw, carl9170-1.fw and ar3k/*.dfu blobs those need — all of which do ship, but unguarded. Concretely: the Realtek Wi-Fi 6 row has no CI presence check at all; the MediaTek Wi-Fi 5 row has its firmware checked but not its module; the MediaTek Wi-Fi 6/7 row has neither; and the Atheros row is covered only for ath6kl_usb, not for ath9k_htc/carl9170. And even where CI does check, a file landing in the image is not the same thing as a dongle actually associating on this board. The hardware validation ledger is the single current source of what has been touched with real hardware; treat anything not listed there as unverified in practice, exactly as that section says.

5. Build, release, and supply-chain engineering

This is the half of the project that has no stock counterpart at all, because stock publishes no build recipe.

  • No vendored Buildroot, ever. The Makefile downloads the pinned release, verifies its SHA-256 against upstream's GPG-signed release manifest, unpacks it under work/, and forwards every target into it. A hash mismatch refuses to unpack and tells you — at length — why pasting the new hash in is the wrong fix.
  • Byte-identical reproducible builds. Two independent builds of the same commit produce identical linux.img and zImage_dtb, and a dedicated CI workflow exists to prove it. This is what makes the four layered build caches trustworthy as inputs only. (docs/reproducibility.md)
  • Full SBOM. Every release ships legal-info.tar.gz: manifest.csv (package, version, license, upstream URL), license texts, and the upstream source tarball of every package conveyed — the GPL accompanying-source obligation, discharged properly. The same manifest is submitted to GitHub's dependency graph on release, so the package list is browsable at Insights → Dependency graph without downloading anything (docs/ci.md#dependency-graph-submission).
  • Provenance attestation on the release images.
  • No binaries in git. Ever. Firmware, payloads, and stock blobs are fetched at build time, pinned by commit or release and hash.
  • Renovate (renovate.json, docs/renovate.md) with a companion hash-sync workflow, because several pins need a companion hash that a naive version bump would leave stale. Kernel -rc bumps fail closed on a missing hash by design, requiring a human pin.

6. Things that are deliberately identical to stock

Parity is a feature, and most of the work is invisible for exactly that reason. This image reproduces stock's behaviour — not merely its file list — for: the MiSTer binary's ABI and every SONAME it links, the boot chain and U-Boot contract, /media/fat mount flags (sync,dirsync,noatime,nodiratime — mounting async would be a real power-off corruption regression), the read-only root with the login-time remount,rw, the Bluetooth key store, the firmware set, the init-script set, busybox applet coverage, the RTC, MIDI/MT-32, Samba, SSH/FTP, /MiSTer.version semantics, and the Downloader's update contract.

Each of those has its own audited parity document — see the documentation map. The uboot.img in the default release channel is shipped byte-identical to stock's, fetched by hash.


What is not better — the honest list

  • One known regression. The Logitech G923 PlayStation variant loses force feedback and range control; steering, pedals and buttons still work as a plain joystick. The G923 Xbox variant and all G29/G27/G25 wheels are fully supported with force feedback intact. (docs/patch-provenance.md §9.3)
  • Not all hardware is validated. Samba and MIDI are build- and CI-verified only. One board, one set of peripherals. The same is true of most of the Wi-Fi/Bluetooth chipset table — one chip family (Realtek rtw88) has actually associated on hardware; the rest, including the whole Broadcom, MediaTek, and Wi-Fi 6/6E additions, compile but have not had a dongle plugged into this board — and CI's presence checks do not even cover all of them (rtw89_885*bu, mt7663u, mt7921u/mt7925u, ath9k_htc, carl9170 and their firmware are unasserted; see the paragraph under that table). The matrix in docs/testlogs/p3-matrix.md is the truth; this README is a summary of it.
  • This forward-port has already shipped one real bug, on hardware. Early builds auto-overclocked the board to 1.2 GHz on boot and produced hard hangs. The carried socfpga overclock patch was written against 5.15, where CPUFREQ_BOOST_FREQ kept the governor off the boost rows by default; on 6.18 the default policy->max resolves differently, so the overclock became default-on. It applied cleanly, compiled clean, and behaved differently. Fixed in PR #24 — proper ->set_boost, boost-only 1000/1200 MHz rows, 800 MHz default — and the board has been stable since. The lesson generalises: any other 5.15-era patch we carry can have the same class of defect, and no amount of CI catches a silent semantic change to a kernel flag. This is the strongest argument for the small-delta posture, and the reason the hardware list stays short.
  • RT is a developer variant. PREEMPT_RT boots and runs on hardware, but no latency measurement has been taken yet — so there is currently no evidence it improves anything for a normal user. It exists for testing, not for daily driving.
  • Debug tooling is temporarily in-tree. gdb/strace/perf/rt-tests and CONFIG_COREDUMP are enabled for two investigations. One of them — a field hard-hang — closed on 2026-07-21; the RT latency measurement has not been taken, so the block stays for now. It costs image size and diverges from stock's config, and is designed to revert as one unit. (docs/debug-tooling.md)
  • The sustainability gate is not met. Nobody has yet signed up, in writing, to track 6.18.y security releases through end-of-life. Until that happens this is a personal project, and saying otherwise would be the one claim that undermines all the others. (ADR 0014)
  • Version numbers drift, and two parity analyses are currently behind the pins. Every figure here was read off the built tree at the time of writing, but nothing in CI asserts a package version, so a Renovate or Buildroot-line bump can move a package out from under the document that reasoned about it. That has already happened twice. The Buildroot 2026.05.1 bump moved Samba 4.23.8 → 4.24.3 and ProFTPD 1.3.8d → 1.3.9a without re-running the P3.6 / P3.7 audits that gated those packages. The 2026.05.2 bump then moved OpenSSH 10.3p1 → 10.5p1, crossing the skipped 10.4 release where upstream made a failed seccomp prctl() fatal rather than a debug() — which broke every SSH connection on the shipped image and forced a release to be pulled. Buildroot classified that bump as openssh: (no CVE assigned). Both parity documents now say so at the top, with the specific unchecked question named. The defconfig and Makefile pins are the ground truth; the prose is a dated reading of it. For the kernel this is handled by not writing the number down: narrative prose says "6.18 LTS", and BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE in the defconfig is the only place the .y lives.

How it is put together

Repository layout

Makefile                 wrapper: fetches + hash-verifies Buildroot, forwards targets
Config.in / external.mk  BR2_EXTERNAL definition for the 16 in-tree packages
configs/                 mister_de10nano_defconfig  (the shipped image)
                         mister_kernel_defconfig    (kernel-only base, shared by variants)
                         mister_rt.fragment         (PREEMPT_RT / 7.x delta)
                         mister_initramfs_defconfig (stage-1 cpio)
                         mister_installer_defconfig (SD-card installer cpio)
board/mister/de10nano/
  linux.config           minimal kernel defconfig  (an absent CONFIG_X is NOT "off")
  linux-patches/         37 carried patches: 36 MiSTer + 1 mainline backport (0047)
  linux-patches-beta/    36 of those (32 symlinks + 4 re-anchored 7.x copies; 0047 is
                         already in 7.2) + 4 beta-local = the 40-entry series
  linux-patches-upstream/what the exported tree carries but our image must not
  rootfs-overlay/        init scripts, sshd wiring, MiSTer-specific files
  post-build.sh          /MiSTer.version stamping, parity fixups
  post-image.sh          linux.img assembly + contract checks
package/                 16 packages: Realtek Wi-Fi, xone, libchdr, lzma-sdk, midilink, munt…
scripts/                 the verification suite, hash-sync, SD-card builder, kernel export
docs/                    ADRs, parity audits, the kernel reconciliation, user docs
.github/                 9 workflows + 4 composite actions

Two kernels, one image

The DE10-Nano's Cyclone V is a dual-core Cortex-A9 (ARMv7-A, 32-bit) — there is no AArch64 path on this silicon, and PREEMPT_RT cannot be a boot-time toggle on ARM32. So RT must be a separately compiled kernel image. PREEMPT_RT for 32-bit ARM merged into mainline in Linux 7.1, which means on 7.2 it is a plain kconfig option with no out-of-tree RT patch to carry.

The design (ADR 0021): make rt runs a kernel-only Buildroot build into output-rt/, hard-asserting CONFIG_PREEMPT_RT=y in the result, then stages its depmod'd module tree into an overlay that the next make all folds into the one shipped linux.img. You get zImage_dtb and zImage_dtb-rt as separate boot images, both of whose modules live in the same rootfs, selected on-device via u-boot.txt. CI builds the variant on every gated run, so a PR that breaks make rt goes red instead of rotting silently.

zImage_dtb-rt is a manual download from the Release page — it is deliberately not on the sdcard.img installer payload and has no db.json entry, so no device is ever pushed an unvalidated real-time kernel. Because its modules are already in the shipped linux.img, opting in is: download it, drop it in /media/fat/linux/, add one u-boot.txt line.

The initramfs that deleted a kernel patch

Stock patches init/do_mounts.c so the kernel itself parses a loop= boot parameter, mounts /media/fat, and loop-mounts linux/linux.img as the root filesystem. That is a patch to the most delicate part of early boot, carried forever, for a job userspace does better.

This project replaces it with a two-stage boot: a tiny BusyBox initramfs, embedded in the kernel via CONFIG_INITRAMFS_SOURCE, does the mount dance in a shell script — with rootwait retries, vfat-then-exfat probing, and a rescue shell on failure. One patch deleted, one whole class of boot failure made debuggable.

The details matter more than they look: -o sync,dirsync is not a tuning choice (async would be a real power-off-corruption regression), the loop device must stay writable even though the rootfs is mounted read-only (or /etc/profile's login-time remount,rw breaks), and mount -o move is required because BusyBox mount has no long options. All of it is annotated in PLAN.md §5 and docs/loop-boot-6.18.md.

Forward-looking: the stock patch is 32-bit-ARM-only by construction. Stock's loop_setup() issues the loop attach as a direct call into the syscall entry points — sys_ioctl(device_fd, LOOP_SET_FD, file_fd) then sys_close() — from inside init/do_mounts.c. That compiles on the DE10-Nano's Cortex-A9 only because 32-bit ARM still defines sys_ioctl() as an ordinary C function. Every 64-bit ARM kernel since 4.19 selects ARCH_HAS_SYSCALL_WRAPPER, which turns each syscall into __arm64_sys_ioctl(const struct pt_regs *) and drops the C-callable sys_* prototypes altogether (include/linux/syscalls.h declares them only under #ifndef CONFIG_ARCH_HAS_SYSCALL_WRAPPER). On aarch64 the stock patch does not merely misbehave; it has no symbol to call and cannot build. The kernel's sanctioned replacements for init-time work — the init_mount(), init_mkdir(), init_chdir() family in fs/init.c — deliberately include no init_ioctl(), because an ioctl is driver-defined and there is nothing generic to wrap, and the internal vfs_ioctl() / blkdev_ioctl() paths are private to fs/ and block/. So the only way to keep loop= in-kernel on a 64-bit ARM SoC is the rewrite this project already had to do for 6.18: export a purpose-built attach helper from the loop driver itself and call it with filp_open()ed files instead of descriptors (linux-patches-upstream/0100-…, carried for the upstream fork, not shipped in this image). The initramfs design, by contrast, contains no architecture-specific line at all: mount, losetup, and switch_root behave identically on any CPU the kernel runs on. Should this image ever need to boot something that is not a Cyclone V, the boot path comes along unchanged, and the kernel carries one patch fewer than it otherwise would.

The full SD-card image

make sdcard produces a complete, dd-able sdcard.img — a two-partition MBR with the data partition on p1 and a type-0xA2 partition carrying uboot.img raw at its start, per the SPL contract. The image ships small, so the compressed asset stays small and the write is fast; a throwaway installer OS then reformats the card to exFAT at the device's real size on first boot — because Linux cannot grow exFAT in place, and exFAT is what every mr-fusion'd card in the wild uses. That is not an invention: it is mr-fusion's own mechanism, adopted rather than replaced, per the standing principle of reusing a proven reference implementation in anything on the boot path. (ADR 0020)

What that first boot looks like. The installer draws a progress splash — banner, nine numbered steps, a bar and an elapsed clock — on the serial console, and blinks the DE10-Nano's on-board HPS_LED at about 0.5 Hz through the long steps. Solid on is the only state that means trouble (stopped — failed, or the card was already installed); a dark LED is just a gap between phases, or the reboot. Note HPS_LED is on the board itself, so a fully-enclosed case will not show it.

The card also carries menu.rbf at the FAT root, which is what U-Boot loads into the FPGA before Linux starts — without it the fabric is never configured and HDMI reads "no signal" for the whole install (ADR 0020 §7). There is still deliberately no splash picture on that output: /dev/fb0 is only scanned out once Main_MiSTer programs the frame reader, and Main_MiSTer does not run in the installer. Painting a picture there would mean shipping mr-fusion's entire stack — its U-Boot, a second DTB and a framebuffer bitstream — a trade ADR 0020 §6 explains and declines.

Install time does not scale with card capacity — a 1 TiB card formats as fast as an 8 GiB one (mkfs.exfat writes 88 KiB–1.15 MiB of metadata either way, in ~10 ms). The dominant cost is the fixed 512 MiB linux.img expansion, so what matters is the card's write speed, not its size.

It ships as a separate release asset, never inside release_*.7z and never referenced by db.json, because its blast radius is the bootloader. The payload it installs is inventoried against real mr-fusion output in docs/verification/sdcard-payload.md.


Building it yourself

make                            # prints help — deliberately NOT a build
make mister_de10nano_defconfig  # load the config
make all                        # build (first run bootstraps a cross-toolchain — hours, not minutes)

Two things that will bite you otherwise:

  • make on its own prints help rather than building. A reflexive bare make in a Buildroot tree with no config starts a full x86 toolchain build that nothing here wants. Use make all — it runs the stage-1 initramfs build first, then the image.
  • Do not pass -j. Buildroot's top level is not parallel-safe; it parallelises each package internally, defaulting to your CPU count. CI runs a bare make all.

Useful targets

Target What it does
make all The shipped image: linux.img + zImage_dtb
make rt Kernel-only PREEMPT_RT build → zImage_dtb-rt + module overlay
make sdcard Full sdcard.img(.xz) — run after make all. The card carries no variant kernel, so make rt is not required first; if you do build RT, run it before make all so its modules land in the image
make initramfs Stage-1 cpio only, and print its size
make menuconfig / linux-menuconfig Interactive Buildroot / kernel config
make savedefconfig Write the config back to the defconfig (always do this after editing)
make buildroot-verify Download + SHA-256-verify the pinned Buildroot tarball
make buildroot-showsig Print upstream's GPG-signed manifest — the only valid hash source
make legal-info Generate the SBOM
make clean / distclean Buildroot's own meanings, applied across every output dir (dl/ is kept — it's a shared cache)

Host requirements

Standard build tools (gcc, make, bc, flex, bison, cpio, rsync, unzip, wget/curl, python3), plus dtc, qemu-user/qemu-system-arm, and shellcheck for the test and inventory scripts.

One sharp edge. Buildroot refuses to build if /usr/bin/install is uutils coreutils 0.8.0 — it detects that exact version and rejects it (upstream bug uutils/coreutils#12166). Debian/Ubuntu's coreutils-from-uutils package installs precisely that as the default install. You do not need to fix this yourself and you should not need sudo: the Makefile detects it and transparently shims a GNU install into PATH for Buildroot only (work/.hostshim/), touching nothing outside this repo. It is inert on a host whose install is already GNU, and fails fast with a clear message if no GNU install exists at all.


Verification & CI

Nothing here is asserted by hand. The parity claims above are backed by scripts that run on every push and pull request.

The test suite

scripts/ci-tests.sh is one command that runs the whole non-hardware suite and prints a self-contained digest last (| tail -n 30 tells you what broke and why, without grepping):

  • the image-contract checks — check-zimage-dtb.sh, check-linux-img.sh, check-size-budget.sh
  • the structural initramfs checks, plus a full QEMU boot test of the initramfs /init (booted six times, across the failure paths)
  • an ABI smoke test running the stock MiSTer binary under qemu-user against the built rootfs: dynamic linking must resolve clean, and it must die at FPGA access and not one instruction earlier
  • per-service parity assertions harvested from each Phase 3 parity document, checked against the shipped rootfs.tar — not against output/target/
  • a sandboxed functional test of the timezone autodetect dhcpcd hook (scripts/test-timezone.sh) — it turns a string off the network into a filesystem path, so every class of hostile answer is asserted rejected, run twice: under the host shell and under the target's own BusyBox ash via qemu-arm

Alongside it, scripts/check-abi.sh runs the full SONAME/loader checklist from docs/abi-contract.md. The overlap on the two highest-value gates is deliberate.

The pipeline

Workflow Trigger Job
build.yml push to master, every PR Kernel-variant matrix, then the image, then the parity + ABI suite — plus the patch-header lint and the kernel-defconfig sync check
release.yml v* tags Rebuilds from scratch (never adopts a CI run), assembles release_YYYYMMDD.7z, drafts the GitHub Release
publish-db.yml release published Regenerates db.json, schema-checks it, deploys it to GitHub Pages
reproducibility.yml manual dispatch Proves two independent builds are byte-identical. Manual on purpose: it is a double build, and its input caches are only warm after build.yml has run
renovate-hash-sync.yml Renovate PRs Refreshes companion hashes a version bump leaves stale; validates the one pin that has none (the _Console cores snapshot)
renovate-validate.yml config changes An invalid Renovate config makes Renovate skip the repo silently — this is the only signal you would ever get
fork-sync.yml weekly Diffs the last-reconciled fork commits against upstream's live HEADs and keeps one issue updated with the backport queue
lint.yml push, PR actionlint over the workflows, shellcheck over scripts/** and the composite actions' run: bodies
cache-prune.yml PR closed, daily Deletes the ~5 GB Actions cache scope a closed PR leaves behind — dead PRs were evicting master's warm dl/ under GitHub's 10 GB LRU ceiling

The full rationale, incident history, and measured numbers live in docs/ci.md — the workflows keep the imperative and the run ID, that document keeps the narrative and the "we tried X and it failed."


Releases & distribution

Release artifacts are published as GitHub Release assets; no binaries live in git. The main set is seven files (the Downloader-contract set: the release_YYYYMMDD.7z, linux.img, zImage_dtb, both .configs, legal-info.tar.gz, SHA256SUMS), plus three -rt files per kernel variant, plus the separately contracted sdcard.img.xz.

Distribution is opt-in and requires zero cooperation from anyone: a community db.json, served from GitHub Pages, that the standard on-device Downloader reads.

Coexistence with the official channel is the hard part, and it is solved (ADR 0025). The Downloader applies at most one Linux image per run, and distribution_mister always offers one too. When several databases offer one, it sorts the candidates by their position in downloader.ini and takes the first — and since drop-in databases are merged after the base ini's own sections, and Update All pins [distribution_mister] to the top on every rewrite, the official image wins whenever that database is actually processed — which is most real runs, since its content changes whenever any core does. (A run where nothing changed skips the database entirely and applies no Linux image at all: can_skip_db returns true when file_checking is FASTEST, the steady-state default, and a skipped database's linux entry is never looked at.) Not a race that might go our way: a loss whenever it is contested, silently reverting the user on a routine core update.

So this project doesn't compete for that slot; it closes it:

  • [MiSTer] update_linux = false in downloader.ini stops every normal run from applying any Linux image. Cores, ROMs, MRAs and Jotego keep updating untouched.
  • Scripts/update_linux_modernization.sh runs the Downloader against its own private ini naming one database, so there is nothing to sort and nothing to lose.
  • That is the entire mechanism. No boot script, no daemon, no state files: the running system already tells you what you need (/MiSTer.version says which image, and update_linux says whether it is protected), and nothing re-applies the setting behind you — the updater is the only thing that writes it, and only when you run it. Which is also why reverting is one edit, with nothing to opt out of.

Verified on hardware: a full update_all.sh run installed 379 cores, rebooted, and came back with linux.img and zImage_dtb byte-identical, the Downloader's own config dump recording update_linux: false and UPDATE_LINUX: undefined — Update All never overrides it. The shipped downloader.ini also reproduces Update All's default database set plus Jotego, so a fresh sdcard.img flash needs no user action at all. Rolling back is one command, update_linux_modernization.sh --restore-stock — see docs/user/rollback.md.

One subtlety worth calling out because getting it wrong bricks the update loop: stock's Downloader compares versions with a strict string inequality, not an ordering, and /MiSTer.version is derived from SOURCE_DATE_EPOCH — which reproducible builds pin to a constant. Two releases from the same Buildroot pin would therefore have carried an identical version string, and a user would never be offered the newer one. Both the image stamp and the published version now derive from the tagged release date instead. (ADR 0018, docs/db-json-versioning.md)

Start here if you want to run it: one-command install · docs/user/onboarding.md · docs/user/rollback.md · docs/user/faq.md · docs/user/serial-recovery.md


Goals

# Goal
G1 A linux.img + zImage_dtb that boots the unmodified, stock MiSTer binary
G2 Modern kernel on a supported LTS with a real security-update path
G3 Modern package set (Buildroot 2026.05) with a real security-update path
G4 No separate kernel repo. All kernel patches live as .patch files in the Buildroot external tree, applied to a pristine kernel.org tarball
G5 Fully reproducible: pinned Buildroot, pinned kernel + hash, checked-in .config, published SBOM
G6 Release artifacts published as GitHub Release assets. No binaries in git. Ever.
G7 Opt-in distribution via a community db.json — zero cooperation required

Documentation map

Start here

Document Read it when
PLAN.md You want the goals, the ABI contract, the design rationale, and the risk analysis
TASKS.md You want the phase-by-phase execution plan with acceptance criteria
CONTRIBUTING.md You are about to change something
docs/user/ You are running this on real hardware

The kernel

Document Contents
docs/patch-provenance.md Every carried patch: origin, upstream status, disposition
docs/kernel-recon/ The full per-commit reconciliation — 123 evidence-backed records, the ledger, the silent-regression triage, and the reproducible pipeline that produced them
docs/kernel-config-deltas.md Every kernel-config divergence from stock, and why
docs/rt-beta-kernel.md The PREEMPT_RT / 7.x variant
docs/loop-boot-6.18.md Why the fork's loop= patch cannot be ported to 6.18, what porting it would cost, and what we did instead — written for whoever maintains the fork
MISTER-KERNEL-PATCH-RECON.md The task spec the reconciliation was executed from

The contracts we must not break

Document Contents
docs/abi-contract.md What the kernel and rootfs must honor for the stock binary to run
docs/boot-chain.md U-Boot contract and its kernel-config implications
docs/uboot-mainline-port.md The plan for a mainline U-Boot built to behave like stock's — design, measured port surface, brick modes. Ships nowhere
docs/uboot-tasks.md Execution tasks for the above (U0–U7), split out of TASKS.md
docs/downloader-contract.md How the on-device Downloader decides to update
docs/package-manifest.md All 251 stock SONAMEs mapped to a package, with major-bump flags
docs/stock-inventory/ The audited inventory of the stock image everything above is measured against

Parity audits — one per subsystem: bluetooth · wifi · firmware · init · usb-automount · netfs · samba · ssh-ftp · midi/mt32 · rtc · util-linux · stock reconciliation — the cross-subsystem one, diffed file-by-file against stock's actual firmware.tar.gz/ modules.tar.gz/addon.tar and the base rootfs, source of the hardware coverage numbers above

Engineering

Document Contents
docs/ci.md The pipeline, the caching traps, and the incident index
docs/reproducibility.md What byte-identical means here, and the four mechanisms that deliver it
docs/renovate.md What Renovate manages, the automerge posture, the hash rules a human must follow
docs/size-budget.md Image headroom and where the bytes go
docs/version-delta.md Five years of upstream movement, package by package
docs/main-shared-libs.md Shared-library coverage for Main_MiSTer
docs/dualsense-tooling.md dualsensectl, and why the DualSense kernel patches are not replaceable by udev/userspace
docs/logitech-pairing.md ltunify + mister-pair-logitech — the one Logitech gap the kernel has no interface for, and why not Solaar
docs/azcopy.md azcopy for off-device backup — what it costs in image space, and what building it for an architecture Microsoft does not support took
docs/debug-tooling.md temporary — the debug block and how to revert it as one unit
docs/decisions/ The ADRs: the open questions, the trade-offs, and who decided what

Contributing

Contributions are welcome once the Phase 4 publication gate is passed. Until then this is a personal-use project.

When it opens, all contributions must follow the discipline in CONTRIBUTING.md: patch provenance tracking, developer sign-off (DCO), and the standing rules from TASKS.md — reproducibility, no vendored binaries, hash-pinned upstream sources, and no behaviour changes smuggled into build fixes.

See PLAN.md §13 for the sustainability requirements and the risk discussion behind the gate.


License layering

  • Repository code (Buildroot external tree, scripts, overlays) — GPLv3, see LICENSE
  • Kernel patches (board/mister/de10nano/linux-patches*/) — GPLv2, because they modify the Linux kernel
  • Packages in package/ — inherit their upstream licenses (GPLv2, BSD, MIT, …); the legal-info artifact in each release is the complete, authoritative SBOM

About

Buildroot image for running the MiSTer FPGA project on DE10-Nano

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages