The Forgetful CPU (Linux on M4)
At this point, a long time went by without me touching the Mac mini. At the Chaos Communication Congress at the end of 2025, I found some new motivation. Finally, I hacked together a very minimal device tree containing only the CPU cores and AIC interrupt controller and loaded the Linux kernel (using m1n1’s linux.py) with the earlycon parameter, but I did not see any output after “Vectoring to next stage”. Since I wasn’t getting any useful output from the kernel, I could only take wild guesses at what was going wrong, right? I decided to try the brute-force method: good old println-debugging. I took the debug_putc assembly routine from m1n1 and adjusted it to print a single ‘a’ character 4. I inserted this into the Linux kernel code very early in boot, and sure enough, I got an ‘a’ after “Vectoring to next stage”! Essentially, I bisected the Linux boot code and landed at the MMU init code (which is still very early, still in the assembly code in arch/arm64/kernel/head.S). Was the MMU initialization crashing the CPU somehow? Not quite: the UART is accessed using memory-mapped I/O. Once the MMU is enabled, all memory accesses are directed to virtual addresses, which are mapped to a corresponding physical addresses through the page-tables. While m1n1 creates mappings to expose the MMIO address space at identical virtual addresses, Linux does not do this, meaning we end up accessing unmapped space instead of the UART once the MMU is enabled. I modified the initial pagetables to add this 1:1 mapping for the MMIO space, and now my debug_putc worked much further into the boot process. Bisecting again, the print now worked up to somewhere in the interrupt controller initialization! I narrowed it down to a write to the implementation-specific CPU register SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2, which triggered the new crash. After I commented out this write, the kernel booted to a shell. Let’s goooo! SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2, which is related to virtualization, has since been unlocked in new iBoot versions, so commenting out the write is no longer necessary. Now that I knew that Linux code was actually being executed, I looked once again into why I hadn’t gotten any printk output on the serial console earlier despite the earlycon boot parameter. 2026-01-23 12:35
added earlycon=s5l,0x3ad200000 to my bootargs 2026-01-23 12:36
and now I'm actually getting useful output before the crash is happening! Sure enough, the device tree was just missing stdout-path = "serial0", and after adding it, I got full register dumps and stack traces from Linux on early crashes.