Mainline Linux (6.12, NOMMU, XIP) running on the Arm Corstone SSE-300 /
Cortex-M55 with 2MB of RAM, with Helium (MVE) usable from userspace
and the Ethos-U55 NPU driven directly from a Linux kernel driver.
Everything here is verified both on QEMU (mps3-an547) and on real
silicon (Arm MPS3 board, AN547 FPGA image).
Measured on the board:
| Kernel image (XIP, flash) | ~1.0 MB — text/rodata never touch RAM |
| Boot-time MemFree | 724 KB of 1408 KB managed RAM |
| Userspace | busybox + uClibc-ng, FDPIC, text XIP from flash |
| Helium | MVE context-switched per task; 3-thread stress clean |
| Ethos-U55 | DS-CNN KWS inference bit-exact vs TFLite reference; YOLO-fastest face detection; NPU fetches weights in place from QSPI XIP |
| Warm reboot | busybox reboot -f → shell in ~2 s (SYSRESETREQ) |
This repository contains only what is needed to build and boot that system: kernel config + patches, the Ethos-U user tooling, a minimal rootfs, a bootwrapper, and a QEMU smoke test. It is meant as a base for porting to other Cortex-M55 silicon (Himax WiseEye2, Infineon, ST — following the layout of rota1001/stm32f429-linux).
linux.config kernel config (XIP + NOMMU + slab diet)
buildroot.config FDPIC uClibc-ng toolchain + busybox
busybox-extra.fragment applets (top/vi/free/... + setsid/cttyhack)
patches/ all kernel changes, wired in by apply.sh
init/ /sbin/init + romfs image builder
bootwrapper/ tiny ITCM loader (DTB copy + HW probe flags)
ethos-u/ vela-model extractor + on-target runner
qemu/ run script + expect boot check
| File | Why the kernel needs it |
|---|---|
mps3-an547*.dts |
No SSE-300/MPS3 board support exists upstream. XIP memory layout, flash partitions, NPU node, arena carveout |
timer-sse300.c |
SSE-300 System Timer clocksource/clockevents (counter is stopped at reset; driver starts it) |
vfp-m.c, vfp-m-asm.S |
M-profile FP/MVE (Helium) context switch (CONFIG_VFP_M). Mainline VFP code is A-profile only — without this, userspace FPU/MVE state is corrupted across task switches |
faults-v7m.c/.S |
Route v7m HardFault/MemManage/BusFault/UsageFault to user signals with CFSR/BFAR diagnostics (mainline: __invalid_entry, i.e. a silent hang) |
v7m-reboot.c |
SYSRESETREQ restart handler for DT-generic boards (mainline only wires it via machine descriptors) |
ethosu-npu.c, ethosu_uapi.h |
/dev/ethosu: direct-register Ethos-U55 driver (see below) |
patches/apply.sh <kernel-tree> copies the files and edits the
Makefiles/Kconfigs idempotently.
QSPI flash 8MB @0x28000000 BRAM 2MB @0x01000000 (all the RAM)
0x000000 xipImage (kernel XIP) kernel .data/.bss ~121K
0x1F0000 DTB MemTotal 1408K
0x200000 romfs 6MB partition carveout@0x1188000 448K (NPU arena)
(userspace text XIP) DTB copy 32K
Three mechanisms make 2MB enough: XIP (no code in RAM), NOMMU pointers
being physical (framebuffer/model/arena all zero-copy), and a
reserved-memory carveout for the NPU arena (a NOMMU malloc(443K)
needs one power-of-two 512K buddy block and will fail — carve it out
instead).
Linux runs on the Cortex-M55 that would normally be the NPU's helper
MCU, so there is no mailbox and no firmware: ethosu-npu.c programs
the U55 registers directly (reset → PROT check → AXI limits → QBASE /
BASEP / REGIONCFG → run → poll). Policy lives in userspace:
- Compile the model with Vela (
--accelerator-config ethos-u55-128). ethos-u/extract_pack.py model_vela.tflite outdir→ a self-contained.m55epack (command stream + weights + IO map,PACK_FORMAT.md).- On target,
npu-run(or your app) mmaps the pack from romfs — on NOMMU that is the flash address, so the NPU reads weights in place from QSPI — puts the arena in the carveout, and issues oneETHOSU_IOC_RUNioctl per inference.
Bus truths discovered on AN547 silicon (baked into the driver): the
NPU only sees real data through the Secure alias (bit 28); QCONFIG=0
and all-zero REGIONCFG selects are the only working AXI ports; ISRAM
reads as zero to the NPU; QSPI XIP is reachable (weights need no RAM).
arm-none-eabi-gcc (kernel), clang+ld.lld (bootwrapper),
qemu-system-arm ≥ 9 (mps3-an547 model), genromfs, expect,
buildroot build prerequisites. For Ethos-U tooling: Python 3.10+ with
ethos-u-vela.
./fetch.sh # download linux-6.12.43 + buildroot-2025.02.3
make toolchain # FDPIC uclibc + busybox (long, once)
make kernel # patches + xipImage + DTBs -> out/
make rootfs # out/romfs.img
make boot # bootwrapper (LLVM=/path/to/llvm/bin if needed)
make check # boot on QEMU, expect V8M-BOOT-PASS
On the real MPS3: burn boot-an547-xip.bin to ITCM and xipImage / DTB
/ romfs to QSPI offsets 0 / 0x1F0000 / 0x200000 via the MCC
(images.txt with AUTOQSPI).
Generic (keep): vfp-m (any M-profile FPU/MVE), faults-v7m,
v7m-reboot, ethosu-npu (any U55/U65 with register access),
linux.config as a baseline, the whole XIP/romfs/FDPIC userspace
recipe, extract_pack.py.
Per-board (rewrite): the DTS (memory map, clocks, UART), the timer
(SysTick or vendor timer instead of timer-sse300), the UART driver
(pl011/vendor instead of mps2-uart), the bootwrapper addresses, the
flash mapping.
Pitfalls we hit that you will too:
- physmap rounds the flash window down to a power of two (
fls64); a 6MBregsilently becomes a 4MB MTD and romfs mount fails with a bareEINVAL. Use a power-of-two window +fixed-partitions. - NOMMU large allocations round up to a power of two — carve out
big buffers (NPU arena, framebuffers) in
reserved-memoryinstead. - FDPIC default stack is 32KB: link with
-Wl,-z,stack-size=...; a NOMMU overflow is a silent wild pointer. FPSCR.LTPSIZEmust be 4 or MVELEtraps INVSTATE; unprivileged VPR access is an architectural NOP (useP0in asm).- A kernel-mode bus fault is a panic on v7m: never enable a DT node whose bus decode you have not proven. Verify on QEMU first — it faults at the same addresses.
- On Ethos-U55 with a different MAC count (WiseEye2 = U55-64),
re-run Vela with the matching
--accelerator-config; command streams are not portable across configurations.
Kernel patches: GPL-2.0. The Ethos-U register sequences follow Arm's
ethos-u-core-driver (Apache-2.0). Layout and approach inspired by
rota1001/stm32f429-linux
and jserv's Linux-on-STM32 course materials.