Booting AC OS sticks on stock Chromebooks #
Stock Chromebook firmware never boots a UEFI stick. In developer mode, Ctrl+U
looks for a GPT partition of ChromeOS-kernel type holding a kernel packed and
signed by vbutil_kernel; the public developer keys are accepted, so no
firmware change (MrChromebox RW_LEGACY or full ROM) is needed. Every AC OS
stick now carries that partition alongside the UEFI layouts, and the same
vmlinuz serves all three.
Layout #
| partition | type | contents |
|---|---|---|
| 1 ACBOOT | Basic data (FAT32) | BOOTX64.EFI (kernel-direct), initramfs, config.json, wifi_creds.json |
| 3 KERN-A | ChromeOS kernel | vmlinuz.kpart — vboot-packed bzImage, 64 MB partition |
| 2 ACEFI | EFI System | splash → systemd-boot → KERNEL.EFI + initramfs |
UEFI firmware picks ACEFI. Depthcharge (Chromebook firmware) picks KERN-A. Partition 3 sits between the two on disk; the number is what macOS and vboot care about. GPT attribute bits follow cgpt semantics: priority 10, tries 5, successful 1 — the flags recovery media use, so the firmware never counts the stick down to unbootable.
How the Chromebook path reaches the real initramfs #
Depthcharge passes the kernel no initrd. The kernel therefore embeds a stub
initramfs (initramfs-stub/init + static busybox, wired in by
docker-build.sh Step 3b through CONFIG_INITRAMFS_SOURCE). The stub waits
for the stick, mounts the first FAT partition carrying initramfs.cpio.gz
and config.json, unpacks the real initramfs into a tmpfs and
switch_roots into it. From there boot is identical to the UEFI path,
including the inscription baked into initramfs.cpio.gz at flash time.
Built-in drivers probe while the stub is still the whole root, so Intel
wifi (iwlwifi) and the SOF audio DSP fail their firmware loads at ~0.7 s and
give up. After unpacking, the stub points firmware_class.path at the
unpacked tree, unbinds and rebinds those PCI drivers, and waits up to 8 s
for a wl* interface and a sound card before switching root. Without this
the Lenovo 500e came up with no wifi and no speaker (2026-09-24). The
regulatory database is still loaded only once by cfg80211 and stays on
the world default on this path.
On UEFI boots the firmware-supplied initrd is unpacked over the embedded
one, so its /init replaces the stub and the stub never runs.
/proc/cmdline contains ac.boot=chromeos only on the depthcharge path;
the kernel's built-in CONFIG_CMDLINE supplies everything else (x86 appends
the bootloader's command line to the built-in one).
Pipeline #
Dockerfile.builderinstallsvboot-utils(Fedora) forvbutil_kerneland/usr/share/vboot/devkeys.docker-build.shbuilds the stub cpio, setsCONFIG_INITRAMFS_SOURCE, and afterbzImagepacks + verifiesvmlinuz.kpart. A missing tool fails the build;AC_SKIP_KPART=1opts out loudly.oven/native-builder.mjsextractsvmlinuz.kpart(required unlessAC_SKIP_KPART=1) andscripts/upload-release.shpublishes it asnative-notepat-latest.vmlinuz.kpart.ac-os pullfetches it when the release has one;scripts/flash-mac.shadds KERN-A whenvmlinuz.kpartis in the source dir, dd's the blob, and verifies it by reading the partition back. Releases without a kpart still flash as two-partition UEFI sticks.
Not yet covered: the Linux ac-os flash path and the ISO (media-layout.sh).
On the Chromebook #
- Developer mode (Esc+Refresh+Power, then Ctrl+D at the recovery screen; this wipes local ChromeOS data once).
- Sign in, open a terminal (Ctrl+Alt+T →
shell, or VT2) and runsudo crossystem dev_boot_usb=1. - Reboot with the stick in. At the "OS verification is OFF" screen press Ctrl+U.
If the stick is ignored, the firmware is not seeing a kernel partition it
accepts: check cgpt show /dev/sdX from the ChromeOS shell for a KERN-A
entry with type ChromeOS kernel and the priority/tries/successful bits.
The stub logs to the console and kmsg as [ac-stub]; if it cannot find the
stick it drops to a shell instead of hanging.
First verified target: Lenovo 500e Chromebook 2nd Gen (board phaser360,
Octopus / Gemini Lake), 2026-09-24 lane.