U-Boot
HakiOS's loader can be started by Das U-Boot instead of Project Silicium's UEFI. The phone's own bootloader (ABL) starts
U-Boot from RAM with fastboot boot, U-Boot finds the loader and its payload in memory and starts the loader as a UEFI
application. Nothing is flashed: not boot, not any other partition, and a reboot gives the phone back to Android.
Status in one line: works on the POCO X5 Pro and in QEMU, the whole chain to the kernel's user space, with xl as it was.
The sections below say exactly what was run.
Why
Silicium (mkxnuimg.py) |
U-Boot (tools/mkubootimg.py) |
|
|---|---|---|
| What it is made of | Mu-Silicium and the phone vendor's own UEFI drivers, which are not in the repositories: you extract them from your own phone first | open source only: U-Boot's Qualcomm support and the phone's device tree |
| Where the loader comes from | the payload the boot shim copies to 0xF0000000 |
the boot image's ramdisk, or U-Boot's own fastboot: the hakios command copies the payload to 0xF0000000 |
| A new loader or payload | a new boot image and fastboot boot through ABL again |
the same, or sent to the running U-Boot over USB (below), without going back through ABL |
xl (run, ramdisk, log, screenshot) |
yes | yes: U-Boot provides the same USB protocol the loader uses under Qualcomm's UEFI |
The chain
ABL ──fastboot boot──> U-Boot (RAM) ──hakios load──> payload at 0xF0000000, loader.efi ──bootefi──> loader ──> XNU
│ ▲
└── no payload in the image ────┘ fastboot stage x.hkub; fastboot oem run:hakios load fastboot
The boot image (
tools/mkubootimg.py): the kernel isgzip(u-boot.bin)with the phone's Android device tree appended, exactly what ABL expects for Silicium's image (header version 1); the ramdisk is anHKUBcontainer with the loader and a payload, or nothing.U-Boot's device tree (
devices/redwood/u-boot/files/dts/upstream/src/arm64/qcom/sm7325-xiaomi-redwood.dts): the panel's framebuffer at0xE1000000as ABL left it (the loader knows the phone by that address), the keys, USB as a device, UFS, and every carve-out of the Android device tree and of Silicium's memory map. The size of RAM is not in it: ABL writes it into the device tree it hands over and U-Boot reads it from there.0xF0000000, 64 MiB is reserved in that device tree (hakios-payload): nothing in U-Boot takes it, the hakios command writes the payload there, and the loader's memory window stays clear of it, as under Silicium.The hakios command (
uefi/u-boot/files/cmd/hakios.c):hakios loadfrom the ramdisk ABL loaded (the device tree ABL handed over says where) hakios load fastbootwhat the host sent last with fastboot stagehakios load ADDR [SIZE]from anywhere in RAM; a container or a payload says its own size hakios bootbootefithe loaderhakios infowhat is in memory It takes an
HKUBcontainer, a bare XNUP payload or a bareloader.efi. A payload with an entryloader.efi(the onexl entermakes) brings its loader with it. Every entry of a container carries a CRC-32, and a damaged one is never started.Qualcomm's USB device protocol (
uefi/u-boot/files/lib/efi_loader/efi_qcom_usb.c): the loader talks fastboot to the host throughEFI_USB_DEVICE_PROTOCOL, the interface of Qualcomm's UEFI (published in its open-source ABL). U-Boot provides it on its own USB gadget stack: the control endpoint is answered from the loader's descriptors, the loader sees its bulk transfers and the cable's state. Soxlworks unchanged.What U-Boot will not do: write the phone's storage. Capsule updates are off (they flash a partition) and U-Boot's watchdog is not started (the loader has its own, and keeps U-Boot's boot services while the kernel runs).
Build
tools/hakios-device.py sync # devices/redwood and uefi/, as for everything else
make u-boot # = uefi/u-boot/apply.sh (U-Boot at uefi/u-boot/BASE into u-boot/) + uefi/u-boot/build.sh redwood
uefi/u-boot/build.sh needs an AArch64 GCC (aarch64-linux-gnu-gcc, or Arm's aarch64-none-elf-gcc from
developer.arm.com; CROSS_COMPILE= to name another). The result is u-boot/build-redwood/u-boot.bin.
Run
With xl, as always:
XL_FIRMWARE=u-boot ./xl enter # without XL_FIRMWARE: Silicium when its image is built, else U-Boot
./xl ramdisk userland/system.apfs
./xl run
One image, loader and payload inside:
xnuloader/mkpayload.py -o payload.bin kernel=hakios-kernel ramdisk=userland/hello --cmdline "-v"
make u-boot-img UBOOT_PAYLOAD=payload.bin # hakios-u-boot.img
fastboot boot hakios-u-boot.img
U-Boot first, then anything over its fastboot (no new boot image for a new kernel):
make u-boot-img # no payload: U-Boot waits for the host
fastboot boot hakios-u-boot.img
tools/mkubootimg.py -p payload.bin -c x.hkub # the container alone
fastboot stage x.hkub
fastboot oem run:"hakios load fastboot"
fastboot continue # hakios boot
U-Boot's fastboot shows as Google's 18d1:4ee0 (the loader's own is 18d1:d00d); fastboot oem console reads U-Boot's log.
Hold volume down while U-Boot starts for its menu (start, fastboot, what is in memory, a shell over USB serial): the volume
keys move, the power key chooses.
What the screen says
U-Boot writes what it does on the phone's screen, each line marked [oosexgang]: what the hakios command took and where it put
it, and, once the loader talks to the host, the cable coming and going, every command of the host (the getvar polls of a run
aside), every download and upload with its size, progress and speed, and every FAIL the loader answers:
[oosexgang] hakios: payload of 66216 bytes at 0xf0000000
[oosexgang] usb: the host is connected
[oosexgang] host: upload
[oosexgang] sending 22 KiB
[oosexgang] sent 22 KiB in 4 ms (5500 KiB/s)
[oosexgang] host: oem screenshot
[oosexgang] host: upload
[oosexgang] sending 10125 KiB
[oosexgang] 30%
(lines from the phone's screen after xl run and xl screenshot)
setenv hakios_status 0 (U-Boot's shell, or the environment file) turns it off. hakios_usb_debug=1 shows much more: every
control request, endpoint and transfer of the USB device protocol, which is how the faults below were found.
Try it in QEMU (nothing of Apple's)
make qemu-u-boot # make qemu-open's kernel, loader and hello, booted by U-Boot: qemu/run-u-boot.sh
qemu/run-u-boot.sh puts an HKUB container at 0x50000000 (where a phone's ABL would have put the ramdisk) and boots
u-boot/build-qemu/u-boot.bin (devices/qemu/u-boot/files/configs/hakios_qemu_defconfig). What it printed (U-Boot 2026.07, the
prebuilt kernel and hello):
hakios: payload of 16825792 bytes at 0xf0000000
hakios: loader of 65536 bytes at 0x1bd510000
== XNULOADER v3 ==
platform qemu-virt
phys window f4000000 size c8000000, claim status 0
Hello from user space on Darwin!
kernel returned: log at f7bdc9c0, 21629 bytes
The same as under edk2 (qemu/run.sh), line for line where they are comparable. A bare payload with loader.efi in it was run the
same way and gave the same.
On the phone: what is known and what is not
Run on a POCO X5 Pro 5G (redwood, bootloader unlocked, Android on slot b), from Windows' platform-tools, 29 October 2026:
- ABL starts the boot image with
fastboot boot; U-Boot comes up on the panel, takes the size of RAM from ABL's device tree and brings UFS up (SK hynix, six LUNs). ./xl enter(XL_FIRMWAREunset, no Silicium image):xl: loader is upin about 7 s. The loader finds the phone by its framebuffer (platform redwood), arms its watchdog, and its fastboot answers (product: xnuloader,version: 0.5)../xl memmap: every carve-out of the device tree reserved, the payload area as U-Boot's, and 5.4 GiB free above0xF4000000for the kernel's window.- Ten
getvarand 1 MiBstagein turn, all answered; the 16 MiB run payload in 0.55 s. XL_RAMDISK=prebuilt/userland/hello ./xl run: the prebuilt kernel boots,Hello from user space on Darwin!,initprocexits (that is whathellodoes) and the kernel returns:result: returned 22037 0. The loader's log is written tologdumpand read back the same../xl screenshot: the kernel's last screen, with U-Boot's[oosexgang]lines under it.- Darwin userland 16.5, the images built from the IPSW (
sysimage/unpack.sh,PREBUILT=1 REST=1 SYSTEM=1 userland/build.sh,make loader), no firmware of the phone yet (no GPU: drawn in software):./xl ramdisk userland/system.apfstakes 2361 MB in 71 s (33 MB/s) through U-Boot, andXL_CMDLINE="debug=0x8 rd=md0 hv_wdt_pet=1 bootcpu=7 cpus=8 cpufreq=1 ufs=2 touchscreen=1" ./xl runbrings the home screen in under a minute (the screenshot at 60 s), with touch. - The way through U-Boot's own fastboot (
stage,oem run:hakios load fastboot,continue): U-Boot takes the container and starts the loader, but the loader's USB then does not come up on the bus: the controller, released by U-Boot's fastboot and taken again by the loader, does not connect. Until that is fixed, put the loader and the payload in the boot image (xl enterdoes).
What it took on the phone, each seen there first (hakios_usb_debug=1 and photographs of the screen):
| Fault | Fix |
|---|---|
| The loader's descriptors (USB 2.10 and a BOS) promise Link Power Management, which U-Boot's DWC3 does not do: the device left the bus 3 s after it came | USB 2.0 and no BOS, as U-Boot's own gadgets |
U-Boot's endpoint autoconfig ORs the endpoint number into the address it is given (0x01 became 0x03) and writes a full-speed packet size (64) |
only the direction goes in; the caller's packet size (512) comes back |
| U-Boot's DWC3 starts a bulk transfer only on the XferNotReady the host's next token brings | it starts on queue, and the next one when one completes, as Linux's dwc3 |
| U-Boot's DWC3 clears a stall with a transfer started (WinUSB clears the halt of a pipe it opens) | the transfer ends first and starts again after, as Linux's dwc3 |
fastboot oem console: every response dequeued a request that was not queued; the error went into the console it was sending, for ever |
only a request in flight is dequeued |
| The loader's log buffer was not aligned to the block size U-Boot's block I/O wants | aligned (xnuloader/loader.c) |
| A download the host gave up half-way left the loader waiting for good | 20 s without a byte and it is given up |
If U-Boot hangs, the menu (volume down) and fastboot oem console are the ways in;
U-Boot's own notes on phones say how to bisect a hang.
- What the loader asks Qualcomm's UEFI for and U-Boot does not have: the charger protocols (the loader goes on without the battery
reading) and
EFI_CPU_ARCH_PROTOCOL(the loader's exception hook is not installed; a fault in the loader resets the phone instead of being logged).
Installed beside Android: a menu at every start
What was done on the POCO X5 Pro, 29 October 2026, in this order, each step checked before the next:
- Backup (
tools/backup-partitions.py --yes, read only, as root): the boot partitions of both slots,vbmeta*,misc,devinfo,persist,modemst1/2,fsg, the first and last 8 blocks of every unit (sda...sdf), each proven by three SHA-256s; a second copy kept off the working tree. darwin(tools/userdata-carve.py --wipe DIR 20): userdata shortened,darwin(sda34, 20 GiB, APFS container type) at the end of LUN 0, two entries changed, both copies of the table checked here and withsfdisk --sector-size 4096; written from Android (root) withdd, read back; thenfastboot -w(Android's data is erased).- The Darwin volume into
darwin: Apple's root volume of the IPSW (tools/apfs/dmg.py extract), the OS and App cryptexes after it, as the port's own layout (docs/ru/notes.md, "Криптексы на флеше"): 8.4 GB as 11 sparse pieces (tools/sparse-split.py: Google's fastboot for Windows runs out of memory on one 8 GB image), read back by U-Boot (hakios sum darwin 0 2042368) with the image's own sum. - Darwin from
darwin, still from RAM:CRYPTEXES=flash FLASHBOOT=1 userland/build.sh,xl ramdisk userland/flashboot.apfs,root_apfs=ufs_darwin ufs=2: the Darwin volume is the root, SpringBoard up. - The boot image of slot a (
tools/mkubootimg.py --install): U-Boot, the loader, a payload with the kernel,root.apfsandflashboot.apfsas itsresidentimage (the loader puts it wherexl ramdiskwould), the flash-root command line, anddevices/redwood/u-boot/install.env;fastboot flash boot_a,fastboot set_active a.
At every start: U-Boot marks slot a successful and shows the menu; Android (the default after 10 s) marks slot a
unbootable and restarts, and ABL makes slot b active itself; fastboot set_active a (or hakios-menu.sh in Android, with
root) brings the menu back. Seen on the phone: the menu, and Android from it.
The data volume: /private/var kept, the size of the space
Without it /private/var is a memory filesystem: empty at every start, and Darwin shows the size of that (a quarter of
the RAM, "1 GB") as the phone's storage. With it, /private/var is HFS Plus in darwin after the Darwin volume and the
cryptexes (block 2042368 to the end, 12.2 GB): kept between starts, journaled, and what Darwin shows is its size.
- the kernel: HFS Plus is built in (Apple's
hfs-627.100.6,bsd/hfs); withufs_write=1the UFS driver gives that stretch out as/dev/ufs_darwin_data, which reads and writes through the loader (each write flushed and read back); init:var_hfs=ufs_darwin_datamounts it on/private/var, a memory filesystem as before if it does not mount;- the empty volume, once:
tools/mkdatavol.py data.simgandfastboot flash darwin data.simg(53 KB: the image says DONT_CARE for the Darwin volume and the cryptexes, and for every block the volume does not use). Flashing it again empties the volume; - the payload's command line gains
ufs_write=1 var_hfs=ufs_darwin_data; - the loader serves writes to any phone's
darwin(name, type, place, size, LUN 0; the unique GUID, whichuserdata-carve.pymakes new on every phone, only printed): until then it compared the first phone's GUID, and on this one the volume never mounted (/dev/ufs_darwin_data: No such file or directory, a memory filesystem instead); - the volume's writes are only written (
BLOCK_OP_WRITE_FAST), the flash's cache flushed when the journal asks (DKIOCSYNCHRONIZE->BLOCK_OP_FLUSH): a flush and a read back after each 4 KB write made the system crawl.
The rest of the installed image's command line is the configuration's (tools/hakios-config.py args) without
adsp, sound and haptic, which stopped the phone under U-Boot: gpu=1 wifi=1 modem=1 modem_again=1 modem_buffer=0xa8000000 socinfo=1 thermal=1. And userland/build.sh with GL=1 (Mesa: tools/mk-mesa.sh): without
libhakios.gl.dylib in the image CoreAnimation draws with its software renderer whatever the GPU does -- slow, and
the wallpaper black ("Failed to initialize Metal or OpenGLES contexts!"). And REST=1: without it the image has
25 of the system's jobs and none of the launch angels, so PosterBoard (the wallpaper) never starts and the applications
that need one of the other services do not open: GL=1 REST=1 CRYPTEXES=flash FLASHBOOT=1 userland/build.sh.
Tried in QEMU first (qemu/run-hfs-test.sh; Darwin with virtio_blk=2 var_hfs=rvblk0): written, stopped hard, the journal
replayed at the next mount, the files there. The volume's root has to be root's (mkfs.hfsplus -U 0 -G 0 -M 755, as
mkdatavol.py makes it): with another owner Darwin takes it for a data volume to migrate and stays at the progress bar.
Which slot is active is never U-Boot's to change
On these phones a slot being active is three things that ABL's SetActiveSlot() changes together: the bits of
boot_a/boot_b, the type GUIDs of every _a/_b pair (23 on LUN 4, vbmeta_system on LUN 0: the active slot's carry
the real types, the other's 77036cd4-..., which XBL loads by), and the UFS boot LUN (1 for a, 2 for b). At every boot
ValidateSlotGuids() refuses a slot whose boot_X and abl_X carry the same GUID or whose LUN does not match:
Failed to load/authenticate boot image: Device Error, for every image, Android's too.
A first version of hakios slot b changed the bits alone. After it no image started, and no ABL command could help: every
set_active swaps the GUIDs and moves the bits, so the two stay apart. What brought the phone back:
- not
fastboot flash partition:Nwith a stock table set blindly: ABL'sWriteGpt()callsErasePartition()on the whole unit first. On this phone that erase left the data as it was (every partition read back the same), but the code says otherwise and LUN 4 holdsabl,tz,hyp,modemand the phone's ownsecdata,storsec,tzsc; fastboot flash partition:4with this phone's own LUN 4 table from step 1, in ABL'sgpt_bothlayout (work/gptboth.py: what ABL'sPatchGpt()makes of it was computed first: every block as it was but the backup header'smy_lba, which ABL always sets to 1), thenfastboot set_active b(the slot already active: ABL sets the boot LUN only);- then the hashes of
boot_b,vendor_boot_b,dtbo_b,vbmeta_a/bread by U-Boot: those of the backup.
So hakios slot changes only the active slot's own bits, as ABL does (successful, unbootable), and only ABL or
Android's boot control HAL change which slot is active. LUN 0's vbmeta_system pair was swapped by ABL three times
during this and is the other way round from LUN 4 now; nothing that starts reads it by type, and it is left so.