Skip to content

Xiaomi 13 (OddlyLabs) ​

OddlyLabs/unixnu-fuxi

Xiaomi 13 (fuxi). Board files from the UniXNU device template, the FTM5 touch driver and its staged kernel probe; CI builds the kernel, the payload, the DEBUG firmware and the fastboot boot image. On 2026-11-02 the image boots on the device to bsd_init (e901139), and the touch probe's stage 3 reads the controller's chip id 36 40 there. With the PMGR bridge (484a650) the kernel runs on to the root device wait, and stage 4 reports real touches, with polling and with the interrupt.

Source record: porting.toml, 78f30b651c0ca6ea325682832bb25767523212f5.

This page is generated from the device record.

AreaStatus
Configurationimplemented
Firmware and boothost-verified
CPU and interruptsimplemented
Memory and DMAimplemented
Storage and rootimplemented
Displayplanned
Inputimplemented
GPUplanned
Networkplanned
Audioplanned
Powerimplemented
Image assemblydevice-verified
Diagnosticsimplemented

Configuration ​

implemented

Components: ArmDTPlatform, ArmCpu, SoftwareAES, XnuPrelink, DarwinBoot.

Hardware and dependencies ​

  • UniXNU examples/device template (SM8550, iPadOS 17.7.11 profile).
  • UniXNU-sm8550-common at 23b2049fcd9e30356161def84231334fb55ecca9.

Configuration ​

  • board.toml: name fuxi, kit fuxi, soc_source sm8550, os_profile ipados-17.7.11-21H461, device_tree.source device:dts/fuxi.dts.
  • configs/fuxi_defconfig: the template options, CONFIG_XNU_NATIVE_ADRENO off (the SoC fragment turns it on), and CONFIG_XNU_EXTRA_LD_ARCHIVES for the touch archive and the force-loaded HID archive. The arch fragment sets CONFIG_XNU_BUILD_ARCHIVES, CONFIG_XNU_NATIVE_AES, CONFIG_XNU_NATIVE_ARCH_TIMER and CONFIG_XNU_NATIVE_PAN; the OS fragment sets CONFIG_XNU_SOURCE_MOCKFS.
  • [kernel] archives in build order: corecrypto (plain and namespaced), ArmDTPlatform, ArmCpu, SoftwareAES and its bindings, libpthread, the mockfs fixture, build-fuxi-platform-power.py, build-fuxi-hid-input.py, build-fuxi-ram-block.py, build-fuxi-touch.py.

Procedure ​

  • ./unixnu defconfig fuxi_defconfig -D ../unixnu-fuxi
  • ./unixnu status --json
  • ./unixnu build kernel

Development findings ​

  • CI (.github/workflows/build.yml) pins OddlyLabs/UniXNU and OddlyLabs/uefi-fuxi at full revisions; after merging upstream into either fork, update UNIXNU_REF or UEFI_REF and let the image job run again.
  • The image job installs Homebrew's openssl@3 itself rather than relying on the runner image: the encryption step uses OpenSSL 3 (PBKDF2 for the image, RSA-OAEP with SHA-256 for its key).
  • configs/fuxi_defconfig sets CONFIG_XNU_DT_PANIC_REGION. The template and the arch, SoC and OS fragments leave it off, and then the BCM2837 target uses its fixed panic log address (see memory); every other SM8550 defconfig in reach (UniXNU-sheng, Kerfand, MinecraftVM) sets it. On the device the kernel with it passes PE_create_console and reaches bsd_init (evidence/2026-11-02-first-boot.md). The kernel check of the image job fails if PE_create_console mentions 0x3c0f.
  • CI pins uefi-fuxi c5793bc2 since the memory map fix for this 12 GB unit (see firmware and memory).
  • configs/fuxi_defconfig sets CONFIG_XNU_SHENG_PMGR and board.toml adds build-fuxi-platform-power.py to the archives and sheng-pmgr=2 to the boot arguments, as UniXNU-sheng and the community fuxi ports do: AMFI needs an ApplePMGRNub (see power).
  • The image job also uploads the unpatched prelinked kernel (gzip) and payload.json, the kernel encrypted with the image key, so payload variants such as entry-probe images are built without macOS.
  • The host tests byte-compile scripts/*.py and ci/*.py and parse ci/decrypt-boot-image.sh with bash -n, so the image tools are checked on every push.
  • The merged configuration is the arch, SoC and OS fragments followed by the defconfig; the archive list has to cover every archive the merged options require, not only those of the template defconfig.
  • CONFIG_XNU_SOURCE_MOCKFS (OS fragment) makes vfs_conf.o reference mockfs, so the link needs build/mockfs-fixture/libmockfs_fixture.a: the first CI kernel build at 450f518 applied all 62 patches and built every listed archive, then stopped at that missing archive.
  • ArmCpu needs the source corecrypto provider, so corecrypto is built before the components, as in UniXNU-sheng.
  • board.toml's kernel_args start the secondary cores (sheng-smp=1 armcpu-spi-ipi=1 armcpu-self-ipi=1, the arguments of image s3-selfipi) and run the touch probe to stage 4 with the HID digitizer (fuxi-touch=4 fuxi-touch-hid=1 fuxi-touch-panic=2), so the CI image waits for a touch and otherwise ends in the root device wait.
  • fuxi_defconfig sets CONFIG_XNU_NATIVE_RAM_BLOCK=y. Without CONFIG_XNU_SOURCE_STORAGE the kernel build script requires CONFIG_XNU_NATIVE_AES (set by the arch fragment) and force-loads build/ram-block/libRamBlock.a; the owner Data code of patches/xnu/offline-owner-systemos-mount.patch stays out, since CONFIG_XNU_OWNER_DATA_MOUNT is not set.
  • board.toml's kernel_args add sheng-touch-hid=1, which NativeHidInput reads to give its default device the digitizer collection.

Completion criteria ​

  • All source pins resolve; every selected component has a record; status shows the intended overrides and patch order.

Current limits ​

  • unixnu.lock is not committed yet.

Next action ​

  • Run ./unixnu fetch and commit unixnu.lock.

Evidence ​

Firmware and boot ​

host-verified

Components: DarwinBoot.

Hardware and dependencies ​

  • OddlyLabs/uefi-fuxi, branch fuxi: a fork of UniXNU/uefi-sheng (the official Mu-Silicium fork for UniXNU) with the nine fuxi bring-up commits of matejkoo217/uefi-fuxi cherry-picked (authors kept, cherry-pick -x); its fuxiPkg derives from Silicium's SM8550 nuwa (Xiaomi 13 Pro).
  • fastboot boot of the image (temporary, nothing is written to the device).

Configuration ​

  • board.toml [firmware]: config fuxi, dsc Platforms/Xiaomi/fuxiPkg/fuxi.dsc, fd Build/fuxiPkg/{target}_CLANGPDB/FV/SILICIUM_UEFI.fd, and the submodules, edk2 patch and package path list of UniXNU-sheng's [firmware].
  • fuxi.dsc: FUXI_VENDOR_HW = 0 (Mu drivers only; the previous stage keeps the panel scanning out), framebuffer 1080x2400x32, DDR base 0x80000000, the UniXNU menu, UniXNUDxe, DarwinBoot, DiskIo, Partition and FAT; FuxiBootMemoryDxe publishes the payload volume the shim hands over.

Procedure ​

  • ./unixnu build firmware --uefi ../uefi-fuxi --target DEBUG (CI: the image job of .github/workflows/build.yml).

Development findings ​

  • UniXNU provides the boot menu (UniXNUPkg) and the XNU loader (DarwinBoot) as UEFI modules; the board firmware that runs them on the phone is a Mu-Silicium platform, and upstream Mu-Silicium has no fuxi platform.
  • Measured on hardware in matejkoo217/uefi-fuxi: with text offset 0 in the shim's ARM64 header the ABL loads the image and the firmware's DEBUG console prints on the panel; with the firmware address there the ABL answers Load Error.
  • The firmware's FdtBlob (sm8550-xiaomi-fuxi.dtb, model 8550 fuxi, msm-id 0x207, board-id 0x10008) carries the root properties the ABL selects an appended DTB by; the DTB merged from OS3.0.309 keeps the base DTB's board-id 0 and is not used for the boot image.
  • fuxiPkg Include/RAW.inc listed the vendor raw files SecParti.cfg, uefipil.cfg and QcomChargerCfg.cfg outside the FUXI_VENDOR_HW gate, so the first firmware build in CI (run 41268231266 at 72300f2) stopped at the missing Binaries/nuwa/RawFiles/SecParti.cfg; uefi-fuxi 87554bf9 gates them with their drivers.
  • With FUXI_VENDOR_HW = 0 the volume holds UniXNU, UniXNUDxe, DarwinBoot, FuxiBootMemoryDxe, SimpleFbDxe, FAT, PartitionDxe and DiskIoDxe. FuxiGopLogDxe and FuxiDisplayDepexDxe are compiled but enter the volume only with USE_CUSTOM_DISPLAY_DRIVER = 1.
  • On the device (2026-11-02, image 931ac958 of run 41273003819): the ABL accepts the v0 image (fastboot boot answers OKAY), the DEBUG firmware log appears on the panel, the UniXNU entry starts DarwinBoot, and DarwinBoot exits boot services and paints its green band. The log ends with SecurityLock LockCapsuleInterface, the normal exit-boot-services message; the DARWINBOOT lines above it are covered by the bands.
  • DarwinBoot's hand-off page (photographed on 2026-11-02): kernel va 0xFFFFFFF00427C000 at pa 0x8C007C000 (the start of the 0x8C0000000 run, 2 MiB aligned, plus the kernel's offset in its 2 MiB block), window 0x130000000, video 0xB8000000 1080x2400 with 4320-byte rows at 32 bpp, EL1, PARange 2 (40-bit).
  • uefi-fuxi c5793bc2 narrows the high DRAM rows of fuxiPkg to RAM on both the 8 GB unit the map came from and this 12 GB unit: 0x880000000 + 0x39D00000 and 0x8C0000000 + 0x140000000. Nothing from 0x8B9D00000 to 0x8C0000000 is listed, not even the 28 MiB that is RAM on both, because it borders holes that move with the DDR size. The rows stay static: clipping them against the ABL's /memory node would take an FDT parser in the shared SiliciumPkg SEC path. The change reaches 46 of the 54 FVMAIN modules, because SerialPortLib (FrameBufferSerialPortLib) finds its region through MemoryMapHelperLib and so links the table: exactly those 46 differ, and all 5859 changed .text instructions differ in immediates only. Booted on the device (run 41401828450, image m1-touch): no change in behavior.
  • The firmware of run 41378843724 (the same code as 627fe52a) hands over a kernel that reaches bsd_init and draws its panic screen on the framebuffer DarwinBoot passes (e901139).

Completion criteria ​

  • Firmware image builds, enters UniXNU, publishes the framebuffer and preserves the documented memory reservations.

Current limits ​

  • The high DRAM rows cover the two measured units (8 GB and 12 GB); another unit has to be compared with its /proc/iomem. Reservations after the hand-off are not checked.

Next action ​

  • Check the reservations after the hand-off (see memory).

Evidence ​

CPU and interrupts ​

implemented

Hardware and dependencies ​

  • CPU layout and GIC ranges of SM8550 v2 (soc_id 519) from the stock device tree.

Configuration ​

  • The template ArmCpu and ArmDTPlatform selection.

Procedure ​

  • Boot to the XNU entry checkpoint and record the enabled CPUs.
  • Boot with sheng-smp=1 armcpu-spi-ipi=1 armcpu-diagnostics=1 and fuxi-touch=4 fuxi-touch-panic=2, then read the two SMP rows of the touch report (package/FuxiTouchProbe.cpp smpText): per secondary core the ArmCpu step (7 = running), failure code and CPU_ON result, the scheduler's processor count, and the timer interrupts and IPIs each core took.

Development findings ​

  • On the device the boot CPU runs the XNU entry with the MMU off (blue band) and reaches the instruction before the MMU enable (magenta band). The image has no reset probe, so nothing marks later progress; no XNU console text and no panic screen appear, and the phone stays off USB. The stop therefore lies between the MMU enable and PE_init_printf in arm_init, the first point that can draw (xnu d8b80295 arm_init order: Apple register setup, DT init, ml_parse_cpu_topology, thread and CPU bootstrap, arm_vm_init, serial_init, PE_create_console, PE_init_printf).
  • Entry-probe resets located the stop (2026-11-02): the kernel reaches the MMU enable, arm_init, cpu_data_init, PE_init_platform and configure_misc_apple_regs, and never returns from ml_parse_cpu_topology. The ADT gives cpu0-2 cluster-type E and cpu3-7 P (dts/fuxi.dts, copied from sheng), so the parser builds two clusters, while BCM2837 keeps MAX_CPU_CLUSTERS 1 (sm8550-common raises only MAX_CPUS to 8); the DEVELOPMENT kernel asserts cluster_id < MAX_CPU_CLUSTERS and panics before any console exists. UniXNU-sheng carries the fix as its board patch zzyf-sheng-amp.patch (__ARM_AMP__, MAX_CPU_CLUSTERS 2, Edge scheduler defaults without a performance controller); fuxi now carries it unchanged.
  • With the AMP patch (65c4f4e) the reset probes after ml_parse_cpu_topology, thread_bootstrap, cpu_bootstrap, rtclock_early_init, kernel_startup_bootstrap, timer_call_init, cpu_init, processor_bootstrap, arm_vm_init, serial_init, kernel_startup_initialize_upto and PE_create_console all rebooted the phone. The probe after PE_init_printf, the next call, did not (one run), and the official image stayed on the bands with no kernel text and no panic screen. The cause was PE_create_console's access to the Raspberry Pi panic log address (see memory): with CONFIG_XNU_DT_PANIC_REGION and no other kernel change (e901139) the boot reaches bsd_init. Why the probe after PE_create_console still reset is not explained; an asynchronous fault would fit.
  • On e901139 the boot CPU runs arm_init to machine_startup and on to bsd_init. Kernel threads, IOSleep, IOMalloc and IODeviceMemory mappings work: the touch probe uses them, and its panic frames are touchThread and Call_continuation. Only the boot CPU runs: sheng-smp is not set, so ArmCpu does not start the other seven cores.
  • With the PMGR bridge (484a650) the boot CPU keeps running past bsd_init: image p1-pmgr ends in the root device wait, and in p3-poll the touch thread's sleeps and SPI transfers ran 3531 times before the tap.
  • Two community fuxi kernels get past this point on the same SoC with the same dram-size and window size: mouse233 (iPadOS 17.7.11, own firmware with a USB RAM receiver) and Kerfand (iOS 16.7.16, the same payload-in-image layout). Both build the sheng kit; this board builds the template components ArmDTPlatform and ArmCpu.
  • The payload's ADT (image m1-touch) has what CPU_ON needs: eight cpu nodes with reg 0x000 to 0x700 (MPIDR affinity), cpu0 running and cpu1-7 waiting, three cortex-a510 in cluster E and two cortex-a715, two cortex-a710 and one cortex-x3 in cluster P; /psci has method smc. UniXNU-sheng boots the same SoC with sheng-smp=1 armcpu-spi-ipi=1. No screen showed ArmCpu's secondary-core variables until the SMP rows of the touch report, so an image with only the arguments changed shows whether the boot survives, not which cores run.
  • Device (680a818, images s2-smp and s3-selfipi): with sheng-smp=1 armcpu-spi-ipi=1 all seven secondary cores start. CPU_ON over smc returns 0 for each, each core enters initCPU with its own MPIDR, finds its redistributor and runs (step 7), the scheduler counts eight processors (processor_avail_count 8), and the probe's thread reported from core 3 and from core 4.
  • Without armcpu-self-ipi=1 the kernel sends no IPI at all (s2-smp: 0 sent, 0 taken): xnu-10063 starts every core with SIGPdisabled in cpu_signal, cpu_signal_internal skips a core while it is set, and only a handled IPI clears it. With armcpu-self-ipi=1 each core sends itself one IPI from initCPU, the self-IPI XNU expects from PE_cpu_machine_init (comment in osfmk/kern/machine.c), and IPIs go through (s3-selfipi: 85 sent, every core took its own, cores 3 and 4 took 28 and 51). Cores 1 and 2 (cluster E) took no timer interrupt in either boot, and core 6 none in s3-selfipi.
  • SGIs do not arrive on fuxi (2026-11-03, image h3-sgi): without armcpu-spi-ipi=1 the eight self-IPIs went out through ICC_SGI1R_EL1 and none was taken (ipi8/0), as UniXNU-sheng records for sheng, so armcpu-spi-ipi=1 is required.
  • One boot of six with sheng-smp=1 (image h2-edge, by the order of the photographs) started no secondary core: CPU_ON returned 0 for all seven, none entered initCPU, and the scheduler ran on core 0 alone. armcpu-edge=1 is read only after processor_start returns, so it cannot hold a core before initCPU; the cause is unknown.
  • Two more SMP boots (11db5c0, h4-hid and h5-edge) started all seven cores, so they failed to start once in nine boots. armcpu-edge=1 did not put work on cores 1 and 2 in a probe boot; with user space running (r3-ramblock) core 1 took timer interrupts and IPIs reached every core.

Completion criteria ​

  • Every enabled CPU reaches its entry checkpoint and timer/IPI handling works on each enabled CPU.

Current limits ​

  • Hardware inputs are not recorded in dts/fuxi.dts beyond the SoC DTSI.
  • Cores 1 and 2 have not been seen taking a timer interrupt. The SMP boots end in the probe's panic within about a minute, so longer SMP runs are not tested. In one boot of nine no secondary core reached initCPU after a successful CPU_ON.

Next action ​

  • Record whether the secondary cores fail to start again; armcpu-edge=1 (h5-edge) changed nothing for cores 1 and 2. Find out whether cores 1 and 2 ever run work (board.toml passes sheng-smp=1 armcpu-spi-ipi=1 armcpu-self-ipi=1 since the HID change); armcpu-edge=1 makes ArmCpu call ml_cpu_init_completed, so the Edge scheduler sets up its cluster matrix.

Evidence ​

Memory and DMA ​

implemented

Hardware and dependencies ​

  • DRAM and reserved-memory nodes of the stock device tree; the apps SMMU at 0x15000000.

Configuration ​

  • dts/fuxi.dts includes sm8550.dtsi and sets model and compatible; package/SmmuBypass.h is the UniXNU-sheng pass-through stream helper the touch probe uses for stream 0xa3.

Procedure ​

  • Fill the memory map from the stock device tree and the firmware's memory map.

Development findings ​

  • fuxi and sheng share the touch engine's SMMU stream (0xa3).
  • The kernel at 65c4f4e had no CONFIG_XNU_DT_PANIC_REGION, so PE_create_console (check_for_panic_log, xnu d8b80295 pexpert/arm/pe_init.c) mapped BCM2837's PLATFORM_PANIC_LOG_PADDR 0x3C0FC000 write-combined, read a word there and called bzero for 16 KiB, before initialize_screen (disassembly of the CI kernel). No RAM range in /proc/iomem and no node of the live device tree covers that address; the nearest nodes are the NSP interconnect at 0x320C0000 and the CDSP at 0x32300000. Three boots ran this code on 2026-11-02 (probes a2-console and b11-createcons, and the official image); after each of them the phone booted Android normally (checked after the third: boot completed, /data mounted). With CONFIG_XNU_DT_PANIC_REGION, the only kernel change, the boot reaches bsd_init (e901139), so this access stopped the earlier boots. Whether it faults, hangs or is ignored was not measured.
  • At the touch probe's panic the kernel manages 0x127068 pages (4.61 GiB) with 0x125ccd free (vm_page_pages, vm_page_free_count): the 4.75 GiB window minus the kernel image and boot data.
  • This unit has 12 GB and its /memory node differs from the firmware map (from an 8 GB unit): in the map 0x8B9D00000 + 2 MiB and 0x8BBD00000 up to 0x8BD300000 are conventional memory, but on this phone they are not RAM. UniXNU-sheng records a synchronous external abort from touching such a range at 0x8BB300000, so these holes differ per unit. uefi-fuxi c5793bc2 removes them from the map (see firmware). The window DarwinBoot takes (0x8C007C000 + 0x130000000) is inside the /memory node of this phone. Live data: /proc/iomem and /sys/firmware/fdt read with root on OS4.0.0.32.

Completion criteria ​

  • All handoff regions are accounted for and each enabled DMA engine accesses its allocated range with the documented cache policy.

Current limits ​

  • The DTS memory map is the SoC default.

Next action ​

  • Account for the hand-off regions XNU maps against the firmware's reservations.

Evidence ​

Storage and root ​

implemented

Hardware and dependencies ​

  • UFS host of SM8550; the GPT of LUN 0 (userdata) on the device.

Configuration ​

  • CONFIG_XNU_NATIVE_UFS is not set.
  • CONFIG_XNU_NATIVE_RAM_BLOCK=y: UniXNU RamBlock's NativeRamBlock (package/NativeRamBlock.cpp and its headers, unchanged copies of RamBlock 4be2d7e) built by scripts/build-fuxi-ram-block.py with the IOBlockStorageDevice addresses of scripts/fuxi_storage_abi.py; the native personality starts it, and it publishes a RAM disk from the loader as a block device. package/FuxiRamBlockStore.cpp answers its persistent-store hooks with no store.

Procedure ​

  • Select a UFS component after the firmware boots.

Development findings ​

  • Other fuxi ports report the UFS controller hand-off from the firmware as usable; not reproduced here.
  • RAM root paths (2026-11-03): handed over as md0 (rd=md0), a RAM disk is a BSD device without IOMedia, so nothing loads APFS, and mockfs, which the OS fragment enables and which accepts any device, is asked before any kext filesystem (devfs, nullfs, bindfs, mockfs and routefs come first in xnu-10063's list). Through NativeRamBlock the disk is an IOMedia: APFS attaches, publishes its volumes, and the root can be chosen by volume UUID (payload.adt.root_match_volume_uuid), as sheng roots on UFS. APFS, IOStorageFamily and the HID kexts are prelinked in the fuxi kernel.
  • UniXNU binds NativeRamBlock through build/storage-abi/interface-audit.json, which needs a research candidate kernel and a gdb observation that CI does not have. IOBlockStorageDevice sits at the addresses UniXNU's marble-milacy port records: vtable 0xfffffff005cb44c0 (address point +16, 190 slots, 14 pure), metaclass 0xfffffff006add1d0, constructor 0xfffffff00496ed98, D2 0xfffffff00496edb8, with D1 and D0 brk traps; the prelinker bound the family to this kernel's IOService constructor and destructor.
  • Device (22579b4, image r2-ramblock): the restore ramdisk mounts as root through NativeRamBlock. DarwinBoot hands over the packed disk, APFS attaches to the whole disk, IOFindBSDRoot finds the volume by UUID and vfs_mountroot mounts it; the managed pages drop by the disk's 129.6 MiB. bsd_init then panics with rootvp not authenticated after mounting: a non-macOS boot requires a sealed root unless bsd_rooted_ramdisk(), which tests the rd boot argument for the prefix md0. rd=md0-ramblock marks the RAM-disk root and leaves IOFindBSDRoot on the UUID match (its md path needs exactly three characters).
  • User space from the restore ramdisk (11db5c0, image r3-ramblock): with rd=md0-ramblock bsd_init skips the sealed-root check, and launchd (pid 1), restored_external (pid 3) and mount_tmpfs (pid 4) run from the ramdisk's APFS volume on /dev/disk1s1, trusted by the restore trust cache. The full OS (OS and SystemOS images, about 7.9 GiB) does not fit a fastboot payload; it needs another delivery, a USB receiver in the firmware (as mouse233's port has) or a UFS driver.

Completion criteria ​

  • Host: register, clock, link and SMMU facts match the board inventory.

Current limits ​

  • No UFS driver is selected. The RAM block is linked but not yet seen on the device; the payload of a fastboot boot image limits a RAM disk to what fits next to the kernel (DarwinBoot takes at most 256 MiB).

Next action ​

  • Boot the IPSW's restore ramdisk (044-75545-038.dmg, one APFS volume ramdisk, UUID 9071148B-B025-402D-AD7A-48464D8B5FFF) through NativeRamBlock with that root_match_volume_uuid and the restore trust cache, and read the SYS row of the touch report: done, it reaches restored_external. Next: choose the delivery of the full OS.

Evidence ​

Display ​

planned

Hardware and dependencies ​

  • Panel xiaomi m3 38 0c 0a (Samsung DDIC by its command set), 1080x2400, DSI0 command mode with DSC 8 bpp, slices 540x40, 1.1 GHz.
  • Splash framebuffer 0xb8000000 (0x2b00000), 32 bpp, stride 1080 pixels, the same region as sheng.

Configuration ​

  • package/FuxiPanelBoard.h holds the panel and controller constants with their sources; no display component is selected.

Procedure ​

  • First stage: keep the firmware framebuffer (GopFramebuffer route), as sheng does.

Development findings ​

  • 73 DSI command sequences of the panel are decoded in facts/panel_cmds_*.txt.
  • The PPS in the on-command gives pic_height 2520 while the timing and page address give 2400; unresolved.

Completion criteria ​

  • A known frame appears with correct geometry and colors, then userland presentation and backlight controls work.

Current limits ​

  • No device evidence for display.

Next action ​

  • Read the framebuffer the firmware publishes on fuxi (address, stride, format).

Evidence ​

Input ​

implemented

Components: QcomGeniSpi, FuxiFtsTouch.

Hardware and dependencies ​

  • Stock device tree (OS3.0.309 vendor_boot DTB[2] with dtbo[4], merged with fdtoverlay): /soc/qcom,qupv3_1_geni_se@ac0000/spi@a90000/fts@0, compatible st,spi, 12 MHz, SPI mode 0.
  • Controller ST FingerTip FTM5, chip id 36 40, firmware 53.42 kept in its own flash (/proc/tp_fw_version on Android), tp-vendor 0x38.
  • Bus QUP1 SE4 at 0xa90000, wrapper 0xac0000, SMMU stream 0xa3; pins 44-47 (qup1_se4); interrupt GPIO 25 (active low, TLMM summary SPI 208); reset GPIO 24.
  • Rails vdd ldob12 1.8 V (RPMh 0x40900) and avdd ldom6 3.2 V (RPMh 0x41b00), measured with root on Android; their only consumer is spi0.0.

Configuration ​

  • board.toml [components] selects QcomGeniSpi and FuxiFtsTouch; [kernel] archives runs scripts/build-fuxi-touch.py.
  • configs/fuxi_defconfig links build/fuxi-touch/libFuxiTouch.a through CONFIG_XNU_EXTRA_LD_ARCHIVES; patches/xnu/fuxi-touch-probe.patch calls native_touch_probe() from bsd_init after IOKitInitializeTime().
  • Boot arguments: fuxi-touch=1..4, fuxi-touch-panic=1 or 2, fuxi-touch-hid=1, fuxi-touch-hz, fuxi-touch-power, fuxi-touch-tcs, fuxi-touch-poll, fuxi-touch-map. board.toml sets fuxi-touch=4 fuxi-touch-hid=1 sheng-touch-hid=1 fuxi-touch-panic=2.
  • The HID digitizer: package/NativeHidInput.cpp, an unchanged copy of UniXNU ShengHidInput 71a40e7, built by scripts/build-fuxi-hid-input.py with the kernelcache addresses of scripts/fuxi_hid_abi.py and force-loaded by fuxi_defconfig. package/FuxiHidProbe.cpp, in the touch archive, loads IOHIDFamily and IOHIDEventDriver, waits for IOHIDResource, registers NativeHidInput's metaclass and starts the touch-only device; the touch probe calls it once sensing runs.

Procedure ​

  • Stage 1: snapshot of the QUP clocks, the pins and the two rails in the command database.
  • Stage 2: RPMh votes vdd then avdd in active TCS 2 of apps RSC DRV2 (tcs-offset 0xd00), wait 3 ms, vote the QUP clocks, configure the pins.
  • Stage 3: GENI SPI at 12 MHz; reset low 10 ms; wait for event 03 (2 ms polls, 100 ms, three tries, then the ICR 4-wire fallback and the chip id); read the chip id.
  • Stage 4: A0 00 00, 50 ms, A0 00 01; falling-edge interrupt; drain the FIFO; publish contacts in the ShengHidInput ABI.
  • Host: run the FuxiFtsTouch tests and tests/board_test.c (README).

Development findings ​

  • The fts_spi driver dispatches events on the high nibble of byte 0, whose bits 2-3 carry the top of the major axis, so 0x13 to 0x1F are all enter events.
  • Coordinates are ten times the panel pixels (fts,support-super-resolution): 0..10800 x 0..24000; the probe maps them to the 30480 x 20320 range that ShengHidInput scales from.
  • Both touch rails stay on while Android runs, also with the screen off, and carry no boot-on property, so the probe votes them before it touches the controller.
  • Stage 3 on the device (e901139, evidence/2026-11-02-first-boot.md): the command database gives ldob12 0x40900 and ldom6 0x41b00, the RPMh request in TCS 2 returns 0 (done bit not seen within 10 ms), the QUP branches run, pins 44-47 read 0x85 0x85 0x85 0x87 (function 1, 6 mA, CS pulled up), the reset pin is driven high, GENI is QUP 3.9 at exactly 12 MHz, the controller answers the first reset with CONTROLLER_READY (03 00 00) and reads chip id 36 40, and no SPI transfer failed.
  • Stage 4 in polling mode on the device (484a650, image p3-poll, fuxi-touch-poll=1): sensing starts (status 0), 3531 FIFO reads bring 12 events, one tap gives 1 enter, 8 moves and 1 leave, with no error event, no re-arm and no failed transfer. The last position is 2885,18595 in the raw range 0..10800 x 0..24000 (pixel 288,1859). The owner was asked to tap the lower left: y is above the short-axis range, so the axes are not swapped, and small x with large y means neither is mirrored; the two interrupt-mode taps read there too (not a calibration).
  • Stage 4 with the interrupt armed works on the device (484a650): image t4c-diag (with armcpu-diagnostics=1) reads in1 mv9 up1 at 1295,16685 with irq11/0 and rd12, and the second run of t4b-touch reads in1 mv8 up1 at 1635,19935 with irq10/0 and rd11: one interrupt per touch event, none spurious, against 3531 FIFO reads in polling mode. The path is sheng's except for the edge (fuxi falling, sheng rising).
  • Two contacts on the device (484a650, third run of t4b-touch): pressing with two fingers and lifting one reads in2 mv539 up1 c1, with the remaining contact at 3095,15215, irq290/0 and rd291: an interrupt brings about two events, because the probe drains the FIFO in one read.
  • Unexplained reset: the first run of t4b-touch reset the phone by itself within seconds of the kernel starting (the owner saw the bands, then a restart; Android up 65 s after the boot command, no panic screen). The same bytes ran to the touch report twice afterwards, one of those started from running Android through adb reboot bootloader, and armcpu-diagnostics=1 reported nothing in t4c-diag. So it is intermittent, one of fourteen boots with the bridge and the interrupt armed, and the cause is unknown. That first run is the only one that entered fastboot by hand after Android: one sample, not a cause.
  • On a boot with sheng-smp=1 the touch interrupt stays on the boot CPU (armcpu_spi_attach routes an SPI to the boot affinity) while the probe's thread may run on any core, so its wakeup can cross cores through an IPI. The report then ends with two SMP rows after the four FTS rows (see cpu and diagnostics).
  • With all eight cores running (s1-smp, s2-smp, s3-selfipi) the touch path works unchanged: irq43/0, irq86/0 and irq8/0, no spurious interrupt, no error event and no failed transfer, while the interrupt stays routed to core 0 and the probe's thread ran on core 3 or 4.
  • The RPMh votes are needed (680a818, image p4-power0, fastboot entered by hand from a panic screen): with fuxi-touch-power=0 only the two rail votes are skipped, and the controller never answers. No ready event after three resets and the 4-wire fallback (FTS_ERR_CHIP), chip id 00 00, while no transfer failed; with the votes every boot reads 36 40.
  • ShengHidInput is not selected as a catalog component: that would pull in its catalog dependency ShengNovatekTouch, and UniXNU's build-sheng-hid-input.py reads a prelinked reference candidate that only an earlier sheng build leaves. fuxi builds the unchanged source with its own script, as it does the PMGR bridge. IOHIDFamily sits at the same addresses in the fuxi kernel as in UniXNU's reference: the pinned constructor, handleStart and handleReport instructions match, getMetaClass returns the bound metaclass, and IOHIDDevice overrides the same 18 IOService slots, with its 56 own slots in the kext.
  • The touch archive also holds FuxiSysProbe.cpp, whose SYS row follows the SMP and HID rows of the panic message; it is empty until a root is mounted, so the reports of the earlier boots are unchanged.
  • HID on the device (22579b4, images h2-edge and h3-sgi): IOHIDFamily and IOHIDEventDriver load, IOHIDDevice has 208 bytes, IOHIDResource is found, NativeHidInput's metaclass registers and the touch-only device starts, but no IOHIDEventDriver attaches below it, so no report is sent. NativeHidInput's comments name sheng's default device (keyboard collection first, digitizer second) as the arrangement whose event driver attaches on hardware; FuxiHidProbe.cpp now creates that one, which needs sheng-touch-hid=1 for its digitizer.
  • HID reports reach IOHIDFamily (11db5c0, images h4-hid and h5-edge): an IOHIDEventDriver attaches to sheng's default device, every published contact set goes out as a digitizer report without error (68 and 40), and NativeHidInput's edge wrapper is installed on the event driver.

Completion criteria ​

  • Touches map to the display, simultaneous contacts keep stable IDs, release events arrive, and buttons produce expected HID reports.

Current limits ​

  • One boot of stage 4 reset the phone for an unknown reason (see findings). Unverified: whether the done bit latches without the TCS interrupt, stable contact IDs over longer gestures, and the HID orientation on a portrait panel. HID reports reach IOHIDFamily on the device; no user-space consumer has read them yet.
  • GENI has no setting for the 10 us chip-select hold the vendor driver uses (SPI_DELAY_CS).

Next action ​

  • Check the HID orientation and contact identifiers once backboardd reads the reports, which needs a display path; the buttons need a driver. If a reset happens again, record how the phone reached fastboot and what ran before it.

Evidence ​

GPU ​

planned

Hardware and dependencies ​

  • Adreno 740 of SM8550, the same GPU as sheng.

Configuration ​

  • CONFIG_XNU_NATIVE_ADRENO is not set.

Procedure ​

  • Reuse the sheng GPU components after display and storage work.

Development findings ​

  • No fuxi GPU observation yet.

Completion criteria ​

  • GPU submissions complete, mapping/fence handling is correct, a known image renders, and the selected Metal client presents it.

Current limits ​

  • Not started.

Next action ​

  • None before the first boot.

Evidence ​

Collect evidence using the next action.

Network ​

planned

Hardware and dependencies ​

  • The Wi-Fi chip of the board, from the stock device tree.

Configuration ​

  • No network component is selected.

Procedure ​

  • Identify the Wi-Fi chip and its bus.

Development findings ​

  • No fuxi network observation yet.

Completion criteria ​

  • Scanning, association, address configuration and sustained receive/transmit traffic pass with the selected firmware.

Current limits ​

  • Not started.

Next action ​

  • None before the first boot.

Evidence ​

Collect evidence using the next action.

Audio ​

planned

Hardware and dependencies ​

  • The audio DSP and codec of the board.

Configuration ​

  • No audio component is selected.

Procedure ​

  • Identify the codec and amplifiers from the stock device tree.

Development findings ​

  • No fuxi audio observation yet.

Completion criteria ​

  • Each declared route carries the selected audio format through playback or capture and handles start/stop correctly.

Current limits ​

  • Not started.

Next action ​

  • None before the first boot.

Evidence ​

Collect evidence using the next action.

Power ​

implemented

Hardware and dependencies ​

  • RPMh command database (/sys/kernel/debug/cmd-db on Android) and the regulator summary with the screen on and off.

Configuration ​

  • package/QcomRpmh.c and QcomRpmh.h are the UniXNU-sheng RPMh helpers; the touch probe uses them for its two rails.
  • PMGR bridge for AMFI: package/ShengPlatformPower.cpp is an unchanged copy of UniXNU-sheng's (08cde20); scripts/build-fuxi-platform-power.py builds it into build/sheng-platform-power/libShengPlatformPower.a, which build-xnu-4k-baseline.sh force-loads with CONFIG_XNU_SHENG_PMGR=y; the SoC patch sheng-platform-power.patch calls sheng_pmgr_run() in bsd_init after bsd_bufferinit, and the boot argument sheng-pmgr=2 makes it publish the original ApplePMGRNub with a ShengPlatformPower provider.

Procedure ​

  • Record each rail a driver needs from the command database and the regulator summary.
  • PMGR bridge: ci/check-pmgr-bridge.py checks the linked kernel (the image job runs it after the prelink).

Development findings ​

  • Panel rails vci 3.0 V, vddio 1.8 V and vddd 1.5 V are off while the screen is off; the touch rails stay on.
  • Without a PMGR the boot panics with 'AMFI: No PMGR?' at ConfigurationSettings.cpp:235 (2026-11-02, the first stage 4 image): AMFI, prelinked unchanged, waits for an ApplePMGRNub service from a thread call. Stage 3 images never showed it because the touch probe panics first, within a second of bsd_init.
  • What AMFI does with the nub (UniXNU scripts/audit-amfi-pmgr-contract.py, checked in this kernel by ci/check-pmgr-bridge.py): it reads through the nub, ORs in bit 2 and writes the value back; the nub dispatches to the provider at nub+0x88, slots 320 and 321, and ApplePMGR's own implementations return 0xffffffff and do nothing, as the iPad's AppleT8011PMGR does. No power hardware is involved.
  • UniXNU-sheng's build script checks the provider table against a prelinked candidate left by an earlier local build, which a fresh CI tree lacks (Kerfand published such a kernel as a release asset, which this repository does not do: it holds Apple's kernelcache). scripts/build-fuxi-platform-power.py keeps every object check and binds slots 320 and 321 to the audited addresses; ci/check-pmgr-bridge.py checks the provider table, the instruction contract and the bsd_init call in the kernel that ships.
  • On the device the bridge satisfies AMFI (484a650, image p1-pmgr, no touch probe): the boot passes the old panic and ends in the root device wait (60 s in the source, not measured) with Failed to mount root device, the expected end for a payload without a root volume. No unimplemented-operation panic appeared on the way.

Completion criteria ​

  • Declared power controls and battery readings match observations; each claimed sleep/resume transition restores its dependent peripherals.

Current limits ​

  • Only the touch rails are used.
  • The PMGR bridge is not power management: every power operation of ShengPlatformPower panics (ShengPlatformPower: unimplemented operation N), so anything that asks the PMGR for CPU, clock or sleep control stops the boot with that message.

Next action ​

  • None for the bridge. Record the operation number if a ShengPlatformPower panic ever appears; the next power work is the panel rails, when a display driver needs them.

Evidence ​

Image assembly ​

device-verified

Components: XnuPrelink.

Hardware and dependencies ​

  • The iPadOS 17.7.11 restore image of the OS profile (members fetched with range requests), a platform seed, the firmware volume, the firmware's FdtBlob.

Configuration ​

  • patches/xnu/series applies zzyf-sheng-amp.patch (unchanged from UniXNU-sheng 08cde20) and fuxi-touch-probe.patch after the arch, SoC and OS series.
  • [payload] kernel_args set the touch probe stage and its panic report; panic_screen = true; [payload.adt] owned_memory_size 0x130000000.
  • [bootimg] payload = "image" and [bootimg.image]: header v0 (fuxi-legacy.toml's container), text_offset 0. The options are in OddlyLabs/UniXNU branch fuxi (scripts/unixnu/bootimg.py).

Procedure ​

  • ./unixnu build kernel; ./unixnu build payload --uefi ../uefi-fuxi --unique-chip-id <seed>; ./unixnu build firmware --uefi ../uefi-fuxi --target DEBUG; ./unixnu bootimg --uefi ../uefi-fuxi --target DEBUG --dtb <FdtBlob>.
  • CI encrypts boot.img to ci/boot-image-recipient.pem; ci/decrypt-boot-image.sh decrypts it with the owner's private key.
  • Probe variants: probe keys in [payload] of a scratch copy of board.toml, ./unixnu build payload --kernel <decrypted kernel.macho>, ./unixnu bootimg --fd <firmware volume from check-boot-image.py --fd-out> (README, probe images).

Development findings ​

  • fuxi-touch-probe.patch keeps only original XNU lines as context, so changes to the shared series around it do not break it.
  • Measured on hardware in matejkoo217/uefi-fuxi: the ABL's fastboot boot refuses a header v4 image (Invalid Parameter), so the fastboot image uses the v0 container; and the ABL keeps the boot image ramdisk in vendor_boot, so the payload follows the firmware volume and the image size at header offset 0x10 covers it (Kerfand: 0x0C300200 for a 192 MiB payload).
  • The first kernel build in CI at 8b4c03f linked and prelinked; bsd_init calls native_touch_probe in the linked kernel.
  • The CI image at 2bbd399 advertises image size 0x0C300200 (shim 0x200, firmware 0x300000, payload 0xC000000), the value Kerfand measured for a booting 192 MiB payload. gzip brings the whole image to 11.1 MiB because the payload volume is mostly free space.
  • The payload's ADT has dram-size 0x80000000 and darwinboot-memory-size 0x130000000, the values of UniXNU-sheng and of both community fuxi ports.
  • zzyf-sheng-amp.patch belongs with the E/P cluster types of dts/fuxi.dts: without it the kernel panics in ml_parse_cpu_topology (see cpu). Both boards pin sm8550-common 23b2049, so the patch context is the same.
  • On the device (e901139) the ABL accepts the v0 image, the firmware finds the payload after its volume, the UniXNU menu reads EFI/UniXNU/unixnu.toml from it, and DarwinBoot loads kernel.macho and its ADT: the kernel reaches bsd_init.
  • Probe variants reuse the tested firmware: ci/check-boot-image.py --fd-out writes the firmware volume of a checked image (627fe52a for run 41273003819), and ci/decrypt-boot-image.sh writes the unpatched kernel next to the image when the artifact carries it.

Completion criteria ​

  • Kernel and prelink succeed, payload contains all declared inputs, and the boot image matches the bootloader format and size limit.

Current limits ​

  • The platform seed in CI is a fixed bring-up value, not the phone's own; replace it before booting to userland.
  • Only the path to bsd_init is exercised: the payload carries no root volume and no Data seed yet.

Next action ​

  • Before a payload carries user data, replace the bring-up platform seed with the phone's own (--unique-chip-id).

Evidence ​

Diagnostics ​

implemented

Hardware and dependencies ​

  • The panic screen of the payload (panic_screen = true) while no display driver runs.

Configuration ​

  • fuxi-touch-panic=1 ends the boot with a panic whose message is the probe's four diagnostic rows; the rows are also printed to the kernel log at each stage.

Procedure ​

  • Photograph the panic screen and compare the FTS rows with fuxi_touch_panel_porting.md section 7.2.
  • On a boot with sheng-smp=1 also transcribe the two SMP rows (fuxi_touch_panel_porting.md section 7.2).
  • Several images per sitting: after photographing a report the owner holds power and volume down, and the next image is booted from fastboot; the photographs are sent together.

Development findings ​

  • The other fuxi ports read their results from panic screen photographs as well.
  • The DEBUG firmware prints its log on the panel before the handoff (matejkoo217/uefi-fuxi), the only console before the touch report.
  • The earlier ci/ scripts compiled the probe inside a UniXNU-sheng build; the fuxi kernel build replaces them.
  • ci/check-boot-image.py checks a decrypted image before fastboot boot against boot.json, board.toml and the firmware checkout: container and load addresses, the shim header, the firmware volume, the payload files, the ADT root and /chosen values, and the boot arguments.
  • The panic screen draws through the kernel's frame buffer mapping in vinfo, which initialize_screen sets at the end of PE_create_console, so a panic before that leaves the panel as it was. A reset probe cannot tell its own reset from one caused by the code it wraps.
  • The entry probe bands work on this panel: DarwinBoot's green band and the probe's blue and magenta bands appear in order, each 1/16 of the screen height, so the probe's use of the boot-args video geometry is right for fuxi.
  • ci/decrypt-boot-image.sh also decrypts the kernel (kernel.macho.gz.enc) when the artifact carries it.
  • ci/check-pmgr-bridge.py reads the prelinked kernel without tools (Mach-O segments and symbol table in Python); with --contract-only it checks only the AMFI -> PMGR contract, which passes on the e901139 kernel.
  • The panic screen draws the touch report on fuxi: the probe's four rows, the panic frames and the page counters, below the green and blue bands (it paints over the magenta band).
  • With sheng-smp=1 the touch report ends with two SMP rows (package/FuxiTouchProbe.cpp smpText) built from ArmCpu's secondary-core variables, which the probe references weakly as it does armcpu_spi_attach (no rows without ArmCpu); without sheng-smp the message is as before. Locally the rows compile without warnings under the probe's -Wall -Wextra -Werror (clang 21, arm64-apple-ios17), and every ArmCpu symbol is a weak reference; the CI kernel build is the check that counts.
  • The SMP rows read on the panel (s2-smp, s3-selfipi). ArmCpu increments its shared IPI totals from several cores without atomics: in s3-selfipi the per-core counts add up to 85 while the total taken reads 84, so the per-core counts are the ones to use.
  • ci/check-hid-bindings.py checks the HID bindings in the prelinked kernel: the IOHIDFamily instructions UniXNU pins, getMetaClass's metaclass, IOHIDDevice's 207-slot vtable with its 56 own slots at the bound addresses, and NativeHidInput's vtable, of which only its ten own slots may differ; --contract-only checks the family alone. With fuxi-touch-hid=1 the touch report ends with a HID row (FuxiHidProbe.cpp fuxi_hid_row), and the run ends only at a lift after the HID start has settled (device and event driver up, a step failed, or 20 s), so an early tap needs a second one.
  • The touch report ends with a SYS row once a root is mounted (package/FuxiSysProbe.cpp fuxi_sys_row): the root's filesystem type and the device it came from, the first six processes by pid with their names, and the number of processes. ci/check-ram-block-bindings.py checks the RAM block bindings after the link, as check-hid-bindings.py does for HID.
  • With HID requested, a tap before the HID start settles does not end the run, so the owner has to tap again after about 20 s: in h1-hid the only tap came early and the root device wait ended the boot first.
  • The SYS row reads on the panel (r3-ramblock): the root's filesystem and device, the first processes by pid with their names (cut at 16 characters, as proc_name gives them) and their number.

Completion criteria ​

  • Each claimed milestone has a reproducible command, a matching source/image revision and an accessible result.

Current limits ​

  • Device records are panel photographs, transcribed in evidence/2026-11-02-first-boot.md; the photographs stay with the owner. No kernel log is kept: there is no serial console, and the ADT has no /pram node, so no panic region survives the reset.

Next action ​

  • Replace the panel photographs with a kernel log once a storage or USB path exists.

Evidence ​

Released under the BSD-2-Clause-Patent License.