Skip to content

Redmi K60 (mondrian/SM8475): UniXNU UEFI bring-up (mzyxsl) ​

mzyxsl/UniXNU-mondrian

Redmi K60 (mondrian/SM8475). Format 2 device: the kernel builds and prelinks, the payload and the boot image still need the board ADT and the hardware bring-up.

Source record: porting.toml, 76552b88441757defce4080a241c2f0ea7464538.

This page is generated from the device record.

AreaStatus
Configurationimplemented
Firmware and bootimplemented
CPU and interruptsimplemented
Memory and DMAimplemented
Storage and rootimplemented
Displayimplemented
Inputplanned
GPUimplemented
Networkplanned
Audioplanned
Powerplanned
Image assemblyhost-verified
Diagnosticsplanned

Configuration ​

implemented

Components: ArmDTPlatform, ArmCpu, XnuPrelink.

Hardware and dependencies ​

  • Four layers merge in order: UniXNU profiles/arch/arm64, the SM8475 soc.toml from UniXNU-sm8475-common, the ipados-16.6.1-20G81 OS profile and this board.toml.
  • Source pins are explicit 40-character commits in feeds.toml: xnu 8796.141.3 (1b191cb5), libplatform (1b2b22f2), libdispatch (ca80d303), AvailabilityVersions (f3b52ca7) and sm8475 (d0c90f1e).
  • The OS profile carries [ipsw] (iPad7,1, 16.6.1, 20G81, device class j120ap, chip 32785 = 0x8011, board 12, fetch KernelCache/DeviceTree/StaticTrustCache/Cryptex1,SystemTrustCache) and [kernel.prelink] (32 roots, slide 0 for the auto-slide wrapper), so board.toml carries neither table.

Configuration ​

  • board.toml schema_version = 2; [board] name mondrian, soc SM8475, soc_source sm8475, os_profile ipados-16.6.1-20G81, kit mondrian, no example flag.
  • [components] include replaces the arch list with ArmDTPlatform, ArmCpu, XnuPrelink and PongUfs; [kernel] defconfig mondrian_defconfig with arch/soc/os fragments, patch_series device:patches/xnu/series only, archives component:ArmDTPlatform/ArmCpu/PongUfs and driver_records DEVELOPMENT_ARM64_PONG.
  • [device_tree] source device:dts/mondrian.dts with include_dirs soc:dts; [userdata] first_lba 53342976, last_lba 61731583, lba_bytes 4096 and windows OS + Cryptex1,SystemOS.
  • patches/xnu/series holds 98 entries in application order: 4 of them are the 021x display patches, 13 are K60-specific (trust cache, panic reporting, compiler fixes) and the remaining 81 are the shared Darwin 22 / Pong bring-up patches; the other 22 files in the directory are intentionally unlisted and documented as switched off or version-bound.

Procedure ​

  • Clone UniXNU (branch k60) with submodules and place UniXNU-sm8475-common, UniXNU-mondrian and uefi-mondrian beside it.
  • ../UniXNU-mondrian/scripts/k60-macos-preflight.sh; ./unixnu defconfig mondrian_defconfig -D ../UniXNU-mondrian; ./unixnu fetch -D ../UniXNU-mondrian; ./unixnu status --json -D ../UniXNU-mondrian; ./unixnu tree -D ../UniXNU-mondrian.
  • python3 scripts/porting-record.py check --device ../UniXNU-mondrian in the core checkout, and python3 ../UniXNU-mondrian/scripts/check-porting-paths.py for the offline identity, component, path and series checks.

Development findings ​

  • Migrated from format 1 on 2026-10-28: the output directory became out/mondrian/ipados-16.6.1-20G81/mondrian/ and every pin moved into feeds.toml.
  • The lock file still recorded the earlier sm8475 commit 70f20268 while feeds.toml pinned the later d0c90f1e (2026-10-28 15:19Z, 'tag every CPU node with a Darwin cluster type'). An explicit format-2 commit wins over the lock, so the lock entry was corrected to the feed value instead of leaving two answers.
  • board.toml [payload] trust_cache_sha256 was a bare string. The payload step tests membership in a list, so a string made every --trust-cache fail the board allow-list; it is now the one-element array of the shipped bundle hash.
  • docs/NEXT-STEPS.md kept the single-CPU first-boot line next to a board that sets cpus = 8. The note now says cpus=1 is the bring-up baseline and that the eight-CPU claim is still unmeasured, so the two documents no longer contradict each other.

Completion criteria ​

  • Every pin resolves to an existing commit, status reports four components and the ordered device series, and both the official and the offline record check pass.

Current limits ​

  • No core checkout or network in this workspace: the pins were confirmed through the GitHub API and the resolution of [ipsw]/[kernel.prelink] was read from the profile, not from a live status run.
  • SoftwareAES from the arch profile is not selected by this board; the K60 links its platform archives through the pong build scripts instead of the format-2 archive recipes.

Next action ​

  • Run status --json and tree on a host with all four checkouts and paste the resolved [ipsw], [kernel.prelink] and archive order into this area as host evidence.

Evidence ​

Firmware and boot ​

implemented

Components: DarwinBoot.

Hardware and dependencies ​

  • Mu-Silicium platform tree uefi-mondrian: Resources/Configs/mondrian.toml, Platforms/Xiaomi/mondrianPkg/mondrian.dsc and the FD at Build/mondrianPkg/{target}_CLANGPDB/FV/SILICIUM_UEFI.fd.
  • Firmware must embed UniXNU, UniXNUDxe, DarwinBoot and UniXNURamDiskDxe plus DiskIoDxe and FAT so the payload FAT volume is discoverable before the config is read.
  • Boot inputs from the OS profile: kernelcache hash d260defc, the 20G81 trust cache bundle (SHA-256 344ae644) and the Data seed; the platform seed is only needed when a board passes --unique-chip-id.
  • Vendor binaries for display, keypad and logger come from uefi-mondrian; this repository only lists them in proprietary-files.txt.

Configuration ​

  • [firmware] config mondrian, dsc Platforms/Xiaomi/mondrianPkg/mondrian.dsc, fd Build/mondrianPkg/{target}_CLANGPDB/FV/SILICIUM_UEFI.fd, patches AsmMacroLib.patch and Boot-Manager.patch, and packages {uefi}, {tree}/package, {tree}/drivers/DarwinBoot22, {edk2}, Platforms/Xiaomi, Common/Mu, Silicon/Qualcomm, Silicon/Silicium, Silicium-ACPI and Binaries/pong.
  • [firmware.defines] DEVICE_MODEL 0, ENABLE_SECUREBOOT 0, MEMORY_PROTECTION TRUE, SHIP_MODE FALSE, BUILDID_STRING UniXNU-Mondrian.
  • DarwinBoot22 contract: boot_args 0x2e0 bytes with a 608-byte command line at 0x6c and Video[6] at 0x28; one 256 MiB EfiConventionalMemory window aligned to 2 MiB plus the kernel base; ADT capped at 16 MiB; trust cache sidecar 1..128 modules with strictly ascending SHA-1 entries.

Procedure ​

  • ./unixnu build firmware --uefi ../uefi-mondrian -D ../UniXNU-mondrian, or scripts/build-firmware.sh from a Pong-layout checkout.
  • python3 scripts/verify-firmware.py --output-dir <build> checks the FV header, checksum, and the UniXNU (9b665204), UniXNUDxe (29b950cf), DarwinBoot (1b64af87) and PongBootMemoryDxe (7b27b767) FFS files plus ARM64 PE machine 0xaa64.
  • scripts/build-darwinboot22-macos.py compiles the loader against the Darwin 22 headers without touching the saved FD; the resulting DarwinBoot22.efi is what a payload-side boot image stages.

Development findings ​

  • The loader installs the UniXNULoader protocol in RAM only and restores the previous interface on unload; no firmware variable or partition is written, which is what keeps fastboot boot bring-up read-only.
  • DarwinBoot must run at EL1: DarwinCurrentEl() != 4 returns EFI_UNSUPPORTED and keeps the firmware alive, so a payload started at EL2 never reaches XNU.
  • The green bar is the trampoline, not the kernel: it paints the top 1/16 of the framebuffer 0xFF00FF00, clears SCTLR_EL1 M/C/I, and branches to the kernel entry. README.md:19-20 therefore records payload entry as unconfirmed, which is why this area is not device-verified.
  • The one-window layout is args at 16 KiB alignment after the kernel image, ADT one page later, page-aligned ramdisk, trust cache, then a copied trampoline page; Used must stay below WindowSize minus 2 MiB or the boot is refused.
  • prebuilt/20G81-trustcache.bundle is a 61,764-byte bundle with two modules (2510 and 61 entries, version word 2) that satisfies the loader validator, and its SHA-256 equals the board allow-list entry.
  • The host tests are real but narrow: Test/Validation.c drives 24 rejected loader requests (6 kernel paths, 6 memory windows, 4 ramdisk layouts, 8 trust-cache layouts) plus the GOP format check, Test/window-size.c asserts five accepted and six rejected window sizes, and scripts/test-window-qemu.py asserts the post-ExitBootServices pixel 12d4a6 with df2020 and aa33dd as failure controls. None of these ran in the last Mac round.

Completion criteria ​

  • Firmware builds with the required modules, the FD passes verify-firmware.py, the menu appears, DarwinBoot prints its handoff lines and the owned window survives into XNU.

Current limits ​

  • Device observation currently stops at the green bar: no serial, ramoops or framebuffer line proves that XNU executed after the trampoline.
  • The boot image has not been packed for this board yet, so the documented payload path is exercised only by host-side tests and QEMU.
  • USB log export is a design in docs/USB-LOGGING-PLAN.zh.md; no USB logger or host reader exists in the tree.

Next action ​

  • Boot one image with the serial console enabled and capture the first XNU-side line after the green bar, then record it as device evidence with the firmware and payload revisions.

Evidence ​

CPU and interrupts ​

implemented

Components: ArmCpu.

Hardware and dependencies ​

  • Eight SM8475 CPUs with MPIDR affinities 0x000..0x700 declared in the SoC dts/sm8475.dtsi, plus the PSCI conduit and the GICv3 distributor and redistributor windows.
  • Measured on the unit: GIC distributor 0x17100000 and redistributor 0x17180000; the driver reads the real bases from /arm-io ranges and /arm-io/intc reg instead of hardcoding them.
  • Redistributor stride is accepted only as 0x20000, 0x30000 or 0x40000 (falling back to TYPER.VLPIS), matching the 0x17180000 window of this SoC.

Configuration ​

  • CONFIG_XNU_ARMCPU=y, CONFIG_XNU_ARM_DT_PLATFORM=y, CONFIG_XNU_NATIVE_ARCH_TIMER=y, CONFIG_XNU_ARMVIRT_PLATFORM=n in configs/mondrian_defconfig.
  • [payload] cpus = 8 caps the ADT CPU list; the AppleARMCPU multi-CPU validation is bypassed by the kext patch on _validateConfiguration.
  • Kernel side: MAX_CPUS 8, MAX_CPU_CLUSTERS 2, GENERIC_PSCI_SMP and ARM_ARCH_TIMER from the psci-smp patch's board_config.

Procedure ​

  • python3 scripts/build-pong-armcpu-macos.py compiles the GICv3/PSCI provider against the Darwin 22 headers; the archive is linked through [kernel] archives component:ArmCpu.
  • Prelink and boot, then read the XNU serial output for the per-CPU registration lines and the PSCI start results.

Development findings ​

  • ArmCpu registers every device_type = cpu nub first and panics if armcpu_registered != ml_get_cpu_count(), so a DTS that hides a CPU fails loudly instead of booting with fewer cores.
  • MPIDR matching uses aff0-aff2 plus aff3 against GICR_TYPER[63:32]; the boot affinity is mpidr & 0xff00ffffff and SGIs are addressed through ICC_SGI1R_EL1 built from all four affinity fields.
  • Only INTID 0 (SGI0 IPI) and INTID 27 (CNTV PPI) are enabled; 32..1019 dispatch to SPI owners with priority 0xa0, and the only in-tree SPI client is Adreno at 332.
  • PSCI uses 0xC4000003 CPU_ON and 0x84000002 CPU_OFF with 5 s start and 3 s stop timeouts, cleaning __TEXT, __TEXT_EXEC, __DATA, __DATA_CONST, __BOOTDATA and every cpu_data_t to the point of coherency before the call.
  • The UFS controller declares SPI 265 in the DTS but never attaches it, so storage is polled rather than interrupt driven; that is a deliberate bring-up choice, not a missing GIC feature.
  • docs/NEXT-STEPS.md documents the first bring-up with cpus=1 and warns against cpus=8, while board.toml has set cpus = 8 since the 2026-10-29 measurement. The note now marks the single-CPU line as the bring-up baseline and points at this area for the missing eight-CPU log; the board value stays.

Completion criteria ​

  • Every enabled CPU reaches its entry checkpoint, the timer interrupt count advances on each CPU, and an IPI reaches every started secondary.

Current limits ​

  • Implemented from source only: the eight-CPU start is unproven on hardware because no run log is stored here.
  • The pong_adt_contract audit accepts exactly one running CPU, so an eight-CPU ADT can only be produced outside that audit; the incompatibility is unresolved.

Next action ​

  • Capture one boot log with 'ArmCpu: CPU n MPIDR=0x... registered' for all eight CPUs, no 'psci: cpu n ... was not started' line, and the armcpu_timer_interrupts delta across a one-second sleep.

Evidence ​

Memory and DMA ​

implemented

Hardware and dependencies ​

  • One contiguous DRAM range: dts/mondrian.dts memory@80000000 reg = <0x0 0x80000000 0x3 0x0>, 12 GiB, matching [payload] dram_size 0x300000000.
  • The ADT generator requires exactly one enabled memory node with exactly one range; the five-bank 16 GiB layout reported in issue #1 cannot be described and needs the narrowed bank0 form documented in README.md.
  • DMA width and contiguity limits: the UFS engine needs one physically contiguous 139 KiB buffer below 4 GiB when CAP bit 24 is clear, and the window parser accepts 0x10000000..0x200000000 in 2 MiB steps.

Configuration ​

  • [payload] dram_size 0x300000000, cpus 8 and timeout 5; the DTS /memory range is the single source that the ADT generator checks against dram_size.
  • CONFIG_XNU_NATIVE_RAM_BLOCK=y, CONFIG_XNU_NATIVE_PAN=y, CONFIG_XNU_DT_PANIC_REGION=y, CONFIG_XNU_PONG_RAMOOPS_CONSOLE=y and CONFIG_XNU_PONG_VOLATILE_PANIC_LOG=y in configs/mondrian_defconfig.
  • Panic and ramoops: darwinboot-memory-size is requested through [payload.adt] owned_memory_size when set; the loaded window is capped at 0x200000000 and must be 2 MiB aligned.

Procedure ​

  • Build the board DTS with the SoC include path: dtc -I dts -O dtb -i ../UniXNU-sm8475-common/dts dts/mondrian.dts.
  • scripts/build-window-probe.py and scripts/build-window-probe-local.py build the own MMU-off probe kernel, and scripts/build-pong-ram-block-macos.py builds the volatile RAM block provider.
  • scripts/test-window-qemu.py reads the framebuffer after ExitBootServices and compares it with the pass and failure colours.

Development findings ​

  • The handoff window is the first boot constraint, not a driver detail: docs/NEXT-STEPS.md records a working p0prime of dram-base 0xB9200000 with dram-size 0x26C0000 and exactly 145502 free 4 KiB pages (36905 at 16 KiB), and that a larger window dies silently at the XPU wall 0xE9F00000.
  • docs/NEXT-STEPS.md recorded that window as '0x26C0000 (620 MiB)'. The page count proves the hex value was wrong: 145502 free 4 KiB pages is about 568 MiB after the kernel image, so the documented value is now 0x26C00000 with the correction noted in place.
  • NativeRamBlock reads /chosen/memory-map RAMDisk or PackedRAMDisk (mutually exclusive, 4 KiB aligned, at most 0x80000000 bytes) and switches from sparse pages to a SeedDisk above 2 GiB, which is what keeps a 32 GiB Data window tolerable.
  • The persistence overlay ('SHNGDATA' v1, 64-byte header with generation and CRC32) only calls weak-imported native_ufs_store_* symbols that no file in this repository defines, so persistence cannot link as committed.
  • probes/window-probe.c is an independent diagnostic kernel, not XNU: it asserts the boot-args magic 0x00020002, CurrentEL 4 and the SCTLR bits, requires the window to be exactly 0x10000000, and writes two patterns at every 4 KiB page of the unused window with an exception path that prints ESR/FAR/ELR.

Completion criteria ​

  • All handoff and reserved regions are accounted for, the ADT carries the DRAM base/size and window size, and every enabled DMA engine reaches its allocated range with the documented cache policy.

Current limits ​

  • The realised window, SMMU geometry and DMA addresses have not been measured on this unit; the 12 GiB / 620 MiB pair above comes from the maintainer notes, not from a captured boot log.
  • The 16 GiB variant needs a different /memory form and a matching dram_size; the ADT generator refuses multi-range memory, so that unit cannot boot until the narrowed form is selected.

Next action ​

  • Capture a window-probe boot and record 'PONG WINDOW PASS' with PAGES CHECKED, then set [payload.adt] owned_memory_size from the measured value instead of the documented one.

Evidence ​

Storage and root ​

implemented

Components: PongUfs.

Hardware and dependencies ​

  • UFS host at 0x01d84000 (0x3000), GCC UFS page 0x00187000, SMMU 0x15000000 (0x100000) with stream ID 0xe0, 78 context banks and 120 stream-match groups declared in the DTS.
  • SMMUv2 register facts: IDR1.NUMCB 75 on the reference device caps the declared 78, IDR2 must advertise A64, banks and pages must fit the 0x100000 window and masked stream IDs are refused.
  • Root volumes come from the OS profile disks: OS 096-57366-083.disk.cdr and SystemOS 096-56782-083.dmg, wrapped in a SHNGROOT v1 manifest with 128-byte window records.
  • The Data volume is the committed seed seed/k60-data-seed.dbr: 33,554,432 bytes of raw APFS whose volume is 'OwnerData', SHA-256 5fd2f3566e989d11cde74289151888392615a4771e5b616ff5e98ca3b24ffc03, regenerated by scripts/k60-make-data-seed.py on macOS.
  • The dual-system plan needs an unlocked TWRP with root ADB, decryptable /data and F2FS online shrink support before any partition work; see docs/DUAL-SYSTEM.md.

Configuration ​

  • [userdata] first_lba 53342976, last_lba 61731583, lba_bytes 4096 with windows OS and Cryptex1,SystemOS: 8,388,608 blocks, 32 GiB starting at byte 218,492,829,696.
  • CONFIG_XNU_NATIVE_UFS=y, CONFIG_XNU_OWNER_CACHE_MOUNT=y, CONFIG_XNU_OWNER_CACHE_STARTUP=y, CONFIG_XNU_OWNER_DATA_MOUNT=y, CONFIG_XNU_OWNER_UNION_SYSTEMOS=y, CONFIG_XNU_OWNER_DATA_UFS=n.
  • CONFIG_XNU_OWNER_DATA_VOLUME_UUID 6DCCF70A-6FD6-403E-ACED-95313CD7CBB8 is the 'OwnerData' volume inside the committed seed (the Pong constant 576A2203-F35D-45DA-80B0-11EF01DDA8C9 never matches it); CONFIG_XNU_OWNER_CACHE_VOLUME_UUID 15B507FA-21B7-4EE5-B55D-0F3445DE0752; [payload.adt] root_match_volume_uuid C5DAF029-2070-4F68-B3CC-1D1320344CF0.
  • Writes are refused unless the boot argument pong_ufs_readwrite=1 is present, and the SCSI allow-list is TUR, START STOP UNIT on LUN 0xd0, READ CAPACITY(10), READ(10), WRITE(10) with FUA and SYNCHRONIZE CACHE(10).

Procedure ​

  • ./unixnu build userdata --dry-run then ./unixnu build userdata for the SHNGROOT image, or scripts/build-userdata.py --component-dir out/ipsw/20G81 --os-disk <cdr> --systemos-disk <dmg>.
  • scripts/derive-ios16-storage-bindings.py --tree <tree> derives the 190 IOBlockStorageDevice slots from the genuine kernelcache; scripts/audit-ios16-storage-abi.py checks 176 bindings and the 0x90 class size.
  • scripts/k60-make-data-seed.py --profile <profile> --output k60-data-seed.dbr builds the 32 MiB APFS Data seed on macOS; scripts/install-pong-ufs-data.py installs a Data window over ADB in recovery.
  • For the independent partition, follow docs/DUAL-SYSTEM.md: partition.py inspect, plan with the f2fs-shrink helper, apply, then verify.

Development findings ​

  • The transport takes over an already-initialised controller: it requires HCE bit 0, HCS low nibble 0xf, zero doorbells and a clear fatal interrupt mask, then runs a polled command ring with no resubmission and a poisoned buffer on timeout.
  • LUN 0 must report exactly 4096-byte blocks through READ CAPACITY(10); any other geometry is refused as 'unsupported LUN geometry', which is the first thing to check on a different UFS part.
  • Every DT property is equality-checked against the compiled constants before the first MMIO read, so a mismatch between dts/mondrian.dts and drivers/NativeUfs fails at probe with a specific message instead of corrupting a controller.
  • The Data mount names the board's own seed volume. With the inherited Pong constant the lookup could never match, so /private/var stayed unmounted and mkbinit could not write /private/var/keybags/systembag.kb. The committed seed really carries 6DCCF70A-6FD6-403E-ACED-95313CD7CBB8: its volume superblock at 0xa2020 holds that UUID at +0xd0 and the name 'OwnerData' at +0x2a0, and both survive in the checkpoint copies at 0x15c020 and 0x15f020.
  • A 32 MiB seed stays far below the 2 GiB switch in NativeRamBlock, so the provider keeps sparse pages instead of building a SeedDisk; that is why the committed image can be this small and still hold a writable /private/var.
  • Data persistence is deliberately off: CONFIG_XNU_OWNER_DATA_UFS=n compiles the Data window out, and patches/xnu/...-pong-ufs-data.patch is not in patches/xnu/series, so the in-tree Data branch never builds.
  • install-pong-ufs-data.py still hardcodes Pong geometry (sda13, 0x413248..0x75a40ea, 454 GiB) and refuses anything else, so it cannot install the K60's own unixnu partition as written.
  • docs/DUAL-SYSTEM.md fixes no size: the only concrete number in the repository is the 32 GiB [userdata] window, which the partition plan must match.

Completion criteria ​

  • Host: register, clock, link and SMMU facts match the board inventory. Reads: READ CAPACITY, GPT and the SHNGROOT windows are correct. Root: the configured volume mounts and launchd starts. Persistence: writes survive a reboot inside the allocated Data region.

Current limits ​

  • No device log exists for this board: the read path is implemented but unproven, the boot RAM seed is the only writable Data, and the persistent UFS Data window is not even compiled.
  • The 32 GiB window is unverified against the real GPT; the scripts and the record disagree about which partition it describes.
  • The dual-system partition has not been created, and the third-party TWRP has not been checked for F2FS online shrink or decryption.

Next action ​

  • Run partition.py inspect on the unit, then align [userdata] first_lba/last_lba with the real unixnu GPT entry and capture one PONG_UFS log with LUN, block size and manifest window counts.

Evidence ​

Display ​

implemented

Hardware and dependencies ​

  • Panel m11a_42_02_0a: 1440x3200 native canvas, DSC video mode, stride taken from vinfo.v_rowbytes, refresh reported through the panel driver.
  • The firmware must publish an EFI_GRAPHICS_OUTPUT_PROTOCOL surface (ABL splash framebuffer plus SimpleFbDxe on this port); DarwinBoot only accepts PixelBlueGreenRedReserved8BitPerColor.
  • Userland presentation needs the 20G81 QuartzCore cache layout, which ui_compat.c pins by address with a slide of base - 0x180000000.

Configuration ​

  • K60_PANEL_WIDTH 1440 and K60_PANEL_HEIGHT 3200 with the k60_panel tunable defaulting to 1 (0210); the canvas is forced to the panel only when vinfo reports exactly 1440x3200 and fbwidth/fbheight/fbscale are refused.
  • kFBDisplayID 1 (0 means no display) and Timing/Color ids 1 (0213); display services start unless pong_software_ui=0 (0211).
  • XNU_K60_ACCEL_STUB=1 (default under PONG && (DEVELOPMENT || DEBUG) && !SECURE_KERNEL) publishes the empty IOAcceleratorES stub with IOAcceleratorStub=true and no MetalPluginName, and accel_stub=0 disables it at run time (0212).

Procedure ​

  • The 0210..0213 display patches are series entries 0209..0213 and are applied in that order; rebuild the kernel and the payload after changing them.
  • Boot and read the XNU console for 'K60 DISPLAY: first frame presented (swap n, WxH canvas ...)' and inspect the published K60Display dictionary for Stride and RefreshHz.

Development findings ​

  • The display stack intentionally starts from the firmware surface: 0210 publishes a K60Display dictionary (PanelWidth, PanelHeight, CanvasWidth, CanvasHeight, Stride, RefreshHz, Native) and refuses to scale a non-native canvas, so the first frame is never a stretched picture.
  • An empty accelerator keeps clients alive without a GPU: 0212 answers up to 16 scalar outputs plus the structure output, logs the first 24 selectors and advertises IOAcceleratorStub, which is what lets SpringBoard present a software frame before the GPU port is enabled.
  • The console has two fixes for the first UI frame: the touch-console patch releases the graphics console after kPEGraphicsMode so vcputc stops painting over the frame, and the console-sink-dedup patch removes duplicate sinks.
  • Userland forces the framebuffer format to 'BGRA' (0x42475241), caches a null renderer and keeps display readiness, with four QuartzCore hooks at 0x1d586e000/0x18826a418, 0x1d586c7c0/0x1884aa47c, 0x1d586c7c8/0x1882787f4 and 0x1d586dfd8/0x1882df668.
  • The 1080x2412 main screen selects PongStatusBarVisualProvider1080 (a Split1080 subclass), so the status bar geometry follows the hardware screen, not the 1440x3200 panel canvas.

Completion criteria ​

  • A known frame appears with the correct geometry and colours, the panel reports its native stride and refresh, presentation survives window changes, and backlight control works.

Current limits ​

  • No device log of a presented first frame exists in the repository; the area is implemented from source only.
  • Backlight control is not implemented on this port; the panel driver only supplies the initial brightness.
  • GPU-backed presentation depends on the gpu area and its accelerator stub replacement.

Next action ​

  • Boot once and capture 'K60 DISPLAY: first frame presented (swap 1, 1440x3200 ...)' together with the K60Display Stride and RefreshHz values, then record it as device evidence.

Evidence ​

Input ​

planned

Hardware and dependencies ​

  • Goodix 'Berlin-D' touch controller behind GENI/QUP0 SE4 at 0x990000 with TLMM at 0xf000000, GCC at 0x100000, RSC at 0x17a20000 and cmd-db at 0x80860000; pins 16/17/18/19 are MISO/MOSI/CLK/CS, 21 is INT and 20 is RESET.
  • The panel is 1080x2412 in touch coordinates, so the driver scales x and y to 0..65535 against max-x 1080 and max-y 2412.
  • Physical keys come from the SPMI PMIC arbiter (0xc400000 cnfg 0xc42d000, observer 0xc440000, EE 0) with PON ppid 0x013 and GPIO6 ppid 0x18d.
  • The XNU-side code only runs when the boot argument pong_touch=1 is present and the DT node matches the compiled resources exactly.

Configuration ​

  • pong_touch=1 enables the driver; touch_flip 1 mirrors left/right and 2 mirrors top/bottom for the panel orientation.
  • The driver publishes /dev/pong-touch (char, 0444, root:wheel, 512-entry ring) with struct pong_touch {u64 time; u32 type, finger, x, y} and /dev/pong-buttons on minor 1.
  • userland/touch.c publishes virtual HID services: digitizer page 0x0d usage 0x04 and consumer page 0x0c usage 0x01, with key usages 0x30 power, 0xe9 volume up and 0xea volume down.

Procedure ​

  • Add the three patches to patches/xnu/series at their intended positions, rebuild the kernel, repack the payload and boot with pong_touch=1.
  • Read the XNU console for the Berlin-D probe line and the first finger report, then check /dev/pong-touch from the userspace bridge.

Development findings ​

  • The driver is complete as source but is not built: pong-touch-console.patch, pong-physical-keys.patch and pong-goodix-resume.patch are absent from patches/xnu/series, so the documented kernel does not contain it. That is why this area stays planned while the firmware keypad path works.
  • The resume patch exists because Android or recovery can leave the controller in sleep or gesture mode: it resets, re-probes and clears one report byte before publishing input.
  • The GENI engine runs at 4 MHz, mode 0, cs0, 8-bit MSB first with a 200 ms deadline and a polled FIFO, and every clock and power vote is a direct register write (GCC vote 0x62008 bits 6,7,8,9,14; RPMh ldoc12 1800 mV then ldoc3 2700 mV).
  • The same touch patch carries the only real RPMh regulator writer in the tree, so enabling touch would also give the first working regulator votes for the panel and controller rails.
  • The firmware keypad (K60KeypadDeviceLib over SPMI PON/GPIO) already works in the UniXNU menu, which is why menu navigation is verified while XNU input is not.

Completion criteria ​

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

Current limits ​

  • Nothing in this area is compiled by the current series, so there is no build or device result to report.
  • No captured report proves that the Goodix controller answers on this unit.

Next action ​

  • Add the three input patches to the series, boot with pong_touch=1 and capture the Berlin-D probe line plus one finger report as device evidence.

Evidence ​

GPU ​

implemented

Hardware and dependencies ​

  • Adreno 730 v3 (chip id 0x07030002, Mesa FD725 / A7XX Gen1) with 2 MiB GMEM, HighestBankBit 16 and a 64x32 tile.
  • Windows: GPU core 0x03d00000, GMU 0x03d68000, RSCC 0x03d50000, cx_misc 0x0b290000, PDC 0x03d9e000, GPUCC 0x03d90000, GPU SMMU 0x03da0000 and AO 0x03d40000; GCC 0x100000 and RSC 0x17a20000.
  • Firmware blobs are not stored here: gmu_gen70000.bin, a730_sqe.fw and a730_zap.mdt/.b01/.b02 are supplied at build time, and ZAP uses PAS id 13 at 0x8b91a000.
  • SPI 332 (GPU), 336 (HFI) and 337 (GMU) are the only interrupts the CP attaches today.

Configuration ​

  • CONFIG_XNU_PONG_ADRENO_PLATFORM=n and CONFIG_XNU_NATIVE_ADRENO=n in the committed defconfig: the GPU path is implemented but switched off until the display stack is verified without it.
  • pong_adreno_start=1 enables the bring-up entry; ASIDs are 1 for the GMU and 2 for the GPU with shared_asid 1, ignore_top_byte 1 and polled_faults 1.
  • Metal frontend: 20G81-only gate on sysctl kern.osversion, _MTLDevice instance size 528, connection types 0x4147 and 0x4148, abi_version 1 and 16 native methods.

Procedure ​

  • scripts/build-pong-adreno-platform.py --output-dir <dir> --firmware-dir <dir> regenerates the GEN7 register header, compiles the objects and embeds the five firmware blobs into libPongAdrenoPlatform.a; the report sets hardware_qualified=false.
  • scripts/build-pong-turnip.py and scripts/build-pong-metal-bridge.py/--plugin.py produce libvulkan_freedreno.dylib and the PongAdrenoMetal bundle for the native Metal path.
  • Boot with pong_adreno_start=1 and read the 'PONG ADRENO' records for the furthest passing phase.

Development findings ​

  • The GPU port reuses Nothing Phone (2) SM8475 work: the register windows, GMU boot config, HFI table and CP firmware names are the Pong values, and the only K60-specific change is the SoC timer offsets 0x16220/0x16224.
  • GMU bring-up requires CM3 held in reset (GEN7_GMU_CM3_SYSRESET == 1), CM3_CFG 0x4052, AHB_FENCE_RANGE_0 0x80c808a0 and a firmware-ready poll of 0x1ff == 0x100 before HFI power commands 4, 3, 14 and 30 and the START10 handshake.
  • The GPU SMMU is SMMUv2 LPAE with 4 KiB leaves and ACTLR 0x32b; it leases SID0|0x400 and SID1|0x401 in one context, excludes the secure SID2|0x402 and routes the GMU on SID5|0x405.
  • The Metal shim is gated on IOS16.6.1: it requires the 528-byte _MTLDevice, advertises abi_version 1 with the exact chip and GMEM values and refuses other OS builds, and configs/pong-iogpu-20G81.json pins the 20G81 IOAcceleratorES metaclass and prelink base 0xfffffff004fbc000.
  • The two metal2vulkan patches add the IOS16_6_1 level with a 7x64-bit MTLPixelFormatGetInfo ABI and optional surface row padding (PONG_IOSURFACE_PADDING=1); the two Mesa patches cap the heap at va_size and retry allocations after a 250 ms fence wait.
  • A link hazard sits behind the switch: PongAdrenoPlatform.cpp calls pong_ramoops_diagnostic_write, which is defined only in the unlisted settings-pmsg patch, so enabling CONFIG_XNU_PONG_ADRENO_PLATFORM without also adding that patch cannot link.
  • Nothing in the tree has produced a GPU frame: README.md records only that the v98 phone reached the iOS main UI with Adreno and Metal unconfirmed.

Completion criteria ​

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

Current limits ​

  • Switched off in the committed defconfig, and no device log records which bring-up phase was reached last.
  • The firmware blobs are supplied externally with image_verification skipped, so it is unknown whether they match the unit.
  • The scanout path needs the display area verified first.

Next action ​

  • Boot once with pong_adreno_start=1, read the 'PONG ADRENO: <phase> result=<n>' records and report the last passing phase, in particular whether 'CP ready; authenticated nonsecure fence' appears.

Evidence ​

Network ​

planned

Hardware and dependencies ​

  • The K60 carries a WCN685x combo on PCIe with MHI firmware loading; neither the attachment nor the firmware partition has been enumerated for this port.
  • A driver needs the PCIe or MHI window, the firmware partition name, its own SMMU stream and a matching OS-facing service such as the IO80211 family.

Configuration ​

  • No configuration entry exists yet: neither board.toml [components] nor configs/mondrian_defconfig selects a network component.

Procedure ​

  • Read the vendor DT and board files for the WCN685x node, then pick an existing component or plan a new driver before writing code.

Development findings ​

  • Nothing exists in the tree for this area: no driver, patch, script or DTS node mentions Wi-Fi, WCN or MHI apart from the userspace About screen, which states that the port has no baseband/WLAN/BT transport.

Completion criteria ​

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

Current limits ​

  • Scope only: the transport is not addressed, so no measurement is possible yet.

Next action ​

  • Enumerate the WCN685x attachment (PCIe or MHI) and its firmware partition from the vendor DT, and record the window, interrupt and SMMU stream in this area.

Evidence ​

Collect evidence using the next action.

Audio ​

planned

Hardware and dependencies ​

  • Audio needs the ADSP remoteproc reserved-memory range and PAS id, the GPR/GLink SMEM queues, the codec and its XO-derived clocks, and the routing table the OS expects for a J120 identity.
  • The OS profile already provides the J120 AP identity that the audio server keys its routing database on, so the identity side is prepared.

Configuration ​

  • No configuration entry exists yet: no component, defconfig option or DTS node covers ADSP, GPR or a codec.

Procedure ​

  • Read the ADSP and codec nodes from the vendor DT, then follow the DSP/audio guide before selecting or writing a driver.

Development findings ​

  • Nothing exists in the tree for this area: the DTS has no ADSP node and no driver, patch or script mentions ADSP, GPR or a codec.
  • The OS profile's product audio node (wireless-splitter 1) is the one audio-related artefact in the repository, and it is an ADT property from the OS side, not a driver.

Completion criteria ​

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

Current limits ​

  • Scope only: no ADSP window, PAS id or codec has been recorded for this board.

Next action ​

  • Record the ADSP reserved-memory range, its PAS id and the GPR/GLink SMEM addresses from the live device tree, which any future driver must use.

Evidence ​

Collect evidence using the next action.

Power ​

planned

Hardware and dependencies ​

  • PMIC and RPMh resources: APSS RSC at 0x17a20000 with TCS blocks at +0xd00, the command-db at 0x80860000 and the SPMI arbiter at 0xc400000 that the firmware keypad already drives.
  • Battery and charger readings need the PMIC ADC channels and a fuel gauge service; neither has been enumerated here.

Configuration ​

  • CONFIG_XNU_SHENG_PMGR=n in the committed defconfig, so the single PMGR boot hook is compiled out.
  • No battery, charger or regulator component is selected in board.toml [components].

Procedure ​

  • Follow the DSP/power guide: enumerate PMIC and RPMh resources, then enable the boot hook and the parent votes before claiming any state transition.

Development findings ​

  • The only power code that exists is GPU-side: PongAdrenoPlatform.cpp votes the GPU parent corner through APSS DRV2 TCS3 and serialises the 58-word PERF and 159-word BW payloads from the command-db, and it explicitly notes that no generic RPMh driver is linked.
  • patches/xnu/...-sheng-platform-power.patch is a 15-line hook that calls sheng_pmgr_run() after bsd_bufferinit(); the function is not defined in this repository and CONFIG_XNU_SHENG_PMGR=n keeps the call out of the build.
  • The only real RPMh regulator writer lives inside the touch patch (pm8350c ldoc12 1800 mV then ldoc3 2700 mV), which is not in the series; powering the panel and touch rails therefore depends on the input area landing first.
  • Resource accounting is suppressed rather than implemented: the boot argument disable_exc_resource=1 stops the wakeups and disk-write monitors from reaching exception_triage(), because on this board the services that would answer those exceptions are still missing and watchdogd was killed for a FATAL wakeups violation it could not answer. Removing the argument is the first power-side milestone.

Completion criteria ​

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

Current limits ​

  • No PMIC, charger, battery or RPMh aggregator exists; the GPU parent votes are the only implemented part and they are unproven on hardware, and resource accounting is switched off rather than provided.

Next action ​

  • Capture the 'parent ARC votes acknowledged' record from PongAdrenoPlatform.cpp on a real boot, which would be the first hardware evidence for the RPMh path.
  • Drop disable_exc_resource=1 once a service answers the wakeups monitor, and show watchdogd surviving without the suppression.

Evidence ​

Image assembly ​

host-verified

Components: XnuPrelink.

Hardware and dependencies ​

  • XNU 8796.141.3 / Darwin 22.6.0 with the 4 KiB page configuration, target DEVELOPMENT_ARM64_PONG, and the 20G81 kernelcache (SHA-256 d260defc) as the prelink input.
  • The prelink needs the XnuPrelink kit, the 32 OS-profile roots, the slide and the early-security-init address; the device preflight installs the auto-slide wrapper over the kit entry point.
  • The payload needs the 20G81 trust cache bundle, the committed Data seed seed/k60-data-seed.dbr and, when a board requires a platform seed, --unique-chip-id.
  • The boot image needs the firmware FD, the firmware DTB from uefi-mondrian and the payload FAT volume.

Configuration ​

  • [kernel.prelink] from the OS profile: tool kits/XnuPrelink/emit_prelink.py, kernel build/xnu/ios-obj/DEVELOPMENT_ARM64_PONG/mach.development.pong.unstripped, slide 0 (the wrapper derives -0xb9c000), 32 roots and kernelcache_sha256 d260defc.
  • [payload] volume UNIXNU, size_mib 192, loader firmware, dram_size 0x300000000, timeout 5, tools true and kernel_args -v native-ufs=1 pong_allow_unauthenticated_root=1 debug=0x144 cs_enforcement_disable=1 pong_software_keybag=1 disable_exc_resource=1.
  • [payload.adt] root_match_volume_uuid C5DAF029-2070-4F68-B3CC-1D1320344CF0 is the only board override; the rest of the ADT identity comes from the OS profile. packed_data_ramdisk stays unset, so the raw APFS seed goes through --data-ramdisk.

Procedure ​

  • ./unixnu build kernel -D ../UniXNU-mondrian, then ./unixnu build payload --trust-cache prebuilt/20G81-trustcache.bundle --data-ramdisk seed/k60-data-seed.dbr and ./unixnu bootimg.
  • Without macOS: python3 scripts/k60-prelink-local.py --kernel mach.development.pong.unstripped --prelinked out/ipsw/20G81/kernelcache.decompressed --output kernel.macho; the script reads [kernel.prelink] from the OS profile and lets board.toml override it.
  • python3 scripts/build-bootimg.py --ramdisk <payload.ramdisk> --output-dir <dir> --fd <SILICIUM_UEFI.fd> packs and self-checks a v4 boot image without a device.
  • Assemble the userspace data image with python3 userland/build.py prepare|apply|finish, which compiles with xcrun for arm64-apple-ios16.6.1 and packs DBRDISK1.

Development findings ​

  • The kernel links and prelinks on macOS CI: run 40650877574 produced 54,419,456 bytes at slide -0xb9c000 with lowest component extent 0xfffffff004426600. README.md still showed 54,349,824 bytes for the earlier round, 69,632 bytes less; this record keeps the newer size.
  • The format-2 prelink lives in the OS profile, not in board.toml: k60-prelink-local.py used to read only board.toml and therefore failed on this device. It now merges the profile table and lets a board [kernel.prelink] override individual keys.
  • The profile keeps slide = "0" on purpose: the K60 __DATA is 0x150000 against 0xb0000 on 20G81, so the components do not fit at slide 0 and the wrapper derives -0xb9c000 from the native segment table instead.
  • The ADT generator refuses a platform seed without [payload.adt] chip_id and refuses more than one memory range, so both the seed path and the 16 GiB multi-bank unit need a board decision before payload packing succeeds.
  • Two boot arguments carry bring-up decisions rather than tuning. pong_software_keybag=1 selects the volatile no-passcode keybag from the software-keybag patch, because this board has no SEP and mkbinit otherwise forces a recovery-mode reboot. disable_exc_resource=1 keeps the wakeups and disk-write monitors from reaching exception_triage() while the services that would answer them are still missing.
  • scripts/build-bootimg.py and build-native-bootimg-local.py both record hardware_tested false: they verify structure and corruption controls but never claim a boot.
  • The userland build pins 20G81 QuartzCore cache addresses, so it is tied to this OS profile and must be re-derived for another build.

Completion criteria ​

  • Kernel and prelink succeed, the payload contains every declared input with 4 KiB alignment and a non-zero ramdisk_bytes, and the boot image matches the bootloader format and size limit.

Current limits ​

  • Only the kernel step is host-verified; payload and boot image have no recorded run for this board.
  • The prelink helper needs a core checkout and a local XnuPrelink kit, neither present in the repository.
  • The ADT platform-seed path needs [payload.adt] chip_id, which is intentionally not set until the seed policy is decided.

Next action ​

  • Run build payload and bootimg once with the trust cache and the Data seed, record ramdisk_bytes from boot.json and the payload hashes, and promote this area to device-verified after one fastboot boot.

Evidence ​

Diagnostics ​

planned

Hardware and dependencies ​

  • Firmware log channels: the UniXNU menu scans 4 MiB Advanced Logger output as QR frames, and the serial port carries UNIXNU: and DARWINBOOT: lines when a UART is available.
  • Kernel log channels: IOLog plus the Pong ramoops console mirror at physical 0xA7000000 (2 MiB with a 12-byte 'DBGC' header recording signature, start and size), which the in-series patch provides. The second 2 MiB is the pmsg zone, and the helper pong_ramoops_diagnostic_write that writes it exists only in the unlisted settings-pmsg patch.
  • Checkpoint sources: the green-bar trampoline, the HANDOFF stage lines and the PongProbeRecord slot/state pairs.

Configuration ​

  • CONFIG_XNU_PONG_RAMOOPS_CONSOLE=y, CONFIG_XNU_PONG_VOLATILE_PANIC_LOG=y, CONFIG_XNU_PONG_APFS_DEBUG=y and CONFIG_XNU_PONG_EARLY_TRACE=n in the committed defconfig.
  • Kernel arguments currently carry debug=0x144 and native-ufs=1; the documented first-boot set is -v keepsyms=1 debug=0x14e cpus=1.
  • USB logging is designed but not wired: 40-byte frame header magic PNGLG001 v1, 1 MiB queue, 16 KiB batches and a 20 ms wait, with no usblog= argument in any code path.

Procedure ​

  • python3 scripts/audit-pong-adt.py <adt> --report <json> --boot-manifest <json> replays 14 negative ADT cases plus a byte-exact round trip.
  • python3 scripts/verify-firmware.py --output-dir <build> checks the FV header, module GUIDs and PE images; scripts/build-probe-stage.sh runs the whole host-side chain without adb or fastboot.
  • python3 scripts/check-porting-paths.py --device . checks this record offline: identity, required areas, evidence shape, component coverage, path ownership and series references.

Development findings ​

  • The host-side audit tooling is real and reusable: audit-pong-adt.py rejects known-bad loader contracts, verify-firmware.py checks four FFS GUIDs and the PE machine type, and the window probe asserts the handoff contract with PASS/FAIL output.
  • Several harnesses still point outside the repository: check-pong-probe-policy.py reads /Volumes/PongDev, build-window-probe-local.py defaults to that driver directory, and test-window-qemu.py expects UniXNU-pong and a qemu-run.py from the core checkout. They cannot run from a clean clone.
  • The record previously attributed globs that match no file (scripts/read-*, scripts/probe-*) and named a package/Kit.toml that does not exist; the patterns now match the scripts that are actually here.
  • No captured log is stored in the repository, so every hardware checkpoint above is described from the code that would print it, not from a transcript.

Completion criteria ​

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

Current limits ​

  • There is no on-device transcript for any milestone, and the USB log path is still a design document.
  • The host harnesses that depend on /Volumes/PongDev or on a Pong-layout checkout cannot be reproduced from a clean clone.

Next action ​

  • Capture one fastboot boot of the window probe and publish the serial or QR transcript with the firmware and payload revisions, which would give the first device evidence in this record.

Evidence ​

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