Skip to content

emOS packer: the Dot 3's image (short cmdline, initrd=, GNU gzip kernel) - #1035

Merged
wilbowes merged 2 commits into
mainfrom
feat/emos-donut-image
Oct 24, 2026
Merged

wilbowes merged 2 commits into
mainfrom
feat/emos-donut-image

Conversation

@wilbowes

@wilbowes wilbowes commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

The emOS packer can now build an image the Echo Dot 3rd gen (donut) boots. This is step 3b of the donut series, after #1033 and #1034.

Found by disassembling the donut's LK, then confirmed on 41C8 (2026-10-24):

  • LK keeps 199 bytes of the header cmdline (snprintf(buf, 200, "%s")). Stock's is already 199, so anything appended (emos.system=, ramoops) was silently dropped. That is why an emOS image booted stock Android.

  • LK only hands over a ramdisk in recovery mode, but it copies the ramdisk to ramdisk_addr on every boot. The donut's recipe therefore keeps bootopt= (kaeru reads it to pick the 64-bit kernel) and emos.system=, and adds initrd=<addr>,<size> for arm64's early initrd. It adds no ramoops, because biscuit's region overruns the donut's reserved one. A cmdline over 199 bytes, or a ramdisk that would reach 0x44410000, is refused.

  • keep_initramfs falls back to GNU gzip -9 -n where zlib's stream does not fit (the donut kernel came out 2,486 bytes over). GNU gzip reproduces the stock stream to the byte on radar and both donut kernels. zlib is still tried first, so radar's images are unchanged.

  • The donut gets emOS's own WiFi tools (second commit). The payload included the supplicant, wpa_cli and busybox only for a 32-bit kernel; the donut runs FireOS 6 on an arm64 kernel, so its image would have had no working supplicant. FIREOS6_BOARDS now covers it, and the board reaches select_payload from both the network update and the wizard.

On hardware (41C8, 2026-10-25): the packer's own output (GNU gzip kernel, md5 3d4a1369) booted emOS, and with the 3c init it joined WiFi. An earlier hang that looked like the kernel stream turned out to follow boots where init had not claimed the LED ring, and has not recurred since that was fixed in 3c.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XMzTzy1gEcUkKfAD2QDkL5

Wil Bowes and others added 2 commits October 24, 2026 21:42
The donut's LK, read by disassembly and confirmed on 41C8 (2026-10-24):
- copies the header cmdline with snprintf(buf, 200, "%s"), so 199 bytes
  survive and the rest vanishes. Stock's is exactly 199, which is why every
  appended parameter (emos.system=, the ramoops block) was lost;
- puts linux,initrd-* in /chosen only in recovery mode and appends
  skip_initramfs otherwise, but still copies the ramdisk to the header's
  ramdisk_addr on every boot.

So a board can now carry a CmdlineRecipe (CMDLINE_RECIPES): the donut keeps
only stock's bootopt= (kaeru reads it to choose a 64-bit kernel) and the
image's own emos.system=, adds initrd=<raddr>,<ramdisk size> for arm64's
early_initrd, appends no ramoops (its DT already reserves 0x44410000), and
is refused past 199 bytes or with a ramdisk that would reach 0x44410000. An
image carrying initrd= rebuilt WITHOUT its board is refused, since its
ramdisk size would go stale; the network update therefore passes the board
the firmware reported (em_state.boards via _EmosIO.board).

The donut joins SKIPS_INITRAMFS, and keep_initramfs falls back to GNU
gzip -9 -n when zlib's stream does not fit. kbuild compressed these kernels
with gzip -9: GNU gzip reproduces the stock stream to the byte on radar and
on both donut kernels, and the renamed parameter leaves it 1-9 bytes
shorter, where zlib is 2,486 bytes over on the donut. zlib is still tried
first, so radar's images are unchanged. A stream of exactly the original
size now goes in unpadded (the FNAME pad needs at least one byte). gzip 1.10
here and 1.13 in the controller image give identical bytes.

Built from 41C8's stock boot_a, the packer's image decompresses to the same
kernel as the hand-made one that booted emOS tonight, with the same 82-byte
cmdline. Tests: the recipe's cmdline, a rebuild restamping the size and
keeping emos.system, the board-less refusal, the reserved-memory refusal,
LK's limit at 199/200, other boards unchanged, GNU fallback and exact-fit
paths, and that the update passes the board (mutation-checked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BofN7aQbgNvVKNVXdGNR2f
…ernel)

select_payload put wpa_supplicant, wpa_cli and busybox into an image only
for a 32-bit kernel, taking the kernel's arch to mean the userspace: arm64
was FireOS 5, where Amazon's supplicant works and must stay. The donut breaks
that: an arm64 kernel under a 32-bit FireOS 6, whose supplicant aborts
without binder. Its image would have built with no working supplicant and no
udhcpc, booted, and never joined a network.

em_emos_build.FIREOS6_BOARDS (radar, donut) now takes the tools too, and the
board reaches select_payload from both callers: the network update
(_EmosIO.board, the firmware's report) and the wizard (its `board` part). An
unknown board keeps the arch rule, so FireOS 5 images are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMzTzy1gEcUkKfAD2QDkL5
@wilbowes
wilbowes marked this pull request as draft October 24, 2026 22:14
@wilbowes
wilbowes marked this pull request as ready for review October 24, 2026 23:05
@wilbowes
wilbowes merged commit fd79eaa into main Oct 24, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant