Mainline Linux on the Apple A12 (T8020)
  • Makefile 41.9%
  • Python 27.8%
  • Shell 22.1%
  • C 8.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
kieroid c4cf6d9460
feat: a display driver, disabled until the DART is sorted out
apple-h11p is a small DRM driver on drm_simple_display_pipe: it reads the mode
out of 0x50080 and the stride out of 0x50060, takes the plane, and flips by
writing surface, end, stride and commit through the write aperture at
0x230200000. The register offsets are the ones verified against
AppleMobileDispH11P and against the hardware.

It gets as far as taking the screen off simpledrm, card1-DSI-1 with no card0,
and then stops, so the node ships disabled.

What blocks it is dart-disp0. Put that in the device tree and apple-dart binds,
resets it and installs empty page tables, which throws away the mapping the
running scanout is using: the screen turns to red flicker and the phone falls
over soon after. That driver has no way to keep a mapping it did not make. And
the registers do not look like what it assumes anyway. With iBoot's mapping
live, TCR says translate, not bypass, while TTBR0 reads 0x0d100744, which is
not a page table address anywhere in this phone's DRAM however it is decoded.

Three of my own bugs are written down too, since each cost a boot: the read and
write views cannot both claim 0x2f0000 or the second request fails with EBUSY,
power domains must be claimed after everything that can fail or the devm unwind
powers them off under a live panel and the screen goes red, and this kernel
needs DRM_FBDEV_DMA_DRIVER_OPS or fbdev setup fails with -22.

Next target is AppleDART in the kernelcache, for this DART's real layout.
2026-10-14 00:41:31 -05:00
dts feat: a display driver, disabled until the DART is sorted out 2026-10-14 00:41:31 -05:00
include/dt-bindings feat: add PMGR power domains and USB device mode to the T8020 tree 2026-10-04 19:42:37 -05:00
LICENSES docs: add a license 2026-10-06 03:39:26 -05:00
patches feat: a display driver, disabled until the DART is sorted out 2026-10-14 00:41:31 -05:00
tools refactor: drop the cpufreq device tree and four knobs nobody sets 2026-10-13 04:29:34 -05:00
.gitignore feat: chase the USB gadget down to dwc2's session handling 2026-10-05 20:10:51 -05:00
LICENSE docs: add a license 2026-10-06 03:39:26 -05:00
Makefile feat: a display driver, disabled until the DART is sorted out 2026-10-14 00:41:31 -05:00
NOTES.md feat: a display driver, disabled until the DART is sorted out 2026-10-14 00:41:31 -05:00
NOTICES.md docs: write up the gadget enumeration failure as open 2026-10-12 21:13:20 -05:00
README.md feat: a display driver, disabled until the DART is sorted out 2026-10-14 00:41:31 -05:00

Bad Apple

Mainline Linux on the Apple A12 (T8020), on an iPhone XS Max (iPhone11,6, D331pAP).

The target is a console and a shell, the same finish line as the A10 and A11 ports. No display driver, touch, wifi, cellular or GPU.

The phone's NAND holds ext4, not iOS. The boot is tethered and reads nothing off the flash, so wiping it does not break anything.

Registers, dead ends and the debugging history are in NOTES.md.

Boot chain

SecureROM
  -> usbliter8 on a Pico 2 W jig, fires the bootrom exploit, leaves pwned DFU
  -> iBSS, patched to stop checking signatures, uploaded by tools/dfuboot.py
  -> device tree and an empty trustcache, over Apple's recovery protocol
  -> m1n1, handed to bootx where a restore kernelcache would go
  -> Linux, 16K pages, t8020-d331p.dtb, initramfs

iBSS stays in the chain on purpose. It brings up DRAM, the ADT and the display, and console=tty0 is the framebuffer it leaves behind.

Dependencies

Host For
clang, ld.lld kernel and m1n1, no aarch64 GCC needed
Rust with rust-src m1n1 (BUILDSTD=1). On Gentoo a USE flag on rust-bin
dtc, cpp make check
python3 tools/, m1n1's proxyclient
git, curl, unzip make deps, make initramfs
ipsw, pyimg4 unpacking the IPSW, wrapping images in img4
irecovery talking to iBSS in recovery mode
usbliter8ra1n-patch the jig firmware, built and flashed once

make deps clones m1n1, the Hoolock kernel and docs into build/, makes build/venv with pyusb, pyserial and construct, and applies patches/. tools/dfuboot.py needs that venv, since pyusb is what talks to DFU. The jig firmware is built separately, see NOTES.md.

You also need an iPhone11,6 IPSW. Everything below takes files out of it.

Build

make deps       # clone, venv, apply patches/
make check      # compile the device tree on its own
make m1n1       # build the m1n1 payload
make initramfs  # download Hoolock's test initramfs
make kernel     # build Image.gz with Hoolock's 16K config

make boot rebuilds m1n1, the device tree and the kernel as needed, so those are only separate targets when you want one of them on its own.

make check is the only target that works on a bare checkout. make patch re-applies patches/ and is safe to run again: an applied patch is skipped, a real conflict stops the build.

cpufreq is disabled by the build, not by you. Frequency requests are accepted and then never completed, because the coprocessor that arbitrates DVFS is not running, see NOTES.md.

First time setup

Done once, from an unpacked IPSW. It patches iBSS so it will load a payload Apple never signed, and wraps the device tree and an empty trustcache for bootx.

make images IPSW=/path/to/unpacked/ipsw

Everything lands in build/iboot/. Any manifest does once iBSS is patched, so the IM4M is Hoolock's 0x8015.im4m. There is no 0x8020 template and there cannot be: from A11 on a manifest is tied to the device's own ECID.

Run

Put the phone in DFU on the jig. The exploit fires by itself in about a second. Move the cable to the PC, then:

make boot

That packs m1n1, the device tree, the kernel and the initramfs into one fake restore kernelcache and hands the whole chain to the phone. make kernelcache builds it without booting anything.

Within about half a minute the phone is 05ac:4142, hands the PC 172.16.42.2 over DHCP, and answers telnet on 172.16.42.1. tools/tsh.py "cmd" runs one command there from a script.

The first USB attach usually fails and the gadget retries by itself, so the link can take a few tries to appear. Nothing to do but wait, see NOTES.md.

If it says the phone is not pwned, check its USB serial for PWND:[usbliter8]. Without it the exploit has not run: unplug the jig from USB for a few seconds, a firmware reset is not enough, and put the phone back in DFU.

Implementation status

  • Boot chain, tethered, nothing read off the NAND
    • Patched iBSS that takes an unsigned payload
    • m1n1 as a fake restore kernelcache, with a real BootArgs
  • Device tree for T8020, PMGR domains generated from the ADT
  • Console
    • Framebuffer, handed over by iBoot
    • Display driver. Written, and the pipe takes a new scanout address, but it needs dart-disp0 and Linux's DART driver destroys the mapping iBoot's scanout runs on, see NOTES.md
    • Brightness. It is a DSI command to the panel, so it needs the pipe
    • Notch trimmed out of the framebuffer, 2688 down to 2556
  • AIC interrupts, including fast IPIs
  • SMP on all six cores
    • Chicken bits reapplied per core by the kernel, m1n1 does it once
  • NVMe through ANS2, reads and writes, ext4 mounted
  • SMC
    • Battery: capacity, health, cycles, voltage, current, temperature
    • Charger detection and the power button
    • Poweroff, with the PMU scratchpad over SPMI so it stays off
    • Reboot, through the watchdog, lands back in recovery
  • Die temperature sensors, eight channels through hwmon
  • USB
    • Gadget: telnet, DHCP, getty on /dev/ttyGS0
    • DART, in m1n1 and through apple-dart in Linux
    • Comes up on every boot, the gadget replugs itself until the host takes it
    • Why the first attach fails at all. Worked around, not understood
  • cpufreq. Needs the PMP/CLPC coprocessor that completes transitions
  • Touch, wifi, cellular, GPU, audio
  • Anything upstream

License

GPL-2.0+ OR MIT, see LICENSE. patches/ keeps the license of the code it patches: linux-* GPL-2.0, m1n1-* MIT. Nothing third party is vendored here. What the boot depends on is listed in NOTICES.md.