The Forgetful CPU: Linux on Apple’s M4
At first, the M4 Mac mini looked completely dead. There was no Linux logo, no scrolling boot message, and no friendly error waiting on the screen. Then a single lowercase a appeared on a serial console.
In low-level operating-system work, one character can be a major breakthrough. It means the processor crossed another boundary before failing. That was the shape of Linux bring-up on Apple’s M4: not one dramatic fix, but a trail of tiny clues left by a machine that kept violating assumptions inherited from earlier Apple Silicon chips.
Why M4 changed the rules
Linux on Apple Silicon is not only a kernel-porting exercise. Apple’s system-on-chip, or SoC, combines CPU cores with memory controllers and many custom peripherals. Most of those devices are undocumented, so the Asahi Linux project has had to discover how they work by observing macOS and gradually teaching Linux to reproduce the same setup.
A crucial tool in that work is m1n1, Asahi’s low-level bootloader, debugging monitor, and experimental hypervisor. On earlier M-series machines, it could run macOS’s kernel under observation and record memory-mapped I/O, or MMIO. MMIO means that a device exposes control registers at memory addresses, allowing software to configure hardware by reading and writing those addresses.
The M4 changed that investigation path. It is the first Apple Silicon generation to require Apple’s Secure Page Table Monitor, or SPTM, when running the macOS kernel, known as XNU. Page tables describe how virtual memory addresses map to physical memory, so controlling them is one of the most sensitive jobs in a kernel. SPTM moves that responsibility into a protected Apple execution environment, making the old “put macOS under m1n1 and watch everything” approach much harder. (asahilinux.org)
First, make the crashes smaller
The first useful progress came from refusing to treat M4 registers like M1–M3 registers.
In raw boot mode, m1n1 tried to initialize GXF, Apple’s Guarded Execution Framework, and crashed. The relevant feature was disabled or locked in that boot path, so the correct fix was not a clever initialization sequence. It was skipping the write entirely.
The same pattern appeared with RVBAR, the Reset Vector Base Address Register. Each CPU core has one; it tells the core where to begin executing when it powers on. Earlier m1n1 code wrote a new entry address while starting Linux or another m1n1 instance. On M4, that write crashed the system even though the register already contained the right value.
This is an uncomfortable lesson in hardware bring-up: a register can exist, have a familiar name, and still be unsafe to touch. The machine may have configured it before Linux arrived, or the firmware may have locked it after initialization.
m1n1 -> USB proxy -> serial console
|
+-- Vectoring to next stage
That USB proxy became a lifeline. It offered a shell and a serial path before ordinary Linux drivers existed, turning a black screen into a sequence of observable states.
The missing a
The next experiment used a tiny device tree containing only the CPU cores and Apple Interrupt Controller. A device tree is a structured description of hardware that the bootloader passes to Linux. It tells the kernel which devices exist, where their registers live, and how interrupts reach the CPU.
The kernel was launched with earlycon, a boot option that enables a very early serial console. Nothing appeared after m1n1 announced that it was handing control to Linux. So a one-character debugging routine was inserted into the earliest assembly code in the kernel. It printed a and nothing else.
That character showed that Linux had begun executing. The next test placed the print at several points through the boot path, narrowing the failure to initialization of the Memory Management Unit, or MMU. The MMU translates the virtual addresses used by normal kernel code into physical addresses connected to RAM and devices.
Why does Linux on an M4 crash after the MMU turns on? In this case, the UART had not disappeared. Linux had lost the address used to reach it.
MMU off: CPU -> 0x3ad200000 -> UART
MMU on: CPU -> virtual address -> page table ->?
└─ no mapping: fault
m1n1 had left an identity mapping in place, meaning a virtual address matched the same physical address. Linux’s initial page tables did not include that mapping for the MMIO region. Once translation began, the kernel’s UART access went somewhere unmapped instead of reaching the serial hardware.
Adding a temporary one-to-one mapping let the debug character travel farther. The following crash came from a write to an Apple-specific virtualization register, SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2. That register was also locked under the firmware version being used. Removing the write allowed Linux to reach a shell; newer iBoot versions later made the register writable, removing the workaround. (yuka.dev)
A silent console can be another bug
There was still a misleading symptom. The kernel was running, but normal printk messages were absent even though earlycon had been supplied.
The missing piece was in the device tree. The stdout-path property tells Linux which serial device should receive console output. Adding this line connected the kernel’s logging system to the UART:
stdout-path = "serial0";
After that, early crashes produced register dumps and stack traces instead of silence. The difference matters. A dead console and a misrouted console look identical until the boot description is checked carefully.
When the secondary cores began to forget
A single working CPU core was not the finish line. Linux also needed symmetric multiprocessing, or SMP, which means bringing up several cores and allowing the scheduler to use them.
Starting the secondary cores revealed the strangest failure yet. The ARM instruction WFI, short for Wait For Interrupt, parks a CPU core until an interrupt wakes it. WFIT is a related form that can wait with a timeout. Architectural state, including general-purpose registers, is expected to survive this wait.
The M4 did not behave that way by default. A vendor-specific control flag, nicknamed a “chicken bit,” selected a deeper sleep behavior in which the core lost its register state. On earlier chips, m1n1 could change that setting. On M4, the relevant controls were locked or unavailable, so the CPU went to sleep and returned without remembering what it had been doing.
Replacing WFI and WFIT with NOP, a no-operation instruction, proved the diagnosis. All cores could boot, although a no-op loop wastes power because the core never reaches its intended idle state. The better solution is staged: avoid WFI during early boot, then let a platform-specific idle driver save the necessary state before entering the deeper sleep mode. Linux now has an idle=nop path for this kind of situation, while the longer-term power-management work continues upstream. (asahilinux.org)
Booting is not the same as support
This kind of progress needs careful wording. As of August 26, 2026, Asahi reported that M4 and M5 bring-up had working NVMe storage, PCIe device enumeration, and a fix for the multicore boot crash in the development environment. The project had not yet enabled M4 in the Asahi Installer because major pieces such as display, input, and other peripherals still needed work. A kernel reaching a shell is an important milestone, but it is not yet a polished desktop system.
The M4 story is therefore less about one mysterious processor than about disciplined debugging. A locked register explains one crash. A missing MMIO mapping explains another. A missing console property hides useful evidence. A sleeping core loses its state because the hardware’s power-saving behavior does not match the architecture’s promise.
The forgetful CPU became understandable one symptom at a time. That is often what operating-system development looks like: make the failure smaller, leave one more breadcrumb, and treat every unexpected character as evidence.
Comments (0)
No comments yet. Be the first to respond!
Leave a Comment
Your comment will be visible after review.