Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Start here

This site documents gbook: bringing mainline Linux up on the Dell XPS Googlebook, a Snapdragon X Elite (X1E80100) ChromeOS laptop whose board name is mica (ChromeOS firmware branch firmware-bluey-16702.B). Everything here is exported from the project’s working repository at a release tag and rebuilt per release; the dates on each page are the dates the facts were read or measured.

Two public ports exist for this machine. The Nura (postmarketOS) port by Anton Bambura installs through pmbootstrap and is the way to get a packaged system today. gbook builds its own kernel image for the ChromeOS kernel partition and a Debian rootfs on a USB stick, and documents the parts the other record leaves open: the ChromeOS boot path (depthcharge, FIT, EL2), the SCMI mailbox stall and the decision it led to, the sound card’s SoundWire wake fix, and a status matrix with a line of evidence for every row.

Where to look:

There is no image to download yet: the recipes are documented and the public repository holds no binaries. The two released images so far are described on the Releases page by their tag messages.

Nothing on this site has been sent upstream. The project holds its kernel and device-tree submissions until its milestone M5 (decision 0010); four local patches are listed by their status lines only until then.

A warning: this is experimental work on a device whose firmware can refuse to boot an unsigned image or need a recovery stick to return to stock. Read the recovery decisions (0001, 0002) before touching developer mode.

Licence: prose CC BY-SA 4.0, tooling MIT, device tree BSD-3-Clause, patches GPL-2.0-only (details).

About this build

Generated by scripts/export-public.sh from the gbook repository at v2026.11.05-21-gf05400b on 2026-11-05 4:42 PM EST. Files exported: 85, plus the generated pages. Links to files that stay private are plain text. Rebuilt once per release tag; nothing between tags is published.

Licence

gbook: mainline Linux on the Dell XPS Googlebook (mica)
Copyright (c) 2026 m3

This tree carries work under four licences. A file's path says which one applies; the texts are in LICENSES/.

1. Prose: every Markdown file and the site rendered from them.
   Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0): LICENSES/CC-BY-SA-4.0.txt.
   Attribution: "m3, gbook", with a link to this site.

2. Tooling: gate.sh, book.toml, the workflow under .github/, the kernel configuration fragments
   (src/kernel/*.config) and the build-script headers quoted under src/scripts/.
   The MIT License: LICENSES/MIT.txt.

3. Device tree sources (src/dts/*.dts, src/dts/*.fixups): BSD-3-Clause, as their SPDX headers state,
   with the copyright lines they carry (Qualcomm, Aleksandrs Vinarskis, and this project's additions).
   LICENSES/BSD-3-Clause.txt.

4. Kernel patches (src/kernel/patches/*.patch): GPL-2.0-only, as Linux kernel code. Each patch names
   its author; the FROMLIST ones are copies of patches posted to the kernel lists by their authors.
   LICENSES/GPL-2.0-only.txt.

Vendor firmware is not in this tree: src/kernel/firmware.sha256 lists hashes only.

Texts: CC BY-SA 4.0, MIT, BSD-3-Clause, GPL-2.0-only.

How mica boots Linux

The Dell XPS Googlebook does not boot like other Snapdragon X laptops. There is no Qualcomm ABL (Android Bootloader) and no UEFI: the machine runs ChromeOS firmware, and Linux gets in through the door that firmware already has for self-signed kernels (boot-path note, “Answer” and “Route C”). Sources: the boot-path note, the FIT packaging note, the EL2 handoff note, ADR 0001 and ADR 0003.

The firmware stack

The firmware is coreboot plus the depthcharge bootloader, with Android boot support added on top; vboot (verified boot) sets the rules depthcharge enforces about what may run (boot-path note, “Answer”). The firmware build carries no hypervisor image at all, no Gunyah and no QHEE (EL2 handoff note, “1. Firmware chain”, also the table’s source).

StageException levelRole
corebootEL3board init; loads BL31 and the secure OS
BL31 (Trusted Firmware)EL3starts the payload at EL2
QTEE (Qualcomm’s trusted execution environment)secure EL1secure OS
depthchargeEL2picks and verifies the kernel
LinuxEL2entered with a plain branch, no eret

Developer mode is the “unlock”

Google’s “Unlock bootloader” menu item is ChromeOS developer mode under a new label, and Android’s “OEM unlocking” switch is the vboot flag that withholds developer boot until it is on. The Chromebook USB-boot settings are preset: in normal mode the firmware turns external boot on and signed-only boot off (boot-path note, “2. Unlock = developer mode”).

“Self-signed” is precise: developer mode takes the keyblock on its hash, but the kernel body must still verify against the keyblock’s own key, so the image is signed with a self-consistent key pair, by convention the public vboot developer keys (boot-path note, “2. Unlock = developer mode”; FIT packaging note, “4. Dev keys”).

The recovery gates come first

Unlocking wipes the device, and nobody had shown that Google’s recovery media restores an unlocked unit or relocks the bootloader. So no unlock happens until Gate 1 passes: recovery media made and booted to the recovery screen, the key combination recorded, the recovery image and build saved, the fingerprint and bootloader version recorded, and the data the next phase needs backed up. The owner does those steps by hand; agents never unlock, flash or enter recovery (ADR 0001, “Context” and “Decision”). On 2026-11-02 Gate 1 passed: Device Recovery Utility media reached the recovery screen and served the installed build. The public recovery zip alone had not passed it, because its build 16471258 is older than the installed 16560795 and anti-rollback might refuse it (ADR 0001, “Update 2026-10-21” and “Update 2026-11-02”).

The route: a FIT image on a ChromeOS kernel partition

vboot treats a GPT entry as bootable when its type is the ChromeOS kernel GUID fe3a2a5d-4f32-41a7-b725-accc3285a309; depthcharge loads the body into its kernel buffer and boots it as a FIT, U-Boot’s Flattened Image Tree (boot-path note, “Route A”). ADR 0003 chose that route on a USB stick: the stock SSD is never written during boot tests, and a bad image falls back to the next partition or the menu, not a brick (ADR 0003, “Decision”, “Consequences”).

The partition holds a vboot kernel blob: a 64 KiB vblock followed by a FIT with one kernel, one device tree blob (DTB) and one ramdisk. mkdepthcharge from depthcharge-tools builds and signs it in one command, wrapping mkimage and vbutil_kernel with the developer keys (FIT packaging note, “Answer” and “1. mkdepthcharge for a custom board”). The firmware’s rules (same note, “2. Compression and the 256 MiB limit”):

ConstraintRule
Kernel formatplain arm64 Image; depthcharge checks the arm64 magic, so Image.gz fails
Compressionnone, lz4 or lzma only; the FDT and ramdisk nodes must be none
Sizethe packed body must fit the firmware’s 256 MiB kernel buffer; the decompressed kernel goes elsewhere

The first-boot image is 256 MiB and holds a 128 MiB KERN-A, rewritten whole for every attempt; since milestone M3 the rootfs is partition 2 of the same stick, from 256 MiB on (ADR 0003, “Update 2026-10-21 (first-boot details)”; ADR 0012, decision 3).

Configuration matching. depthcharge prefers google,mica-rev<board>-sku<sku> down to a plain google,mica, ranks each FIT configuration by its best match, and boots /configurations/default when nothing matches (boot-path note, “Route A”). A DTB whose root compatible holds google,mica matches any board-id and sku-id, and the default is a safety net and a trap, since a wrong DTB still boots (FIT packaging note, “Answer”, step 6). On 2026-11-02 the coreboot table reported board-id 3 and no sku-id (first boot, “Result”).

Partition attributes. vboot keeps priority, tries and successful bits in each entry’s GPT attributes: it picks the highest priority above zero and spends a try on each boot of an entry whose successful bit is clear (FIT packaging note, “5. Partition type and attributes”). The project writes cgpt add -t kernel -S 0 -T 3 -P 15, three tries then fall back. pmbootstrap’s -S 1 -T 5 -P 10 is for an installed system: with successful already set, tries are ignored and a kernel that panics is picked again on every boot (same note, “Answer”, step 5).

Entering at EL2

depthcharge jumps to the kernel with a plain branch, no eret, so Linux starts at EL2 with no hypervisor underneath (EL2 handoff note, “Answer”). On 2026-11-02 the first boot logged CPU: All CPU(s) started at EL2 (first boot, “Result”).

Mainline’s hamoa.dtsi assumes Gunyah owns some hardware, so the FIT carries x1e80100-google-mica-el2.dtb: the mica DTS with the kernel’s x1-el2.dtso overlay applied. The overlay enables the Qualcomm APSS watchdog and the PCIe SMMU (the system IOMMU), maps the PCIe controllers onto that SMMU, disables the generic SBSA watchdog, the GPU zap shader and the IRIS video block, and adds iommus to the ADSP and CDSP remote processors (ADR 0003, “Update 2026-10-21 (first-boot details)”; EL2 handoff note, “2. What x1-el2.dtso changes”). Overlays cannot delete properties, so mica’s differences live in the fixups file: it removes those remoteproc iommus again, because the stock mica tree has none, and with them the PAS (peripheral authentication service) driver asks TrustZone for a resource table that mica’s TrustZone refuses with -EIO, leaving the ADSP offline (attempt 2026-11-02-4; fixups header; dts README, “EL2 overlay” row).

The command line and the console

mkdepthcharge prepends kern_guid=%U to the stored command line; at boot depthcharge replaces %U with the kernel partition’s PARTUUID in lowercase, appends cros_secure and writes the result into /chosen/bootargs. The rest is console=ttyMSM0,115200n8 earlycon=qcom_geni,0x894000; the UART address was spelled out because the draft DTS then lacked /chosen/stdout-path, which a bare earlycon needs; the DTS gained it on 2026-10-21 (FIT packaging note, “3. Kernel command line”; dts README, “Console” row).

The console is read through Closed Case Debugging (CCD): with the Google Security Chip (GSC) still locked, a SuzyQ cable exposes the AP (application processor) and EC (embedded controller) consoles, read-only here, though Google’s documentation warns that the AP console is disabled on production devices (ADR 0006, “Context” and “Decision”; boot-path note, “5. Closed Case Debugging”). On 2026-11-02 the AP console carried kernel output only from 0.67 s on, once ttyMSM0 probed; the full log came from ramoops, read back on the next stock boot (first boot, “Channels”).

What the initramfs does

The FIT’s ramdisk holds /init, which finds the block device whose PARTUUID equals kern_guid, takes that device’s parent disk, and requires the disk’s partition 2 to carry the label gbook-root. It mounts that partition on /newroot, moves /dev, /proc and /sys over, and runs switch_root into the Debian rootfs; without it, or on a failed mount, it falls back to capture-and-reboot (ADR 0012, decision 4; the M3 rootfs note, “4.2”). The rootfs path has worked on the device since the 2026-11-04 attempts (status, “Rootfs from USB”).

Since 2026-11-05 /init also enforces “the stock drive is never written”: right after the stick is identified, it sets every other block device read-only at the block layer with blockdev --setro; an unknown GUID locks nothing. It is advisory (root can undo it) and was untested on the device when recorded (ADR 0003, “Update 2026-11-05”).

What this page does not cover

The Nura postmarketOS port installs through pmbootstrap and is the way to get a packaged system today: Dell XPS Googlebook (google-mica). There is no image to download from this site: the recipes are documented in the notes above, and the public repository holds no binaries.

SCMI over the CPUCP mailbox, and why PSCI cpuidle is off

On mica the three CPU clusters take their performance levels from a firmware core, and mainline talks to it with the System Control and Management Interface (SCMI) over a mailbox. That path stalled on most boots, and the board device tree now turns Power State Coordination Interface (PSCI) cpuidle off because of it. The evidence is in the SCMI research note and the recon note, which number boot attempts per day as 2026-11-03-N, shortened to -N below; the decisions are ADR 0008 and ADR 0009.

What SCMI does on this SoC

The mailbox is the CPUSS Control Processor (CPUCP) mailbox controller, qcom,x1e80100-cpucp-mbox, a doorbell between the application processor and the CPUCP (the pinned kernel’s binding, qcom,cpucp-mbox.yaml). The SCMI node uses mailbox channels 0 and 2 with shared memory in SRAM, and each CPU takes its perf domain from scmi_dvfs; the stock tree drives the same block the same way (recon note, “Why”). Up to attempt -7 the mailbox driver was a module and the initramfs loads none, so firmware:scmi deferred forever. With QCOM_CPUCP_MBOX=y (attempt -8), SCMI bound at 0.358 s, read Firmware version 0x20000, and scmi-cpufreq came up on all three clusters, 710.4 MHz to 3417.6 MHz under schedutil (recon note, “Result: pass”). Status lists it as works.

What failed

The next boot, attempt -9, had the same SCMI configuration and failed. From 0.437 s the log shows ten timed out in resp lines about 36 ms apart (the 30 ms default timeout rounded up to jiffies at 250 Hz), then replies for requests the kernel had already dropped: Message for 7 type 0 is not expected!. The version query was among the timeouts, so the base protocol read Firmware version 0x0, and scmi-cpufreq probed with -110 and never retried (research note, section 1). The note reads this as a slow CPUCP, not lost interrupts, since other messages in the same window succeeded (inference). Every upstream x1e80100 SCMI and CPUCP fix was already in the pinned kernel (research note, “Summary”, section 2).

A version of 0x0 carries a hazard. The quirk that forces the fast channel for PERF_LEVEL_GET is declared for Qualcomm firmware from 0x20000 up; with the version unread it stays off, and the driver falls back to the regular message that Sibi Sankar (Qualcomm) said crashes this firmware when idle states are enabled (research note, “Summary” item 5 and section 4, the review thread).

Cold boot versus warm reset

The embedded controller’s console separates cold starts (G3->S5) from warm resets out of running stock Android. On 2026-11-03 SCMI worked in both cold boots and failed in every warm reset; the same image failed warm (-11) and worked cold (-12). The recon note’s inference is that the stock kernel leaves the CPUCP in a state that survives a warm reset, so stock is powered off before every attempt (recon note, “Update: cold boot vs warm reset”, 5:25 PM ET). By 5:59 PM ET three cold starts had given one -110, one run of timeouts and one pass, so a cold start is no guarantee either (recon note, “Update: cold starts fail too”).

Measuring the replies

The research note recommended measuring first, then sizing arm,max-rx-timeout-ms from the measured latency (“Ranked recommendation”). On attempt -22 the trace showed the base protocol’s discovery messages taking 23 to 36 ms each; the implementation-version message took 36.0 ms, which the 30 ms default misses, leaving the version 0x0. Other replies took 155 to 222 ms, four requests timed out at 250 ms (one message twice), and the boot still came up (recon note, “Update: reply latency per message”).

WindowBootsOutcomeSource (recon note)
30 ms (default)7 cold starts2 good“Update: 1000 ms window”
250 ms-20, -21, -22-20 failed (7 timeouts of 78, version 0x0); -21 clean, 4.0 ms peak; -22 up with 4 timeouts“Update: the 250 ms window and the trace”, “Update: reply latency”
1000 ms-23, -24, -25all pass; -23 and -24 with single replies at 0.6 to 1 s, -25 within 3.6 ms“Update: 1000 ms window”

When the CPUCP answers, it answers in milliseconds; on a bad boot single messages wait up to a second or never come, and the window only absorbs that (recon note, “Update: 1000 ms window”). The board DTS carries arm,max-rx-timeout-ms = <1000> on /firmware/scmi. No stock node has it, so the DT README lists it as unverified.

The stall

With cpuidle.off=1 every reply came within 0.1 ms on 3 of 3 cold starts (recon note, “Update: cpuidle.off=1 A/B”). With arm,no-completion-irq (polling) and idle states on, the CPUCP freed the shared-memory channel within 0.1 ms of every request, 3 of 3: the CPUCP is not slow, the completion interrupt is. In interrupt mode every boot counted one mailbox interrupt per reply, all on CPU 0, stalled boots included: the interrupts arrive, late (recon note, “Update: arm,no-completion-irq A/B”).

The wake trace on -32 shows what CPU 0 did during one 534 ms stall: it entered cpuidle state1 (the PSCI retention state cluster_c4) and left it 26694 times, about every 20 µs, while taking only three interrupts. The stall ended when the arch_timer, a Private Peripheral Interrupt (PPI), ran; 6 µs later the mailbox interrupt was taken (recon note, “Update: the wake trace”). The idle counters on -33 showed PSCI refusing the system-level composite state about 598000 times in 95 s (“Update: idle counters”). Removing every domain idle state gave 3 of 3 clean boots (“Update: no PSCI domain idle states”), but two later boots on that tree stalled with zero refusals and CPU 0’s loop at 130000 to 156000 entries (“Update: ADR 0008 is not enough”). A diagnostic kernel patch then routed the mailbox’s Shared Peripheral Interrupt (SPI) to CPU 1: the loop moved with it (CPU 1 at 134862 entries, CPU 0 at 6131) and the reply stalled for 872 ms (“Update: the stall follows the interrupt’s CPU”). The reading, marked inference in every source, is that a core in PSCI retention is woken by a pending SPI but does not take it until a PPI runs.

The A/B tests

The counts are the recon note’s own tally lines (scripts/attempt.sh tally reads machine-recorded boot health; a “clean” boot has no timeout and every reply under 100 ms: status, “What clean means”).

ConfigurationStalled bootsSource
PSCI domain idle states present, interrupt mode (-23 to -27, -30 to -33)7 of 10 known, -31 unknownrecon note, “Update: the counts from attempt.sh tally”
ADR 0008’s DT, no domain idle states (-34, -35, 2026-11-04-1 to -3)3 of 7same
Polling, arm,no-completion-irq (-29)0 of 3same
No PSCI cpuidle (-28, 2026-11-04-4)0 of 4same
No PSCI cpuidle at ten boots (adds 2026-11-04-5 to -10)0 of 10“Update: the tally at ten boots without PSCI cpuidle”

At ten boots, no PSCI cpuidle against domain states present is a one-sided Fisher p of 0.0015; against ADR 0008’s DT it is 0.051 (same section). ADR 0009 later records 0 of 17 (“Update 2026-11-04 (11:24 AM ET)”), the count status carries today.

Two decisions

ADR 0008 (accepted 2026-11-03) deleted the cluster and system domain-idle-states from the board DTS and kept per-CPU retention. The 2026-11-04 boots above showed the stall did not need the composite states, and the ADR was superseded the same day (ADR 0008, “Status” and “Update 2026-11-04”).

ADR 0009 (accepted 2026-11-04) deletes cluster_c4 from cpu_pd0 to cpu_pd11 instead. With no idle state on any CPU, cpuidle-psci does not load and every core idles in WFI, as under cpuidle.off=1; the cluster and system states return to stock, since nothing requests them; the 1000 ms window stays (ADR 0009, “Decision”). Attempt 2026-11-04-4 confirmed it: no cpuidle driver, 78 replies within 0.1 ms, cpufreq on all three clusters (ADR 0009, “Update 2026-11-04 (12:57 AM ET)”). Both ADRs are deliberate deviations from the stock tree under ADR 0004, recorded in the DT README.

What it costs: no core enters PSCI retention and no cluster or the SoC powers down when idle, so idle power is higher by an amount that has not been measured (ADR 0009, “Consequences”). Status lists CPU idle as partial for that reason; the measurement is parked until a battery-only session, because the debug cable powers the bench (status, “CPU idle” and “Idle power”). An s2idle round trip completes on this DT, in WFI, but which low-power state the SoC reaches is unmeasured (ADR 0009, “Update 2026-11-04 (5:01 AM ET)”).

What is still open

  • Why a core in retention does not take a pending SPI until a PPI runs: the GIC’s wake path or the PSCI firmware (ADR 0009, “Consequences”).
  • Whether the CPUCP also answers within 0.1 ms when every core sleeps: untested (recon note, “Update: what -29 shows and does not”).
  • A warm reset from stock now works, 1 of 1, but with the window and ADR 0009 both in place, so which change fixed it is not separated (status, “SCMI after a warm reset from stock”, inference).
  • The 1000 ms window stays unverified (recon note, “Update: 1000 ms window”).

A report on the mechanism is drafted. It is held, not sent: under ADR 0010 nothing goes to a mailing list before milestone M5, and the owner reviews and sends it then (ADR 0009, “Update 2026-11-04 (10:07 AM ET)”).

Sound on mica

How the speakers and microphones of the Dell XPS Googlebook (board mica) came to work on mainline Linux. Status lists “Speakers” and “Microphones” as works on the released image v2026.11.05; the Nura port’s wiki lists Audio as Broken as of its 2026-10-31 edit (https://wiki.nura.eco/wiki/Dell_XPS_Googlebook_(google-mica)). Sources: the audio recon note, the DT README, the audio fragment, the patch list. The owner’s hearing reports, with their times, are the evidence that sound came out; the register reads say why.

The hardware

The card is Qualcomm’s qcom,x1e80100-sndcard, model X1E80100-Dell-XPS-13-9345, on the Low Power Audio Subsystem (LPASS). Four WSA8845 smart amplifiers (Qualcomm’s WSA family) sit on two MIPI SoundWire buses, two per bus; four digital microphones (DMICs) hang off the voice-activation (VA) macro. The audio DSP’s topology file loads at the card’s probe, and Debian’s ALSA Use Case Manager (UCM) profile for the XPS 13 9345 resolves through a link the rootfs adds (audio recon note, “Result”).

PartStock ChromeOS treeMainlinePins and rails
Two WSA8845, SoundWire link 1 (swr0)wsa884x@02170220 SpkrLeft (unique id 0), @02170221 SpkrRight (id 1), wsa_swr_mastersdw:1:0:0217:0204:00:0, :1LPI gpio10/11; shared reset TLMM 204
Two WSA8845, link 4 (swr3)@02170220 Spkr2Left, @02170221 Spkr2Right, wsa2_swr_mastersdw:4:0:0217:0204:00:0, :1LPI gpio15/16; shared reset TLMM 205
Suppliesvdd-1p8 = L15Bplus vdd-io = L12BL12B unverified
Four DMICs, VA macrodmic01_default, dmic23_defaultLPI 6/7, 8/9EN_PP3300_MIC, TLMM 69, as vdd-micb-supply
PCM devicesplayback 1, capture 3UCM Speaker, Mic

(audio recon note, “What the boots proved about the mapping”; DT README, “What changed from the 9345”, row “Audio”)

Where the mapping came from

The node was modelled on the Lenovo Yoga Slim 7x tree at the pinned commit a804c57, then checked against the stock ChromeOS tree from the public recovery image: the SoundWire masters and codecs, the shutdown pins, L15B, the VA macro and the DMIC pins trace to the stock base tree, the microphone rail to the stock board overlay. It booted as a TEST IMAGE from 2026-11-04, moved into the board DTS on 2026-11-05 and was proven on the base image over three clean cold boots. One deviation: stock drives the shutdown pins high when active; mica starts them low, the Yoga’s way, and the codec driver’s reset-gpios releases them; all four play that way (DT README, row “Audio”).

The two kernel options that blocked the card

The initramfs loads no modules, so the audio fragment builds in every driver the node needs; all were =m in the arm64 defconfig (audio fragment, header). Two were not obvious. The WSA884x driver asks the reset framework for its shutdown pin; the reset core turns reset-gpios into an auxiliary reset-gpio device and defers the codec until that device’s driver, CONFIG_RESET_GPIO, binds. With it built in (kernel #31) the reset-gpio devices deferred in turn: TLMM 204 and 205 are each shared by two codecs, and the kernel’s shared-GPIO support routes such a pin through a proxy chip that only CONFIG_GPIO_SHARED_PROXY registers. Both are =y since kernel #32, and the card bound on attempt 2026-11-04-28. A distribution kernel loads both as modules, so the Yoga never showed this (audio recon note, “The two blockers, both kernel options”).

The first sound, and the wake problem

The speaker test on boot -28 (2026-11-04, 11:51 PM EST): the owner heard two sounds, one louder and one lower, and the spoken names gave “Front Right” and “Rear Right”; after some minutes, nothing. The masters’ enumerator registers showed one enumerated slot per bus (unique id 1) and a slave stuck at device number 0. The note’s reading (inferred): the right-side units played, the id-0 pair never enumerated, and every re-enumeration after a clock stop failed the same way (audio recon note, “The speaker test”).

The debug boot (2026-11-05, 12:25 AM EST) showed all four amplifiers enumerating at boot, with the unique ids the DT says. The exit from clock stop lost them: after the SoundWire masters runtime-suspend, each wake races the hardware enumerator against the attach interrupts, bus by bus, and which bus wins is chance: both on the first wake, neither on the first tone, one on the second; the owner heard nothing at either tone. With the masters’ runtime power management forced on, both buses re-enumerated and the owner heard “front left, front right, rear right, rear left” (audio recon note, “The debug boot”). A udev rule forcing the masters on was rejected: its own write is the first wake, and on that boot (1:01 AM EST) one bus lost the race and stayed lost (audio recon note, “The udev-rule boot”).

The fix: two local patches, one of them the fix

Both patches carry the same first line, quoted because the files stay unpublished until milestone M5 and the patch list gives them by status line only:

Status: LOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-05

Patch 0010 re-arms a stalled SoundWire enumerator in Qualcomm’s master driver (audio recon note, “The udev-rule boot”). Patch 0011 sets clk_stop_mode1 on the WSA884x codec, so every resume of the masters runs the full reset-and-init sequence that has enumerated at every boot, instead of the bare clock restart (audio recon note, “The patch boot, revision 3 and patch 0011”).

With both (kernel #35, 1:34 AM EST) three wakes came back whole, where two earlier revisions of 0010 alone had lost a bus on the first; the owner heard all the beeps each time (same section). The rule-free boot (2:05 AM EST) met the ticket’s bar, a tone after ten minutes idle included (audio recon note, “The rule-free boot”). The 0011-alone boot (kernel #36, 12:32 PM EST) set 0010 aside and every wake came back whole; the owner heard tone 1, tone 2 and the four names twice (12:30 and 12:32 PM EST). So 0011 carries the fix; the owner decided at 12:38 PM EST that 0010 stays until the M5 patch review and is then dropped (audio recon note, “The 0011-alone boot”). The released v2026.11.05 is kernel #35, with both (status, “Speakers”).

The microphones

The capture test (2026-11-05, 9:26 AM EST) enabled UCM’s Speaker and Mic, recorded ten seconds from capture device 3 while the owner spoke, and played the file back through the speakers. The owner heard their own voice. The file, measured on the build host, varies second by second on both channels; the journal has no VA macro, DMIC or LPASS line for the capture. So the mic rail and the DMIC clock work as described (audio recon note, “The capture test”).

Boot by boot

Date, time (EST)Boot, kernelFindingHeard (owner)
2026-11-04-26 #30, -27 #31codecs, then reset-gpio devices, deferred; no cardno test
2026-11-04, 11:32 and 11:51 PM-28, #32card 0 bound; speaker test: one slot per bus, then nothingtwo sounds, then silence
2026-11-05, 12:25 AM-1, #32, debugall four at boot; wakes lose buses by chance; masters on: all fournothing, nothing, four names
2026-11-05, 1:01 AM-1, udev rulethe rule’s write is the first wake; one bus lostno tone
2026-11-05, 1:34 AM-4, #35, 0010 + 0011three wakes, all four backall the beeps, three times
2026-11-05, 2:05 AM-4, #35, no rulefive wakes whole, one after 10 min idleeight beeps; four names
2026-11-05, 9:26 AMboot 7, #35DMIC capture, played backown voice
2026-11-05, 12:32 PM-6, #36, 0011 aloneevery wake wholetwo tones, four names twice

(audio recon note, “Result” through “The 0011-alone boot”)

What is unverified

Audio values the DT README marks as not traced to the stock tree (DT README, “Unverified (check at first boot)”):

  • vdd-io-supply L12B on the four codecs: not in the stock codec nodes; every a804c57 X1E80100 board uses it.
  • qcom,dmic-sample-rate = <4800000>: the Yoga’s; stock sets only the clock divider 16.
  • The 3.3 V EN_PP3300_MIC as vdd-micb-supply: the Yoga’s is a 1.8 V LDO; the stock rail is always-on, so a wrong binding is harmless.
  • Which physical unit each UCM prefix (WooferLeft, TweeterLeft, WooferRight, TweeterRight) drives: stock calls swr0’s pair SpkrLeft/SpkrRight and swr3’s Spkr2Left/Spkr2Right; open.

What is left

A read FIFO underflow burst on every wake and two Parity error detected lines remain open; they did not stop playback (audio recon note, “The debug boot”). PipeWire and WirePlumber are not yet in the rootfs; a per-card WirePlumber drop-in is a recorded proposal for when they are (SteamOS on ARM research note, “What gbook could adopt”).

The device tree, compared with the Nura port

Two public device trees describe mica, and both carry the same compatible string, "google,mica", "qcom,x1e80100". This page says how gbook’s tree was built, what the other one does differently, and what the two authors would have to settle before either goes upstream. The comparison was made on 2026-11-05, 2:37 PM EST by compiling both sources against the same pinned kernel tree (torvalds a804c57) and diffing the compiled trees node by node; facts about the other tree are structural (which nodes exist and are enabled, which properties are deleted), not quotations of its text.

gbook’s tree in one paragraph

x1e80100-google-mica.dts starts from mainline’s Dell XPS 13 9345 file, the closest X1E80100 laptop, and changes every board-specific pin to what the stock ChromeOS tree says (the DT README, “What changed from the 9345”). The stock tree is extracted from Google’s public recovery image for this model, so each change cites a line in it; a value with no stock counterpart is listed as unverified (“Unverified (check at first boot)”). The first-boot tree adds a framebuffer reservation at the stock splash address and is built as the board DTB plus mainline’s x1-el2.dtso overlay, followed by mica-el2.fixups, two property deletions that an overlay cannot express (“First-boot additions”). Every enabled node binds since 2026-11-04 (the unbound-nodes note).

The other tree

Anton Bambura’s x1e80100-bluey-mica.dts (authored 2026-10-19, in his kernel tree for the postmarketOS-based Nura port; pmaports merge request 9856 “google-bluey: new device”, merged 2026-11-03) names the board "Google Mica" and includes the same two SoC files, hamoa.dtsi and hamoa-pmics.dtsi. It compiles against a804c57 once one binding header his tree carries is stubbed (a camera orientation header not in the pinned tree), with three dtc warnings about thermal zones that use the PMIC’s ADC as a sensor without a sensor-cells property at this commit. Compiled, the two trees have 1,466 and 1,451 nodes; 1,383 and 1,366 of them are enabled.

What each tree enables that the other does not

Areagbook onlyNura port only
Soundthe sound card, both SoundWire buses with two WSA8845 amplifiers each, the VA macro’s microphone links, the two speaker-shutdown pin states, the microphone 3.3 V rail (Sound on mica)nothing; the port’s wiki page lists audio as broken
USBboth USB MP (multiport) QMP PHYs enabled, as the 9345 has them (stock disables them; unverified)the two eUSB reset pin states are named differently; a usb-mp-reset-n state
Videothe Iris video codec enabledIris disabled, with a note that it needs IOMMU work to start from EL2
Inputtouchpad and touchscreen pin states grouped per device, the TPM interrupt stategpio-keys with a volume-up key on a PMIC GPIO, the PMIC’s power-key resin node
Timers and watchdogsthe PMIC RTC, the SBSA generic watchdogthe APSS watchdog
SoC blocksthe crypto engine explicitly disabled (its driver is marked broken at this pin and the unbound node held two interconnect paths open)the crypto engine, the two GPI DMA controllers and the PCIe SMMU enabled
Regulatorsfixed regulators for the backlight, fingerprint MCU, touchpad, touchscreen, camera and microphones, named per consumerthe same rails under other names, plus a PWM power switch; eight RPMh regulators (seven LDOs and one SMPS) declared on the PMICs that gbook’s tree does not declare
Reserved memorythe SoC file’s regions kept, one framebuffer reservation added at the stock splash addressthe SoC file’s reserved-memory deleted as a whole and the needed regions redefined, with a comment that most of them overlap what depthcharge inserts at boot

The 47 nodes only gbook enables and the 34 only the port enables are counted from the compiled trees; pin-state subnodes make up most of both lists.

Where the two trees make the same choice

  • Both enter the kernel at EL2 and both drop the iommus property from the ADSP and CDSP remoteproc nodes. gbook does it with the fixups file after applying mainline’s EL2 overlay; the port folds the overlay’s changes into the source (its commit message says it includes the x1-el2.dtso changes except those two properties, which make the firmware fail to load). The reason gbook recorded on 2026-11-02: with iommus set the driver asks TrustZone for a resource table and mica’s TrustZone answers with an error (the fixups).
  • Both load the ADSP and CDSP from the stock firmware. gbook names the stock split files (adsp.mdt and adsp_dtb.mdt) under a lowercase qcom/x1e80100/google/mica/ path and ships hashes only (firmware.sha256, ADR 0005); the port names single .mbn files under a capitalised path and redistributes them in a firmware package. The two paths differ in case, so a rootfs built for one tree will not find the other’s firmware without a symlink (inferred).
  • Both keep the console on uart21 and the Bluetooth UART on uart14, and both drive the ChromeOS EC over spi13 with the keyboard matrix behind it.

Where they differ in substance

TopicgbookNura port
CPU idledomain-idle-states deleted from the twelve CPU power domains, so PSCI cpuidle does not load and the cores idle in WFI (ADR 0009; why: SCMI over the CPUCP mailbox)the SoC file’s idle states kept
SCMIarm,max-rx-timeout-ms = <1000> on the SCMI node, marked unverifiedthe SoC file’s default
Sounddescribed, booted, speakers and microphones working as of 2026-11-05absent
Thermalthe SoC file’s zonesboard thermal zones on the PMIC ADC (warnings at this kernel pin)
Model string"Dell XPS Googlebook""Google Mica"

Whether the port’s machine shows the SCMI stall gbook measured is unknown here: its tree does not change the SCMI timeout or the idle states, and no report either way was found (inferred from the tree alone).

What this means for upstream

One compatible string can have only one board file in mainline. gbook holds every submission until its milestone M5 (ADR 0010); the project’s owner intends to write to the port’s author before then to coordinate a single google,mica tree and to offer the sound description and the SoundWire wake fix. Until that conversation, this page is the diff. The DT README’s “Before sending upstream” list (the binding entry, the block order, the attribution header) applies to whichever tree goes first.

Boot a gbook image on mica

A how-to for one boot attempt of a gbook kernel image from a USB stick, written from the project’s own runbook with the host-specific tooling left out. It assumes you have an image: the build recipe is documented (How mica boots Linux, the kernel configuration and patch pages) and the build scripts are published with a later release. No image download exists yet.

Roles in the project: a person does every key press, every unlock and every write to a stick; an agent builds, checks and reads consoles (ADR 0001). If you are alone, you are both.

Before the first attempt (once)

  1. Prove the way back first. Make a Google recovery stick for this model with the ChromeOS recovery tool, boot it once with the recovery keys (Esc + Refresh (F2) + Power) and confirm you reach its screen, then label it and never rewrite it. The project calls this Gate 1; nothing else happens before it passes (ADR 0001).
  2. Back up what you care about on the machine. Developer mode wipes user data.
  3. Unlock the bootloader on your own responsibility. On this model the steps are Google’s: turn on OEM unlocking in the system’s Developer options, hold Esc + Refresh (F2) + Power to reach the recovery screen, then Advanced options, Unlock bootloader. The firmware screen reads “Bootloader is unlocked” from then on, and self-signed kernels boot from an external drive without further settings (the boot-path note, chromeos-boot-path.md, “Unlock = developer mode”).
  4. A second stick for images: USB 3, 4 GB or more, used for nothing else. Keep it visibly different from the recovery stick; the project once put the recovery stick in by mistake and Google recovery ran.
  5. Optional but useful: a SuzyQ debug cable in the left port gives the AP console (ADR 0006). Without it the panel and, once Wi-Fi is up, the network log are the only channels.

Write the stick

  1. Write the whole image to the stick’s block device with dd, addressing the stick by its stable device path (for example under /dev/disk/by-id/), never by a letter that can change. The image is a complete disk of 256 MiB: a GPT with a 128 MiB ChromeOS kernel partition holding the signed FIT, and, on the same stick from 256 MiB on, a second partition for a rootfs when one is used (ADR 0003).
  2. Read the stick back and compare it with the image before you unplug it. Every rewrite also resets the kernel partition’s tries counter: depthcharge spends one try per boot, and the image sets 15.
  3. Power the stick off or eject it cleanly, then carry it straight to the laptop.

Boot

  1. Power the laptop off. Check: image stick in the right port, no charger, no hub, lid open. Battery at half or more.
  2. Power on with no keys held. On the “bootloader is unlocked” screen choose Boot from external drive.
  3. Never press the recovery keys with the image stick in. Recovery mode scans the stick for a Google-signed kernel, rejects the developer-signed one and marks the kernel partition bad; the stick then needs a rewrite (the project lost two boots to this on 2026-11-03).
  4. If you see “Recovering your device”, the recovery stick is in. Interrupt nothing. It reinstalls the same build and leaves developer mode on; a cut firmware update can brick the device.
  5. The first lines appear on the AP console at about 0.6 s and on the panel once the display driver is up. The initramfs prints GBOOK: status lines; with a rootfs on the stick it hands over to it, otherwise it reboots after its sweep. Pull the stick as soon as it reboots so the next boot is stock; with the stick still in, the firmware waits on its boot screen.
  6. SCMI needs a cold boot. After a warm reset from stock the CPUCP answers late or not at all and cpufreq fails, so power the machine off before each attempt (SCMI over the CPUCP mailbox).

If nothing appears

What you seeWhat to do
Looks hungWait two minutes for the kernel’s own panic reboot before holding Power (8 s); a forced reset usually loses the ramoops log
Firmware says “No valid image detected”The first refusal marks the kernel partition bad on the stick; re-inserting cannot work. Rewrite the stick
Prints, then hangs at a driverDisable that device-tree node for the next attempt, one change per attempt
LED code 3,5EC power-on timeout: charge, end the session, retry the same image later
Nothing new after five attemptsStop and ask; the project’s failure signatures are in the boot-path and first-boot notes

Back to stock

Esc + Refresh + Power with the recovery stick in the right port, then “Use USB drive or SD card”. It erases the disk and installs the build the stick carries. The project’s own return-to-stock plan, including the untested probes with a spare SSD, is in the M4 recovery matrix.

Feature status

As of 2026-11-05, 1:57 PM EST. Kernel: torvalds a804c57 plus kernel/patches/0001-0011. Board DTS: dts/x1e80100-google-mica.dts. The default state is “untested”. Every row cites the recon note it rests on, or “none” when no note covers it.

This is the feature matrix of ADR 0010: the milestones note, recommendation 2, which the owner adopted on 2026-11-04 (“all a” to its section 5). Asahi and Nura publish their feature tables the same way.

Patch numbers: 0008 and 0009 here are the PCIe GDSC and PCIe PHY GDSC retention patches. Both numbers named other patches before the rule against reusing numbers. 0008 was a GENI I2C timing test (commit 16a2828, attempt 2026-11-04-2), and 0009 was the IRQ-to-CPU-1 diagnostic (commit cee735d, attempt 2026-11-04-3, deleted by ADR 0009). Recon notes about those two attempts mean the old patches.

Legend

  • State: works, partial, broken or untested. A parked row keeps its state, and its note says “parked until ” (ADR 0010, update 2026-11-04).
  • Where it works:
    • mainline at a804c57: the driver is unchanged;
    • + patch NNNN: a local patch in kernel/patches/;
    • board DTS: needs the mica device tree;
    • config fragment (name): kernel/<name>.config builds the driver in, because the initramfs loads no modules.
    • These combine as needed; — means it works nowhere yet. In the Upstream column, — means there is nothing to report.
  • Upstream: what mainline or linux-firmware already has, and what gbook would have to send.
    • “mica DTS not sent” covers both the board DTS and the google,mica binding (dts/README.md, “Before sending upstream”).
    • The patch words follow ChromiumOS, with LOCAL in place of its CHROMIUM: FROMLIST means posted to a list but not merged, and LOCAL means carried here and not sent.
    • Nothing is sent before M5.
  • Evidence: the dated recon note, plus the attempt id where the note gives one. “none” means no note covers the feature.
  • Note: kept short. inference marks a judgement call, such as the choice of state.

Boot and consoles

FeatureStateWhere it worksUpstreamEvidenceNote
Boot from USB (dev-signed FIT, KERN-A)worksmainline at a804c57 + board DTSmica DTS not sentfirstboot, 2026-11-02-2right port; power on with no keys held, because the recovery keys mark the stick bad (wifi, update)
Kernel at EL2worksmainline at a804c57 + board DTS (with x1-el2.dtso)overlay in mainline; mica DTS not sentfirstboot, 2026-11-02-2All CPU(s) started at EL2; KVM not tried
AP console (uart21, ttyMSM0)partialmainline at a804c57 + board DTSmica DTS not sentfirstboot, 2026-11-02-2output from about 0.6 s; earlycon prints nothing before ttyMSM0 probes (inference: partial)
Panel console (simpledrm, then msm fbcon)worksmainline at a804c57 + config fragment (mica-firstboot, mica-display, mica-console)drivers in mainlinefirstboot, 2026-11-02-2; display, 2026-11-03-4coreboot framebuffer first; msm takes over at 1.6 s; banner on tty1
ramoops, read back through dropboxworksmainline at a804c57 + config fragment (mica-firstboot)drivers in mainlinefirstboot, 2026-11-02-2; wifi, 2026-11-03-3read on the next stock boot; later boots start mid-log, with no banner
Netlog over Wi-Fiworksn/a (gbook /init, attempt.sh netlog)n/awifi, 2026-11-03-3whole boot from 0.0 s, sent after GBOOK: end
Reboot back to stockworksmainline at a804c57n/afirstboot, 2026-11-02-2; gpu, 2026-11-03-5stock boots once the stick is out

SoC: power, clocks, SCMI/cpufreq, idle, suspend

FeatureStateWhere it worksUpstreamEvidenceNote
Every enabled DT node bindsworksmainline at a804c57 + patch 0002-0007 + board DTS + config fragments (mica-sync and others)drivers in mainline; GMU driver FROMLIST (patches 0002-0006); mica DTS not sentunbound, 2026-11-03-35; gmu-crypto, 2026-11-03-26unbound= 0: every interconnect and clock provider reaches sync_state(). A proxy, not a per-node bind check: a failed probe with no sync_state() consumer is not counted (the touchscreen’s HID probe still fails -22)
SCMI cpufreq (CPUCP mailbox)worksmainline at a804c57 + board DTS (1000 ms window, ADR 0009) + config fragment (mica-scmi)driver in mainline; window and ADR 0009 not sentscmi, 2026-11-03-8, 2026-11-04-4; ADR 00093 clusters, 710 MHz-3.42 GHz; 0 of 17 boots stalled without PSCI cpuidle; the window is unverified
SCMI after a warm reset from stockworksmainline at a804c57 + board DTS (1000 ms window, ADR 0009) + config fragment (mica-scmi)driver in mainline; window and ADR 0009 not sentscmi, 2026-11-03-9, -11; attempt 2026-11-04-131 of 1 warm reset on the current tree (Restart from stock; record: warm, AP reset in S0 with no G3->S5): firmware 0x20000, cpufreq 3 of 3, 0 timeouts, replies 0.1 ms. On 2026-11-03 2 of 2 warm resets failed (-110) with the 30 ms window and PSCI cpuidle on; which change fixed it is not separated (inference: ADR 0009 or the window)
CPU idlepartialboard DTS (ADR 0009)LOCAL DTS deviation; stall report drafted, not sentscmi, 2026-11-04-3, -4; ADR 0009a deliberate deviation (ADR 0009, accepted): PSCI cpuidle is off and cores idle in WFI; with it on, SCMI replies stalled up to 1 s; the revisit is open, since idle and suspend power are unmeasured (inference: partial)
s2idle round tripworksmainline at a804c57 + patch 0008 + patch 0009 + board DTS + config fragment (mica-ec, mica-pcie)0008 FROMLIST (LKML 2026-01-02); 0009 LOCAL, not sentsuspend, 2026-11-04-11 (3 boots)3 of 3 cold boots, against 0 of 7 before 0009; test images (gbook.suspend=1); which low-power state was reached is unmeasured
Wake from s2idle: EC RTC alarmworksmainline at a804c57 + board DTS + config fragment (mica-ec)drivers in mainline; mica DTS not sentsuspend, 2026-11-04-5, -11rtc0 is cros-ec-rtc; suspend-wakeup stays empty, so no wakeup source counted the wake
Wake from s2idle by keyboardpartialmainline at a804c57 + patch 0008 + patch 0009 + board DTS + config fragment (mica-ec)drivers in mainline; mica DTS not sentattempts 2026-11-04-14, -15every s2idle wake seen came on the EC interrupt (msmgpio 66 chromeos-ec, IRQ 181). On -15’s second boot, space woke it 7.1 s into s2idle and it resumed (rc 0, about 13 s of resume). On -15’s first boot, space did not wake it, and a power-button tap then left it powered off (no netlog, no SuzyQ, ramoops lost). On -14, an EC event woke it after 18.7 s with no key pressed. Lid and power-button wake untested
RTC (cros-ec-rtc, rtc0)worksmainline at a804c57 + board DTS + config fragment (mica-ec)drivers in mainline; mica DTS not sentec, 2026-11-03-14; suspend; 2026-11-04-12its alarm woke every suspend test. It counts through power-off: 242 s on the RTC against 240 s on the build host between two 2026-11-04-12 boots, with G3 between them (netlog rtc-at-send against the file’s write time). It is not set to real time: it reads 1970-01-19

Storage and buses: USB, Type-C, PCIe, NVMe

FeatureStateWhere it worksUpstreamEvidenceNote
USB 2 host, all four controllersworksmainline at a804c57 + board DTS + config fragment (mica-usb)drivers in mainline; mica DTS not sentusb, 2026-11-02-3webcam, FPMCU and the stick enumerate
USB 3, right portworksmainline at a804c57 + board DTS + config fragment (mica-usb, mica-adsp)drivers in mainline; mica DTS not sentusb, 2026-11-02-5; dp, 2026-11-03-65000M; needs the ADSP for pmic_glink; runs alongside DP
USB 3, left portuntested——usbparked until a session without the SuzyQ in the left port (owner, 2026-11-04 park round); the SuzyQ sits there; no device tested
Type-C ports (UCSI over pmic_glink)worksmainline at a804c57 + board DTS + config fragment (mica-usb, mica-adsp)drivers in mainline; mica DTS not sentusb, 2026-11-02-5; dp, 2026-11-03-62 ports, roles read; every read so far shows orientation=normal; connector@0 is the left port
PCIe, both controllersworksmainline at a804c57 + board DTS + config fragment (mica-pcie)drivers in mainline; mica DTS not sentpcie, 2026-11-02-6Gen4 x4 (NVMe) and Gen3 x2 (WCN7850) link up
NVMe SSD (driver, I/O)untested—driver in mainlinepcie, 2026-11-02-6parked until the spare SSD (M4, ADR 0002) (owner, 2026-11-04 park round); enumerates; the driver is held out (# forbid-y) until the owner approves a read-only step

Network: Wi-Fi, Bluetooth

FeatureStateWhere it worksUpstreamEvidenceNote
Wi-Fi (WCN7850, ath12k)worksmainline at a804c57 + board DTS + config fragment (mica-pcie)driver, firmware and board data in mainline and linux-firmware; mica DTS not sentwifi, 2026-11-03-1, -26 GHz at 320 MHz EHT, DHCP, ping; relinks after s2idle (suspend)
Bluetooth (WCN7850 on uart14)worksmainline at a804c57 + board DTS + config fragment (mica-bt) + firmware (linux-firmware qca/hmtbtfw20.tlv, hmtnv20.b112)drivers and firmware in mainline and linux-firmware; mica DTS not sentattempt 2026-11-04-14 (netlog)hci_uart_qca binds, hci0 registers, setup completes (BTFW.HAMILTON.2.0.1, board id 0x112); no userspace, so no scan or pairing was tried. In s2idle something woke the system after 18.7 s from IRQ 181, possibly Bluetooth (inference)

Display and GPU

FeatureStateWhere it worksUpstreamEvidenceNote
eDP panel (LG LP134WQ, 2560x1600)worksmainline at a804c57 + patch 0001 + board DTS + config fragment (mica-display)driver in mainline; panel entry LOCAL patch 0001, not sent; mica DTS not sentdisplay, 2026-11-03-4; panel-edp, 2026-11-03-9without 0001 it runs on conservative timings; 0001’s delays do not come from a datasheet
Backlight (PWM on the LPG)worksmainline at a804c57 + board DTS + config fragment (mica-display)drivers in mainline; mica DTS not sentdisplay, 2026-11-03-4; 2026-11-04-12 (3 boots)set to 1023 and read back 1023, then restored to 2048, in 3 of 3 base-image boots (2026-11-04-12)
DP alt mode, right port (connector@1)worksmainline at a804c57 + board DTS + config fragment (mica-usb, mica-adsp, mica-display)drivers in mainline; mica DTS not sentdp, 2026-11-03-6a 1920x720 monitor on DP-2, with USB 3 alongside
DP alt mode, left port (connector@0)untested——dpparked until a session without the SuzyQ in the left port (owner, 2026-11-04 park round); needs the SuzyQ out, so netlog only
GPU probe (Adreno X1-85)worksmainline at a804c57 + board DTS + config fragment (mica-gpu)driver and firmware in mainline and linux-firmware; mica DTS not sentgpu, 2026-11-03-5; gmu-crypto, 2026-11-03-26 (with 0002-0007)SQE and GMU firmware load; zap off under EL2; msm.separate_gpu_kms=1; no rendering test
GMU driver bindingworksmainline at a804c57 + patch 0002-0007 + board DTS + config fragment (mica-gpu)FROMLIST (patches 0002-0006: v1 of “drm/msm: Attach a driver to GMU”, superseded by v2 on 2026-10-30); 0007 LOCAL (Omarchy’s fix-up, not ours to send)gmu-crypto, 2026-11-03-26bound 3d6a000.gmu; the GMU left the unbound= list

Input and EC

FeatureStateWhere it worksUpstreamEvidenceNote
ChromeOS EC (cros-ec-spi)worksmainline at a804c57 + board DTS (GPIO chip-select) + config fragment (mica-ec)drivers in mainline; mica DTS not sentec, 2026-11-03-14the hardware chip-select gave garbage (attempt 2026-11-03-13, error -28)
Built-in keyboardworksmainline at a804c57 + board DTS + config fragment (mica-ec)drivers in mainline; mica DTS not sentec, 2026-11-03-14389 events in 20 s; layout, Fn and top-row keys not checked
Keyboard backlightworksmainline at a804c57 + board DTS + config fragment (mica-ec)drivers in mainline; mica DTS not sentec, 2026-11-03-140 and 100 set and read back; the owner saw it light
Touchpad (I2C HID)worksmainline at a804c57 + board DTS + config fragment (mica-touch)drivers in mainline; mica DTS not senttouch-tpm, 2026-11-03-1615554 events in 30 s
Touchscreen (I2C HID)broken—driver in mainline; cause unknowntouch-tpm, 2026-11-03-15 to 2026-11-04-2; 2026-11-04-12parked until a new touch lead (owner, 2026-11-03): reads turn to 0xff partway through; the DT matches stock. On 2026-11-04-12 it probed in 1 of 3 boots (2731 events from event4 while touched, IRQ 202 from 1 to 377) and failed -22 in the other two
Fanspartialmainline at a804c57 + board DTS (EC on spi13) + config fragment (mica-ec)drivers in mainline; mica DTS not sentec, 2026-11-03-14EC hwmon reads 2 fans at 0 RPM (idle); the EC drives them; never seen spinning (inference: partial)
Battery reportingpartialmainline at a804c57 + board DTS + config fragment (mica-adsp)drivers in mainline; ADSP firmware not in linux-firmwareusb, 2026-11-02-5; suspend, 2026-11-04-11capacity has never been read: empty at 2026-11-02-5 and in every 2026-11-04 netlog capture; status reads Charging or Not charging; power_now sign unchecked (inference: partial)
Lid switchuntested——noneparked until a stock-tree or EC lead (owner, 2026-11-04 park round); no lid GPIO in the stock tree (dts/README.md)
Power button and EC buttons (cros_ec_buttons)worksmainline at a804c57 + board DTS + config fragment (mica-ec)drivers in mainline; mica DTS not sentec, 2026-11-03-14; 2026-11-04-12one brief press gave 4 and 6 events (ec-buttons, event3) in two 2026-11-04-12 boots; the third had no press. What a press does without userspace is untested
Ambient-light sensoruntested——noneparked until a stock-tree or EC lead (owner, 2026-11-04 park round); not described in the DTS (dts/README.md)
Glowbar (lid light)untested——noneparked until a stock-tree or EC lead (owner, 2026-11-04 park round); not described in the DTS (dts/README.md)

Security

FeatureStateWhere it worksUpstreamEvidenceNote
TPM (Ti50, cr50_i2c)worksmainline at a804c57 + board DTS + config fragment (mica-tpm)driver in mainline; mica DTS not senttouch-tpm, 2026-11-03-15tpm0 and tpm-rng-0; probe traffic only (TCG_TPM2_HMAC kept out)
Crypto engine (QCE)broken—QCE depends on BROKEN at the pingmu-crypto, 2026-11-03-26parked until upstream drops BROKEN for QCE (owner, 2026-11-04 park round); node disabled in the board DTS (Omarchy 2210); not parked by the owner

Remoteprocs

FeatureStateWhere it worksUpstreamEvidenceNote
ADSPworksmainline at a804c57 + board DTS (mica-el2.fixups) + config fragment (mica-adsp)driver in mainline; stock firmware not in linux-firmware; mica DTS not sentusb, 2026-11-02-5stock modem_a firmware; its start waits 5 s on the CDSP’s SSCTL reply (cdsp)
CDSPworksmainline at a804c57 + board DTS (mica-el2.fixups) + config fragment (mica-adsp)driver in mainline; stock firmware not in linux-firmware; mica DTS not sentcdsp, 2026-11-03-7still running at about 130 s; FastRPC is =m, so nothing uses it yet

Multimedia

FeatureStateWhere it worksUpstreamEvidenceNote
Speakersworksbase image bc62c69e… (v2026.11.05, the sound node in the board DTS; proven first on the TEST IMAGE with dts/diag/sound.dtsi), kernel #35: all four units play and survive the SoundWire masters’ runtime PM wakes with local patches 0011 (WSA884x clk_stop_mode1) and 0010 (boot 6 of 2026-11-05: tones a minute apart, a tone after 10 minutes idle, the four names heard); in the board DTS since 2026-11-05 (9d120a5)no (M5 hold)audio-2026-11-04.md: aplay -l card 0 X1E80100-Dell-XPS-13-9345, playback device 1; 2026-11-05-1 with both masters’ power/control=on: the owner heard “front left, front right, rear right, rear left”; without it the clock-stop exit loses the re-enumeration by chance (two units on -28, none at 69.9 s on -1)kernel/mica-audio.config (RESET_GPIO=y, GPIO_SHARED_PROXY=y, attempts -26 and -27); ticket 16 closed 2026-11-05 (the udev rule was not a workaround and is gone; patches 0011 and 0010); the node is in the board DTS; released as v2026.11.05 on 2026-11-05 (attempt 2026-11-05-5: 3 clean cold boots, the four names heard on each)
Microphonesworksthe same image, boot 7 of 2026-11-05: arecord on capture device 3 (UCM Mic) recorded the owner’s voice and the playback was heardno (M5 hold)audio-2026-11-04.md, “The capture test”: the owner heard the playback (9:26 AM EST); the file’s RMS varies second by secondmic rail EN_PP3300_MIC (TLMM 69, stock overlay) as the VA macro’s vdd-micb-supply, the DMIC rate the Yoga’s (dts/README.md, “Unverified”); in the board DTS since 2026-11-05
Headphone outputuntested——noneparked until M3 (owner, 2026-11-04 park round); the stock tree disables the WCD938x headset codec (9345 diff); whether mica has a jack is not confirmed
Camerauntested——usb, 2026-11-02-3parked until M3 (owner, 2026-11-04 park round); the USB webcam 1bcf:28dc enumerates on usb_2; no capture tried
Fingerprintuntested——usb, 2026-11-02-3parked until M3 (owner, 2026-11-04 park round); the FPMCU 18d1:5406 enumerates on usb_mp; nothing more tried

Power

FeatureStateWhere it worksUpstreamEvidenceNote
Idle poweruntested——suspendparked until a battery-only power session (owner, 2026-11-04 park round); unmeasured, because the SuzyQ powers the bench; ADR 0009 raises it by an unknown amount
Suspend poweruntested——suspend, 2026-11-04-11parked until a battery-only power session (owner, 2026-11-04 park round); unmeasured; the battery reads as charging on the bench
Deep suspend (mem_sleep=deep)broken——attempt 2026-11-04-16 (AP and EC consoles, ramoops)parked until a debug boot with no_console_suspend (owner, 2026-11-04 park round: a failed test may be parked). mem with mem_sleep=deep: the EC saw the AP sleep (“Handle sleep: 1”, EC 145.3 s), then “AP_RST_L transitions not expected” (152.3 s, about 7 s in, before the 20 s alarm) and a host reboot. The kernel log stops at “Suspending console(s)” (136.0 s)

Userspace

FeatureStateWhere it worksUpstreamEvidenceNote
Initramfs (busybox /init)worksn/a (gbook kernel/initramfs/init)n/afirstboot, 2026-11-02-2captures, then reboots to stock; loads no modules
Rootfs from USBworksboard DTS + kernel/initramfs/init (switch_root) + the M3 rootfs (scripts/build-rootfs.sh)n/arootfs, 2026-11-04-18, -19; release boots -21 to -23; fast sweep -24 (m3-end 74 s); rootfs side -25 (m3-end 49 s, cage 37.8 s)Debian sid arm64 from the stick’s partition 2 (gbook-root): /init finds it by kern_guid, systemd boots, Wi-Fi joins in userspace (wpa_supplicant, networkd DHCP), Ghostty 1.4.0 runs under cage on the panel; the rootfs sends its netlog and journal over that link, and the build host reaches it by key-only ssh (ADR 0012 update)
Mesa (freedreno)worksmainline at a804c57 + patch 0002-0007 + the M3 rootfs (Mesa 26.2.6-1)drivers in mainline; GMU driver FROMLISTrootfs, 2026-11-04-18, -19; release boots -21 to -23eglinfo: OpenGL 4.6 Core on “Adreno X1-85”; kmscube on the display card (card2) exit 0, renderer Adreno X1-85; cage’s wlroots renderer “Adreno X1-85”, GLES 3.2; Vulkan on Turnip: vkcube through cage’s Wayland socket selected “Adreno X1-85” and rendered (boot 3)

Milestone M2: where it stands

M2 is done (2026-11-04, 12:38 PM ET): all four criteria are met. Criterion 1 is met by the unbound= proxy, and the s2idle row’s boots ran on test images.

M2 is “board on the console” in ADR 0010, from the milestones note, section 4. The ADR’s bar has four criteria:

  1. every enabled DT node binds;
  2. the M2 rows read works: USB host, Type-C, PCIe enumeration, Wi-Fi, eDP + backlight, GPU probe, EC keyboard, touchpad, TPM, SCMI cpufreq and the s2idle round trip;
  3. every other row reads works or partial, or is parked with a note;
  4. one base image (no debug command-line or DT extras) has 3 clean cold boots by scripts/attempt.sh tally.
CriterionMet?Why
1. Every enabled DT node bindsmet (by proxy)measured by unbound= (no sync_state() pending line), not per node: 0 since attempt 2026-11-03-35 (unbound), and unbound=0 in the health column of every 2026-11-04 row of dumps/firstboot/attempts.tsv
2. The M2 rows read worksmetall twelve; the backlight was set and read back in 3 of 3 base-image boots (2026-11-04-12). The s2idle row’s boots ran on test images (gbook.suspend=1)
3. Every other row is works, partial or parkedmetBluetooth works; keyboard wake from s2idle is partial (1 of 2); deep suspend failed and is parked (a reset about 7 s into deep sleep); 16 more rows are parked (owner, 2026-11-04 park round), and the touchscreen was parked on 2026-11-03 after three boots: 18 parked rows in all
4. A base image with 3 clean cold bootsmetattempt 2026-11-04-12, kernel #26 (7.3.0-gbook-gf12f3f6-ga804c5742bff-dirty), attempt.sh stick: base image; 3 cold boots, 3 clean (below)

Under criterion 3, rootfs, Mesa and the audio state are also M3 criteria in the ladder (section 4).

Counts (checked by scripts/status_check.py): 55 rows: 36 works, 5 partial, 3 broken, 11 untested; 14 parked. The line above is the one scripts/check.sh compares with the tables (retro-8/06), so it changes with them.

Criterion 4, read-only from dumps/firstboot/attempts.tsv (gitignored), at 11:24 AM ET:

$ scripts/attempt.sh tally '^2026-11-04-12$'
group 1 /^2026-11-04-12$/: attempts=1 health=1 boots=3 known=3 stalled=0 clean=3 unknown=0 max=0.1-0.1ms
  2026-11-04-12 0/3
  • The base image: packaged with no command-line or DT extras; attempt.sh verify recorded “base image: no command-line or DT extras” and image sha256 2f08fb87463eeac6. All three boots started cold (EC G3->S5).
  • What “clean” means: in tally, “clean” is an SCMI verdict: no timeout, and every reply under 100 ms (scripts/boot_health.py). It does not judge the whole boot.
  • Before 2026-11-04-12 the only row with 0008 + 0009 was 2026-11-04-11, a test image (gbook.suspend=1).
  • Next: M3, userspace from USB (a rootfs with Mesa, ADR 0010).

dts

x1e80100-google-mica.dts is the mainline device tree for the Dell XPS Googlebook (mica). Draft, never booted (2026-10-21). Booted since 2026-11-02; each feature’s state is in docs/status.md.

  • Experiments: device-tree diagnostics go in diag/ as .dtsi fragments, built on top with GBOOK_DTS_EXTRA, not into this file (2026-11-03).
  • Base: forked from arch/arm64/boot/dts/qcom/x1e80100-dell-xps13-9345.dts at torvalds/linux a804c5742bff224d4cb4d25dd383554d598be32b. The first commit is the unmodified copy, so git log -p shows every mica change. The SoC dtsi at that pin is hamoa.dtsi (with hamoa-pmics.dtsi), not x1e80100.dtsi.
  • Board changes: from the stock Googlebook OS tree (stock-dtb.md), mapped to mainline labels in 9345-dts-diff.md. The stock tree is two files: dumps/recovery-16471258/dtbo/entry00-base.dts (the SoC) and entry01-overlay.dts (the board: regulators, the sound card’s overrides, thermal trips). Search both; a value “not in the stock tree” was found in the overlay on 2026-11-04 (retro-12/01).
  • Compatible: "google,mica", "qcom,x1e80100", so depthcharge’s FIT matcher (google,mica-rev*-sku* down to google,mica) picks it.

Build and check

scripts/setup-toolchain.sh   # once: sparse kernel checkout at a804c57 + dtschema venv + schema, in ~/.cache/gbook
scripts/build-dtb.sh ~/.cache/gbook/linux-a804c57 dts/x1e80100-google-mica.dts /tmp/mica.dtb   # cpp + dtc, Kbuild W=0
~/.cache/gbook/venv-dtschema/bin/dt-validate -s ~/.cache/gbook/schema-a804c57.json /tmp/mica.dtb
# first-boot DTB: the same base plus the pinned x1-el2.dtbo, applied with fdtoverlay like Kbuild's -dtbs targets
scripts/build-dtb.sh ~/.cache/gbook/linux-a804c57 dts/x1e80100-google-mica.dts /tmp/mica-el2.dtb \
  ~/.cache/gbook/linux-a804c57/arch/arm64/boot/dts/qcom/x1-el2.dtso

scripts/check.sh builds and validates both DTBs, and also fails when the keymap drifts from stock. When the google,mica binding lands, remove its allowance in check.sh.

State 2026-10-21:

  • Build: no warnings.
  • dt-validate: one finding. google,mica is not yet in Documentation/devicetree/bindings/arm/qcom.yaml; upstreaming needs that binding patch. The unmodified 9345 validates clean with the same schema.
  • The keymap in the built DTB is identical to stock (119 entries).
  • Every board-specific TLMM pin in the built DTB traces to the stock tree. A programmatic check compared the decompiled DTB with the stock overlay and base. The only other pins are SoC-level states from hamoa.dtsi (QUP UARTs, LPASS) and the PCIe clkreq pins the stock base also sets. No 9345-only pin survived.

What changed from the 9345

AreaChangeEvidence
Identitymodel, compatibledepthcharge fit.c matching
Console/chosen/stdout-path = "serial0:115200n8" (serial0 = uart21), added 2026-10-21stock base chosen; ADR 0006
Keyboard, lidremoved the I2C keyboard (i2c0 0x5), GPIO lid (92) and camera LED (110); added cros_ec on spi13 (int TLMM 66, 3 MHz) with cros-ec-keyb 8x18 and the stock keymap; spi13 CS on gpio55 (stock pin) as cs-gpios, a mainline choice after trogdor/herobrine; the hardware CS gave garbage, the GPIO CS works (attempts 2026-11-03-13 and -14, ec-2026-11-03.md)stock overlay; no lid or LED GPIO in the stock tree
Touchpadmoved from i2c17 to i2c0 0x2c; int TLMM 3; reset TLMM 2 held high by pinctrl; vdd L12B, vddl a 3.3 V switch on TLMM 36stock overlay
Touchscreenhid-descr-addr 0; vdd L15B, vddl a 3.3 V switch on TLMM 88 (150 ms startup); report-enable TLMM 89 low (replaces the 9345’s TLMM 75)stock overlay
SCMIarm,max-rx-timeout-ms = <1000> on /firmware/scmi (250 on attempts -20 to -22), unverified diagnostic with no stock counterpart (2026-11-03: the default 30 ms failed on 5 of 7 cold starts)scmi-cpucp-timeouts.md, option 2
CPU idledeviation from stock (ADR 0009, superseding ADR 0008): domain-idle-states (cluster_c4) deleted from cpu_pd0-11, so cpuidle-psci does not load and cores idle in WFI; the cluster and system states stay as stock. A core in PSCI ret did not take a pending SPI until a PPI ran (SCMI stalls on whichever CPU the IRQ was routed to, 2026-11-04-3)stock base has cluster_c4 on every CPU; scmi-2026-11-03.md
Crypto engine&crypto disabled, not from the stock tree: QCE is BROKEN at the pin, and the unbound node kept aggre2_noc and mc_virt from sync_state() (Omarchy 2210, 2026-11-03)drivers/crypto/Kconfig:531
TPMgoogle,cr50 on i2c10 0x50, int TLMM 91, EDGE_FALLINGstock overlay; tpm_tis_i2c_cr50.c:751 requests IRQF_TRIGGER_FALLING at a804c57
eUSB2 repeatersi2c9 → i2c5; 0x4f (reset 184) and new 0x4b (reset TLMM 10) for usb_2stock overlay
USBusb_2 host plus usb_2_hsphystock overlay (usb4, usb2_phy_4)
Backlightpwm-backlight: pmk8550_pwm channel 0, 270000 ns; enable PMC8380-3 GPIO 4; supply switch PMC8380-3 GPIO 10. Panel enable TLMM 74 removed. 2026-11-03: the edp_bl_en pin state moved from TLMM 74 (left over from the 9345) to PMC8380-3 GPIO 4; the PWM pin (PMK8550 GPIO 5) takes the stock edp_pwm_active configstock overlay (sde_edp qcom,edp-backlight-en-gpio, fragment 76 edp_pwm_active); same wiring as mainline T14s
Always-on railscamera 3.3 V (TLMM 206), fingerprint MCU (TLMM 22)stock overlay
Reserved GPIOsunion of stock <34 2> <44 4> and the 9345’s <76 4> <238 1>stock base gpio-reserved-ranges
EL2 overlayx1-el2.dtso applied, then dts/mica-el2.fixups deletes iommus from remoteproc_adsp and remoteproc_cdsp (2026-11-02)stock base entry00-base.dts:5513, :12925 have none; with it, mica’s TrustZone refuses the PAS resource-table call (-EIO)
Audioadded 2026-11-05 (from dts/diag/sound.dtsi, booted as a TEST IMAGE 2026-11-04-26 to 2026-11-05-6; the moved node proven on the base image bc62c69e…, attempt 2026-11-05-5, 3 clean cold boots, the four names heard on each, released as v2026.11.05): sound (qcom,x1e80100-sndcard, model X1E80100-Dell-XPS-13-9345), 4x WSA8845 on swr0 and swr3 (speaker@0,0/@0,1 per bus, reset TLMM 204 for swr0’s pair, 205 for swr3’s, vdd-1p8 L15B), 4 DMICs on lpass_vamacro (LPI 6/7, 8/9; capture heard 2026-11-05), the mic rail EN_PP3300_MIC (TLMM 69, fixed, always-on) as vdd-micb-supply; pin states dmic_pp3300_en, spkr_02_sd_n_active, spkr_13_sd_n_active. Modelled on the Yoga Slim 7x at a804c57. Deviation: stock’s active state drives the shutdown pins high, the pin states here start them low (the Yoga’s way, the codec driver’s reset-gpios releases them); all four units enumerate and play that way (ticket 16)stock base entry00-base.dts: card 17905 (routing 17921, dmic pins 17925), wsa_swr_master 17641 (codecs 17658, 17675; pins 16697, 16760; shutdown 18214 = TLMM 204, 5080), wsa2_swr_master (pins 17049, 17112; shutdown 18222 = TLMM 205, 5115), pm_humu_l15 10857, va-macro@7660000 17499; stock overlay entry01-overlay.dts 1389 (regulator-dmic-en), 1374 (dmic_pp3300_pin); audio-2026-11-04.md
Firmware pathsqcom/x1e80100/google/mica/…not in linux-firmware. 2026-11-02: ADSP and CDSP names changed to the stock split files adsp.mdt/adsp_dtb.mdt and cdsp.mdt/cdsp_dtb.mdt, read from the stock modem_a partition’s image/ (recovery stick copy, build 16560795)

Kept from the 9345 and confirmed in the stock tree: retimers and all six rail GPIOs; USB-C orientation GPIOs 121 and 123; eDP 3.3 V on TLMM 70 and HPD on 119; NVMe 3.3 V on TLMM 18; PCIe4 (146-148) and PCIe6a (152-154); Wi-Fi 3.3 V on TLMM 214, WLAN 117, BT 116; uart14 BT; uart21 debug.

First-boot additions

Decided 2026-10-21 (What goes into the first-boot DTB), in the tree since 2026-11-02:

  • reserved-memory: framebuffer@e4800000, reg = <0x0 0xe4800000 0x0 0x2200000>, no-map. The address comes from the stock splash_region, the size from coreboot’s reservation.
  • The first-boot DTB is x1e80100-google-mica-el2.dtb = this DTB + x1-el2.dtbo from the pinned kernel. scripts/build-dtb.sh and check.sh build and validate both. 2026-11-02: both build with no warnings, both give only the google,mica dt-validate finding, and the -el2 DTB keeps compatible = "google,mica". The reservation overlaps no hamoa.dtsi region (nearest: tags_mem1, ending at 0xe36a0000).

Unverified (check at first boot)

  • The left/right labels on the USB-C connectors (copied from the 9345). 2026-11-03: swapped on mica. connector@0 (retimer on i2c3, usb_1_ss0, mdss_dp0) is the left port, connector@1 (i2c7, usb_1_ss1, mdss_dp1) the right one (dp-2026-11-03.md).
  • usb_2_hsphy supplies (from other hamoa boards; the stock overlay does not set them).
  • usb_mp QMP PHYs: stock disables both, and this draft keeps the 9345’s enabled ones so usb_mp probes.
  • The panel model (edp-panel probes it over AUX) and the PWM period.
  • The edp_bl_en pin config on PMC8380-3 GPIO 4 (function = "normal", from the T14s; stock has no pin state for it).
  • Through the EC, no DT node needed (2026-11-03, attempt -14): keyboard backlight (chromeos::kbd_backlight), fan RPM (cros_ec hwmon).
  • Audio (2026-11-05, moved from dts/diag/sound.dtsi): vdd-io-supply L12B on the four codecs (not in the stock codec nodes; every a804c57 x1e80100 board uses it); qcom,dmic-sample-rate = <4800000> (the Yoga’s; stock sets only the clock divider 16); the 3.3 V EN_PP3300_MIC as the VA macro’s vdd-micb-supply (the Yoga’s is a 1.8 V LDO; the stock rail is always-on, so a wrong consumer binding is harmless); which physical unit each prefix (WooferLeft, TweeterLeft, WooferRight, TweeterRight, the UCM profile’s names) drives: stock calls swr0’s pair SpkrLeft/SpkrRight and swr3’s Spkr2Left/Spkr2Right. The speakers play (ticket 16, 2026-11-05). 9:26 AM EST 2026-11-05: capture passed (the owner heard the recording played back, audio-2026-11-04.md, “The capture test”), so the DMIC rate and the mic rail work as written; the values stay the Yoga’s where stock has no counterpart, and the unit-to-prefix question stays open.
  • Not described yet: EC PWM, Glowbar, ambient-light sensor (the 9345 file has no sound node at this commit; mica’s is above), camera (USB), fingerprint (USB).

Before sending upstream

  • Add google,mica to Documentation/devicetree/bindings/arm/qcom.yaml (x1e80100 boards).
  • Reorder the &label blocks alphabetically, as qcom reviewers expect. They are left in place here so the diff against the 9345 copy stays readable.
  • Replace the copied 9345 copyright header with the right attribution for a derived file.

Source files

x1e80100-google-mica.dts (the board DTS) and mica-el2.fixups (the differences from mainline’s x1-el2.dtso), as exported.

Kernel configuration fragments

Merged onto the arm64 defconfig in name order after mica-firstboot.config (scripts/build-kernel.sh). A # require: line must end =y in the result; a # forbid-y: line must not. Each fragment’s own header follows. Also here: firmware.sha256 (hashes of the vendor firmware the initramfs stages; the blobs are not redistributed), dt-gaps.txt and known-failures.txt.

mica-adsp.config

Phase 3, ADSP: boot the audio DSP so pmic_glink (Type-C orientation and mode, battery) can come up.
Source: the USB run (docs/recon/usb-2026-11-02.md): pmic-glink stayed deferred with "failed to initialize pdr".
Firmware: the stock modem_a partition's image/adsp.mdt + .bNN and adsp_dtb.mdt + .bNN (build
ADSP.HT.5.9.c5-00032.4-HAMOA-1), packed into the initramfs from ~/.cache/gbook/firmware (ADR 0005: not in git).
The remoteproc driver for qcom,x1e80100-adsp-pas and its helpers (all =m in defconfig at a804c57).
require: REMOTEPROC
require: RPMSG_QCOM_GLINK
require: QCOM_SMEM
require: QCOM_SMP2P
require: QCOM_AOSS_QMP
require: QCOM_IPCC
require: QCOM_PDR_HELPERS
require: QCOM_QMI_HELPERS
require: FW_LOADER

mica-audio.config

M3, audio (ticket 10, 2026-11-04): the sound card of the board DTS (dts/diag/sound.dtsi until 2026-11-05; 4x WSA8845 on swr0 and swr3, 4 DMICs on
the VA macro, the ADSP's APM and PRM services over GPR). scripts/dt-kconfig-map.py on the sound -el2 DTB,
2026-11-04: every driver below was =m, and the initramfs loads no modules. The topology
qcom/x1e80100/X1E80100-Dell-XPS-13-9345-tplg.bin is requested at the card's component probe
(sound/soc/qcom/qdsp6/topology.c:1422, q6apm.c q6apm_audio_probe), so scripts/stage-firmware.sh puts it in the
initramfs. Firmware-side: the stock ADSP (adsp.mdt) must offer the "avs/audio" protection domain; QCOM_PD_MAPPER
and QCOM_PDR_HELPERS are =y already (pmic_glink uses them).
require: SND_SOC_QDSP6_COMMON
require: SND_SOC_QDSP6_APM
require: SND_SOC_QDSP6_APM_DAI
require: SND_SOC_QDSP6_APM_LPASS_DAI
require: SND_SOC_QDSP6_PRM
require: SND_SOC_QDSP6_PRM_LPASS_CLOCKS
require: SND_SOC_QCOM_COMMON
require: SND_SOC_QCOM_SDW
require: SND_SOC_TOPOLOGY
require: SND_SOC_LPASS_MACRO_COMMON
require: REGMAP_SOUNDWIRE
require: QCOM_PDR_HELPERS
require: QCOM_PD_MAPPER
require: RPMSG_QCOM_GLINK_SMEM

mica-bt.config

M2, Bluetooth (owner, 2026-11-04 park round: "test"): the WCN7850's Bluetooth on uart14 (dts: &uart14 { bluetooth {
compatible = "qcom,wcn7850-bt" } }). scripts/dt-kconfig-map.py names BT_HCIUART (=m in arm64 defconfig) for it, so
the node never bound. hci_uart's QCA protocol (BT_HCIUART_QCA, already =y) probes it over serdev, powers it through
the WCN power sequencer (pwrseq-qcom-wcn) and, when the core powers the new controller on, downloads
qca/hmtbtfw<rom>.tlv and an NVM file chosen by board id (btqca.c:825-827, 919-921, qca_get_nvm_name_by_board at
a804c57); scripts/stage-firmware.sh stages all five hmtnv20.* candidates. No userspace (no BlueZ): the controller
is set up, then the core powers it off again; nothing scans or pairs.
require: BT_HCIUART_SERDEV
require: BT_HCIUART_QCA
require: SERIAL_DEV_BUS
require: SERIAL_QCOM_GENI
require: POWER_SEQUENCING_QCOM_WCN

mica-console.config

The owner's view on the Googlebook's own panel (2026-11-03): /init mirrors a readable status page to tty1 (fbcon on
the eDP panel; the kernel console stays on the serial port, console=ttyMSM0). The default 8x16 font gives 320x100
characters on the 2560x1600 13.4" panel, too small to read at arm's length; Terminus 16x32 gives 160x50
(lib/fonts/Kconfig:126-134). The command line selects it with fbcon=font:TER16x32 (scripts/package-in-container.sh).
require: FRAMEBUFFER_CONSOLE
require: DRM_FBDEV_EMULATION
require: VT_CONSOLE

mica-debug.config

Event tracing for the boot captures (owner round 2026-11-03: 1a), docs/research/debug-tools.md option 1.
arm64 defconfig:1970 sets "# CONFIG_FTRACE is not set", so the kernel had no tracefs and no trace events. The command
line (scripts/package-in-container.sh) arms scmi, i2c and qcom_geni_i2c events with trace_event= from start_kernel;
/init saves a filtered copy into the netlog. ENABLE_DEFAULT_TRACERS selects TRACING, which selects EVENT_TRACING,
RING_BUFFER and TRACEPOINTS (kernel/trace/Kconfig:179-188, :588-591). No function tracer.
require: EVENT_TRACING
require: TRACEPOINTS

mica-display.config

Phase 3, display: msm DRM drives the eDP panel, so the panel stays lit after the regulator core's unused-supply
cleanup. Source: attempt 2026-11-03-3 (docs/recon/wifi-2026-11-03.md): "VREG_EDP_3P3: disabling" at about 33 s,
because only simpledrm drives the screen and nothing claims the panel supply. The mica DTS already describes the
path (&mdss, &mdss_dp3 with an edp-panel on its aux-bus, &mdss_dp3_phy, pwm-backlight); every driver on it is =m
in defconfig at a804c57 and the initramfs loads no modules.
The GPU is left out: the command line carries msm.skip_gpu=1 (scripts/package-in-container.sh), because the GPU
would join the MDSS component match (drivers/gpu/drm/msm/msm_drv.c:1076-1086) and wait on gpucc.
MDSS/DPU/DP (drivers/gpu/drm/msm/Kconfig:3). Sub-options DPU, DP and KMS_FBDEV default to y.
require: DRM
require: DRM_MSM_DPU
require: DRM_MSM_DP
require: DRM_MSM_KMS_FBDEV
require: DRM_FBDEV_EMULATION
require: PWM
require: REGULATOR_FIXED_VOLTAGE
require: DRM_AUX_HPD_BRIDGE

mica-ec.config

Phase 3, EC: the ChromeOS EC on spi13, its keyboard and its children (owner round 2026-11-03: 1a 2a).
Source: every boot on 2026-11-03 (10 in dumps/firstboot/2026-11-03-netlog.txt) logs 77 lines of
"sync_state() pending due to a94000.spi" and not one cros_ec line. a94000 is spi13 (hamoa.dtsi:1905), the bus of
cros_ec ec@0 in dts/x1e80100-google-mica.dts. Its driver (qcom,geni-spi, drivers/spi/Kconfig:1013) is =m in arm64
defconfig, and the initramfs loads no modules, so the EC and cros-ec-keyb never probed.
The EC's children are MFD cells that cros_ec_dev adds per EC feature (drivers/mfd/cros_ec_dev.c:106-153):
cros-keyboard-leds for EC_FEATURE_PWM_KEYB ("chromeos::kbd_backlight", cros_kbd_led_backlight.c:201), the EC LEDs,
and the cros_ec hwmon (fan RPM, temperatures; SENSORS_CROS_EC is already =y).
require: CROS_EC
require: CROS_EC_SPI
require: CROS_EC_PROTO
require: MFD_CROS_EC_DEV
require: KEYBOARD_CROS_EC
require: SENSORS_CROS_EC
forbid-y: CROS_EC_TYPEC
forbid-y: CROS_EC_UCSI

mica-firstboot.config

First-boot config fragment for mica, merged onto arm64 defconfig at torvalds/linux a804c57:
  scripts/kconfig/merge_config.sh -m arch/arm64/configs/defconfig kernel/mica-firstboot.config
  make ARCH=arm64 LLVM=1 olddefconfig
Decided in .scratch/phase-2-first-boot/issues/06-kernel-tree.md (inputs: tickets 04 and 05).
Boot arguments are not set here; they go on the mkdepthcharge command line (ticket 08).

scripts/check.sh checks offline that every CONFIG_ line and every "# require:" symbol exists in a804c57 Kconfig.
After the merge, the build fails unless all of them are =y.
Silent-boot channel 1: the failed boot's printk in depthcharge's ramoops region (defconfig: PSTORE_RAM=m).
require: SERIAL_QCOM_GENI
require: SERIAL_QCOM_GENI_CONSOLE
require: QCOM_GENI_SE
require: PINCTRL_MSM
require: PINCTRL_X1E80100
require: QCOM_PDC
require: COMMON_CLK_QCOM
require: CLK_X1E80100_GCC
require: QCOM_RPMH
require: QCOM_COMMAND_DB
require: QCOM_CLK_RPMH
require: QCOM_RPMHPD
require: INTERCONNECT
require: INTERCONNECT_QCOM
require: INTERCONNECT_QCOM_X1E80100
require: ARM_SMMU
require: ARM_SMMU_QCOM
require: ARM_PSCI_FW
require: ARM_PSCI_CPUIDLE
require: ARM_PSCI_CPUIDLE_DOMAIN

mica-gpu.config

Phase 3, GPU: the Adreno X1-85 (gpu@3d00000, qcom,adreno-43050c01) with its GMU (gmu@3d6a000).
Source: attempt 2026-11-03-4 (docs/recon/display-2026-11-03.md): 3da0000.iommu (the GPU SMMU) stayed deferred on
"supplier 3d90000.clock-controller not ready", and the display only bound because the command line carried
msm.skip_gpu=1. scripts/dt-kconfig-map.py on the mica-el2 DTB: gpucc is the one GPU-path driver still =m.
The command line now carries msm.separate_gpu_kms=1 instead (drivers/gpu/drm/msm/msm_drv.c:57-66,1082;
adreno_device.c:275), so the GPU gets its own DRM device and a GPU failure cannot take the display with it.
Firmware: qcom/gen70500_sqe.fw and qcom/gen70500_gmu.bin (a6xx_catalog.c:1573-1577), loaded on the first open of
the GPU's DRM device (msm_drv.c msm_open -> load_gpu), staged by scripts/stage-firmware.sh from
linux-firmware-qcom 20260916-1. The zap shader is disabled by x1-el2.dtso, so a6xx_gpu.c:1507-1516 writes
SECVID_TRUST_CNTL directly and warns "Zap shader not enabled".
The GPU clock controller (drivers/clk/qcom/Kconfig:278): GDSCs and clocks for the GPU, GMU and adreno_smmu.
require: DRM_MSM
require: ARM_SMMU
require: ARM_SMMU_QCOM
require: PM_DEVFREQ
require: DEVFREQ_GOV_SIMPLE_ONDEMAND
require: PM_OPP
require: QCOM_LLCC

mica-pcie.config

Phase 3, PCIe: bring up both PCIe controllers and enumerate their devices.
Source: every boot so far left pcie@1bf8000 and pcie@1c08000 deferred ("Failed to parse Root Port: -517");
scripts/dt-kconfig-map.py on the mica-el2 DTB shows their suppliers are =m in defconfig at a804c57.

Safety: the NVMe slot holds the stock SSD, which is never written (ADR 0003, ticket 07). BLK_DEV_NVME stays =m
(not loaded), so enumeration reads PCI config space only and no NVMe command reaches the SSD.
QMP PCIe PHYs: gen4x4 phy@1bfc000 (pcie@1bf8000) and gen3x2 phy@1c0e000 (pcie@1c08000). Their menu parent
PHY_QCOM_QMP is already =y in mica-usb.config.
forbid-y: BLK_DEV_NVME
require: PCIE_QCOM
require: PCIEPORTBUS
require: PCI_MSI
require: POWER_SEQUENCING
require: PCI_PWRCTRL
require: PCI_PWRCTRL_PWRSEQ

mica-pmdebug.config

Suspend debugging (2026-11-04, ADR 0009's suspend test): attempts 2026-11-04-5 to -8 reset the AP about 0.7 s after
the s2idle wake. With patch 0008 the PCIe GDSC warning is gone and the kernel prints nothing before the reset, so
/init (gbook.suspend=1) turns on /sys/power/pm_print_times and pm_debug_messages: every device's suspend and resume
callback is logged as "calling <cb> @ <pid>, parent: <dev>" before it runs (drivers/base/power/main.c:227-241 at
a804c57), and the last such line before the reset names the device. Both files exist only with PM_SLEEP_DEBUG
(kernel/power/main.c:644, a def_bool on PM_DEBUG && PM_SLEEP, kernel/power/Kconfig:257-259).
require: PM_SLEEP_DEBUG

mica-rootfs.config

M3, the rootfs (ADR 0012, decision 6; .scratch/m3-rootfs ticket 01). systemd v262's README ("REQUIREMENTS") says
userspace firmware loading "is not supported and should be disabled in the kernel"; arm64 defconfig sets
FW_LOADER_USER_HELPER=y (built config, read 2026-11-04). Off here; firmware still loads from the filesystem
(Documentation/driver-api/firmware/fw_search_path.rst at a804c57). USB Attached SCSI for the rootfs stick: the stick
ran bulk-only so far (USB_UAS not set); UAS falls back to bulk-only for devices that lack it.
CONFIG_FW_LOADER_USER_HELPER is not set
forbid-y: FW_LOADER_USER_HELPER

mica-scmi.config

Phase 3, SCMI: CPU performance domains (cpufreq) through the CPUCP mailbox.
Source: every boot up to attempt 2026-11-03-7 left "firmware:scmi: supplier 17430000.mailbox not ready", then
"arm-scmi: failed to setup channels": the mailbox driver for qcom,x1e80100-cpucp-mbox
(drivers/mailbox/qcom-cpucp-mbox.c:205, Kconfig:343) is =m in defconfig, and the initramfs loads no modules.
The stock tree drives the same block: cpucp@18830000 (qcom,cpucp-v2) with reg 0x17430000 + 0x18830000, scmi mboxes
<cpucp 0 cpucp 2>, shmem at 0x18b4e000 (entry00-base.dts:384-402, :908-926, :10534-10540; stock also lists a vendor protocol@80 that hamoa.dtsi has not), as hamoa.dtsi does.
The CPUs take their "perf" power domain from scmi_dvfs (hamoa.dtsi:78-79 and the other cpu nodes).
require: MAILBOX
require: ARM_SCMI_PROTOCOL
require: ARM_SCMI_TRANSPORT_MAILBOX
require: ARM_SCMI_PERF_DOMAIN
require: ARM_SCMI_CPUFREQ
require: DEBUG_FS

mica-sync.config

Phase 3, the last never-bound devices (handoff 2026-11-03 item 3; kernel/dt-gaps.txt "sync_state lead").
Source: attempt -26 left unbound=6: four bandwidth-monitor PMUs (pmu@24091000 qcom,sc7280-llcc-bwmon;
pmu@240b3400/@240b5400/@240b6400 qcom,sdm845-bwmon), videocc@aaf0000 and camcc@ade0000, all =m in defconfig while
the initramfs loads no modules (scripts/dt-kconfig-map.py at a804c57). An unbound interconnect consumer keeps the
providers from sync_state() and their boot-time votes in place (the crypto node did, Omarchy 2210); whether these
do is what the boot shows. Kconfig at a804c57: QCOM_ICC_BWMON drivers/soc/qcom/Kconfig:266 (selects PM_OPP,
REGMAP_MMIO), SM_VIDEOCC_8550 drivers/clk/qcom/Kconfig:1981 (covers X1E80100), CLK_X1E80100_CAMCC :247.
require: QCOM_ICC_BWMON
require: SM_VIDEOCC_8550
require: CLK_X1E80100_CAMCC

mica-touch.config

Phase 3, touch: the I2C HID touchpad and touchscreen (owner 2026-11-03, after the retro's DT gap check).
Source: scripts/dt-kconfig-map.py --gaps listed "hid-over-i2c  I2C_HID_OF=m  touchscreen@10,touchpad@2c": the
OF glue for hid-over-i2c (drivers/hid/i2c-hid/Kconfig:25, selects I2C_HID_CORE) is =m in arm64 defconfig, and the
initramfs loads no modules, so neither node has ever bound. Nodes: touchpad@2c on i2c0 (descriptor 0x20, TLMM 3)
and touchscreen@10 (LG SW42101M, descriptor 0x0, TLMM 51) in dts/x1e80100-google-mica.dts.
HID_MULTITOUCH: a precision touchpad and a touchscreen report as multitouch; hid-generic would see a mouse at best.
require: I2C_HID
require: HID_GENERIC
require: INPUT_EVDEV
require: I2C_QCOM_GENI

mica-tpm.config

Phase 3, TPM: the Ti50 GSC's TPM on I2C (owner 2026-11-03, after the retro's DT gap check).
Source: scripts/dt-kconfig-map.py --gaps listed "google,cr50  TCG_TIS_I2C_CR50=m  tpm@50": the driver
(drivers/char/tpm/Kconfig:116) is =m in arm64 defconfig, so tpm@50 has never bound. It knows the Ti50 by its
DID/VID ("ti50 DT" / "ti50 OT", tpm_tis_i2c_cr50.c:693-702) and prints "<name> TPM 2.0 (i2c 0x50 irq N id 0x..)".
TPM traffic: probe sends startup/self-test and capability reads (tpm_chip_register); reboot sends
TPM2_Shutdown(CLEAR) (tpm-chip.c:272), as any Linux kernel with a TPM does. No NV or key writes.
require: TCG_TPM
require: HW_RANDOM_TPM
forbid-y: TCG_TPM2_HMAC

mica-usb.config

Phase 3, USB: build the whole USB chain in, so the module-free initramfs can use it.
Merged after kernel/mica-firstboot.config by scripts/build-kernel.sh.
Source: the first boot (docs/recon/firstboot-2026-11-02.md): all four dwc3 controllers stayed deferred
("failed to register DWC3 Core") because every PHY, repeater, retimer and I2C driver below was =m in defconfig
at a804c57 and no module was loaded. Each line maps a compatible in x1e80100-google-mica-el2.dtb to its driver.
I2C buses that carry the PS8830 retimers and PTN3222 redrivers (qcom,geni-i2c).
require: USB_DWC3
require: USB_DWC3_QCOM
require: USB_XHCI_HCD
require: USB_XHCI_PLATFORM
require: USB_STORAGE
require: USB_HID
require: USB_ROLE_SWITCH
require: SPMI_MSM_PMIC_ARB
require: MFD_SPMI_PMIC

Local kernel patches

Applied by the build onto torvalds a804c57 in name order. Line 1 of each file is its status (the ChromiumOS words, LOCAL for CHROMIUM: decision 0007). The four LOCAL gbook patches stay unpublished until milestone M5 (decision 0010); their status lines are listed.

PatchStatusFile
0001-drm-panel-edp-add-lgd-0x0804.patchLOCAL gbook (not sent: the EDID gives only part of the model name), checked 2026-11-04held until M5
0002-drm-msm-a6xx-cxpd-device-link-stateless.patchFROMLIST [RFT 1/5] of “drm/msm: Attach a driver to GMU” v1 (Akhil P Oommen) 20260513-gmu-sync-state-fix-v1-1-6e33e6aa9b4f@oss.qualcomm.com; v2 has it as 2/8 20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-040002-drm-msm-a6xx-cxpd-device-link-stateless.patch
0003-drm-msm-centralize-standalone-drm-check.patchFROMLIST [RFT 2/5] of “drm/msm: Attach a driver to GMU” v1 (Akhil P Oommen) 20260513-gmu-sync-state-fix-v1-2-6e33e6aa9b4f@oss.qualcomm.com; dropped in v2 20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-040003-drm-msm-centralize-standalone-drm-check.patch
0004-drm-msm-adreno-fix-drvdata-typecast.patchFROMLIST [RFT 3/5] of “drm/msm: Attach a driver to GMU” v1 (Akhil P Oommen) 20260513-gmu-sync-state-fix-v1-3-6e33e6aa9b4f@oss.qualcomm.com; v2 has it as 1/8 20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-040004-drm-msm-adreno-fix-drvdata-typecast.patch
0005-drm-msm-component-model-for-standalone-gpu.patchFROMLIST [RFT 4/5] of “drm/msm: Attach a driver to GMU” v1 (Akhil P Oommen) 20260513-gmu-sync-state-fix-v1-4-6e33e6aa9b4f@oss.qualcomm.com; v2 has it as 5/8 20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-040005-drm-msm-component-model-for-standalone-gpu.patch
0006-drm-msm-attach-a-driver-to-the-gmu.patchFROMLIST [RFT 5/5] of “drm/msm: Attach a driver to GMU” v1 (Akhil P Oommen) 20260513-gmu-sync-state-fix-v1-5-6e33e6aa9b4f@oss.qualcomm.com; v2 has it as 6/8 20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com; copy: Omarchy linux-omarchy-aarch64 (below), ported to v7.2.8 by Brandon Williams; checked 2026-11-040006-drm-msm-attach-a-driver-to-the-gmu.patch
0007-drm-msm-gmu-optional-and-lock-init.patchLOCAL Brandon Williams via Omarchy (third-party fix-up on 0002-0006), https://github.com/omacom/omarchy-pkgs/tree/master/pkgbuilds/linux-omarchy-aarch64, checked 2026-11-040007-drm-msm-gmu-optional-and-lock-init.patch
0008-clk-qcom-gcc-x1e80100-pcie-gdsc-retention.patchFROMLIST [6/7] (Krishna Chaitanya Chundru) 20260102-pci_gdsc_fix-v1-6-b17ed3d175bc@oss.qualcomm.com, changes requested on linux-arm-msm patchwork; regenerated against a804c57; checked 2026-11-040008-clk-qcom-gcc-x1e80100-pcie-gdsc-retention.patch
0009-clk-qcom-gcc-x1e80100-pcie-phy-gdsc-retention.patchLOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-04held until M5
0010-soundwire-qcom-rearm-enumerator-after-clock-stop.patchLOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-05held until M5
0011-asoc-wsa884x-clk-stop-mode1.patchLOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-05held until M5

Build scripts

The header of each build script in the private repository, as of this build. The scripts themselves run on the project’s build host and are not published yet (their paths and listeners are specific to it).

setup-toolchain.sh

Set up the DTS toolchain outside the repo (idempotent): a sparse torvalds/linux checkout at the pinned commit
(DTS sources, bindings, every Kconfig file and the arm64 defconfig), a dtschema venv, and the processed binding schema.
  scripts/setup-toolchain.sh            -> $GBOOK_TOOLS (default ~/.cache/gbook)
  PYTHON=python3.14 scripts/setup-toolchain.sh   (the macOS host: bare python3 is 3.15 there)
  GBOOK_FULL=1 scripts/setup-toolchain.sh        (the build host: the whole tree, for scripts/build-kernel.sh; ~1.5 GB)
Then: scripts/build-dtb.sh ~/.cache/gbook/linux-<short> dts/x1e80100-google-mica.dts /tmp/mica.dtb
      ~/.cache/gbook/venv-dtschema/bin/dt-validate -s ~/.cache/gbook/schema-<short>.json /tmp/mica.dtb
The commit is the one the mica DTS was forked from (ADR 0004); change both together.

build-dtb.sh

Compile a board DTS against a mainline kernel tree the way Kbuild does (cpp, then dtc), without building a kernel.
  scripts/build-dtb.sh <kernel-tree> <file.dts> [out.dtb] [extra.dtsi ...] [overlay.dtso ...] [fixes.fixups ...]
A .dtsi is a diagnostic built on top of the board DTS (a wrapper #includes the DTS, then each .dtsi in order), so an
experiment from dts/diag/ never edits the stock-traceable DTS (retro 2026-11-03; GBOOK_DTS_EXTRA in package-image.sh).
With overlays, out.dtb is the base with each overlay applied by fdtoverlay, as Kbuild builds "-dtbs" targets
(scripts/Makefile.dtbs), e.g. the first-boot DTB:
  scripts/build-dtb.sh <tree> dts/x1e80100-google-mica.dts /tmp/mica-el2.dtb <tree>/arch/arm64/boot/dts/qcom/x1-el2.dtso \
    dts/mica-el2.fixups
A .fixups file lists "delete-property <label> <property>" lines (labels from /__symbols__), applied with fdtput
after the overlays, because overlays cannot delete properties.
The kernel tree only needs arch/arm64/boot/dts/qcom, include/dt-bindings and scripts/dtc/include-prefixes
(a sparse checkout is enough). The DTS may live outside the tree: its own directory is searched first.
Preprocesses with $CC -E like Kbuild (Apple's cpp wrapper misparses -o). Prints dtc warnings; exits non-zero on any error.

build-kernel.sh

Build the mica kernel Image on the build host: arm64 defconfig at the pinned commit + every kernel/*.config fragment
(mica-firstboot.config first, then the rest in name order),
clang/lld (LLVM=1), out of tree. Image only: no modules, no in-tree dtbs (scripts/build-dtb.sh builds the DTB).
  GBOOK_FULL=1 scripts/setup-toolchain.sh     # once: the full kernel tree
  scripts/build-kernel.sh [kernel-tree] [out-dir]
Defaults: $GBOOK_TOOLS/linux-a804c57 and $GBOOK_TOOLS/build-a804c57 (GBOOK_TOOLS defaults to ~/.cache/gbook).
Applies kernel/patches/*.patch first (ADR 0007; the kernel release then ends in -dirty).
Writes <out-dir>/localversion-gbook (gbook_localversion below), so the release names the gbook commit too: it reads
like 7.3.0-gbook-g6934235-ga804c5742bff-dirty (a release keeps that form: ADR 0011 maps the hash to its tag).
Fails if any CONFIG_ line or "# require:" symbol of any fragment is not =y after the merge (ticket 06), or if any
"# forbid-y:" symbol is =y (safety rails, e.g. the NVMe driver near the stock SSD).
Needs clang, ld.lld, make, flex, bison, bc, perl and openssl headers.

package-image.sh

Package the first-boot image on the build host: stage Image, the -el2 DTB and /init, then run scripts/package-in-container.sh
in the digest-pinned Debian sid container through pkexec (the owner stays out of the docker group, ticket 06).
  scripts/build-kernel.sh && scripts/package-image.sh
Output: $GBOOK_TOOLS/out/{mica-usb.img,mica.kpart,manifest.txt,...} (GBOOK_TOOLS defaults to ~/.cache/gbook).
Firmware: scripts/stage-firmware.sh rebuilds $GBOOK_TOOLS/firmware (stock modem_a + the build host's linux-firmware, never in
git: ADR 0005) and checks it against kernel/firmware.sha256; the tree goes into the initramfs at /lib/firmware.
Wi-Fi: ~/.local/state/gbook/wifi.conf (owner-written wpa_supplicant network block), if present, goes in as
/etc/wpa_supplicant.conf.
Writes nothing to a USB drive; the owner does that with pkexec dd (ticket 13).

build-rootfs.sh

Build the M3 rootfs (ADR 0012; .scratch/m3-rootfs ticket 03): Debian sid arm64 from one snapshot.debian.org
timestamp, mmdebstrap in the digest-pinned container of scripts/package-image.sh, run rootless with podman
(retro-8/05). Output in $GBOOK_TOOLS/rootfs (GBOOK_TOOLS defaults to ~/.cache/gbook), never in git:
  out/rootfs.ext4     the ROOT image (GBOOK_ROOTFS_SIZE, default 4G; label gbook-root), from out/rootfs.tar
  out/lock.txt        every installed package as pkg:arch=version, sorted (the lock list)
  out/manifest.txt    snapshot, SOURCE_DATE_EPOCH, container digest, tool versions, counts and sha256s
  out/mmdebstrap.log  the build log; cache/ keeps every .deb (snapshot answered 503/504 on 2026-11-04)
  scripts/build-rootfs.sh [YYYYMMDDTHHMMSSZ]      (default: today 00:00 UTC; ticket 02 sized 20261104T000000Z)
  GBOOK_ROOTFS_MIRROR=http://deb.debian.org/debian   while snapshot.debian.org is down: the manifest then says snapshot=none
                                                     and requested_snapshot=<ts>, so release tooling (ticket 12) sees it
  GBOOK_ROOTFS_OFFLINE=1   rebuild from cache/ alone, pinned to the previous out/lock.txt (no network for packages)
The manifest's lock_changed= line counts lock.txt lines that differ from the previous build's (kept as lock.prev.txt).
Mode (ADR 0012, decision 9): the container runs mmdebstrap inside its own user namespace (unshare --map-root-user
--map-auto, the substance of mmdebstrap's unshare mode; as container root mmdebstrap would skip the namespace and
fail to mount, 2026-11-04). GBOOK_ROOTFS_MODE=sysadmin instead adds SYS_ADMIN to the rootless container and runs
mmdebstrap's root mode directly: a capability inside podman's user namespace, not on the host.
Writes nothing to a USB drive; the owner writes ROOT (ticket 11).

stage-firmware.sh

Rebuild the initramfs firmware tree $GBOOK_TOOLS/firmware (default ~/.cache/gbook/firmware) from its two sources,
and check every file against kernel/firmware.sha256 (hashes only in git; the blobs never are: ADR 0005).
  scripts/stage-firmware.sh                 stage and check; fails on any hash change or missing file
  scripts/stage-firmware.sh --update        stage and rewrite kernel/firmware.sha256 (after a deliberate change)
  scripts/stage-firmware.sh board2 [regex]  list board-2.bin entry names matching regex (e.g. 'subsystem-vendor=105b')
Sources:
  stock  = the recovery-stick copy's modem_a partition (dumps/gate1/), read with scripts/fat_ls.py, no mount
  distro = the build host's linux-firmware-atheros and wireless-regdb packages (/usr/lib/firmware), zstd-decompressed
  pkg    = files from an Arch package the build host does not install (linux-firmware-qcom: the Adreno X1-85 GPU firmware and
           the Dell XPS 13 9345 audio topology),
           downloaded once into $GBOOK_TOOLS/pkgs, signature-checked against pacman's keyring, extracted, never
           installed. The version matches the distro linux-firmware packages (20260916-1).
package-image.sh copies the staged tree into the initramfs at /lib/firmware.

mkpatch.sh

Assemble a local kernel patch (ADR 0007) from the edited kernel tree, then restore the tree (retro-15/02: three
revisions of patch 0010 were 40-line heredocs each, and a prose "Status:" line failed check 34 once).
  scripts/mkpatch.sh <NNNN-slug> --subject '<text>' --notes <file> [--body <file>] [--status '<WORD> <reference>']
                     [--assisted-by <id>] [--tree <dir>] [--out <file>]
Edit the files in the tree first (its HEAD is the pin). The patch is the tree's diff of its modified tracked files:
  line 1   Status: <status>, checked <today>        default: LOCAL gbook (ours; not sent: upstream hold until M5)
  prose    the notes file: why, evidence, what it changes (gbook's block before the mail header)
  mail     From 0000…, From: the gbook repo's git identity, Date: now (date -R), Subject: [PATCH] <subject>,
           the body file (or the subject alone), Assisted-by: <id> (default Claude), ---, diffstat, diff
Then the touched files go back to HEAD, so the tree stays what build-kernel.sh expects (it applies every patch itself).
--out names the file (default kernel/patches/<NNNN-slug>.patch; a new number takes the next unused one, check 34).

release-draft.sh

Draft a release's annotated tag message (ADR 0011 and its 2026-11-04 update; .scratch/m3-rootfs ticket 12). Checks the
release bar first, prints the message, writes it to $GBOOK_TOOLS/out/tag-<version>.txt and names the owner's git tag
command. Tags nothing: the owner tags (CLAUDE.md, "Git").
  scripts/release-draft.sh <milestone> <attempt id regex> [vYYYY.0M.0D[.MICRO]]   (default version: today's date)
The bar (ADR 0011): one base image, 3 clean cold boots. "clean" is scripts/attempt.sh tally's SCMI verdict over the
rows the regex selects (anchor it: '^2026-11-04-12$'); "cold" is each row's start column (boot_start.py, the row's first
boot); "base image" is what attempt.sh stick last read back from the stick. The rootfs must be pinned by date
(manifest snapshot=, not none: a GBOOK_ROOTFS_MIRROR build is not release material).
The rootfs on the stick's partition 2 must be that manifest's (attempt.sh root-verify's record, retro-9/01).
check.sh 46 runs the two checks alone: release-draft.sh --self-check <tally line> <starts> <stick file> <manifest> [<root file>].

Project glossary

mica

The Googlebook’s Android device codename (ro.product.device, read over adb 2026-10-20).

bluey

The ChromeOS reference baseboard for Qualcomm X1 devices in coreboot (src/mainboard/google/bluey); mica is one of its variants. Firmware branch firmware-bluey-16702.B.

Hamoa

Qualcomm’s platform name for the X1E80100 family; mainline Linux renamed x1e80100 device-tree files to hamoa.

Tributo

Codename of the Dell XPS 13 9345, the Windows sibling whose mainline device tree (x1e80100-dell-xps13-9345.dts, compatible dell,xps13-9345) is the starting point for this one.

Stock DT

The device tree Googlebook OS boots with: an LZ4-compressed vendor base DTB plus board and keyboard overlays in the dtbo_a partition (dtb_a is empty). It is used only as a hardware map (ADR 0004).

Recovery image

Google’s public mica-user/16471258/recovery.zip: one 19 GB GPT disk image. Build 16471258 is older than the installed 16560795.

Stock OS

Googlebook OS (codename Aluminium): Android 17 with a desktop shell, as shipped. Phase 1 reads it without changing it.

Stock drive and spare drive

The stock drive is the M.2 2230 SSD the laptop shipped with; it stays untouched in a drawer during experiments. The spare drive is a second 2230 SSD that takes every experiment (ADR 0002).

Gate

The condition that ends a phase. Work from the next phase does not start until its gate passes. Gate 1 = a proven way back to stock. Gate 2 = a mainline kernel’s own Linux version line, captured from the AP console, the panel or the ramoops readback.

Milestone

A named rung between the gates, M0 to M7 (ADR 0010), each with a “done when” bar. M0 and M1 coincide with Gates 1 and 2, M4 with Gate 3 and M6 with Gate 4. M5 (upstream-ready) ends the upstream hold. Per-feature state lives in docs/status.md.

Attempt

One boot of one image, with an ID YYYY-MM-DD-N and a row in the gitignored dumps/firstboot/attempts.tsv (scripts/attempt.sh). Repeat boots of the same image are added to its row.

Base image and test image

A base image is packaged with no debug command-line or DT extras. A test image has some (for example gbook.suspend=1). scripts/attempt.sh stick names which one the stick holds. Only a base image can become a release.

Release and version

A release is a base image with 3 clean cold boots by scripts/attempt.sh tally, which the owner would hand on. Its version is a CalVer annotated tag vYYYY.0M.0D[.MICRO] on the commit that built it (ADR 0011). A version names an image, and an attempt names one boot of it. uname -r carries the gbook commit it was built from, for example -gbook-g6934235 (+ for a tree with changes). A released image keeps that form, and its tag message maps the hash to the version.

Patch status

The first line of a kernel/patches/ file: Status: LOCAL, FROMLIST, FROMGIT or UPSTREAM, plus a reference (ChromiumOS’s words, with LOCAL for its CHROMIUM; ADR 0007 update). Patch numbers are never reused.

Recovery media

A USB drive (32 GB+) written by Google’s Device Recovery Utility Chrome extension; it reinstalls stock OS from the firmware recovery screen (Esc + F2, then Power, per Google’s Googlebook article; Esc + Refresh is the Chromebook combo).

Developer mode

ChromeOS firmware state that allows booting non-Google-signed kernels. On mica the recovery screen calls it “Unlock bootloader” / “Bootloader is unlocked”; it needs OEM unlocking turned on in Android first.

Kernel partition and FIT

A GPT partition of the ChromeOS-kernel type holding a vboot-signed image. On Arm the payload is a FIT (Flattened Image Tree): kernel plus one or more device trees, picked by compatible (google,mica-rev*-sku*).

Google’s USB bridge cable or dongle that depthcharge’s fastboot looks for as USB host; a Prolific PL27A1 also works. Without one, firmware fastboot runs over TCP via a USB-Ethernet adapter.

ABL

Qualcomm’s Android Bootloader, which loads Android boot images and speaks fastboot. mica does not use it (it boots through depthcharge, found 2026-10-21); kept here because the older plan assumed it.

RAM boot

fastboot boot <image>: boots an image once from memory without writing it to storage. mica’s depthcharge fastboot does not implement it; booting from a USB drive replaces it.

GSC

Google Security Chip on the board in place of a Windows TPM; on mica it is a Ti50 (coreboot TPM_GOOGLE_TI50). On Chromebooks it provides CCD.

CCD

Closed Case Debugging: UART consoles for the main processor and the EC over USB-C through the GSC, without opening the case. mica has it: CCD is Locked, with AP and EC console reads allowed Always (gsctool -a -I, 2026-10-21).

SuzyQ

The USB-C debug cable (here the Fyra Labs SuzyQ Board) that exposes CCD consoles to a second computer. On mica it works in the left port. the build host sees Ti50 18d1:5066, with ttys for the GSC shell, AP (uart21), EC and FPMCU.

EC

Embedded controller: battery, fans, keyboard, lid, Glowbar, LEDs. On mica it is a ChromeOS EC (Zephyr, Nuvoton NPCX9) over SPI, so mainline cros_ec drivers are the starting point; its mica overlay is partly private.

DTS and DTB

Device tree source and its compiled blob: the hardware description the kernel boots with on Arm.

GSI and DSU

Generic System Image, an AOSP system image for any Treble device; Dynamic System Updates boots one from new partitions without touching stock. The cheapest test after unlocking.

ROOT

The kernel stick’s partition 2, GPT name gbook-root, 8 GiB from sector 524288, declared by every KERN-A image’s GPT (ADR 0012, decision 3). The owner writes rootfs.ext4 to it; /init finds it as partition 2 of the disk that holds the kern_guid partition.

M3 rootfs

The Debian sid arm64 root filesystem of milestone M3, built by scripts/build-rootfs.sh from rootfs/packages.txt with mmdebstrap in the pinned container; rootfs/customize.sh adds its units. gbook-m3 is its capture service (GBOOK: m3-<key>= lines, a second netlog transfer); gbook-cage runs Ghostty under cage as user gbook.

mica (ssh host)

the build host’s ssh mica entry: the booted M3 rootfs at the Wi-Fi lease, key-only, user gbook with sudo (ADR 0012 update 2026-11-04). Reads are free; writes need the owner; one connection attempt per boot.

Releases

One entry per annotated tag vYYYY.0M.0D[.MICRO] (decision 0011); the tag message is the release note.

v2026.11.05

v2026.11.05: M3

Milestone: M3 (ADR 0010)
Kernel base: torvalds a804c57 (v7.3 + 1703, the 7.4 merge window; ADR 0011)
Kernel release: 7.3.0-gbook-2026.11.04-35-g7c6a1aa-ga804c5742bff-dirty (uname -r; build 69edd54 of gbook)
Local patches (kernel/patches/, ADR 0007):
  0001-drm-panel-edp-add-lgd-0x0804.patch: LOCAL gbook (not sent: the EDID gives only part of the model name), checked 2026-11-04
  0002-drm-msm-a6xx-cxpd-device-link-stateless.patch: FROMLIST [RFT 1/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-1-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 2/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0003-drm-msm-centralize-standalone-drm-check.patch: FROMLIST [RFT 2/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-2-6e33e6aa9b4f@oss.qualcomm.com>; dropped in v2 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0004-drm-msm-adreno-fix-drvdata-typecast.patch: FROMLIST [RFT 3/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-3-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 1/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0005-drm-msm-component-model-for-standalone-gpu.patch: FROMLIST [RFT 4/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-4-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 5/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0006-drm-msm-attach-a-driver-to-the-gmu.patch: FROMLIST [RFT 5/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-5-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 6/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below), ported to v7.2.8 by Brandon Williams; checked 2026-11-04
  0007-drm-msm-gmu-optional-and-lock-init.patch: LOCAL Brandon Williams via Omarchy (third-party fix-up on 0002-0006), https://github.com/omacom/omarchy-pkgs/tree/master/pkgbuilds/linux-omarchy-aarch64, checked 2026-11-04
  0008-clk-qcom-gcc-x1e80100-pcie-gdsc-retention.patch: FROMLIST [6/7] (Krishna Chaitanya Chundru) <20260102-pci_gdsc_fix-v1-6-b17ed3d175bc@oss.qualcomm.com>, changes requested on linux-arm-msm patchwork; regenerated against a804c57; checked 2026-11-04
  0009-clk-qcom-gcc-x1e80100-pcie-phy-gdsc-retention.patch: LOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-04
  0010-soundwire-qcom-rearm-enumerator-after-clock-stop.patch: LOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-05
  0011-asoc-wsa884x-clk-stop-mode1.patch: LOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-05
KERN-A image sha256 (attempt.sh stick): bc62c69ecf560525
Rootfs image sha256: 6fea12f2dc824f63947e7251fafd31ae799f44ea6c5e40056ebaddba0f72ac56
Rootfs snapshot: 20261104T000000Z (snapshot.debian.org); lock list sha256 12f96fa08768262e228319783831fa28bd229d9e821b2b5bdfd8be2d02cc1388; 321 packages
Proof (attempt.sh tally '^2026-11-05-5$'): group 1 /^2026-11-05-5$/: attempts=1 health=1 boots=3 known=3 stalled=0 clean=3 unknown=0 max=0.1-0.1ms
Attempts: 2026-11-05-5; start: cold
Changes since v2026.11.04: 45 commit(s)
  69edd54 Release bar met for v2026.11.05: base image bc62c69e (sound node in the board DTS), 3 clean cold boots, the names heard on each
  e4820b0 Audio: the capture test passes (the owner heard the recording played back); Microphones: works; boot 7 added to 2026-11-05-4
  9d120a5 Audio: the sound node moves from dts/diag/sound.dtsi into the board DTS (mapping and unverified values in dts/README.md)
  176058d Retro 15: check.sh -q, mkpatch.sh, localversion on the kernel's inputs, watch replaces and wait-rootfs, attempt lifecycle in CLAUDE.md, audio-state --names
  b9c6ca2 Audio: ticket 16 closed (the owner heard the idle tone and the four names); Speakers works; README resume line
  b93bd13 Audio: the rule-free boot meets ticket 16's bar (tones a minute apart, after 10 minutes idle, the four names)
  86d6fd2 Ticket 16: tone 3 of boot -4 has no hearing report; audio-state.sh's masters label is stale
  e7c2791 Audio: the udev rule leaves the rootfs (patches 0011 and 0010 fix the wakes); check 45 now refuses it; status row for the speakers
  9a61e0b Audio: boot 2026-11-05-4 recorded, patch 0011 with 0010 revision 3 passes the first bar (three wakes whole)
  c08dc60 Audio: boot 2026-11-05-3 recorded (revision 2 sees the stall, the re-arm does not clear it)
  7c6a1aa Audio: patch 0010 revision 3 (a wedged enumerator gets a controller reset and init) and patch 0011 (wsa884x clk_stop_mode1)
  ed3af1e Audio: boot 2026-11-05-2 recorded (patch 0010 revision 1 did not fire); ideas #29 and #30 on the task list
  d4c3b68 Audio: patch 0010 revision 2, a delayed check re-arms the enumerator while a peripheral sits at device 0 (ticket 16)
  22ffd5c Audio: the udev rule is not a workaround (its write is the first wake, bus 1 lost it); attempt 2026-11-05-2 opened for patch 0010
  50d16d2 Research sources: CodeLinaro through its GitLab API, patchwork and GitHub commits API for upstream-fix reads
  4921d2f Audio: ticket 16 comment, recon Next and log row for patch 0010 and the udev-rule rootfs build
  50ac603 Audio: local patch 0010, re-arm the SoundWire enumerator when re-enumeration stalls after clock stop (ticket 16)
  ffb7273 Retro 14: recon.sh stock-kmsg, audio-state UCM path and masters runtime PM, sdw status caveat, Next pruned
  f9b770d Audio: all four speakers play with the SoundWire masters kept clocked (boot 2026-11-05-1); udev workaround, ticket 16 fix shape
  c23ab29 Retro 13: gaps check reads reset-gpios and shared GPIOs; audio-state, swrm_enum, mica-ssh; one watch at a time
  dbfd2ef Research: ASoC and SoundWire debugging tools (ticket 16 reads)
  7f9fe97 Audio: speaker test, the left amplifiers never enumerate (ticket 16); ssh user joins audio
  9b8fc6b Audio: the sound card binds (attempts 2026-11-04-26 to -28)
  c613c1d mica-audio.config: GPIO_SHARED_PROXY=y (the shared reset pins, attempt -27)
  e8c45c0 mica-audio.config: RESET_GPIO=y (the codecs deferred on their reset-gpios, attempt -26)
  afca45f Retro 12: stock tree is two files, + kernel release refused at the bar, navigation lines
  a7d9d52 Log: the audio offline part; attempts row 2026-11-04-26
  c80b1a3 sound.dtsi: the DMIC supply from the stock overlay (EN_PP3300_MIC, TLMM 69)
  7f5199a Audio attempt, offline part (M3 ticket 10)
  7072998 Log: retro 11 done but ticket 03 (RTK.md, owner); README next line
  752eb8b Retro-11 tickets 02, 04, 05 and retro-9 07: journal group, tally pending row, Evidence column, the ssh block
  a71c205 watch: classify the EC power sequence (retro-11/01)
  ca9cb6f Retro 11: five tickets for the rootfs-side session
  4ec4dae Docs: the EC's G3->S5 after power-down is soft-off; Ghostty's 38 s marked inferred
  e085d11 Log: boot -25 powered off over ssh
  8079c55 Docs: the rootfs side measured, m3-end at 48.7 s (attempt 2026-11-04-25)
  e6d0e06 rootfs: cage at boot, polled cage read, joined channel handed over (task #26)
  82549ae Recon: the rootfs side of boot 2026-11-04-24, by the journal
  54d6e82 Log: the fast-sweep boot powered off over ssh
  a5ab9d2 Docs: the fast-sweep boot is still up; times from logged anchors
  ... and 5 more

v2026.11.04

v2026.11.04: M3

Milestone: M3 (ADR 0010)
Kernel base: torvalds a804c57 (v7.3 + 1703, the 7.4 merge window; ADR 0011)
Kernel release: 7.3.0-gbook-g82c43ed-ga804c5742bff-dirty (uname -r; build 506159b of gbook)
Local patches (kernel/patches/, ADR 0007):
  0001-drm-panel-edp-add-lgd-0x0804.patch: LOCAL gbook (not sent: the EDID gives only part of the model name), checked 2026-11-04
  0002-drm-msm-a6xx-cxpd-device-link-stateless.patch: FROMLIST [RFT 1/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-1-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 2/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0003-drm-msm-centralize-standalone-drm-check.patch: FROMLIST [RFT 2/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-2-6e33e6aa9b4f@oss.qualcomm.com>; dropped in v2 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0004-drm-msm-adreno-fix-drvdata-typecast.patch: FROMLIST [RFT 3/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-3-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 1/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0005-drm-msm-component-model-for-standalone-gpu.patch: FROMLIST [RFT 4/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-4-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 5/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below); checked 2026-11-04
  0006-drm-msm-attach-a-driver-to-the-gmu.patch: FROMLIST [RFT 5/5] of "drm/msm: Attach a driver to GMU" v1 (Akhil P Oommen) <20260513-gmu-sync-state-fix-v1-5-6e33e6aa9b4f@oss.qualcomm.com>; v2 has it as 6/8 <20261030-gmu-sync-state-fix-v2-0-89d3082d97af@oss.qualcomm.com>; copy: Omarchy linux-omarchy-aarch64 (below), ported to v7.2.8 by Brandon Williams; checked 2026-11-04
  0007-drm-msm-gmu-optional-and-lock-init.patch: LOCAL Brandon Williams via Omarchy (third-party fix-up on 0002-0006), https://github.com/omacom/omarchy-pkgs/tree/master/pkgbuilds/linux-omarchy-aarch64, checked 2026-11-04
  0008-clk-qcom-gcc-x1e80100-pcie-gdsc-retention.patch: FROMLIST [6/7] (Krishna Chaitanya Chundru) <20260102-pci_gdsc_fix-v1-6-b17ed3d175bc@oss.qualcomm.com>, changes requested on linux-arm-msm patchwork; regenerated against a804c57; checked 2026-11-04
  0009-clk-qcom-gcc-x1e80100-pcie-phy-gdsc-retention.patch: LOCAL gbook (ours; not sent: upstream hold until M5), checked 2026-11-04
KERN-A image sha256 (attempt.sh stick): 9673d6d782ef4cdb
Rootfs image sha256: 4b0e2e027c0b2d74699eaa907a0eaca8597504a9eab6caec394b28c6f15c5d3c
Rootfs snapshot: 20261104T000000Z (snapshot.debian.org); lock list sha256 12f96fa08768262e228319783831fa28bd229d9e821b2b5bdfd8be2d02cc1388; 321 packages
Proof (attempt.sh tally '^2026-11-04-2[123]$'): group 1 /^2026-11-04-2[123]$/: attempts=3 health=3 boots=3 known=3 stalled=0 clean=3 unknown=0 max=0.1-0.1ms
Attempts: 2026-11-04-21 2026-11-04-22 2026-11-04-23; start: cold,cold,cold
Changes since the first commit (first release): 256 commit(s)
  506159b M3 release bar met: snapshot rootfs on ROOT, 3 clean cold boots; release-draft.sh fixes
  0d297ff Log: the snapshot-pinned release rootfs built (20261104T000000Z, 321 packages)
  07885c4 Retro-9/01: ROOT write and read-back as attempt.sh root-cmd and root-verify
  8b5181a Docs: README resume, docs index, glossary and ADR 0010 updated for the first ROOT boots
  4f59c59 Retro-9/02 resolved: udiskie honours the sticks' no-automount hint
  584702c Retro 9: tickets for the ROOT-boot session's tooling gaps
  d21096d Third ROOT boot: ssh from the build host, the netlog with the journal, Vulkan
  6fe07af M3 ticket 13: sshd for the build host in the rootfs, keys handed over at boot
  575493b Research: how the build host reaches the booted rootfs (sshd, reverse ssh, journal, USB gadget)
  e11ae23 The rootfs boots from USB: Mesa on the GPU, Wi-Fi in userspace, Ghostty
  b9514cc Ticket 09: note the terminfo symlink for the next rebuild
  a2dee5a M3 ticket 12: release-draft.sh checks the bar and drafts the tag message
  879a770 M3 ticket 09: Ghostty under cage in the rootfs
  1e0ebb5 M3 ticket 07: the rootfs's units and the M3 captures
  d8123c7 Rootfs: offline mode from the .deb cache, and byte-identical builds
  5e3956f M3 ticket 08: Ghostty 1.4.0 built for arm64 by cross compilation
  ebd263f Research: pinned, reproducible rootfs builds when snapshot throttles
  acfbcf4 M3 ticket 05: /init finds ROOT and switch_roots into it
  be3091b Portable checks and an honest manifest for a non-snapshot rootfs build
  98a9391 M3 ticket 03: build-rootfs.sh builds the rootfs reproducibly
  e95ebd4 Retro-7/08 resolved: stick boot tries counted and raised to 15
  61258b6 Retro-8/01 resolved: watch and verify say when a console tty is gone
  d2e8a94 Retro-8/04 resolved: known parked failures collapse in watch
  a9a53d4 Retro-8/06 resolved: a format check for docs/status.md
  4cf4b78 Retro-7/07 resolved: check 32 reads every added markdown line
  f9e2532 Retro-8/05 resolved: rootless podman for the rootfs sizing
  80064c8 Retro 8: tickets for the M2 bench session's tooling gaps
  148a2d7 M3 tickets 04 and 06 verified on attempt 2026-11-04-17
  82c43ed M3 tickets 04 and 06: ROOT in every image's GPT; one netlog sender
  d573942 M3 ticket 02: size the rootfs (1.45 GiB installed, 303 packages)
  f2bac5a M3 ticket 01 resolved: build #28 has FW_LOADER_USER_HELPER off, USB_UAS on
  f5cb508 M3 plan: ADR 0012 (Debian sid rootfs on the stick), 12 tickets
  139bcdc docs/research/sources.md: where the evidence comes from, how to read it
  ca3c851 Research: the M3 rootfs (Debian sid now, Omarchy aarch64 at M4)
  ce13a86 Milestone M2 reached; deep suspend resets the AP and is parked
  b97066c Keyboard wake from s2idle: partial (1 of 2), via the EC interrupt
  cfe5ed6 Bluetooth works (2026-11-04-14); capture the s2idle wake IRQ
  635448d Bluetooth for M2: mica-bt.config, QCA firmware, /init capture
  8ebbda3 /init suspend modes: key (wake by key, 120 s safety alarm) and deep
  48fc483 M2 park round: 16 rows parked, 3 tests left (wake, deep, Bluetooth)
  ... and 216 more

Decisions

Architecture decision records, Nygard’s shape (status, context, decision, consequences) with dated updates.

1. No bootloader unlock until a way back to stock is proven

Status

Accepted (from the project log, 2026-10-17; recorded as an ADR 2026-10-21)

Context

Unlocking the bootloader wipes all data and disables device security (Google Help). The device is new and the only one the owner has. Dell’s Service Manual (Rev. A00, pages 65-66) documents USB recovery media from Google’s Device Recovery Utility, but nobody has shown that it restores a device that was unlocked, that has a blank or foreign SSD, or that it lets the bootloader be relocked. Google may serve only the latest image, so anti-rollback could block an older build.

Decision

Phase 2 (unlock) starts only after Gate 1 passes:

  • recovery media made and tested to reach the recovery screen,
  • the working recovery key combo recorded,
  • the recovery image and its build number saved,
  • build fingerprint and bootloader version recorded,
  • device-specific data that Phase 2 needs is backed up or dumped.

Agents never unlock, flash or enter recovery or fastboot; the owner does those steps by hand.

Consequences

Phase 1 stays on the stock OS, read-only over adb, which slows the start but keeps the device returnable. The spare-SSD approach (ADR 0002) reduces risk further but does not replace this gate, because the bootloader state lives in firmware, not on the SSD.

Update 2026-10-21

  • Google’s recovery image for mica is public: mica-user/16471258/recovery.zip (downloaded to dumps/, sha256 in stock-dtb.md). Its build CL3B.260622.273.R1/16471258 (AVB rollback index 1339) is older than the installed CL3B.260622.276.R2/16560795. Whether anti-rollback accepts it is unknown, so it does not pass Gate 1 by itself.
  • Unlocking is ChromeOS developer mode (ADR 0003). “Lock bootloader” on the developer screen returns to normal mode. Whether that restores verified boot without a reinstall is still untested.
  • Still required for Gate 1: Device Recovery Utility media made and booted to the recovery screen, and the build it serves compared with the installed one.

Update 2026-10-21 (recovery steps)

Google’s Recover your Googlebook (read 2026-10-21) documents:

  • Media: recovery media from the Device Recovery Utility in Chrome on a Chromebook, Windows or Mac. Linux is not listed. The drive is 32 GB or more and gets erased.
  • Recovery mode: Esc + F2, then Power. Esc + Refresh in Dell’s Service Manual is the Chromebook combo.
  • Recovery erases: recovering “permanently erases everything”.

For Gate 1, the owner stops at the recovery screen and does not select Use USB drive. The checklist lives in Pass Gate 1.

Update 2026-11-02 (Gate 1 passed)

Device Recovery Utility media reaches the recovery screen with Esc + F2, and serves CL3B.260622.276.R2/16560795, the installed build (rollback index 1339). A full copy of the stick is in dumps/gate1/. Details: Pass Gate 1. The relock test runs on unlock day, before any Linux boot (unlock-day ticket).

2. Experiment on a spare M.2 2230 SSD; keep the stock drive intact

Status

Accepted (from the project log, 2026-10-17; recorded as an ADR 2026-10-21)

Context

Dell’s Setup and Specifications guide (Rev. A00) lists one M.2 2230 PCIe Gen4 x4 NVMe slot, and the Service Manual reaches the SSD right after the base cover (4 Torx T5 screws). The SSD is a field-replaceable part. Dell names “Google Qualified” drives, so an unlisted drive may or may not be accepted.

Decision

All Linux experiments run on a second 2230 SSD. The stock drive comes out and stays in a drawer. Before any internal work: disconnect the battery-cable bracket and hold power for 20 s (Service Manual).

Consequences

  • Returning to stock is a drive swap, as long as the bootloader and firmware state allow it (see ADR 0001).
  • Open, testable questions: does the firmware accept an unlisted drive; does a cloned stock drive boot (tells whether the drive is bound to the device); do recovery and fastboot live in the 32 MB firmware flash (a blank drive answers that, since fastbootd lives on the SSD).
  • Opening the case may matter for warranty; the owner decides when.

Update 2026-10-21

The stock internal disk (from the recovery image’s GPT, stock-dtb.md) mixes ChromeOS KERN-A/B and ROOT-B with Android A/B partitions (boot_* use the ChromeOS-kernel type GUID). The first Linux boot goes on external or spare storage (ADR 0003). Nothing in that plan needs the stock drive.

Update 2026-10-21 (first boot)

Owner decision (boot media and port hookup):

  • First boot: Phase 2 runs from a dedicated USB stick in the right USB-C port, on battery.
  • Spare SSD: deferred to Phase 3, when Linux needs persistent storage.
  • Stock SSD: for Phase 2 it stays installed rather than in a drawer, and it is never written (ADR 0003). It is the fallback boot, and the stock OS on it reads back a failed boot’s ramoops through dropbox.

The rest of this decision applies once Phase 3 starts.

3. First boot through a ChromeOS kernel partition on external storage

Status

Accepted 2026-10-21. Supersedes the log’s original Phase 2 plan (fastboot boot of an Android boot image, then Mu-Silicium as a fallback).

Context

The original plan assumed Qualcomm ABL and Android fastboot. Recon and source research showed otherwise (recon, chromeos-boot-path.md):

  • The bootloader is ChromeOS depthcharge (Google_Mica.16702.*, branch firmware-bluey-16702.B).
  • Google’s “Unlock bootloader” is vboot developer mode. After the transition, the firmware enables external boot and fastboot, and it boots self-signed kernels.
  • depthcharge’s fastboot has no boot command, so there is no RAM boot. It runs only through an ALink or PL27A1 bridge, or over TCP via a USB-Ethernet adapter.
  • vboot accepts ChromeOS-type kernel partitions (FIT image: kernel plus DTBs) and Android vbmeta partitions. depthcharge picks a FIT configuration by compatible, down to google,mica.

Decision

The first Linux boot uses a ChromeOS kernel partition (GUID fe3a2a5d-…) on a USB drive or the spare SSD:

  • Image: a FIT image with the mainline Image, the mica DTB and an initramfs, signed with the public vboot developer keys.
  • Build: depthcharge-tools with a [boards/custom/mica] section.
  • Boot: from the “Boot from external drive” menu.

The Android GKI route (modified boot/vendor_boot/dtbo) and RW_LEGACY alternate firmware are fallbacks, not the plan.

Consequences

  • The stock SSD and its Android partitions are never written during first-boot tests.
  • No special cable is needed. The SuzyQ on the left port gives the AP console (ADR 0006).
  • Partition attributes (priority, tries, successful) decide what boots. A bad image falls back to the next partition or the menu, not to a brick.
  • postmarketOS’s trogdor recipe (deviceinfo_generate_depthcharge_image, cgpt) applies almost directly.
  • Still to confirm on the device: the board-id/sku-id DT match, and whether depthcharge leaves a framebuffer.

Update 2026-10-21

Source research answered both items under “Still to confirm on the device”, pending a live boot:

  • DT match: not a blocker. A DTB whose root compatible holds google,mica matches any board-id and sku-id, and a single-DTB FIT keeps a default config that boots even without a match. depthcharge prints the real ids on the AP console (fit-packaging.md).
  • Framebuffer: depthcharge adds no simple-framebuffer node. coreboot’s scan-out at 0xE4800000 stays live, and mainline can use it through COREBOOT_FRAMEBUFFER and simpledrm only if the DTS reserves that region no-map (silent-boot-observation.md).
  • Exception level: the kernel starts at EL2 with no hypervisor, so the DTB needs the x1-el2.dtso changes (el2-handoff.md).

Update 2026-10-21 (first-boot details)

Decided on the first-boot map:

  • Image: a 256 MiB image holding only KERN-A (128 MiB), cgpt add -i 1 -t kernel -S 0 -T 3 -P 15. It is rewritten whole for every attempt, which resets vboot’s tries (Iteration loop).
  • Hookup: a dedicated USB stick in the right USB-C port, on battery, with the SuzyQ on the left. The owner writes the stick with pkexec dd (Boot media and port hookup).
  • Firmware fastboot over TCP is ruled out: it writes only the single fixed disk, the stock SSD (depthcharge 694eabf src/fastboot/disk.c:40-44).
  • DTB: the FIT carries x1e80100-google-mica-el2.dtb, because the kernel enters at EL2 with no hypervisor (EL2 handoff).

Update 2026-11-05 (12:20 PM EST): the stock drive locked read-only from /init

“The stock drive is never written” is now enforced mechanically for the Linux side of a boot: /init’s lock_stock() (task #36, after the Steam Frame initramfs’s blockdev_ro hook, re-implemented from its description in steamos-arm.md) runs right after the stick is identified by its kern_guid partition and sets every other block device, whole disks and partitions, read-only at the block layer (blockdev --setro); ram, loop, zram and dm devices are skipped, and an unknown GUID locks nothing (locking the stick blindly would break ROOT). The capture GBOOK: lock-ro= names what was locked, none: only the stick (sda) while the NVMe driver stays # forbid-y, or skipped: with the reason. It is advisory against mistakes: a root process can --setrw. check.sh 58 drives it on a synthetic /sys/class/block with a fake blockdev. Untested on the device until the next boot.

4. Mainline device tree forked from the XPS 13 9345; stock tree as hardware map only

Status

Accepted 2026-10-21.

Context

  • No mica DTS exists anywhere: not in mainline, not in the ChromiumOS 6.12 kernels, not on lore.
  • The stock DT is a Qualcomm vendor tree: an LZ4 base plus overlays in dtbo_a, with sde, qupv3_seN and qcom,… properties. Mainline cannot boot it.
  • The 9345 is a close match: address-by-address comparison (9345-dts-diff.md) shows mica reuses its retimer block, touchscreen, PCIe, eDP and uart21 debug console. The parts that differ are ChromeOS ones: cros-ec, Ti50, QSPI flash, a USB fingerprint MCU.

Decision

  • dts/x1e80100-google-mica.dts starts as an unmodified copy of mainline x1e80100-dell-xps13-9345.dts at a pinned commit (torvalds a804c57), committed alone. Mica’s changes go in later commits, so the diff is the record.
  • compatible = "google,mica", "qcom,x1e80100".
  • The stock tree is used only as a hardware map: buses, addresses, GPIOs, regulators, keymap. Every board-specific pin must trace to it. Anything copied from the 9345 without a stock match is marked “unverified”.
  • Checks before a commit counts: scripts/build-dtb.sh with no warnings, and dt-validate with no findings beyond the pending google,mica binding.

Consequences

  • Upstreaming later needs a qcom.yaml binding patch, alphabetical node order and correct attribution.
  • Mainline changes to the 9345 file should be followed by re-basing onto a newer pinned commit.
  • Missing pieces (keyboard backlight, Glowbar, ALS, fans, audio, camera) get added once something boots.

Update 2026-10-21

  • EL2 changes come from mainline’s x1-el2.dtso, applied at build time (x1e80100-google-mica-el2.dtb), the way mainline builds every x1 board. The DTS stays the plain board file (What goes into the first-boot DTB).
  • One board fact traces to firmware source rather than the stock tree. The framebuffer reservation’s address matches the stock splash_region (0xe4800000). Its size, 0x2200000, comes from coreboot’s memlayout.ld; stock uses 0x21c0000 with no no-map.
  • /chosen/stdout-path = "serial0:115200n8" is now in the DTS, traced to the stock base (commit 709212e).

5. Vendor content and per-unit identifiers stay out of git; extraction stays reproducible

Status

Accepted 2026-10-21. It records rules that CLAUDE.md has applied since the scaffold.

Context

  • Vendor blobs: the work needs Google’s and Dell’s binaries (the recovery image, the stock DTB, and the DSP and GPU firmware), and they are not ours to redistribute.
  • Identifiers: recon output is meant for public channels (#aarch64-laptops, linux-arm-msm), but adb, the GSC and the recovery image all expose per-unit identifiers (serials, chip IDs, MACs, pcr0).
  • Remote: the repo is private on the private remote today and may become public.

Decision

  • dumps/ is gitignored. It holds downloads and extractions (recovery zip and image, partitions, stock DTBs, firmware). *.img, *.bin, *.mbn, *.elf, *.dtb and .firecrawl/ are gitignored too.
  • Commit derived facts, not blobs: hashes, sizes, node maps, addresses, keymaps as text, and source URLs.
  • Extraction is scripted from public inputs (scripts/extract-stock-dtb.sh from the public recovery URL), so anyone can reproduce it.
  • Redaction happens before writing, for anything committed: scripts/recon.sh redacts serial numbers, boot ids and MACs. GSC console captures leave out the Ti50 serial, chip ID and pcr0.

Consequences

  • A fresh clone cannot boot anything without running the extraction scripts first.
  • Before any push, check: git ls-files | rg '^dumps/' must be empty, and no serial-like strings in docs/.

Update 2026-11-05, 2:08 PM EST: the public export

The identifier list grows for publication (public-writeup.md, “Build round”; task #39). LAN addresses are redacted at source like serials (commit bf34583: ADR 0012 and two recon notes). Host, user and remote names and the owner’s mail domain stay in this private repo’s text and are substituted when scripts/export-public.sh builds the public tree; that script holds the name list, and site/gate.sh (which ships with the export) holds only the generic classes (MACs, serials, chip IDs, RFC 1918 addresses). Check 61 runs the export and the gate before every commit.

6. Debug console through GSC CCD and a SuzyQ, read-only while CCD is locked

Status

Accepted 2026-10-21.

Context

First boot needs a console that works before display or USB drivers do. Tested 2026-10-21 (ccd-gsc.md):

  • A Fyra Labs SuzyQ in the Googlebook’s left USB-C port exposes the Ti50 as 18d1:5066, with Shell, AP, EC and FPMCU consoles.
  • ccdstate shows UARTAP+TX: the AP UART is bridged both ways while CCD is Locked.
  • The stock and mainline DTs both name uart21 (0x894000) as serial0.
  • Opening CCD (gsctool -a -o plus power-button presses) needs developer mode, and it changes GSC state.

Decision

  • Kernel console: uart21 is the first-boot console: stdout-path = "serial0:115200n8", console=ttyMSM0,115200. the build host reads it through the SuzyQ’s AP console.
  • Read only: agents run the SuzyQ consoles read-only. Before reading, run stty raw -echo on the tty so nothing echoes back. On the GSC shell, agents send only read-only commands (version, ccdstate, help). Never ccd open/unlock/reset, wp, reboot or any flash command.
  • Opening CCD (for a full AP flash backup over raiden_debug_spi) is an owner decision. It happens after unlocking and gets its own ADR.
  • the build host access: the uaccess udev rule /etc/udev/rules.d/70-ti50-ccd.rules (owner choice over joining uucp).

Consequences

  • The left USB-C port is taken by the SuzyQ during bring-up. Boot media and power go through the right port (a hub with PD pass-through) or the spare SSD.
  • If the AP console stays silent under Linux, the fallbacks are ramoops (depthcharge always reserves it) and the panel.

Update 2026-10-21

  • The DTS has the console now: it sets stdout-path = "serial0:115200n8" (commit 709212e).
  • Read-only still applies with our own kernel: the first-boot initramfs prints its captures, then reboots, and has no shell (Initramfs contents and pass criteria).
  • Logs: raw AP-console logs go to dumps/firstboot/.
  • Second channel: when the console is silent, the stock OS’s dropbox SYSTEM_LAST_KMSG holds the failed boot’s last 64 KiB (stock pstore).

7. Local kernel patches live in kernel/patches/ and apply at build time

Status

Accepted 2026-11-03 (owner, “1a 2a 3b”).

Context

  • Until now the first-boot kernel was torvalds a804c57 plus config fragments only (kernel/*.config).
  • The first change that needs kernel source is a panel-edp entry for the mica panel (LGD 0x0804): without it the driver warns and uses fallback timings (panel-edp.c:755-772).
  • The kernel tree lives outside the repo (~/.cache/gbook/linux-a804c57, set up by scripts/setup-toolchain.sh), and the DTS scripts and check.sh read the same tree.

Decision

  • Patches are files in git: kernel/patches/NNNN-<topic>.patch, unified diffs against the pinned commit, each with a header saying what it changes, its source and whether it went upstream. Name order is apply order.
  • scripts/build-kernel.sh applies them before every build: it restores the files the patches touch from the pinned commit, fails if the tree has any other change, then runs git apply on each patch. The patches stay applied after the build, so the kernel release ends in -dirty.
  • check.sh check 20 fails when a patch no longer applies (or reverts) cleanly against the tree.
  • Upstream is a separate decision per patch. The first one (0001, panel-edp) stays local: the EDID gives only part of the model name (“LP134WQ…”), and an upstream entry needs the full name.

Consequences

  • The pinned tree is no longer pristine after a build; git -C ~/.cache/gbook/linux-a804c57 status shows the patched files. Running build-kernel.sh again is safe, and any foreign edit stops it.
  • Moving the pin (ADR 0004) means re-checking every patch; check 20 shows which ones break.

Update 2026-11-04 (10:07 AM ET): a status line on every patch, and numbers are never reused

Owner: “all a” to question 5 of milestones-versioning.md, section 5.

  • Line 1 of every patch reads Status: <WORD> <reference>. The words are ChromiumOS’s patch-tagging words, with LOCAL in place of its CHROMIUM and no BACKPORT (a modified copy says so in its reference):

    • LOCAL <who>: carried downstream and not posted to a list (gbook’s own, or a third party’s downstream fix-up);
    • FROMLIST <list reference>: posted to a list, not in a maintainer tree when checked;
    • FROMGIT <tree> <branch>: in a maintainer or -next tree, not in mainline;
    • UPSTREAM <tag or commit>: in mainline, a backport onto the pin.

    A lore Message-ID goes in the reference when one is known, and the line says “not recorded” otherwise. The free-text header below the line stays. git apply ignores everything before the first diff, so check 20 is unaffected.

  • A patch number is never reused. A deleted patch’s number is retired, and the next patch takes the next unused number. Two numbers were reused before this rule:

    • 0008 first named a GENI I2C timing patch (16a2828) and now names the PCIe GDSC patch;
    • 0009 first named the IRQ-to-CPU-1 diagnostic (cee735d, deleted by ADR 0009) and now names the PCIe PHY GDSC patch.

    The new patches arrived on 2026-11-04 (5272658 at 4:07 AM ET and ddecbd8 at 4:47 AM ET). A log entry older than those commits that says “0008” or “0009” means the old patch.

  • check.sh check 34 enforces both rules. It allows exactly those two old reuses.

8. The board DTS removes the PSCI domain idle states (cluster cl4/cl5, system ss3)

Status

Superseded 2026-11-04 by ADR 0009 (owner round 4): the stall does not need the domain states; the board DTS now drops the per-CPU cluster_c4 instead and keeps the domain states as stock.

Accepted 2026-11-03 (owner, grilling round 2: “yes” to all four). A deliberate deviation from the stock tree (ADR 0004).

Context

  • On mica the CPUCP’s SCMI replies stalled for 0.5 to 1 s on most cold boots, with a timeout now and then. That needed the local arm,max-rx-timeout-ms = <1000> to come up at all (scmi-2026-11-03.md, attempts -20 to -26).
  • A/B boots, all cold, interrupt mode unless noted:
    • Without cluster_cl5 (-27) or without domain_ss3 (-30): both still stalled.
    • No stalls: cpuidle.off=1 (-28), polling with arm,no-completion-irq (-29), and no domain idle states at all (-34). Each was 3 of 3, with replies within 0.1 to 4 ms.
    • With the domain states present: 8 of 11 boots stalled.
  • The wake trace (-32): during a stall, CPU 0, which takes the mailbox interrupt, left the PSCI ret state every ~20 µs without taking any interrupt.
  • The counters (-33): PSCI in OS-initiated mode refused the composite ss3 request about 598000 times in 95 s, from the last CPU of clusters 1 and 2, and genpd retried each time.
  • The stock tree has the same four states and topology (entry00-base.dts: cluster_pd0-2 with cl4 and cl5, system_pd with system_ss3). Stock runs Qualcomm’s own cpuidle and SCMI stacks, so its behaviour does not carry over (inference).

Decision

  • x1e80100-google-mica.dts deletes domain-idle-states from cluster_pd0, cluster_pd1, cluster_pd2 and system_pd. The per-CPU domains keep cluster_c4, so CPUs still enter PSCI retention.
  • The 1000 ms SCMI window stays as a safety net. It costs nothing on a fast boot and is still marked unverified.
  • Polling stays a diagnostic. arm,no-completion-irq is not in the board DTS: the binding describes hardware without a completion interrupt, and mica has one.
  • Upstream waits. A report draft lives in scmi-cpucp-stall-report.md, and sending it is a separate owner decision after the suspend test.

Consequences

  • The clusters and the SoC never power down when idle, so idle power is higher. How much has not been measured.
  • Suspend is untested either way. It is the trigger to revisit this ADR, together with a firmware update or an upstream fix. The finer cut, keeping cl4 and dropping cl5 and ss3, was not tested.
  • Why the firmware refuses the composite states, and how that delays the interrupt to CPU 0, is still open.
  • dts/README.md lists the change under “Changes” as a deviation, citing this ADR.

Update 2026-11-04 (12:26 AM ET): not a complete fix

Attempts 2026-11-04-1 and -2 stalled on this board DTS (max replies 1019.2 ms with 2 timeouts, then 652.0 ms): 2 of 6 cold boots without domain idle states, against 8 of 11 with them. Both stalled boots show CPU 0’s state1 loop (130000-156000 entries by the early read, against 4700-19000 on clean boots) with 0 PSCI refusals, so the stall does not need the composite states (scmi-2026-11-03.md, “ADR 0008 is not enough”). The decision is open again: keep, revert or replace this change. The 1000 ms window keeps every boot working either way.

9. No PSCI cpuidle on mica: the per-CPU domains lose cluster_c4

Status

Accepted 2026-11-04 (owner, grilling round 4: “yes” to all four). Supersedes ADR 0008. A deliberate deviation from the stock tree (ADR 0004).

Context

The evidence is in scmi-2026-11-03.md, attempts -27 to 2026-11-04-3.

  • The symptom: SCMI replies from the CPUCP stalled for 0.5 to 1 s, with an occasional timeout, on most cold boots. The 1000 ms window kept every boot working.
  • No stalls: with plain WFI (cpuidle.off=1, -28) and with polling (arm,no-completion-irq, -29), 0 of 3 each. Polled replies take 0.1 ms, so the CPUCP answers fast.
  • During a stall (-32), the core that takes the reply’s SPI leaves the PSCI ret state (cluster_c4, arm,psci-suspend-param 0x4) about every 20 µs. It takes IPIs but not the SPI, until a timer PPI runs.
  • The stall follows the interrupt’s CPU (2026-11-04-3, kernel/patches/0009 routing SPI 28 to CPU 1): CPU 1 looped (134862 state1 entries) and the reply stalled for 872 ms, while CPU 0 stayed normal.
  • The domain states are not the cause. Removing the cluster and system states (ADR 0008) still left 2 of 6 cold boots stalling. The PSCI refusals of ss3 (-33) ran alongside the stall.
  • Other SPIs stalled too: GENI I2C timeouts on -30, and a loop with no SCMI pending on -32 (inference).
  • Mainline gives no per-CPU way out. irqaffinity= always keeps the boot CPU (kernel/irq/irqdesc.c:36). A CPU without idle states makes cpuidle-psci fail for every CPU (cpuidle-psci.c:399-401, :437-447 at a804c57).

Decision

  • x1e80100-google-mica.dts deletes domain-idle-states (cluster_c4) from cpu_pd0 to cpu_pd11. cpuidle-psci then does not load, and every core idles in WFI, as with cpuidle.off=1.
  • The cluster and system domain states stay as stock. Without cpuidle-psci nothing requests them, so ADR 0008’s deletions are reverted.
  • The 1000 ms SCMI window stays as a safety net and is still marked unverified.
  • kernel/patches/0009 (the IRQ-to-CPU-1 diagnostic) is deleted. The idle-counter and wake-trace tooling stays.
  • The upstream report is rewritten around this mechanism in scmi-cpucp-stall-report.md and is not sent. Sending is a separate owner decision after the suspend test.

Consequences

  • No core enters PSCI retention, and no cluster or the SoC powers down when idle. Idle power is higher; how much has not been measured.
  • s2idle uses cpuidle states, so suspend will not reach PSCI low-power states with this DT (inference). The suspend test is the trigger to revisit, together with a firmware update or an upstream answer.
  • Why a core in ret does not take a pending SPI is open: the GIC’s wake path, or the PSCI firmware. That is the report’s question.

Update 2026-11-04 (12:57 AM ET): confirmed on the board DTS

Attempt 2026-11-04-4 booted this DT (kernel #21). There was no cpuidle driver, SCMI replied within 0.1 ms (78 of 78, 0 timeouts), and cpufreq came up on all three clusters. With -28, plain WFI is 4 of 4 cold boots without a stall.

Update 2026-11-04 (2:31 AM ET): the tally

scripts/attempt.sh tally (scmi-2026-11-03.md, “the counts from attempt.sh tally”): domain states present 7 of 10 stalled against no PSCI cpuidle 0 of 4 (p = 0.035); ADR 0008’s DT 3 of 7 against 0 of 4 (p = 0.21, not significant alone). The decision stands on the mechanism plus the first comparison. More boots on this DT will sharpen the count; record now adds each one to the table.

Update 2026-11-04 (4:40 AM ET): the suspend test, and the tally at ten boots

The suspend test ran: six cold boots, 2026-11-04-5 to -10 (suspend-2026-11-04.md). s2idle entry and the RTC wake work. The resume resets the SoC every time, in the PCIe controllers’ noirq resume: first the PCIe GDSCs (fixed by local patch 0008, from an upstream series), then the NVMe endpoint behind PCIe 6a (link kept) and PCIe 4’s host re-init (link shut down). The failure comes before any idle state matters, so the test neither confirms nor refutes this ADR’s suspend cost (inference). The revisit stays open until a resume completes.

Those boots also added to the count (scripts/attempt.sh tally with group 2 '^2026-11-03-28$|^2026-11-04-([4-9]|10)$'; kernels #21 to #23 and the no-PCIe-6a diagnostic DT, none of which touches the SCMI probe). No PSCI cpuidle: 0 of 10 stalled. Against domain states present, 7 of 10: p = 0.0015. Against ADR 0008’s DT, 3 of 7: p = 0.051.

Update 2026-11-04 (5:01 AM ET): a complete suspend round trip, and the count against ADR 0008

With local patches 0008 and 0009 (the PCIe GDSCs and the PCIe PHY GDSCs to retention), attempt 2026-11-04-11 completed an s2idle round trip on this DT (suspend-2026-11-04.md, newest update). The CPUs had no cpuidle driver, so s2idle ran in WFI. Whether the SoC reached a low-power state, and at what power, is not measured. The revisit trigger is met, and the suspend cost of this ADR is still the open question. Measuring it needs a power reading that is not charge current.

Count: console boot #11 adds one more clean boot to -10. Group 2 is now '^2026-11-03-28$|^2026-11-04-([4-9]|1[01])$': 0 of 12 stalled. That is p = 0.0007 against domain states present (7 of 10), and p = 0.036 against ADR 0008’s DT (3 of 7), now under 0.05.

Update 2026-11-04 (5:13 AM ET): suspend 3 of 3, the count at 14

Two more boots of -11’s image completed the round trip too: 3 of 3 (suspend-2026-11-04.md). All were clean for SCMI. The same group 2 regex now gives 0 of 14 stalled: p = 0.0003 against domain states present, and p = 0.026 against ADR 0008’s DT.

Update 2026-11-04 (10:07 AM ET): when the report is sent

The “Decision” line that made sending scmi-cpucp-stall-report.md an owner decision after the suspend test is superseded by ADR 0010. The report waits for milestone M5, like the binding and DTS. The owner reviews and sends it, and an agent never does.

This ADR’s “patch 0009” is the deleted IRQ-to-CPU-1 diagnostic (cee735d). Today’s kernel/patches/0009 is a different patch, the PCIe PHY GDSCs, added before numbers were retired (ADR 0007 update).

Update 2026-11-04 (11:24 AM ET): the first base image, and the count at 17

Attempt 2026-11-04-12 is the first base image on this DT with patches 0008 and 0009: kernel #26, no command-line or DT extras. It had 3 cold boots, all clean for SCMI. scripts/attempt.sh tally with group 2 '^2026-11-03-28$|^2026-11-04-([4-9]|1[0-2])$' gives boots=17 known=17 stalled=0 clean=17: no PSCI cpuidle has 0 of 17 stalled.

10. Milestones M0 to M7, a feature status table, and the upstream hold ends at M5

Status

Accepted 2026-11-04 (owner: “all a” to the open questions 1, 7 and 9 of milestones-versioning.md, section 5). Supersedes the line in ADR 0009 “Decision” that made sending the stall report an owner decision after the suspend test.

Context

  • The phases and gates (README “Phases and gates”) mark four big steps. Phase 3, from first boot to a daily-drivable install, is a long stretch with no checkpoint in between. Progress was tracked as prose in README “Resume here”.
  • Comparable projects track progress per feature and per stage (research note, section 1):
    • Asahi’s feature-support tables have a column saying where each feature lives (upstream release, downstream, WIP).
    • postmarketOS (now Nura) puts devices into categories with entry criteria.
    • The XPS 13 9345’s first upstream DTS went in with the EC, speakers, mics and camera not working, and it listed what worked and what did not.
  • The kernel’s process documents favour posting early (3.Early-stage.rst, 5.Posting.rst at the pin). A board DTS needs a documented compatible (bindings submitting-patches.rst item 7), must pass dtbs_check with no new warnings, and must pass dt-check-style in relaxed mode (maintainer-soc.rst) (research note, section 3).
  • AI agents must not add Signed-off-by and must never send anything themselves (coding-assistants.rst:34 at the pin). 207 of the repo’s 210 commits had a Claude co-author trailer (git log, 2026-11-04).
  • On 2026-11-04 the owner put upstream sends (the SCMI stall report, patch 0009) on hold “until the project progresses further”.

Decision

  • The milestone ladder. Each rung fits inside the phases and gates:

    MNamePhase / gateDone when
    M0Way back to stockPhase 1, Gate 1recovery media reach the recovery screen and relock is tested (ADR 0001)
    M1First lightPhase 2, Gate 2our Linux version line on the AP console, the panel or the readback
    M2Board on the consolePhase 3every enabled DT node binds; the M2 rows of docs/status.md read works; every other row is works, or partial or parked with a note; one base image has 3 clean cold boots by scripts/attempt.sh tally
    M3Userspace from USBPhase 3a rootfs from USB with Mesa on the GPU and Wi-Fi in userspace; the audio state is known; the first release tag (ADR 0011)
    M4Daily-drivablePhase 3, Gate 3installed on the spare SSD (ADR 0002), with dual-boot or a documented return to stock
    M5Upstream-readyPhase 4 entrythe google,mica binding and the DTS, rebased on the qcom tree, pass dt_binding_check, dtbs_check (no new warnings), relaxed dt-check-style and checkpatch.pl; a Working / Not working list is written; every local patch has its status line; the owner has reviewed and signed off every patch
    M6UpstreamPhase 4, Gate 4the binding and DTS are applied to the qcom tree, then reach a mainline release
    M7In a distribution (optional)after Phase 4a Nura testing device package, or an Ubuntu Concept entry

    The M2 rows are: USB host, Type-C, PCIe enumeration, Wi-Fi, eDP and backlight, GPU probe, EC keyboard, touchpad, TPM, SCMI cpufreq and an s2idle round trip. A base image is one with no debug command-line or DT extras: its scripts/attempt.sh stick first line reads base image.

  • docs/status.md is the feature matrix. It has one row per feature and four state words: works, partial, broken, untested. Untested is the default. Each row says where the feature works (mainline at the pin, a local patch, the board DTS, a config fragment), what upstream has or what gbook would have to send, and the recon note behind it. README “Resume here” points to it. Its prose stays as the owner’s dated record.

  • The upstream hold ends at M5. Nothing goes to a list before then. At M5 the binding and DTS go out first as a [PATCH RFC] series, following the kernel’s own versioning (ADR 0011). The stall report and patch 0009 wait for the same bar. The owner reviews, signs and sends every patch. An agent never adds Signed-off-by and never sends.

  • The research note’s other rider is the owner’s call. It suggests a recon post in #aarch64-laptops before M5 (Phase 1 list in the project log). That post is the owner’s to make, and an agent does not post.

Consequences

  • Progress has a name between the gates: “M2 nearly done” says more than “Phase 3”.
  • docs/status.md has to be kept current. A recon note that changes a feature’s state also changes its row.
  • M5 can run in parallel with M3 and M4, because it is about the binding and DTS, not userspace.
  • What blocks M5 today (inference, research note section 3): the unverified arm,max-rx-timeout-ms, ADR 0009’s deleted idle states, the merge-window pin, and the panel entry that lacks the full model name.

Update 2026-11-04 (11:37 AM ET): parking untested rows

Owner, park round (“yes” to all recommendations). A parked row in docs/status.md keeps its state (untested, partial or broken), and its note says “parked until ”. M2’s third criterion counts a parked row as settled. The first round parked 16 rows:

  • NVMe I/O until the spare SSD (M4);
  • the left port’s USB 3 and DP until a session without the SuzyQ;
  • idle and suspend power until a battery-only session;
  • audio, camera, fingerprint, the rootfs and Mesa until M3;
  • the lid switch, ambient-light sensor and Glowbar until a stock-tree or EC lead;
  • the crypto engine until upstream drops BROKEN.

Three rows stay open for M2: wake from s2idle by a key or the power button, deep suspend, and Bluetooth.

Update 2026-11-04 (12:38 PM ET): M2 reached

All four criteria of M2 are met (docs/status.md, “Milestone M2”):

  • every enabled DT node binds, by the unbound= proxy;
  • the twelve M2 rows work; the backlight was set and read back in 3 of 3 base-image boots;
  • every other row works, is partial, or is parked: Bluetooth works (2026-11-04-14), keyboard wake from s2idle is partial (-15), deep suspend failed and is parked (-16), and 16 rows were parked in the park round;
  • the base image 2026-11-04-12 has 3 clean cold boots (tally).

The next milestone is M3, userspace from USB. Its first base image that passes the release bar gets the first tag (ADR 0011).

Update 2026-11-04 (7:11 PM ET): M3’s technical criteria have evidence; the release is pending

Three ROOT boots on 2026-11-04 (attempts -18 to -20; rootfs-2026-11-04.md): a rootfs from USB with Mesa on the GPU (eglinfo, kmscube, cage and vkcube on the Adreno X1-85), Wi-Fi in userspace (the supplicant and networkd carried the netlog), and the audio state known (aplay -l: no sound card; the attempt is ticket 10). The status rows “Rootfs from USB” and “Mesa (freedreno)” read works. M3 closes with the first release tag (ADR 0011): a snapshot-pinned rootfs build, 3 clean cold boots of one base image, and the owner’s tag drafted by scripts/release-draft.sh.

Update 2026-11-04 (8:39 PM EST): M3 reached, first release v2026.11.04

The release bar was met the same evening: the rootfs rebuilt from snapshot 20261104T000000Z (321 packages, 4b0e2e027c0b2d74…), written to ROOT and read back equal (attempt.sh root-cmd, root-verify), then three clean cold boots of base image 9673d6d782ef4cdb (attempts -21 to -23, tally clean=3 stalled=0; rootfs-2026-11-04.md, “Release boots”). The owner tagged v2026.11.04 from scripts/release-draft.sh’s message and pushed it to the private remote. M3 is passed. The upstream hold stays until M5; the next milestone is M4, daily-drivable (Gate 3).

11. Versions: CalVer tags for released images, the gbook version in uname -r

Status

Accepted 2026-11-04 (owner: “all a” to the open questions 2, 3, 4, 6 and 8 of milestones-versioning.md, section 5). Question 5 (patch status lines) is an update to ADR 0007.

Context

  • Several things change at different rates: the repo (every commit), the boot image (every package-image.sh), the kernel base (a re-pin), the local patches, and the boots themselves. Each boot has an attempt ID YYYY-MM-DD-N in attempts.tsv. The repo has no tags. An image is identified only by its kernel banner (release plus UTS #N).
  • SemVer requires a declared public API (semver.org 2.0.0, item 1), and gbook has none. CalVer suits projects whose releases are driven by external changes (calver.org, “When to use CalVer?”). Here those changes are the mainline base and the milestones.
  • Annotated git tags “are meant for release” (git-tag(1)), and git describe reads only annotated tags by default.
  • scripts/setlocalversion at the pin appends the contents of localversion* files in the object and source dirs to the release. init/Kconfig caps the release at 64 characters.
  • The pin a804c57 is a 7.4 merge-window commit, 1703 commits after v7.3 and before v7.4-rc1 (GitHub compare API, research note section 2).

Decision

  • A release is a base image with 3 clean cold boots. A base image has no debug command-line or DT extras (scripts/attempt.sh stick reads base image). The 3 boots of one image hash are counted by scripts/attempt.sh tally, and the image is one the owner would hand on. Every other image is an experiment, named only by its attempt IDs.

  • A release gets an annotated tag vYYYY.0M.0D[.MICRO] (CalVer, e.g. v2026.11.20; .1 for a second release the same day). The tag goes on the gbook commit that built the image, after its 3 boots. The image must have been built from a clean tree (no + in its uname -r). No SemVer.

  • The tag message is the release note until a tagged image first leaves the build host. From then on, CHANGELOG.md in Keep a Changelog 1.1.0 form is the release note. The message contains:

    • the milestone;
    • the kernel base (a804c57, “v7.3 + 1703, 7.4 merge window”) and the kernel release from uname -r;
    • each local patch with its status line;
    • the image sha256 as scripts/attempt.sh stick prints it;
    • the attempt IDs and the tally line that proved it;
    • what changed since the last tag.
  • uname -r names the gbook state. scripts/build-kernel.sh writes localversion-gbook into the build directory (the O= dir, never the source tree). Its gbook_localversion() gives the exact rules, and check 35 tests them:

    • -gbook- plus git describe against the nearest annotated v[0-9]* tag, without the v (-gbook-2026.11.20-3-g1a2b3c4);
    • -gbook-g<hash> before the first tag;
    • a trailing + when the tree differs from the commit (changed tracked files, or untracked ones that are not ignored).

    Today that reads 7.3.0-gbook-g6934235-ga804c5742bff-dirty. The kernel tree’s own -dirty (the applied patches, ADR 0007) stays. A released image keeps the -gbook-g<hash> it was built with, because the tag comes after its boots. The tag message maps that hash to the version, and a rebuild at the tag would be a new, unproven image. The kernel column of attempts.tsv already records the release, so each attempt names its gbook commit, and no new column is needed.

  • Upstream series use the kernel’s scheme only. That means [PATCH RFC], then [PATCH vN], with the per-version changelog below --- and git format-patch --base (submitting-patches.rst at the pin). A gbook version never appears in a patch subject.

  • The pin moves onto a mainline release tag. The next re-pin is onto v7.4 (or the release current then), after M3 and before M5 in any case (ADR 0010). After that, the pin moves at most once per mainline release. Each re-pin re-checks every local patch (check 20) and the DTS, and the first base image on the new pin needs its own 3 clean cold boots before it can be released.

Consequences

  • No tag exists on 2026-11-04. The image on the stick is a test image (gbook.suspend=1 no_console_suspend), and no base image with patches 0008 and 0009 has 3 clean cold boots yet.
  • Every gbook commit changes the kernel release, so a rebuild relinks the version objects. The UTS #N keeps counting builds as before.
  • The release grows by about 20 to 33 characters (worst case -gbook-2026.11.04.1-12-g1234567+), which stays under the 64-character cap.
  • A tool to cut a release (check the tally, compose the tag message) is not needed until M3. It is left for then.
  • Verified on 2026-11-04: build #25 (kernel Image 52734464 bytes) reads 7.3.0-gbook-g8493ab4+-ga804c5742bff-dirty in include/config/kernel.release, and scripts/image_uts.sh reads the same release from the Image banner. The + was an uncommitted log edit at build time.
  • The v7.4 re-pin replaces patches 0002 to 0007. Their series has a v2 (2026-10-30) that drops 0003 and adds four patches, and torvalds master’s cf27afc3bd6e (“drm/msm: default separate_gpu_kms to auto selection”) conflicts with 0002, 0003 and 0005 to 0007 (patch-status lookup, 2026-11-04). The status lines of ADR 0007’s update record both.

Update 2026-11-04 (2:03 PM ET): a release names the rootfs too

Owner, M3 round (“yes” to question 10 of m3-rootfs.md). From M3 on, a release is two artifacts: the KERN-A image and the rootfs image (ADR 0012). The tag message adds the rootfs image’s sha256, its snapshot.debian.org timestamp and the hash of its lock list, next to the KERN-A image hash that scripts/attempt.sh stick prints.

Update 2026-11-04 (8:39 PM EST): the first release, v2026.11.04

Tagged by the owner from scripts/release-draft.sh M3 '^2026-11-04-2[123]$' at commit 506159b: kernel 7.3.0-gbook-g82c43ed-ga804c5742bff-dirty (patches 0001 to 0009), KERN-A image 9673d6d782ef4cdb, rootfs 4b0e2e027c0b2d74… from snapshot 20261104T000000Z (lock 12f96fa08768262e…, 321 packages), 3 clean cold boots. Two things the first real run taught the tool: the Attempts line must come from attempts.tsv (tally lists every id on one line), and git log -n 40 rather than | head, which ended the script by SIGPIPE under pipefail before the owner’s tag line. The tag also records the rootfs read back from the stick (attempt.sh root-verify, retro-9/01): release-draft.sh refuses a stick whose ROOT is not the manifest’s rootfs.

Update 2026-11-05 (9:54 AM EST): the second release, v2026.11.05

Tagged by the owner from scripts/release-draft.sh M3 '^2026-11-05-5$' at commit 69edd54: kernel 7.3.0-gbook-2026.11.04-35-g7c6a1aa-ga804c5742bff-dirty (patches 0001 to 0011; the two new ones, 0010 and 0011, keep the four speakers through the SoundWire masters’ runtime PM wakes), KERN-A image bc62c69ecf560525 (the sound node in the board DTS, no extras), a rootfs from the same snapshot and lock as v2026.11.04‘s (20261104T000000Z, lock 12f96fa08768262e…, 321 packages), rebuilt 2026-11-05 as 6fea12f2… (the audio group, the m3-swrm capture; the udev rule tried that night never shipped), 3 clean cold boots (attempt 2026-11-05-5), the four spoken channel names heard on each boot and the microphones’ capture heard played back. The AP console was absent for the three boots (the SuzyQ was unplugged on the build host’s side); the bar’s evidence is the netlog, which attempt.sh tally reads. The draft ran once before the final docs commit and again on it, so the tag file names the tagged commit: run it last.

12. M3 rootfs: Debian sid from a snapshot, on the stick’s partition 2, entered by switch_root

Status

Accepted 2026-11-04 (owner: “yes” to the ten open questions of m3-rootfs.md, section 5). Nothing in it has run on mica yet; section 4 of the note is inference until the first rootfs boot.

Context

  • M3 (ADR 0010) needs a rootfs from USB with Mesa on the GPU and Wi-Fi in userspace, a known audio state, and the first release tag (ADR 0011).
  • Mesa does not separate the candidates: X1-85 support landed in Mesa 24.2 (MR !30249, merged 2024-07-18), and every candidate ships 26.x. Ghostty 1.4 arm64 exists only in Omarchy’s aarch64 repo (research note, sections 1-2).
  • The packaging already runs in a digest-pinned Debian sid container, and Debian can be pinned to one snapshot.debian.org timestamp. Omarchy aarch64 has no dated pin, and its install path is UEFI-only.
  • Everything the kernel needs is built in, and uname -r changes with every gbook commit (ADR 0011).

Decision

  1. Debian sid arm64, built by mmdebstrap in the pinned container from one snapshot.debian.org timestamp, with a lock list and a local .deb cache. Omarchy aarch64 (Arch Linux ARM base) is revisited at M4.
  2. Ghostty 1.4.0 is built from its signed source tarball (Zig, as Omarchy’s PKGBUILD does). If that fails, M3 goes without Ghostty until M4.
  3. ROOT is partition 2 of the same stick, from 256 MiB on. Every image’s GPT declares it, so the per-attempt 256 MiB dd, verify and gpt stay as they are. The owner writes ROOT only when the rootfs changes.
  4. /init finds ROOT next to the booted kernel partition: the partition whose PARTUUID equals kern_guid, its disk’s partition 2, which must be labelled gbook-root. It mounts it, then switch_roots. No ROOT, or a failed mount, runs today’s capture-and-reboot path. The command line does not change.
  5. The Wi-Fi network block and the stock ADSP/CDSP firmware stay in the initramfs. /init copies them to the new root’s /run/gbook/ before the hand-off. The rootfs image carries no secret, no vendor blob and no identifier (ADR 0005).
  6. Every driver stays built in. New needs get kernel/*.config fragments. The rootfs ships no /lib/modules. kernel/mica-rootfs.config turns FW_LOADER_USER_HELPER off, as systemd’s README asks.
  7. Audio gets one test attempt: the 9345’s sound node as a dts/diag/ overlay (unverified per ADR 0004), a kernel/mica-audio.config, and Debian’s 9345 topology and UCM. Either outcome is the “known state”.
  8. No getty on the AP console, no sshd, nothing listening. The panel runs Ghostty under cage as an unprivileged user.
  9. The container build tries mmdebstrap’s unshare or fakechroot mode first, and --cap-add SYS_ADMIN only if those fail.
  10. A release names both images. See the ADR 0011 update of the same date.

Consequences

  • The rootfs keeps the netlog format and listener: one shared sender script serves /init and the rootfs. A base rootfs sends after its checks and then stays up.
  • The first rootfs boot must check two assumptions: that firmware lookups after switch_root resolve against the new root, and that the GPU’s first open there finds gen70500_*. If either fails, the stock files or the command line change, and this ADR gets an update.
  • wpa_supplicant@wlan0 needs a drop-in to read /run/gbook/.
  • The work is tracked in .scratch/m3-rootfs/.

Update 2026-11-04 (3:08 PM ET): how decision 9 came out

scripts/build-rootfs.sh runs the pinned container rootless with podman (retro-8/05), not through pkexec docker. Inside it, mmdebstrap’s own unshare mode does not apply: as container root it skips the user namespace, and container root cannot mount without SYS_ADMIN (probe, 2026-11-04). The script makes the same namespace by hand (unshare --map-root-user --map-auto --mount --pid --fork, a subuid range for root) and runs mmdebstrap’s root mode inside it; podman’s --security-opt unmask=ALL lets that namespace mount /proc (“Mount too revealing” otherwise). The arm64 essential set built this way in 32 s under qemu binfmt. fakechroot was not tried: mmdebstrap(1) says the mode fails with a different libc inside the chroot than outside, which a foreign-architecture chroot is. GBOOK_ROOTFS_MODE=sysadmin keeps the third option (--cap-add SYS_ADMIN, a capability inside podman’s user namespace only, never on the host).

Update 2026-11-04 (3:52 PM ET): the reproducibility contract, and throttling

From rootfs-pinning.md (recommendations 1, 4 and 7), after the first builds of ticket 03:

  • Two builds of one snapshot timestamp must agree on lock.txt and the rootfs.tar sha256. The rootfs.ext4 sha256 is expected to agree only between builds with the same e2fsprogs version, which the manifest records: the container installs its tools from live sid at build time. The first two builds agreed on all three.
  • The answer to snapshot.debian.org’s throttling is the .deb cache plus an offline flat repository (GBOOK_ROOTFS_OFFLINE=1, pinned to the previous lock.txt), not a mirror switch: Debian publishes no limit figures, snapshot-cloudflare.debian.org fronts the same backend (live headers, inference), and no third-party snapshot mirror resolved on 2026-11-04. A build on another mirror (GBOOK_ROOTFS_MIRROR) says snapshot=none in its manifest and is not release material (ADR 0011).

Update 2026-11-04 (6:38 PM ET): decisions 5 and 8 change, sshd for the build host

Owner (“yes” to the ranked options after the second ROOT boot; rootfs-access.md, recommendations 1 and 2). The rootfs runs sshd, key-only, for the build host alone (AllowUsers gbook@<the build host's LAN address>, PermitRootLogin no, no passwords), and gbook has sudo for the agent’s reads and unit restarts; writes on the device still need the owner’s word, as everywhere. The image stays free of keys: the host key and the build host’s authorized key travel in the initramfs (~/.local/state/gbook/ssh, packaged like the Wi-Fi block) and /init copies them to /run/gbook/; ssh.service has ConditionPathExists on the host key, Debian’s baked host keys are removed from the image, and sshd-keygen is masked (an empty machine-id is no first boot, so it would never run, and must not write keys to ROOT). The AP console stays read-only (ADR 0006); the gettys stay masked. The boot journal also travels as a section of the rootfs’s netlog, so a Wi-Fi failure still leaves the journal on the stick and a working boot sends it.

Research notes

Where the evidence comes from and how to read each source (blocked sites and their mirrors, the APIs that work): sources.md.

One file per topic, each claim with its source and the date it was read.

Done:

  • chromeos-boot-path.md (2026-10-21): how Linux can boot through mica’s depthcharge, fastboot, CCD, recovery image

  • stock-dtb.md (2026-10-21): the stock DT extracted from the recovery image, its hashes and board devices

  • ccd-gsc.md (2026-10-21): SuzyQ on the left port, console map, GSC version and ccdstate, EC log

  • 9345-dts-diff.md (2026-10-21): node-by-node against mainline 9345; AP console uart21; what to copy and what to replace

  • el2-handoff.md (2026-10-21): depthcharge enters the kernel at EL2 with no Gunyah; build the mica DTB with x1-el2.dtso

  • fit-packaging.md (2026-10-21): mkdepthcharge FIT + dev-key signing for mica, 256 MiB limit, cmdline, cgpt attributes, FIT matching, offline checks, tools on Arch

  • console-kernel-config.md (2026-10-21): built-in drivers and boot arguments for a console-only first boot on uart21, earlycon limits, Image size, jhovold/jglathe/Ubuntu Concept configs

  • silent-boot-observation.md (2026-10-21): if the AP console is silent: depthcharge ramoops and its readback, the coreboot framebuffer, EC console, firmware log, LED codes, ccdstate

  • scmi-cpucp-timeouts.md (2026-11-03): intermittent SCMI timed out in resp and scmi-cpufreq -110 at boot; upstream fixes (all in the pin), the FastChannel quirk hazard on 0x0 firmware version, arm,max-rx-timeout-ms, measure-first plan

  • milestones-versioning.md (2026-11-04): a milestone ladder M0-M7 over the gates, CalVer image tags, kernel [PATCH vN] rules and patch status words, when the upstream hold could end (AI agents may not add Signed-off-by); 9 open questions for the owner

  • m3-rootfs.md (2026-11-04): which rootfs for M3 (Debian sid, Arch Linux ARM + Omarchy, Ubuntu, Fedora, postmarketOS: Mesa for the X1-85, Ghostty 1.4, our kernel, pinning) and how it boots: ROOT on the same stick, /init finds it by kern_guid, switch_root; 10 open questions for the owner

  • debug-tools.md (2026-11-03): the per-boot debug tools: SCMI and I2C trace events, dyndbg, what each boot carries and how attempt.sh trace reads it

  • rootfs-pinning.md (2026-11-04): pinning and reproducing the M3 rootfs when snapshot.debian.org throttles

  • rootfs-access.md (2026-11-04): reaching the booted rootfs from the build host without a secret in the image (keys in the initramfs, journal upload, ssh)

  • asoc-debug-tools.md (2026-11-04): ASoC, SoundWire and audioreach debugging tools; the downstream driver’s enumerator handling (ticket 16)

  • scmi-cpucp-stall-report.md (2026-11-04): DRAFT report for linux-arm-msm on the PSCI-retention stall, not sent (M5 hold)

  • steamos-arm.md (2026-11-05): what Valve published for the Steam Frame (no ARM SteamOS tree; package repos, repair image, the Deckard DTS, kernel commits through GitHub), five adoption candidates and the owner’s decisions

  • m4-recovery-matrix.md (2026-11-05): Valve’s five recovery rows mapped onto mica’s vboot and recovery-stick mechanisms, each labelled proven or inference; the three owner probes that close M4’s return to stock; the slot-policy question

  • public-writeup.md (2026-11-05): publishing gbook as a guide: the repo inventory and identifier exposure, Nura and aarch64-laptops conventions, Diátaxis, licences, venues with versions, a redaction gate; finds Anton Bambura’s public Nura port of mica (DTS 2026-10-19, same compatible)

  • cloudflare.md (2026-11-05): which Cloudflare products fit the chosen GitHub Pages setup: a DNS-only CNAME and the challenge TXT now, Web Analytics by snippet, R2 or GitHub Releases for the artefacts at the first release that has them, token permission names; Pages, Workers, Turnstile, Tunnel and rate limiting skipped with reasons; 6 open questions for the owner

Reading upstream (learned 2026-11-03, scmi-cpucp-timeouts.md):

  • lore.kernel.org, patchwork and git.kernel.org sit behind a bot check (Anubis). Read threads on lkml.rescloud.iu.edu or lists.infradead.org/pipermail, keep the lore Message-ID, and read torvalds master on the GitHub mirror.
  • The owner’s web clippings in ~/code/obsidian/the build host/Clippings/ may already hold the thread (the X1E CPUFreq series was there).

Queued:

  • fcc-p212g.md: FCC filings for regulatory model P212G (internal photos, chip markings, SSD placement)

Sources: where the evidence comes from, and how to read it

Compiled 2026-11-04, 1:59 PM ET from the research and recon notes, and from the 2026-11-04 session’s lookups. This is an index: each claim lives in the note that cites it (last column), and a new finding goes there, not here. Primary sources only. News coverage of the Googlebook exists in recon notes, but it is never the authority for a claim (CLAUDE.md, “Docs conventions”).

How to read them

SourceWorksBlocked or flaky, and the way round
Kernel source and docs at the pinthe local tree ~/.cache/gbook/linux-a804c57 (cite file:line); github.com/torvalds/linux at a804c5742bff; the GitHub compare API for “is X after the pin”git.kernel.org sits behind a bot check (Anubis)
LKML threadslkml.rescloud.iu.edu and lists.infradead.org pipermail; keep the lore Message-IDlore.kernel.org sits behind Anubis (README, “Reading upstream”)
Patch state (series, versions, review)the patchwork REST API: https://patchwork.kernel.org/api/series/<id>/, /api/patches/?msgid=<id> (read 2026-11-04 for 0002-0006 and 0008)the patchwork web UI sits behind Anubis
Mesathe GitLab API: https://gitlab.freedesktop.org/api/v4/projects/176/merge_requests/<n>; release notes at docs.mesa3d.org/relnotes
Debian packagesqa.debian.org/madison.php, sources.debian.org API, snapshot.debian.org and its API file, metasnap.debian.net; manpages.debian.org and raw files on salsa.debian.org (apt, devscripts)snapshot.debian.org answered 503/504 three times on 2026-11-04 (m3-rootfs); keep a local .deb cache. At 3:33 PM ET [20:33 UTC] the same day snapshot.debian.org answered and snapshot-cloudflare.debian.org gave 503 through the same Varnish/Fastly chain (rootfs-pinning). Debian publishes no limit figures; snapshot.debian.org/news/ and /doc/api/ are 404 (the front page and the salsa API file hold that content)
GNU manualsgnu.org/software/tar/manualanswered 429 three times on 2026-11-04; tar --help on the build host gives the option semantics (rootfs-pinning)
Arch and Omarchy packagespacman -Si / pacman -Qi live on the build host; pkgs.omarchy.org (aarch64); omacom/omarchy-pkgs PKGBUILDsno dated archive for Arch Linux ARM (m3-rootfs)
GitHub repos and releasesgh api repos/<o>/<r>/releases/latest, gh api repos/<o>/<r> --jq .license.spdx_id
Busybox in the initramfsDebian’s busybox config: salsa.debian.org installer-team/busybox, debian/config/pkg/static
Valve’s SteamOS ARM (Steam Frame) sources (2026-11-05 lookups)gitlab.steamos.cloud through its API: https://gitlab.steamos.cloud/api/v4/projects/<id>/repository/{tree,files/<path>/raw,commits} (public projects, no token); the package servers’ directory listings and pacman databases: holo-packages.steamos.cloud/archlinux-deckard/archlinux/main/{core,extra}/os/aarch64/*.db.tar.xz and .files.tar.xz, archlinux-deckard-hotfixes/ (Valve’s own packages, one repo per merge request); steamdeck-images.steamos.cloud/recovery/ (the Frame repair image and the QDL package, 4 GB each, not downloaded); release-note bodies from the Steam news API api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=4165890&maxlength=0 (the store pages render by JS); Valve’s private kernel tree’s commits resolve through GitHub’s fork network: gh api repos/torvalds/linux/commits/<sha> and compare/v7.2...<sha>; partner.steamgames.com pages through firecrawlGitLab blob search needs a token (401); the package servers’ roots answer 401 (go straight to the repo paths); steamdeck-packages is x86_64 only; GitHub’s search/commits?q=hash: does not find fork-network commits; the downloaded atomupd and custom packages carry a CI credential and Valve’s RAUC certificate, so nothing from them goes into the repo (gitleaks passed on the note)
Nura (postmarketOS) and its GitLab (2026-11-05 lookups)gitlab.postmarketos.org redirects (308) to gitlab.nura.eco; pmaports is project 226 there: https://gitlab.nura.eco/api/v4/projects/226/merge_requests/<n>, /repository/files/<path>/raw?ref=; the wiki pages (wiki.postmarketos.org) by title; Anton Bambura’s kernel tree on gitlab.com: https://gitlab.com/api/v4/projects/jenneron%2Flinux/repository/commits/<sha> and /files/<path>/raw?ref=<sha>projects/postmarketOS%2Fpmaports answers a 301 to the id; use the id
The Googlebook itselflive reads through the gbook skill (adb, SuzyQ consoles), dated in docs/recon/

By topic

TopicPrimary sourcesCited in
Boot chain: depthcharge, FIT, vboot, GPTchromium.googlesource.com, chromium.org developer library, depthcharge-toolschromeos-boot-path, fit-packaging, el2-handoff
Recovery image, stock OSdl.google.com/device/recovery, support.google.com/googlebook, source.android.comstock-dtb, ADR 0001
Hardware and sibling boardsDell XPS 13 9345 manuals, mainline x1e80100-dell-xps13-9345.dts, Linaro arm64-laptops, aarch64-laptops, jglathe/linux_ms_dev_kit, dwhinham/linux-surface-pro-119345-dts-diff, console-kernel-config
SCMI, CPUCP, idlethe kernel source at the pin; LKML threads by Message-ID (Johan Hovold, Sibi Sankar)scmi-cpucp-timeouts, scmi-cpucp-stall-report, ADR 0009
Qualcomm downstream audio drivers (CodeLinaro audio-kernel-ar)the GitLab API: https://git.codelinaro.org/api/v4/projects/clo%2Fla%2Fplatform%2Fvendor%2Fqcom%2Fopensource%2Faudio-kernel-ar/repository/files/soc%2F<file>/raw?ref=audio-kernel.lnx.6.0.r67-rel (/repository/tree?path=soc&ref=… lists a folder); save downloads in their own scratch folder and read them with sed -n or rtk rgthe -/raw/ web URLs answered 429 on 2026-11-05
Is there an upstream fix for a driver (quick read)patchwork’s search: https://patchwork.kernel.org/api/patches/?q=soundwire%3A+qcom&order=-date&per_page=40 (/api/patches/<id>/ holds content and diff); a file’s torvalds history: gh api 'repos/torvalds/linux/commits?path=drivers/soundwire/qcom.c&per_page=10' (read 2026-11-05 for patch 0010)both miss a fix that is only in a maintainer tree’s next branch (git.kernel.org sits behind Anubis)
Local patches’ upstream statepatchwork API; omacom/omarchy-pkgs linux-omarchy-aarch64line 1 of each kernel/patches/*.patch, ADR 0007
Kernel process, versions, milestonesDocumentation/process/* at the pin (submitting-patches.rst, coding-assistants.rst, maintainer-soc.rst); semver.org, calver.org, keepachangelog.com; Asahi feature support; Nura (postmarketOS) docsmilestones-versioning, ADR 0010, ADR 0011
Rootfs, Mesa, userspace (M3)Mesa MRs and release notes; Debian, Ubuntu and Fedora package pages; ghostty.org docs, Ghostty release tarballs; Omarchy’s aarch64 repom3-rootfs
Pinned, reproducible rootfs builds (M3)snapshot.debian.org and metasnap; the Debian man pages for mmdebstrap, debsnap, debootsnap, debrebuild, apt, dpkg-query, dpkg-scanpackages, apt-ftparchive, diffoscope, mke2fs; the mmdebstrap script; apt and devscripts source on salsa; tytso/e2fsprogs source and release notes; reproducible-builds.org (SOURCE_DATE_EPOCH spec, system images), reproduce.debian.net; Isar, Debusine, live-build, debos, ELBE and meta-debian docs; the isar-users listrootfs-pinning
Reaching the booted rootfs (M3)the build host’s man pages for openssh 10.6p1 and systemd 262.1 (the sid arm64 versions; Debian’s copies at manpages.debian.org/unstable): sshd_config, sshd, ssh_config, ssh, ssh-keygen, systemd-journal-upload, systemd-journal-remote, journal-upload.conf, journal-remote.conf, journalctl, journald.conf, machine-id, systemd.unit, systemd.socket, systemd-ssh-generator, systemd.system-credentials; Debian packaging on salsa (ssh-team/openssh debian/systemd/*, openssh-server.postinst, rules, patches/debian-config.patch; systemd-team/systemd debian/systemd.postinst); systemd/systemd at v262 (src/ssh-generator/ssh-generator.c, src/core/main.c, src/core/manager.c, src/basic/initrd-util.c) and v259 (src/core/import-creds.c); the mmdebstrap script (cleanup list); madison and the packages.debian.org file lists; the pinned kernel tree (Documentation/usb/gadget_configfs.rst, gadget-testing.rst, bindings/usb/usb-drd.yaml, drivers/usb/dwc3/, x1e001de-devkit.dts) and the built .config; live reads on the build host (ss -ltn, systemctl cat, modinfo)rootfs-access
Terminal tools on the build host (2026-11-04 lookups)TerminalTextEffects and its docs; Omarchy’s ttfx (omacom/ttfx, pacman -Qi ttfx); gum with colorprofile (NO_COLOR handling); no-color.org; fastfetch; Ghostty about / libghostty; Quickshell and quickshell.orgthis file; the decisions are in the running log (the follow colour guard, check 36)
SteamOS on ARM, the Steam Frame (ideas for M4 and the rootfs)Valve’s GitHub and gitlab.steamos.cloud projects, the package servers (pacman databases, ten packages read), the mainline sm8650-valve-deckard.dts, Valve’s kernel commits through GitHub, Steamworks partner docs and Steam Support pages, the Steam news API, Mesa and FEX upstreamsteamos-arm.md
M4 return to stock, slot policy (planning)the repo’s own notes at firmware-bluey-16702.B (fit-packaging.md section 5, chromeos-boot-path.md sections 2 to 6), ADRs 0001 to 0003 and 0010, the first-boot runbook, the unlock-day ticket, the project log’s relock and stick-tries rowsm4-recovery-matrix.md
Publishing the project (guide, site, wiki)the repo’s own docs; Nura wiki device-page conventions and pmaports; aarch64-laptops, jhovold and jglathe repos; diataxis.fr; adr.github.io (MADR); creativecommons.org; each tool’s docs and release list (mdBook, MkDocs, Zola, Hugo, Quartz, Wiki.js, GitHub Pages, Forgejo, Codeberg)public-writeup.md
Closed Case Debugging, the SuzyQ consolesthe GSC’s own version and ccdstate reads on the build host, the EC log, ChromiumOS CCD documentationccd-gsc.md
A silent first boot: ramoops, the firmware framebuffer, EC console, LED codesdepthcharge and coreboot source at their pins, AOSP, the stock DTsilent-boot-observation.md
Per-boot debug tools: SCMI and I2C trace, dyndbg, attempt.sh tracethe kernel tree at the pin (Documentation/trace/, the drivers’ trace points)debug-tools.md
ASoC, SoundWire and audioreach debuggingthe kernel tree at the pin (sound/soc/, drivers/soundwire/), CodeLinaro’s downstream audio-kernel-arasoc-debug-tools.md
Cloudflare for the public site and release artefacts (2026-11-05 lookups)developers.cloudflare.com: every page also serves plain Markdown at <page>/index.md, and each product has an index at <product>/llms.txt (/usr/bin/curl into a scratch folder, then rtk rg); the API reference pages under /api/resources/ name the permission groups each endpoint accepts; the Cloudflare blog for dated announcements (2025-04-08 on Pages and Workers, 2020-12-09 on Web Analytics); cloudflare.com/plans/sase-zero-trust for the Zero Trust seats; docs.github.com for the Pages custom-domain, HTTPS, verification, limits and releases pagescloudflare.md

Keeping it current

  • A new research or recon note that relies on a new kind of source adds one row here, in the same commit.
  • A source that becomes blocked, or a new way round, is recorded in “How to read them”, with the date.

How Linux can boot on mica’s depthcharge firmware

Researched 2026-10-21 from firmware source, Google’s docs and a live read of the device. Nothing was changed on the device.

Answer

The Googlebook boots through ChromeOS firmware: coreboot plus the depthcharge bootloader, with Android boot support added on top. Qualcomm ABL is not involved. That changes the plan:

  1. “Unlock bootloader” is ChromeOS developer mode with new labels. Once unlocked, the firmware boots from external drives and accepts self-signed kernels by default. No crossystem step is needed.
  2. The best first-boot route is a ChromeOS kernel partition on a USB drive. It holds a FIT image (kernel plus device trees), signed with the public developer keys. postmarketOS and depthcharge-tools already boot Qualcomm Chromebooks this way. The stock drive stays untouched.
  3. Firmware fastboot has no boot command, so the planned fastboot boot RAM-boot test cannot work. Fastboot is also reachable only through a bridge cable (ALink or Prolific PL27A1) or over TCP via a USB-Ethernet adapter. That explains the “specialized cables” report.
  4. Nobody has a device tree for mica. Mainline has hamoa/x1e80100 boards, including the XPS 13 9345, but no Chrome-firmware X1 board. Writing that device tree is the core work, as expected.
  5. Closed Case Debugging exists. GSC consoles are readable while CCD stays locked, so a SuzyQ should give EC (and possibly AP) logs before anything is unlocked.

Items marked inference below are reasoned from source, not tested on the device.

Sources and revisions

RepoRefCommit
depthchargefirmware-bluey-16702.B694eabf646855067acd51935059c3191436ccc79
depthchargemain (fastboot check only)e91770fa63a431c6a84ff59387a41f7e19222919
vboot_referencefirmware-bluey-16702.B34fbbe62cc1832016e956d55d72b4aba606b7e21
ChromiumOS corebootfirmware-bluey-16702.Bff3258d575b0ddf4ba0514a2cd03c6a1936dba69
ChromiumOS corebootmain80ab88b8322882ce45c18211c21404500d6f689d
ChromiumOS ECfirmware-bluey-16702.B-main19b76df3d2722a371141bbac61bf14be79f7a44e
ChromiumOS EC (CCD docs)cr50_stabffacd3c99e6a9090a61439d58d46099f0b5ed09e
chromiumos-overlayfirmware-bluey-16702.B04eaf2635e1fc4533a7ebf32601aa09a4958affb
Linuxtorvalds mastera804c5742bff224d4cb4d25dd383554d598be32b
pmaports / boot-deploy / pmbootstrapmain / 0.25.0 / 3.12.1a683af41… / a50452a0… / 7d23010a…
depthcharge-toolsv0.6.2e62d095bc61a371b69587ba2773e10a5f2e87308

Correction 2026-10-21: e62d095 is not v0.6.2. It is the main head, 107 commits after the tag (git ls-remote reads refs/tags/v0.6.2^{} = 95d17c444ae3a9dec1c51ead6301ed04777314b9). The citations below point at e62d095. Found by the FIT packaging research (docs/research/fit-packaging.md on branch research/fit-packaging).

Repos: https://chromium.googlesource.com/chromiumos/platform/depthcharge, …/platform/vboot_reference, …/third_party/coreboot, …/platform/ec, https://github.com/alpernebbi/depthcharge-tools, https://gitlab.postmarketos.org/postmarketOS/pmaports. These are branch heads read on 2026-10-21. The exact commits behind the device’s Google_Mica.16702.59.0/Google_Mica.16702.143.0 build are not public. Local copies are in .firecrawl/ (gitignored).

1. What mica is

FactEvidence
mica is a variant of the bluey baseboardcoreboot src/mainboard/google/bluey/Kconfig.name: config BOARD_GOOGLE_MICA "-> Mica". First upstream commit a1048f60938f (CL 90951, merged 2026-01-31)
SoC family Hamoa = X1E80100Kconfig line 140 (16702.B): select SOC_QUALCOMM_HAMOA. Linux 8c0b058ab598 renames x1e80100 to hamoa. Live read: soc_id 555, X1E80100, 12 CPUs
32 MB AP flash, read over QSPIBOARD_ROMSIZE_KB_32768. Matches Dell’s “32 MB flash EPROM”
USB-C retimer PS8820 (the 9345 uses PS8830)select MAINBOARD_HAS_PS8820_RETIMER
Camera and fingerprint reader on USBMAINBOARD_HAS_CAMERA_VIA_USB, MAINBOARD_HAS_FINGERPRINT_VIA_USB
USB-C debug accessory (CCD) portHAVE_DEBUG_ACCESS_PORT_SOURCE_SINK, DAP_USE_SMB1. Which physical port is unknown
EC: Zephyr on a Nuvoton NPCX9M7FB, partly privateEC zephyr/program/bluey/BUILD.py: register_npcx9_project(project_name="mica", …, extra_modules=["google-private", …])
GSC: Ti50MAINBOARD_HAS_GOOGLE_TPM selects TPM_GOOGLE_TI50. depthcharge CONFIG_DRIVER_TPM_GOOGLE_TI50=y
depthcharge boardconfigs/config.mica: BOARD_DIR="bluey", KERNEL_FIT=y, KERNEL_START=0xC7800000, KERNEL_SIZE=0x10000000, KERNEL_FIT_FDT_ADDR=0xDA800000, BOOTCONFIG=y, ANDROID_PVMFW=y, DRIVER_EC_CROS_SPI=y

config.mica also sets DRIVER_SOC_X1P42100=y. That is the name of the depthcharge SoC driver for the whole X1 family. coreboot builds mica as Hamoa, and the device reports X1E80100.

Internal disk, live read 2026-10-21 (recon): ChromeOS KERN-A, KERN-B and ROOT-B sit next to Android A/B partitions: boot, init_boot, vendor_boot, dtb, dtbo, vbmeta, pvmfw and super.

2. Unlock = developer mode

  • The menu item. depthcharge src/vboot/ui/screens.c:765 (16702.B): the Advanced options item .name = "Unlock bootloader" targets UI_SCREEN_RECOVERY_TO_DEV (“Transition to developer mode”). Once unlocked, the screen reads “Bootloader is unlocked”. Its menu offers Lock bootloader, Boot from internal disk, Boot from external drive (:1801), Select alternate bootloader and Advanced options (which holds “Bootloader fastboot”).
  • The OEM unlocking toggle. Android’s switch in Developer options maps to the NV bit VB2_NV_OEM_LOCK. While that bit is set, vboot withholds DEV_BOOT_ALLOWED (vboot firmware/2lib/2misc.c ~600-613). The firmware then shows “Booting with an unlocked bootloader is disabled… enable OEM unlocking in Developer options”. This matches Google’s steps: turn on OEM unlocking, then Esc + F2 + Power, then Advanced options > Unlock bootloader.
  • Defaults are already set. vboot 2misc.c:296-303: while in normal mode, the firmware presets DEV_BOOT_EXTERNAL=1 and DEV_ENABLE_FASTBOOT=1, and clears DEV_BOOT_SIGNED_ONLY. That is the trogdor crossystem dev_boot_usb=1 dev_boot_signed_only=0 step, done ahead of time. This matters because Googlebook OS has no crossystem (live read: not on PATH).
  • Self-signed kernels boot. vb2_need_kernel_verification is false in dev mode unless FWMP or dev_boot_signed_only says otherwise (2misc.c ~821-837). On the ChromeOS path, a keyblock whose signature fails is still accepted on its hash (2load_kernel.c ~163-253). The body must still match the keyblock’s own key, so the kernel must be self-signed, not unsigned. The public dev keys (/usr/share/vboot/devkeys) work.

3. Boot routes after unlocking

vboot treats a GPT entry as bootable when its type is the ChromeOS kernel GUID fe3a2a5d-4f32-41a7-b725-accc3285a309 or the Android vbmeta GUID (cgptlib_internal.c, 2load_kernel.c:696-733). For ChromeOS entries, depthcharge loads the body to KERNEL_START and boots it as a FIT (src/arch/arm/fit.c:57-68).

FIT image rules (verified in source):

  • Compression: none, lzma, lz4 or legacy-lz4 only (src/boot/fit.c:291-311). The arm64 Image magic is checked (boot64.c:188), so a gzip Image.gz fails.
  • Device-tree choice: depthcharge ranks each FIT configuration’s compatible against google,mica-rev<board>-sku<sku>, then google,mica-rev<board>, google,mica-sku<sku> and google,mica (fit.c:49-102, name from coreboot’s MAINBOARD_PART_NUMBER “Mica”). With no match it boots /configurations/default. The ordering is documented in the kernel’s Documentation/arch/arm/google/chromebook-boot-flow.rst.
  • What depthcharge writes into the DT:
    • /chosen: bootargs, kaslr-seed and rng-seed.
    • Memory: replaces every memory node with one /memory from coreboot’s map and adds /reserved-memory regions (dt_update.c).
    • Ramdisk: linux,initrd-start/end.
    • /firmware/coreboot (with board-id, sku-id, fw-config), /firmware/chromeos, ramoops, LPDDR info and the pKVM pvmfw and DRNG reservations.
    • bluey’s cpumap_fixup.c also marks defective CPU cores status = "fail" based on SoC ID.

Tooling: depthcharge-tools mkdepthcharge wraps mkimage and vbutil_kernel and signs with the dev keys. Its boards.ini has no mica entry; a custom section is needed (inference: values from config.mica):

[boards/custom/mica]
arch = arm64
codename = custom-mica
dt-compatible = google,mica(-rev\d+)?(-sku\d+)?
image-format = fit
image-start-address = 0xC7800000
image-max-size = 256 MiB

Correction 2026-10-21: this section is incomplete. [boards/arm64] in boards.ini at e62d095 (lines 3514-3521) sets boots-lz4-kernel, boots-lzma-kernel and loads-fit-ramdisk = True. Per the FIT packaging research, a custom board section does not pick these up, so without them depthchargectl build patches the initramfs into the DTB, pads the kernel and tries only an uncompressed kernel. Add these three lines to the section:

loads-fit-ramdisk = True
boots-lz4-kernel = True
boots-lzma-kernel = True

A plain dt-compatible = google,mica is enough: depthcharge-tools extends it to the -rev/-sku pattern itself.

Disk layout, per the pmbootstrap precedent (pmb/install/partition.py:105-203): a ChromeOS-kernel partition with cgpt add -t kernel -S 1 -T 5 -P 10, holding the signed image, plus a root partition. depthcharge-tools adds kern_guid=%U, which the firmware expands to the partition’s PARTUUID, so root=PARTUUID=%U/PARTNROFF=1 finds the next partition.

Route B: modified Android GKI boot image

vboot’s Android path (2load_android_kernel.c:320-468) verifies boot, init_boot, vendor_boot, pvmfw and dtbo with AVB. In dev mode, verification errors are allowed and the boot state turns orange. The DT comes from the dtbo partition’s table (android_dtboimg.c). The kernel command line comes only from vendor_boot. Inference: if no dtbo base entry matches, depthcharge falls back to a FIT inside the boot image. This route overwrites stock partitions, so it ranks below Route A.

Route C: alternate firmware payload (RW_LEGACY)

coreboot’s chromeos.fmd has an RW_LEGACY region outside write-protected RO. depthcharge runs payloads listed in altfw/list when DEV_BOOT_ALTFW is allowed (payload.c, screens.c:2041-2063). This is how a UEFI payload (EDK2 or U-Boot) could boot generic distro images. It needs flashrom with root to write RW_LEGACY, plus an arm64 Hamoa payload that nobody has built. This is a later option, not the first boot.

4. Firmware fastboot

  • Commands (src/fastboot/cmd.c, 16702.B): continue, upload, download, erase, flash, getvar, oem cmdline|bootconfig add/del/set, oem logs, oem read/write-ufs-descriptor, oem set-successful, oem set-priority, oem sha256, reboot[-…], set_active and snapshot-update. main adds only fetch. There is no boot, no flashing unlock and no oem unlock.
  • Transport (fastboot.c:241-261, usb.c:16-28): depthcharge is the USB host. It looks for a Prolific PL27A1 (067b:27a7) or a Google ALink cable or dongle (18d1:506d/506e/506f/5084), then falls back to TCP port 5554 over DHCP with ASIX, r8152 or smsc95xx USB-Ethernet adapters. bluey registers no USB device-mode controller, so a plain USB-C cable to a PC cannot reach it.
  • Present in production? It is linked in only with CONFIG_FASTBOOT_IN_PROD, which the depthcharge ebuild sets when use avb. Inference (strong): mica needs AVB for Android boot, so fastboot is present. The board overlay is private.
  • Use here: flash with no allow-list and oem set-priority/set-successful can install and select a kernel partition, given a USB-Ethernet adapter on a DHCP network. That makes it an optional install path, not a RAM boot. Android’s userspace fastbootd (reboot-fastboot) is separate.

5. Closed Case Debugging

Live read 2026-10-21 (gsctool -a -I runs as the adb shell user): CCD Locked, no password. The Always capabilities are:

  • UartGscRxAPTx (read the AP console)
  • UartGscRxECTx (read the EC console)
  • UartGscRxFpmcuTx
  • FlashRead
  • UnlockNoReboot and UnlockNoShortPP

FlashAP, FlashEC, OverrideWP and OpenNoDevMode are IfOpened, and AllowUnverifiedRo is Never. GSC firmware: RO 2.0.119, RW 0.33.291.

From docs/case_closed_debugging_gsc.md (EC cr50_stab):

  • Consoles: the doc says Ti50 enumerates as 18d1:504a. On mica it is 18d1:5066, and Linux exposes the consoles as ttys (ccd-gsc.md), so usb_console is not needed. usb_console -d 18d1:504a -i 1 gives the AP console and -i 2 the EC console.
  • Opening CCD: needs developer mode, then gsctool -a -o and repeated power-button presses over about 5 minutes. Opening wipes dev mode.
  • Flashing over CCD: flashrom -p raiden_debug_spi:target=AP works once CCD is open (FlashAP).
  • Caveat: “the AP console itself is disabled for production devices”. So the AP UART may carry nothing, even though the capability allows it.

FlashRead is not a flash dump. The same doc’s capability table says it allows “dumping a hash of the AP or EC flash”. A full AP flash backup over raiden_debug_spi still needs CCD opened, and opening needs developer mode.

Inference: with the Fyra SuzyQ on the debug-accessory port, the EC console (and maybe firmware logs) can be read now, with no unlock. A flash hash could also be taken now, to compare against later.

6. Recovery image (Gate 1)

https://dl.google.com/device/recovery/mica-user/16471258/recovery.zip answered HEAD with 200, 7,601,764,157 bytes, Last-Modified 2026-10-05 15:44 GMT [11:44 EDT] (found via cros.tech/googlebook/mica, not downloaded). Saving it covers the log’s “save a copy of the recovery image” item, with one problem: its build number 16471258 is older than the installed build 16560795 (fingerprint …/CL3B.260622.276.R2/16560795). Anti-rollback may refuse it. Before relying on it for Gate 1, check the build inside the zip and whether the Device Recovery Utility serves a newer image than this static URL. It should also contain the stock dtb, dtbo and vendor_boot images, which means the vendor device tree and firmware without unlocking (inference: check the zip’s layout).

7. Proposed path

Status 2026-10-21: step 1 is done. The recovery zip is extracted (stock-dtb.md) and the EC and GSC consoles were read (ccd-gsc.md). The DTS for step 3 is drafted (dts/). Recorded as ADR 0003.

  1. Before unlock: save the recovery zip (check the build-number question in section 6) and extract the stock DTB, dtbo and firmware. Read the EC console (and the AP console, if live) with the SuzyQ.
  2. Unlock (owner, after Gate 1): OEM unlocking, Esc + F2 + Power, Unlock bootloader.
  3. First boot (Route A):
    • Build a mainline kernel with a google,mica DTS: start from x1e80100-dell-xps13-9345.dts and use the stock DTB as the hardware map.
    • Package it with mkdepthcharge and dev keys onto a USB drive’s ChromeOS-kernel partition.
    • Choose Boot from external drive.
  4. Console: CCD AP/EC UART if live. Otherwise ramoops (depthcharge always reserves it) read back on the next stock boot, or the panel through mdss once the DTS is good enough.

Not found

  • Any mica or bluey device tree (mainline, chromeos-6.12, the 16702.B kernel branch, lore linux-arm-msm).
  • Public board overlays for bluey or mica (to confirm use avb), and the exact firmware build commits.
  • Which physical USB-C port is the debug-accessory port: the left one works (tested 2026-10-21); the right is untested.
  • Any public report of Linux or another non-Android OS running on a Googlebook. AskUbuntu 1570130 (2026-10-07) is unanswered.
  • Board-id and sku-id values (still unknown): /proc/device-tree reads are denied to adb shell. They are in /firmware/coreboot once booted, or in the stock DTB.

mica’s stock device tree

Extracted 2026-10-21 on the build host from Google’s public recovery image. The device was not touched. The DTBs themselves stay in dumps/ (gitignored, vendor content). This note keeps how to reproduce them, their hashes and what they show.

Source

ItemValue
URLhttps://dl.google.com/device/recovery/mica-user/16471258/recovery.zip (Last-Modified 2026-10-05 15:44 GMT [11:44 EDT])
recovery.zip7,601,764,157 bytes, sha256 cb68dd6dbd73e568cfeecca0cbd4ceb23338640814dc3ee52aba5507e5a645d7
Contentsone file, android-desktop_signed_recovery_image.bin (19,230,966,272 bytes, dated 2026-09-29), a GPT disk image
Build (AVB props in vbmeta_a)Dell/mica/mica:17/CL3B.260622.273.R1/16471258:user/release-keys, rollback index 1339 (location 0)
Installed build (recon 2026-10-21)…/CL3B.260622.276.R2/16560795: newer than this image

Reproduce: scripts/extract-stock-dtb.sh dumps/recovery-16471258/recovery.zip. It needs unzip, sfdisk, lz4 and dtc, and uses scripts/dtbo_split.py.

Partition table (from the image’s GPT)

The image is a full internal-disk layout: 38 partitions, with A/B pairs for most Android partitions. _b twins are left out below; each has the same size and type as its _a partition. Only vbmeta_b carries attributes, GUID:48,49,50,51,56 (priority 15, successful). The live device boots slot _b (ro.boot.slot_suffix, recon). Read with sfdisk -d.

PartitionSizeType GUIDNote
ota_recovery_a128 MiBLinux data
KERN-A0 MiBLinux data
KERN-B4 MiBLinux data
ROOT-B32 MiBLinux data
super12288 MiBLinux data
vbmeta_a4 MiBAndroid vbmeta
init_boot_a8 MiBLinux data
vendor_boot_a32 MiBLinux data
boot_a64 MiBChromeOS kernel
pvmfw_a4 MiBLinux data
dtb_a2 MiBLinux data
dtbo_a8 MiBLinux data
desktop_security_persist16 MiBLinux data
desktop_security_storage32 MiBLinux data
misc4 MiBLinux data
metadata64 MiBLinux data
recovery_a32 MiBLinux data
frp_persist2 MiBLinux data
modem_a350 MiBLinux data
bluetooth_a8 MiBLinux data
dsp_a75 MiBLinux data
keymaster_a10 MiBLinux data
persist32 MiBLinux data
rawdump256 MiBrawdump
userdata4154 MiBLinux data

Points that matter for the boot path (chromeos-boot-path.md):

  • boot_a/b use the ChromeOS-kernel type GUID (fe3a2a5d-…).
  • vbmeta_a/b use the Android vbmeta GUID (88434509-…). vboot loads Android kernels through it.
  • KERN-A (1 sector), KERN-B (a 4 MiB squashfs) and ROOT-B are typed as plain Linux data, so vboot does not treat them as kernels.

Where the DT lives

  • dtb_a (2 MB) is all zeros.
  • dtbo_a is an Android DT table (magic d7b7ab1e, version 1, 3 entries of 32 bytes). Each entry is an LZ4 frame (04 22 4d 18), not a raw DTB.
  • That matches depthcharge’s Android path, which builds the DT from the dtbo table (boot path, route B).
EntryDecompressedKindTable fields after size/offsetsha256
0414,268 Bbase, root compatible = "qcom,x1e80100", no model0x0 0xffff0000 0x3 0x0 0xffffffff 0x04d019aa922b3d92695bafbc741bd3fd0073454995410a43d98904f4f9dac0f84
136,860 Boverlay: the board0x0 0xffff0000 0x3 0x0 0xffffffff 0x0ae86b73044c9b6bc2af8318291c62d2cbef53bdbe4eea09cbaecafeccb375e84
2996 Boverlay: keyboard matrix only0x0 0xf0001 0x3 0x0 0xffffffff 0x0678a35b9f776a8aa759e542b5f31dc0a2308e940e5d2e3985e69675de9b1c4fd

dtbo_a.img sha256: a94f74e2b9360351a94618699f5e9362984f41a68ac37e16efb81e5cf01d06ba. The table fields probably encode board-id and SKU ranges for depthcharge’s matcher (android_dtboimg.c). The decoding is not checked yet.

What it shows

The base is a Qualcomm vendor tree, not mainline. It has 608 compatible properties (257 distinct). Some match mainline bindings: qcom,x1e80100-gcc, -dispcc, -adsp-pas, -cdsp-pas, -pcie, qcom,pmic-glink. Display is downstream (qcom,sde-kms, sde_* labels), and buses use downstream labels (qupv3_seN_i2c). Remoteproc firmware names are bare (adsp.mdt, cdsp.mdt), loaded from /vendor/firmware. So it is a hardware map (buses, addresses, GPIOs, regulators) for writing a mainline DTS, not something to boot as is.

Board devices in overlay 1 (bus label as in the stock tree):

DeviceCompatibleBus / addressNote
Touchpadhid-over-i2cqupv3_se0_i2c @ 0x2c, hid-descr-addr 0x20vdd from touchpad-vdd-regulator
Touchscreenhid-over-i2cqupv3_se8_i2c @ 0x10LG SW42101M per chromium CL 7743220
GSC (Ti50)google,cr50qupv3_se10_i2c @ 0x50mainline tpm_tis_i2c_cr50 binding
ChromeOS ECgoogle,cros-ec-spiqupv3_se13_spi @ CS0plus google,cros-ec-keyb (8 rows x 18 columns, keymap in overlays 1 and 2)
USB-C retimers ×2parade,ps8830qupv3_se3_i2c @ 0x08, qupv3_se7_i2c @ 0x08coreboot says MAINBOARD_HAS_PS8820_RETIMER; the DT names the PS8830 binding
eUSB2 repeaters ×2nxp,ptn3222qupv3_se5_i2c @ 0x4f and 0x4b
USB-C ports ×2usb-c-connectorunder ucsi, connectors 0 and 1
Fingerprint MCUusb18d1,5406usb_mp port 2USB-attached, as coreboot’s FINGERPRINT_VIA_USB says
AP flashjedec,spi-norqspi CS0the 32 MB firmware flash
Regulatorsregulator-fixedpp3300-ucam, dmic-en, fpmcu-vcc, touchpad/touchscreen vdd, rtmr0/1-{1p15,1p8,3p3}

The overlay also targets display (mdss_*, sde_edp, sde_dp, sde_dp1, eDP HPD on tlmm 119), the battery charger, the USB multiport PHYs, reserved memory (a splash region, and a pKVM protected region for the GIC ITS at 0x17040000), and it disables seven apps_rsc_drv2 children.

Against the XPS 13 9345 (first pass)

  • The 9345 uses parade,ps8830 retimers too. Mica’s two retimers sit on different buses, and mica has no USB4/Thunderbolt in Dell’s spec (10 Gbps). Compare the 9345’s ps8830 nodes and its USB4 nodes.
  • The 9345 has a Dell EC (dell-xps-ec work); mica replaces it with google,cros-ec-spi and google,cros-ec-keyb. Battery and charging go through pmic-glink on both.
  • The 9345’s TPM is not a GSC; mica adds google,cr50 on I2C.
  • Camera: the 9345’s camera is the SoC camera-subsystem work in Linaro’s tree (project log); mica’s is USB (MAINBOARD_HAS_CAMERA_VIA_USB, pp3300-ucam rail), so likely plain UVC.

Follow-up (2026-10-21): the node-by-node comparison is 9345-dts-diff.md, and the draft DTS built from it is in dts/.

Closed Case Debugging on mica with the SuzyQ

Tested 2026-10-21 on the build host with a Fyra Labs SuzyQ Board. CCD stayed Locked, nothing was opened or flashed, and the only commands sent to the GSC were version and ccdstate. Per-unit identifiers (the Ti50 USB serial number, chip ID and pcr0) are left out.

Hookup that works

  • The SuzyQ’s USB-C plug goes into the Googlebook’s left USB-C port. The board’s own USB-C socket goes to the build host with a data cable. The board is only a pass-through; it needs both connections at once (Fyra docs: “The SuzyQ USB-C connector goes into the CCD port … You must provide a USB cable that can carry data”).
  • It enumerated in the first orientation tried. The other orientation and the right port were not tested.
  • the build host sees 18d1:5066 Google Inc. Ti50. Linux’s usb_serial_simple (“google converter”) creates the ttys. Note the ID: the GSC doc names 18d1:504a, but this Ti50 reports 5066.
ttyUSB interfaceConsoleSeen 2026-10-21
ttyUSB00 ShellGSC (Ti50) shellanswers commands
ttyUSB11 APAP UARTsilent: the stock kernel has console=null console=ttynull
ttyUSB22 ECEC consolelogs events
ttyUSB36 FPMCUfingerprint MCUsilent
(none)3 Ti50 Update, 4 SPI, 5 I2Cfirmware update, flash and I2C bridgesnot used

Use /dev/serial/by-id/usb-Google_Inc._Ti50_*-if0N-port0 instead of the ttyUSBN numbers, which can change.

the build host access

The ttys are root:uucp 0660 by default. Since 2026-10-21, /etc/udev/rules.d/70-ti50-ccd.rules tags SUBSYSTEM=="tty" with ATTRS{idVendor}=="18d1" and ATTRS{idProduct}=="5066" as uaccess, so the active desktop session gets an ACL. It sorts before systemd’s 73-seat-late.rules, which applies the tag. Check with getfacl /dev/ttyUSB0. To undo, delete the file.

Reading safely

Turn off local echo first, or the tty line discipline echoes received bytes back into the console:

stty -F /dev/ttyUSB2 raw -echo -echoe -echok -echoctl -echoke
timeout 60 cat /dev/ttyUSB2          # EC: passive
printf 'ccdstate\r' > /dev/ttyUSB0   # GSC: one command, read the reply from a cat already running on ttyUSB0

What it showed

GSC version:

  • RO: A 0.0.113, B 2.0.119 (active).
  • RW: A 0.33.270, B 0.33.291 (active), ti50a_common_mp-16604.B:v0.0.151-54d5cc09, built 2026-06-11.
  • This matches gsctool -a -f from adb (RO 2.0.119 / RW 0.33.291).

GSC ccdstate:

FieldValueMeaning
APon
Servodisconnected
Rddconnectedthe GSC sees the debug accessory on the left port
CCD_MODEasserted
State flagsUARTAP+TX UARTEC UARTFPMCUthe AP UART is bridged both ways; EC and FPMCU are read-only
CCD ports blockednone

gsctool -a -I from adb shows CCD Locked with UartGscRxAPTx, UartGscRxECTx and UartGscRxFpmcuTx allowed Always. UartGscTxAPRx shows Y Always too, which explains UARTAP+TX.

EC console (Zephyr logging), charger test:

I: ADSP: Charging from USB0
I: ADSP: Charging from USB1

The EC reports charge-port changes coming from the SoC’s ADSP. That fits the stock DT’s pmic-glink and UCSI connectors: on Linux, battery and USB-C state go through the ADSP, as on other X1 laptops, and the EC is told.

What it means for bring-up

  • A console for the first boot exists. The AP UART is bridged both ways while CCD is locked. A mainline kernel booted with console=ttyMSM0,115200 (or the right GENI UART) should print to ttyUSB1. Which GENI serial engine is wired to the GSC is still to be found in the stock DT (inference to test).
  • The EC console shows charge and power events during experiments, with no unlock needed.
  • Still locked: FlashAP, FlashEC, OverrideWP and GscFullConsole need CCD opened. Opening needs developer mode, gsctool -a -o and power-button presses (boot path, section 5).

Open

  • The right port and the other plug orientation (does CCD work only on the left port, which matches coreboot’s single DAP_USE_SMB1?).
  • Whether depthcharge prints to the AP UART during boot. A reboot while ttyUSB1 is being read would show it. That is a harmless test, but it reboots the owner’s device, so it needs the owner’s OK.

mica vs the mainline XPS 13 9345 device tree

Compared 2026-10-21. Both trees were read on the build host; the device was not touched.

  • Mainline: arch/arm64/boot/dts/qcom/x1e80100-dell-xps13-9345.dts with hamoa.dtsi and hamoa-pmics.dtsi, torvalds/linux a804c5742bff224d4cb4d25dd383554d598be32b.
  • Stock: the base DTB and board overlay from recovery build 16471258 (stock-dtb.md, overlay 1 sha256 ae86b730…).

Stock labels use Qualcomm’s vendor names (qupv3_seN_i2c). Each was matched to its mainline label by node address through the base DTB’s __symbols__ (for example qupv3_se0_i2c = i2c@b80000 = mainline &i2c0). GPIO numbers below are decimal; the stock overlay writes them in hex.

Answer

mica is a close derivative of the 9345 board. The USB-C retimer block (both PS8830s, all six power-rail GPIOs, both reset GPIOs), the touchscreen, the touchpad interrupt line, the PTN3222 reset, Wi-Fi enable, the NVMe and Wi-Fi PCIe controllers, the eDP controller and the debug UART all match the 9345 exactly. The changes cluster in a few places:

  1. ChromeOS parts replace Dell parts: a cros-ec on SPI (keyboard, and probably lid and battery events), a Ti50 TPM on I2C, the AP firmware flash on QSPI, a USB fingerprint MCU.
  2. Touchpad moved: it now sits on i2c0, where the 9345 has its I2C keyboard. The interrupt line is the same.
  3. Backlight is PWM (PMK8550 PWM). The 9345’s panel node has no PWM.
  4. eUSB2 repeaters: two PTN3222 on i2c5 (0x4f, 0x4b) where the 9345 has one on i2c9 (0x4f).
  5. Audio: the WCD938x headset codec is disabled. Speakers use the WSA macros, plus 4 DMICs.

So the bring-up DTS should be #include the 9345’s board choices where they match, and replace the keyboard, EC, backlight and repeater sections. Inference: the 9345 DTS can be copied as the starting file, not merely used as reference.

AP console: uart21

9345 mainlinemica stock
Aliasserial0 = &uart21serial0 = …/qcom,qup_uart@894000 (qupv3_se21_2uart, qcom,geni-debug-uart)
Addressserial@894000@894000
Settingsstdout-path = "serial0:115200n8"

Inference (strong): the GSC’s AP console (ttyUSB1 on the build host, ccd-gsc.md) is wired to uart21. A mainline kernel with &uart21 { status = "okay"; }, stdout-path = "serial0:115200n8" and console=ttyMSM0,115200 should print there. It is the first thing to check at first boot.

Node by node

Function9345 mainlinemica stock (mainline label)Same?
Debug UART&uart21&uart21 @ 894000yes
Bluetooth UART&uart14, qcom,wcn7850-bt, bt-enable TLMM 116&uart14 @ a98000 (msm-geni-serial-hs), bt_en TLMM 116, qcom,kiwiyes (WCN7850 = “kiwi”)
Wi-Fi&pcie4, pci17cb,1107, wlan-enable TLMM 117pcie@1c08000 = &pcie4, wlan-en-gpio TLMM 117yes
NVMe&pcie6apcie@1bf8000 = &pcie6a, qcom,no-l0s-supportedyes; note no-L0s
Keyboard&i2c0 keyboard@5 hid-over-i2c, int TLMM 67cros-ec-keyb under the EC, 8 rows x 18 columnsno
ECDell EC (not in this DTS)&spi13 ec@0 google,cros-ec-spi, 3 MHz, int TLMM 66no
TPMnone&i2c10 tpm@50 google,cr50, int TLMM 91new
AP flashnone&qspi flash@0 jedec,spi-nor, write-protect TLMM 19new
Touchpad&i2c17 touchpad@2c, int TLMM 3&i2c0 touchpad@2c, hid-descr-addr 0x20, int TLMM 3, vdd PM “humu” L12, vddl TLMM 36bus differs
Touchscreen&i2c8 touchscreen@10, int TLMM 51&i2c8 touchscreen@10, int TLMM 51, vdd “humu” L15yes (LG SW42101M)
Retimer 0&i2c3 typec-mux@8 parade,ps8830, reset PM8550 GPIO 10&i2c3 typec-mux@8 parade,ps8830, reset PM8550 GPIO 10yes
Retimer 1&i2c7 typec-mux@8, reset TLMM 176&i2c7 typec-mux@8, reset TLMM 176yes
Retimer railsRTMR0: PMC8380-5 GPIO 8, PM8550VE-9 GPIO 8, PM8550 GPIO 11. RTMR1: TLMM 188, 175, 186identical (PMC8380 “F” = SID 5, PM8550VE “J” = SID 9)yes
eUSB2 repeater&i2c9 redriver@4f nxp,ptn3222, reset TLMM 184&i2c5 redriver@4f (reset TLMM 184) and redriver@4b (reset TLMM 10)bus differs, one extra
USB-C portsusb_1_ss0/ss1 + pmic_glink connectorsusb0/usb1 = &usb_1_ss0/ss1, role switch, retimers in the graph, UCSI connectors 0 and 1yes
USB multiport&usb_mp, both HS and QMP PHYs on&usb_mp, HS PHYs on, QMP PHYs disabled, fingerprint at port 2 (usb18d1,5406)partly
Third USB controllernoneusb4 = &usb_2 (a200000) okay, HS PHY &usb_2_hsphy behind PTN3222 0x4bnew
Panel&mdss_dp3 eDP, edp-panel on aux-bus, enable TLMM 74sde_edp = &mdss_dp3, HPD TLMM 119yes, same controller
Backlightno PWM in the panel nodePWM pmk8550_pwm channel 0, period 270000 ns; enable PMC8380-D GPIO 4, power PMC8380-D GPIO 10no
DP alt mode&mdss_dp0, &mdss_dp1sde_dp, sde_dp1 = &mdss_dp0/dp1yes
SpeakersWSA8845 via the WSA macroswsa_macro, wsa2_macro tuned (bat-cfgs, rloads, gains)likely same (both list 4x WSA8845)
Headset codec(check the 9345 sound node)WCD938x disabled, qcom,wcd-disabledto compare
MicsDMIC0-3, regulator-dmic-en on TLMM 69 (stock overlay entry01-overlay.dts 1389: EN_PP3300_MIC, fixed, always-on; the base tree has none)dts/diag/sound.dtsi carries it as the VA macro’s vdd-micb-supply (2026-11-04, ticket 10); in the board DTS since 2026-11-05
Charger PMICs&smb2360_0, &smb2360_1the same two; smb2360_2/3 disabledyes
GIC ITSenabledgic_its disabled, region reserved as pkvm,protected-regionstock pKVM detail; ignore for Linux at EL2
Thermalsys-therm-6 passive tripport later

Not compared yet: the camera (USB per coreboot, so probably UVC on usb_mp or usb_2), the ambient-light sensor, the Glowbar, the lid switch, and the two fans. None of them appear in the board overlay. Inference: they sit behind the EC or on USB.

Plan for dts/x1e80100-google-mica.dts

Status 2026-10-21: done as a draft (dts/x1e80100-google-mica.dts, ADR 0004). Two changes from this plan: the QSPI flash was left out because mainline hamoa has no QSPI node, and the touchpad reset is held by pinctrl because generic hid-over-i2c has no reset property. The dts/README.md table supersedes this list.

  1. Copy x1e80100-dell-xps13-9345.dts at a804c57. Set compatible = "google,mica", "qcom,x1e80100" so depthcharge’s FIT match (google,mica…) selects it, and set the model.
  2. Keep the matching blocks: uart21, uart14 with BT, pcie4 with Wi-Fi, pcie6a with NVMe, both retimers and their rails, the touchscreen, mdss_dp0/1/3, usb_1_ss0/ss1, smb2360_0/1, the remoteprocs.
  3. Replace:
    • i2c0 keyboard → the touchpad (from i2c17);
    • drop the Dell EC;
    • add &spi13 { cros_ec: ec@0 { compatible = "google,cros-ec-spi"; … } } with cros-ec-keyb (keymap from overlays 1 and 2);
    • add &i2c10 tpm@50 google,cr50;
    • i2c9 PTN3222 → i2c5 at 0x4f and 0x4b, and enable usb_2;
    • backlight → pwm-backlight on the PMK8550 PWM.
  4. Start minimal for the first boot: UART, NVMe or USB storage, simple-framebuffer if depthcharge leaves one (unknown), then add the rest.
  5. Firmware names: the stock tree loads bare adsp.mdt and cdsp.mdt from /vendor/firmware. Mainline expects qcom/x1e80100/<vendor>/<board>/…; this needs a qcom/x1e80100/google/mica/ path and the blobs pulled from vendor in super.

Caveats

  • The stock tree is a vendor tree. Only addresses, GPIOs, I2C addresses and regulators carry over. Property names (qcom,…) generally do not.
  • parade,ps8830 appears in the stock DT while coreboot selects MAINBOARD_HAS_PS8820_RETIMER. The Linux driver binding may cover both. Check drivers/usb/typec/mux/ps883x.c before the first USB-C test.
  • The 9345 mainline DTS was read at one commit. Recheck against the tag the kernel is built from.

Exception level at kernel entry on mica, and x1-el2.dtso

Researched 2026-10-21 from firmware and kernel source at pinned commits, the stock DT extracted from the recovery image, and the existing recon. The device was not touched. Resolves ticket 02.

Answer

  1. mica’s firmware enters Linux at EL2. coreboot asks BL31 (TF-A) to start the payload at EL2. depthcharge (built on libpayload) runs at EL2 and jumps to the kernel with a plain branch, with no eret. The Android and the FIT routes both use that same jump. The bluey firmware has no Gunyah or other Qualcomm hypervisor: on the application cores it starts only BL31 (EL3), QTEE (secure EL1) and depthcharge. AOP, CPUCP, SHRM and ADSP-lite firmware go to other processors. On stock, EL2 belongs to the Android kernel’s pKVM (kvm-arm.mode=protected).
  2. Apply x1-el2.dtso. Either build x1e80100-google-mica-el2.dtb from mica.dtb + x1-el2.dtbo (the way the Makefile does it for the 9345), or put the same changes in the mica DTS directly, as the sc7180/sc7280 Chromebook trees do. mica has no Gunyah variant, so the plain-EL1 DTB has nothing to boot on. The overlay applies cleanly to the current draft and passes dt-validate (section 5).
  3. What breaks without it (inference from the overlay’s comments and the drivers):
    • First-boot risk: sbsa_watchdog stays enabled. Its comment says it “is implemented in software in Gunyah and can’t be used when running in EL2”. If CONFIG_ARM_SBSA_WATCHDOG probes a watchdog that does not exist, the result is unknown. Separately, apss_watchdog (the real hardware watchdog) stays reserved.
    • As soon as NVMe or Wi-Fi is on: pcie_smmu stays reserved and pcie4/pcie6a get no iommu-map. Under Gunyah, firmware programs that SMMU; at EL2 nobody does.
    • Phase 3: the GPU tries to load a zap shader through PAS, IRIS tries PAS boot (broken at EL2, per commit c0f045e), and the ADSP/CDSP lack the iommus that the PAS driver needs to map their firmware.
  4. Applying it wrongly means applying it under a hypervisor that owns the PCIe SMMU and APSS watchdog. Linux would then fight that hypervisor for both. That cannot happen on this firmware.

1. Firmware chain: who runs at which EL

Sources: ChromiumOS coreboot firmware-bluey-16702.B at ff3258d575b0ddf4ba0514a2cd03c6a1936dba69 and depthcharge firmware-bluey-16702.B at 694eabf646855067acd51935059c3191436ccc79 (pinned in chromeos-boot-path.md). Read 2026-10-21.

StepEvidence
coreboot starts at EL3src/arch/arm64/Kconfig:26-28: ARM64_CURRENT_EL defaults to 3. Neither src/soc/qualcomm/x1p42100/Kconfig nor src/mainboard/google/bluey/Kconfig nor src/soc/qualcomm/common/Kconfig overrides it (no CURRENT_EL in any of the three)
It uses TF-A, which needs EL3src/soc/qualcomm/x1p42100/Kconfig:11 select ARM64_USE_ARM_TRUSTED_FIRMWARE; src/arch/arm64/Kconfig:44-47 depends on … ARM64_CURRENT_EL = 3
The payload is requested at EL2src/arch/arm64/boot.c:17-20: payload_spsr = get_eret_el(EL2, SPSR_USE_L), passed to run_bl31()
BL31 gets that SPSR for BL33src/arch/arm64/bl31.c:108-110: bl33_ep_info.spsr = payload_spsr
The secure OS runs at secure EL1bl31.c:88-104: BL32 loaded from CBFS secure_os, spsr = get_eret_el(EL1, …). Hamoa selects ARM64_USE_SECURE_OS (x1p42100/Kconfig:48-52)
depthcharge’s runtime runs at EL2libpayload payloads/libpayload/arch/arm64/head.S:38 writes sctlr_el2 and :42 writes hcr_el2 (HCR_TGE) at _entry. Those writes are undefined below EL2 (Arm ARM DDI 0487, MSR to an *_EL2 register from EL1 is UNDEFINED), and depthcharge visibly runs on mica. Also arch/arm64/mmu.c:84 (“We should be in EL2…”) and exception_asm.S:97-126 (spsr_el2, elr_el2)
depthcharge does not change EL before the kerneldepthcharge src/arch/arm/boot64_asm.S:38-46: TLB invalidate, MMU off, x0 = fdt, then br x25. No eret, no SPSR write
Both boot routes use that jumpsrc/arch/arm/fit.c:57-124: boot() takes bi->dt (the Android dtbo route) or fit_load() (the ChromeOS FIT route), then boot_arm_linux(). That is boot64.c:166-229, ending in boot_arm64_linux_jump()

No hypervisor image in the bluey build. src/soc/qualcomm/x1p42100/Makefile.mk:88-431 lists every Qualcomm blob put into CBFS: QcLib, DCB, pre-DDR DTB, CPR, QUP firmware, AOP, CPUCP, ADSP-lite, SHRM, the QTEE configs tzoem_cfg/tzqti_cfg, the access-control configs tzac_cfg/hypac_cfg, APDP and ramdump. TF-A (bl31) and QTEE (secure_os) come from the arch Kconfig. No hypervisor image (hyp.mbn, Gunyah, QHEE) is there. src/soc/qualcomm/x1p42100/soc.c:115-147 loads the four configs, then preloads bl31 and secure_os, and nothing else.

DRAM map agrees. src/soc/qualcomm/x1p42100/memlayout.ld:267-268 puts coreboot’s own post-RAM stack at 0x80000000. On Windows-firmware boards, mainline reserves that address as gunyah_hyp_mem (hamoa.dtsi:482-485 at a804c57). TZ is at 0xD8000000, with BL31 right after it (:293-294). Inference: the mainline gunyah_hyp_mem reservation wastes 8 MiB on mica and does no harm.

Stock agrees. Recon 2026-10-21 (recon-2026-10-21.md rows 19 and 21):

  • the command line has kvm-arm.mode=protected and id_aa64mmfr1.vh=0, plus kvm-arm.protected_modules= with pkvm-smc-filter.ko and qcom_smmu_v2_v3_dispatcher.ko;
  • ro.boot.hypervisor.protected_vm.supported=1.

Protected KVM installs its hypervisor at EL2 from the kernel, and the kernel can only do that if it was entered at EL2. depthcharge also loads pvmfw and adds its reserved-memory node for pKVM (src/arch/arm/fit.c:79-97, CONFIG_ANDROID_PVMFW=y in configs/config.mica). Inference: on stock, pKVM plus its Qualcomm SMMU and SMC modules fill the role that Gunyah fills on Windows laptops.

Linux allows either level: “The CPU must be in non-secure state, either in EL2 (RECOMMENDED in order to have access to the virtualisation extensions), or in EL1.” (Documentation/arch/arm64/booting.rst:174-175 at a804c57)

2. What x1-el2.dtso changes, and why

File: arch/arm64/boot/dts/qcom/x1-el2.dtso at torvalds/linux a804c5742bff. History read through the GitHub commits API for that path on 2026-10-21 (four commits):

Commit (author date)ChangeReason given
e01acd8f3cc1 (2025-05-03) “x1e/x1p: Add EL2 overlay for WoA devices”creates the overlay and the -el2.dtb Makefile targets“WoA devices using x1e/x1p use android firmware to boot, which notably includes Gunyah hypervisor … Linux-based OS could only boot in EL1.” With slbounce Linux can take EL2, and “some modifications to the DT are necessary to facilitate the absence of Gunyah services”
c0f045e303e0 (2025-09-15) “x1-el2: Disable IRIS for now”&iris { status = "disabled" }Reset and IOMMU handling for IRIS live in the hypervisor on X1E. At EL2 “the PAS interface normally used by IRIS to boot the video firmware is not working”
f010e0b99703 (2025-12-24) “x1-el2: Enable the APSS watchdog”&apss_watchdog { status = "okay" }“In EL2, the APSS watchdog is available”
47c88db49f6c (2026-02-03) “hamoa: Add remoteproc IOMMUS in EL2 device trees”iommus for remoteproc_adsp (0x1000 0x80) and remoteproc_cdsp (0x0c00 0x0)“without Gunyah, the IOMMU is managed by the consumer of this DeviceTree. Therefore, describe the firmware streams”

The whole overlay (x1-el2.dtso:10-69):

  • apss_watchdog okay;
  • gpu_zap_shader disabled (“linux can zap the gpu on it’s own”);
  • iris disabled;
  • pcie3, pcie4, pcie5, pcie6a get iommu-map onto pcie_smmu (stream bases 0x30000, 0x40000, 0x50000, 0x60000);
  • pcie3 and pcie5 also get msi-map onto gic_its (“ITS emulation in Gunyah is broken”);
  • pcie_smmu okay;
  • the remoteproc iommus above;
  • sbsa_watchdog disabled.

The mainline defaults it overrides are in hamoa.dtsi (a804c57):

  • pcie_smmu is status = "reserved"; /* Controlled by Gunyah. */ (:8520-8532);
  • apss_watchdog is "reserved"; /* Reserved by Gunyah */ (:8560-8566);
  • sbsa_watchdog has no status, so it is enabled (:8777-8782);
  • pcie4 and pcie6a already have msi-map (:3721, :3441), so mica’s two PCIe controllers have MSIs either way.

The Makefile builds x1e80100-dell-xps13-9345-el2.dtb := x1e80100-dell-xps13-9345.dtb x1-el2.dtbo (:440-441). That is the board mica’s DTS is drafted from (ADR 0004).

3. Chromebook precedent: Qualcomm Chrome trees carry the same changes

Older Qualcomm Chromebooks boot the same way: coreboot, TF-A and depthcharge, with no QHEE. Their mainline trees already contain what the WoA EL2 overlays add:

  • sc7180-el2.dtso:15: “Venus can be used in EL2 if booted similarly to ChromeOS devices”. Commit 0d95f64be417 (2025-05-03): WoA sc7180 “includes QHEE hypervisor”, hence EL1.
  • sc7180-trogdor.dtsi:44 /delete-node/ &gpu_zap_shader; and :45 /delete-node/ &hyp_mem;; :1005-1008 adds video-firmware { iommus = … } to Venus. That is the same zap and video change sc7180-el2.dtso makes.
  • sc7280-chrome-common.dtsi:3-9 (“memory protections put into place by the Chrome bootloader … different than … Qualcomm’s typical bootloader”) deletes gpu_zap_shader and hyp_mem (:23-24) and sets &watchdog { status = "okay"; } (:143-145). In the base tree that watchdog is status = "reserved"; /* Owned by Gunyah hyp */ (kodiak.dtsi:6918-6923).

Inference: mica’s bluey firmware is the same design one generation later, so it needs the same DT treatment. For X1 that treatment is already packaged as x1-el2.dtso.

4. Against the stock mica tree

Derived facts from the recovery-image base DTB (dumps/recovery-16471258/dtbo/entry00-base.dts, gitignored; reproduce with scripts/extract-stock-dtb.sh, see stock-dtb.md). Line numbers refer to that decompiled file.

Overlay itemStock micaFits EL2 without Gunyah?
pcie_smmu (arm,smmu-v3 at 0x15400000)present with no status, so enabled (:13161-13171)yes: Android owns it (through pKVM’s SMMU module)
pcie4 / pcie6a iommu-mappcie4 maps onto pcie_smmu at 0x40000, pcie6 at 0x60000 (:14378, :14288)yes: the same stream bases as the overlay
APSS watchdog at 0x17410000qcom,msm-watchdog, enabled (:12829-12839)yes: HLOS owns it, as at EL2
sbsa_watchdog (arm,sbsa-gwdt at 0x1c840000)present, enabled (:10597-10601)counter-signal, see below
GPU zap-shadera zap-shader child exists under the GPU (:11731)counter-signal, see below
ADSP/CDSP iommusnone on qcom,x1e80100-adsp-pas (:5512-5537)neutral: stock PAS goes through pKVM and the vendor driver
PCIe MSImsi-parent points at qcom,pci-msi controllers (:14324-14331); the GIC ITS is a pkvm,protected-region (overlay 1)neutral: vendor pKVM detail (9345-dts-diff.md)

Counter-signals, and why the overlay is still right:

  • hypac_cfg. coreboot loads a “hyp access-control” config (hyp_ac_config.elf, Makefile.mk:371-376, soc.c:135-138). Which component reads it is not public. Nothing besides BL33 is ever entered at EL2 (section 1), so it does not mean there is a hypervisor. Inference: QTEE or TF-A applies it as a policy.
  • Stock enables the SBSA watchdog. That cuts against the overlay’s comment that Gunyah emulates it. The stock tree is a Qualcomm vendor base, and enabling a node does not show anything exists at 0x1c840000. Disabling it is safe either way. Leaving it enabled is the only one of the two choices that can go wrong at first boot.
  • Stock keeps a zap-shader node. ChromeOS precedent goes the other way (section 3: trogdor and chrome-common delete it). Inference: the stock node serves Android’s pKVM and vendor-driver path, not mainline’s. adreno_gpu.c:49-52 at a804c57 treats a missing or disabled zap-shader as “zap not available” (-ENODEV). It then takes the zap-less path. The overlay’s own comment says that path works at EL2: “linux can zap the gpu on it’s own” (x1-el2.dtso:14).

5. Check: the overlay applied to the draft DTS

Run 2026-10-21 on the build host in a scratch directory (nothing committed):

  1. scripts/build-dtb.sh compiles with dtc -@ (line 17), so the base keeps __symbols__.
  2. dtc -@ -I dts -O dtb x1-el2.dtso and fdtoverlay -i mica.dtb -o mica-el2.dtb x1-el2.dtbo applied with no errors.
  3. dt-validate -s schema-a804c57.json mica-el2.dtb reported only the known pending google,mica root-compatible finding, the one scripts/check.sh already allows.
  4. fdtget before → after:
    • iommu@15400000: reserved → okay;
    • watchdog@17410000: reserved → okay;
    • watchdog@1c840000: (enabled) → disabled;
    • video-codec@aa00000: okay → disabled;
    • pcie@1c08000 iommu-map = 0 <pcie_smmu> 0x40000 0x10000;
    • pcie@1bf8000 = … 0x60000 0x10000.

The draft dts/x1e80100-google-mica.dts sets &gpu_zap_shader { firmware-name } (:748-750) and &iris { status = "okay" } (:959-962), following the 9345. The overlay disables both, which is intended at EL2.

Packaging note for the FIT ticket: depthcharge can apply overlays itself. A FIT configuration’s fdt property may list a base followed by overlays (src/boot/fit.c:115-134, :177-178), applied with dt_apply_overlay() (:464-479). The configuration also needs a compatible (:261-263). Applying it at build time is simpler and matches the kernel Makefile.

Watchdog left armed by firmware? coreboot’s common Qualcomm watchdog.c (src/soc/qualcomm/common/watchdog.c:10-16, linked in romstage at x1p42100/Makefile.mk:34) only reads the AOSS reset status to record a tombstone. It does not arm a watchdog. Inference: a kernel without apss_watchdog will not be reset by a firmware-armed watchdog. The depthcharge watchdog code was not read (only part of depthcharge is cached).

Not found

  • TF-A’s Qualcomm platform code that reads bl33_ep_info.spsr. coreboot uses a prebuilt BL31 plus libqtisec.a (Makefile.mk:99-104). That BL31 honours the requested EL2 is inferred from the stock pKVM evidence; its source was not read.
  • Who reads hypac_cfg, and what policy it applies.
  • What is at 0x1c840000 on mica, and whether the SBSA watchdog exists there without Gunyah.
  • Whether mica’s QTEE supports the PAS resource-table SCM call. With iommus present, qcom_q6v5_pas.c takes that path (:862 has_iommu, :475-500 qcom_pas_get_rsc_table(), :927 use_tzmem). It decides whether ADSP/CDSP boot at EL2 (Phase 3).
  • The state firmware leaves the PCIe SMMU in (enabled, bypass or abort) when Linux leaves it alone, which decides how NVMe fails without the overlay.
  • Anything on exception levels in chromebook-boot-flow.rst. At a804c57 (Documentation/arch/arm/google/chromebook-boot-flow.rst) it covers FIT compatible matching only.
  • A boot log showing CPU: All CPU(s) started at EL2. It would confirm this answer, and the first boot (or ramoops) will show it.
  • The full depthcharge tree at 694eabf: only the files under .firecrawl/src-depthcharge-… were read.

Packaging and signing a mica FIT, and checking it offline

Researched 2026-10-21 on the build host from upstream source at pinned commits, distro package metadata and a read of the stock dtbo table in dumps/ (read only). The Googlebook was not touched. Nothing was installed. Items marked inference are reasoned from source, not tested.

Ticket: 03-fit-packaging. Decision context: ADR 0003, chromeos-boot-path.md.

Answer

A mica kernel partition is a vboot kernel blob (64 KiB vblock + body) whose body is a FIT with one kernel, one DTB and one ramdisk. mkdepthcharge builds it in one command. Everything except the actual boot can be checked on the build host.

Step list

  1. Tools. Run the toolchain in a Debian sid container (all four packages are official there), or install extra/uboot-tools plus AUR vboot-utils and run depthcharge-tools from a git checkout (see 8). Docker is active on the build host, but the owner is not in the docker group (live read 2026-10-21), so the container runs through pkexec:

    pkexec docker run --rm -it -v /home/the owner/.cache/gbook/out:/out debian:sid bash
    # inside:
    apt-get update && apt-get install -y depthcharge-tools vboot-kernel-utils vboot-utils cgpt u-boot-tools device-tree-compiler
    sha256sum /usr/share/vboot/devkeys/{kernel.keyblock,kernel_data_key.vbprivk,kernel_subkey.vbpubk}
    

    The three hashes must equal the pinned tests/devkeys values in 4.

  2. Inputs.

    • Image: the plain arm64 Image, not Image.gz. depthcharge checks the arm64 magic 0x644d5241 after its own decompression step and knows only none/lz4/lzma.
    • mica.dtb: from scripts/build-dtb.sh. Its root compatible must contain the exact string google,mica. The current draft does: google,mica qcom,x1e80100 (live read of the built DTB, 2026-10-21).
    • initramfs.cpio.gz: any cpio the kernel can unpack. Gzip inside the cpio is fine. FIT-level ramdisk compression must be none, and mkimage writes none.
  3. Build and sign (keys default to /usr/share/vboot/devkeys):

    mkdepthcharge -o /out/mica.kpart --tmpdir /out/mkdc-tmp -v \
      -A arm64 --format fit -C none \
      -c "console=ttyMSM0,115200n8 earlycon=qcom_geni,0x894000 ignore_loglevel" \
      -d /out/Image -i /out/initramfs.cpio.gz -b /out/mica.dtb
    
    • mkdepthcharge prepends kern_guid=%U, so the stored cmdline is kern_guid=%U console=ttyMSM0,115200n8 earlycon=qcom_geni,0x894000 ignore_loglevel.
    • At boot depthcharge replaces %U with the kernel partition’s PARTUUID (lowercase) and appends cros_secure. It also appends cpufreq.default_governor=powersave, but only when booting with no battery.
    • It runs mkimage -f auto -A arm64 -T kernel -O linux -C none -n … -i initramfs -b mica.dtb -d Image, then fdtput … type kernel_noload, then vbutil_kernel --version 1 --arch aarch64 --vmlinuz depthcharge.fit --config kernel.args --bootloader empty.bin --keyblock … --signprivate … --pack, then vbutil_kernel --verify.
    • --tmpdir keeps depthcharge.fit for the checks below.
    • Leave out root= for the first boot. The initramfs is the whole userspace.
    • --kernel-start is not needed. On arm64 it only feeds the 32-bit initramfs hacks, which stay off.
  4. Offline checks (all on the build host, no device):

    fdtget mica.dtb / compatible                      # contains google,mica
    dumpimage -l mkdc-tmp/depthcharge.fit             # kernel-1, fdt-1, ramdisk-1, conf-1
    fdtget mkdc-tmp/depthcharge.fit /configurations default        # conf-1 (one DTB keeps the default)
    fdtget mkdc-tmp/depthcharge.fit /images/fdt-1 compression      # none
    fdtget mkdc-tmp/depthcharge.fit /images/kernel-1 compression   # none | lz4 | lzma
    fdtget mkdc-tmp/depthcharge.fit /images/kernel-1 data-size     # must FAIL: data is embedded, not external
    vbutil_kernel --verify mica.kpart --signpubkey /usr/share/vboot/devkeys/kernel_subkey.vbpubk --verbose
    vbutil_kernel --verify mica.kpart                 # hash-only path, same as dev-mode firmware
    futility show mica.kpart
    stat -c %s mica.kpart                             # body must be <= 0x10000000 (256 MiB)
    

    Expected --verify output: keyblock flags 23 (0x17), “Body verification succeeded”, and a Config that echoes the cmdline with a literal %U.

  5. Disk (build as a file on the build host, then copy it to the USB drive as an owner step):

    truncate -s 2G mica-usb.img
    cgpt create mica-usb.img
    cgpt boot -p mica-usb.img                         # protective MBR, for Linux later
    cgpt add -i 1 -t kernel -b 8192 -s 262144 -l KERN-A -S 0 -T 3 -P 15 mica-usb.img   # 128 MiB
    cgpt add -i 2 -t data -b 270336 -s 3923935 -l ROOT mica-usb.img   # optional rootfs for later; fits the 2 GiB file
    dd if=mica.kpart of=mica-usb.img bs=512 seek=8192 conv=notrunc
    cgpt show mica-usb.img
    cgpt show -i 1 -P mica-usb.img; cgpt show -i 1 -T mica-usb.img; cgpt show -i 1 -S mica-usb.img
    
    • -t kernel gives type fe3a2a5d-4f32-41a7-b725-accc3285a309.
    • -S 0 -T 3 -P 15 means “try three times, then fall back”. vboot spends a try on each attempt while successful is 0.
    • pmbootstrap’s -S 1 -T 5 -P 10 is for an installed system. With successful=1 the tries count is ignored, so a kernel that panics is picked again on every boot.
    • Sizes are in device blocks. The numbers above assume 512-byte sectors.
  6. FIT matching is not a blocker. depthcharge prefers google,mica-rev<R>-sku<S>, then google,mica-rev<R>, google,mica-sku<S> and google,mica. A DTB whose root compatible holds google,mica matches whatever board-id and sku-id the EC reports. With one DTB, mkdepthcharge also keeps default = "conf-1", which boots even with no match. That is a safety net, and a trap: a wrong DTB still boots.

What cannot be checked offline:

  • depthcharge’s own config choice. There is no upstream unit test for fit.c at 694eabf; the logic is small enough to check by reading.
  • The real board-id and sku-id.
  • Whether depthcharge’s DT fixups (dt_apply_fixups) succeed on a mainline-shaped DT.
  • Whether earlycon prints anything.

On the AP console, depthcharge prints Compat preference: … and Choosing best match … (fit.c:384-387, 439-440), which reveals board-id and sku-id on the first boot.

Follow-ups for other files (not edited here):

  • chromeos-boot-path.md labels e62d095 as depthcharge-tools v0.6.2. It is v0.6.2-107-ge62d095 (2026-10-19); the tag is 95d17c4 (2023-06-30).
  • Its draft [boards/custom/mica] section lacks loads-fit-ramdisk, boots-lz4-kernel and boots-lzma-kernel. See 1.
  • The draft DTS has no /chosen/stdout-path (fdtget → FDT_ERR_NOTFOUND, 2026-10-21), so a bare earlycon would do nothing. Hence the explicit earlycon=qcom_geni,0x894000 above.

Update 2026-10-21: all three follow-ups are applied on main in commit 709212e: dated corrections in chromeos-boot-path.md, and /chosen/stdout-path in the DTS.

Sources and revisions

RepoRefCommit
depthchargefirmware-bluey-16702.B694eabf646855067acd51935059c3191436ccc79
vboot_referencefirmware-bluey-16702.B34fbbe62cc1832016e956d55d72b4aba606b7e21
ChromiumOS corebootfirmware-bluey-16702.Bff3258d575b0ddf4ba0514a2cd03c6a1936dba69
depthcharge-toolsmaster (v0.6.2-107)e62d095bc61a371b69587ba2773e10a5f2e87308 (tag v0.6.2 = 95d17c444ae3a9dec1c51ead6301ed04777314b9)
pmaportsmastera683af41125794aeeba199cd53928332df4e6256
pmbootstrap3.12.17d23010a125ca7386fec4e390d14128b6c24d266
boot-deploymastera50452a054233c2d7bf6142cbc332c94c79cc18f
U-Boot (tools/fit_image.c)tag v2026.07 (Arch uboot-tools 2026.07-2)tag
Linuxtorvalds mastera804c5742bff224d4cb4d25dd383554d598be32b

All read 2026-10-21. Paths below are relative to each repo. Line numbers refer to these commits.

1. mkdepthcharge for a custom board

mkdepthcharge is board-agnostic. It takes the arch, format, compression, cmdline, keys and files (depthcharge_tools/mkdepthcharge.py:597-978). For fit it runs:

  • mkimage -f auto -A <arch> -T kernel -O linux -C <compress> -n <name> [-i initramfs] -b <dtb>… -d <vmlinuz> (:1190-1202);
  • fdtput <fit> <kernel node> type kernel_noload (:1214-1219);
  • fdtput -d /configurations default, only when there is more than one DTB (:1221-1227);
  • vbutil_kernel --version <n> --arch <vboot arch> --vmlinuz <fit> --config <cmdline file> --bootloader <512-byte empty file> --keyblock … --signprivate … --pack <out> (:1240-1250), then vbutil_kernel --verify (:1401-1410).

For arm64 the vboot arch string is aarch64 (utils/platform.py:536-545), which futility maps to ARCH_ARM (vboot futility/cmd_vbutil_kernel.c:290-291). --kernel-start is used only by the --patch-dtbs and --pad-vmlinuz hacks (mkdepthcharge.py:1059-1188). The manual says arm64 depthcharge decompresses the kernel to a safe place itself, so the hacks are for 32-bit boards (mkdepthcharge.rst:151-169).

The board section only matters for depthchargectl build/check. boards.ini tells users to add “fully specified sections with a unique codename” (depthcharge_tools/boards.ini:7-29). Sections inherit only from their /-separated parents (utils/collections.py:11-42). So [boards/custom/mica] inherits nothing from [boards/arm64] (boards.ini:3514-3521, which sets boots-lz4-kernel, boots-lzma-kernel and loads-fit-ramdisk).

Those keys default to False (depthchargectl/__init__.py:123-137). With loads-fit-ramdisk False, depthchargectl build turns on patch_dtbs (_build.py:549). That adds /chosen/linux,initrd-* to the DTB and pads the kernel. Without the two boots-* keys, only none compression is tried (_build.py:434-442).

A complete section:

[depthchargectl]
board = custom-mica

[boards/custom/mica]
arch = arm64
codename = custom-mica
name = Dell XPS Googlebook (mica, custom)
dt-compatible = google,mica
image-format = fit
image-max-size = 256 MiB
image-start-address = 0xC7800000
loads-fit-ramdisk = True
boots-lz4-kernel = True
boots-lzma-kernel = True
  • A plain dt-compatible = google,mica is extended to google,mica(-rev\d+)?(-sku\d+)? automatically (utils/string.py:112-131). It is used only to pick DTBs from an fdtdir (_build.py:238-292).
  • image-start-address is informational on arm64 (see above).
  • image-max-size makes depthchargectl build reject a too-large image (_build.py:589).

Version differences. e62d095 is 107 commits past the v0.6.2 tag. For a one-DTB FIT build, the relevant code is the same in both, apart from the new default removal for multi-DTB FITs (git diff v0.6.2 e62d095 -- depthcharge_tools/mkdepthcharge.py).

Precedent.

  • pmaports trogdor: deviceinfo_depthcharge_board="qc7180", deviceinfo_depthcharge_compression="lzma", a 128 MiB kpart at sector 8192 (device/community/device-google-trogdor/deviceinfo:21-34).
  • boot-deploy runs depthchargectl build --root none --board … --kernel … --kernel-cmdline … --initramfs … --fdtdir … --compress … --output … (boot-deploy-functions.sh:1078-1108).

2. Compression and the 256 MiB limit

Allowed formats.

  • depthcharge reads a FIT image node’s compression as none, lzma or lz4. Anything else is invalid (src/boot/fit.c:140-165), and decompression supports exactly those (:291-311).
  • mkdepthcharge offers none, lz4 and lzma and passes -C none explicitly, because mkimage otherwise assumes gzip (mkdepthcharge.py:685-699, 993-1003).
  • The FDT image node must be none when the config has no compatible (fit.c:254-259), and mkimage writes none for every -b file (U-Boot tools/fit_image.c:392-393).
  • The FIT ramdisk must be none (fit.c:484-488); a ramdisk node with no compression property counts as none (fit.c:143).

Embedded data only. depthcharge reads only the data property (fit.c:149-151). A FIT built with external data (mkimage -E) would load empty images. mkimage -f auto embeds the data (fit_image.c:368).

Kernel placement.

  • depthcharge partly decompresses the kernel to read the arm64 header, checks magic 0x644d5241 (boot64.c:44, 178-192) and takes image_size from it (:194-197).
  • It picks a KASLR slot outside [min(BASE_ADDRESS, KERNEL_START, FIT_FDT_ADDR), 4 GiB) (boot64.c:129-133) and decompresses or copies the kernel there (:199-210).
  • So the decompressed kernel never has to fit the 256 MiB buffer. Only the packed body does.

The 256 MiB limit.

  • config.mica sets CONFIG_KERNEL_START=0xC7800000 and CONFIG_KERNEL_SIZE=0x10000000.
  • The linker script turns those into _kernel_start/_kernel_end (src/image/depthcharge.ldscript.S:38-39), and vboot gets kernel_buffer_size = _kernel_end - _kernel_start (src/vboot/stages.c:162-163).
  • vboot rejects a body larger than the buffer (“Kernel body doesn’t fit in memory”, vboot firmware/2lib/2load_kernel.c:389-392). It requires the body to start within the first 64 KiB of the partition (:378-382; futility’s default --pad is 65536, cmd_vbutil_kernel.c:33).
  • The body is roundup(FIT, 4K) + 4 KiB cmdline + 4 KiB params + roundup(bootloader, 4K) (futility/vb1_helper.c:668-685, sizes in futility/kernel_blob.h:15-21). So the FIT may be up to about 256 MiB − 12 KiB.
  • The partition must hold 64 KiB + body.

The DT limit. The flattened DT after depthcharge’s edits must fit the 8 MiB FDT buffer at 0xDA800000 (depthcharge.ldscript.S:40-41, src/arch/arm/fit.c:105-111). The draft DTB is 215,695 bytes.

Ramdisk placement. The ramdisk stays inside the FIT in the kernel buffer, below 4 GiB, as fit_add_ramdisk assumes (fit.c:335-348).

Choice.

  • Inference: use -C none for the first boot. It has the fewest moving parts, and an arm64 Image plus a small initramfs is far below 256 MiB.
  • lz4 is the fallback if USB read time or partition size matters. lzma is the slowest to decompress in firmware.
  • depthchargectl itself tries none, then lz4, then lzma, and keeps the first that fits (_build.py:434-442, 562-597).

3. Kernel command line

  • Storage. mkdepthcharge writes the cmdline to a file for vbutil_kernel --config. An empty cmdline becomes --, because vbutil_kernel rejects an empty one (mkdepthcharge.py:876-881). The config slot is 4096 bytes including the NUL (kernel_blob.h:15, vb1_helper.c:73-75).
  • kern_guid=%U. mkdepthcharge prepends it unless --no-kern-guid is given (mkdepthcharge.py:885-905, mkdepthcharge.rst:236-249).
  • %U expansion.
    • vboot copies the chosen entry’s unique GUID into params->partition_guid (2load_kernel.c:761-762).
    • depthcharge passes it to commandline_subst (src/vboot/stages.c:307-312), which replaces each %U with the GUID in lowercase (src/boot/commandline.c:79-85).
    • It then appends every queued parameter with a leading space (:94-98):
      • cros_secure on the ChromeOS path (src/vboot/boot_info.c:56);
      • cpufreq.default_governor=powersave only when board_boot_in_no_battery_mode() (src/board/bluey/board.c:161-163).
    • The result goes into /chosen/bootargs (src/boot/dt_update.c:38). The working buffer is 2 × 4096 bytes (stages.c:293, src/vboot/boot_info.h:43).
  • root=PARTUUID=%U/PARTNROFF=1. The kernel resolves this form itself (block/early-lookup.c:41-61, 226-246 at a804c57). It matters only when the kernel mounts root, that is with no initramfs /init.
    • Inference: an initramfs’s own init would have to parse PARTNROFF itself.
    • For the first boot, leave root= out. Keep kern_guid=%U: pmOS’s boot-deploy uses it to find the partition to write the next image to (boot-deploy-functions.sh:1121-1144).
  • Console.
    • uart21 is serial@894000 (arch/arm64/boot/dts/qcom/hamoa.dtsi:1296-1298 at a804c57). The draft DTS sets compatible = "qcom,geni-debug-uart" on it and serial0 = &uart21.
    • The GENI driver registers earlycon qcom_geni for that compatible and names its ttys ttyMSM (drivers/tty/serial/qcom_geni_serial.c:1755, 1769, 1785).
    • earlycon=qcom_geni,<addr> “must already be setup and configured” (Documentation/admin-guide/ kernel-parameters.txt:1515-1519). Bare earlycon uses /chosen/stdout-path (:1398-1403), which the draft DTS lacks.
    • So: console=ttyMSM0,115200n8 earlycon=qcom_geni,0x894000.
    • Inference: if the firmware leaves uart21’s clocks off, earlycon stays silent while ttyMSM0 still works once the driver probes. The kernel needs CONFIG_SERIAL_QCOM_GENI_CONSOLE=y.

4. Dev keys

  • What vboot checks in developer mode.

    • need_keyblock_valid is false (2load_kernel.c:143; chromeos-boot-path.md section 2).
    • When the keyblock signature fails against mica’s kernel subkey, vboot accepts the keyblock on its SHA-512 hash (:164-181).
    • Keyblock flag mismatches only clear keyblock_valid (:184-211).
    • The body must still verify against the keyblock’s own data key (:433-438).
    • So any self-consistent key pair boots. The public dev keys are the convention.
  • Where they come from. vboot_reference ships them in tests/devkeys/. make install copies them to /usr/share/vboot/devkeys (Makefile:30, 448-453). At 34fbbe6, sha256:

    Filesha256
    kernel.keyblockca2feab9d94eae768fea8616e52cda6cb5576b3d45cd8f7f8af0ce14cb9ce252
    kernel_data_key.vbprivkbc9e707a86e55a93f423e7bcdae4a25fd470b868e53829b91bbe2ccfbc6da27b
    kernel_subkey.vbpubk36b9c5fa6f5d0432b9acbe8e2b7da2e602162a87a425575c6d7ba975a0440708
    • kernel.keyblock decodes to header 2.1, size 1208 bytes and flags 0x17 = DEVELOPER_0 | DEVELOPER_1 | RECOVERY_0 | MINIOS_0 (offsets from firmware/2lib/include/2struct.h:371-418, read on the build host 2026-10-21). That matches KERNEL_KEYBLOCK_MODE (scripts/keygeneration/common.sh:87) and passes in dev mode without recovery and without miniOS.
    • key.versions says kernel key version 1 and kernel version 1. mkdepthcharge’s default --tpm-kernel-version is 1 (mkdepthcharge.py:966-978). Rollback checks do not apply when need_keyblock_valid is false (2load_kernel.c:313-323).
  • On the build host. mkdepthcharge searches, in order:

    • --keydir, or the directories of any given key;
    • /etc/depthcharge-tools;
    • /usr/share/vboot/devkeys;
    • /usr/local/share/vboot/devkeys (utils/platform.py:166-198, mkdepthcharge.rst:257-268).

    Sources:

    • AUR vboot-utils copies tests/devkeys to /usr/share/vboot/devkeys (its PKGBUILD package()).
    • Debian’s vboot-kernel-utils ships the same file names there (packages.debian.org sid file list, read 2026-10-21).
    • With no install at all, --keydir <vboot_reference checkout>/tests/devkeys works.

    Check any copy against the table above.

5. Partition type and attributes

  • Type. vboot boots GPT entries whose type is the ChromeOS kernel GUID fe3a2a5d-4f32-41a7-b725-accc3285a309 or the Android vbmeta GUID (firmware/include/gpt.h:46-47, firmware/lib/cgptlib/cgptlib_internal.c:157-170). cgpt’s alias for the first is kernel (cgpt/cgpt_common.c:653); data is Linux data (:656).
  • Attribute bits. Priority sits in bits 48-51, tries in 52-55 and successful in 56 (cgptlib_internal.h:40-53). cgpt add -S/-T/-P set them (cgpt/cmd_add.c:16-33). cgpt show -i N -S|-T|-P|-A reads them back (cgpt/cmd_show.c:19-41).
  • Selection.
    • GptNextKernelEntry skips entries with neither successful nor tries, and picks the highest priority > 0 (firmware/lib/cgptlib/cgptlib.c:44-120).
    • A booted entry with successful=0 gets a try deducted (2load_kernel.c:768-779). With successful=1, tries are ignored (cgptlib.c:135-144).
    • A failing entry is marked bad and the next one is tried (2load_kernel.c:742-743).
  • Precedent. pmbootstrap uses cgpt add -i <kernel> -t kernel -b <start> -s <size> -l pmOS_kernel -S 1 -T 5 -P 10, then an efi boot partition and a data root (pmb/install/partition.py:107-204). It writes the image with dd (pmb/install/_install.py:651-663).
  • First-boot recommendation. Use -S 0 -T 3 -P 15 instead (inference from the selection code). That matches ADR 0003’s “a bad image falls back”. depthchargectl’s bless --oneshot sets successful=0, tries=1 and the highest priority (depthchargectl.rst:247-252).
  • Stock disk, for contrast (stock-dtb.md). The stock boot_a has the ChromeOS-kernel type but holds a plain Android boot image (magic ANDROID! at offset 0, read 2026-10-21) and has priority 0. So vboot’s ChromeOS path never picks it, and the Android path goes through vbmeta.

6. FIT configuration matching, and what the stock dtbo table says

The match list.

  • depthcharge builds the compatibles from coreboot’s mainboard part string, lowercased with _ → - (src/boot/fit.c:49-102). For mica that string is "Mica" (coreboot src/mainboard/google/bluey/Kconfig:317-325 at ff3258d).
  • The list is google,mica-rev<board_id>-sku<sku_id>, google,mica-rev<board_id>, google,mica-sku<sku_id> and google,mica. Entries whose id is undefined are skipped (fit.c:80-101).
  • Nothing in the bluey board code adds compatibles: fit_add_compat is called only from fit.c.
  • The ordering is documented in Documentation/arch/arm/google/chromebook-boot-flow.rst:21-49.

How a config is ranked.

  • Each FIT config is ranked by the best (lowest-index) list entry that equals any of its compatible strings (fit.c:228-289; exact string match). The best-ranked config wins; with no match, /configurations/default boots (fit.c:436-449).
  • mkimage -f auto writes conf-N { kernel, fdt, loadables, ramdisk } with no compatible (fit_image.c:482-531).
  • So depthcharge takes the “legacy FIT” branch and reads the first compatible property of the DTB’s root node. That requires an uncompressed FDT and no overlays (fit.c:248-274).
  • The draft DTB’s root compatible = "google,mica", "qcom,x1e80100" therefore matches rank 3 on any board-id and sku-id.

Where board-id and sku-id come from.

  • mica selects MAINBOARD_HAS_CHROME_EC (bluey Kconfig:79-92), which selects EC_GOOGLE_CHROMEEC (:282-287).
  • So coreboot reads board_id from the EC board version and sku_id from the EC’s SKU (bluey/boardid.c:21-24, 41-42).
  • The values are not public. depthcharge prints them on the AP console (fit.c:384-387) and writes them to /firmware/coreboot in the booted DT (chromeos-boot-path.md section 3).

Stock dtbo table (dumps/recovery-16471258/parts/dtbo_a.img, header and entries decoded 2026-10-21; version 1, 3 entries of 32 bytes). depthcharge parses v1 entries as dt_size, dt_offset, id, rev, flags, custom[3] (src/boot/android_dt_table.h:43-53). It treats rev as min_rev = rev & 0xffff, max_rev = rev >> 16; 0..0xffff means “any” (android_dtboimg.c:82-92). custom[0] & 0xff == 0 makes custom[1] a SKU, where 0xffffffff means any (:139-145, 228-229, 259-263). Compression is flags & 0xf (android_dt_table.h:35-41).

Entryidrevmin..max board_idflagsSKU (custom[1])Meaning
0 base00xffff00000..0xffff (any, no rev check)3 = LZ40xffffffff (any)every board
1 board overlay00xffff0000any3 = LZ4anyevery board
2 keyboard overlay00x000f00011..153 = LZ4anyboard_id 1-15 only
  • id is unused (0) and only echoed as an index. The table does not encode a SKU at all.
  • The only board-id information is that the keyboard-matrix overlay applies to board revisions 1-15.
  • Inference: production mica reports a board_id between 1 and 15, and rev 0 (early proto) boards had a different keyboard. The real value stays unknown until the first boot.
  • The mainline DTB does not depend on it, as shown above. A rev- or sku-specific DTB can be added later with google,mica-rev<N> compatibles.

7. What can be verified offline

CheckCommandProvesSource
DTB root compatiblefdtget mica.dtb / compatiblecontains google,mica → matches on any rev/skufit.c:49-102, 248-289
FIT layoutdumpimage -l depthcharge.fit (or mkimage -l)one kernel, one fdt, one ramdisk, conf-1fit_image.c:339-531
FIT detailsfdtget on /configurations default, /images/*/compression, absence of data-size/data-offsetdefault kept, FDT and ramdisk uncompressed, embedded datafit.c:140-165, 254-259, 484-488
Kernel magicod -A d -t x4 -j 56 -N 4 Image → 644d5241depthcharge’s header check passesboot64.c:43-44, 188-192
Signature chainvbutil_kernel --verify mica.kpart --signpubkey …/kernel_subkey.vbpubk --verbosekeyblock signed by the dev subkey, body signed by the data keyvb1_helper.c:495-640
Dev-mode pathvbutil_kernel --verify mica.kpart (no key)keyblock hash + body, which is what dev-mode firmware checksvb1_helper.c:510-528; 2load_kernel.c:164-181
Summaryfutility show mica.kpartkeyblock flags, versions, body size, cmdlinefutility/cmd_show.c:454-, file_type.inc:34
Sizebody size from --verify ≤ 0x10000000fits KERNEL_SIZE2load_kernel.c:389-392
Board rulesdepthchargectl check --config mica.ini --board custom-mica mica.kpart (mica.ini holds the section from 1; depthchargectl.rst:191-194, _check.py:223-224)exit 3 size, 4 unreadable, 5 signature, 6 format, 7 missing DTBdepthchargectl.rst:415-432
Diskcgpt show [-i 1 -t|-P|-T|-S] mica-usb.imgtype GUID and attributescgpt/cmd_show.c:19-41

Cannot be exercised without the device:

  • depthcharge’s FIT selection. tests/boot/ at 694eabf has tests for android_mte, bootconfig, commandline, memchipinfo and payload, but none for fit (gitiles tree listing, 2026-10-21). The %U substitution does have tests/boot/commandline-test.c.
  • The real board-id and sku-id.
  • depthcharge’s DT fixups on a mainline DT. dt_apply_fixups failing aborts the boot (src/arch/arm/fit.c:99-100).
  • Console output.

8. Installing the tools on Arch

ToolArch officialAURDebian sid (container)
mkimage, dumpimageextra/uboot-tools 2026.07-2 (pacman -Si, 2026-10-21)u-boot-tools 2025.01-3.2
vbutil_kernel, futility, dev keysnone (pacman -Ss vboot: no match)vboot-utils 154.16805-1, last modified 2026-10-05 00:45 EDT [04:45 UTC]vboot-kernel-utils 0~R156-16836.B+dfsg-1
cgptnonein vboot-utilscgpt 0~R156-16836.B+dfsg-1
mkdepthcharge, depthchargectlnonedepthcharge-tools 0.6.2-1, flagged out of date 2026-05-06 14:36 EDT [18:36 UTC], last modified 2024-01-05 07:01 EST [12:01 UTC]depthcharge-tools 0.6.2-5
dtc/fdtget, lz4, xzinstalled (dtc 1.8.1, lz4 1.10.0, xz 5.8.4; pacman -Q)device-tree-compiler, lz4, xz-utils

Sources: pacman -Si/-Ss/-Q on the build host; AUR RPC /rpc/v5/search and the PKGBUILDs at aur.archlinux.org (read 2026-10-21); qa.debian.org/madison.php and packages.debian.org file lists (read 2026-10-21). Debian sid depthcharge-tools depends on cgpt, vboot-kernel-utils, vboot-utils, u-boot-tools, device-tree-compiler, lz4, xz-utils, zstd and others.

AUR packages.

  • AUR vboot-utils builds release-R154-16805.B and runs make install … MINIMAL=1, then copies tests/devkeys. It depends on AUR cbfstool and chromeos-flashrom-git, which is heavy for three binaries.
  • AUR depthcharge-tools builds the 2023 v0.6.2 tag with python setup.py. It declares only python as a dependency, so mkimage, vbutil_kernel and fdtput must come from elsewhere.

Options, in order of preference.

  1. Container. pkexec docker run … debian:sid with the packages in the Answer. No host install, all official packages. vboot is R156 rather than the firmware’s branch.
    • Inference: the keyblock and preamble formats (2.1/2.2) have not changed between them, and --verify uses the same verification code the firmware links.
    • A pinned image digest or a Dockerfile in scripts/ would make it reproducible (not done here).
  2. Arch host. pacman -S uboot-tools, AUR vboot-utils, and depthcharge-tools run from the pinned checkout.
    • For example PYTHONPATH=<checkout> python3 -m depthcharge_tools.mkdepthcharge …; the module has a __main__ guard (mkdepthcharge.py:1415). Inference: untested.
    • That needs an owner OK for the installs.
  3. Scratch build. Build futil and cgpt from the vboot_reference checkout at 34fbbe6 (Makefile:360-361 defines futil). Inference: untested; it needs OpenSSL and libuuid headers.

Not found

  • Board-id and sku-id values. Neither public source nor the stock dtbo reveals them; only the keyboard overlay’s board_id range 1-15. The EC console’s cbi command would show them, but sending it needs the owner’s OK under ADR 0006.
  • A depthcharge unit test for fit.c at 694eabf, so FIT selection can only be checked by reading the code.
  • Whether dt_apply_fixups, including bluey’s CPU-map fixup, succeeds on a mainline-shaped DT. The fixup source was not located under src/board/bluey/ at 694eabf.
  • Whether depthcharge leaves uart21 clocked, so that earlycon=qcom_geni prints before the GENI driver probes.
  • Measured image sizes. There is no mica kernel build yet, so the 256 MiB margin is not measured.
  • A tested run of the container or AUR route on the build host. Nothing was installed for this note.

Console-only kernel config and boot arguments for the first mica boot

Researched 2026-10-21 for ticket 04. Sources were read on the build host. Nothing was built, and the Googlebook was not touched. Items marked inference are reasoned from source, not tested.

Answer

Derive the config with plain make defconfig. Do not trim it. At torvalds a804c57 the arm64 defconfig already builds in every driver on uart21’s supplier chain. An arm64 defconfig Image is about 50 MiB, so the 256 MiB KERNEL_SIZE gives no reason to trim. After make defconfig, check the list below in .config before building, so that a later tree or fragment cannot quietly switch one of them to =m.

Must be =y (built in). These are needed for ttyMSM0 on uart21 with no initramfs and no module loading:

Option(s)What needs itKconfig defaultdefconfig @ a804c57
SERIAL_QCOM_GENI, SERIAL_QCOM_GENI_CONSOLEthe qcom,geni-debug-uart driver, the ttyMSM console and the qcom_geni earlyconnone=y (:585-586)
QCOM_GENI_SEthe qupv3_2 wrapper (qcom,geni-se-qup); it creates the uart21 child only once it probesARCH_QCOMnot in the file, so default y
PINCTRL_MSM, PINCTRL_X1E80100TLMM: qup_uart21_default (gpio86/87, qup2_se5)ARCH_QCOMnot in the file, so default y
QCOM_PDCTLMM’s wakeup-parent = <&pdc>; TLMM probe defers until PDC existsnone=y (:1746)
COMMON_CLK_QCOM, CLK_X1E80100_GCCGCC_QUPV3_WRAP2_S5_CLK (“se”) and the wrapper’s m-ahb/s-ahb clocksGCC: ARCH_QCOM=y (:1472, :1487)
QCOM_RPMH, QCOM_COMMAND_DB, QCOM_CLK_RPMH, QCOM_RPMHPDrpmhpd CX powers both GCC and uart21, and the UART’s OPP table requires rpmhpd levels. rpmhcc and the BCM voter sit under apps_rscRPMHPD: none=y (:1660, :1654, :1496, :1678)
INTERCONNECT, INTERCONNECT_QCOM, INTERCONNECT_QCOM_X1E80100the qup-core/qup-config paths on clk_virt, gem_noc and config_nocnone=y (:1865, :1871, :1901)
ARM_SMMU (+ ARM_SMMU_QCOM, def_tristate y)iommus = <&apps_smmu 0x423 0x0> on qupv3_2none=y (:1617)
ARM_PSCI_FW (selected by arm64), ARM_PSCI_CPUIDLE, ARM_PSCI_CPUIDLE_DOMAINapps_rsc has power-domains = <&system_pd>, a PSCI domain. Without its provider, RPMh never probes, and nothing downstream does eitherDOMAIN: yARM_PSCI_CPUIDLE=y (:95); DOMAIN by default

Three entries are missing from the defconfig file only because their Kconfig default (ARCH_QCOM) already makes them y: PINCTRL_MSM, PINCTRL_X1E80100 and QCOM_GENI_SE. Do not “fix” the file by adding them. No firmware file is needed. coreboot loads the UART protocol into QUP2 SE5, and the kernel loads GENI firmware only when the SE reports no protocol.

Boot arguments (they go into the vbutil_kernel --config command line, which depthcharge writes to /chosen/bootargs. defconfig sets no CONFIG_CMDLINE):

console=ttyMSM0,115200n8 earlycon=qcom_geni,0x894000 clk_ignore_unused pd_ignore_unused

Optional for the first attempt: ignore_loglevel, and keep_bootcon (it keeps earlycon after ttyMSM0 takes over, so lines print twice). root= and init= belong to the FIT/initramfs tickets.

  • Use the explicit earlycon=qcom_geni,0x894000. A bare earlycon reads /chosen/stdout-path, and dts/x1e80100-google-mica.dts has no /chosen node yet. hamoa.dtsi:34 is chosen { };, and the 9345 file sets none either. ADR 0006 records stdout-path = "serial0:115200n8" as decided, so the DTS needs that line. That is a DTS-ticket item; this note does not edit the DTS. Update 2026-10-21: the DTS now sets stdout-path = "serial0:115200n8" (commit 709212e).
  • Earlycon does not wait for clocks. It registers from early_param before any driver probes, and its setup writes only SE registers. It sets no clock, pinmux, interconnect, OPP or baud rate. So it prints only if firmware left SE5 clocked, loaded with UART firmware, pinmuxed and running at 115200. coreboot always loads the UART firmware, but it sets the baud divider and pinmux only when its own serial console is built in. Whether mica’s production firmware does that is not known (see “Not found”).
  • ttyMSM0 does not depend on firmware state. The real console sets its own clocks, pins and 115200 baud when it probes. That happens once the chain above is up, and printk then replays the log buffer.
  • clk_ignore_unused pd_ignore_unused: the stock Googlebook kernel boots with both, and so does Ubuntu Concept’s x1e GRUB config. They are not needed for ttyMSM0 itself, because the driver claims its clocks when it probes. They matter in the failure case: if the real driver never probes, clk_disable_unused would gate the QUP clocks at late_initcall_sync (they carry no CLK_IGNORE_UNUSED) and silence the earlycon (inference).

Image size against KERNEL_SIZE. KernelCI’s arm64 defconfig build of mainline b88c841d (v7.3-rc6+, clang-21, 2026-10-11) gives:

  • Image: 52,271,616 bytes (49.9 MiB);
  • header image_size: 53,084,160 bytes (50.6 MiB);
  • Image.gz: 14.6 MiB.

depthcharge’s kernel buffer is KERNEL_SIZE = 256 MiB. It holds the whole loaded body: the FIT with its kernel, DTB and any ramdisk. An uncompressed Image uses about a fifth of it. That build is close to a804c57, not a804c57 itself.

Other trees, as facts for the kernel-tree decision:

Tree, pinConfig usedGENI serialRest of the console chainNotable differences
jhovold wip/x1e80100-6.16 @ a9cd6cda (2025-07-28)arch/arm64/configs/johan_defconfig, 429 lines=y, console =yall =yQualcomm-only, a trimmed defconfig. No ACPI, NVMe =m, PCIE_QCOM=m, DRM =m. Newest x1e branch on GitHub; repo last pushed 2025-09-19
jglathe jg/ubuntu-qcom-x1e-7.3.y @ 0151068a (2026-10-20)Ubuntu packaging, debian.qcom-x1e/config/annotations=m, console =yall =yUbuntu distro config: ACPI, EFI_ZBOOT, CMDLINE="console=ttyAMA0", NVMe =m, CROS_EC_SPI=m, TCG_TIS_I2C_CR50=m
Ubuntu Concept linux-qcom-x1e 7.0.0-22.22, qcom-x1e-7.0 @ d997b73b (2026-03-26)debian.qcom-x1e/config/annotations (flavour arm64-qcom-x1e)=m, console =yall =yACPI, EFI_ZBOOT, NVMe =m, PCIE_QCOM=y, PHY_QCOM_QMP_PCIE=y. Its resolute-x1e branch uses the generic Ubuntu config, where GCC, x1e TLMM and x1e interconnect are =m

Both Ubuntu-based configs load the GENI serial driver from an initramfs. So a first boot without an initramfs would print nothing on ttyMSM0 from them, and earlycon would be missing too (inference). For a console-only first boot, use defconfig or johan_defconfig, or add SERIAL_QCOM_GENI=y to one of the Ubuntu configs.

Sources and revisions

SourceRefCommit
torvalds/linuxmastera804c5742bff224d4cb4d25dd383554d598be32b (Makefile reads 7.3.0)
depthchargefirmware-bluey-16702.B694eabf646855067acd51935059c3191436ccc79
ChromiumOS corebootfirmware-bluey-16702.Bff3258d575b0ddf4ba0514a2cd03c6a1936dba69
jhovold/linux (GitHub)wip/x1e80100-6.16a9cd6cda46ab81b1ec1d687d40ff0933dc6e6915
jglathe/linux_ms_dev_kit (GitHub)jg/ubuntu-qcom-x1e-7.3.y0151068ae447fd8f687b57837dd4be857cff7ca8
Launchpad ~ubuntu-concept/ubuntu/+source/linux/+git/resoluteqcom-x1e-7.0d997b73be317eed5dd6e617028769fb48addc3db
same reporesolute-x1eba0042b4d0be94e27274fc336655f21fa0146027
Launchpad ~ubuntu-concept/ubuntu/+source/ubuntu-x1e-settingsdefault HEAD / ubuntu/resolutec38933d09985f3eae7f8f3982ac9d666fee101a2 / d40028294dfd621a701e67ff79aff7df011b7e79
KernelCIkbuild node 6acbaef4a3f195e1aa6556c8mainline b88c841d11641923fa3a4da011d7202cd89df68a

The kernel files came from raw.githubusercontent.com/torvalds/linux/<commit>/…, because the local sparse checkout holds only DTS and bindings. The Ubuntu trees were read from shallow sparse clones. Paths below without a repo prefix are in torvalds/linux at a804c57.

Evidence

1. The probe chain from uart21 to its suppliers (DT)

  • uart21: serial@894000 (hamoa.dtsi:1296-1318):
    • clocks = <&gcc GCC_QUPV3_WRAP2_S5_CLK>;
    • interconnects on clk_virt (MASTER_QUP_CORE_2), gem_noc and config_noc (SLAVE_QUP_2);
    • power-domains = <&rpmhpd RPMHPD_CX> and operating-points-v2 = <&qup_opp_table_100mhz>, whose OPPs require rpmhpd_opp_low_svs/svs (:671-681);
    • pinctrl-0 = <&qup_uart21_default>: gpio86 TX and gpio87 RX, function qup2_se5 (:6726-6737).
  • mica sets compatible = "qcom,geni-debug-uart" and serial0 = &uart21 (dts/x1e80100-google-mica.dts:32,1552-1555). That is the same as the 9345, CRD and devkit.
  • The parent qupv3_2: geniqup@8c0000 (hamoa.dtsi:847-862) has the clocks GCC_QUPV3_WRAP_2_M_AHB_CLK and _S_AHB_CLK, and iommus = <&apps_smmu 0x423 0x0>.
  • gcc (:759-807) has power-domains = <&rpmhpd RPMHPD_CX> (:804).
  • apps_rsc: rsc@17500000 (:8575-8607) has power-domains = <&system_pd> (:8591). Its children are apps_bcm_voter, rpmhcc and rpmhpd.
  • system_pd is a child of psci { compatible = "arm,psci-1.0"; } (:377-474).
  • cmd-db is a reserved-memory node (:517-518).
  • tlmm (:6095-6108) has wakeup-parent = <&pdc>.

Code that turns these links into hard dependencies:

  • The wrapper probe gets its clocks, then devm_of_platform_populate(). The UART child exists only after that (drivers/soc/qcom/qcom-geni-se.c:1603-1650).
  • geni_se_resources_init() gets the se clock, the OPP table and the interconnect paths (qcom-geni-se.c:1196-1226, geni_icc_get at :1220). The serial probe calls it through resources_init (drivers/tty/serial/qcom_geni_serial.c:1921).
  • TLMM returns -EPROBE_DEFER while the PDC wakeup domain is missing (drivers/pinctrl/qcom/pinctrl-msm.c:1471-1477).
  • A missing PM-domain provider sends genpd to driver_deferred_probe_check_state() (drivers/pmdomain/core.c:3362-3383). That defers, then returns -ETIMEDOUT once deferred_probe_timeout expires (drivers/base/dd.c:285-298). The default is 10 s when MODULES=y (drivers/base/Kconfig:76-79). rpmh-rsc uses pdev->dev.pm_domain (drivers/soc/qcom/rpmh-rsc.c:1124-1128). So without the PSCI domain driver, apps_rsc never probes. The domain driver matches arm,psci-1.0 and registers at core_initcall (drivers/cpuidle/cpuidle-psci-domain.c:126,192).
  • Init order when everything is built in: the RPMh core and the x1e interconnect at core_initcall (rpmh-rsc.c:1177, drivers/interconnect/qcom/x1e80100.c:1915), TLMM at arch_initcall (pinctrl-x1e80100.c:1866), GCC at subsys_initcall (gcc-x1e80100.c:7506), and the GENI wrapper as a module init (qcom-geni-se.c:1689).
  • Inference: QCOM_SCM (selected by PINCTRL_MSM and ARM_SMMU_QCOM), AOSS QMP and IPCC are not on this chain.

2. Kconfig and defconfig

  • SERIAL_QCOM_GENI is a tristate that depends on QCOM_GENI_SE. SERIAL_QCOM_GENI_CONSOLE is a bool that selects SERIAL_EARLYCON (drivers/tty/serial/Kconfig:915-929).
  • The qcom,geni-debug-uart match entry exists only with the console option (qcom_geni_serial.c:2191-2199).
  • The earlycon is OF_EARLYCON_DECLARE(qcom_geni, "qcom,geni-debug-uart", …) (:1755-1756).
  • Inference: with the driver =m, the earlycon table entry sits in the module and is never seen at early boot.
  • Defaults:
    • QCOM_GENI_SE, QCOM_COMMAND_DB and QCOM_RPMH: default ARCH_QCOM (drivers/soc/qcom/Kconfig:30-43,155-158).
    • PINCTRL_MSM: default ARCH_QCOM. The SoC drivers sit inside if PINCTRL_MSM, and PINCTRL_X1E80100 has default ARCH_QCOM (drivers/pinctrl/qcom/Kconfig:2-17, Kconfig.msm:2,582-591).
    • CLK_X1E80100_GCC and QCOM_CLK_RPMH: default ARCH_QCOM (drivers/clk/qcom/Kconfig:267-276,416-419).
    • QCOM_RPMHPD has no default (drivers/pmdomain/qcom/Kconfig:20-26). Neither does INTERCONNECT_QCOM_X1E80100 (drivers/interconnect/qcom/Kconfig:461-469), ARM_SMMU (drivers/iommu/arm/Kconfig:3-15) or QCOM_PDC (drivers/irqchip/Kconfig:523-529).
    • ARM_SMMU_QCOM is def_tristate y and depends on ARCH_QCOM (drivers/iommu/arm/Kconfig:53-59).
    • ARM_PSCI_FW is selected by ARM64 (arch/arm64/Kconfig:110). ARM_PSCI_CPUIDLE_DOMAIN is a bool with default y (drivers/cpuidle/Kconfig.arm:17-44).
  • arch/arm64/configs/defconfig sets: ARCH_QCOM=y :69, ARM_PSCI_CPUIDLE=y :95, MODULES=y :127, SERIAL_QCOM_GENI(_CONSOLE)=y :585-586, COMMON_CLK_QCOM=y :1472, CLK_X1E80100_GCC=y :1487, QCOM_CLK_RPMH=y :1496, ARM_SMMU=y :1617, QCOM_COMMAND_DB/GENI_SE=y :1654-1655, QCOM_RPMH=y :1660, QCOM_RPMHPD=y :1678, QCOM_PDC=y :1746, INTERCONNECT(_QCOM)=y :1865/:1871, INTERCONNECT_QCOM_X1E80100=y
    1. It has no CONFIG_CMDLINE line.
  • Also in defconfig, outside the console chain: CROS_EC=y and CROS_EC_SPI=y :1427-1430, TCG_TIS_I2C_CR50=m :611, BLK_DEV_NVME=m :309, PSTORE_RAM=m :1928. PSTORE_RAM matters for the ramoops fallback in ADR 0006, which the observation ticket covers.

3. Earlycon and console mechanics

  • earlycon= with no options takes the console from stdout-path. qcom_geni,<addr> starts a polled console; “the serial port must already be setup and configured. Options are not yet supported” (Documentation/admin-guide/kernel-parameters.txt:1398-1402,1515-1518).
  • Parsing:
    • setup_earlycon() tries names with an empty compatible first, then qcom_geni (drivers/tty/serial/earlycon.c:176-214).
    • 0x<addr> parses as MMIO (drivers/tty/serial/serial_core.c:2175-2176).
    • The register page goes through the fixmap (earlycon.c:39-48; FIX_EARLYCON_MEM is def_bool y, arch/arm64/Kconfig:364-365).
    • The parameter is an early_param (earlycon.c:242).
  • A bare earlycon on a DT boot with ACPI support goes through early_init_dt_scan_chosen_stdout(). That returns -ENOENT without stdout-path (earlycon.c:223-241, arch/arm64/kernel/acpi.c:231-233, drivers/of/fdt.c:950-969). arm64 selects ACPI_SPCR_TABLE when ACPI is on (arch/arm64/Kconfig:12), and defconfig has ACPI=y (:116).
  • qcom_geni_serial_earlycon_setup() (qcom_geni_serial.c:1702-1754):
    • It returns -ENXIO unless the SE already reports the UART protocol.
    • It writes only packing, FIFO and frame registers in the SE. It touches no clock, pinctrl, ICC or baud divider.
  • The real console:
    • It is named ttyMSM, and its index comes from the serial alias (:1768-1787, :1895-1897).
    • Only index 0 is valid (GENI_UART_CONS_PORTS 1 at :88, check at :1615).
    • Setup defaults to 115200 and parses console= options (:1605-1649).
  • If the SE has no protocol, the driver tries geni_load_se_firmware() (:1358-1367). That needs a firmware-name property on the wrapper (qcom-geni-se.c:1565-1567), which hamoa.dtsi does not set.
  • Boot consoles are dropped when the preferred real console registers, unless keep_bootcon (kernel/printk/printk.c:3868-3878,4211-4220).

4. What firmware leaves on SE5 (coreboot ff3258d)

  • UART_FOR_CONSOLE defaults to 21 (src/soc/qualcomm/x1p42100/Kconfig:80-82). That is QUP2 SE5, the same engine as Linux uart21.
  • load_qc_se_firmware_late() loads UART firmware into QUPV3_2_SE5 “even if coreboot’s serial output is disabled” when CONSOLE_SERIAL is off (src/mainboard/google/bluey/mainboard.c:290-297). It runs from mainboard_late_init (:320-322).
  • qupv3_se_fw_load_and_init() loads the firmware and sets up the SE (src/soc/qualcomm/common/qupv3_config.c:14-80).
  • The baud divider (from XO), the TX/RX pinmux and the watermarks are set only in uart_init() (src/soc/qualcomm/common/qupv3_uart.c:47-90). That is coreboot’s serial-console path.
  • qupv3_fw_init() turns on all QUP clocks (qupv3_config.c:255-262). Where bluey calls it was not traced.

Inference: with production firmware (serial console presumably off), the SE reports UART, so earlycon registers. Whether its output appears at 115200 depends on divider and pinmux state that coreboot does not set. The real ttyMSM0 console does not depend on any of this.

5. clk_ignore_unused and pd_ignore_unused

  • Both are documented as debug/development aids that “should not be needed on a platform with proper driver support” (kernel-parameters.txt:788-798,5279-5283).
  • Their code: clk_disable_unused() runs at late_initcall_sync (drivers/clk/clk.c:1580-1627), and genpd_power_off_unused() also at late_initcall_sync (drivers/pmdomain/core.c:1356-1387).
  • The QUP clocks have no CLK_IGNORE_UNUSED. gcc_qupv3_wrap2_s5_clk has only CLK_SET_RATE_PARENT (gcc-x1e80100.c:4900-4912). The wrapper AHB clocks are at :5014-5035.
  • Who passes them:
    • Stock Googlebook kernel: its live /proc/cmdline contains clk_ignore_unused pd_ignore_unused (recon, /proc/cmdline row).
    • Ubuntu Concept x1e: grub.d/ubuntu-x1e-settings.cfg sets GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} clk_ignore_unused pd_ignore_unused cma=128M efi=noruntime", both at c38933d0 and on ubuntu/resolute @ d4002829. The resolute copy also sets GRUB_BADRAM. The repo log has “Add efi=noruntime as recommended by jhovold” (2025-08-12).
    • Inference: efi=noruntime and the GRUB_BADRAM workaround are for UEFI boots, so they do not apply to a depthcharge DT boot.

6. Image size and depthcharge limits

  • config.mica: KERNEL_START=0xC7800000, KERNEL_SIZE=0x10000000, KERNEL_FIT_FDT_ADDR=0xDA800000, BASE_ADDRESS=0xDB000000.
  • The ldscript defines _kernel_end = CONFIG_KERNEL_START + CONFIG_KERNEL_SIZE (src/image/depthcharge.ldscript.S:38-39).
  • vboot loads into kernel_buffer = _kernel_start, size _kernel_end - _kernel_start (src/vboot/stages.c:161-162). So 256 MiB bounds the loaded partition body.
  • boot_arm_linux() reads the arm64 header and needs a contiguous free range of header image_size bytes. It picks a KASLR slot outside [min(BASE_ADDRESS, KERNEL_START, FDT_ADDR), 4 GiB) and reserved memory, then decompresses into it (src/arch/arm/boot64.c:113-210). The Image therefore runs from RAM above 4 GiB (or below 0xC7800000), not from the 256 MiB buffer.
  • FIT compression: none, lz4 or lzma (chromeos-boot-path.md).
  • Measurement (2026-10-21): KernelCI node 6acbaef4a3f195e1aa6556c8, defconfig, clang-21, artifact files.kernelci.org/kbuild-clang-21-arm64-mainline-6acbaef4a3f195e1aa6556c8/Image.gz, created 2026-10-11 11:44 EDT [15:44 UTC]. Results:
    • Image.gz: 15,284,553 bytes;
    • Image: 52,271,616 bytes;
    • header text_offset: 0;
    • header image_size: 53,084,160;
    • magic ARM\x64.

7. Other trees

jhovold:

  • johan_defconfig @ a9cd6cda sets:
    • CPU_IDLE=y :47, ARM_PSCI_CPUIDLE=y :48;
    • SERIAL_QCOM_GENI(_CONSOLE)=y :177-178, PINCTRL_MSM/X1E80100=y :188-190;
    • COMMON_CLK_QCOM=y :316, QCOM_CLK_RPMH=y :321, ARM_SMMU=y :335;
    • QCOM_COMMAND_DB/GENI_SE/RPMH/RPMHPD/PDC=y :348-363, INTERCONNECT_QCOM_X1E80100=y :381;
    • GCC by default;
    • PCIE_QCOM=m :115, BLK_DEV_NVME=m :129.
  • It has no ACPI line, and ACPI defaults to y only on x86 (drivers/acpi/Kconfig:9-17).
  • Branch head commits: “johan_defconfig: add custom config” and “arm64: defconfig: build Qualcomm PCIe driver as a module”.
  • GitHub API, 2026-10-21: the newest wip/x1e80100-* branch is 6.16 (head 2025-07-28), and the repo was last pushed 2025-09-19. kernel.org johan/linux.git only has master at 6.5-rc6.

jglathe:

  • debian/debian.env is DEBIAN=debian.qcom-x1e, and rebuild.sh runs dpkg-buildpackage.
  • debian.qcom-x1e/config/annotations sets:
    • SERIAL_QCOM_GENI m :10798, console y :10799;
    • CLK_X1E80100_GCC y :2359, PINCTRL_MSM/X1E80100 y :8781/:8973;
    • INTERCONNECT_QCOM_X1E80100 y :5729, ARM_SMMU y :1396, PSCI cpuidle y :1360-1361;
    • QCOM_RPMH/RPMHPD y :9297-9298, QCOM_PDC y :9280;
    • EFI_ZBOOT y :168, CMDLINE "console=ttyAMA0" :2384, DEBUG_INFO_NONE y :3171;
    • BLK_DEV_NVME m :51, PCIE_QCOM y :8421;
    • CROS_EC_SPI m :2859, TCG_TIS_I2C_CR50 m :12233, EC_QCOM_HAMOA n :4046.
  • Its dev-build arch/arm64/configs/ubuntu_x1e_defconfig (used by devbuildconfig.sh) also has SERIAL_QCOM_GENI=m :2290 and CMDLINE="console=ttyAMA0" :130.

Ubuntu Concept:

  • debian.qcom-x1e/config/annotations @ d997b73b (header FLAVOUR: arm64-qcom-x1e) sets:
    • SERIAL_QCOM_GENI m :10416, console y :10417;
    • CLK_X1E80100_GCC y :2139, PINCTRL_X1E80100 y :8625;
    • INTERCONNECT_QCOM_X1E80100 y :5471, ARM_SMMU y :1203, PSCI cpuidle y :1169-1170;
    • QCOM_RPMH/RPMHPD y :8940-8941, QCOM_PDC y :8922;
    • ACPI y :502, EFI_ZBOOT y :156, CMDLINE "console=ttyAMA0" :2161;
    • BLK_DEV_NVME m :48, PCIE_QCOM y :8144, PHY_QCOM_QMP_PCIE y :8340.
  • resolute-x1e @ ba0042b4 builds from debian.master (generic arm64), where these are m: CLK_X1E80100_GCC :3033, PINCTRL_X1E80100 :10897 and INTERCONNECT_QCOM_X1E80100 :7167.
  • With a CONFIG_CMDLINE set, the bootloader’s command line still wins under the default CMDLINE_FROM_BOOTLOADER (inference, from Kconfig defaults; not read for this note).

Not found

  • mica firmware serial state. Whether mica’s production coreboot is built with CONSOLE_SERIAL is unknown. So is whether depthcharge or anything after coreboot leaves SE5’s divider, pinmux and clocks usable. Without that, nobody can say whether earlycon output will be readable at 115200. A read of ttyUSB1 across a reboot answers it (silent-boot observation ticket). Where bluey calls qupv3_fw_init() was not traced.
  • No stdout-path in the mica DTS. dts/x1e80100-google-mica.dts has no /chosen/stdout-path, although ADR 0006 decided serial0:115200n8. That belongs to a DTS change, not this ticket.
  • No Image built from a804c57 itself. The closest public measurement is b88c841d (v7.3-rc6+). A defconfig build of the chosen tree should record its own Image size.
  • No current jhovold x1e tree past 6.16 on GitHub or kernel.org as of 2026-10-21.
  • Launchpad cgit for the Ubuntu Concept kernel repos returns HTTP 403 for log/tree pages. Branch heads came from code.launchpad.net ref pages, and the configs from shallow clones.
  • Interconnect lookup when the x1e provider is a module. of_icc_get()’s behaviour was not read; the “defers” claim in the table rests on the DT link plus fw_devlink, inference.

Observing a silent first boot on mica

Researched 2026-10-21 from firmware and kernel source at pinned commits, AOSP source, and the stock DT already extracted from the recovery image (stock-dtb.md). Nothing was run on the Googlebook and no SuzyQ tty was opened. Items marked inference are reasoned from source, not tested on the device.

Ticket: 05-silent-boot-observation.

Answer

If the AP console (uart21 through the SuzyQ -if01) stays silent, these channels show how far the kernel got. They are ranked by how much they tell and how little they need from the new kernel.

  1. ramoops: the kernel log of the failed boot, read on the next boot. This is the best channel.
    • depthcharge always adds /reserved-memory/ramoops (compatible = "ramoops") on mica. The buffer is the coreboot CBMEM entry RAMOOPS, 1 MiB by default, and it sits in CBMEM just below 0x866C0000.
    • The exact address is decided at boot, not fixed in source. Inference: it lies between 0x85F80000 and 0x866C0000.
    • Layout: 256 KiB console, 128 KiB ftrace, 128 KiB pmsg and two 256 KiB dump records. max-reason = 2 (oops and panic).
    • Mainline a804c57 picks the node up unchanged. The kernel needs CONFIG_PSTORE=y, CONFIG_PSTORE_CONSOLE=y and CONFIG_PSTORE_RAM=y built in. arm64 defconfig has PSTORE_RAM=m and no PSTORE_CONSOLE.
    • What to read: console-ramoops-0 holds every printk of the previous boot, up to the hang. A dmesg-ramoops-N record appears only after an oops or panic.
    • Readback:
      • (a) Stock OS, after the next boot: adb shell cat /sys/fs/pstore/console-ramoops-0. adb bugreport also carries it as “LAST KMSG”. AOSP grants the shell user both the file mode and the SELinux rule. Inference to test: the stock kernel has pstore enabled.
      • (b) The next mainline boot: /sys/fs/pstore/.
      • Update 2026-10-21 (live read): on stock build 16560795, /sys/fs/pstore is denied to adb shell, so (a) as written fails. adb shell dumpsys dropbox --print SYSTEM_LAST_KMSG returns the last 64 KiB instead (stock pstore ticket).
    • Conditions:
      • DRAM must survive the reset. Let the kernel reboot itself (panic=5, plus softlockup_panic=1 or hung_task_panic=1 for hangs). Do not hold the power button: that is an EC power-off.
      • The next boot must run the same firmware.
  2. The panel, through the firmware framebuffer.
    • coreboot brings up eDP and draws its splash into a 32 bpp linear framebuffer at 0xE4800000 (region size 0x2200000). It reports that framebuffer in the coreboot table.
    • depthcharge adds no simple-framebuffer node and does not reserve the region. At handoff it clears the screen to black. bluey registers no display driver, so depthcharge has no way to stop the scan-out. Inference: the panel is still scanning out when the kernel starts.
    • Mainline can use it with COREBOOT_TABLE, COREBOOT_FRAMEBUFFER, DRM_SIMPLEDRM and fbcon. The driver registers a simple-framebuffer device from the coreboot table, so no width, height or stride needs to go into the DTS.
    • The DTS must add a no-map reservation for 0xE4800000+0x2200000; the stock overlay does the same with its cont_splash_region. Without it simpledrm cannot probe: arm64 ioremap refuses memory in the linear map.
      • Correction 2026-10-21: the stock splash_region (entry01-overlay.dts:40-42) has the same address, but its size is 0x21c0000 and it has no no-map. The first-boot DTB uses coreboot’s 0x2200000 with no-map (decision).
    • Keep the lid open: coreboot skips display init when the lid is closed.
    • Inference: add clk_ignore_unused pd_ignore_unused, and do not let the DT turn off the eDP rails, or the kernel cuts the display’s clocks and power.
  3. EC console (SuzyQ -if02), passive.
    • It logs every AP power-state change (power state N = S0…) and AP resets (AP_RST_L transitions). It also logs POWER_GOOD loss and the 8 s long-press power-off.
    • It does not log kernel progress. Host-command tracing is off by default and cannot be turned on through a read-only console.
    • Use it as a heartbeat: a kernel that reaches panic=5, or an initramfs that runs reboot -f or poweroff -f on purpose, shows up as a reset or an S0→S5 transition.
  4. Firmware log of the previous boot (/sys/firmware/log).
    • coreboot’s CBMEM console keeps appending across warm reboots. The next boot’s log therefore holds the failed boot’s firmware lines, including depthcharge’s Choosing best match … and jumping to kernel.
    • It proves depthcharge picked our FIT configuration and jumped. It holds no kernel output.
    • It is readable on a mainline boot with COREBOOT_MEMCONSOLE. adb shell on the stock OS is denied (recon).
  5. Glowbar / service-LED codes: firmware stage only.
    • 4,3 matches coreboot’s mica-only LCD check, MAINBOARD_LCD_FAIL_DETECT. When eDP EDID/DPCD read or link training fails in coreboot, it sends the EC lightbar sequence 101 (inference that 101 is the 4,3 pattern).
    • 3,5 is the EC’s power-on timeout. Inference: it is the POWER_GOOD not seen in time path.
    • A ramdump pattern (sequence 55) exists, but mainline leaves Qualcomm download mode off.
    • None of these reports kernel progress. The pattern table is in the EC’s private module.
  6. GSC ccdstate: coarse. The AP: line says whether the AP is powered and out of reset. In cr50 source it follows TPM_RST_L; Ti50 is closed (inference that Ti50 behaves the same). It cannot tell a hung kernel from a running one. Its output also prints pcr0, which must be redacted before anything is saved.

For the runbook (inference, for the console-config and failure-playbook tickets):

  • Kernel config: PSTORE, PSTORE_CONSOLE, PSTORE_RAM, COREBOOT_FIRMWARE (the menu that gates the next three), COREBOOT_TABLE, COREBOOT_MEMCONSOLE, COREBOOT_FRAMEBUFFER, DRM_SIMPLEDRM and FRAMEBUFFER_CONSOLE, all =y.
  • Command line: panic=5 softlockup_panic=1, plus clk_ignore_unused pd_ignore_unused while the framebuffer is the display.
  • DTS: a no-map region at 0xE4800000.
  • After a silent boot: watch the EC console for the reset, let the device fall through to the stock OS, and run adb shell cat /sys/fs/pstore/console-ramoops-0.
    • Pull the USB drive first, or make sure the internal disk is the default: another mainline boot overwrites the zone.
    • Read it before any further reboot: the stock kernel’s own log replaces it on the next boot.

Sources and revisions

RepoCommitRead how
depthcharge firmware-bluey-16702.B694eabf646855067acd51935059c3191436ccc79blobless clone, 2026-10-21
ChromiumOS coreboot firmware-bluey-16702.Bff3258d575b0ddf4ba0514a2cd03c6a1936dba69blobless sparse clone
ChromiumOS EC firmware-bluey-16702.B-main19b76df3d2722a371141bbac61bf14be79f7a44eblobless sparse clone
ChromiumOS EC cr50_stab (GSC analogue)ffacd3c99e6a9090a61439d58d46099f0b5ed09egitiles
Linux (torvalds)a804c5742bff224d4cb4d25dd383554d598be32braw files at the commit
AOSP platform/system/core maina3b721a32242006b59cb12bd62c9133632af3a2dgitiles
AOSP platform/system/sepolicy main4571ddd9440721fec583c906a337de949a77749egitiles
AOSP platform/packages/modules/adb main1cf2f017d312f73b3dc53bda85ef2610e35a80e9gitiles
AOSP platform/frameworks/native main4f463a6b1de9198963dc6aff74154a504ba3f8f6gitiles

The 16702.B commits are the ones pinned in chromeos-boot-path.md. The AOSP commits are main-branch heads on 2026-10-21. The Googlebook OS build is Android 17 based and its exact AOSP revision is not public, so AOSP facts are inference for the device until read live.

1. ramoops

Where depthcharge puts it

  • The node. ramoops_fixup first deletes any existing node with compatible = "ramoops" (depthcharge src/boot/ramoops.c:47-50). It then calls dt_add_reserved_memory_region(tree, "ramoops", "ramoops", start, size, false) (:52-53).
  • Its shape. The helper creates /reserved-memory/ramoops with a compatible string and reg, and adds no-map only when asked (coreboot src/commonlib/device_tree.c:2171-2198). Here it is not asked, so the node has no unit address and no no-map.
  • Properties (ramoops.c:36-40, 59-68):
    • record-size = 0x40000, console-size = 0x40000, ftrace-size = 0x20000, pmsg-size = 0x20000;
    • max-reason = CONFIG_RAMOOPS_DUMP_REASON;
    • an empty dump-oops.
  • max-reason. RAMOOPS_DUMP_REASON defaults to 2, or 4 with RAMOOPS_DUMP_REASON_MAX (src/boot/Kconfig:124-135). configs/config.mica sets neither, so mica gets 2 = KMSG_DUMP_OOPS (kernel include/linux/kmsg_dump.h:23-30), unless the private board overlay adds RAMOOPS_DUMP_REASON_MAX.
  • Address and size come from coreboot’s table: lib_sysinfo.ramoops_buffer and ramoops_buffer_size (ramoops.c:76-77). The region is marked used (:87), so depthcharge’s memory wipe skips it. The wipe runs on every non-normal boot, developer mode included (src/vboot/stages.c:66-71), and it subtracts used ranges first (src/vboot/util/memory.c:98-100).
  • Built for mica, on both boot paths. ramoops.c is built when KERNEL_FIT=y (src/boot/Makefile.inc:22), and config.mica sets it. boot() applies the DT fixups both for an Android DT (bi->dt) and for a FIT (src/arch/arm/fit.c:57-99). So the stock Android kernel gets the same node as ours.
  • The buffer is also covered by a CBMEM reservation. dt_update_memory adds every CBMEM range as a mapped region@<addr> reservation (src/boot/dt_update.c:183-193, 219-220).

coreboot side:

  • ramoops_alloc does cbmem_add(CBMEM_ID_RAM_OOPS, CONFIG_CHROMEOS_RAMOOPS_RAM_SIZE) at BS_DEV_INIT_CHIPS. lb_ramoops then publishes it as LB_TAG_RAM_OOPS (src/vendorcode/google/chromeos/ramoops.c:12-45), called from src/lib/coreboot_table.c:571-572.
  • CHROMEOS_RAMOOPS defaults to y and its size to 0x00100000 (src/vendorcode/google/chromeos/Kconfig:25-32). bluey’s Kconfig does not override it (src/mainboard/google/bluey/Kconfig). The board’s coreboot defconfig is in the private overlay, so 1 MiB is inference (the default).
  • Where CBMEM sits. cbmem_top_chipset() returns _dram_pil (src/soc/qualcomm/x1p42100/cbmem.c:7-10), which is 0x866C0000 (memlayout.ld:289). CBMEM sits below that.
  • The address is stable, not fixed. coreboot itself notes that entries created early are “more likely to stay in the same place” across boots (src/lib/cbmem_console.c:169-173). Inference: the same firmware build gives the same ramoops address on every boot.
  • A bound from the stock DT. The stock base DT reserves wlan-msa@85380000 with size 0xc00000 (ending at 0x85F80000) and adsp-boot-dtb@866c0000. Inference: the stock kernel would collide with CBMEM if CBMEM reached below 0x85F80000, so ramoops lies in [0x85F80000, 0x866C0000).
  • Mainline overlaps. Mainline hamoa.dtsi has no no-map region in that window. Its nearest are spu-secure-shared-memory@85300000 (size 0x80000) and adsp-boot-dtb@866c0000 (hamoa.dtsi reserved-memory, read from .firecrawl/torvalds-a804c57-hamoa.dtsi).

Does mainline pick it up unchanged?

Yes, with the right config.

  • Binding. reserved-memory/ramoops.yaml wants a /reserved-memory child with compatible = "ramoops" and reg, plus at least one size (:9-13, 33-62, 113-121). max-reason defaults to 2 (:76-86).
    • dump-oops is not in the binding (unevaluatedProperties: false, :111). depthcharge adds it at boot, so it never reaches our dt-validate run. ram.c ignores it; it reads only the deprecated no-dump-oops (fs/pstore/ram.c:672-678).
  • Device creation. of_platform_default_populate_init creates platform devices for /reserved-memory children that match "ramoops" (drivers/of/platform.c:490-499, 567-571). It is an arch_initcall_sync (:604). The driver matches "ramoops" (fs/pstore/ram.c:898).
  • Parsing. ramoops_parse_dt takes the reg resource and reads each size and max-reason (ram.c:646-697). Zones are laid out as dump records first, then console, pmsg and ftrace (ram.c:784-812). With depthcharge’s sizes, that leaves 0x80000 for dumps, two 256 KiB records. On probe it prints ramoops: using 0x…@0x…, ecc: 0 (:869), which gives the live address on the first mainline boot.
  • Old contents survive probe. A zone whose header carries the DBGC signature is kept as the previous boot’s record (fs/pstore/ram_core.c:39, 526-557). The files are named <type>-ramoops-<id> (fs/pstore/inode.c:365-368).
  • The console zone gets everything. The pstore console registers with CON_PRINTBUFFER (fs/pstore/platform.c:423), so it replays the log buffer from boot. A hang before the ramoops probe (arch_initcall_sync) leaves the zone with the previous run’s content. That stage needs the AP console or earlycon.
  • Config.
    • PSTORE, PSTORE_CONSOLE and PSTORE_RAM all default to off (fs/pstore/Kconfig:2-4, 37-39, 68-70).
    • arm64 defconfig has only CONFIG_PSTORE_RAM=m (arch/arm64/configs/defconfig:1928). A module loads too late for early hangs, so the runbook config should set all three =y.

Will DRAM survive the reset?

  • The platform keeps DDR across a warm reset. bluey’s romstage checks the download-mode cookie in shared IMEM and, when set, loads Qualcomm’s XBL ramdump image to dump memory (src/mainboard/google/bluey/romstage.c:411-426, 471-486; src/soc/qualcomm/common/qclib.c:566-585). That only works if DDR contents survive a warm reset into coreboot. coreboot also tells warm from cold resets (romstage.c:459-462).
  • EC-initiated resets are cold on bluey. PLATFORM_EC_POWERSEQ_QC_EXP_WARM_RESET=n with the comment “Bypassing warm reset as it’s currently unreliable” (EC zephyr/program/bluey/program.conf:42-43). warm_reset_seq() then logs Warm reset is disabled, falling back to cold reset (power/qcom_exp.c:1041-1084). A long press powers the AP off (qcom_exp.c:11-23).
  • An AP-initiated reset does not go through that path. The EC only counts AP_RST_L transitions (qcom_exp.c:262-288).
  • What the kernel sends. It issues PSCI SYSTEM_RESET, or SYSTEM_RESET2 (warm) with reboot=warm when the firmware supports it (drivers/firmware/psci/psci.c:309-325).
  • Inference: a kernel-initiated reboot (panic=N) keeps ramoops. A power-button hold or an EC apreset may not. Whether PSCI SYSTEM_RESET is warm here is decided in closed Qualcomm firmware. Test it on the first boot that reaches a shell: write a marker to /dev/kmsg, reboot, and look for it.

Reading it back

WhereHowStatus
Stock OS after the failed bootadb shell cat /sys/fs/pstore/console-ramoops-0, or adb bugreport (“LAST KMSG”)Inference to test
Next mainline bootcat /sys/fs/pstore/console-ramoops-0, dmesg-ramoops-*Source-verified, needs config above
Firmware console logNot possible: the kernel log never goes into the CBMEM consolen/a

Stock-OS evidence (AOSP):

  • Mount and file modes. init.rc mounts pstore at /sys/fs/pstore as system:log 0550. It makes console-ramoops, console-ramoops-0 and pmsg-ramoops-0 system:log 0440 (system/core rootdir/init.rc:396-405).
  • Group. adbd runs the shell with supplementary group AID_LOG (adb daemon/main.cpp:103, 115).
  • SELinux. sepolicy labels pstore pstorefs (private/genfs_contexts:333) and allows shell pstorefs:dir search and shell pstorefs:file r_file_perms (private/shell.te:296-299). A direct cat of the file should work. ls of the directory may be denied, since the shell has only search.
  • bugreport. dumpstate dumps console-ramoops or console-ramoops-0 as “LAST KMSG” (cmds/dumpstate/dumpstate.cpp:172-173, 994-1001). Its domain has the same read rule (private/dumpstate.te:213-215).
  • Unknown: whether the stock kernel builds PSTORE_RAM and PSTORE_CONSOLE. A one-line live check after any stock reboot answers it: adb shell 'wc -c < /sys/fs/pstore/console-ramoops-0'. It is a read; it is not run here.
  • One shot. Only the first boot after the failure holds the mainline log. Each kernel saves the old zone at probe and then writes its own console over it (ram_core.c:526-557). So the next boot must be the stock OS (pull the USB drive or keep the internal disk as the default), and the file must be read before another reboot.
  • Unlocking wipes data. Unlocking wipes user data (ADR 0001), so adb has to be paired again before this channel works.
  • Address. The ramoops node has no unit address, so even a listing of /sys/firmware/devicetree/base/reserved-memory would not give the address. The recon already shows the stock shell is denied DT property reads (recon).

2. Framebuffer

  • coreboot sets it up.
    • x1p42100 selects HAVE_LINEAR_FRAMEBUFFER, MAINBOARD_HAS_NATIVE_VGA_INIT and MAINBOARD_FORCE_NATIVE_VGA_INIT (src/soc/qualcomm/x1p42100/Kconfig:22-26).
    • The memory layout places FRAMEBUFFER(0xE4800000, 0x2200000) (memlayout.ld:296).
    • bluey’s display_startup() (src/mainboard/google/bluey/display.c:158-185) brings up MDSS and eDP. It reads EDID, sets 32 bpp and registers the framebuffer for the coreboot table (fb_new_framebuffer_info_from_edid, :179-180). It then renders the splash and turns on the backlight (:131-156).
    • It runs on a normal or no-battery boot (mainboard.c:332-333). vboot always requests display on this board (VBOOT_ALWAYS_ENABLE_DISPLAY, bluey Kconfig:307-311; src/security/vboot/vboot_logic.c:311-313).
    • It is skipped when the lid is closed (display.c:160-163).
  • It is not reserved. soc_read_resources lists the firmware carve-outs as reserved RAM, and the framebuffer is not among them (src/soc/qualcomm/x1p42100/soc.c:74-114). So depthcharge’s /memory covers it as ordinary RAM. The stock overlay reserves splash_region at 0xe4800000, size 0x21c0000, labelled cont_splash_region (stock DT overlay entry 1, fragment@1), which is the same buffer.
  • depthcharge adds no framebuffer node.
    • A search of the whole depthcharge tree at 694eabf finds no simple-framebuffer or simplefb.
    • Only MediaTek rauru adds a framebuffer reservation (src/board/rauru/board.c:33-59). bluey’s board.c registers no display ops, and src/drivers/video/ has no Qualcomm X1 driver.
    • At handoff, display_cleanup calls ui_display_clear(), which paints black (src/vboot/ui/display.c:372-376). It then calls display_stop(), which with no ops only logs “not implemented” (src/drivers/video/display.c:27-33, 82-108).
    • Inference: the MDP keeps scanning out the black framebuffer with the backlight on when the kernel starts.
  • Mainline can reuse it without a DT node.
    • depthcharge writes /firmware/coreboot (compatible = "coreboot", reg = coreboot table and CBMEM) (src/boot/coreboot.c:30-70). Mainline’s coreboot_table driver matches it (drivers/firmware/coreboot/coreboot_table.c:261-262).
    • COREBOOT_FRAMEBUFFER (drivers/firmware/coreboot/Kconfig:83-91) registers a simple-framebuffer platform device from LB_TAG_FRAMEBUFFER, or coreboot-framebuffer with DRM_COREBOOTDRM (framebuffer-coreboot.c:107-155).
    • DRM_SIMPLEDRM binds it (drivers/gpu/drm/sysfb/Kconfig:57-64).
    • arm64 defconfig builds the coreboot table only as a module (defconfig:270-272).
  • The no-map reservation is required, not optional.
    • A device from the coreboot table carries only a memory resource. simpledrm therefore maps it with devm_ioremap_wc and fails the probe when that returns NULL (drivers/gpu/drm/sysfb/simpledrm.c:775-810).
    • arm64 ioremap returns NULL for any page in the linear map (“ioremap attempted on RAM pfn”, arch/arm64/mm/ioremap.c:26-29). Since depthcharge leaves 0xE4800000 as plain RAM, the DTS must carve it out with no-map.
  • A hand-written simple-framebuffer node is the alternative. The binding needs reg, width, height, stride and format. It accepts memory-region, clocks, power-domains and *-supply (Documentation/devicetree/bindings/display/simple-framebuffer.yaml:58-152).
    • Width and height come from the panel EDID at boot. They are not in source, so the coreboot-table route needs fewer guesses.
  • Keeping it alive (inference). Unused-clock and power-domain cleanup and the regulator core can switch off the display path the firmware left on. Use clk_ignore_unused pd_ignore_unused and do not declare the eDP rails as switchable regulators with no consumer. DRM_MSM is a module in defconfig (:1012), so it will not take over unless loaded.

3. EC console

Source: EC 19b76df (power/qcom_exp.c, power/common.c). bluey sets CONFIG_AP_ARM_QUALCOMM_QC_EXP=y (zephyr/program/bluey/program.conf:34).

EC messageMeaningSource
power state N = S0, in 0x… (and S5, G3)every chipset state changepower/common.c:696
POWER_GOOD seen / POWER_GOOD not seen in timeAP power-on succeeded / timed outqcom_exp.c:874-878
AP running ...power-on sequence doneqcom_exp.c:1221
AP_RST_L transitions not expected: Nan AP reset with an unusual pulse countqcom_exp.c:252
power waiting for long press …, power off after long press …the 8 s power-button offqcom_exp.c:976-987
POWER_GOOD is lostthe AP dropped its power-good: shutdownqcom_exp.c:995
chipset_reset(N) then Warm reset is disabled, falling back to cold resetEC-initiated resetqcom_exp.c:1081, 1107
  • Host-command tracing is off by default. HCDEBUG_OFF is the default unless PLATFORM_EC_BRINGUP (zephyr/Kconfig:557-562), and turning it on needs a console write. The SuzyQ EC console is receive-only while CCD is locked (ccd-gsc.md: UARTEC without +TX).
    • So the EC console cannot show the kernel’s cros-ec traffic.
    • It does show a kernel that reboots or powers off on purpose.
  • The stock log seen 2026-10-21 (I: ADSP: Charging from USB0) comes from the ADSP, which coreboot starts. It says nothing about the kernel.

4. Firmware console log

  • It persists. The CBMEM console “will persist across multiple boots and append to the previous log” when the storage allows (src/lib/cbmem_console.c:10-17).
    • A valid header found at the new CBMEM console address is kept, not reset (:58-79).
    • The console is created early “so that the console region … [is] more likely to stay in the same place” (:159-173).
    • The same DRAM-retention condition as ramoops applies.
  • What it shows of the failed boot. depthcharge prints Choosing best match <config> for compat <string>. (src/boot/fit.c:439) and jumping to kernel (src/arch/arm/boot64.c:215).
  • Where to read it. Mainline exposes it as /sys/firmware/log with COREBOOT_MEMCONSOLE (drivers/firmware/coreboot/Kconfig:93-101; memconsole.c:29-42). The stock adb shell is denied that file (recon).
    • Firmware fastboot’s oem logs is another reader, but it needs the USB-Ethernet route (boot path, section 4).
  • On the AP UART. coreboot loads the console UART’s QUP firmware “even if coreboot’s serial output is disabled” (src/mainboard/google/bluey/mainboard.c:290-297, QUPV3_2_SE5). That is uart21 (ADR 0006). Whether production firmware prints there (CONSOLE_SERIAL) is in the private defconfig. The stock-boot capture in ccd-gsc.md answers it.

5. Glowbar and service-LED codes

Dell’s codes (project log, Service Manual page 64): 3,5 power-rail failure (an SoC power-on timeout seen by the EC); 4,3 LCD failure (EDID/DPCD read or eDP link training failed; it boots on after 10 minutes).

  • mica’s LED setup. mica selects EC_GOOGLE_CHROMEEC_LED_CONTROL, MAINBOARD_LCD_FAIL_DETECT and MAINBOARD_RAMDUMP_LED_NOTIFICATION (bluey Kconfig:79-92).
  • The LCD check. It is documented as reading EDID/DPCD and running link training in the preboot phase, then telling the EC (Kconfig:209-216). Its sequence number is MAINBOARD_LIGHTBAR_CMD_SEQ_DIAG_LCD, 101 on mica (:218-224).
    • qcom_mdss_edp_init sends it when edp_ctrl_init fails (display.c:72-83).
    • The call is EC_CMD_LIGHTBAR_CMD / LIGHTBAR_CMD_SEQ (src/ec/google/chromeec/ec.c:2266-2284).
    • Inference: sequence 101 is Dell’s 4,3. It reflects coreboot’s eDP bring-up, before any kernel runs.
  • The ramdump pattern. On download-mode entry coreboot sends sequence 55 (MAINBOARD_LIGHTBAR_CMD_SEQ_RAMDUMP, Kconfig:387-402; romstage.c:476-482). Mainline’s qcom_scm.download_mode defaults to 0 (drivers/firmware/qcom/qcom_scm.c:41, 2749-2750), so a mainline panic should not trigger it.
  • 3,5 lives in the EC. The public lightbar sequence list stops at 12 (include/lightbar_msg_list.h:9-22). mica builds with CONFIG_GOOGLE_PRIVATE=y (zephyr/program/bluey/mica/project.conf:9), and sequences 55 and 101 are not public. Inference: 3,5 is raised from the EC power sequencer’s POWER_GOOD not seen in time path (qcom_exp.c:874). That would be a firmware/PMIC-stage failure, not a kernel one.
  • A deliberate beacon (inference, untested). Mainline’s cros-ec-lightbar is added when the EC reports EC_FEATURE_LIGHTBAR (drivers/mfd/cros_ec_dev.c:299-311). Its sequence sysfs file accepts a number (drivers/platform/chrome/cros_ec_lightbar.c:433-470). If mica’s EC advertises the feature, an initramfs could flash the Glowbar at a checkpoint. That needs a working cros-ec-spi, so it is a late-stage signal.

6. GSC ccdstate

  • What ccdstate prints. In cr50 (the public GSC firmware), ccdstate prints the AP state, the AP UART state, pcr0, the EC, servo and Rdd states, CCD_MODE and the state flags (board/cr50/rdd.c:473-490).
  • How the AP state is tracked. It follows TPM_RST_L: AP on when it deasserts, AP off after it has been asserted for a while (board/cr50/ap_state.c:119-132, 186-187, 240). The AP: line adds K or F for kernel or firmware (:24-29).
  • Ti50. Ti50’s source is not public. The 2026-10-21 read showed AP on (ccd-gsc.md). Inference: Ti50 uses the same signal.
  • Use.
    • It shows whether the AP is held in reset or powered off. It cannot show where a running kernel stopped.
    • The command is on ADR 0006’s allowed list.
    • ccdstate output includes pcr0, a per-unit value: redact it before saving.

Not found

  • The live ramoops address and size. CBMEM allocation is dynamic. The first mainline boot prints it (ramoops: using …). The stock shell cannot read the DT.
  • mica’s coreboot defconfig (private overlay): CHROMEOS_RAMOOPS_RAM_SIZE, CONSOLE_SERIAL, CONSOLE_CBMEM_BUFFER_SIZE and BMP_LOGO.
  • Whether the stock Googlebook kernel builds PSTORE_RAM/PSTORE_CONSOLE and mounts pstore. AOSP policy allows the read; the kernel config is not public.
  • Whether PSCI SYSTEM_RESET on this firmware keeps DDR. It is decided in Qualcomm firmware outside coreboot’s public tree. Only an on-device test answers it.
  • The EC Glowbar pattern table (sequences 55, 101 and the 3,5 trigger). It is in the EC google-private module.
  • Whether mica’s EC advertises EC_FEATURE_LIGHTBAR.
  • Ti50 ccdstate semantics. No public Ti50 source; cr50 is used as the analogue.
  • What writes the 64 KiB dram_dc_log region at 0x81CE4000. coreboot reserves it (src/soc/qualcomm/x1p42100/memlayout.ld:281, soc.c:97), and the stock DT names it depthcharge-log (no-map). Mainline hamoa.dtsi calls the same address uefi-log. No writer exists in depthcharge 694eabf or coreboot ff3258d.

SCMI over the CPUCP mailbox: intermittent timeouts at boot

Researched 2026-11-03 from the kernel source at the pin (a804c5742bff, Makefile says 7.3.0; it sits between v7.3 and v7.4-rc1), torvalds master via the GitHub mirror (head 05441d697f7f, 2026-11-03), the upstream review threads, and the attempt -8/-9 logs. lore.kernel.org, patchwork and git.kernel.org sit behind a bot check (Anubis), so threads were read on the LKML mirror lkml.rescloud.iu.edu and on lists.infradead.org; lore Message-IDs are given where the mail quotes them. Mirror dates are US Eastern. Read 2026-11-03. The device was not touched.

Summary

  1. Attempt -9 shows a slow CPUCP, not lost interrupts. Replies came later than the 30 ms timeout between 0.37 s and 0.81 s (how much later is not in the log), while some messages in the same window succeeded, and the TX channel was still held when the next request was due (section 1). Inference from the log and shmem.c:88-119.
  2. Every upstream x1e80100 SCMI/CPUCP fix is already at the pin. The one later mailbox fix (readq(), v7.4-rc1) does not touch the SCMI reply path. No upstream patch to backport was found (section 2).
  3. No upstream x1e board overrides the scmi node. Nor does the stock mica tree (section 3).
  4. Upstream never reported this boot-time stall. Their x1e timeouts were on resume (IRQ disabled, fixed) and on PERF_LEVEL_GET by regular message (firmware crash, fixed by a quirk). Both fixes are at the pin (section 4). No primary report of an intermittent boot-time -110 on X1 laptops was found (section 5).
  5. Hazard: a bad boot reads the firmware version as 0x0. The FastChannel quirk then stays off, and PERF_LEVEL_GET falls back to the regular message that Qualcomm says crashes this firmware (section 4).

Ranked recommendation

  1. Measure first. Boot the same image several more times to get a failure rate. Add CONFIG_ARM_SCMI_DEBUG_COUNTERS=y and the scmi trace events so each failure shows which message and how late the reply was.
  2. Then set arm,max-rx-timeout-ms on mica’s scmi node, sized from the measured latency (a bounded value such as 100-250 ms, not i.MX’s 5000). The property is in the binding and driver at the pin. A boot passes only if it prints the new SCMI max-rx-timeout: <N>ms (driver.c:3205-3208), Firmware version 0x20000 and Enabling SCMI Quirk [quirk_perf_level_get_fc_force].
  3. Guard against the hazard: treat Firmware version 0x0 or a missing FC quirk line as a failed boot, and never rebind scmi-cpufreq in such a boot. A local kernel/patches/ file that matches the quirk without the version range is optional (section 6).
  4. One-off diagnostics, not configs to keep: arm,no-completion-irq (polling) and cpuidle.off=1 A/B boots.

1. What -8 and -9 show

Source: dumps/firstboot/2026-11-03-netlog.txt (gitignored), the scmi-dmesg capture of each boot. Same config; -9 adds only kernel/patches/0001 (panel-edp). Kernel CONFIG_HZ=250 (build-a804c57/.config:483).

-8 (good)-9 (bad)
Using scmi_mailbox_transport0.358 s0.367 s
First timeoutnone0.437 s, then every ~36 ms (30 ms timeout rounded up to 8 jiffies at 250 Hz, plus one)
Base protocol line0.361 s, Firmware version 0x200000.541 s, Firmware version 0x0, after 3 timeouts
Quirks enabledperf_level_get_fc_force, clock_rates_triplet_out_of_specclock_rates_triplet_out_of_spec only
Late repliesnoneMessage for 7 at 0.579 s, 9 at 0.650 s, 32 at 0.809 s
Perf protocolnormalFailed to query supported version for protocol 0x13 (0.613 s)
scmi-cpufreq3 policies-110 at 0.649 s, never retried
Initialized 3 performance domains0.376 s0.806 s

What the log and the source say:

  • The boot waited on SCMI. In -9 the only other kernel line between 0.369 s and 0.805 s is Freeing initrd memory, and simpledrm comes at 0.807 s (0.377 s in -8). All SCMI work took 18 ms in -8.
  • The firmware held the channel. Two timeouts are 68 ms apart (0.473 s to 0.541 s), not 36 ms. shmem_tx_prepare() busy-waits up to 2 × the timeout for the platform to set CHANNEL_FREE before it writes a new request (drivers/firmware/arm_scmi/shmem.c:94-119). It did not give up (no Timeout waiting for a free TX channel !). Inference: CPUCP was still busy with the request that had timed out, so the next request waited about 32 ms. The same gap fits one slow reply (~32 ms, under the timeout) between two timeouts; both readings mean a slow firmware. The first gap (0.369 s to 0.437 s) is the same shape.
  • The interrupt path works. The late replies are reported through the IRQ path (transports/mailbox.c:54-78 to driver.c:1012-1023). Messages in the same window also succeeded: the OPP lines at 0.656-0.660 s need several PERF replies. A lost or disabled IRQ would fail every message.
  • “Message for N type 0 is not expected!” means a reply came for a token the kernel had already given up on (scmi_xfer_command_acquire(), driver.c:1012-1023; type 0 = command reply). The source describes this case: “a spurious IRQ from a previous timed-out reply which arrived late” (transports/mailbox.c:58-66).
  • Why it can happen this early (inference): on mica, SCMI is built in and binds at 0.36 s, right after depthcharge, which loaded CPUCP through coreboot (el2-handoff.md). On the UEFI devkit upstream tested, SCMI bound at 7.9 s (Marc Zyngier’s dmesg, section 4), so upstream may never run in this early window.

2. Upstream fixes and whether the pin has them

Presence was read in the pinned source; the landing release is the first tag that contains the commit (GitHub compare API against torvalds tags).

CommitTitleReleaseAt a804c57
d2fab3fc27cbmailbox: qcom-cpucp: Mark the irq with IRQF_NO_SUSPEND flag (Sibi Sankar, 2024-10-30; fixes the x1e resume timeout)v6.12yes, drivers/mailbox/qcom-cpucp-mbox.c:176
da1642bc97c4firmware: arm_scmi: Queue in scmi layer for mailbox implementation (timeouts no longer start while queued)v6.12yes, transports/mailbox.c:288-305
a0a18e91eb3afirmware: arm_scmi: Reject clear channel request on A2Pv6.12yes, driver.c:1067-1077
1780e411ef94 + 54962707f8b8Use max-rx-timeout-ms from devicetree; Use vendor string in max-rx-timeout-msv6.12yes, driver.c:3187-3190; binding Documentation/devicetree/bindings/firmware/arm,scmi.yaml:127-132
5d8a76622658 + e2261bb81e0dSkip opp duplicates; Report duplicate opps as firmware bugs (the [Firmware Bug] … opps_by_lvl lines)v6.12yes (perf.c, the -8 log lines)
c23c03bf1faaFix timeout checks on polling pathv6.15yes (polling path only)
487c407d57d6, 94a263f981a3, 397f802d06c4quirk framework; Ensure that the message-id supports fastchannel; quirk: Force perf level get fastchannel (Johan Hovold)v6.16yes, quirks.c:173-174, driver.c:1924-1951
06e3c7ec8045 + 892c83aa39cbarm64: dts: qcom: x1e80100: Add cpucp mailbox and sram nodes; Enable cpufreqv6.16yes, hamoa.dtsi:338-351, :8568-8573, :8758-8776
0fac05fdd9afRework protocol version negotiation logic (source of Trying version 0x40000)v7.0yes, driver.c:2169-2211
d8283ac2c8fbImplement arm,no-completion-irq propertyv7.1yes, driver.c:3202-3203, binding :149-154
3690aaa6d18f + fc4f2f995302qcom-cpucp: fix PREEMPT_RT self-deadlock in IRQ handler; handle NULL data in send_data callbackv7.3yes, qcom-cpucp-mbox.c:69-84, :120-126
74f1cf02d9femailbox: qcom-cpucp: read mbox hardware register using readq()v7.4-rc1no: qcom-cpucp-mbox.c:64 still reads 32 bits at +0x4

Notes:

  • 74f1cf02d9fe does not affect SCMI. It changes only the value handed to the client. The SCMI mailbox client ignores that value: its rx_callback() reads the header from shared memory (transports/mailbox.c:54-78).
  • No newer change in master. The other arm_scmi commits between the pin and master add ACPI support and fix raw mode (e.g. f35ee1a26795, 4f5f291a5704, 0543fc1443de). None touches timeouts or the mailbox path. linux-next could not be read (git.kernel.org bot check), so changes queued for 7.5 are unknown.
  • The default timeout is 30 ms for the mailbox transport (transports/mailbox.c:383, “We may increase this if required”), with no Qualcomm-specific value anywhere.

3. Device trees: does anyone set something different?

  • The upstream SoC node is hamoa.dtsi:338-351: mboxes = <&cpucp_mbox 0>, <&cpucp_mbox 2>, shmem cpu_scp_lpri0/1 in sram@18b4e000, only protocol@13. The node has no label.
  • No x1e/x1p board file and no x1-el2.dtso mentions scmi or cpucp (rg over arch/arm64/boot/dts/qcom/x1* and purwa*).
  • The other Qualcomm SCMI nodes at the pin (sm8750.dtsi:225-238, glymur.dtsi:409-422, kaanapali.dtsi:230-243) set no timeout, no atomic-threshold-us and no arm,no-completion-irq.
  • The only users of the properties in arm64 are the i.MX SoCs with arm,max-rx-timeout-ms = <5000> (freescale/imx95.dtsi:346, imx94.dtsi:84, imx952.dtsi:216) and one Renesas board with arm,no-completion-irq (renesas/r8a78000-ironhide.dts:31).
  • Stock mica (dumps/recovery-16471258/dtbo/entry00-base.dts:384-403) uses the same mailbox channels (<cpucp 0 cpucp 2>) and adds protocol@80. It has no timeout property. The mailbox is qcom,cpucp-v2, IRQ SPI 28 level-high (:10535-10540), the same as hamoa.dtsi:8571.
  • Consequence for a DT override: mica’s DTS includes hamoa.dtsi (dts/x1e80100-google-mica.dts:22), so the override has to use a path reference, &{/firmware/scmi} { … };. Per ADR 0004 it is not from the stock tree, so mark it “unverified” in dts/README.md.

4. What the maintainers said

The CPUFreq series review (“[PATCH V7 0/2] qcom: x1e80100: Enable CPUFreq”, lore):

  • Johan Hovold, 2024-11-01: grepping cpufreq sysfs on the CRD reset the machine after arm-scmi arm-scmi.0.auto: timed out in resp(caller: do_xfer+0x164/0x564) (2411.0/00567).
  • Marc Zyngier, 2024-11-01: “I’m seeing similar things indeed. Randomly grepping in cpufreq/policy* results in hard resets”. His dmesg has SCMI max-rx-timeout: 30ms and the SCMI bind at 7.93 s (00663).
  • Johan, 2024-11-05: the ThinkPad T14s “hard resets when accessing the cpufreq sysfs attributes as well” (05166).
  • Sibi Sankar (Qualcomm), 2024-12-05, root cause: “The PERF_LEVEL_GET implementation in the SCP firmware side is the reason for the crash … the crash happens when idle states are enabled and a regular LEVEL_GET message is triggered from the kernel. This was fixed a while back but it will take a while to flow back to all the devices” (2412.0/06550).
  • Marc, 2024-12-05: “this fix will never reach some platforms … How do we distinguish between good and bad firmware versions?” (06821).
  • Sibi, 2025-01-06: “The perf protocol version won’t have any changes so detecting it isn’t possible”, and “the X1E firmware supports fast channel level get but when queried it says it doesn’t support it … The PERF_LEVEL_GET regular messaging which gets used as a fallback has a bug which causes the device to crash” (2501.0/03550).
  • Johan, 2025-01-10, the mitigation: “Why can’t you add a quirk for x1e platforms that makes sure that the driver always uses fastchannel level get?” (2501.1/03949).
  • The outcome: that quirk is 397f802d06c4 (lore). Its message says the firmware “does not set the FastChannel supported attribute bit for PERF_LEVEL_GET but crashes when falling back to regular messaging”, and that an upper version bound can be added “when we learn which version has a fix”. Johan asked to merge the DT “With the SCMI perf quirk for the Qualcomm firmware merged for 6.16” (2505.1/00578). Bjorn Andersson applied both DT patches on 2025-05-17 (2505.2/01472).

The earlier resume timeout (“Ensure that the message-id supports fastchannel”, 2024-09):

  • Johan: the patch “breaks resume from suspend on the x1e80100 crd … arm-scmi firmware:scmi: timed out in resp(caller: do_xfer+0x164/0x568)” (958170, lore).
  • Konrad Dybcio: “it looks like PERF_LEVEL_GET (msgid 8) requires the use of FC, but the firmware fails to inform us about it through BIT(0) in attrs” (958413).
  • Sudeep Holla asked why the firmware would not implement PERF_LEVEL_GET on the normal channel (“I am very much interested to know the reason”) (958443).
  • Sibi, 2024-10-06, root cause: “the SCP was servicing the request but mailbox had the interrupt disabled during suspend which caused the timeout”. The fix is IRQF_NO_SUSPEND (d2fab3fc27cb, at the pin) (966573).

Late replies in general: Cristian Marussi (Arm) explains the agent’s per-message state machine and why a late immediate reply is “discarded” (2501.2/08228, 2025-01-23). That mail is about out-of-order delayed responses. -9’s lines are type 0 (plain replies), which the source comments above cover.

What this means for -9 (inference):

  • The quirk failed for a known reason. perf_level_get_fc_force is declared for vendor "Qualcomm" with implementation range "0x20000-" (quirks.c:174), and it is enabled only if impl >= start_range (quirks.c:302-305). The version query timed out, so the version read as 0x0 and the quirk stayed off.
  • The crash path was not taken, by luck. scmi-cpufreq failed, so nothing sent PERF_LEVEL_GET. Had cpufreq bound in that boot, it would have used the regular-message fallback that Qualcomm says crashes the SCP. A partly failed boot is therefore riskier than a fully failed one.
  • No maintainer has spoken to this case. None of the threads read here discusses SCMI timeouts at boot with an otherwise healthy firmware.

5. Reports from other X1 machines

  • Searched: GitHub issues (“timed out in resp”, “scmi” + “x1e”) and web search for scmi-cpufreq … -110 on Snapdragon X.
  • No primary report found of an intermittent boot-time SCMI timeout or scmi-cpufreq -110 on X1 laptops.
  • The known x1e reports are the resume timeout and the sysfs hard reset (section 4), both fixed at the pin.
  • Unrelated: dwhinham/archiso-aarch64-sp11#36 (X1P64100 OPPs flagged turbo, a different firmware bug) and Sky1-Linux/linux-sky1#25 (CIX SoC).

6. Options, ranked

#OptionEvidenceCost and risk
1Measure: more boots of one image. Add CONFIG_ARM_SCMI_DEBUG_COUNTERS=y (Kconfig:58-70; debugfs scmi/0/transport/counters/, e.g. xfers_response_timeout, err_msg_unexpected, driver.c:3002-3046) and tracing for the scmi_xfer_begin/scmi_xfer_end/scmi_rx_done events (include/trace/events/scmi.h:37-145). Read both in /init’s SCMI captureOne good and one bad boot give no rate. The counters and trace show which protocol/message waits and for how long. CONFIG_FTRACE is unset and the counters are off (build-a804c57/.config:2028)Config fragment only. Tracing makes the image bigger
2arm,max-rx-timeout-ms on &{/firmware/scmi}, sized from option 1In the binding (arm,scmi.yaml:127-132) and read at probe (driver.c:3187). Upstream allows larger values (i.MX: 5000). -9’s late replies came within the ~0.44 s windowIf CPUCP is really dead, every message waits up to 3 × the value (shmem.c:107-119 spins 2 ×, then the wait). At 200 ms and -9’s 10 timeouts, about 6 s of boot. Not from the stock tree
3Pass criteria and a guard in /init/attempt.sh: fail the boot on Firmware version 0x0 or a missing quirk_perf_level_get_fc_force line; never rebind scmi-cpufreq in such a bootSection 4: without the quirk, PERF_LEVEL_GET falls back to the message that crashes the firmwareNone
3bOptional local patch (kernel/patches/, ADR 0007): declare perf_level_get_fc_force with no version range, or with a machine compatible (quirks.c:307-309 supports compats)Johan’s own commit names the SoC-compatible match as the fallback designOne-line patch, local only; it hides the version-read failure but not the timeouts
4A/B diagnostics: arm,no-completion-irq (polling, driver.c:3202); cpuidle.off=1Polling separates IRQ delivery from firmware latency; section 1 already points at the firmware. Sibi says the firmware bug depends on idle states (2412.0/06550); whether that also affects reply latency is inferenceOne boot each; polling busy-waits, cpuidle off costs power. Not configs to keep
5Backport an upstream fixNone found (section 2); the only fix newer than the pin (readq()) does not touch the SCMI pathn/a

Sources

  • Pinned tree ~/.cache/gbook/linux-a804c57 at a804c5742bff:
    • drivers/mailbox/qcom-cpucp-mbox.c
    • drivers/firmware/arm_scmi/{driver,shmem,quirks,base}.c and transports/mailbox.c
    • Documentation/devicetree/bindings/firmware/arm,scmi.yaml
    • arch/arm64/boot/dts/qcom/hamoa.dtsi
  • Commit metadata: GitHub API on torvalds/linux (commits by path since 2024-06; compare against the v6.11-v7.4-rc1 tags).
  • Review threads: the LKML mirror and infradead pipermail URLs above. The owner’s lore search clipping lists the full V3-V7 thread (~/code/obsidian/the build host/Clippings/sx1e80100 Enable CPUFreq.md).
  • Logs: attempts -8 and -9 in dumps/firstboot/2026-11-03-netlog.txt (gitignored). Summary in docs/recon/scmi-2026-11-03.md.
  • Stock tree: dumps/recovery-16471258/dtbo/entry00-base.dts (stock-dtb.md).

Update: the cause narrowed, the board DTS changed (2026-11-03, 11:43 PM ET)

The stalls follow the PSCI domain idle states (attempts -27 to -34, scmi-2026-11-03.md). Options 3-4 were run: cpuidle.off=1 (-28) and arm,no-completion-irq (-29) each gave 3 of 3 clean cold boots, and so did removing every domain-idle-states (-34). The board DTS now does the last of these (ADR 0008) and keeps the 1000 ms window. An upstream report draft is in scmi-cpucp-stall-report.md (not sent).

Milestones, version numbers and maturing the project

Researched 2026-11-04 from the kernel’s own process documents at the pin (~/.cache/gbook/linux-a804c57, a804c5742bff), the GitHub mirror of torvalds/linux, and project-owned pages: semver.org, calver.org, keepachangelog.com, asahilinux.org, docs.nura.eco and wiki.nura.eco (postmarketOS), discourse.ubuntu.com (Canonical), chromium.org and gitlab.com/Linaro. Raw pages are cached in .firecrawl/milestones/ (gitignored). The device was not touched. Inference marks what the source does not state; the Recommendation sections are this note’s own proposals.

Summary and recommendation

Decided 2026-11-04: the owner answered “all a” to section 5. The decisions are recorded in ADR 0010, ADR 0011 and the ADR 0007 update.

#RecommendationWhy, in one line
1A milestone ladder M0 to M7 that slots rungs between the existing gates (table in section 4)Gate 2 to Gate 3 is a long stretch; Asahi, Nura and the 9345’s own upstream commit all measure progress per feature
2A feature matrix (docs/status.md): one row per feature, a state and an upstream columnAsahi’s tables and Nura’s device pages both do this; the README’s “Resume here” paragraph does it as prose
3CalVer YYYY.0M.0D[.MICRO] for a known-good image, as an annotated git tag, also visible in uname -rSemVer needs a public API, which gbook does not have; the upstream kernel drives the releases
4The kernel’s own [PATCH vN] rules for anything sent upstream; a status word on every kernel/patches/ header; never reuse a patch numberOne scheme per audience: lists read vN, the repo reads dates
5Keep holding upstream sends, but end the hold on a checkable bar (M5: binding + DTS pass the upstream checks) rather than on “mature”The kernel docs favour posting early; the 9345 DTS landed with EC, speakers, mics and camera not working

1. Milestones: how comparable projects define them

Asahi Linux (Apple Silicon): a per-feature table with an upstream column

  • One table per SoC generation, with rows for SoC blocks and per-device features, and columns per device (M1 Series Feature Support, read 2026-11-04; index at Feature Support Overview).
  • Each cell says where the feature lives, not just whether it works (same page, legend):
    • a kernel release, e.g. 6.0: “incorporated upstream as of this release”;
    • linux-asahi (kernel release): stable, in Fedora Asahi Remix, “should be upstream by the release indicated”;
    • linux-asahi: “(mostly) stable and available for use”;
    • WIP: actively progressing, “not yet ready for wider testing”;
    • TBA: no active work.
  • No dates are promised: “If a feature is not ready, then there is no estimation on when it will be ready” (same page, intro).
  • Progress reports follow the upstream kernel: the blog titles them “Progress Report: Linux 7.0”, “7.1”, “7.2” (Asahi blog index, read 2026-11-04).
  • “Officially supported” means the installer offers it, with listed exceptions. M3 support “has now been merged into the installer”, “almost everything supported on the M1 and M2 series machines just works”, with DCP, GPU and sleep listed as known limitations, at first behind the installer’s Expert mode (M2: Episode 1, 2026-09-06, read 2026-11-04).

postmarketOS, now Nura: device categories with entry criteria

wiki.postmarketos.org now redirects to wiki.nura.eco, and the categorization page moved to docs.nura.eco (live read 2026-11-04). The categories (Device Categorization, read 2026-11-04) give the clearest written criteria for “this device reached level X”:

CategoryRequirements (abridged from the page)
Testingclose-to-mainline kernel as new as the oldest supported LTS; compiled with LLVM; “Port and dependencies build”; “The device boots”
Communityinstall instructions on the wiki; close-to-mainline kernel; pmbootstrap kconfig check --community; automatic kernel upgrades work; maintainer joins release testing; kernel “upgraded through 3 kernel releases”; kernel not older than 6 months
Mainteam endorsement; at least 5 maintainers; hardware CI in two locations for at least 3 devices; UEFI boot; generic kernel; a maintained working-features file
Downstream / Archivedvendor kernels as a step toward mainline; unmaintained or superseded ports
  • Demotion is part of the scheme: maintainers get two weeks (up to one more month on request) to meet the criteria again before a move down (same page, “Moving to a lower category”).
  • Per-device feature matrix: each device page lists features as Works / Broken / Untested. The Lenovo Yoga Slim 7x (x1e80100, category testing) lists, among others, Battery, Screen, Keyboard, Touchpad, 3D Acceleration, Audio, Wi-Fi and FDE as Works; Camera, Bluetooth and GPS as Broken; USB OTG as Untested (Lenovo Yoga Slim 7x (lenovo-yoga-slim7x), served from wiki.nura.eco, read 2026-11-04). No Nura page exists for the Dell XPS 13 9345 (live read 2026-11-04).

The sibling laptop’s own bar: the 9345 initial DTS in mainline

The commit that added the XPS 13 9345 board file (f5b788d0e8cd, “arm64: dts: qcom: Add support for X1-based Dell XPS 13 9345”, authored 2024-10-03, applied by the qcom maintainer; commit, read 2026-11-04) states its level in three lists:

  • Working: touchpad, keyboard (after a suspend/resume, with an i2c-hid patch), touchscreen, eDP with brightness, NVMe, USB-C at USB 2/3 in one orientation, Wi-Fi, GPU/aDSP/cDSP firmware loading, lid switch, sleep/suspend.
  • Not working: speakers, microphones, fingerprint reader, USB as DP/USB 3 (retimer), camera, EC over I2C.
  • Untested: the higher-resolution panel.

Its binding went in as a separate patch, 1aa50217d7fd “dt-bindings: arm: qcom: Add Dell XPS 13 9345” (GitHub commit list for qcom.yaml, read 2026-11-04). Retimer support followed in a later commit (bd2dbbb1f35a, 2024-11-12). So an x1e80100 laptop DTS was accepted upstream as “initial support” with audio, camera and EC missing. At the pin, mainline carries 28 x1*/hamoa* board .dts files: laptops, reference and dev kits, and one desktop (ls arch/arm64/boot/dts/qcom/).

Distributions for the same SoC

  • Ubuntu Concept (Canonical) lists x1e laptops in two groups, “Devices we have seen successful installations on” (the XPS 13 9345 among them) and “Currently not (yet) supported”, with one Launchpad bug per device as its tracker (Ubuntu Concept ♥️ Snapdragon X Elite, section “What works”, read 2026-11-04).
  • Linaro’s arm64-laptops kernel names its integration branches by upstream base and series version, e.g. arm64-laptops-v7.3-rc1-camss-v13, qcom-laptops-v6.18-rc4-camss-next (gitlab.com/Linaro/arm64-laptops/linux, branch list, read 2026-11-04). No first-party per-laptop status page was found there; the repository page carries the kernel README.

ChromiumOS: status words for downstream kernel patches

There is no first-party “mainline on Chromebooks” status page that this search found. What ChromiumOS does publish is a vocabulary for how far each downstream patch is from upstream (Kernel Development, section “UPSTREAM, BACKPORT, FROMLIST, and you”, read 2026-11-04):

  • UPSTREAM: “exclusively for patches that have actually landed in Linus’ tree”;
  • BACKPORT: same, with changes, which must be documented;
  • FROMGIT: cherry-picks from a maintainer tree, naming tree and branch;
  • FROMLIST: “sent to a public mailing list for review, but hasn’t yet been merged”, with a link;
  • CHROMIUM: changes “which cannot be submitted upstream”; a rejected list patch kept anyway is CHROMIUM: FROMLIST: with the reason.

The older design page says the kernel is rebased onto new upstream releases “approximately every 3-6 months, and approximately every other kernel versions” (Chromium OS Kernel, “Upgrades”, read 2026-11-04; the page’s examples are 2.6.32 and 3.x era, so treat it as dated).

What the sources agree on (inference)

  • A milestone is a set of features at a stated location (works locally, sent, in a release), not a date.
  • Levels are defined by checkable entry criteria (Nura) or a Working / Not working list (the 9345 commit, Asahi’s M3 post), and both allow named exceptions.
  • Upstream releases are the clock for reports and rebases (Asahi reports, ChromiumOS rebases, Linaro branch names).

2. Version numbers

What there is to version in gbook

ObjectTodayChanges when
The repo209 commits, no tags (git tag, 2026-11-04)every unit of work
The boot image~/.cache/gbook/out/mica-usb.img; its identity is the kernel banner (release + #<n> UTS build number, scripts/image_uts.sh)each package-image.sh
The kernel basetorvalds a804c57 (ADR 0004)a re-pin
Local patcheskernel/patches/NNNN-*.patch, header says source and upstream state (ADR 0007)per patch
The DTSdts/x1e80100-google-mica.dts, history in gitper commit
A bootattempt ID YYYY-MM-DD-N in gitignored dumps/firstboot/attempts.tsv (scripts/attempt.sh)every boot
An upstream seriesnone sent yetper posting

Facts about the base (live reads 2026-11-04): the pin’s Makefile reads 7.3.0 with an empty EXTRAVERSION; the GitHub compare API puts a804c5742bff 1703 commits after v7.3 (tagged 2026-10-18 7:14 PM ET [23:14 UTC]) and before v7.4-rc1 (tagged 2026-11-01 4:26 PM ET [21:26 UTC]); the pin was committed 2026-10-21 2:04 AM ET [06:04 UTC]. So the pin is a 7.4 merge-window commit. No kernel/*.config sets CONFIG_LOCALVERSION (grep, 2026-11-04).

Two local patch numbers have been reused: 0008 named a GENI I2C timing patch (commit 16a2828) and now names the PCIe GDSC patch (5272658), and 0009 named the IRQ-to-CPU-1 diagnostic (ADR 0009, “Decision”: deleted) and now names the PCIe PHY GDSC patch (running log, 2026-11-04 rows).

The schemes

  • SemVer 2.0.0 (semver.org/spec/v2.0.0, read 2026-11-04):
    • item 1: “Software using Semantic Versioning MUST declare a public API”;
    • item 4: “Major version zero (0.y.z) is for initial development. Anything MAY change at any time”;
    • item 10: build metadata after + (e.g. 1.0.0-beta+exp.sha.5114f85) is ignored for precedence;
    • FAQ “How do I know when to release 1.0.0?”: when it is used in production or users depend on a stable API;
    • FAQ “Is v1.2.3 a semantic version?”: no; v1.2.3 is a tag name, the version is 1.2.3.
  • CalVer (calver.org, read 2026-11-04):
    • conventions YYYY, 0M, 0D, MICRO; multiple releases a day append a micro segment, 2026.9.11.1 (“Multiple releases per day?”);
    • “When to use CalVer?”: a large or changing scope, or “Do other external changes drive new project releases?”;
    • “CalVer deliberately does not attempt to communicate breaking changes … There is no substitute for a changelog”;
    • the same section opens “If third parties use your project, then use a serious version.”
  • Kernel patch series (Documentation/process/submitting-patches.rst at the pin, GitHub):
    • “The canonical patch format”, “Subject Line”: “v1, v2, v3” for versions sent in response to comments, “RFC” for a request for comments, [PATCH v2 M/N];
    • “Commentary”: the per-version changelog goes below the --- line, with links to earlier versions on lore;
    • “Describe your changes”: each version must be self-contained, “Don’t just say that this is version N”;
    • “Don’t get discouraged - or impatient”: [PATCH Vx RESEND] only for an unmodified resend;
    • “Providing base tree information”: git format-patch --base, and the base “in an official maintainer/mainline tree”;
    • “Tooling”: b4 automates dependency tracking, checkpatch and sending.
  • Git tags (git-tag(1), local man page, read 2026-11-04): “Annotated tags are meant for release while lightweight tags are meant for private or temporary object labels”; git describe ignores lightweight tags by default.
  • Keep a Changelog 1.1.0 (keepachangelog.com/en/1.1.0, read 2026-11-04): for humans; an entry per version, newest first, with its date; Added, Changed, Deprecated, Removed, Fixed, Security; an Unreleased section on top; “Using commit log diffs as changelogs is a bad idea”.
  • Making it visible on the device: CONFIG_LOCALVERSION appends a string to the kernel release, after “any files with a filename matching localversion* in your object and source tree”; the total is at most 64 characters (init/Kconfig at the pin, config LOCALVERSION).

Recommendation

  • Do not use SemVer for the repo or the image. gbook declares no public API (item 1), and the thing others will depend on, the google,mica binding and DTS, is versioned by upstream once merged (inference).
  • Use CalVer YYYY.0M.0D[.MICRO] for a released image, tagged as an annotated tag v2026.11.04 (v as the tag prefix, per the SemVer FAQ’s convention). A release is an image you would hand to someone else: one that passed a stated number of cold boots, shown by attempt.sh tally (CLAUDE.md docs conventions). The upstream base and the milestone are the reasons a new one is cut, which is CalVer’s “external changes drive releases” case.
  • One tag message carries the links. It names the kernel base (a804c57, “v7.3 + 1703, 7.4 merge window”), the patch list with status words, the milestone, and the attempt IDs that proved it.
  • Version vs attempt ID: a version names an image, and an attempt ID names one boot of an image. Several attempts boot one version; an attempt of an untagged image stays an experiment. Add the version to the attempt’s row when attempt.sh new sees a tagged commit (inference: the column does not exist yet).
  • Show the version on the device: have build-kernel.sh write a localversion-gbook file into the build directory (e.g. -gbook2026.11.04, or -gbook<git describe> for an untagged build), so uname -r and the netlog’s Linux version line name the gbook state. The kernel tree’s own -dirty suffix stays as it is.
  • Upstream series use the kernel’s scheme only: [PATCH RFC], then [PATCH v2] and so on, changelog below ---, --base, b4 if adopted. The repo version never appears in a patch subject.
  • Local patches: add one status line to each header, using the ChromiumOS words plus one of ours: LOCAL (gbook only, like CHROMIUM:), FROMLIST <lore Message-ID>, FROMGIT <tree branch>, UPSTREAM <tag>. Today that reads 0001 LOCAL, 0002-0006 and 0008 FROMLIST, 0007 Omarchy’s fix-up (LOCAL, third-party), 0009 LOCAL (inference from the headers and ADR 0007). Numbers only grow: a deleted patch’s number is retired.
  • Changelog: the running log stays the history. Start CHANGELOG.md (Keep a Changelog) only when a tagged image first leaves the build host; until then the annotated tag message is the release note.

3. Maturing the project

When to start upstreaming: what the kernel docs say

  • Post early. “posting your plans during the early stages can only be helpful” (Documentation/process/3.Early-stage.rst, “When to post?”). For complex work “there is a lot to be gained by getting feedback from the community before the work is complete”, and code not ready for inclusion should say so and list the known problems (5.Posting.rst, “When to post”).

  • The merge window lasts about two weeks; afterwards only fixes go in for six to ten weeks until the release (2.Process.rst, “The big picture”). New board files reach the merge window through the platform tree: for qcom the T: entry is git.kernel.org/pub/scm/linux/kernel/git/qcom/linux.git with maintainers Bjorn Andersson and Konrad Dybcio, list linux-arm-msm@vger.kernel.org, IRC #linux-msm on OFTC (MAINTAINERS at the pin, “ARM/QUALCOMM SUPPORT”). Patches should be prepared against that tree, not mainline (submitting-patches.rst, “Obtain a current source tree”).

  • Board DTS rules (maintainer-soc.rst, “Validating Devicetree Files”; maintainer-soc-clean-dts.rst; maintainer-devicetree.rst):

    • make dtbs_check adds no new warnings;
    • no scripts/dtc/dt-check-style warnings in relaxed mode, and most strict-mode ones addressed (the script exists at the pin);
    • DTS go through the SoC platform tree, separate from driver patches.
  • Binding rules (Documentation/devicetree/bindings/submitting-patches.rst, items 1, 5, 6, 7):

    • the binding is a separate patch, dt-bindings: <dir>: …;
    • it comes before its users in the series;
    • every compatible in a board DTS “must be previously documented”, which for mica means a google,mica entry in arm/qcom.yaml;
    • DTS patches go last or in their own series.

    qcom.yaml already describes depthcharge boards (“many devices … use the open source depthcharge bootloader”) with google,<board>[-revN][-skuN] entries such as google,coachz-rev1. The match order they rely on is documented in Documentation/arch/arm/google/chromebook-boot-flow.rst; it falls back to plain google,$(BOARD), which fits ADR 0004’s google,mica.

  • AI-assisted work (coding-assistants.rst, generated-content.rst):

    • “AI agents MUST NOT add Signed-off-by tags. Only humans can legally certify the Developer Certificate of Origin”;
    • the human submitter reviews all AI-generated code and takes responsibility;
    • an AI assistant “must never send anything itself”;
    • contributions should be transparent about which tools were used, on which parts, and how it was tested.

    207 of gbook’s 210 commits carry a Co-Authored-By: Claude trailer (git log, 2026-11-04), so this is a hard prerequisite for any send.

  • DT ABI: “a stable binding means that a newer kernel will not break on an older device tree” (Documentation/devicetree/bindings/ABI.rst). Inference: once google,mica is upstream, changes to it carry that constraint, which is one reason to post the binding only when the board’s compatible scheme is settled.

What blocks a clean upstream DTS today (inference, from repo files)

  • Unverified items: arm,max-rx-timeout-ms = <1000> is marked unverified (README “Resume here”), and other unverified items are listed in dts/README.md.
  • No idle states: ADR 0009 deletes domain-idle-states from every CPU domain. Upstream may want the SoC problem fixed or explained first; the unsent stall report is the natural RFC for that.
  • Base tree: the pin is a merge-window commit; a series has to be rebased onto the qcom tree.
  • Kernel-side support: the panel entry (0001) needs the full panel name, and the GMU and GDSC patches are other people’s series in flight.

Release cadence, roadmap, CI

  • Cadence (recommendation): event-driven, not calendar-driven. Cut a tagged image when a milestone is reached or the pin moves. Re-pin at most once per mainline release, onto a release tag such as v7.4; ChromiumOS rebases every other release, and Asahi reports once per release.
  • Roadmap: the ladder in section 4, kept in the README’s phase table, with a .scratch/<milestone>/map.md per active milestone. The repo already uses wayfinder maps (.scratch/phase-2-first-boot/map.md, issue tracker).
  • Status: docs/status.md in the Asahi/Nura form. Rows: feature; state (works / partial / broken / untested); where it works (mainline at pin / local patch NNNN / board DTS only); upstream (no / sent vN / in 7.x); evidence (recon note). It replaces the feature prose in “Resume here”.
  • CI: scripts/check.sh through lefthook already runs on every commit. Add upstream’s own checks when M5 starts: make dtbs_check, dt-check-style, checkpatch.pl on exported patches. Server-side CI on the the private remote and hardware CI (a Nura “main” requirement) are out of proportion for one owner and one device.
  • Outside contact before upstream: the plan already lists joining #aarch64-laptops (project log, Phase 1). Posting the recon there is “posting your plans” at no ABI cost.

4. Proposed milestone ladder for gbook

Inference throughout: rung names, criteria and states are proposals. “Borrowed from” names the outside criterion each rung copies. The state column is from the README’s “Resume here” and the recon notes.

MNamePhase / gateDone whenBorrowed fromState 2026-11-04
M0Way back to stockPhase 1, Gate 1recovery media reach the recovery screen; relock tested (ADR 0001)(gbook)passed 2026-11-02
M1First lightPhase 2, Gate 2our Linux version line on the AP console, panel or readback (CONTEXT.md)Nura testing: “The device boots”passed 2026-11-02
M2Board on the consolePhase 3, rung 1every enabled DT node binds; USB, PCIe, Wi-Fi, display, GPU probe, EC keyboard, touchpad, TPM, suspend round trip; N clean cold boots by tallythe 9345 commit’s Working listnearly: all of these since 2026-11-04 (unbound, suspend); touchscreen parked (touch-tpm)
M3Userspace from USBPhase 3, rung 2a rootfs from USB with Mesa on the GPU, networked Wi-Fi, audio state known; first tagged imageNura device page rows (3D, Wi-Fi, Audio, Battery)not started
M4Daily-drivablePhase 3, Gate 3install on internal storage (spare SSD, ADR 0002), dual-boot or documented return to stockREADME Gate 3; Asahi “official”: the installer offers itnot started
M5Upstream-readyPhase 4 entrybinding and DTS on the qcom tree pass dt_binding_check, dtbs_check (no new warnings), dt-check-style relaxed, checkpatch; Working / Not working list written; every local patch labelled; a human signs offmaintainer-soc, bindings/submitting-patches, coding-assistantsnot started; can run in parallel with M3/M4
M6UpstreamPhase 4, Gate 4google,mica binding and DTS applied to the qcom tree, then in a mainline release; firmware question settledREADME Gate 4; Asahi’s “kernel release” cellnot started
M7In a distribution (optional)after Phase 4a Nura testing device package, or a device entry in Ubuntu ConceptNura categories; Ubuntu Concept listnot started

5. Open questions for the owner

Reply in the usual form (“1a 2 ok”); each has a recommended answer.

  1. The ladder. a) adopt M0-M7 and add the rungs to the README phase table; b) keep phases and gates only. Recommended: a.
  2. Version scheme for images. a) CalVer YYYY.0M.0D[.MICRO] annotated tags for released images; b) SemVer 0.y.z, one minor per milestone; c) no versions until upstream. Recommended: a.
  3. What counts as a release. a) an image that passed 3 clean cold boots by tally and is the one you would hand on; b) every image that reaches a new milestone, regardless of boot count. Recommended: a.
  4. Version on the device. a) build-kernel.sh writes a localversion-gbook file, so uname -r names the gbook state; b) leave the kernel release as is. Recommended: a.
  5. Patch headers. a) add a status line (LOCAL / FROMLIST / FROMGIT / UPSTREAM) to every kernel/patches/ header and retire deleted numbers; b) leave the free-text headers. Recommended: a, as an ADR 0007 update.
  6. Changelog. a) the annotated tag message is the release note until an image leaves the build host, then CHANGELOG.md (Keep a Changelog); b) start CHANGELOG.md now. Recommended: a.
  7. When the upstream hold ends. a) hold until M5’s checks pass, then send the binding and DTS as [PATCH RFC]; b) post an RFC of the binding and current DTS after M2; c) hold until M4. Recommended: a, plus the IRC post from the Phase 1 list now, which costs no ABI.
  8. Re-pin policy. a) re-pin onto the next mainline release tag (v7.4) after M3, and before M5 in any case; b) re-pin only when a needed fix lands. Recommended: a.
  9. Status matrix. a) a new docs/status.md, with “Resume here” pointing to it; b) a table in the README. Recommended: a.

Sources

All read 2026-11-04. Kernel documents at the pin a804c5742bff (local tree ~/.cache/gbook/linux-a804c57, mirrored at https://github.com/torvalds/linux/tree/a804c5742bff).

  • Kernel: Documentation/process/submitting-patches.rst (sections named inline), 2.Process.rst, 3.Early-stage.rst, 5.Posting.rst, maintainer-soc.rst, maintainer-soc-clean-dts.rst, maintainer-devicetree.rst, coding-assistants.rst, generated-content.rst; Documentation/devicetree/bindings/submitting-patches.rst, bindings/ABI.rst, bindings/arm/qcom.yaml; Documentation/arch/arm/google/chromebook-boot-flow.rst; MAINTAINERS (“ARM/QUALCOMM SUPPORT”); init/Kconfig (LOCALVERSION); Makefile.
  • torvalds/linux on GitHub: f5b788d0e8cd (9345 DTS), commit list for qcom.yaml (1aa50217d7fd) and the 9345 DTS (bd2dbbb1f35a); tags v7.3, v7.4-rc1 and compare v7.3...a804c5742bff via gh api.
  • SemVer 2.0.0: https://semver.org/spec/v2.0.0.html
  • CalVer: https://calver.org/
  • Keep a Changelog 1.1.0: https://keepachangelog.com/en/1.1.0/
  • git-tag(1), DESCRIPTION (local man page).
  • Asahi Linux: https://asahilinux.org/docs/platform/feature-support/overview/ , https://asahilinux.org/docs/platform/feature-support/m1/ , https://asahilinux.org/blog/ , https://asahilinux.org/2026/09/m2-episode-1/
  • Nura (postmarketOS): https://docs.nura.eco/pmaports/main/packaging/device-categorization.html , https://wiki.postmarketos.org/wiki/Lenovo_Yoga_Slim_7x_(lenovo-yoga-slim7x) (served from wiki.nura.eco)
  • Ubuntu Concept: https://discourse.ubuntu.com/t/ubuntu-concept-snapdragon-x-elite/48800
  • Linaro: https://gitlab.com/Linaro/arm64-laptops/linux
  • ChromiumOS: https://www.chromium.org/chromium-os/developer-library/guides/kernel/kernel-development/ , https://www.chromium.org/chromium-os/chromiumos-design-docs/chromium-os-kernel/

M3 rootfs: which distribution, and how it boots from the stick

Researched 2026-11-04 (written 1:03 PM ET) on the build host. Sources: distro package indexes and package pages, Mesa and Ghostty upstream at named commits and tags, the Omarchy repositories at named commits, the kernel tree at the pin (~/.cache/gbook/linux-a804c57, a804c5742bff, with local patches applied in drivers/gpu/drm/msm and elsewhere, not in block/, fs/ or init/), the cached vboot source in .firecrawl/, and the built kernel’s ~/.cache/gbook/build-a804c57/.config. The Googlebook was not touched: no adb, no gbook, no SuzyQ. Nothing was installed. Inference marks what no source states. All package versions are as read on 2026-11-04 and will move.

Context: ADR 0010 sets M3 as “a rootfs from USB with Mesa on the GPU and Wi-Fi in userspace; the audio state is known; the first release tag” (ADR 0011). The boot path is the dev-signed FIT on KERN-A (ADR 0003, fit-packaging.md), and the kernel stays ours: torvalds a804c57 plus kernel/patches/, every needed driver built in.

Answer

  1. Debian sid arm64 for M3, built by mmdebstrap in the same digest-pinned sid container that packages the image, pinned to one snapshot.debian.org timestamp, with a lock list and a local .deb cache (snapshot answered 503 today).
  2. Mesa does not decide it. Every candidate ships Mesa 26.x; the Adreno X1-85 is supported since Mesa 24.2.0, and Mesa’s kmsro path should drive our split msm-kms + msm devices (inference from source).
  3. Ghostty decides the runner-up. Only Omarchy’s aarch64 repo has a 1.4.0 binary; Debian has no Ghostty at all. Recommended: build 1.4.0 from its signed source in the container, as Omarchy’s PKGBUILD does (owner question 2).
  4. Boot (inference): ROOT is partition 2 on the same stick from 256 MiB on, so the per-attempt 256 MiB dd never touches it; /init finds ROOT through depthcharge’s kern_guid=%U, copies its log, Wi-Fi block and stock firmware into the new root’s /run, and switch_roots. No ROOT, and it falls back to today’s capture-and-reboot.
  5. Omarchy aarch64 (Arch Linux ARM base) is the natural M4 base: Ghostty 1.4.0, Quickshell and Hyprland are in its repo, and the owner runs Omarchy on the build host. It lacks a dated pin, which matters for M3’s release tag.
Debian sidArch Linux ARM + Omarchy aarch64Ubuntu 26.04 / 26.10Fedora 44 / 45postmarketOS (Alpine edge)
Mesa (X1-85 since 24.2.0)26.2.626.2.6 (ALARM), 26.2.5 (Omarchy’s mirror)26.0.8 / 26.2.426.2.6 / 26.2.326.2.6
GTK 4 / libadwaita (Ghostty 1.4 needs 4.18 / 1.7)4.24.1 / 1.10.04.24.1 / 1.10.04.22.4 / 1.9.1; 4.24.1 / 1.10.04.22.5 / 1.9.4; 4.24.1 / 1.10.04.24.1 / 1.10.0
Ghostty arm64none (community 1.3.1 .deb)1.4.0 (Omarchy repo; not in ALARM)1.3.0 (universe)none (COPR, Terra)1.3.1 git, testing only, musl
Dated pinsnapshot.debian.orgnone foundarchive only; ports not confirmednone found (koji NVRs)none found
Built in the existing sid containeryes (mmdebstrap 1.6.0-5)possible (pacman 7.1.0, arch-install-scripts 32 in sid; keyrings are not)likely (inference)possible (mkosi 27.1, dnf 4.24 in sid; inference)possible (pmbootstrap 3.12.1 in sid)
Takes our kernel and /inityesyesyesyesonly by bypassing its own initramfs (inference)

1. What every candidate shares

Mesa and the Adreno X1-85

  • Support landed in Mesa 24.2.0. Rob Clark’s “freedreno: Enable the X1-85” (b0d22461b945, MR !30249, merged 2024-07-18) is first contained in tag mesa-24.2.0 (GitLab API commits/<sha>/refs?type=tag, read 2026-11-04).
  • Mesa 24.3.0 is the practical floor. “freedreno: Add compute constlen quirk for X1-85” (3c8190e8b221, MR !31764) halves the compute constlen, because without it “there are sporadic hangs in dEQP-VK.robustness.robustness2.*” on the GPU “in x1e laptops”. It is first in mesa-24.3.0.
  • The device entry. Mesa main 32f5c9344041 has GPUId(chip_id=0xffff43050c01, name="Adreno X1-85") in the a7xx “gen2” group with the a740 (src/freedreno/common/freedreno_devices.py:1242; at mesa-26.2.0, line 1262). The kernel binds qcom,adreno-43050c01 and loads gen70500_sqe.fw and gen70500_gmu.bin (gpu-2026-11-03.md).
  • GL and Vulkan. The freedreno page says the gallium driver “implements up to OpenGL ES 3.2 and desktop OpenGL 4.5”, and Turnip “is a Vulkan 1.3 driver for Adreno 6xx GPUs” (docs.mesa3d.org/drivers/freedreno.html, read 2026-11-04; it names no a7xx part). At mesa-26.2.0, freedreno_screen.c:518-526 sets GLSL feature level 460 for every is_a6xx() screen except a702, and is_a6xx() is screen->gen >= 6 (freedreno_screen.h:275-278), which includes a7xx.
  • Every candidate clears both floors (versions in the table above). So Mesa does not decide the distribution.

Mesa on our split DRM devices (inference from source, untested on mica)

msm.separate_gpu_kms=1 is on the base command line (scripts/package-in-container.sh). At the pin, the display then registers as DRM driver msm-kms and the GPU as msm (drivers/gpu/drm/msm/msm_drv.c:885, 904); on mica they were card2 and renderD128 (gpu-2026-11-03.md).

  • Mesa’s pipe loader has no descriptor named msm-kms, so it falls back to kmsro: “kmsro supports lots of drivers, try as a fallback” (src/gallium/auxiliary/pipe-loader/pipe_loader_drm.c:181-183 at mesa-26.2.0, :174 at mesa-26.0.0).
  • kmsro opens a render node, and when its kernel driver is msm it creates a freedreno renderonly screen (src/gallium/winsys/kmsro/drm/kmsro_drm_winsys.c:100-103 at mesa-26.2.0; :99 at mesa-26.0.0).
  • Inference: a compositor on card2 renders on the X1-85 through kmsro. M3’s first rootfs boot checks this before anything else (4.4).

Ghostty’s floor

  • GTK and libadwaita. “1.4.x | 4.18 | 1.7”; 1.3.x needs GTK 4.14 and libadwaita 1.5. The rule is “the minimum version that is available in the most recent LTS releases of Debian, Fedora, and Ubuntu” (ghostty.org/docs/linux, read 2026-11-04).
  • OpenGL. “We require at least OpenGL 4.3” (src/renderer/OpenGL.zig:38-40 at tag v1.4.0). Inference: freedreno’s GLSL 460 level and “up to OpenGL 4.5” clear it. eglinfo on the device settles it.
  • Release. Tag v1.4.0 is dated 2026-10-27 (GitHub tag object ce787fc18a77). Ghostty is MIT-licensed (Omarchy’s PKGBUILD license=(MIT)).
  • Building it. Omarchy’s PKGBUILD builds 1.4.0 from https://release.files.ghostty.org/1.4.0/ghostty-1.4.0.tar.gz with a pinned upstream Zig: “Ghostty 1.4.x requires Zig 0.17.x”, fetched from ziglang.org for x86_64 and aarch64 with sha256 sums (pkgbuilds/ghostty/PKGBUILD at omarchy-pkgs 66368e24e900). Ghostty’s build page lists the Debian build dependencies (libgtk-4-dev libgtk4-layer-shell-dev libadwaita-1-dev gettext libxml2-utils), says release tarballs carry minisign signatures, and does not mention cross-compiling. Its Zig table stops at 1.3.x (ghostty.org/docs/install/build, read 2026-11-04). Debian sid has no zig package (madison, read 2026-11-04).

Our kernel under a systemd userspace

Every candidate except postmarketOS boots systemd; Debian sid and ALARM both ship 262.1. systemd v262’s README “REQUIREMENTS” lists the kernel options it needs. The built config (read 2026-11-04) has DEVTMPFS, CGROUPS, INOTIFY_USER, SIGNALFD, TIMERFD, EPOLL, UNIX, SYSFS, PROC_FS, FHANDLE, DMIID, BLK_DEV_BSG, NET_NS, USER_NS, IPV6, AUTOFS_FS, TMPFS_XATTR, SECCOMP, SECCOMP_FILTER, KCMP, CGROUP_BPF, BPF_JIT and EFI_PARTITION all =y, and UEVENT_HELPER off.

  • One mismatch: CONFIG_FW_LOADER_USER_HELPER=y (from defconfig), where the README says “Userspace firmware loading is not supported and should be disabled in the kernel”. FW_LOADER_USER_HELPER_FALLBACK is off. Inference: harmless in practice, but a one-line fragment turns it off.
  • Root filesystem and stick. EXT4_FS=y and USB_STORAGE=y, but USB_UAS is not set, so the stick runs over bulk-only transport. Inference: slower than UAS, not wrong.
  • Modules. MODULES=y, with 1450 symbols =m, but the initramfs loads none (kernel/*.config header, status legend). ADR 0011 changes uname -r with every gbook commit, so modules baked into a write-once rootfs would stop matching after the next KERN-A-only rewrite. Inference: keep the all-built-in rule and add fragments (audio below) rather than ship /lib/modules.

Firmware

  • The stock ADSP and CDSP images (qcom/x1e80100/google/mica/) are vendor content: never in git and never in an image that leaves the build host (ADR 0005).
  • Everything else the boot loads is redistributable: ath12k, Bluetooth, the regulatory database, and the GPU pair. Debian’s firmware-qcom-soc 20260916-1 (sid, non-free-firmware) carries qcom/gen70500_sqe.fw, qcom/gen70500_gmu.bin and qcom/x1e80100/X1E80100-Dell-XPS-13-9345-tplg.bin (packages.debian.org file list, read 2026-11-04). That is the same linux-firmware tag as scripts/stage-firmware.sh’s pinned linux-firmware-qcom-20260916-1.
  • The firmware loader searches firmware_class.path, then /lib/firmware/updates/… and /lib/firmware/…. The path can also be set at run time through /sys/module/firmware_class/parameters/path (Documentation/driver-api/firmware/fw_search_path.rst at the pin).

2. The candidates

Debian sid arm64 (mmdebstrap)

  • Mesa: mesa 26.2.6-1 and mesa-vulkan-drivers 26.2.6-1 (arm64) in sid (qa.debian.org madison, read 2026-11-04; madison also still lists 26.2.5-1 for sid). Forky has 26.1.6-1.
  • Ghostty: no package. madison and sources.debian.org return nothing for ghostty. GTK 4.24.1+ds-1 and libadwaita-1 1.10.0-2 exceed 1.4’s floor. There are two routes:
    • Build 1.4.0 from the signed tarball with Zig 0.17, as Omarchy’s PKGBUILD does. Under qemu-user in the container it is slow, and the time is unmeasured. Cross-compiling is undocumented.
    • A community .deb: ghostty_1.3.1-0.ppa2_arm64_forky.deb (also trixie and Ubuntu builds) from mkasberg/ghostty-ubuntu release 1.3.1-0-ppa2, which ghostty.org lists as “Community”.
  • Our kernel: yes. A Debian rootfs needs no Debian kernel package; systemd 262.1-1 (sid) meets the requirements above.
  • Firmware and audio data: firmware-qcom-soc (above), alsa-ucm-conf 1.2.16.1-1. Upstream alsa-ucm-conf v1.2.16.1 has ucm2/Qualcomm/x1e80100/Dell-Xps-9345.conf and Xps9345-HiFi.conf (GitHub, read 2026-11-04).
  • Pinning: snapshot.debian.org takes archive/debian/<yyyymmddThhmmssZ>/ URLs, and apt needs [check-valid-until=no] for snapshots older than about twelve days (snapshot.debian.org, read 2026-11-04).
    • mmdebstrap: with SOURCE_DATE_EPOCH set, “the result will be reproducible across multiple runs with the same options and mirror content”. For ext4 output, “the filesystem UUID and hash_seed will be set to a UUID derived from SOURCE_DATE_EPOCH” (mmdebstrap(1), 1.6.0-5, read 2026-11-04).
    • Availability: snapshot.debian.org/archive/debian/2026110…/dists/sid/… answered HTTP 504 and 503 on three tries on 2026-11-04. So the non-free-firmware component under snapshot is unconfirmed, and a local cache of the exact .deb files is needed regardless. The project already does this for firmware packages (~/.cache/gbook/pkgs).
  • Building on the build host:
    • mmdebstrap lists “foreign architecture chroots using qemu-user” and checks the arch “using qemu binfmt_misc support”.
    • the build host registers qemu-aarch64 with interpreter /usr/bin/qemu-aarch64-static and flags PF (/proc/sys/fs/binfmt_misc/qemu-aarch64, read 2026-11-04). The F flag opens the interpreter at registration, so it also works inside a container (inference, from binfmt_misc semantics).
    • mmdebstrap’s root mode “needs to be able to mount and thus requires CAP_SYS_ADMIN”. Inference: the container then needs --cap-add SYS_ADMIN (or --privileged), which package-image.sh does not pass today. That is an owner-OK change.
    • Output can be an .ext4 image directly. Offline rebuilds need the cached .deb set served as a local mirror (inference; not tried).
  • Licence: main plus non-free-firmware (redistributable firmware). No stock blob goes in the image.
  • Size: not measured. The stick is “4 GB+” (README owner tasks). Step 1 of the plan measures it (4.7).

Arch Linux ARM + Omarchy aarch64

  • What Omarchy on aarch64 is. Its aarch64 pacman-stable.conf enables [omarchy] (https://pkgs.omarchy.org/stable/$arch) ahead of [core], [extra] and [alarm]. Those three come from “Omarchy’s mirror of Arch Linux ARM”, https://arm-stable-mirror.omarchy.org/$arch/$repo (default/pacman/aarch64/ at omarchy 84b3f2872b50, branch quattro). The Snapdragon set (install/omarchy-aarch64-qualcomm.packages) adds linux-firmware-qcom, qcom-firmware-extract, systemd-ukify, vulkan-freedreno and pipewire-libcamera.
  • Mesa:
    • ALARM’s mirror has mesa and vulkan-freedreno 1:26.2.6-1, while Omarchy’s ALARM mirror has 1:26.2.5-1 (extra.db of both, read 2026-11-04).
    • Omarchy’s mesa-aurora is the Asahi driver only (“Aurora Asahi Gallium, Zink and Honeykrisp”).
    • Inference: the lag between the two mirrors is Omarchy’s curated “stable” channel.
  • Ghostty: 1.4.0-1 for aarch64 in Omarchy’s stable repo (omarchy.db, 187 packages, read 2026-11-04), built by the PKGBUILD above. ALARM’s own extra has no ghostty. GTK 1:4.24.1-1 and libadwaita 1:1.10.0-1 are in ALARM extra.
  • M4 continuity: quickshell 0.3.2, hyprland 0.56.2 and qcom-firmware-extract are in the same aarch64 repo. Omarchy’s Snapdragon install builds a UKI “with the device trees systemd-stub picks one from”, which is a UEFI path. mica’s depthcharge boots FIT kernel partitions, not UKIs, so the Omarchy installer does not fit as is. A pacstrapped rootfs with Omarchy packages would (inference).
  • Our kernel: yes. ALARM’s linux-aarch64 (7.3.1-2) and Omarchy’s linux-omarchy (7.2.8-13 in the aarch64 repo) are simply not installed.
  • Pinning: none found. Arch’s dated archive covers x86_64 only, and no official ALARM archive was found. The stable mirror is curated, not addressed by date. Inference: superseded packages leave the mirrors, so the local cache plus a lock list would be the only pin.
  • Building on the build host: Debian sid ships pacman-package-manager 7.1.0-3 and arch-install-scripts 32-1. The ALARM and Omarchy keyrings are not in Debian. Package hooks run in the arm64 chroot under the same qemu binfmt (inference; not tried).
  • Licence: free packages from ALARM. The Omarchy repo also carries proprietary apps, so only named packages are pulled.

Ubuntu 26.04 “resolute” and 26.10 “stonking”

  • Releases: 26.10 is the “Current Stable Release” (released 2026-10-15) and 26.04 is supported (Launchpad API ubuntu/series, read 2026-11-04).
  • Mesa: 26.0.8-1ubuntu0.3 (resolute-updates) and 26.2.4-1ubuntu1 (stonking) (Ubuntu madison, read 2026-11-04).
  • Ghostty: ghostty 1.3.0~us1-0ubuntu1.1 in resolute-updates/universe and 1.3.0~us1-0ubuntu4 in stonking, both with arm64 builds. ghostty.org lists Ubuntu as “Official, from 26.04”. GTK is 4.22.4 and libadwaita 1.9.1 on 26.04, 4.24.1 and 1.10.0 on 26.10, so either could run 1.4 if built.
  • Concept images for X1 laptops: UEFI ISOs (questing-desktop-arm64+x1e.iso) that use SMBIOS “to load the correct dtb automatically”. The page as served shows Mesa 24.2.3 and replies from November 2024, so it reads stale (discourse.ubuntu.com/t/48800, read 2026-11-04). mica has no UEFI, so only Ubuntu’s userspace would apply.
  • Our kernel: yes, as for Debian.
  • Pinning: snapshot.ubuntu.com covers the archive from 2023-03-01 (snapshot.ubuntu.com, read 2026-11-04). arm64 lives on ports.ubuntu.com, whose resolute Release has no Snapshots: field, and snapshot.ubuntu.com/ubuntu-ports/ answered 404 (read 2026-11-04). So no dated arm64 pin is confirmed.
  • Building on the build host: inference: mmdebstrap can bootstrap Ubuntu suites with the Ubuntu keyring; not checked.

Fedora 44 and 45

  • Mesa: 26.2.6-1.fc44 (F44 updates) and 26.2.3-1.fc45 (mdapi srcpkg/mesa, read 2026-11-04).
  • GTK and libadwaita: 4.22.5 / 1.9.4 on F44, 4.24.1 / 1.10.0 on F45.
  • Ghostty: none in Fedora (mdapi returns nothing for rawhide, F44 or F45). ghostty.org lists COPR scottames/ghostty and Terra, both community; their arm64 builds were not checked.
  • X1E: the Fedora wiki covers the “aarch64 Fedora Workstation 44 Live ISO” on X1E/X1P laptops with clk_ignore_unused pd_ignore_unused systemd.tpm2_wait=0 modprobe.blacklist=qcom_q6v5_pas, an EFI firmware path (Snapdragon WoA Laptop Install, oldid 775997, read 2026-11-04).
  • Our kernel: yes. A Fedora rootfs without kernel-core is possible (inference).
  • Pinning: no dated snapshot mirror found. Koji keeps every build by NVR.
  • Building on the build host: mkosi 27.1-1 and dnf 4.24.0-1 are in sid. Whether either builds Fedora 45 arm64 there was not checked.

postmarketOS (now Nura) on Alpine edge

  • Mesa: Alpine edge main/aarch64 has mesa, mesa-dri-gallium and mesa-vulkan-freedreno 26.2.6-r0 (APKINDEX, read 2026-11-04). Inference: pmOS edge uses Alpine edge’s Mesa (wiki.nura.eco was behind a bot check).
  • Ghostty: 1.3.1_git20260908-r2 in testing/aarch64 only (edge, not in a stable branch), built against musl. gtk4.0 4.24.1-r0, libadwaita 1.10.0-r0, linux-firmware-qcom 20260916-r0.
  • Our kernel: pmOS’s strength is the boot side: pmbootstrap already builds depthcharge images (deviceinfo_generate_depthcharge_image, cgpt layout, fit-packaging.md section 5). x1e80100 laptops are in pmaports (Yoga Slim 7x in testing, milestones-versioning.md section 1). But a pmOS device brings its own kernel package and its own initramfs. Inference: keeping our /init and its netlog means working around pmOS rather than with it.
  • Pinning: none found for edge. pmbootstrap 3.12.1-1 is in sid.

3. Recommendation, and why

  • For M3, Debian sid. It is the only candidate with a citable dated pin, and it builds in the container that already packages every image (same tool, same base). Its Mesa, GTK and libadwaita are as new as anyone’s, and its non-free-firmware carries the GPU pair and the 9345 topology. The cost is Ghostty: build 1.4.0 (question 2).
  • For M4, revisit with Omarchy aarch64. Ghostty 1.4.0, Quickshell and Hyprland come as binaries from the owner’s own distro. The boot design in section 4 is distro-neutral: only the rootfs build script changes.
  • Not Ubuntu, Fedora or pmOS for M3. Ubuntu adds Ghostty 1.3.0 but no confirmed arm64 pin. Fedora has no Ghostty and no pin. pmOS fights our /init and uses musl.

4. Boot design (inference throughout; no part of it has run on mica)

4.1 Stick layout

The owner rewrites the whole 256 MiB mica-usb.img for every attempt, GPT included (scripts/attempt.sh dd-cmd, scripts/package-in-container.sh), and attempt.sh verify reads the same 256 MiB back.

  • ROOT starts at 256 MiB. fit-packaging’s sketch put ROOT at sector 270336, right after KERN-A (fit-packaging.md, step 5); a 256 MiB dd would overwrite its start. Start ROOT at sector 524288 (256 MiB) and run it to the end of a declared size D.
  • The image’s GPT declares ROOT. Otherwise each kernel rewrite deletes the entry. cgpt create runs on a sparse file of size D, cgpt add -i 2 -t data -l gbook-root adds ROOT, and the first 256 MiB is cut out as mica-usb.img. The attempt dd, verify and gpt stay as they are. The container’s offline cgpt show attribute checks and cgpt.txt then run on the full sparse file before the cut, because the cut file’s backup header is not at its end (inference: cgpt’s handling of an invalid backup header was not read).
  • D must not exceed the stick. vboot rejects a primary header whose last_usable_lba lies beyond the drive (firmware/lib/cgptlib/cgptlib_internal.c:144-146 at vboot 34fbbe6). A backup header not at the drive’s end fails its check (:119-121), and GptRepair rebuilds it in memory from the primary (:337-358). WriteAndFreeGptData then runs on every boot (2load_kernel.c:783). Whether it writes the repaired backup was not read (gpt_misc.c is not in the cache).
    • Today’s image already has its backup header at 256 MiB, not at the stick’s end, and it boots. So this case is tested in practice.
    • Linux warns “GPT:Alternate GPT header not at the end of the disk” and carries on with the primary (block/partitions/efi.c:552 at the pin).
  • depthcharge ignores ROOT. vboot considers only ChromeOS-kernel and Android-vbmeta type entries (fit-packaging.md section 5).
  • Writing ROOT is a rare owner step: dd of rootfs.ext4 to sector 524288 (or to partition 2), named first and run with ! like the kernel write. It is done again only when the rootfs changes, not per kernel.

4.2 How /init finds and mounts ROOT

  • Use kern_guid, not root=. depthcharge already replaces %U in the stored kern_guid=%U with KERN-A’s PARTUUID (fit-packaging.md section 3). The kernel’s own root=PARTUUID=%U/PARTNROFF=1 form only matters when the kernel mounts root itself, which it never does with an initramfs /init. So the command line and check 27’s stored-line compare stay unchanged.
  • The lookup is plain busybox. At the pin, part_uevent exports PARTN, PARTNAME and PARTUUID (block/partitions/core.c:258-262). /init waits for USB storage (as its USB stage already does, up to 20 s), finds the /sys/class/block/*/uevent whose PARTUUID equals kern_guid, takes its parent disk, and requires that disk’s partition 2 to carry PARTNAME=gbook-root.
  • Mount and hand off:
    1. fsck is optional (e2fsprogs is not in the initramfs). The ext4 journal covers an unclean stop.
    2. Mount ROOT read-write on /newroot, mount a tmpfs on /newroot/run, and copy the hand-off files there (4.3).
    3. Move /dev, /proc and /sys over, then exec switch_root /newroot /sbin/init.
  • Fallback: no ROOT (or a failed mount) within the USB wait runs today’s path unchanged: captures, netlog, reboot. A broken rootfs still yields a netlog.

4.3 What stays in the initramfs

  • Everything that probes before root: busybox, /init, iw/wpa_supplicant and the whole staged firmware tree. Built-in drivers load firmware at probe time (ath12k, the ADSP and CDSP remoteprocs), before ROOT can be mounted.
  • Copied into the new root’s /run, never into the rootfs image:
    • /run/gbook/gbook.log: the GBOOK: lines so far (switch_root frees the initramfs);
    • /run/gbook/wpa_supplicant.conf, mode 600: the owner’s network block, as /init handles it today;
    • /run/gbook/firmware/: the stock qcom/x1e80100/google/mica/ files only, for a remoteproc restart after the hand-off. /init writes that path to /sys/module/firmware_class/parameters/path before switch_root (fw_search_path.rst).
    • The rootfs then holds only redistributable firmware, from Debian’s firmware-qcom-soc and firmware-atheros 20260916-1. The latter carries ath12k/WCN7850/hw2.0/{amss,m3,board-2}.bin and qca/hmtbtfw20.tlv (packages.debian.org file list, read 2026-11-04).
  • Two to verify on the first rootfs boot: that firmware lookups after switch_root resolve against the new root (kernel_read_file_from_path_initns opens the path under the init task’s filesystem, fs/kernel_read_file.c:147-166; not traced further), and that the GPU’s first open in userspace loads gen70500_* from the rootfs.
  • The rootfs image stays shareable: no Wi-Fi secret, no stock blob, no per-unit identifier. That is what ADR 0011’s “image leaves the build host” case needs.

4.4 Showing the M3 criteria

A gbook-m3.service in the rootfs runs once after network-online.target. It prints GBOOK: lines to /dev/console (the AP console, still read-only to us) and appends them to the netlog. Its checks:

  • Mesa on the GPU:
    • eglinfo -B names “Adreno X1-85” and freedreno; vulkaninfo --summary names Turnip.
    • kmscube (or glmark2-es2-drm) on card2 proves the kmsro path, and the owner sees it on the panel.
    • Then cage runs Ghostty as an unprivileged user, a real OpenGL 4.3 app on the panel.
    • Capture GL_RENDERER, the GL version and any GPU fault lines from dmesg.
  • Wi-Fi in userspace:
    • wpa_supplicant@wlan0 reads /run/gbook/wpa_supplicant.conf through a unit drop-in (inference: the stock unit reads a file under /etc/wpa_supplicant/), and systemd-networkd takes DHCP.
    • Debian sid has every tool named here (madison, read 2026-11-04): mesa-utils and mesa-utils-bin 9.0.0-2+b3 (eglinfo), vulkan-tools 1.4.341.0, kmscube 0.0.0~git20260822-1, glmark2 2023.01, cage 0.3.1-1 and wpasupplicant 2:2.11-7.
    • Proof: networkctl status wlan0 (redacted), iw dev wlan0 link (signal and frequency only), a gateway ping, and the netlog itself sent over that link from userspace.
  • Audio state known: the status rows read “no sound node in the DTS”.
    • Minimum: record what aplay -l shows (expected: no card) as the known state.
    • Better: one diagnostic attempt with the 9345’s sound node as a dts/diag/*.dtsi (marked unverified per ADR 0004), a kernel/mica-audio.config building SND_SOC_X1E80100, SND_SOC_WSA884X, the LPASS macros, SOUNDWIRE_QCOM, QCOM_APR and SND_SOC_QDSP6 in (all =m today), and Debian’s 9345 topology and UCM.
    • mica’s stock ADSP image may not match the 9345’s topology, so either outcome counts as “known”.
  • No listener, no getty on the AP console: mask serial-getty@ttyMSM0 (ADR 0006; /init has “No interactive shell”). Install no sshd; today’s /init says “Nothing listens on the network”. A getty or an autologin session on the panel (tty1) is the owner’s to allow.

4.5 Keeping the attempt and netlog tooling

  • What the tooling parses: watch, follow and compare read GBOOK: lines; boot_health.py and tally read the netlog’s dmesg and SCMI trace sections; image-check compares the booted kernel with the verified stick.
  • One sender for both: move /init’s netlog block (redact, sections, --- end of netlog) into one script in kernel/initramfs/, used by /init and shipped into the rootfs. Same format, same listener (attempt.sh netlog start).
  • The rootfs sends after its checks and then stays up. A base image cannot depend on a gbook.* debug argument (ADR 0011), and an M3 release is a usable system, not a capture-and-reboot. Test rootfs builds may reboot like today.
  • Release identity: a release names two artifacts. Inference/proposal: the tag message adds the rootfs image’s sha256, the snapshot timestamp and the lock file’s hash, next to the KERN-A image hash attempt.sh stick prints.

4.6 Kernel-side follow-ups

  • kernel/mica-rootfs.config: # CONFIG_FW_LOADER_USER_HELPER is not set (systemd README). USB_UAS=y is optional.
  • kernel/mica-audio.config for the audio attempt above.
  • Omarchy’s linux-omarchy-aarch64 patches 2002 and 2003 are kernel patches, not Mesa or rootfs patches (README “Resume here” lists “a rootfs with Mesa” as a lead; a handoff paired it with them).
    • 2002-power-supply-qcom_battmgr-Notify-charger-supplies-of-online-changes.patch (Brandon Williams, 2026-10-13): without it “UPower keeps the state it read at startup”.
    • 2003-wifi-ath12k-don-t-count-a-vdev-restart-as-a-started-.patch (Birk Skyum, 2026-10-20): stops 3 s time out while waiting for fw stats done stalls after an AP changes channel or bandwidth.
    • Both matter once UPower and a long-lived Wi-Fi link exist. They would enter as kernel/patches/ with status lines (ADR 0007). Their upstream status was not checked.

4.7 Steps

  1. Measure: an mmdebstrap --simulate (or apt-get install --dry-run) of the package list in the container, to size the rootfs and to choose D for the owner’s stick.
  2. scripts/build-rootfs.sh: snapshot pin, lock list, .deb cache, SOURCE_DATE_EPOCH, ext4 output, a manifest with sha256. It runs through pkexec docker run with the extra capability.
  3. package-in-container.sh: the two-entry GPT (4.1); /init’s ROOT lookup and hand-off (4.2-4.3); the shared netlog sender; a check in scripts/check.sh for the lookup against synthetic uevent files.
  4. Owner: write ROOT once, then attempts as today. First boot: eglinfo and kmscube, then Wi-Fi, then Ghostty, then audio.
  5. An ADR for the rootfs choice and the boot design, once the owner answers section 5.

5. Open questions for the owner

Reply in the usual form (“1a 2 ok”); each has a recommended answer.

  1. Distribution for M3. a) Debian sid arm64 via mmdebstrap, snapshot-pinned, in the existing container; b) Arch Linux ARM + Omarchy’s aarch64 repo now (Ghostty 1.4.0 binary, no dated pin); c) Ubuntu 26.10; d) postmarketOS. Recommended: a, with b reconsidered for M4.
  2. Ghostty at M3. a) build 1.4.0 from the minisign-checked tarball with Zig 0.17 in the container (first-party sources, build time unmeasured); b) the community 1.3.1 arm64 .deb (fast, third-party binary, not in a release image); c) none until M4, with kmscube/glmark2 as the GL proof. Recommended: a, falling back to c if the emulated build proves impractical.
  3. Stick layout. a) ROOT as partition 2 on the same stick from 256 MiB, declared in every image’s GPT, so the per-attempt dd is unchanged; b) a second stick through a hub. Recommended: a.
  4. Finding ROOT. a) /init matches kern_guid to a PARTUUID and mounts partition 2 labelled gbook-root on the same disk; b) a fixed root=PARTUUID=… of ROOT on the command line. Recommended: a (no stick-specific value in the image, command line unchanged).
  5. Secrets and vendor firmware. a) the Wi-Fi block and the stock ADSP/CDSP files stay in the initramfs and are copied to the new root’s /run; the rootfs carries only redistributable firmware; b) put both into the rootfs image (it then never leaves the build host). Recommended: a.
  6. Modules. a) keep everything built in, adding mica-rootfs.config and mica-audio.config; b) ship /lib/modules in the rootfs (stale after each kernel-only rewrite). Recommended: a.
  7. Audio for M3. a) one diagnostic attempt with the 9345 sound node, mica-audio.config and Debian’s 9345 topology and UCM, recording whatever happens; b) record “no sound node” as the known state. Recommended: a.
  8. Console and network policy. a) mask serial-getty@ttyMSM0, no sshd, nothing listening; an autologin cage session with Ghostty on the panel only; b) also a getty on tty1. Recommended: a.
  9. Container capability. a) let build-rootfs.sh run its container with --cap-add SYS_ADMIN (mmdebstrap’s root mode mounts); b) try --mode=unshare or fakechroot without it first. Recommended: b, then a if it fails.
  10. Release identity. a) an M3 tag message also names the rootfs sha256, snapshot timestamp and lock hash (an ADR 0011 update); b) tag the kernel image only. Recommended: a.

Sources

SourceRefCommit or versionRead
omacom/omarchy-pkgsmaster66368e24e900bef4baa6d136c1254bc66daaef0d2026-11-04
omacom/omarchyquattro84b3f2872b5023c13967543ed4a98b08fcac5b212026-11-04
Mesa (gitlab.freedesktop.org API)main; tags mesa-24.2.0, mesa-24.3.0, mesa-26.0.0, mesa-26.2.032f5c9344041e53c3bff89c6fc5b107c8b7ed4712026-11-04
Ghosttytag v1.4.0tag object ce787fc18a7782e81585beb63f0d905ccaf692842026-11-04
systemdtag v262, READMEtag2026-11-04
alsa-ucm-conftag v1.2.16.1tag2026-11-04
Linuxtorvaldsa804c5742bff (local tree, patches applied outside block/, fs/, init/)2026-11-04
vboot_referencefirmware-bluey-16702.B34fbbe62cc18 (cached in .firecrawl/)2026-11-04

Package indexes, all read 2026-11-04:

  • Debian: https://qa.debian.org/madison.php, https://packages.debian.org/sid/all/firmware-qcom-soc/filelist, https://sources.debian.org/api/src/ghostty/ (404).
  • Ubuntu: https://people.canonical.com/~ubuntu-archive/madison.cgi, https://api.launchpad.net/devel/ubuntu/series, http://ports.ubuntu.com/ubuntu-ports/dists/resolute/Release.
  • Fedora: https://mdapi.fedoraproject.org/<release>/srcpkg/<name>, https://bodhi.fedoraproject.org/releases/.
  • Alpine edge: https://dl-cdn.alpinelinux.org/alpine/edge/{main,community,testing}/aarch64/APKINDEX.tar.gz.
  • Arch Linux ARM: http://mirror.archlinuxarm.org/aarch64/{core,extra}/*.db.
  • Omarchy: https://pkgs.omarchy.org/stable/aarch64/omarchy.db, https://arm-stable-mirror.omarchy.org/aarch64/extra/extra.db.
  • Pages: ghostty.org docs/linux, docs/install/binary, docs/install/build; docs.mesa3d.org drivers/freedreno.html; manpages.debian.org mmdebstrap(1) (1.6.0-5); snapshot.debian.org; snapshot.ubuntu.com; discourse.ubuntu.com t/48800; fedoraproject.org wiki Snapdragon_WoA_Laptop_Install; github.com/mkasberg/ghostty-ubuntu releases.
  • Local reads on the build host: /proc/sys/fs/binfmt_misc/qemu-aarch64; ~/.cache/gbook/build-a804c57/.config.

Not found

  • A Ghostty package in Debian (any suite) or Fedora; a Ghostty arm64 binary of 1.4 anywhere but Omarchy’s repo.
  • A dated archive for Arch Linux ARM or Alpine edge; a confirmed snapshot service for Ubuntu ports (arm64).
  • Whether snapshot.debian.org serves non-free-firmware for sid: the service answered 503/504 on 2026-11-04.
  • The Zig version for Ghostty 1.4 on ghostty.org (its table stops at 1.3.x); the 0.17 figure is from Omarchy’s PKGBUILD.
  • Whether vboot writes a repaired backup GPT back to the stick (gpt_misc.c not read).
  • The rootfs size; the build time of Ghostty under qemu-user; whether mmdebstrap runs without CAP_SYS_ADMIN in the container.
  • The upstream status of Omarchy patches 2002 and 2003; whether mica’s speaker path matches the 9345’s.
  • A current postmarketOS page on its edge base (wiki.nura.eco served a bot check).

Debug tools for the next boots: SCMI timeouts and the touchscreen 0xff reads

Researched 2026-11-03 from the kernel tree at the pin (~/.cache/gbook/linux-a804c57, a804c5742), the build config ~/.cache/gbook/build-a804c57/.config (kernel #13 era), the pinned Debian busybox-static, the netlog dumps/firstboot/2026-11-03-netlog.txt (gitignored) and the GitHub page of morluto/rea. The device was not touched. Line numbers are at the pin unless a repo path is given. Inference marks what the source does not state.

Ranked recommendation for the next boot

#AddProblemCost
1Event tracing: two config lines, trace_event= on the command line, trace dump into the netlogbothone kernel rebuild (~5 min); 0 owner time per boot
2log_buf_len=4Mbothnone
3IRQ counts, pin and regulator state in /initbothnone (sysfs, debugfs, /proc reads)
4Raw i2ctransfer reads of the touchscreen’s HID descriptortouchnone; needs the owner’s OK (I2C writes of a register pointer)
5dyndbg on the silent i2c-hid and GENI error linestouchnone (GBOOK_CMDLINE_EXTRA)

1. Kernel fragment (a new kernel/mica-debug.config; it sorts after mica-firstboot.config):

# Event tracing (tracefs, trace_event=). arm64 defconfig:1970 sets "# CONFIG_FTRACE is not set".
CONFIG_FTRACE=y
CONFIG_ENABLE_DEFAULT_TRACERS=y
# require: EVENT_TRACING

olddefconfig on a scratch copy of the build config with these two lines adds only TRACING, EVENT_TRACING, RING_BUFFER, TRACEPOINTS, NOP_TRACER, CONTEXT_SWITCH_TRACER, TRACEFS_AUTOMOUNT_DEPRECATED, BRANCH_PROFILE_NONE, UPROBES, UPROBE_EVENTS, EPROBE_EVENTS, BPF_EVENTS, DYNAMIC_EVENTS, PROBE_EVENTS and the module-only IWLWIFI_DEVICE_TRACING. No FUNCTION_TRACER, so no per-function patch sites. The probe-event extras can be pinned off with # CONFIG_UPROBE_EVENTS is not set (and EPROBE_EVENTS, BPF_EVENTS) for a leaner image. EVENT_TRACE_STARTUP_TEST stays off.

Command line (GBOOK_CMDLINE_EXTRA, one line; the -18 boot proved a quoted dyndbg= survives packaging):

log_buf_len=4M trace_event=scmi:scmi_xfer_begin,scmi:scmi_xfer_response_wait,scmi:scmi_rx_done,scmi:scmi_xfer_end,scmi:scmi_msg_dump,i2c:*,qcom_geni_i2c:* dyndbg="file i2c-hid-core.c line 1055 +p; file i2c-qcom-geni.c line 235-306 +p"

/init additions (sketch; the SCMI awk was tested on a synthetic trace only):

# After the sysfs mount:
mount -t tracefs tracefs /sys/kernel/tracing 2>/dev/null
T=/sys/kernel/tracing

# Before the touch window (after touch-input). Adapter numbers follow probe order, so resolve them.
i2c_bus() { for b in /sys/bus/i2c/devices/i2c-*; do case "$(readlink -f "$b")" in */"$1"/i2c-*) echo "${b##*i2c-}"; return ;; esac; done; }
ts_bus=$(i2c_bus a80000.i2c); tp_bus=$(i2c_bus b80000.i2c)
cap i2c-bus "touchscreen=i2c-${ts_bus:-none} touchpad=i2c-${tp_bus:-none}"
cap irq-counts "$(grep -E 'apss_cpucp_mbox|a80000\.i2c|b80000\.i2c|hid-over-i2c' /proc/interrupts | tr -s ' ')"
cap ts-pins "$(grep -E '^ gpio(32|33|48|51|88|89) +:' /sys/kernel/debug/gpio 2>/dev/null)"
for r in /sys/class/regulator/*; do
  [ "$(cat "$r/name" 2>/dev/null)" = vreg_touchscreen_3p3 ] && cap ts-vreg "state=$(cat "$r/state") users=$(cat "$r/num_users")"
done
# (item 4 goes here, before the snapshot, so its transfers are in the trace too)
# Keep SCMI, the two touch buses and their GENI controllers; drop i2c10 (TPM) and the other buses.
trace_keep() { grep -E "entries-in-buffer|scmi_|geni_i2c_[a-z_]+: (a80000|b80000)\.i2c|i2c_[a-z]+: i2c-(${ts_bus:-x}|${tp_bus:-x}) " "$T/trace" | redact; }
trace_keep >/tmp/trace-probe.txt # before the touch window floods the ring
cap trace "$(head -n 1 /tmp/trace-probe.txt) kept=$(wc -l </tmp/trace-probe.txt)"
cap geni-setup "$(grep -o 'geni_i2c_bus_setup: .*' /tmp/trace-probe.txt | sort -u)"
cap geni-err "$(grep -o 'geni_i2c_err: .*' /tmp/trace-probe.txt | sort | uniq -c)"
cap scmi-latency "$(awk '{t=""; s=""; for (i = 1; i <= NF; i++) { if ($i ~ /^[0-9]+\.[0-9]+:$/) t = substr($i, 1, length($i) - 1); if ($i ~ /^seq=/) s = $i }
  /scmi_xfer_begin:/ { b[s] = t }
  /scmi_rx_done:/ && (s in b) { d = (t - b[s]) * 1000; n++; if (d > m) m = d }
  /scmi_xfer_end:/ && / s=-110 / { to++ }
  /t=!RESP/ && (s in b) { late = late sprintf(" %s:%.0fms", s, (t - b[s]) * 1000) }
  END { printf "replies=%d max=%.1fms timeouts=%d late=%s\n", n, m, to, (late == "" ? " none" : late) }' /tmp/trace-probe.txt)"

# In the netlog block, after the dmesg section (never through say/cap: the console runs at 115200 baud):
#   echo "--- trace at probe"; cat /tmp/trace-probe.txt; echo "--- trace at end"; trace_keep

What one boot then answers:

  • SCMI: the reply latency of every message (scmi_xfer_begin to scmi_rx_done), the arrival time of each late reply (!RESP dump), and with the apss_cpucp_mbox IRQ count next to the sent_ok/response_ok counters already in /init, whether the interrupts arrive late or not at all. That sizes arm,max-rx-timeout-ms from data (scmi-cpucp-timeouts.md, ranked step 1-2).
  • Touch: the bytes on the wire per read (i2c_reply), the GENI status of every interrupt during it (geni_i2c_irq: m_stat and rx_st, whose low bits are the RX FIFO word count), any controller error (geni_i2c_err), and the SCL counters actually programmed (geni_i2c_bus_setup).

Not for the next boot: tp_printk, ftrace_dump_on_oops, initcall_debug, the function tracer, SCMI raw mode, i2cdetect/i2cdump/i2cget (reasons below). Housekeeping: softlockup_panic=1 on the base command line does nothing in this build (section 6).

1. Event tracing from an initramfs

State today. CONFIG_FTRACE is not set (.config:12029; arch/arm64/configs/defconfig:1970), so there is no tracefs and no trace event in the current kernel. FTRACE defaults to y if DEBUG_KERNEL (kernel/trace/Kconfig:204-207), but defconfig turns it off explicitly. ENABLE_DEFAULT_TRACERS selects TRACING (:588-591), and TRACING selects EVENT_TRACING, RING_BUFFER and TRACEPOINTS (:179-188).

Command-line parameters (all [FTRACE], Documentation/admin-guide/kernel-parameters.txt:7628-7700):

ParameterHandlerUse here
trace_event=<list>trace_events.c:4659the capture. Comma-separated system:event, system:*
trace_buf_size=nn[KMG]trace.c:1035per CPU; default 1 441 792 bytes (trace.c:740). Default is enough with the snapshot before the touch window
tp_printktrace.c:354events also to printk. Not now: see below
tp_printk_stop_on_boottrace.c:361stops tp_printk at late_initcall_sync
ftrace_dump_on_oops[=orig_cpu]trace.c:296dumps the ring to the console on an oops. Not now
trace_trigger=, trace_instance=trace_events.c:3854, trace.c:320not needed

Armed before the SCMI probe. start_kernel calls trace_init() (init/main.c:1086), which runs trace_event_init() (trace_events.c:4987) and event_trace_enable(), which enables the trace_event= list (:4884-4912) and again at early_initcall (:4932-4946). arm-scmi binds at about 0.36 s, so its first message is in the ring.

Events at the pin (all built in, so trace_event= reaches them):

  • scmi (include/trace/events/scmi.h): scmi_xfer_begin (:37, called driver.c:1429), scmi_xfer_response_wait (:65, driver.c:1349, prints the timeout in ms), scmi_rx_done (:121, driver.c:1164 for replies), scmi_xfer_end (:93, driver.c:1467, prints the return code), scmi_msg_dump (:147, with payload; a reply for an xfer that already timed out is dumped as !RESP through scmi_bad_message_trace, driver.c:867-891, from the “Message for %d … is not expected” path, :1015-1021). Leave out scmi_fc_call (:12): it fires on every FastChannel perf level set (perf.c:708), each cpufreq change.
  • i2c (include/trace/events/i2c.h): i2c_read, i2c_write, i2c_reply, i2c_result, emitted around every __i2c_transfer (i2c-core-base.c:2283-2307) once the event’s static key is on. i2c_reply records the bytes read; the text output shows the first 64 (%*phD, capped in lib/vsprintf.c:1205): the whole 30-byte HID descriptor, the first 64 of the 935-byte report descriptor.
  • smbus (include/trace/events/smbus.h): only SMBus calls; i2c-hid uses i2c_transfer, so not needed.
  • qcom_geni_i2c (include/trace/events/qcom_geni_i2c.h, created in i2c-qcom-geni.c:4-5): geni_i2c_bus_setup (i2c-qcom-geni.c:263), geni_i2c_irq (:344, every interrupt), geni_i2c_err (:299, every error, including NACKs and timeouts that dev_err skips).
  • qcom_geni_se: geni_se_regs dumps 50 SE registers, but only spi-geni-qcom.c emits it, not the I2C driver.
  • hid, i2c-hid: no trace events at the pin.
  • GPI DMA: no events; and QCOM_GPI_DMA=m (.config:8490), so these GENI I2C buses run FIFO / SE DMA.

Why not tp_printk. It copies every enabled event to printk; with ignore_loglevel they go to the 115200-baud console. The kernel doc warns it can live-lock with high-frequency events (kernel-parameters.txt:7644-7648), and the touchpad alone gave 841 to 4055 reports per window (attempts.tsv, -18, -19). Console output inside the SCMI window could also change the timing under study (inference). Fallback only for a boot that never reaches the netlog, and then with trace_event=scmi:* alone plus tp_printk_stop_on_boot. ftrace_dump_on_oops writes the whole ring to the same console: neither problem is an oops, so skip it.

Timestamps. The default local trace clock is sched_clock() (trace_clock.c:37-42, trace.c:1077), so trace times line up with the dmesg times (inference: arm64 printk also uses sched_clock).

Later, if useful. BOOTTIME_TRACING (Kconfig:226) adds per-event filters at boot through a bootconfig in the initrd. A persistent ring buffer (reserve_mem=… trace_instance=…@…, kernel-parameters.txt:7734-7758) survives a warm reset, but the next boot is usually stock, so it is of little use here.

2. The printk ring wraps

CONFIG_LOG_BUF_SHIFT=17 (.config:162) gives a 128 KiB ring. In the netlog, two of the last six boots’ dmesg sections hold about 126 KB of text and start mid-boot (first line GICv3: … and KASLR enabled, not Booting Linux): the earliest lines were lost. Any added dyndbg makes this worse. log_buf_len= is an early_param (kernel/printk/printk.c:1071); 4M costs 4 MiB of RAM.

3. Read-only /init captures (no rebuild)

  • /proc/interrupts. The CPUCP mailbox IRQ is apss_cpucp_mbox (qcom-cpucp-mbox.c:175-176); each GENI I2C IRQ carries its device name, a80000.i2c for the touchscreen bus (i2c-qcom-geni.c:1201-1202). The SCMI research doc could not tell lost interrupts from slow firmware (its section 1); an IRQ count against sent_ok/response_ok and the late replies does.
  • Pins, /sys/kernel/debug/gpio. msm_gpio_dbg_show_one prints direction, level, function, drive and pull per pin (pinctrl-msm.c:654-716) and skips gpio-reserved-ranges (:683), so reading it does not touch secure pins. It reads TLMM registers only. Pins: SDA/SCL gpio32/gpio33 (hamoa.dtsi:6180-6186, 2 mA, bias-pull-up), IRQ gpio51, reset-n gpio48 and report-enable gpio89 (both output-low, as in the stock overlay entry01-overlay.dts:1085-1094), VDD enable gpio88.
  • Regulator. vreg_touchscreen_3p3 is regulator-boot-on, not always-on (dts/x1e80100-google-mica.dts:336-352). Its state tells whether the panel is powered when the raw read runs.
  • Clock table. geni_i2c_bus_setup prints clk_div: 2 means the 19.2 MHz table {2, 5, 11, 22} (i2c-qcom-geni.c:188), 4 the 32 MHz table {4, 3, 9, 19} (:196), which the driver avoids unless the clock already runs at 32 MHz, because it “breaks I2C transfers on X1E80100 CRD” (:207-218). The formula comment (:174-185) gives, for 19.2 MHz at 400 kHz: t_high = 0.52 µs, t_low = 1.15 µs, SCL ≈ 436 kHz. The comment says the firmware adds cycles beyond that formula. Inference: by the formula alone, both periods sit under the Fast-mode minimums (t_low 1.3 µs, t_high 0.6 µs, NXP UM10204; values from memory, not re-read today). The stock side (Qualcomm’s vendor driver) needs a device-side read; that is out of scope here.

4. Busybox I2C applets and a raw descriptor read

The applets are there. The image’s busybox is Debian busybox-static:arm64 1:1.38.0-3+b1 (scripts/package-in-container.sh:9). ~/.cache/gbook/out/ held no initramfs during this research, so the same package was fetched from deb.debian.org (sha256 968d1aa8f579fa1ac59c26afa365454369e13cf29848e6400b50028fed0ffda0; compare with manifest.txt on the next packaging) and listed under qemu-aarch64-static: i2cdetect i2cdump i2cget i2cset i2ctransfer, plus devmem, xxd, hexdump, nc, timeout. CONFIG_I2C_CHARDEV=y (.config:3896) and devtmpfs give /dev/i2c-N.

Which applet. Only i2ctransfer (i2ctransfer [-fay] I2CBUS {rLEN[@ADDR] | wLEN[@ADDR] DATA…}…) can issue HID-over-I2C’s 16-bit register write and read in one transfer with a repeated start, as i2c_hid_read_register does (i2c-hid-core.c:200-243). i2cget and i2cdump use 8-bit SMBus commands, wrong for this device. i2cdetect probes every address with SMBus quick writes by default (-q), and i2cset writes; use neither. i2cdetect -l (list adapters, no bus traffic) is safe.

When the read is meaningful. If i2c-hid’s own probe fails, it powers the panel down (i2c-hid-core.c:1320-1324), and the regulator turns off when its last user disables it (regulator/core.c:3269-3270): a raw read would then only NACK. In -17 and -18 the failure (usage index exceeded, hid-core.c:266) came later, in the HID driver’s report parse; i2c-hid stayed bound, its IRQ kept counting, so the panel stayed powered (inference from -17’s IRQ count 1 to 730). So run it only while /sys/bus/i2c/devices/<bus>-0010/driver exists, with -f (the address is busy). i2c_transfer holds the adapter lock for the whole transfer (i2c-core-base.c:2346-2352), so it cannot interleave with an i2c-hid read mid-transfer.

ts_dev=/sys/bus/i2c/devices/${ts_bus}-0010
if [ -n "$ts_bus" ] && [ -e "$ts_dev/driver" ] && [ -c "/dev/i2c-$ts_bus" ]; then
  for n in 30 30 16 20 24 32; do # 30: FIFO path, as at probe; 32 and up: SE DMA (i2c-qcom-geni.c:528, i2c-core-base.c:2684)
    cap ts-raw "desc r$n $(i2ctransfer -f -y "$ts_bus" w2@0x10 0x00 0x00 r$n 2>&1 | tr '\n' ' ')"
  done
  cap ts-raw "rdesc r64 $(i2ctransfer -f -y "$ts_bus" w2@0x10 0x21 0x00 r64 2>&1 | tr '\n' ' ')" # wReportDescRegister 0x21
else
  cap ts-raw "skipped: bus=${ts_bus:-none} driver=$(basename "$(readlink "$ts_dev/driver" 2>/dev/null)" 2>/dev/null)"
fi

Safety (inference from the HID-over-I2C read path above): the two written bytes only set the register pointer, as i2c-hid’s own descriptor fetch does; register 0x0000 here is the HID descriptor and 0x0021 the report descriptor (the -18 dump), both read-only. Still an I2C write by an agent’s script, so it needs the owner’s OK (CLAUDE.md). What it separates: a cut at byte 20 for every length points at the device or the bus; a cut that moves with the length, or only on one path (FIFO vs DMA), points at the controller.

5. GENI error prints and dyndbg

  • geni_i2c_err (i2c-qcom-geni.c:291-313) logs dev_err only for bus-protocol, arbitration, overrun, illegal command and the GP_IRQ errors, followed by geni_i2c_err_misc’s register dump (:269-289: m_cmd, geni_status, geni_ios, FIFO/DMA status), whose lines are dev_dbg. NACK, abort and timeout are dev_dbg only (:302-307) and skip the register dump even with dyndbg on. The error table is at :152-164 (-ENXIO address NACK, -EPROTO bus protocol, -EAGAIN arbitration, -ETIMEDOUT).
  • dyndbg line range 235-306 covers “I2C clk selected” (:235, the clk_idx) and every error line, but not :368, which prints on every SE DMA interrupt (the touchpad’s 47-byte reports) and would flood the console. Line ranges are in the dyndbg grammar (Documentation/admin-guide/dynamic-debug-howto.rst:131-142).
  • i2c-hid-core.c:1055 is the silent “nothing at this address” that -19 could not see.
  • Probe-time dev_errs in qcom-geni-se.c cover clocks, interconnect, OPP and SE firmware load (:932-1649); none is a transfer diagnostic. There is no GENI debugfs at the pin.

6. Other per-boot aids

  • softlockup_panic=1 is dead. SOFTLOCKUP_DETECTOR, HARDLOCKUP_DETECTOR, DETECT_HUNG_TASK and WQ_WATCHDOG are all off (.config:11947-11951). kernel/watchdog.c, which owns the parameter (:428), is built only with LOCKUP_DETECTOR (kernel/Makefile:105). Every netlog boot logs it under “Unknown kernel command line parameters”. Either drop it from package-in-container.sh:10 or add CONFIG_SOFTLOCKUP_DETECTOR=y (then DETECT_HUNG_TASK defaults on, lib/Kconfig.debug:1229, 120 s). Neither open problem is a hang, so low priority.
  • initcall_debug (init/main.c:795) prints two lines per initcall to the console. The same data costs nothing on the console as initcall:initcall_start,initcall:initcall_finish (include/trace/events/initcall.h:27,48) in trace_event=. Useful only for the probe-order question from -19.
  • pstore/ramoops: already built in and mounted by /init; it covers the panic and console of a failed boot.
  • SCMI raw mode (ARM_SCMI_RAW_MODE_SUPPORT, arm_scmi/Kconfig:33-46) injects messages to the firmware and inhibits the normal stack: a write path, not a diagnostic for this project.
  • GENERIC_IRQ_DEBUGFS (off, .config:75) would show per-IRQ trigger and state; /proc/interrupts covers the question for now.

7. morluto/rea: no

Fetched 2026-11-03 (GitHub page and raw README.md, HTTP 200). REA (“Reverse Engineer Anything”) is an MCP server and CLI that gives a coding agent reverse-engineering tools: native binaries through an existing Hopper, Ghidra, IDA or Kuna, JavaScript/Electron, .NET, Android APKs over adb, websites, packet captures, firmware images through Binwalk or Unblob. README: “REA connects your agent to tools for inspecting native binaries, JavaScript and Electron apps, .NET assemblies, and websites.” It is a Node.js package (rea-agents, Node 22.x/24.x/26.12.1+), MIT, run on the host (npx rea-agents setup). The last-commit date did not render in the fetch.

Fit with this setup:

  • It runs on a workstation, not on the target: it needs Node.js and npm, which an aarch64 busybox initramfs has not, and it has no I2C, SCMI, device-tree, serial-console or kernel-tracing feature. Its only Linux-kernel feature is static inspection of .ko modules; this kernel builds none.
  • Both open problems sit in the running kernel’s view of hardware (reply latency, bytes on the wire); a static analyser of binaries does not observe either.
  • Where it could help at all: reading a stock vendor binary on the build host (for example the vendor GENI I2C driver’s timing table) through Ghidra. That runs on the build host, and ADR 0005 keeps vendor content out of git.

Verdict: no for the boot loop. A maybe, later and off-device, only if the project decides to reverse-engineer a stock vendor binary; nothing in its README shows aarch64 kernel-module support.

Update: the wake trace (2026-11-03, 10:50 PM ET)

For the late SCMI completion IRQ (scmi-2026-11-03.md, attempts -28 to -30): GBOOK_CMDLINE_EXTRA="trace_event=irq:irq_handler_entry,power:cpu_idle,timer:hrtimer_expire_entry". A second trace_event= appends to the first (setup_trace_event(), trace_events.c:4644-4649 at a804c57). When power:cpu_idle is on the command line, /init’s probe-time capture also keeps CPU 0’s cpu_idle, irq_handler_entry and hrtimer_expire_entry lines (the end copy does not, to keep the netlog short). attempt.sh trace wake [ms] prints, per slow reply, CPU 0’s idle state before the apss_cpucp_mbox interrupt and what woke it. cpu_idle state= is the cpuidle state index on CPU 0, not the cluster or system domain state.

  • Attempt -31 (10:58 PM ET): with the default ring (TRACE_BUF_SIZE_DEFAULT 1441792 bytes per CPU, trace.c:740) the probe snapshot kept 604908 of 1255636 events and lost the SCMI probe. The wake trace needs trace_buf_size=8M too (per CPU, set_buf_size(), trace.c:1020; 96 MB over 12 CPUs).

Update: the fast sweep (2026-11-04, 9:21 PM EST)

A boot whose stick carries ROOT takes /init’s fast sweep (task #24, .scratch/m3-rootfs/issues/14-fast-sweep.md). It keeps the trace: the same trace_event= ring, the probe copy from trace_snapshot (now at about 32 s, 586 lines kept on 2026-11-04-24, since no touch window floods the ring) and the end copy in the netlog, so attempt.sh trace scmi and trace i2c read a fast boot as before. Gone on the fast sweep: the *-dmesg captures (the console has the printk lines live, the netlog’s dmesg section has them all), the prompts and settles. GBOOK_CMDLINE_EXTRA=gbook.sweep=full brings the M2 sweep back on a ROOT stick.

Debugging the sound card: ASoC, SoundWire and audioreach tools

Research pass by an agent on 2026-11-04 (11:55 PM EST) (from date in the writing command), for M3 ticket 16 (the left amplifiers never enumerate; docs/recon/audio-2026-11-04.md). Sources are cited per item: the kernel at a804c57 (K), the alsa-utils 1.2.16 man pages, alsa-ucm-conf master. Items marked inferred or unverified are that. The first lead (a UCM PA Volume 12 cset above the driver’s cap) was checked on boot -28: every PA Volume read 6 and alsaucm exited 0, so it is not the cause of the silent pair; the enumerator registers are (the recon note, “The speaker test”).

Sources: kernel /home/the owner/.cache/gbook/linux-a804c57 (K), alsa-utils 1.2.16 man pages, alsa-ucm-conf master (installed 1.2.16.1 may differ; check /usr/share/alsa/ucm2 on the device).

Lead to run first (inferred, verify on device)

  • Master ucm2/codecs/wsa884x/four-speakers/SpeakerSeq.conf sets cset "name='WooferLeft PA Volume' 12" (and the other three), plus DAC/COMP/BOOST/PBR=1, VISENSE=0, WSA MODE=0 per amp. K/sound/soc/qcom/x1e80100.c:46-51 caps all six * PA Volume at 6 (0 dB on pa_gain -9 dB/1.5 dB steps, wsa884x.c:1732) and WSA[2] WSA_RX0/1 Digital Volume at 81 (-3 dB). SOC_SINGLE_RANGE_TLV puts through snd_soc_put_volsw (soc.h:94), whose soc_mixer_valid_ctl (soc-ops.c:155) returns -EINVAL above platform_max, so a cset of 12 fails. What alsaucm does after a failed cset, and the installed file’s ordering (per amp would leave later amps without DAC Switch; per control would not) are unverified. Check: grep -rn 'PA Volume' /usr/share/alsa/ucm2/codecs/wsa884x/ (value and order); alsaucm -n -b - with open <name from alsaucm listcards>, set _verb HiFi, set _enadev Speaker, list1 _enadevs, watching stderr; amixer -c0 contents | grep -A2 -E 'PA Volume|DAC Switch' for per-amp values and the reported max.

ASoC debugfs (K/sound/soc/soc-core.c:135-216, soc-dapm.c:2470-2600, soc-pcm.c:275-300)

  • /sys/kernel/debug/asoc/components, .../dais: every registered component and DAI name.
  • /sys/kernel/debug/asoc/<card>/<component>/dapm/<widget>: <name>: On|Off [(forced)] in N out N (w->power; N = connected endpoints), stream <name> active|inactive, widget-type, then one in|out "<path>" "<peer widget>" "<peer component>" per path with connect true: a missing expected line means a disconnected route. .../dapm/bias_level per component and card. Diff an audible amp’s widgets against a silent one’s (prefix from DT sound-name-prefix, soc-core.c:1418).
  • /sys/kernel/debug/asoc/<card>/<FE dai_link>/state: DPCM state with each connected BE’s negotiated Hardware Params (channels, rate): the only view of what WSA_CODEC_DMA_RX_0 runs under the MultiMedia2 FE. /proc/asound/card0/pcm1p/sub0/hw_params is the FE side only (designs/procfile.rst:130).
  • /sys/kernel/debug/asoc/dapm_pop_time (ms, soc-core.c:216): non-zero logs every DAPM widget power event to dmesg (soc-dapm.c:173), cheaper than ftrace.
  • /sys/kernel/debug/regmap/<sdw dev>/registers, cache_only: one per amp (devm_regmap_init_sdw, wsa884x.c:2103); diff a silent vs audible amp. cache_only is 1 until ATTACHED and again on runtime suspend (:1548-1556, :2139), so read it during playback.

SoundWire (K/drivers/soundwire/debugfs.c, sysfs_slave.c:222-246, qcom.c)

  • /sys/kernel/debug/soundwire/master-<ctrl>-<link>/<sdw:…>/registers: Register Value table (DP0, SCP, DP1..14 banks 0/1; XX = read failed; the read resumes the device). Compare DPn CHANNELEN of quiet amps. /sys/bus/soundwire/devices/sdw:*/status (Unattached|Attached|Alert) and device_number. Caveat (2026-11-04/05): that status follows the driver’s last enumeration event, not the hardware; it read UNATTACHED while two units played and Attached after they were lost. The masters’ enumerator registers decoded by scripts/swrm_enum.py (or scripts/audio-state.sh) are the truth.
  • no slave alert found.spurious interrupt (qcom.c:739): SLAVE_PEND_IRQ fired but no device in SWRM_MCP_SLV_STATUS is in Alert; ratelimited, harmless by itself. SWR bus clsh detected (qcom.c:760): MASTER_CLASH_DET; the driver masks that interrupt after the first hit, so it logs once; follow up with status/registers.
  • qcom,port-mapping (bindings/sound/qcom,wsa8840.yaml:35, wsa884x.c:2089, qcom.c:1179): 6 u32s in slave port order (DAC, COMP, BOOST, PBR, VISENSE, CPS; wsa884x.c:798-805), each the controller port whose qcom,ports-* config that codec port takes; absent = positional fallback. A wrong map silences amps without errors.

Tracing and dyndbg (K/include/trace/events/asoc.h, Documentation/trace/events.rst:45-56, admin-guide/dynamic-debug-howto.rst:249-269, kernel-parameters.txt:7684)

  • Boot: trace_event=asoc:* (snd_soc_dapm_widget_power, snd_soc_dapm_path, snd_soc_dapm_connected, …); runtime echo 'asoc:*' > /sys/kernel/tracing/set_event, read /sys/kernel/tracing/trace. snd_pcm:* needs CONFIG_SND_DEBUG (designs/tracepoints.rst). SoundWire has no tracepoints.
  • dyndbg="file drivers/soundwire/bus.c +p; file drivers/soundwire/qcom.c +p; file sound/soc/codecs/wsa884x.c +p" on the command line, or the same query into /proc/dynamic_debug/control. Yield at a804c57: bus.c is rich (enumeration, dev_num, PING status, clock stop); qcom.c has 3 sites, wsa884x.c 2; lpass-wsa-macro.c, q6apm.c, topology.c, audioreach.c have no dev_dbg, so dyndbg there shows nothing.

Topology (K/sound/soc/qcom/qdsp6/topology.c:1415-1445)

  • Loaded from qcom/<card->driver_name>/<card->name>-tplg.bin (q6apm.c:841); no debugfs of its own; its widgets/routes appear in the q6apm component’s dapm/. Decode offline: alsatplg -d <bin> -o <out.conf>.

alsa-utils (man pages 1.2.16)

  • speaker-test -D hw:0,1 -c 4 -t wav -s N for N 1-4 (-s one channel; -t wav speaks the channel name) and -m FL,FR,...; the card’s 4-channel map is FL, LB, FR, RB by index (x1e80100.c:110-115, DSP positions; physical amp per index unverified). Read the pattern: both silent amps on one bus (WSA vs WSA2) points at bus config (ports, port-mapping, BE params); one woofer plus one tweeter points at per-amp control state.
  • aplay -v prints the hw/sw params actually set; --dump-hw-params lists the device capabilities.
  • amixer -c0 contents (all controls, values, limits), cget; from a script, address a control whose name has spaces by numid=N (amixer cset name="WSA WSA_RX0 Digital Volume" 0 found nothing on 2026-11-04 and the mute test was void; numid=21 worked); alsaucm -c <name> list _verbs, list _devices/HiFi, list1 _enadevs, set _enadev Speaker (batch -n -b -).
  • /usr/sbin/alsa-info --no-upload --stdout --with-amixer (Debian path): one-shot snapshot of cards, mixer, dmesg, /proc/asound.

WSA884x specifics (K/sound/soc/codecs/wsa884x.c)

  • hwmon wsa884x per amp (/sys/class/hwmon/hwmon*/name, temp1_input milli-degrees, :2114); measured only with the PA off, cached value while playing (:1873): four instances prove enumeration, not playback.
  • Controls: PA Volume (0-31 raw, capped 6), WSA MODE (Speaker/Receiver), DAC|COMP|BOOST|PBR|VISENSE|CPS Switch (:1740-1750, each enables one SoundWire port; VISENSE = V/I sense). DAPM map: only SPKR <- IN (:1754).

The downstream driver (research pass, 2026-11-05 (12:27 AM EST))

Source: CodeLinaro audio-kernel-ar, branch audio-kernel.lnx.6.0.r67-rel (no x1e80100 branch exists there; 6.12.r4 has the same probe logic): https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/audio-kernel-ar/-/blob/audio-kernel.lnx.6.0.r67-rel/<path>. Read for ticket 16 against the stock log dumps/firstboot/2026-11-05-stock-lastkmsg-swr.txt.

  • wsa884x_swr_probe (asoc/codecs/wsa884x/wsa884x.c:1989-2045): supplies on, the spkr-sd-n-node active state, a 5 ms sleep “for soundwire auto enumeration”, then swr_get_logical_dev_num(); a failure is dev_dbg only and the probe defers. VPHX EN Status (wsa884x_validate_dt_configuration_params, :1130-1138) is a codec register read over SoundWire (WSA884X_VPHX_SYS_EN_STATUS, wsa884x-registers.h:323) from the ASoC component probe, which runs only after the device number was assigned: a VPHX line for 0220 proves that amplifier enumerated on stock; its “devnum assigned” line fell outside the pstore window.
  • swrm_get_logical_dev_num (soc/swr-mstr-ctrl.c:2860-2931): reads the hardware enumerator table (0x530/0x534 + 8*i) for i = 1..qcom,swr-num-dev (2 per bus on stock mica, entry00-base.dts:17647,17716), matches addr exactly, requires the SWRM_MCP_SLV_STATUS slot to read attached or alert; no retry inside; “device 0x%llx is not ready” on failure (dev_err).
  • AUTO_ENUM_FAILED (swr-mstr-ctrl.c:2675-2687): the downstream IRQ handler writes SWRM_ENUMERATOR_CFG 0, waits for SWRM_ENUMERATOR_STATUS idle, writes 1 (re-arms the hardware enumerator). Mainline drivers/soundwire/qcom.c at a804c57 only defines the bit (:62). CHANGE_ENUM_SLAVE_STATUS goes to swrm_process_change_enum_slave_status (:2431-2500). Bus clash handling matches mainline (mask the interrupt after the first hit).
  • Unique id: downstream reg = <0x4 0x2170220>/0x2170221 is the same 48-bit id mainline byte-swaps out of the two registers; the last digit is unique_id 0 or 1 (speaker@0,0/@0,1). Neither driver handles two slaves with equal ids; no SWR_ID strap code exists in wsa884x.c. Qualcomm’s pineapple reference pairs the shutdown pin by unique id (wsa_spkr_en02 for both 0220s, wsa_spkr_en13 for both 0221s; audio-devicetree pineapple-audio-overlay.dtsi:218/237/296/315), where stock mica pairs it by bus (entry00-base.dts:17662/17679/17731/17748), as dts/diag/sound.dtsi does. A disabled swr_haptics@f0170220 sits on stock swr3 (:17615-17621).

Pinning and reproducing the M3 rootfs when snapshot.debian.org throttles

Researched 2026-11-04 (written 3:42 PM ET) on the build host. Sources: the snapshot.debian.org front page and the API file in its salsa repository, metasnap.debian.net, the Debian unstable man pages (mmdebstrap, debsnap, debootsnap, debrebuild, apt-get, apt.conf, apt_preferences, sources.list, dpkg-query, dpkg-scanpackages, apt-ftparchive, diffoscope, mke2fs), the mmdebstrap script at gitlab.mister-muffin.de, apt and devscripts source on salsa, e2fsprogs source (the tytso/e2fsprogs GitHub mirror) and release notes, reproducible-builds.org, the Isar, Debusine, live-build, debos, ELBE and meta-debian documentation, the isar-users and debian-devel lists, two live HTTP reads at 3:33 PM ET [20:33 UTC], and tar --help, strings and getent on the build host. Nothing was installed, no container ran, the Googlebook was not touched. Inference marks what no source states. Script line numbers are from scripts/build-rootfs-in-container.sh as read while this note was written; the file was being edited during this research, so re-check them before quoting.

Context: ADR 0012 decision 1 builds Debian sid arm64 “from one snapshot.debian.org timestamp, with a lock list and a local .deb cache”. scripts/build-rootfs.sh and scripts/build-rootfs-in-container.sh implement it: mmdebstrap --variant=apt against http://snapshot.debian.org/archive/debian/$ts/ (line 14), a .deb cache synced in and out of /var/cache/apt/archives (lines 33, 50), lock.txt as pkg:arch=version (line 35), and mke2fs -d rootfs.tar with -U and hash_seed from the epoch (lines 73-74). On 2026-11-04 the owner saw both snapshot hosts answer 503/504 and deliver 17 packages in 14 minutes (owner’s report, this session). This note asks what the primary sources say about the service’s limits, the alternatives, lock lists, and the reproducibility of the output.

1. snapshot.debian.org: limits, the Cloudflare name, other services, debsnap

What Debian publishes. The front page (snapshot.debian.org, read 2026-11-04) gives the usage (deb [check-valid-until=no] https://snapshot.debian.org/archive/debian/<yyyymmddThhmmssZ>/ <suite> main; “Valid date formats are yyyymmddThhmmssZ or simply yyyymmdd”) and, in its 2024-10-08 news entry, the only statement on throttling: “The Netfilter rate limiting has been disabled.” “It will be replaced by rate limiting on the HTTP layer, which should be easier for clients to deal with.” The same entry says the HTTP interface is served from the new Glasklar Teknik server and a LeaseWeb VM, and that the archive holds 172 TB. No number, window or policy is published; there is no FAQ; bugs go to the snapshot.debian.org pseudo-package and questions to the debian-snapshot list. The machine interface (API file, read 2026-11-04) documents /mr/package/<src>/<ver>/binfiles/<bin>/<binver>, /mr/binary/<bin>/, /file/<hash> and /mr/timestamp and states no usage policy. The repository README says of the web application: “It’s not really built to scale well, but for now it’s one way to access the data.”

The CDN. Philipp Kern, replying on debian-devel to a “503 Response object too large” report (lists.debian.org/debian-devel/2025/06/msg00147.html, quoted in Christof Warlich’s message of 2025-06-11): “Generally Fastly requires to turn on segmented caching to serve larger objects”, “our account still supports 2 GB for requests without Streaming Miss (snapshot.d.o) and 5 GB for requests with Streaming Miss (deb.d.o)”. So snapshot.debian.org is served through Fastly.

snapshot-cloudflare.debian.org. No Debian document read for this note names it: not the front page, the API file, the README, nor the debootsnap and debrebuild scripts. Live read at 3:33 PM ET [20:33 UTC], one GET per host of /archive/debian/20261104T000000Z/dists/sid/Release:

Host (DNS at 3:28 PM ET)AnswerHeaders
snapshot.debian.org (151.101.2.132, .66.132, .130.132, .194.132)302 to /file/8528f033…/Releaseserver: Apache, via: 1.1 varnish (Varnish/7.1), 1.1 varnish, x-served-by: cache-mia-kfll1870075-MIA, x-cache: MISS
snapshot-cloudflare.debian.org (104.20.45.122, 172.66.150.28)503server: cloudflare, cf-cache-status: BYPASS, retry-after: 0, and also via: 1.1 varnish, x-served-by: cache-mia-kmia1760085-MIA, x-cache: MISS

Inference from the headers: the Cloudflare name is a second CDN front that forwards to the same Varnish/Fastly chain (the x-served-by cache node naming is identical), not a separate copy of the archive. Its 503 came from behind Cloudflare, so switching hosts gains nothing when the backend is the bottleneck. The only statement on its history is third-party: Felix Moessbauer (Siemens) on isar-users, 2024-09-03: “Please note, that snapshot-cloudflare stopped mirroring in June.” and “there we have massive problems with rate limiting” (groups.google.com/g/isar-users/c/LSwbPYxSNCE). An isar-cip-core patch of April 2024 still used a snapshot-cloudflare.debian.org/archive/debian/20240411T000000Z URL (lore.kernel.org). Note that at 3:33 PM the main host answered while the owner’s reads earlier in the day failed: the state changes within a day.

How apt retries. apt’s acquire-worker.cc (salsa main, read 2026-11-04, source): Acquire::Retries defaults to 3 (acquire-item.cc), Acquire::Retries::Delay defaults to true, the delay is 1 << Iter seconds capped by Acquire::Retries::Delay::Maximum (default 30), and Retry-After is honoured only when the method reports TooManyRequests (HTTP 429) and Acquire::Retries::HandleRetryAfter is true. The code comments: “Usually, all requests run into the rate limit at the same time. Distribute the retries to avoid hitting the limit again.” A 503 gets the plain backoff, and today’s 503 carried retry-after: 0 anyway. apt.conf(5) documents only Acquire::Retries (“Number of retries to perform. If this is non-zero APT will retry failed files the given number of times.”); the Delay options are undocumented there. Isar’s response to the same problem (isar-users patch, Felix Moessbauer, 2024-11-06): “The snapshot mirrors apply strict rate limiting, making it impossible to use them with a default apt configuration.” It sets Acquire::Retries to 10 and Acquire::Retries::Delay::Maximum to 600 whenever ISAR_USE_APT_SNAPSHOT is on, 3 and unset otherwise. debrebuild sets Acquire::Retries "5" and Acquire::http::Dl-Limit "1000" and notes “Release files from snapshots.d.o have often expired by the time we fetch them” (debrebuild.pl).

Other services.

  • snapshot.notset.fr and debian.notset.fr resolve to no address today (getent ahostsv4, 3:28 PM ET). The software behind them, fepitre/snapshot-mirror, calls itself a “Debian snapshot service written from scratch” that “currently uses snapshot.debian.org set of timestamps and data for provisioning the service”, serves “A partial timestamps set” at snapshot.notset.fr, and limits clients in nginx (limit_conn conn_limit_per_ip 20; limit_rate 10m;) “for allowing every Debian rebuilder infrastructure to scale their actual builders”. The repository page shows no archive notice and no commit date (read 2026-11-04).
  • snapshot-mlm-01.debian.org was offered as a workaround endpoint during the June 2024 hardware migration (bug #1072675: “We’re currently migrating to new hardware. Not losing any data.”, Linus Nordberg, 2024-06-12). It is a Debian machine, not documented for general use (inference: not a pin target).
  • metasnap.debian.net “is an addition to snapshot.d.o”: for each suite, architecture and binary package it records “which version of it was seen in which snapshot.d.o timestamps in that suite”; its pkgs= endpoint takes name=version pairs and returns a minimal set of timestamps covering them (POST for lists over 32768 bytes); timestamps are indexed only once they are 12 hours old. Run by Johannes Schauer Marin Rodrigues.
  • Debusine serves its own repositories at /{scope}/{workspace}/{timestamp}, “compatible with snapshot.debian.org and snapshot.ubuntu.com” (explanation/package-repositories). That is Freexian’s archive, not a mirror of Debian’s.

debsnap (debsnap(1)) “is a tool to help with retrieving snapshots of old packages from a daily archive repository”: by default every version of one source package, --binary and -a <arch> for binaries, --first/--last for a range, DEBSNAP_BASE_URL defaulting to https://snapshot.debian.org, exit status 2 when some files “are no longer available on the snapshot mirror”. It is a per-package fetcher against the same service, so it fills a cache but does not avoid the limit.

debootsnap (debootsnap(1) and the script) “Combines debootstrap and snapshot.debian.org to create a chroot tarball with exact package versions from the past”. Its input is a list pkg1:arch=ver1,pkg2:arch=ver2 (or --buildinfo), produced on the source machine with dpkg-query --showformat '${binary:Package}=${Version}\n' --show. The script asks metasnap for the timestamps, writes one deb line per timestamp, downloads each name:arch=version with apt-get download through a local caching proxy (devscripts.proxy.setupcache, http_proxy=http://127.0.0.1:<port>, Acquire::Retries "5"), builds a local flat repository served as deb [trusted=yes] http://localhost:<port>/ ./ with a dummy package whose Depends: names every pkg:arch (= ver), and runs mmdebstrap --variant=essential --include=debootsnap-dummy --format=tar. This is the pattern reproduce.debian.net runs at scale: “rebuilderd calls debrebuild that calls debootsnap, mmdebstrap and finally sbuild”.

2. Local mirrors and caches; how other projects pin

mmdebstrap’s own tools (mmdebstrap(1), 1.6.0-5, read 2026-11-04):

  • sync-in <outside> <inside>: “Recursively copy everything inside pathoutside into pathinside inside the chroot” “without deleting anything from pathinside but overwriting content as necessary”; sync-out the reverse. EXAMPLES: “If, for some reason, you cannot use a caching proxy like apt-cacher or apt-cacher-ng, you can use the sync-in and sync-out special hooks to synchronize a directory outside the chroot with /var/cache/apt/archives inside the chroot”, and “Instead of copying potentially large amounts of data with sync-in you can also use a bind-mount in combination with a file:// mirror”.
  • The essential step “Removes all packages downloaded in the download step, except those which were present in /var/cache/apt/archives/ before (if any). This can be disabled using –skip=essential/unlink.” The script’s --skip=essential/unlink (line 53) therefore keeps the newly downloaded .debs for the sync-out (inference from the two sentences; the man page does not spell out the combination).
  • --hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount: “A hook that automatically makes the contents of file:// mirrors as well as .deb packages given with –include available inside the chroot”; “This hook takes care of copying all relevant file to their correct locations and cleans up those files at the end.” --include accepts “apt features like pkg/suite, pkg=version, pkg-” and .deb paths, with the caveat “Since apt (outside the chroot) passes paths to dpkg (on the inside) verbatim, you have to make the .deb package available under the same path inside the chroot as well”. copy:// is not mentioned.
  • --aptopt values are “permanently added to /etc/apt/apt.conf.d/99mmdebstrap inside the chroot”; the man page’s own example is --aptopt='Acquire::Check-Valid-Until "false"', “necessary for allowing old timestamps from snapshot.debian.org”. So line 53’s option, and any retry option added the same way, stays in the rootfs.
  • --simulate: “Only the package cache is initialized but no binary packages are downloaded or installed”, “No hooks are executed”, and for a tarball “no output is produced” (ticket 02 hit this).

What the cache does not cover. sync-in fills /var/cache/apt/archives, apt’s download cache (apt.conf(5): Dir::Cache::archives is “the location to place downloaded archives”). apt still needs the mirror’s InRelease and Packages indexes, so a snapshot that answers 503 blocks the build even with every .deb cached (inference: the man page describes only what the hooks copy). The way round, which debootsnap takes, is a local flat repository:

  • DebianRepository/Format: “A flat repository does not use the dists hierarchy of directories” and “places meta index and indices directly into the archive root”; the sources line is deb uri directory/. sources.list(5): the “suite must end with a slash (/)”; “The file scheme allows an arbitrary directory in the file system to be considered an archive”; copy is the same but copies into the cache; trusted=yes makes apt accept the source without authentication, which the page says to use only in local, trusted contexts.
  • dpkg-scanpackages(1) “creates a Packages file”; -m includes every version found; “apt(8) ignores uncompressed Packages files except on local access (i.e. file:// sources)”. apt-ftparchive(1) is “a superset of the dpkg-scanpackages(1) program”; its release command writes a Release file “containing (by default) an MD5, SHA1, SHA256 and SHA512 digest” with APT::FTPArchive::Release::{Origin,Label,Suite,Codename,Valid-Until, Acquire-By-Hash,...}. Neither page covers signing.

apt-cacher-ng (user manual, sections as numbered there): “a caching proxy for software packages which are downloaded by Unix/Linux system distribution mechanisms from mirror servers accessible via HTTP”; clients use Acquire::http::proxy "http://CacheServerIp:3142"; or put the mirror host after the cache address in the URL (3.2); Remap-debrep merges mirrors that are “considered equal” (4.3); “The internal cache directory tree follows the URL requests from the clients unless modified by Remapping rules” (4.3.2), so each snapshot timestamp is its own tree (inference); existing files go into the _import directory (8.1); expiration removes files that are “not referenced in any index file” after a grace period (ExTreshold, 7.1, 9.3). An Offlinemode setting was not found in the pages read (unverified). Isar users measured “~12min” to “~7min” for four images with it (Alexander Heinisch, isar-users, 2024-08-22, thread above). Trade-off: a daemon and its expiration rules, for a benefit the sync-in cache already gives a single-image project.

aptly (overview, mirror create): “aptly’s goal is to establish repeatability and controlled changes in package environments”; a mirror “consists of metadata, list of packages and package files”, a snapshot is an “immutable list of packages”; aptly mirror create <name> <archive url> <distribution> [<component1> ...] with -architectures, -filter=<query> and -filter-with-deps; flat repositories take ./ as distribution. A mirror of one snapshot timestamp filtered to rootfs/packages.txt with dependencies would be a self-hosted pin, at the cost of a Go tool and its database.

reprepro (reprepro(1)) “is a tool to manage a repository of Debian packages”, filled by includedeb or by update from conf/updates rules (Method is an apt URI, FilterFormula selects packages). Whether current reprepro keeps more than one version per package per distribution (a Limit field) was not found in the man page text read: unverified.

How established projects pin (each from its own documentation, read 2026-11-04):

  • Debian reproducible builds: the debootsnap chain above; .buildinfo files from buildinfos.debian.net, exact versions from metasnap and snapshot (reproduce.debian.net).
  • Debusine: timestamp URLs as above; “by-hash files under dists/, and all files resulting from a timestamp-specific query are immutable: they may be removed, but they won’t be replaced with different contents at the same URL”, served with Cache-Control: max-age=31536000; clients pin by putting the snapshot ID in the repository URL, and a Snapshots field in generated Release files is planned. Its MmDebstrap task turns bootstrap_repositories into a sources.list “fed to mmdebstrap as input”, extra_packages into --include.
  • live-build (manual 8.1-8.4): --mirror-bootstrap, --mirror-chroot, --mirror-binary*; pinning through config/archives/*.pref and config/apt/preferences; local .debs in config/packages.chroot/ (“automatically installed into the live system during build”); --apt-options passes options through.
  • debos (actions): the debootstrap action has suite, mirror, variant, keyring-package, keyring-file, check-gpg, merged-usr; the apt action packages, recommends, unauthenticated, update. No snapshot, pin or cache field.
  • ELBE (schema reference): mirror with a primary_host and a url-list of extra repositories; each pkg has version, pin (“prefer the defined version of the debian package”) and md5/sha256 attributes. No snapshot mention.
  • Isar (user manual, debian-common.conf): ISAR_USE_APT_SNAPSHOT “Use a frozen apt snapshot instead of the live mirror”, ISAR_APT_SNAPSHOT_DATE in 20240702T082400Z form, the timestamp “Automatically derived from SOURCE_DATE_EPOCH if not overwritten”; the premirror rewrites deb.debian.org/(.*) to snapshot.debian.org/archive/\1/<date>; base-apt caches “upstream debian packages to reduce time for further downloads and to be able to work offline” (ISAR_USE_CACHED_BASE_REPO; “By default, the local caching repo is not gpg signed”); retries 10 / 600 s when snapshots are on.
  • meta-debian (README): poky recipes for “cross-building GNU/Linux images using Debian source packages”; a source build, not a binary pin; no snapshot text.

3. Lock lists

pkg=version. apt-get(8): “A specific version of a package can be selected for installation by following the package name with an equals and the version of the package to select.” “Both of the version selection mechanisms can downgrade packages and must be used with care.” apt parses the suffix at the last / or = (cacheset.cc, find_last_of("/=")) and fails with Version '%s' for '%s' was not found (canNotGetVerFromVersionNumber) or Release '%s' for '%s' was not found. Against a snapshot timestamp the index does not move (inference from the service’s purpose; the front page’s news entries describe imports of the archive at each timestamp), so the version is there as long as the service answers; against GBOOK_ROOTFS_MIRROR=http://deb.debian.org/debian (build-rootfs.sh line 10) sid moves daily (live read: sid’s Release is dated Wed, 04 Nov 2026 14:14:42 UTC, Valid-Until seven days later) and “was not found” is the failure mode for any pinned version that has been superseded. mmdebstrap passes pkg=version through --include (section 2), and debootsnap avoids the solver moving a version by making every entry a pkg:arch (= ver) dependency of one dummy package.

apt preferences (apt_preferences(5)): P >= 1000 “causes a version to be installed even if this constitutes a downgrade of the package”, 500 <= P < 990 “unless there is a version available belonging to the target release”, P < 0 “prevents the version from being installed”, P = 0 “has undefined behaviour”; Pin: version 5.32* with Pin-Priority: 1001 is the page’s example; “the first specific-form record matching an available package version determines the priority”, so “always list wild-card pins first, so later specific pins override it”. A 300-stanza preferences file is a second way to lock; the --include list is shorter and is what debootsnap uses.

apt’s Snapshot: option. sources.list(5): Snapshot “allows selecting an earlier version of the archive from the snapshot service”; IDs “are usually timestamps in the form of YYYYMMDDTHHMMSSZ”; apt.conf(5): “snapshots can only be acquired if a URI is known from where to get them”. Live read of deb.debian.org/debian/dists/sid/Release: the fields present are Suite, Codename, Date, Valid-Until, Acquire-By-Hash; no Snapshots field. The timestamp in the mirror URL, as the scripts do it, stays the working form.

Verifying a rebuild. dpkg-query(1): -W lists installed packages, default format ${binary:Package}\t${Version}\n, where binary:Package carries an architecture qualifier only for Multi-Arch same or foreign packages; the man page states no output order. The script’s ${Package}:${Architecture}=${Version} (line 35) qualifies every line and matches debootsnap’s pkg:arch=ver input format, so lock.txt can be fed straight back to debootsnap or, comma-joined, to --include. Two locks compare with comm -3 after LC_ALL=C sort (which lock_check already enforces, build-rootfs.sh line 25); the manifest’s lock_sha256 is the one-line form. metasnap’s pkgs= endpoint (section 1) can confirm which timestamps hold a lock, once the timestamp is 12 hours old.

4. Reproducibility of the output

The specification. SOURCE_DATE_EPOCH is “A UNIX timestamp, defined as the number of seconds, excluding leap seconds, since 01 Jan 1970 00:00:00 UTC”; tools embedding a time “MUST use a timestamp no later than the value of this variable” (clamping); “Build processes MUST NOT unset this variable for child processes if it is already present”.

mmdebstrap (man page and script, main, read 2026-11-04):

  • “By setting SOURCE_DATE_EPOCH the result will be reproducible across multiple runs with the same options”; the output “will differ if it is run on machines with differing /etc/resolv.conf and /etc/hostname contents” (copied in for debootstrap compatibility). The script’s line 34 overwrites /etc/hostname; nothing in the script writes or removes /etc/resolv.conf, so the tar carries the podman container’s copy, a reproducibility input the build does not control and possibly a LAN resolver address (whether mmdebstrap’s cleanup removes it was not verified). Cleanup will “Remove files that make the result unreproducible and write the empty string to /etc/machine-id”; “A tarball created in this mode [unshare] will be bit-by-bit identical to a tarball created with the root mode”.
  • The script extracts the essential packages chrootless “because otherwise directory creation timestamps will differ” since “we want to create bit-by-bit identical tar output” (setup()), and builds the tar with --sort=name, --mtime=@$mtime, --clamp-mtime, --numeric-owner, --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime, plus --xattrs for tar output; $mtime is $ENV{SOURCE_DATE_EPOCH} + 0.
  • ext4 output: “If SOURCE_DATE_EPOCH is set, the filesystem UUID and hash_seed will be set to a UUID derived from SOURCE_DATE_EPOCH to create reproducible images” (man page). The script derives $uuid = create_v5_uuid(create_v5_uuid($UUID_NS_DNS, "mister-muffin.de"), $mtime) (a name-based RFC 4122 version-5 UUID, SHA-1 over namespace and name; $UUID_NS_DNS = 6ba7b810-9dad-11d1-80b4-00c04fd430c8) and runs mke2fs -q -F -o Linux -T ext4 -U $uuid -E hash_seed=$uuid -b 4096 -d - <target> <numblocks>, the block count from its approx_disk_usage. It sets no E2FSPROGS_FAKE_TIME: it relies on mke2fs reading SOURCE_DATE_EPOCH itself.

e2fsprogs. Release notes: 1.47.1 (2024-05-20) “Mke2fs -d can now support an input tar file if the libarchive library is available.” and “Add support for SOURCE_DATE_EPOCH environment variable per the specification https://reproducible-builds.org/specs/source-date-epoch”; 1.46.6 (2023-02-01) “Fix resize2fs to honor the E2FSPROGS_FAKE_TIME environment variable … to create reproducible images”; 1.47.2 (2025-01-01) gnu.translator xattrs in tar input and “Allow building e2fsprogs without libarchive-dev installed”; 1.47.3 (2025-07-08) -d copies fs-verity metadata and chattr flags; 1.47.4 (2026-03-06) “Fix mke2fs -d so it can handle copying a file larger than 2 GiB.” mke2fs(8): “Tarball input is only available if mke2fs was compiled with libarchive support enabled and if the libarchive shared library is available at run-time”; -E hash_seed=: “Use the specified UUID as the seed for hashing, rather than generating a random seed each time. Intended for use with reproducible builds.”; -U takes a UUID or clear, random, time; -E root_owner sets the root directory’s uid:gid. The source (tytso/e2fsprogs master, read 2026-11-04):

  • lib/ext2fs/initialize.c: SOURCE_DATE_EPOCH is read first and sets fs->now plus EXT2_FLAG2_USE_FAKE_TIME; E2FSPROGS_FAKE_TIME is read only when SOURCE_DATE_EPOCH is unset and sets fs->now without the flag; s_mkfs_time and s_lastcheck come from ext2fsP_get_time(fs).
  • misc/create_inode.c: clamped_time returns fs->now only if ((fs->flags2 & EXT2_FLAG2_USE_FAKE_TIME) && t > fs->now); file times are set with ext2fs_inode_xtime_set(&inode, i_mtime, clamped_time(fs, st->st_mtime)); a directory input is walked with scandir(".", &dent, NULL, alphasort); hard links are matched by device and inode and added with add_link.
  • misc/create_inode_libarchive.c: libarchive is dlopened as libarchive.so.13 (“you need libarchive to be able to process tarballs” when missing); entries are applied in archive order (archive_read_next_header loop), metadata through archive_entry_stat and set_inode_extra, hard links through add_link. So the tar’s own order and mtimes decide inode allocation and timestamps, which is why mmdebstrap sorts and clamps the tar first.
  • misc/mke2fs.c: without -U, uuid_generate(fs->super->s_uuid); with an all-zero hash seed, uuid_generate on s_hash_seed; hash_seed= is parsed with uuid_parse (“Invalid hash seed” otherwise).
  • Live read on the build host: strings of mke2fs 1.47.4 (6-Mar-2025) and libext2fs.so (e2fsprogs 1.47.4-1) contain both SOURCE_DATE_EPOCH and E2FSPROGS_FAKE_TIME.

What this means for lines 16 and 73-74. SOURCE_DATE_EPOCH is exported (line 16), so the E2FSPROGS_FAKE_TIME=$epoch prefix on line 74 is never consulted (initialize.c reads SOURCE_DATE_EPOCH first); on its own it would fix the superblock times but not clamp file mtimes (no EXT2_FLAG2_USE_FAKE_TIME). The comment above line 73 says the UUID is derived “as mmdebstrap’s own ext4 output does”: the -U/hash_seed pairing matches, the derivation does not (sha256 of the epoch cut into UUID shape here, a version-5 UUID over mister-muffin.de there). Both are deterministic for one epoch; only the comment is wrong.

Tool versions float. Line 23 installs mmdebstrap and e2fsprogs from live sid at build time, and the manifest records the versions (lines 83-84). Inference: two builds of one timestamp on different days may run different mke2fs versions and lay the image out differently while the tar is byte-identical, so the tar is the stronger contract. e2fsck -fn (line 76) checks consistency, not identity.

What the reproducible-builds documentation says about images (system-images): “It is also possible to produce bit-reproducible filesystems with recent mkfs.ext2 from e2fsprogs-1.47.1”, with -E hash_seed=<uuid> and -U <uuid> in its example; an image that “has been created by mkfs.ext4, mounted, modified and saved as an image … would not be reproducible: the allocation of the inodes is undefined”; general problems are filesystem timestamps and “UUID or a label which are not set explicitly”; “clamp mtimes of files in the root filesystem to $SOURCE_DATE_EPOCH before generating the system image”, with tar --sort=name --mtime=@$SOURCE_DATE_EPOCH --clamp-mtime in its tool table. The GNU tar manual’s Reproducibility page answered 429 three times today; tar --help (GNU tar 1.35 on the build host) gives the semantics: --clamp-mtime “only set time when the file is more recent than what was given with –mtime”, --sort=ORDER “directory sorting order: none (default), name or inode”.

What to compare between two builds. In order of cost: lock.txt (comm -3), then sha256 of rootfs.tar (content, ownership, modes, mtimes, xattrs), then sha256 of rootfs.ext4 (layout, which also depends on the e2fsprogs version). To explain a mismatch, diffoscope “will recursively unpack archives of many kinds”, lists “ext2/ext3/ext4/btrfs/fat filesystems” and “tape archives (.tar)” among its formats, and --exclude-directory-metadata ignores “permissions, timestamps, xattrs etc.” when only content matters.

Recommendations

  1. Add an offline mode to scripts/build-rootfs-in-container.sh that builds a flat repository from /work/cache (apt-ftparchive packages and release, or dpkg-scanpackages -m), passes deb [trusted=yes] file:///work/cache ./ as the mirror with --hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount, and sets --include from the previous lock.txt (pkg:arch=version is apt syntax). Why: sync-in spares the .deb downloads but apt still needs the snapshot’s indexes, so a 503 stops the build today even with a full cache; this is the debootsnap pattern. Trade-off: the flat repository is unsigned (the .debs were verified when first fetched; lock_sha256 and the cache’s sha256s are the chain of custody), and a package missing from the cache fails with “Version … was not found”, which is the wanted behaviour.
  2. Raise apt’s retries for the snapshot mirror with --aptopt='Acquire::Retries "10"' and --aptopt='Acquire::Retries::Delay::Maximum "600"' (Isar’s values), only when the mirror is a snapshot host. Why: apt’s defaults are 3 retries capped at 30 s of backoff and Retry-After counts only for 429, while snapshot answers 503 with retry-after: 0. Trade-off: a dead mirror takes longer to fail, and the options land in the rootfs’s /etc/apt/apt.conf.d/99mmdebstrap (harmless for a rootfs that never runs apt; a customize hook can delete the file).
  3. Fix lines 73-74: drop E2FSPROGS_FAKE_TIME= (dead with SOURCE_DATE_EPOCH exported) and reword the comment: -U and hash_seed follow mmdebstrap, the derivation is this script’s own sha256 form. Add e2fsprogs >= 1.47.1 as a stated floor (tar input and SOURCE_DATE_EPOCH; 1.47.4 for files over 2 GiB). Trade-off: none.
  4. State the reproducibility contract in ADR 0012: two builds of one timestamp must agree on lock.txt and the rootfs.tar sha256; the rootfs.ext4 sha256 is expected to agree only with the same e2fsprogs version (recorded in the manifest). Why: the container’s tools float with sid (line 23). Alternative: install the tools from the same snapshot timestamp. Trade-off: that puts the tool install behind the throttled service too; recording versions and comparing tars costs nothing.
  5. When GBOOK_ROOTFS_MIRROR points at deb.debian.org, feed --include from the last lock.txt so drift fails loudly instead of silently moving versions, or have the manifest carry a lock_changed=N line from comm -3 against the previous lock. Why: sid moves daily and the manifest’s mirror= line alone does not say what changed. Trade-off: a pipeline test against a moved sid fails until the lock is refreshed on purpose.
  6. Add the lock comparison to the normal build: keep the previous lock.txt as lock.prev.txt and write lock_changed=N into the manifest. Why: it is the cheapest reproducibility check and the first thing to read when sha256s differ. Trade-off: none.
  7. Record in ADR 0012’s next update that snapshot-cloudflare.debian.org fronts the same backend (live read, section 1), that Debian publishes no limit figures, and that the project’s answer to throttling is the cache plus the offline flat repository rather than a mirror switch; note metasnap’s 12-hour rule if debootsnap or metasnap are ever used to find timestamps. Trade-off: none.
  8. Write a fixed /etc/resolv.conf in the customize hook, or remove it and let systemd-resolved or networkd manage it at boot. Why: mmdebstrap copies the host’s file in, the man page names it as a reason outputs differ, and the script does not touch it, so the container’s resolver address ends up in the tar. Trade-off: none for a rootfs that gets its network from /run/gbook/ and DHCP.
  9. Do not add apt-cacher-ng or aptly for this. Why: the sync-in cache plus recommendation 1 covers a single-image project; both tools add a daemon or a database. Reconsider only if several images or hosts share the cache.

Sources

M4 recovery matrix: Valve’s five rows mapped onto mica’s ChromeOS mechanisms

Written 2026-11-05, 10:58 AM EST [15:58 UTC], task #32 (owner decision 2026-11-05 on steamos-arm.md, “Decisions”, 1). Planning material for M4 (daily-drivable, Gate 3: ADR 0010: “installed on the spare SSD, with dual-boot or a documented return to stock”). Nothing here was run on the device; no ADR is written yet (the slot policy is an open owner decision below). Every row carries one of three labels: proven on mica (an attempt id or log row), proven in source (a research note’s file:line at its pin), or inference / untested. The vboot and depthcharge citations come from fit-packaging.md and chromeos-boot-path.md, both pinned at firmware-bluey-16702.B; this note does not reopen the source.

Answer in short

M4’s “documented return to stock” is already proven except for the physical swap. ADR 0002 puts M4 on a spare M.2 2230 SSD with the stock drive in a drawer, and ADR 0003 forbids writing the stock drive, so dual-boot on the stock SSD is excluded; “dual-boot” today is stick in, stick out (status row “Reboot back to stock”: stock boots once the stick is out, proven 2026-11-02-2 and 2026-11-03-5). Return at M4 is therefore: power off, put the stock SSD back, power on, Boot from internal disk, and Lock bootloader if a locked device is wanted. Lock bootloader wiping the data and relocking without the recovery stick is proven (project log 2026-11-02, “Unlock day, relock test passed”; recon.sh read green, flash.locked=1, locked). What is untested is what ADR 0002 already lists: does the firmware accept an unlisted drive, does a blank drive still reach recovery and fastboot, and is the drive bound to the device. That turns M4’s gate from “design a recovery system” into three owner-run probes (section “Test plan before M4”).

The Frame’s five rows and mica’s answers:

NeedSteam Frame (first-party recipe)mica today (M3, stick)mica at M4 (spare SSD)Label
Boot the previous OS, keep dataAUX + power, “Previous B” in the U-Boot menu; splctl set-state <slot> good after a verified bootno slot picker in the dev-mode menu (Lock bootloader, Boot from internal disk, Boot from external drive); vboot picks the highest-priority kernel entry with tries or successful, deducts a try from a successful=0 entry, marks a failing entry 0/0the same vboot rules on the SSD: two kernel partitions, “previous” is either the automatic fallback when the new one’s tries run out, or a cgpt write from the running OS before rebootproven in source (vboot selection, fit-packaging.md section 5); the count-down proven on mica (retro-7/08: 3 tries ran out after 3 boots, so packaging sets 15); the two-slot layout untested
Reset the OS, keep user data“Repair Steam Installation” menu entry; steamos-reset-tool from Settingsnone: the stick’s ROOT is rewritten whole with attempt.sh root-cmdrewrite the inactive root partition from the build host or from the running OS, user data on its own partitioninference (nothing exists)
Wipe user data without booting the OS“Erase User Data” menu entry (/efi/steamos/factory-reset)nonea mke2fs of the data partition from /init behind a command-line flag (proposal; the M4 shape of #36)inference (nothing exists)
Reinstall from USBboot menu, USB repair stick, “Wipe Device & Install SteamOS”the Gate 1 recovery stick: Esc + F2, then Use USB drive; erases the disk, installs 16560795 (the installed build)the same stick; with the spare SSD installed it would install stock onto the spare drive, untestedproven on mica to the recovery screen (Gate 1, 2026-11-02, ADR 0001 update); the install itself never run; the firmware rewrite it may do is inference (runbook section 9)
Unbrick the boot firmwarea QDL (EDL) package, 4.1 GBno EDL path documented; ChromeOS recovery mode is the only path, CCD stays locked, no agent writes firmware (ADR 0001, ADR 0006)the sameproven in source that depthcharge’s fastboot has no boot and no unlock command (chromeos-boot-path.md section 4); “no EDL path” is the absence of evidence, stated as such
Know the state“Purple, solid: in bootloader” (LED)the AP console and the EC’s power lines over SuzyQ (attempt.sh watch classifies them); LED codes 3,5 and 4,3 (runbook section 8); Advanced options > Firmware log; the dev-mode warning screenthe sameproven on mica (every boot since 2026-11-02; the three release boots of 2026-11-05 had no AP console because the SuzyQ was unplugged on the build host’s side, and the netlog covered only the post-Wi-Fi window)

1. Previous slot: what vboot does, and what the owner can do

  • Selection (fit-packaging.md section 5, vboot at firmware-bluey-16702.B): GptNextKernelEntry skips entries with neither successful nor tries and picks the highest priority (cgptlib.c:44-120); a booted entry with successful=0 loses a try (2load_kernel.c:768-779); with successful=1 tries are ignored (cgptlib.c:135-144); a failing entry is marked bad, 0/0, and the next one is tried (2load_kernel.c:742-743).
  • On mica today: the stick’s KERN-A is written -S 0 -T 15 -P 15 (scripts/package-in-container.sh, retro-7/08): depthcharge takes one try per boot, nothing marks the kernel successful, and successful=0 keeps the 0/0 mark a rejected kernel gets (“No valid image detected”, runbook section 8). attempt.sh stick prints the tries left and new warns at 0. Proven on mica: 3 tries ran out after 3 boots on 2026-11-04.
  • The Frame’s set-state good maps to cgpt add -i <N> -S 1 on the boot disk from the running OS after a verified boot (cgpt add -S/-T/-P, cmd_add.c:16-33). Two consequences:
    • The rootfs has no cgpt today (rootfs/packages.txt, read 2026-11-05); Debian ships it in cgpt (source vboot-utils): a package to add for M4, to be checked with scripts/size-rootfs.sh first.
    • It is a GPT write to the boot disk: M4 work under the owner’s OK, never in the stick era, where the stick is the only disk gbook writes and the build host writes it.
  • “Previous” without a menu: with two kernel partitions on the SSD (KERN-A, KERN-B), “boot the previous OS” is either the automatic fallback (the new slot’s tries run out, or it is rejected and marked 0/0, and vboot takes the other) or an explicit cgpt add -i <old> -P <higher> before reboot. Neither is a key press at power-on. The firmware-fastboot oem set-priority and oem set-successful exist (chromeos-boot-path.md section 4) but need an ALink or PL27A1 bridge or a USB-Ethernet adapter over DHCP: not available with the current hardware.
  • The policy decision (open, grilling question 1): keep the stick’s count-down (-S 0 -T n, a bad kernel marks itself 0/0, a good one still runs out of tries unless something writes successful), pmbootstrap’s installed-system values (-S 1 -T 5 -P 10, unlimited boots, no 0/0 signal), or two slots with the OS flipping successful after a verified boot (Valve’s shape; the “verified” test is gbook’s: m3-end reached, the names heard, the audio state read).

2. Reset the OS, keep user data

Nothing exists. Today the whole ROOT partition is rewritten from the build host (attempt.sh root-cmd, root-verify), which is the stick’s “reinstall”. At M4 the analogue is a root partition per slot (ROOT-A, ROOT-B) and a separate data partition (home, or Valve’s var + home split), so that a root rewrite leaves the data. Valve’s update discipline (write the inactive slot, read back, compare, only then switch) is the one attempt.sh verify already follows for the stick; keep the ordering when KERN-B comes (steamos-arm.md, section 5, second proposal). Inference throughout; no partition layout is decided.

3. Wipe user data without booting the OS

Nothing exists. Proposal: /init takes a command-line flag (gbook.wipe=data, name open) and runs mke2fs on the data partition, after printing what it is about to erase on the AP console and the panel, with a countdown. It needs mke2fs in the initramfs (busybox has mkfs.ext2 only; check before relying on it) and a way to set the flag without a rootfs: on the stick era that is a repackage; at M4, cgpt can only set attributes, not the command line, so the flag would live in the FIT’s command line of a dedicated “wipe” kernel entry (inference), or the wipe stays a the build host-side operation on the swapped-out drive. Task #36 covers the read-only lock in /init; this flag is its M4 sibling.

4. Reinstall from USB: the recovery stick

  • Proven: Device Recovery Utility media reaches the recovery screen with Esc + F2 and serves 16560795, the installed build (Gate 1, 2026-11-02; ADR 0001 update). A full copy is in dumps/gate1/.
  • Never run: Use USB drive. Runbook section 9: it erases the disk and installs 16560795; the stick’s ROOT-B carries chromeos-firmwareupdate, so a recovery may rewrite AP and EC firmware (inference).
  • Two hazards Valve’s recipe has no analogue for:
    • the recovery keys with the image stick inserted mark KERN-A bad (runbook section 8; proven on mica, wifi-2026-11-03.md: the owner powered on with the recovery keys and the stick was rejected), so the image stick and the recovery stick are different Lexar drives and the recovery one stays out unless a recovery is intended;
    • with the spare SSD installed, Use USB drive would install stock onto the spare drive (inference), which is the “unlisted drive” probe’s second half: does it install, and does the result boot.

5. Unbrick the boot firmware

No EDL path is documented for mica. The boot firmware is depthcharge (Google_Mica.16702.*); CCD stays Locked and the agent rules forbid every firmware write (ADR 0001, ADR 0006, CLAUDE.md). ChromeOS recovery mode plus the recovery stick’s firmware updater (inference, section 4) is the only path on record. Valve’s QDL package is an answer for a Qualcomm device whose PBL exposes EDL; whether mica’s does is unknown and stays untested: an EDL probe would need a cable in the data lines at power-on and is outside every gate.

6. Know the state

SignalWhat it showsWhere
AP console over SuzyQ (-if01)depthcharge’s screens as text, the kernel’s early log, every GBOOK: captureattempt.sh watch, attempt.sh follow
EC console (-if02)the power sequence: EC: POWER ON (S5->S3), powered off (S5->G3), soft-offattempt.sh watch (scripts/ec_power.awk)
LED codes3,5 = EC power-on timeout; 4,3 = LCD fail at the firmware stagerunbook section 8
The dev-mode warning screenthe device is unlocked; its menu has the three entriesdepthcharge screens.c:1801 (unlock-day ticket)
Advanced options > Firmware logwhy depthcharge refused an imagerunbook section 8
scripts/recon.sh on stockverifiedbootstate, flash.locked, vbmeta.device_statethe relock test
The netlogthe kernel log after Wi-Fi joins, the rootfs journalattempt.sh netlog

The netlog is the fallback when the SuzyQ is out, not a replacement: it starts after Wi-Fi joins (about 35 s) and shows nothing of depthcharge. The three release boots of 2026-11-05 ran that way (log row “Release bar met for v2026.11.05”).

Test plan before M4 (owner only, no agent writes)

Each step opens the case: ADR 0002’s prerequisite (disconnect the battery-cable bracket, hold power 20 s, Service Manual) and the warranty note apply. In order, each one a dated recon note:

  1. Blank-SSD probe (project log Phase 3 list): the spare SSD, empty, installed; power on with no keys. Learns whether recovery and fastboot live in the 32 MB firmware flash (fastbootd lives on the SSD), and what the firmware shows with no bootable disk (the dev-mode menu, or recovery). The SuzyQ records it.
  2. Unlisted-drive probe (ADR 0002): the spare SSD with a gbook install written on the build host (the stick’s layout: KERN-A plus ROOT, cgpt attributes as decided in grilling question 1). Boot from internal disk. Pass = m3-end on the netlog and the names heard. Dell lists “Google Qualified” drives; this is the test of whether an unlisted one is accepted at all.
  3. Swap-back probe: the stock SSD returns; power on; Boot from internal disk; stock boots (recon.sh reads orange, unlocked, the installed build). Then, if the owner wants the locked state: Lock bootloader (wipes; proven to relock without the recovery stick on 2026-11-02), stock boots, recon.sh reads green, flash.locked=1, locked. This probe is the one that closes M4’s “documented return to stock”.

Optional, after 2: the recovery stick with the spare SSD installed (section 4’s second hazard), only if the owner wants stock on the spare drive too.

Commands (named, not run)

PurposeCommandWhen
Read a disk’s kernel entriescgpt show -i <N> -P / -T / -S (or -A) <disk>any time on the build host against the stick (attempt.sh gpt wraps it)
Mark the booted kernel good (Valve’s set-state good)cgpt add -i <N> -S 1 <disk>M4, from the running OS, owner’s OK, after the verified-boot test
Prefer the other slot before a reboot (Valve’s “Previous”)cgpt add -i <other> -P 15 <disk> (and -T 1 -S 0 to try it once)M4, owner’s OK
Re-arm a rejected entryrewrite the image (attempt.sh dd-cmd); cgpt add -i 1 -T 15 -P 15 alone would re-arm a kernel the firmware already refusedafter “No valid image detected”
Return to stock, stick erapull the stick, power onproven
Return to stock, M4swap the stock SSD back, Boot from internal disk, Lock bootloader if wantedthe swap-back probe

Decisions (owner, 2026-11-05, 11:02 AM EST)

Grilling round; the owner took every recommendation:

  1. Slot policy: two kernel partitions on the SSD; the OS marks the booted one successful after the verified-boot test (m3-end reached, the names heard), so a bad update falls back by itself. cgpt joins the rootfs for M4.
  2. The stick stays as a second recovery path at M4.
  3. The data-wipe flag in /init comes at M4, when a data partition exists (not with task #36).
  4. The M4 ADR is written after the three probes, with their results.

Open questions (grilling round, as asked)

  1. Slot policy for the SSD install (section 1): count-down as the stick, installed-system values, or two slots with the OS flipping successful after a verified boot.
  2. Whether M4 keeps the stick as a second recovery path (the proven one; it costs nothing).
  3. The data-wipe flag in /init (section 3): with #36 now, or at M4.
  4. The ADR: now, or after the three probes.

Sources

SourceWhatRead
steamos-arm.md section 5the Frame’s boot menu, splctl, the first-party recovery matrix (Steam Support pages 1B71-EDF2-EB6D-2BB3 and 65B4-2AA3-5F37-4227), the update discipline2026-11-05
fit-packaging.md section 5vboot selection and attribute bits at firmware-bluey-16702.B (cgptlib.c, 2load_kernel.c, cmd_add.c, cmd_show.c); pmbootstrap’s values; the stock boot_a with priority 02026-10-21 note, re-read 2026-11-05
chromeos-boot-path.md sections 2, 3, 4, 6developer mode, Route A, firmware fastboot’s commands and transports, the recovery image2026-10-21 note, re-read 2026-11-05
ADR 0001, updates 2026-10-21 and 2026-11-02Gate 1 passed; the recovery combo; the relock test’s place2026-11-05
ADR 0002the spare SSD, the three open probes, the case-opening prerequisite2026-11-05
ADR 0003the stock drive is never written; the ChromeOS kernel partition route2026-11-05
ADR 0010M4’s “done when”2026-11-05
runbook, first boot sections 5, 8, 9unlock day and the relock test; the failure playbook (0/0 mark, Firmware log, LED codes); back to stock2026-11-05
unlock-day ticketthe dev-mode menu’s three entries (depthcharge screens.c:1801); the relock pass criteria2026-11-05
project log, 2026-11-02 “Unlock day, relock test passed”; 2026-11-04 “Retro-7/08: stick boot tries”the relock proven without the recovery stick; the count-down proven, 15 tries2026-11-05
status.md rows “Boot from USB” and “Reboot back to stock”stock boots once the stick is out2026-11-05
rootfs/packages.txtno cgpt in the rootfs2026-11-05
scripts/package-in-container.sh (the GPT comment), scripts/attempt.sh (stick_tries)the stick’s -S 0 -T 15 -P 15 and the tries bookkeeping2026-11-05

SteamOS on ARM: what Valve has published for the Steam Frame, and what gbook can take from it

Researched 2026-11-05 (written 10:41 AM EST [15:41 UTC]) on the build host, task #30 (owner’s idea 2026-11-05). First-party sources only: Valve’s GitHub organisation and gitlab.steamos.cloud (through their APIs), Valve’s package servers (directory listings and the pacman databases, plus ten packages downloaded into the scratchpad and read, never installed), the Steam store, Steamworks partner documentation and Steam Support pages, the Steam news API, Valve-authored commits reachable through GitHub’s torvalds/linux commit endpoint, Mesa and FEX upstream, and the kernel tree at gbook’s pin (~/.cache/gbook/linux-a804c57). The Googlebook was not touched. Nothing was installed. Inference marks what no source states. Package versions are as listed on 2026-11-05 and will move. Proprietary packages (licence field Proprietary) are described; only identifying strings (menu entries, messages, a comment fragment, USB IDs) are quoted from them, never code. One configuration file carries a shared CI credential, which is not copied.

Context: gbook builds its rootfs with scripts/build-rootfs.sh (Debian sid, snapshot-pinned, mmdebstrap in a rootless podman container, ADR 0012) and its boot image with scripts/package-image.sh (FIT image on the ChromeOS KERN-A partition, dev-key signed, ADR 0003); the kernel is torvalds a804c57 plus kernel/patches/0001-0011 (ADR 0007) and the kernel/*.config fragments; firmware is staged by scripts/stage-firmware.sh (stock modem_a plus distro packages, hashes in kernel/firmware.sha256). Milestone M4 needs a documented return to stock (ADR 0010).

Answer in short

  1. Valve has not published an ARM SteamOS source tree or image-build pipeline. What is public is the Steam Frame’s package repositories (Arch Linux rebuilt for aarch64 plus Valve’s own deckard-* and steamos-* packages), a repair image and a QDL flash package (about 4 GB each), the mainline sm8650-valve-deckard.dts, Lepton (the Android compatibility tool), an Arch aarch64 “preview” package-source mirror, and developer documentation.
  2. The Frame’s kernel is Linux 7.2 plus 1,178 commits (a 6.18 branch carries 1,215) from a private GitLab tree whose commits resolve through GitHub. None of gbook’s eleven patches is in it; Valve touches the same SoundWire, WSA884x and GMU code with different, mostly HACK:-prefixed changes, and carries no x1e80100, SCMI or panel-edp work.
  3. The OS layout is Steam Deck’s, ported: RAUC A/B raw root slots named by “partsets”, read-only root, /etc overlay on a per-slot var, shared home, casync/desync delta updates, U-Boot with a boot menu, splctl for the boot-firmware slot. The best gbook candidates are the recovery matrix (section 5), a ramoops region, the ath12k IOMMU and IRQ workarounds, and the WirePlumber card rules (section 6).

What is public

WhereWhatPin read 2026-11-05Licence
github.com/ValveSoftware (55 repos listed by the API)Nothing ARM. steamos_kernel (“SteamOS kernel branches”, default branch brewmaster-4.11, last push 2023-05-22), steamos_mesa (last push 2018-10-13), linux (Debian kernel-packaging fork, 2018-01-11), SteamOS (issue tracker), gamescope (fork)repo listNOASSERTION on the kernel repos
gitlab.steamos.cloud (46 public projects listed by /api/v4/projects)holo/holo-core-aarch64-preview, frame-public/lepton, frame-public/frame-developer-tools, frame-public/android-vulkan-headers, two Waydroid forks, SteamHardware/SteamFrame, holo/steamos-manager, holo/fakemachine, devkit/steamos-devkit, the Steam Runtime (steamrt/*) and Proton SDK groupssee rows belowper project
holo/holo-core-aarch64-preview (project 1471)Read-only mirror of the package sources (PKGBUILDs) behind the “preview Arch Linux aarch64 port”, built by Collabora from Arch’s packaging state of 2025-11-18 (97c0a0b4), with aarch64 build fixes and licence fixes (Arch RFC 40). Binary repo at holo-packages.steamos.cloud/holo-core-aarch64-preview/mash-20251118{,.2,.3}/{core,extra}/os/aarch64/, a system.rootfs.zst (384,971,555 bytes, not downloaded), pacman.conf with SigLevel = Optional. Its linux is Arch’s 6.17.8.arch1-1 PKGBUILD, but the config in that tree is the x86 one (# Linux/x86 6.17.8-arch1 Kernel Configuration); its mesa 1:25.2.7-1 enables freedreno in both gallium-drivers and vulkan-drivers. “Technology preview … not intended for use as a permanent operating system” (readme)main = 67f0d559 (2026-06-10)no licence file; the PKGBUILDs carry Arch’s
frame-public/lepton (project 1518)Lepton, “a tool for use with the Steam client which allows games which are exclusive to Android to run on the Linux operating system”: a Waydroid/Anbox/Halium-derived Android container image (image/) and a non-root compat tool (compat_tool/) that mounts the host’s Mesa and Zink into the container. Built in a podman container (image/do_build.sh)main = 5964972a (2026-11-03), tag v3.0.10MIT (tool), GPL-3.0 (image) per LICENSE.md
frame-public/frame-developer-tools (project 1324)perfetto, gpuvis and gfxreconstruct wrappers, gamescope helpers, and pacman/deckard-pacman-src, a 50-line script that maps a package’s repo URL to its PKGBUILD location: holo-packages.steamos.cloud/archlinux-deckard/archlinux//main/{core,extra}/os/aarch64 → gitlab.steamos.cloud/potato/mash/monorepo, .../core-bootstrap/... → potato/mash/bootstrap, archlinux-deckard/bootstraps/ → potato/smash/holo, archlinux-deckard-hotfixes/ → deckard/deckardos/holo. Every one of those GitLab paths answers 404 unauthenticatedmain = 16d1942e (2026-09-02)none stated
SteamHardware/SteamFrame (project 1521)CAD of the core module, head strap and controllers, expansion-port documents, two USB-hub reference designsmaster = bdbb41ba (2026-10-13)CC-BY-NC-SA-4.0 (license.txt)
holo/steamos-manager (project 1288)The D-Bus daemon behind Steam’s system settings; data/devices/steam-frame.toml identifies the Frame by dt.compatible = "qcom,sm8650" and board-info/board_revision, names it device = "steam_frame", variant = "Deckard", and sets GPU devfreq limits (/sys/devices/platform/soc@0/3d00000.gpu/devfreq/3d00000.gpu, manual minimum 500, powersave maximum 629) and per-profile cpufreq caps for policies 0, 2, 5 and 7 (Performance 2,265,600 / 3,148,800 / 2,956,800 / 3,052,800 kHz)main = a3b20137 (2026-10-24)MIT
holo/fakemachine (project 1315)A fork of Collabora’s fakemachine (the VM runner used by debos); last commit 2021-05-31master = 74dd229bApache-2.0
steamdeck-packages.steamos.cloud/archlinux-mirror/The Steam Deck and Steam Machine repos (jupiter-*, holo-*, 217 folders). x86_64 only: every */os/ folder lists x86_64/ alone; holo-main/os/aarch64/, jupiter-main/os/aarch64/ and guessed frame-* names answer 404listing
holo-packages.steamos.cloud/archlinux-deckard/archlinux/main/{core,extra}/os/aarch64/The Frame’s base repos: Arch Linux rebuilt for aarch64 by “GitLab CI Package Builder ci-package-builder-1@steamos.cloud” (core.db). core: 272 packages, extra: 4,114, dated 2025-12-01 to 2026-10-30, each with a .sig. archlinux-deckard/archlinux/ also lists snapshot folders mash-20240428 … mash-20260528 (some .pvt), archives/, builds/, dev/, pipeline/. The server root answers 401listing and core.db.tar.xzArch’s per package
holo-packages.steamos.cloud/archlinux-deckard-hotfixes/Valve’s own Frame packages: 1,978 package files, 497 non-debug package bases, dated 2025-08-06 to 2026-11-04, plus 160 subfolders (goldmaster/, mr-1020/ … mr-1133/, one pacman repo per merge request) and create_new_release_from_main.sh/create_staging.sh. Database deckard-arch-hotfixes.db.tar.xz. Package URLs name private GitLab projects under deckard/deckardos/ (all 404)listing, .db and .filesper package (see below)
steamdeck-images.steamos.cloud/recovery/steamframe-oobe-repair-20260922.5153644-0.3.0.img.bz2 (4,068,109,407 bytes, Last-Modified 2026-09-22 22:57 UTC) and .zip; steamframe-oobe-repair-qdl-20260922.5153644-0.3.0.tar.gz (4,100,462,821 bytes) and .zip; the store’s “download SteamOS for Frame” link (store.steampowered.com/steamos/download/?ver=steamframe) points at steamframe-repair-latest.img.bz2, which redirects to the first file. Not downloaded (see “Not found”)HEADnot stated
torvalds arch/arm64/boot/dts/qcom/sm8650-valve-deckard.dts“Add initial DeviceTree for the Valve “Steam Frame” codename Deckard … based on the Qualcomm Snapdragon 8 Gen 3 SoC (SM8650) … The bootloader starts Linux in EL2, thus a bunch of EL1 & DSP related nodes needs to be removed“ (commit message). Author Neil Armstrong (Linaro), Copyright (c) 2026, Valve Software; binding abbbaa99ac66; merged through qcom-arm64-for-7.4-2 and soc-dt-7.4 (7b9a0e857b42, 2026-10-22). Not in gbook’s pin: compare a804c5742bff...f905cb6e600a says diverged, 135 commits ahead. Follow-ups on linux-arm-msm patchwork: chassis-type (2026-09-24), watchdog (2026-10-02), camera sensors v2 (Vladimir Zapolskiy, 2026-10-02)f905cb6e600a (2026-09-23)BSD-3-Clause
Steamworks partner docs, partner.steamgames.com/doc/steamhardware/steamframe{,/setup,/debugging,/loadgames,/compat,/engines/custom}“The Steam Frame runs on SteamOS, which is a gaming focused Linux distribution based on Arch”; Developer Mode gives ssh, adb and RDP (user steamos, XRDP Xorg session); “By default the filesystem is read-only … sudo steamos-readonly disable”; sudo pacman -S; coredumpctl; runtimes offered by the devkit client: Android (APK through Lepton), “Steam Linux Runtime 3.0 ARM64 (Sniper)” for native arm64 builds, “Steam Play (Proton)” for Windows builds; custom engines should target “Android 10 or Linux Arm64”; System Update Channel Betaread 2026-11-05Valve
Steam Support: SteamOS Recovery and Troubleshooting, SteamOS Installation and Repair, Steam Frame guide, LEDs and button chordsRollback and USB-repair steps for the Frame (section 5); “Purple, solid: Steam Frame is in bootloader”read 2026-11-05Valve
Steam news for app 4165890 (api.steampowered.com/ISteamNews/GetNewsForApp/v2/)“SteamOS 0.3.0 for Steam Frame” (2026-09-17), 0.4 (2026-10-08), “0.5.7 Beta” (2026-11-04). The version line is 0.x (for Steam Frame), separate from the Deck’s 3.x; the store page lists “SteamOS 3”. No kernel versions in any noteread 2026-11-05Valve
Valve’s kernel tree, through GitHublinux-72-deckard 7.2.0+g344f27110f05-1 and linux-618-deckard 6.18.0+gee93c0d9394b-1 name gitlab.steamos.cloud/deckard/deckardos/linux-valve (404). Both SHAs resolve at api.github.com/repos/torvalds/linux/commits/<sha> because GitHub serves objects from the whole fork network; search/commits?q=hash: finds no repository, so there is no named public branch (section 3)344f27110f05 (2026-11-03), ee93c0d9394b (2026-10-23)GPL-2.0-only
FEXFEX-Emu/FEX (MIT, release FEX-2610 2026-10-07; the GitHub login Plagman, git@plagman.net, has 44 commits); fex-emu.com “The scourge of x86 emulation” names “the platform we’re testing on, which is of course the Valve Steam Frame”, the kernel patch bylaws/linux 7ae989a43ae7 (Billy Laws, 2025-08-06, arch/arm64/kernel/unaligned_atomic.c) and the Turnip MR !41323 (Ryan Houdek, merged 2026-05-08)read 2026-11-05MIT / GPL-2.0
Mesa upstream (GitLab API)Adreno 750 (the Frame’s GPU) merge requests that name Valve content: !44840 “tu: Set SP_CHICKEN_BITS_2 bit 30 on A750 for HL: Alyx” (Dhruv Mark Collins, Igalia, merged 2026-10-06), !44348 SUBPASS_FENCE on a7xx+ (Connor Abbott, 2026-09-14), !45615 a7xx gen1/gen2 quirk (Danylo Piliaiev, Igalia, 2026-10-27). The MRs do not state who funds the workread 2026-11-05MIT

1. Repos, packages and trees, with licences and pins

The table above pins each source. Licences of the Frame packages, from the desc entries in deckard-arch-hotfixes.db (read 2026-11-05):

Package (newest in the hotfixes repo)WhatLicence fieldSource URL in the package
linux-72-deckard 7.2.0+g344f27110f05-1 (built 2026-11-04 18:15 UTC); linux-618-deckard 6.18.0+gee93c0d9394b-1the kernel, Image + modules + every qcom/*.dtbGPL-2.0-onlydeckard/deckardos/linux-valve (404)
linux-firmware-deckard 20261023.1+git594f649d-1Valve’s firmware set (section 4)customdeckard/deckardos/linux-firmware (404)
linux-firmware-qcom 20260519-1 (core)Arch’s split of kernel.org linux-firmwareLicenseRef-WHENCEkernel.org
deckard-boot-firmware 20261021.1.uboot20261019.1-3, deckard-boot-luns, deckard-boot-images, deckard-hw-support 20261103.1-1U-Boot build, Qualcomm boot firmware, LUN GPT images, boot splash, hardware-support scripts and initramfs hooksProprietary (deckard-boot-images also OFL-1.1)deckard/deckardos/u-boot, .../deckard-hw-support (404)
deckard-uboot-splctl 20260128.3-6“Deckard second-stage bootloader controller” (/usr/bin/splctl)GPL-2.0+deckard/deckardos/deckard-uboot-splctl (404)
steamos-efi 20260122.2-3“Stage 1 bootloader for SteamOS 3+”, here an aarch64 build (/usr/lib/steamos-efi/aarch64-efi/steamcl.efi)GPL2deckard/deckardos/steamos-efi (404)
steamos-customizations-deckard 20261002.1-4“SteamOS customizations (Deckard fork)”: RAUC and atomupd configuration, partsets, mounts, reset tools, udev rules (section 2, 5)LGPLv2+repo.steampowered.com
deckard-atomupd-config 20250820.1-1, steamos-atomupd-client 0.20260331.1-4, atomupd-daemon 0.20260810.0-4, steamos-reset 20241008.1-6the update and reset stackMIT, LGPL2.1, MIT, GPLholo/* (404)
deckard-audio-config 20261027.2-1PipeWire, WirePlumber, UCM2 and Valve’s LV2/LADSPA plugins (section 6)MITdeckard/deckardos/deckard-audio-config (404)
deckard-mesa-linux-aarch64, -x86_64, -android-aarch64 27.0.0_devel+gitb826d29e-1Valve’s Mesa builds (section 6); b826d29e is not in mesa/mesa (404 Commit Not Found), so a Valve branch (inference)custommesa3d.org
fex-wtf 2606-5“Tool for seeing FEX emulator stats”MITgithub.com/Sonicadvance1/what-the-FEX
deckard-steam-rel, deckard-steamvr-rel, deckard-steamvr-session, lepton-compatthe Steam client, SteamVR, their session, Leptonnot read (Steam client)

The hotfixes repo is also where Arch packages get Valve rebuilds with sub-release numbers (bluez 5.79-1.6, chromium 129.0.6668.58-1.6, busybox 1.37.0-1.5); the mr-NNNN/ folders hold one package each (mr-1133/ has deckard-steamvr-session-1.7.0-1), so a merge request becomes a pacman repo before it reaches goldmaster/.

Observed licence discrepancy. deckard-hw-support is marked Proprietary, yet its initramfs hooks (usr/lib/initcpio/hooks/{partsets,blockdev_ro,etc_overlay,factory_reset}) carry SPDX-License-Identifier: GPL-2.0-only headers, and deckard-boot-firmware (also Proprietary) bundles a U-Boot binary (U-Boot 2025.07-rc3-00656-gfbfd7e7ef9a0 for deckard board, GPL-2.0-or-later upstream) with Qualcomm blobs. This note treats both packages as proprietary and quotes nothing from them.

What gbook could adopt (proposals)

  • Proposal: add one sources.md row for the four new source kinds this note used: the gitlab.steamos.cloud API, the holo-packages listings and .db.tar.xz, the ISteamNews API, and GitHub’s fork-network SHA resolution (this note did not edit sources.md).
  • Proposal: none of the Frame’s deckard-* source trees is public, so gbook cannot depend on them; the Arch-rebuilt repos are the only reproducible part, and even they float (mash-* snapshots are listed but their contents were not compared).

2. Rootfs and image build

How the Frame image is assembled is not public. The observable pieces:

  • Packages. Arch Linux rebuilt for aarch64 at a “mash” snapshot (the preview readme credits Collabora for “the build infrastructure, developed the tooling required to build and publish packages”), plus Valve’s packages built by GitLab CI (builddir = /builds/deckard/deckardos/holo/deckard/linux-72-deckard in the kernel’s .BUILDINFO; makepkg 7.1.0). The pinned state is a snapshot name (mash-20251118), not a dated archive like snapshot.debian.org.
  • Image. holo/fakemachine (debos’ VM runner) is the only build-tool clue; that Valve builds images with debos is inference. The RAUC system.conf (LGPL, steamos-customizations-deckard) says: compatible=steamos-aarch64, bootloader=custom, max-bundle-download-size=268435456, activate-installed=false, two slots rootfs.0 (bootname A, /dev/disk/by-partsets/A/rootfs, type=raw) and rootfs.1 (B), a [casync] section with use-desync=true and --seed /var/lib/steamos-atomupd/rootfs.caibx --in-place --error-retry 35, and custom handlers (bootloader-custom-backend.sh, which creates the by-partsets links on old layouts and then calls /usr/bin/splctl). So a root slot is one raw image, delivered as a casync index and chunks, written in place.
  • Immutability. The root is read-only (partner docs: steamos-readonly disable); /etc is an overlay whose upper directory lives in the slot’s var partition (var-A/var-B, mounted by the etc_overlay initramfs hook); /home is shared and holds “offload” bind mounts for /root, /var/log, /var/lib/flatpak, /var/cache/pacman and others (*.mount units in the customizations package); usr/lib/repart.d/90-home.conf (Type=home) lets systemd-repart grow it. atomic-update-keep.conf lists the /etc paths that survive an update.
  • Update channels. etc/steamos-atomupd/client.conf: ImagesUrl = https://steamdeck-images.steamos.cloud/, MetaUrl = https://steamdeck-atomupd.steamos.cloud/meta, Variants = vr, Branches = stable;rc;beta;bc;preview;pc;main. The same file carries a shared Username/Password for the image server; not reproduced here.
  • Signing. RAUC bundles verify against /etc/rauc/trusted_keys/ (plus a development_keys/ directory); the older deckard-atomupd-config ships one self-signed certificate, subject and issuer O=Valve, CN=Valve, notBefore 2023-05-24 22:10:05 GMT, notAfter 2027-03-24 22:10:05 GMT (openssl x509 -noout -subject -issuer -dates on the extracted file, 2026-11-05; the certificate itself is not copied). Packages carry .sig files; the preview’s pacman.conf sets SigLevel = Optional.
  • Initramfs. mkinitcpio (dracut 111-1 is also in extra). etc/mkinitcpio.conf.d/steamos.conf adds the hooks blockdev_ro partsets initrd_shell factory_reset etc_overlay deckard_fw, the binaries splctl fw_printenv fw_setenv bash strace zstd lsblk, and regulatory.db. The post hook usr/lib/initcpio/post/deckard-uimage (LGPL) wraps the result with mkimage -A arm64 -O linux -T ramdisk -C none into /boot/initrd.uImage.
  • Compared with gbook. gbook pins one Debian snapshot and keeps a lock list and manifest (build-rootfs.sh); the Frame pins a package snapshot by name and ships raw slot images with delta updates. gbook has one root partition on the stick; the Frame has two root slots, two var slots, one home. gbook signs a FIT for depthcharge with a dev key; the Frame signs RAUC bundles with Valve’s CA, and whether anything checks a signature on the raw Image and the mkimage -T ramdisk initrd before the kernel runs is unknown (verify=n in uboot.env only disables U-Boot’s image data-checksum check; see section 5).

What gbook could adopt (proposals)

  • Proposal (M4): name partitions the partsets way, rootfs-A/rootfs-B, var-A/var-B, home, and resolve self/other from one kernel-command-line token (the Frame uses rauc.slot=A|B; the partsets hook parses it). gbook’s /init already finds ROOT through depthcharge’s kern_guid=%U; the same token could pick the matching root slot. The hook is GPL-2.0-only inside a proprietary package, so re-implement, do not copy.
  • Proposal (M4): read-only root with an /etc overlay in a writable slot partition. This is what makes “Previous slot” a real rollback: user state lives in var-X and home, never in the root image.
  • Proposal (M3, cheap): an initramfs “shell with auto-reboot”: the Frame’s initrd_shell prints “Headset will auto-reboot in 20…” and stops the countdown on a keypress, a serial character or an adb connection, with gotoinitrd=yes or break on the command line forcing the shell. gbook’s capture-and-reboot /init could add the countdown and the break escape without changing its sweep.
  • Proposal (M3): lock the stock partitions read-only at the block layer from /init (blockdev --setro on every partition of the stock disk), which enforces CLAUDE.md’s “never write AP or EC firmware” rule mechanically. The idea comes from the proprietary blockdev_ro hook; a one-line re-implementation needs no code from it.

3. Kernel

Tree and version. linux-72-deckard is 7.2.0-g344f27110f05: compare v7.2...344f27110f05 gives ahead 1178, behind 0, merge base 8d3ae59288f1 “Linux 7.2” (2026-08-16). linux-618-deckard is v6.18 plus 1,215. Committer dates of the 1,178: 987 in 2026-08 (a rebase onto 7.2 on 2026-08-24, inference), 58 in September, 132 in October, 1 in November. Author domains: valvesoftware.com 501, linaro.org 359, arcturus.industries 85, gmail.com 78, marek.ca 57, tgoyen.com 37, quicinc.com 16, oss.qualcomm.com 16, igalia.com 10, intel.com 5, collabora.com 3. Subject prefixes: gabe_defconfig 68, Revert 62, fixup! 60, HACK 44, WIP 9, FROMLIST 9, FEX 2, TEMP 1. One CI commit says “Don’t run pipelines in linux-valve-publicly-released-branches” (d643bc7139a5), so a public branch is planned or exists somewhere this search did not find (inference). The full subject list is in the scratchpad (kdts/valve/all-1178.tsv), not in git.

Configuration (from linux-72-deckard-headers’ build/.config, # Linux/arm64 7.2.0 Kernel Configuration, 2,584 =y and 61 =m; a single gabe_defconfig maintained by 68 commits):

AreaFrame (gabe_defconfig)gbook (~/.cache/gbook/build-a804c57/.config)
Pages, VA, HZ, preempt4K, VA_BITS=48, HZ=1000, PREEMPT=y, SCHED_CLASS_EXT=y, KVM=yVA_BITS=52, HZ=250, PREEMPT=y
cpufreq / cpuidleARM_QCOM_CPUFREQ_HW=y, default governor performance, ARM_PSCI_CPUIDLE=y with the teo governor; # CONFIG_ARM_SCMI_PROTOCOL is not set (SM8650 has no SCMI cpufreq)ARM_SCMI_PROTOCOL=y, ARM_SCMI_CPUFREQ=y, ARM_QCOM_CPUFREQ_HW=y, schedutil, ARM_PSCI_CPUIDLE=y but no PSCI cpuidle on mica (ADR 0009)
GPU, displayDRM_MSM=y with DPU, DP, DSI; DRM_PANEL_DECKARD=y (Valve’s dual-DSI panel driver), # CONFIG_DRM_PANEL_EDP is not setDRM_MSM=y, panel-edp with patch 0001
AudioSOUNDWIRE_QCOM=y, SND_SOC_WSA883X=y, WSA884X=y, MAX98390=y, the four LPASS macros, the whole QDSP6 stack built in, QCOM_Q6V5_PAS=y, QCOM_APR=y; # CONFIG_SND_SOC_SM8250 is not setSOUNDWIRE_QCOM=y, WSA884X=y
IOMMUIOMMU_DEFAULT_DMA_STRICT=y (worked around in userspace, section 6)IOMMU_DEFAULT_DMA_STRICT=y
Wi-Fi, BTATH12K=m (one of the few modules), BT_QCA=y, BT_HCIUART_QCA=yATH12K=y, BT_QCA=y
pstorePSTORE_RAM=y, PSTORE_CONSOLE=y, PSTORE_PMSG=y, ramoops node in the DTPSTORE_RAM=y, PSTORE_CONSOLE=y, no ramoops node in dts/x1e80100-google-mica.dts
Modules, signingMODULE_SIG=y, # MODULE_SIG_FORCE is not set, # KEXEC is not set
AndroidANDROID_BINDER_IPC=y, ANDROID_BINDER_DEVICES="anbox-binder,anbox-hwbinder,anbox-vndbinder" (for Lepton)
Stray x1e80100 bitsCLK_X1E80100_GCC=m, CLK_X1P42100_CAMCC=m, CLK_X1P42100_VIDEOCC=m (modules, so not used on the Frame; inference)

Device tree. The production DTB in the kernel package is sm8650-mp.dtb (model = "SM8650 MP rev1 4slam 2et", compatible = "qcom,sm8650-dv1", "qcom,sm8650", 9,488 lines decompiled), beside sm8650-dv1/dv2/ev1/ev2/ev3*, sm8650-mp-power, sm8650-mp-dbg, sm8650-el2-hdk, sm8650-ayaneo-* and the HDK boards. U-Boot picks /boot/dtbqcom/sm8650-<variant>.dtb (section 5). Compared with the mainline sm8650-valve-deckard.dts:

  • Both run at EL2 without the DSPs: the mainline file /delete-node/s ipa, gpu_zap_shader, the five LPASS macros, lpass_tlmm, remoteproc_adsp/cdsp/mpss, reserved_memory, the three smp2p nodes and swr0-3, then redefines reserved-memory (aop, ramoops at 0x84a00000, cmd-db, smem, cpucp-fw, video, tz-stat, four LPASS LPM pools, a 48 MiB framebuffer at 0xaf400000). That is the full-DTS version of what gbook’s dts/mica-el2.fixups does on top of an overlay (overlays cannot delete nodes), and it validates the approach.
  • Mainline enables gpu (no zap node), pcie0 with the WCN7850 as pci17cb,1107 and its PMU regulators, uart14 (qcom,wcn7850-bt), uart15 console, ufs_mem_hc, sdhc_2, usb_1 as a USB 2.0 peripheral (dr_mode = "peripheral", maximum-speed = "high-speed", USB 3 PHY removed), four I2C buses with two maxim,max98390 amplifiers (Speaker Left/Right), gpio-keys, PMIC temperature alarms; dispcc is disabled and the simple-framebuffer under chosen is 4320x2160 ARGB8888 “Bootloader will enable the node”.
  • The production DTB adds what is still out of tree: the deckard-panel (dual-panel, low-fps-support, eight supplies), the qcom,lpass-sndcard card (“SM8250 LPASS” model) driving lpaif@7d00000 with the two MAX98390s on the “SPKR Playback” link and the VA macro DMIC on “VA Capture” (no ADSP: the DTB has no remoteproc node at all), valve,fpga, two arcimx616 cameras plus eyes, allegro,als31300, PSCI idle-states for all three clusters (arm,psci-suspend-param = <0x40000004>, “silver/gold/gold-plus-rail-power-collapse”), qcom,sm8650-cpufreq-epss, qcom,adreno-43051401 with its OPP table, and qcom,pm8550b-charger.

Patch areas, against gbook’s kernel/patches/0001-0011 (line 1 of each gbook patch read 2026-11-05; Valve-tree commit messages and touched files read through the GitHub commit endpoint):

gbook patchValve’s tree carriesOverlap
0001 drm/panel-edp LGD 0x0804 (LOCAL)no panel-edp, eDP or x1e80100 subject among the 1,178; the Frame’s panel is DSI with its own drivernone
0002-0007 “drm/msm: Attach a driver to GMU” (Akhil P Oommen, FROMLIST) plus the Omarchy fix-upnot the series. On GMU: c76da00c0873 “a6xx_gmu: fixup bw voting to use AB voting” (+4 lines), d36695feb7f3 “a6xx_gmu_wait_for_idle uses sleeps instead of spinning hard”, f43185c19923 “vote minimal opp on shutdown” (reverted in 272f8245e6f5), 768a6e680987 “HACK: use correct register address for GMU AO cnter on a750” (reverted and reapplied twice). Two other Akhil commits are present: 00b93c80cab0 “drm/msm: Skip devfreq IDLE when possible”, e01f24c3b2f5 “drm/msm/adreno: Relax devfreq tunings”. For the no-TZ GPU: 6eb3e65348d9 “HACK: drm/msm: adreno: add a7xx zap init”, afd29180cc00 “fix for a7xx without zap/tz” (one line in sm8650.dtsi), c4eca5004b53 “enable back zap shader loading via SCM”same area (GMU, EL2 GPU bring-up), different changes; nothing to copy for the stateless device-link problem
0008 gcc-x1e80100 PCIe GDSC retention (FROMLIST, Krishna Chaitanya Chundru), 0009 PCIe PHY GDSC retention (LOCAL)no x1e80100 or PCIe-GDSC change. GDSC work is display and camera: 0ae07b602274 “clk: qcom: tie mdss gdsc to branch clocks” (gdsc.c, clk-branch.c, dispcc-sm8550.c), a6ddcda546d1 “dispcc-sm8550: … mdss_gdsc as ALWAYS_ON”, fb9ea49232da “HACK: media: qcom: camss: add ALWAYS_ON flag to titan top and ife gdscs”. PCIe: f0aaa175a5f7 “pcie: qcom: add opt-in link_reset sysfs attr for PERST# pulse” (Arcturus), and the defconfig commits ca6987de7ff9 “enable CONFIG_PCIEPORTBUS to allow the PCIe bus to go in D3Cold” and 9a8c8ea46950 “enable CONFIG_PCIEASPM to allow the PCIe endpoints to enter Lower energy levels” (both 2026-10-20/21)none on the retention bit; Valve’s endpoint low-power path is ASPM + port-bus D3cold rather than GDSC retention (inference from the two defconfig subjects)
0010 soundwire: qcom re-arm enumerator after clock stop (LOCAL)603ad2317660 “WIP: soundwire: qcom: add some post bank switch delay” (cherry-picked from Srinivas Kandagatla: “on wsa883x we see Port Collision during bank switch”), 8f6efbaf3646 “HACK: soundwire: qcom: disable runtime pm” (“there’s a bug somewhere that needs to be debugged”, +17/-11 in qcom.c), c5e49e3d99ac “allow more rates and formats” (PDM-only bus)same driver, same family of bug; Valve sidesteps clock-stop by disabling runtime PM (inference: no runtime PM means no clock-stop entry). No fix to copy, and these commits come from the EL2/HDK/Aya experiments (65834eec3fcb “aya el2 audio”), not from the production Frame, which has no SoundWire codec
0011 wsa884x clock-stop mode 1 (LOCAL)9066a20246f7 “ASoC: codecs: wsa884x: remove mute_unmute_on_trigger” (with a Fixes: tag: “trigger is atomic … soundwire register writes are not safe to run in an atomic context”), ad9e0544ab85 the same for wsa883x, 5f9768d70cd3 “HACK: ASoC: codec: wsa884x: workaround for perma muted state” (“If the codec is unmuted and muted again immediately, no more audio will play ever again”), 24098704fd61/7f4b1c3f9436 “use older driver here” and its revertsame codec driver, mute and trigger path; no clock-stop-mode change

Other Valve-tree work worth knowing (counts by subject): ath12k 65 (board-id from DT for a new diplexer, FTM/test mode, txpower forcing, “work around dp_tx: failed to find the peer”, forced RDDM and coredump, “fix survey indexing across bands” from Collabora); display 109 (DSI CTRL v2.8.0, bonded-DSI DSC, a DPU “CAC” pipeline from Linaro, the acronym unexpanded in the subjects, drm/msm/dpu1: fix early delivery of page flip events); camera 71; charger and fuel gauge 36 (qcom_pm8550b_charger, max1720x); IOMMU 8 (iommu: arm-smmu-qcom: add display bypass if missing, defer attach of nodes with iommu-defer-attach property, an LPAC SID split); cpufreq 7ab3f57ab244 “set lowest freq and disable OPP on cpu offline” and a1190a326d84 “set suspend frequency”; PSCI 1b265e654037 “HACK: workaround for TZ PSCI CPU_OFF/CPU_ON bug” (7 lines in psci.c, 4 in irq-gic-v3.c); EL2 plumbing e9f42a84bfc1 “HACK: power: reset: qcom-pon: add support for EL2 PS_HOLD control setup” and 55476188d208 “enable pmk8350-el2-pon to properly handle reboot and shutdown”; a9505502e5e8 “FEX: arch: arm64: Implement unaligned atomic emulation/backpatching” (2,056 lines in arch/arm64/kernel/unaligned_atomic.c, a new prctl); ce749094e4ee “hexagon-baremetal: adsp, cdsp, cvp support” (inference from the subject and the DTB’s lack of remoteproc nodes: Valve runs the DSP cores without the Qualcomm remoteproc stack; out of gbook’s scope).

What gbook could adopt (proposals)

  • Proposal (DTS, needs dt-validate and ADR 0004 tracing): a ramoops reserved-memory node for mica. gbook already builds PSTORE_RAM=y and PSTORE_CONSOLE=y, so a region makes the last console survive a hang. The Frame uses 0x84a00000 (1 MiB mainline, 2.25 MiB in the production DTB); mica needs its own address chosen against the stock reserved-memory map, and the choice is “unverified” in dts/README.md terms until a stock pin is found.
  • Proposal (kernel/mica-pcie.config, measure first): try PCIEASPM and PCIEPORTBUS before extending the GDSC retention patches: the Frame reaches endpoint low power that way on a sibling SoC. A boot-rate claim would need attempt.sh tally for both configurations (CLAUDE.md).
  • Contrast, not adoption: the Frame keeps PSCI cpuidle on, with a seven-line TZ CPU_OFF/CPU_ON hack and the teo governor. gbook’s ADR 0009 removes PSCI cpuidle because the per-CPU domains lose cluster_c4; a different SoC and a different symptom, so ADR 0009 stands; the hack’s diff is the thing to read if mica ever shows CPU_OFF failures.
  • Contrast: Valve marks patch state in the subject (HACK:, WIP:, FROMLIST:, fixup!) and carries 62 reverts; gbook’s Status: line and renumbering rule (ADR 0007, check 34) are stricter and should stay.

4. Firmware handling

Arch’s split linux-firmware-* packages in core and one Valve package supply /usr/lib/firmware on the Frame (file lists from deckard-arch-hotfixes.files and core.files); the two that matter here:

  • linux-firmware-qcom 20260519-1 (Arch’s split package, LicenseRef-WHENCE, 163.2 MiB) in core, the same source gbook’s stage-firmware.sh draws the GPU and audio-topology files from (linux-firmware-qcom, “20260916-1” on the build host).
  • linux-firmware-deckard 20261023.1+git594f649d-1 (“custom”, 24.4 MB, 282 entries) in the hotfixes repo: the Adreno 750 set at the root of the firmware directory (a750_sqe.fw, a750_aqe.fw, a750_zap.mbn, gen70900_sqe.fw, gen70900_aqe.fw, gen70900_zap.mbn, gmu_gen70900.bin; gbook’s X1-85 loads gen70500_sqe.fw and gen70500_gmu.bin under qcom/), ath12k/WCN7850/hw2.0/{amss,board,board_77,m3,regdb}.bin, the QCA Bluetooth set (qca/hmtbtfw20.tlv, hmtnv20.bin, hpbtfw21.tlv, hpnv21.bin, ht*), qcom/sm8650/{adsp,cdsp}.mbn, {adsp,cdsp}_dtb.mbn, adspr/adsps/adspua/cdspr/battmgr.jsn, ipa_fws.mbn, qupv3fw.elf, SM8650-MTP-tplg.bin, SM8650-QRD-tplg.bin, qcom/vpu/vpu33_4v.mbn, CAMERA_ICP.mbn, cvpss.mbn, the MAX98390 dsm_param*.bin files, i2c_fw.bin, spi_fw.bin, default.vrfont/default.vrterminal, and the kernel.org rtl_bt, rtl_nic and renesas_usb_fw.mem sets. So it is a linux-firmware checkout with Valve’s additions on top (inference from the mix); which files are Qualcomm-licensed is not stated beyond custom.
  • The initramfs carries only qcom/sm8650/qupv3fw.elf, dsm_param.bin and the two .vr* files (deckard_fw hook) plus regulatory.db; everything else loads from the root.
  • Boot firmware is a separate, proprietary package (section 5): bootfw.tar.xz holds xbl.fw, xblconfig.fw, tz.fw, hyp.fw, aop.fw, aopconfig.fw, cpucp.fw, cpucpconfig.fw, devcfg.fw, shrm.fw, qupfw.fw, uefi.fw, and zero-length keymaster.fw and uefisecapp.fw (sizes from bsdtar -tvf).

Compared with gbook. Same layout (kernel.org paths), same idea of a hash-checked set (pacman’s desc holds the package sha256; gbook keeps kernel/firmware.sha256), and the same two origins (a distro package plus device-specific blobs). The difference is provenance: gbook’s device blobs come from the stock modem_a partition the owner dumped (ADR 0005), Valve’s from Qualcomm under contract.

What gbook could adopt (proposals)

  • Proposal: nothing structural; stage-firmware.sh already matches. One check worth adding to kernel/firmware.sha256 review: the Frame confirms that a7xx GPU firmware is versioned by chip (gen70900 for the Adreno 750, gen70500 for the X1-85), so a firmware bump that renames the file is a kernel-catalog change, not a packaging one (a6xx_catalog.c:1573-1577 at the pin, per stage-firmware.sh’s header).

5. Boot flow, rootfs discovery, rollback and return to stock

Partition map of the two boot LUNs (GPT with 4 KiB sectors, parsed from the 256 MiB images in deckard-boot-luns; names and sizes only, the content is proprietary):

  • LUN 1 (28 entries): xbl_a/b (3,584 KiB each; xbl_a has attribute bit 2 set and type dea0ba2c-cbdd-4805-b4f9-f428251c3e98), xblconfig_a/b (240 KiB), shrm_a/b (256), aop_a/b (512), aopconfig_a/b (512), cpucp_a/b (1,024), cpucpconfig_a/b (64), tz_a/b (4,096), devcfg_a/b (128), hyp_a/b (8,192), uefi_a/b (32,768), uefisecapp_a/b (2,048), keymaster_a/b (512), qupfw_a/b (80). Every stage of Qualcomm’s boot firmware is A/B.
  • LUN 2 (14 entries): bootenv, bootenvb (128 KiB), uboot_a (32 MiB), ubootenv_a (128 KiB), ubootfw_a (4 MiB), the same three for _b, uefivarstore, cdt, ddr, xblramdump, toolsfv, adpd.
  • The OS LUN (root slots, var, home, esp) is not in these images; the blockdev_ro hook locks /dev/sdb, sdc and sdd, so the OS lives on /dev/sda (inference). Sizes are unknown without the repair image.

Boot chain, as far as the public artefacts show it. Qualcomm PBL finds the XBL partition by a “magic parttype uuid” (the kernelsetup.sh comment in the proprietary package says so; not quoted further) and runs the A/B boot firmware; Valve’s U-Boot (U-Boot 2025.07-rc3-00656-gfbfd7e7ef9a0 for deckard board, from uboot.img strings) then:

  • shows the “Deckard Boot Menu” (ANSI or graphical) with entries Current <slot>, Previous <slot>, Boot from USB / USB: <disk> [<n> GB], Erase User Data, Repair Steam Installation, ADB mode, Battery Ship Mode, each a setenv slottarget ...; bootcount, bootlimit, altbootcmd and upgrade_available are present (U-Boot’s standard boot-count fallback; that Valve uses it is inference);
  • loads /boot/Image, /boot/initrd.uImage and /boot/dtbqcom/sm8650-<variant>.dtb (/boot/maindtb.dtb as an alternative) from the chosen root partition; the strings show a deckard_eeprom_init symbol beside the sm8650-%s.dtb template, so the variant comes from the EEPROM (inference);
  • builds the command line from a template ending init=/sbin/init rauc.slot=<A|B> noresume rootwait earlycon, with root=PARTLABEL=<rootfs-A|rootfs-B> (“Trying old rootfs partition names…” covers an older scheme), and the stored default_bootargs=loglevel=3 rd.log=console rd.log=file id_aa64mmfr0.ecv=1; the environment also holds bootdelay=2 and verify=n (the whole uboot.env has three variables);
  • can instead chain-load an EFI binary from /efi/steamos/ (deckard_bootefi); the aarch64 steamcl.efi (“Stage 1 bootloader for SteamOS 3+”, the Deck’s chainloader) is packaged. Which path a shipping Frame takes is not established; fw_env.config in deckard-hw-support says the Deck-style partsets-from-bootloader scheme is the goal “until that is working”, so the direct U-Boot path is the current one (inference).
  • Linux starts at EL2 (mainline DTS commit message). hyp_a/b partitions exist and hyp.fw is 1.6 MB, so what runs there, if anything, is unknown.

In the initramfs: partsets parses rauc.slot= (and steamos.efi=) and builds /dev/disk/by-partsets/{A,B,self,other,shared}; etc_overlay mounts by-partsets/self/var and the /etc overlay; factory_reset mounts esp and, if /esp/efi/steamos/factory-reset/*.cfg exists, re-creates var-A, var-B and home (mke2fs -t ext4, home with casefold), zeroes ubootenv_a/b and reboots; initrd_shell starts adbd over USB FFS inside the initramfs (vendor 0x28de, product 0x2460, “Steam Frame (initrd)”) with the auto-reboot countdown.

Updates to the boot firmware (deckard-boot-firmware, proprietary; behaviour only): for the inactive OS slot, write uboot.img, ubootfw.img and uboot.env to uboot_<x>, ubootfw_<x>, ubootenv_<x> when their hash differs from the partition’s; for the boot firmware, verify every *.fw against the active firmware slot, and if any differs write all mismatching files to the other slot, read each back and compare, and only then splctl set-bootfw-slot. Old layouts (EV2/EV3) get partition fix-ups. splctl (GPL-2.0+, the only public piece) also offers force-slot A|B, force-usb and set-state A|B good.

Rollback and return to stock, the first-party recipe (Steam Support pages, read 2026-11-05):

NeedFramegbook today
Boot the previous OS, keep datahold AUX, press power, keep holding until the “SteamOS” menu, choose “Previous B”KERN-A/KERN-B priorities exist in the ChromeOS GPT, gbook writes only KERN-A (ADR 0003)
Reset OS, keep games“Repair Steam Installation” menu entry, or Settings > factory reset through steamos-reset-tool (platform.toml)none
Wipe user data without booting“Erase User Data” menu entry (/efi/steamos/factory-reset)none
Reinstall from USBboot menu (AUX + power), navigate with Volume Up/Down, select with AUX, boot the USB repair stick (4 GB image written with Rufus, Etcher or bzcat piped into dd), then “Wipe Device & Install SteamOS” from a desktop sessionthe stock recovery image on a USB stick (ADR 0001, Gate 1)
Unbrick the boot firmwaresteamframe-oobe-repair-qdl-...tar.gz (4.1 GB): a QDL package, that is Qualcomm EDL flashing with the open qdl tool (inference from the name; not downloaded)ChromeOS recovery mode; no EDL path documented
Know the state“Purple, solid: Steam Frame is in bootloader” (LED reference)the AP console over SuzyQ

What gbook could adopt (proposals)

  • Proposal (M4, the headline item): write mica’s recovery matrix in the same five rows, with the ChromeOS mechanisms that already exist: KERN-A/KERN-B cgpt priority, tries and successful bits are the analogue of Valve’s slot state (splctl set-state <slot> good after a verified boot, Previous = boot the other kernel partition), the stock recovery stick is the analogue of the repair image, and the “Erase User Data” step is a mke2fs of the data partition from /init. The runbook already has the stick; what is missing is the documented “mark good” and “boot the other slot” commands, and a statement that there is no EDL path on mica.
  • Proposal (M3/M4): carry the U-Boot update discipline into attempt.sh: write to the inactive slot, read back and hash-compare before switching. attempt.sh verify already hashes the stick; the “switch only after read-back” ordering is the part to keep when KERN-B comes.
  • Proposal (M3): an initramfs adbd/sshd over the USB gadget is Valve’s answer to “the device is off Wi-Fi”; gbook’s rootfs-access.md already studied usb-drd gadget mode (sources.md row “Reaching the booted rootfs”); the Frame shows it working from the initramfs, before the root is found.

6. Userspace: GPU, x86 emulation, audio

GPU. The Arch rebuilds provide mesa 1:26.1.1-2 and vulkan-freedreno 1:26.1.1-2 (extra, 2026-10-30); the Frame runs Valve’s own builds instead: deckard-mesa-linux-aarch64 27.0.0_devel+gitb826d29e-1 (“native aarch64 build”, 2026-11-04) ships libvulkan_freedreno.so (Turnip), zink_dri.so, vkms_dri.so, libgallium-27.0.0-devel.so, libgbm, kmsro-named *_dri.so entries and the freedreno register XML; the 48-entry file list has no msm_dri.so or freedreno_dri.so, so the build carries no freedreno GL driver and OpenGL goes through Zink on Turnip (inference from the list). deckard-mesa-linux-x86_64 is “FEX x86_64 build”: a complete x86_64 and i386 Mesa under /usr/share/guestos/fex-mesa/usr/{lib,lib32} (libvulkan_freedreno.so, zink_dri.so, graphics_provider.json); that x86 games under FEX therefore use an x86 Turnip talking to the aarch64 kernel directly rather than a thunk to the host driver is inference from the file list (the FEX blog describes thunks as an option, and FEX-2610’s notes mention “Vulkan thunks”). deckard-mesa-android-aarch64 is Mesa for Lepton’s Android (vendor/lib64/hw/vulkan.freedreno.so, libEGL_mesa.so). deckard-gamescope-layer-x86_64 is a Vulkan layer for the x86 side (gamescope 3.16.24-1 in extra). Upstream, the Adreno 750 work that names Valve content is the Igalia MR for Half-Life: Alyx (SP_CHICKEN_BITS_2 bit 30, “a performance uplift of 2.4%”, applied only to that title) and the FEX-driven Turnip override that makes uncached buffers cached-coherent “when this option is set” (!41323). None of the three MRs states Valve funding; the blog does for the FEX team.

x86 games. FEX is not a system package: the LGPL FEXBash wrapper in steamos-customizations-deckard runs $STEAMPATH/steam/steamapps/common/FEX-Emu/fex-compat-tool inside SteamLinuxRuntime_4, so FEX arrives as a Steam depot (“compat tool”) like Proton. fex-wtf (“what-the-FEX”) is the stats viewer. Windows games go through Proton (partner docs), native arm64 Linux builds through “Steam Linux Runtime 3.0 ARM64 (Sniper)”, Android builds through Lepton. The kernel side is the unaligned-atomic emulation commit (section 3), which the FEX blog credits for a 5x latency improvement on unaligned atomics on the Frame’s Cortex-X4 versus the X925.

Audio. PipeWire 1:1.6.6-1, WirePlumber 0.5.14-1, alsa-ucm-conf 1.2.15.3-1 (extra). deckard-audio-config (MIT) adds: UCM2 card files conf.d/SM8250_LPASS/{SM8250_LPASS,HiFi}.conf and conf.d/LPASS-SNDCARD/... with codecs/sm8250-lpass/{SpkrEnableSeq,VAEnableSeq}.conf (the kernel card is qcom,lpass-sndcard, model “SM8250 LPASS”); WirePlumber rules that, for alsa_card.platform-*, set api.acp.disable-pro-audio = true and device.routes.default-sink-volume = 0.6, and for alsa_output.platform-* force S16LE, 48,000 Hz, two channels FL,FR, api.alsa.period-size = 256, api.alsa.headroom = 1024, node.force-quantum = 480, priority 800 (50-alsa-config.conf); 51-alsa-disable.conf disables three nodes by name (alsa_output.platform-6b80000.lpass.playback.2.0, .3.0, alsa_input.platform-6b80000.lpass.capture.4.0); spatial audio with a virtual 7.1 option (60-spatial-audio.conf, 70-spatial-node-config.conf, spatial-node-config.lua); microphone processing with CPU and DSP echo-cancellation filter chains and a microphone-tracker.lua; Valve’s plugins valve_boulder_speakers.lv2, valve_denver_speakers.lv2, valve_deckard_mic.lv2 (built with faust2lv2, the Makefile says) and LADSPA vraudiocompositor.so; a deckard-audio-setup.service and PipeWire/WirePlumber user drop-ins for SteamVR. The 0.5.6 and 0.5.7 notes add echo cancellation for external microphones and a fix for “audio would stop working with USB or Bluetooth speakers if an application tried to access the microphone”.

Compared with gbook. mica’s speakers are WSA884x over SoundWire behind the ADSP (q6apm) with a topology file, which is the X1E80100 path; the Frame drives the LPASS from the application processor with I2C/I2S MAX98390 amplifiers and no DSP, so its UCM and kernel audio bits do not transfer. Its WirePlumber layer does.

What gbook could adopt (proposals)

  • Proposal (M3 audio, cheap): a wireplumber.conf.d drop-in for mica’s card that fixes format, rate, period and quantum and disables the PCMs that are not wired (the Frame disables three by node name), instead of hiding unusable DAI links in the DTS. The current scripts/audio-state.sh read gives the node names.
  • Proposal (M3 Wi-Fi): the 60-ath12k-iommu-fq.rules udev rule (LGPL, two lines) switches the WCN7850’s IOMMU group to DMA-FQ; its own comment gives the trade-off: with IOMMU_DEFAULT_DMA_STRICT=y “every RX buffer unmap does a synchronous SMMU TLB sync with IRQs off (~32% of NET_RX samples)”, and “a write-after-unmap bug corrupts memory silently instead of raising an SMMU fault”. gbook builds IOMMU_DEFAULT_DMA_STRICT=y, so the same CPU cost applies; try it only with a measured before/after, and never on a debugging boot.
  • Proposal (later, suspend): ath12k-irq-respread.sh (LGPL) records each ITS MSI’s effective CPU before suspend and pins it back after resume, because non-managed MSIs stay on CPU 0 after CPUs are hot-unplugged. Relevant once mica suspends.
  • Proposal (tooling): steamos-manager’s device TOML shows GPU limits set through devfreq sysfs and CPU caps through cpufreq policies, both from userspace. gbook’s attempt.sh trace could read the same sysfs nodes for the X1-85 when GPU clocks become a question; no kernel change.

Not found / not public

  • No SteamOS-for-ARM source repository, image recipe or build pipeline: potato/mash/*, potato/smash/holo, deckard/deckardos/{holo,linux-valve,linux-firmware,u-boot,steamos-efi,deckard-hw-support,deckard-audio-config,...}, holo/{holo,steamos-efi,steamos-reset,atomupd-daemon,steamos-atomupd-client} all answer 404 to the public API (2026-11-05); GitLab blob search needs a token (401). holo-core-aarch64-preview has no licence file of its own.
  • Valve’s kernel tree has no public branch or tag that this search found (search/commits?q=hash: returns 0 for both tips; no linux-valve repository on GitHub); the commits are readable only by SHA through GitHub’s torvalds/linux endpoint. Cite them by SHA with that caveat.
  • Mesa b826d29e is not in mesa/mesa.
  • The repair image (4.07 GB) and the QDL package (4.10 GB) were not downloaded, so the OS LUN’s partition sizes, the root filesystem type (U-Boot carries btrfs and ext4 code; the factory reset formats var and home as ext4), the installer’s steps and the EDL flashing procedure are unread. The preview’s system.rootfs.zst (385 MB) likewise.
  • The proprietary packages (deckard-boot-firmware, deckard-boot-luns, deckard-boot-images, deckard-hw-support) are described from their file lists, strings and GPT headers only.
  • deckard-steam-rel (671 MiB) and deckard-steamvr-rel were not read; where FEX’s binaries land on disk is known only from the FEXBash wrapper’s paths.
  • The Steam Frame release notes name no kernel, Mesa or firmware versions; the store page says “SteamOS 3”, the notes say “SteamOS 0.x (for Steam Frame)”.
  • No first-party statement ties the Igalia or Connor Abbott Mesa work to Valve funding.

Sources

URLWhat it isReadPin / commitLicence
https://api.github.com/orgs/ValveSoftware/reposValve’s GitHub organisation (55 repos)2026-11-05steamos_kernel last push 2023-05-22NOASSERTION
https://gitlab.steamos.cloud/api/v4/projects (?search=, /<id>/repository/{tree,files,commits,branches,tags})Valve’s GitLab, public projects and files2026-11-05per project aboveper project
https://gitlab.steamos.cloud/holo/holo-core-aarch64-previewArch aarch64 preview package sources2026-11-0567f0d559none stated
https://holo-packages.steamos.cloud/holo-core-aarch64-preview/mash-20251118.3/preview binary repo (pacman.conf, pacman.mirrorlist, system.rootfs.zst)2026-11-05mash-20251118.3 (2026-07-10)per package
https://gitlab.steamos.cloud/frame-public/leptonLepton2026-11-055964972a, v3.0.10MIT, GPL-3.0
https://gitlab.steamos.cloud/frame-public/frame-developer-toolsdeveloper tools, pacman/deckard-pacman-src2026-11-0516d1942enone stated
https://gitlab.steamos.cloud/SteamHardware/SteamFramehardware references2026-11-05bdbb41baCC-BY-NC-SA-4.0
https://gitlab.steamos.cloud/holo/steamos-manager (data/devices/steam-frame.toml, data/platform.toml)SteamOS Manager2026-11-05a3b20137MIT
https://gitlab.steamos.cloud/holo/fakemachinefakemachine fork2026-11-0574dd229bApache-2.0
https://steamdeck-packages.steamos.cloud/archlinux-mirror/Deck/Machine package mirror (x86_64)2026-11-05listing
https://holo-packages.steamos.cloud/archlinux-deckard/archlinux/main/{core,extra}/os/aarch64/ and core.db.tar.xzFrame base repos2026-11-05newest 2026-10-30Arch’s
https://holo-packages.steamos.cloud/archlinux-deckard-hotfixes/ with deckard-arch-hotfixes.{db,files}.tar.xzFrame Valve packages; eight packages downloaded and read: linux-72-deckard, -headers, deckard-boot-firmware, deckard-boot-luns, deckard-atomupd-config, deckard-audio-config, deckard-boot-images (listed only), steamos-customizations-deckard, deckard-hw-support, steamos-efi2026-11-05newest 2026-11-04per package (table in section 1)
https://steamdeck-images.steamos.cloud/recovery/repair images (HEAD only)2026-11-0520260922.5153644-0.3.0not stated
https://store.steampowered.com/steamos/download/?ver=steamframedownload page (redirect target read from its HTML)2026-11-05
https://github.com/torvalds/linux/commit/f905cb6e600a and abbbaa99ac66Valve Deckard DTS and binding2026-11-05f905cb6e600aBSD-3-Clause / GPL-2.0
https://api.github.com/repos/torvalds/linux/commits/344f27110f05 (and compare/v7.2..., commits?sha=...&path=...)Valve’s kernel commits through GitHub’s fork network2026-11-05344f27110f05, ee93c0d9394b, base 8d3ae59288f1GPL-2.0-only
https://patchwork.kernel.org/api/patches/?q=deckard&project=linux-arm-msmDeckard follow-up patches2026-11-05
https://partner.steamgames.com/doc/steamhardware/steamframe and /setup, /debugging, /loadgames, /compat, /engines/customSteam Frame developer documentation (fetched with firecrawl)2026-11-05Valve
https://help.steampowered.com/en/faqs/view/1B71-EDF2-EB6D-2BB3, 65B4-2AA3-5F37-4227, 2668-0B6E-79E0-A85B, 4F01-E85C-4977-EA0ESteam Support: recovery, install, Frame guide, LEDs2026-11-05Valve
https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=4165890SteamOS-for-Frame release notes2026-11-050.3.0 … 0.5.7 BetaValve
https://store.steampowered.com/sale/steamframe and the 2026-09-14 announcement postproduct page and launch post2026-11-05Valve
https://github.com/FEX-Emu/FEX, https://fex-emu.com/Scourge-of-emulation/, https://fex-emu.com/FEX-2610/FEX and its blog2026-11-05FEX-2610MIT
https://github.com/bylaws/linux/commit/7ae989a43ae7e3cb8007ac21c28dacc24c9d8320the unaligned-atomics patch as published by its author2026-11-05branch fex-unaligned-atomicGPL-2.0
https://gitlab.freedesktop.org/api/v4/projects/176/merge_requests/{41323,44840,44348,45615}Mesa Turnip MRs2026-11-05merged 2026-05-08, 10-06, 09-14, 10-27MIT
~/.cache/gbook/linux-a804c57, ~/.cache/gbook/build-a804c57/.config, kernel/patches/*.patch line 1, the three script headersgbook’s own pin, config and patch list2026-11-05a804c57

Decisions (owner, 2026-11-05, 10:52 AM EST)

Grilling round on the five candidates; the owner took every recommendation:

  1. M4 recovery matrix: write it now as the M4 planning doc, before any SSD work (Valve’s five rows mapped onto ChromeOS mechanisms).
  2. ramoops: add on a later diagnostic boot, with a stock-safe address and ADR 0004’s “unverified” marking.
  3. ath12k IOMMU DMA-FQ rule: measure first, one boot with the rule, iperf against a LAN host before and after; never on a debugging boot.
  4. WirePlumber per-card drop-in: adopt when PipeWire enters the rootfs.
  5. Initramfs: the block-layer read-only lock of the stock partitions from /init now (re-implemented, not copied); the auto-reboot countdown shell later, with M4.

Each is on the task list (TaskList); this note is the source they cite.

Evidence

Dated read-only reads of the device and of each boot, redacted of per-unit identifiers at collection time. Newest first.

Phase 3: Wi-Fi firmware, WCN7850 up (2026-11-03)

Attempt 2026-11-03-1. Image 550a4055eee29513: kernel, config and DTB unchanged from attempt 2026-11-02-6. The new /init and the Wi-Fi firmware went into the initramfs (commit 07c76e5). Source: the AP console log dumps/firstboot/2026-11-03-ap.log (gitignored); MACs are not printed.

Result

The Wi-Fi works: the ath12k probe completes. wlan0 stays down; nothing scans or transmits.

  • Firmware boot: MHI powers the chip on and it reaches mission mode, then QMI reports chip_id 0x2 chip_family 0x4 board_id 0xff soc_id 0x40170200.
  • Firmware version: fw_version 0x1103006c, build WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 (2026-03-06). That’s the linux-firmware amss.bin.
  • Interfaces: GBOOK: wifi-phy=phy0, GBOOK: wifi-net=wlan0 operstate=down driver=ath12k_wifi7_pci wireless=yes, wifi-wait=0s.
  • No errors: no failed to fetch board data and no regdb warning.
  • PCI subsystem IDs, model-level and new in the pci= line:
    • WCN7850 0004:01:00.0: subsystem 105b:e0ee;
    • NVMe 0006:01:00.0: subsystem 1344:1100, still driver=none (# forbid-y: BLK_DEV_NVME).

Board data

WCN7850 matches board data by bus and board (ath12k/wifi7/pci.c:135,159 at a804c57). The name it builds is bus=pci,vendor=17cb,device=1107,subsystem-vendor=105b,subsystem-device=e0ee,qmi-chip-id=2,qmi-board-id=255 (core.c:232-247). board-2.bin in linux-firmware-atheros 20260916-1 has exactly that entry. It also has variant=DE01 and DE02 entries, but those are only picked through an SMBIOS/ACPI variant, which a DT boot doesn’t have (qmi.c:2973-2982). A board-data miss would have aborted the probe before wlan0 registered (core.c:520-580), so the mica module uses the generic Foxconn 105b:e0ee entry. The stock bdwlan.* files from modem_a are not needed.

Other captures (unchanged from attempt 6)

  • ADSP running; CDSP offline.
  • Type-C ports 0 and 1.
  • Both PCIe links up: Gen4 x4 and Gen3 x2.
  • Deferred: arm-scmi (mailbox 17430000) and 3da0000.iommu (waits for GPU clock controller 3d90000; also seen in the 2026-11-02 log).
  • The 5 s settle in /init works: the GBOOK lines no longer interleave with probe messages.
  • Ramoops readback: the newest dropbox SYSTEM_LAST_KMSG entry (12:53 PM) holds this boot and ends at [ 81.894902] reboot: Restarting system.
  • The udev no-automount rule works: on 2026-11-03 the kernel stick plugged into the build host with nothing mounted.

Firmware in the initramfs (not in git, ADR 0005)

Decompressed from the build host’s linux-firmware-atheros 20260916-1 (/usr/lib/firmware) into ~/.cache/gbook/firmware:

Filesha256
ath12k/WCN7850/hw2.0/amss.bin43aadfd3df887f27de74020273aee484bac6a31dd53068f91baf2a9b094d6a68
ath12k/WCN7850/hw2.0/m3.bin0e72f44df7defc269fe92dcea25d4d409046c04b77d41c510c52879b3dfc1055
ath12k/WCN7850/hw2.0/board-2.bin1abee7132dbccb523cca44a8de4e8968aa7bf5a5fcc032c338f687f94ea5bf4e
regulatory.db (wireless-regdb)7e236caecd939c8ec98be4870bf30422f28ffef2565a38aaaa2d9ddabd0c2641
regulatory.db.p7s50332f0db09b8bcc719235ec4985c27377f2cc2f42abb7b5dcf951866c5a888a

Notice.txt (the licence) sits next to them. The firmware tree hash in manifest.txt is 8a75fc5ebfeea9766b1d59c6a718c42e080721f68ccb078df3edf0ce59f03075 (68 files, ADSP included).

Stock reference (read, not used)

  • modem_a /image/kiwi/ (the cnss2 layout): amss.bin, amss20.bin, bdwlan.e01…e54, bdwlan01.*, bdwlan.elf, regdb.bin, phy_ucode*.elf.
  • Stock Android drives the chip with the downstream cnss2/cnss_pci driver (/sys/module/cnss2), through the DT node qcom,cnss-kiwi@b0000000.
  • Shell can’t read the PCI IDs or /sys/kernel/cnss/* there (Permission denied).

First try refused: “No valid image detected”

The first stick boot of this image failed with “No valid image detected” on screen. A re-insert gave the same error.

  • What the firmware log showed: every rescan printed GptNextKernelEntry … s0 t0 p0, no more kernels and vb2api_load_kernel() = 0x100b2000.
  • What was on the stick: read back on the build host, KERN-A had priority 0, tries 0. The image had written 15/3.
  • Why a re-insert can’t help: vboot marks a partition it rejects as bad (GPT_UPDATE_ENTRY_BAD, 2load_kernel.c:743 at firmware-bluey-16702.B 34fbbe6).
  • Why the reason is lost: the log is a 128 KiB ring (Read cbmem console: size=131064). It was full of rescans and menu lines, and the first attempt’s error line was gone.
  • Outcome: the stick was rewritten with the same image (read-back identical), and the next boot worked.
  • Cause: unknown. Inference: a transient USB read on the first try, since the bytes and the size (the FIT is 58.9 MiB, against the 256 MiB KERNEL_SIZE) were identical both times. The runbook’s failure playbook now says to read the log right after the first refusal and to rewrite the stick before retrying.

Attempt 2026-11-03-2: join, DHCP, ping

Image ec9f9f4b96ad4f86 (commit 4428f27). The kernel, DTB and firmware are as in attempt 1. The initramfs adds:

  • iw:arm64 6.17-1+b1 and wpasupplicant:arm64 2:2.11-7 from sid, with their 13 shared libraries in /lib;
  • the owner’s network block, from ~/.local/state/gbook/wifi.conf (mode 600, never printed or hashed).

AP console, MACs and the SSID redacted:

StepResult
Regulatoryiw reg set US → country US: DFS-FCC; phy0 is self-managed (the firmware reports US)
Scanexit=0 bss=17 2g=7 5g=8 6g=2 strongest=-24.00dBm; own SSID seen at −33 dBm
Joinauthenticated, associated (status=0) after 15 s; WPA: Key negotiation completed [PTK=CCMP GTK=CCMP]
Link6135 MHz (6 GHz), −41 dBm, tx 2882.6 MBit/s 320MHz EHT-MCS 7 EHT-NSS 2 (Wi-Fi 7)
DHCPbusybox udhcpc: lease of <the device's lease>/24 from <the gateway>, 86400 s
Pinggateway <the gateway>: 3/3 received, rtt 3.4/9.7/16.2 ms
Enddeauthenticating … (Reason: 3=DEAUTH_LEAVING), wlan0 down, reboot: Restarting system at 118.7 s

The panel goes dark at about 33 s: VREG_EDP_3P3: disabling. The kernel switches off unclaimed regulators, and no display driver holds the panel supply yet. That’s expected while only simpledrm drives the screen.

The wpa_supplicant log printed the own SSID once; /init now redacts SSID='…' too.

Update 2026-11-03: the cause of the “No valid image” refusals

The inference above (“a transient USB read”) was wrong. Attempt 2 was refused the same way on its first two power-ons. The EC console logged boot_keys: recovery (boot_keys_value=0x00000009) before both refused boots in dumps/firstboot/2026-11-03-ec.log, and not before either boot that worked.

The owner had powered on with the recovery keys, because the agent’s instructions said so; runbook section 6 says plain power-on. In recovery mode vboot scans the stick for a recovery kernel. The dev-signed kernel fails that check, and the partition is marked bad (GPT_UPDATE_ENTRY_BAD, cgptlib.c:155 at 34fbbe6). On that power-on, the first USB scan already showed s0 t0 p0 (owner photo of the firmware log).

After a rewrite and a power-on with no keys held, the stick booted. Runbook section 6 step 3 now forbids the recovery keys with the image stick in.

Attempt 2026-11-03-3: netlog over Wi-Fi

Image 6582ef037a626828, commit d3b37f8. After GBOOK: end, /init sends /tmp/gbook.log (every GBOOK: line) and the whole dmesg with busybox nc to <the build host's LAN address>:5140. That target is in /etc/gbook-netlog, written by package-image.sh. On the build host, scripts/attempt.sh netlog start runs socat bound to the LAN address. It accepts only <the device's lease> (range=), and the owner-approved ufw rule allows only that peer.

  • Sent: GBOOK: netlog=sent 1176 lines to <the build host's LAN address>:5140 exit=0.
  • Received: dumps/firstboot/2026-11-03-netlog.txt (mode 600), 1176 lines: 361 GBOOK: lines and 813 kernel lines.
  • The netlog covers the whole boot: it starts at [ 0.000000] Booting Linux on physical CPU 0x0000000000 and includes the Linux version banner. The AP console starts at about 0.6 s; ramoops starts mid-boot and has no banner.
  • Redaction: no MAC-like strings and no SSIDs; 12 <mac> substitutions.
  • One fix: the serial pattern also caught Serial: AMBA PL011 and Serial: 8250/16550. It now matches only SerialNumber: and serial number:.
  • The rest matched attempt 2: join after 15 s, DHCP <the device's lease>/24, gateway ping 3/3 (11.1 ms average), reboot at 117.6 s, stock Android back.

The Googlebook now has three independent log channels: the SuzyQ AP console, ramoops through dropbox, and netlog.

USB under mainline on mica (2026-11-02)

Attempt 2026-11-02-3: the first-boot image plus kernel/mica-usb.config (the USB chain built in) and USB captures in /init. Source: the AP console over SuzyQ (raw log dumps/firstboot/2026-11-02-ap.log). Same DTB as the first boot.

Result: USB host works on all four controllers

BusController (DT)DeviceSpeed
1a200000 usb_2 (USB 2 only)Integrated Webcam FHD 1bcf:28dc480M
2, 3a400000 usb_mp (multiport)FPMCU 18d1:540612M
4, 5a600000 usb_1_ss0none (inference: the left Type-C port, holding the SuzyQ)
6, 7a800000 usb_1_ss1the kernel image stick, Lexar 21c4:0809 → sda, sda1 = KERN-A480M
  • Root hubs: four USB 2 (480M) and three SuperSpeed (10000M), one per SS-capable controller.
  • The right Type-C port is usb_1_ss1 (the stick booted from it and enumerated on bus 6).
  • SuperSpeed does not come up on Type-C yet: the same stick runs at 5 Gbps on the build host. Inference: the PS8830 retimer and the QMP combo PHY get no orientation or mode, because pmic-glink stays deferred (qcom_pmic_glink: failed to initialize pdr): it needs the ADSP running with its firmware.

Still deferred (/sys/kernel/debug/devices_deferred)

DeviceReason
pmic-glinkfailed to initialize pdr (ADSP not running)
3da0000.iommu (GPU SMMU)supplier 3d90000.clock-controller (GPU clock controller) not ready
firmware:scmisupplier 17430000.mailbox not ready
aux_bridge.0-.3failed to acquire drm_bridge (DisplayPort chain)
1bf8000.pcie, 1c08000.pcieFailed to parse Root Port: -517

What changed from the first boot

The first boot left every dwc3 deferred (“failed to register DWC3 Core”) because the eUSB2 and QMP PHYs, the eUSB2 repeaters, the PTN3222 redrivers, GENI I2C, TYPEC and PS883X were all =m in defconfig and no module loads. The QMP children also need their menu parent PHY_QCOM_QMP=y (drivers/phy/qualcomm/Kconfig:53-125 at a804c57); scripts/build-kernel.sh caught that demotion.

Attempt 2026-11-02-4: ADSP (failed, cause found)

kernel/mica-adsp.config + the stock adsp.mdt firmware in the initramfs. Results (AP console):

  • remoteproc0: Booting fw image qcom/x1e80100/google/mica/adsp.mdt, then qcom_q6v5_pas 6800000.remoteproc: Error in getting resource table: -5; adsp stays offline.
  • pmic-glink now probes (no longer deferred): the PD mapper and QRTR are built in. qcom-battmgr-{ac,bat,usb,wls} register but read empty, since the ADSP is down. No Type-C ports yet. The stick is still at 480M.
  • The GPU SMMU and the DP aux bridges are no longer on the deferred list; SCMI and PCIe still are.

Cause: the resource-table request runs only when the PAS node has iommus (qcom_pas_parse_firmware, a804c57), which mainline’s x1-el2.dtso adds for the 9345. Mica’s TrustZone answers -EIO. The stock mica tree has no iommus on the ADSP or CDSP node (entry00-base.dts:5513, :12925) and uses the same adsp.mdt/adsp_dtb.mdt names. Fix: dts/mica-el2.fixups deletes both properties after the overlay (attempt 2026-11-02-5).

Attempt 2026-11-02-5: ADSP running, SuperSpeed on Type-C

Same kernel and firmware as attempt 4; the DTB drops the PAS iommus (dts/mica-el2.fixups).

  • remoteproc remoteproc0: remote processor adsp is now up at 1.6 s; adsp running. cdsp stays offline (no firmware shipped: -2).
  • pmic_glink UCSI registers two Type-C ports: port0 (host, sink) and port1 (host, source: powering the stick), both orientation=normal.
  • qcom-battmgr answers: USB supply online, battery Charging (inference: power from the SuzyQ link in the left port); capacity still empty at capture time. UCSI adds two ucsi-source-psy supplies.
  • The kernel image stick enumerates on bus 7, the SuperSpeed hub of usb_1_ss1, at 5000M (480M before).
  • Still deferred: arm-scmi (mailbox) and both PCIe controllers.

USB on mica now works end to end: all four controllers, both Type-C ports at SuperSpeed (the right port tested), Type-C role/orientation and battery reporting through the ADSP.

Every enabled DT node binds: bandwidth monitors, videocc, camcc (2026-11-04)

Attempt 2026-11-03-35 (written before midnight, booted 2026-11-04 about 12:00 AM ET): kernel #18 (commit d41ab28, kernel/mica-sync.config), the board DTS with ADR 0008 (no PSCI domain idle states), image 4c8699959d256a77. One cold start (the EC reset itself at power-on: Reset cause: sysjump hard efs). Sources: the netlog dumps/firstboot/2026-11-04-netlog.txt and the AP console log (both gitignored), attempt.sh compare against -34 boot 3.

Change

kernel/mica-sync.config builds in three drivers that arm64 defconfig has as modules (the initramfs loads none):

  • QCOM_ICC_BWMON: the four bandwidth-monitor PMUs pmu@24091000 (qcom,sc7280-llcc-bwmon) and pmu@240b3400, @240b5400, @240b6400 (qcom,sdm845-bwmon).
  • SM_VIDEOCC_8550: clock-controller@aaf0000 (qcom,x1e80100-videocc).
  • CLK_X1E80100_CAMCC: clock-controller@ade0000.

The symbols come from scripts/dt-kconfig-map.py at a804c57; their four lines left kernel/dt-gaps.txt in the same commit.

Result

  • unbound= went from 6 to 0. No line in dmesg says sync_state() pending due to, so every interconnect and clock provider reached sync_state(). deferred= is empty.
  • No probe error from the new drivers (dmesg has no failure line naming bwmon, videocc or camcc).
  • SCMI: 78 replies, max 3.9 ms, 0 timeouts. That is the fourth stall-free cold boot with no domain idle states. cpufreq is on all three clusters (710 MHz to 3.42 GHz).
  • The touchscreen still fails (hid-generic ... -22). Its rail and enable pin read on this time (ts-vreg=enabled users=1, gpio88 out high, against disabled/low on -34 boot 3), which is the same failure with a different probe order (inference).
  • compare labels this boot “warm”: it took the AP reset at EC 179 s, which was /init’s reboot after the run, while the EC’s own reset at power-on restarted its clock. The boot was cold.

Open

  • What the providers’ sync_state() changes in power (bandwidth and clock votes dropped to what consumers ask for) has not been measured. The suspend test covers it.

Phase 3: touchpad, touchscreen and the Ti50 TPM (2026-11-03)

Attempts 2026-11-03-15 (image b5f533fb178c67a2, kernel #13, commit 7bffb22) and 2026-11-03-16 (image bf2dd269aa121412, same kernel, /init from commit a9d7aeb). Owner pick after the retro (2026-11-03): touchpad, touchscreen and TPM. Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt and the AP and EC console logs dumps/firstboot/2026-11-03-{ap,ec}.log (all gitignored), and the pinned tree a804c57.

Why

The retro’s gap check (scripts/dt-kconfig-map.py --gaps, commit 4e1f825) listed hid-over-i2c I2C_HID_OF=m touchscreen@10,touchpad@2c and google,cr50 TCG_TIS_I2C_CR50=m tpm@50. Both drivers are =m in arm64 defconfig and the initramfs loads no modules, so none of the three nodes had ever bound.

Changes (commit 7bffb22)

  • kernel/mica-touch.config: I2C_HID_OF, I2C_HID_CORE, HID_MULTITOUCH built in.
  • kernel/mica-tpm.config: TCG_TIS_I2C_CR50 built in; TCG_TPM2_HMAC forbidden at =y (it creates a primary key in the null hierarchy at every boot, tpm2-sessions.c:1408-1412). TPM traffic is probe-time startup, self-test and capability reads, and TPM2_Shutdown(CLEAR) at reboot (tpm-chip.c:272).
  • Both compatibles left kernel/dt-gaps.txt.
  • /init: touch-* captures with a touch window, tpm and hwrng captures; from b6b6662 and a9d7aeb the hid-over-i2c IRQ counts around the window and a 30 s window behind a banner.

TPM: pass (attempt -15)

  • cr50_i2c 1-0050: ti50 OT TPM 2.0 (i2c 0x50 irq 177 id 0x5066) at 2.80 s (the driver’s Ti50 name, tpm_tis_i2c_cr50.c:693-702).
  • tpm=tpm0 tpmrm0 major=2 driver=cr50_i2c; hwrng lists tpm-rng-0 beside smccc_trng (current).

Touchpad: pass (attempt -16)

  • hid-multitouch 0018:27C6:0D4F: Mouse (event0) and Touchpad (event1) on 2-002c, both boots.
  • -15: 0 events, but the owner missed the 20 s prompt. -16: touch-events=event1 bytes=373296 events=15554 in the 30 s window; IRQ msmgpio 3 Level hid-over-i2c went 646 to 2888.

Touchscreen: probes on one boot of two, reports never arrive whole

  • -15: hid-multitouch 0018:1FD2:5010 bound (event2, and event3 named UNKNOWN, likely a vendor collection; inference); no touch in the missed window.
  • -16: the descriptor read at probe came back garbled: hid-generic 0018:FFA5:FFFF.0001: unexpected long global item, probe with driver hid-generic failed with error -22. In the window the IRQ (msmgpio 51) still went 305 to 921, and each report failed i2c_hid_of 0-0010: i2c_hid_get_input: incomplete report (64/106) (i2c-hid-core.c:570-573: 64 is the buffer size from the bad descriptor, 106 the size the panel sent).
  • So the panel answers and reports touches; the descriptor read at probe is unreliable (inference: power-on timing). The node matches stock (descriptor address 0, TLMM 51 level-low, L15B and the TLMM 88 3.3 V switch with startup-delay-us = <150000> and regulator-boot-on, overlay entry01 fragments 69 and 75). The 9345’s panel is a different one (descriptor address 1), so it gives no precedent.
  • Candidate fix (not tried): post-power-on-delay-ms on touchscreen@10 (i2c-hid-of.c:58-59, :105). Not in the stock tree, so it would be marked unverified (ADR 0004). Judge it over several boots: one good probe in two is the baseline.

Other results on these boots

  • compare printed start: cold for both (the retro’s boot_start.py fix works) and no WRONG IMAGE.
  • unbound= lists 1dfa000.crypto, the four CPU bandwidth-monitor PMUs, 3d6a000.gmu, aaf0000 and ade0000 clock controllers: the gap list’s leads, as expected.
  • SCMI failed on both cold starts (Firmware version 0x0, cpufreq=none): four of the last five cold starts (scmi-2026-11-03.md).

Update: touchscreen attempts -17 to -19 (2026-11-03, 7:32 PM ET)

AttemptChangeTouchscreen at probeIn the window
-17post-power-on-delay-ms = <150> (commit 42047ed)ID right (1FD2:5010), report descriptor usage index exceeded, -22IRQ 1 to 730, every report incomplete (64/106)
-18same, plus dyndbg for i2c-hid-core.c:944 and :804 (GBOOK_CMDLINE_EXTRA, commit bc705a1)see the dump belowno input device
-19i2c8 at 100 kHz (commit 2d4c257)no message at all: no dump, not deferred, not unboundno IRQ line registered

The -18 dump (i2c_hid_of 0-0010: HID Descriptor:): 1e 00 00 01 a7 03 21 00 24 00 6a 00 25 00 6c 00 22 00 23 00 a5 ff ff ff ff ff ff ff ff ff.

  • The first 20 bytes are a valid HID-over-I2C descriptor: length 30, version 1.00, report descriptor 0x3a7 (935) bytes at register 0x21, input register 0x24, wMaxInputLength 0x6a = 106, output 0x25/108, command 0x22, data 0x23.
  • From byte 20 the read returns 0xff: vendor a5 ff instead of d2 1f, then ff to the end. The report descriptor read gives 05 0d 09 04 43 and then 0xff (a valid one starts 05 0d 09 04 a1 01).
  • The touchpad’s dump on 2-002c (another bus) is clean: ... 2f 00 ... c6 27 4f 0d ... (27C6:0D4F, wMaxInputLength 47).
  • So the panel’s descriptor is right and the read loses data partway: the line reads high (0xff) once the device stops driving it (inference). The “64” on -16/-17 was a corrupted wMaxInputLength. The 30-byte read uses the GENI FIFO path (DMA starts at 32 bytes, i2c-qcom-geni.c:528), so SE DMA is not the cause.

-19 at 100 kHz is inconclusive: the probe ran earlier than on any other boot (touchpad at 1.5 s against 3.2 to 5.4 s), and the one silent failure path in i2c-hid is nothing at this address (i2c-hid-core.c:1055, dev_dbg, not enabled). Speed and timing are mixed.

Reverted (commits 9126648, 6d66b05): the 100 kHz bus and the power-on delay, neither shown to help; the DTS traces to stock again. Three boots did not converge, so the touchscreen is parked.

Leads for a later session (inference, untested):

  • Stock reaches the panel through Qualcomm’s vendor GENI I2C driver at 400 kHz; mainline’s GENI clock table and transfer handling differ. Compare the SCL timing values (i2c-qcom-geni.c clock table) with a stock-side read.
  • Enable i2c-hid-core.c:1055 and the GENI I2C error lines in the next touch boot, so a silent probe failure shows.
  • The panel’s own reset or report-enable line (TLMM 89, dts/README.md) and its timing against the first read.

Update: the touchscreen on the wire, attempt -21 (2026-11-03, about 8 PM ET)

Attempt 2026-11-03-21 (kernel #16, commit 778abbf) traced the i2c and qcom_geni_i2c events (kernel/mica-debug.config, debug-tools.md); the probe-time trace is in the netlog.

  • Descriptor read clean this time (30 bytes, FIFO path): … 22-00-23-00-d2-1f-10-50-30-50-00-00-00-00 (1FD2:5010, version 0x5030).
  • Report descriptor (935 bytes, DMA path): 05-0d-09-04 then ff to the end; hid-generic failed -22.
  • Input reports (64-byte reads, the fallback buffer): every one starts 6a-00-10-03-01-48-36-1d-33 (length 106, report ID 0x10), and about half turn to ff after 9 to 12 bytes.
  • No controller error: geni-err is empty; the GENI status shows ordinary done interrupts (m_stat=0x08000081).
  • Bus setup identical on both touch buses: clk_freq=400000 clk_div=2 t_high=5 t_low=11 t_cycle=22 (i2c-qcom-geni.c:188, the 19.2 MHz table). By the driver’s formula (:180-182) that is about 1.15 µs low and 0.52 µs high, under the I2C Fast-mode minimums (1.3 µs, 0.6 µs); the comment says the firmware adds cycles the formula leaves out (:176-178), so the real timing is unknown (inference). The touchpad tolerates it.
  • Reading: the panel answers and starts each reply correctly, then lets the data line float (0xff) at a varying byte, with no error the controller sees. Bus timing that this touch controller does not tolerate fits (inference); -19’s 100 kHz boot was inconclusive and is worth repeating with the trace on.

Update: 100 kHz with the trace, attempt -22 (2026-11-03, 8:14 PM ET)

i2c8 at 100 kHz again (commit 20a1f8a), with tracing and dyndbg on i2c-hid-core.c:1055 and i2c-qcom-geni.c:235-306. The bus came up at 100 kHz (clk_div=7 t_high=10 t_low=12 t_cycle=26), and the touchscreen did not acknowledge its address: geni_i2c a80000.i2c: NACK: target device unresponsive, check its power/reset-ln twice, then i2c_hid_of 0-0010: nothing at this address: -6. That is -19’s silent failure. So the panel answers at 400 kHz (and drops out mid-read) and not at all at 100 kHz: the slower bus made it worse, so “too fast for the panel” does not fit (inference). Reverted (commit 6671f61); the DTS traces to stock again. Parked. Untested leads: the panel’s own enable and reset sequencing before the first read (TLMM 89, the 3.3 V switch on TLMM 88, vreg_l15b), compared with what stock does at boot; and the stock driver’s I2C settings.

Update: panel follower and a full stock comparison, attempt 2026-11-04-1 (2026-11-04, 12:15 AM ET)

Offline first: the touchscreen’s DT matches stock line for line. ts0_default (int-n gpio51; reset-n gpio48 output low; report-enable gpio89 output low), the 3.3 V switch on gpio88 with a 150 ms startup delay, vdd L15B, IRQ_TYPE_LEVEL_LOW, hid-descr-addr 0 (stock entry01-overlay.dts fragment@68 and fragment@75). The bus pins match as well: stock sets bias-pull-up plus qcom,i2c_pull, and mainline’s bias-pull-up = <2200> selects the same TLMM I2C strong pull (pinctrl-msm.c:467, i2c_pull_bit = 13 in pinctrl-x1e80100.c at a804c57).

Attempt 2026-11-04-1 (kernel #18, dts/diag/touch-panel-follower.dtsi, commit 5b9ee4e): panel on the touchscreen node points at the eDP panel, so i2c-hid powers and probes it only after the panel is prepared, as the ChromeOS in-cell boards do (sc7180-trogdor-homestar.dtsi:119). The owner touched the screen during the prompt.

  • The link works and changes nothing. The panel came up at 3.95 s and the touchscreen probed after it, at 4.30 s. The 30-byte HID descriptor read cleanly. The 935-byte report descriptor read 05-0d-09-04-43 and then 0xff to the end (unknown main item tag 0x4, -22).
  • The interrupt fires and the controller reports. There were 1936 msmgpio 51 interrupts on CPU 0 and 1940 input reads (64 bytes each; the device announces 106). 1549 of them turn to 0xff from byte 5 to 8, while the rest read cleanly (6a-00-10-03-01-b6-47-cc-3a-c6-07-cc-04-00 …). There was no controller error.
  • Reading (inference): the controller starts every reply right and then stops driving SDA at a random byte. With the DT, pins and pull-ups as stock and the panel timing ruled out, what is left is the controller side: the GENI SCL timing (19.2 MHz table: t_low ≈ 1.15 µs, t_high ≈ 0.52 µs by the formula, under the Fast-mode minimums, i2c-qcom-geni.c:174-188) or clock stretching that the controller does not honour. Stock runs a vendor GENI driver whose timing cannot be read from the image. -22’s 100 kHz NACK does not fit timing alone.
  • The fragment stays until the owner decides (status: failed for the probe; the link itself is harmless).

Update: in-spec GENI timing, attempt 2026-11-04-2 (2026-11-04, 12:26 AM ET)

kernel/patches/0008 (kernel #19) set the GENI 400 kHz entry to 2/6/13/24, and the trace confirms it on both touch buses (geni_i2c_bus_setup: a80000.i2c: … t_high=6 t_low=13 t_cycle=24, the same on b80000.i2c). No panel follower. No change: the touchscreen’s HID descriptor read FFFF:FFFF this time (-22), 608 of 773 reads ended in 0xff, and there was one NACK: target device unresponsive. The touchpad (27C6:0D4F) and the TPM worked at the new timing. SCL timing is ruled out (within the formula’s view of it). The touchscreen is parked again. Every setting readable from the stock tree matches; what is left is the vendor GENI driver’s behaviour, or the panel controller’s own state. The patch goes (a failed diagnostic, ADR 0007), as does dts/diag/touch-panel-follower.dtsi.

Suspend (s2idle) on mica, 2026-11-04

ADR 0009’s revisit trigger: one s2idle round trip per boot, from /init with gbook.suspend=1 on the command line (commits cf49174, c3fa7e7). The wake is the cros-ec-rtc alarm, 20 s after entry. Six cold boots, attempts 2026-11-04-5 to -10 (dumps/firstboot/attempts.tsv). Sources: the AP and EC SuzyQ console logs, and stock’s SYSTEM_LAST_KMSG (ramoops) via scripts/attempt.sh readback. Times are kernel uptime unless marked EC.

Result

Suspend works; resume resets the SoC every time. Entry, the device suspend and the wake all work. The resume dies in the PCIe noirq resume, and the EC then power-cycles the AP. No netlog arrives from these boots.

AttemptKernel, DT extraWhere the resume died
-5#21Unseen: the console sleeps during suspend (Suspending console(s))
-6#21, no_console_suspendgcc_pcie_6a_gdsc status stuck at 'off' (WARNING gdsc.c:192, genpd_resume_noirq), then silence
-7#21, dts/diag/no-pcie6a.dtsigcc_pcie_4_gdsc status stuck at 'off', then silence
-8#22 (patch 0008)No GDSC warning; nothing printed before the reset
-9#23 (0008 + pm_print_times)pci 0006:01:00.0 (NVMe, no driver) pci_pm_resume_noirq never returned; last line qcom-pcie 1c08000 (PCIe 4) genpd_resume_noirq called
-10#23, no PCIe 6aqcom-pcie 1c08000 (PCIe 4) genpd_resume_noirq called at 144.093 s, never returned; every other noirq resume returned. Before this boot the Googlebook went to G3 18 s after a first power-on (EC 451.46 s), cause not known, and was powered on again

What the logs show

  • The wake path works. In -5 the EC logged Handle sleep: 1 at EC 2359.68 s and the RTC host event (event set 0x02000000) at EC 2379.80 s, 20.1 s later. Then AP_RST_L transitions not expected came 0.76 s later (-8: 0.70 s), and MKBP: The AP is failing to respond. The EC ran a host reboot (Reset cause: hard), and the firmware fell through to stock.
  • The PCIe GDSCs. At a804c57, gcc-x1e80100.c gives the PCIe GDSCs PWRSTS_OFF_ON, so gdsc_disable() really switches them off at suspend (gdsc.c:369-380). On resume they stayed off (-6, -7). sc8280xp’s PCIe GDSCs use PWRSTS_RET_ON, which leaves them on and lets the hardware retain them.
    • Upstream: “clk: qcom: gcc-x1e80100: Do not turn off PCIe GDSCs during gdsc_disable()”, Krishna Chaitanya Chundru (Qualcomm), patch 6/7 of a series, LKML 2026-01-02, https://lkml.rescloud.iu.edu/2601.0/00459.html, Reviewed-by Bryan O’Donoghue. It is not in a804c57.
    • It is now local patch kernel/patches/0008 (ADR 0007). With it, the GDSC warnings are gone (-8, -9, -10).
  • The resume still dies in the PCIe controllers. With PM_SLEEP_DEBUG (kernel/mica-pmdebug.config) and pm_print_times:
    • -9: PCIe 6a’s controller resumed (genpd_resume_noirq 526 µs) and so did its root port. The NVMe endpoint’s pci_pm_resume_noirq then never returned, while other devices went on resuming for about 100 ms.
    • -10: with 6a disabled, PCIe 4’s own controller resume (qcom-pcie 1c08000) never returned.
    • In both boots, PCIe 4’s genpd_suspend_noirq took 3.07-3.29 s on the way in.
    • Ramoops ends on the same line as the console (-6, -9), so the reset leaves nothing more.
  • Two different failures (pm_print_times timing and the source). qcom_pcie_suspend_noirq() (pcie-qcom.c:2340 at a804c57) calls dw_pcie_suspend_noirq() (pcie-designware-host.c:1225). That either sends PME_Turn_Off, waits for L2 (polled in 1 ms steps) and shuts the host down, or returns at once and keeps the link, depending on pci_host_common_d3cold_possible() (pci-host-common.c:269-325).
    • PCIe 6a kept its link. Its genpd_suspend_noirq took 167 µs (-9) and its resume 526 µs: too short for either the L2 wait or a host re-init with link training. So the NVMe endpoint’s config access went over a link that was never shut down, and still hung.
    • PCIe 4 shut its link down. Its genpd_suspend_noirq took 3.07-3.29 s (unexplained: the L2 poll times out at 10 ms), and on resume its host re-init (qcom_pcie_resume_noirq, pcie-qcom.c:2398) never returned.
    • A lead, unverified. Patch 0008 and the upstream series change only the gcc_pcie_N_gdsc entries. The PCIe PHY GDSCs (gcc_pcie_3/4/5/6_phy_gdsc) are still PWRSTS_OFF_ON (gcc-x1e80100.c:6595-6663), and pcie6a_phy has power-domains = <&gcc GCC_PCIE_6_PHY_GDSC> (hamoa.dtsi:3546). So genpd collapses the PHY’s domain at suspend. The USB PHY GDSCs in the same file are already PWRSTS_RET_ON (gcc-x1e80100.c:6775-6867). Upstream changed those because “the QMP combo PHYs lose their configuration on machine suspend” (“clk: qcom: gcc-x1e80100: Fix USB 0 and 1 PHY GDSC pwrsts flags”, patchwork comment 25978987). A PCIe PHY that lost its configuration would explain a dead link on 6a. That is an inference; the next boot can test it.
    • Inference, not verified: an access to a PCIe block that is not powered, or behind a dead link, hangs the bus. On Qualcomm that ends in a reset with no kernel message.
  • Other observations.
    • The touchscreen bus times out on the way into suspend in every boot (geni_i2c a80000.i2c: Timeout cancel_m_cmd); this is the parked touchscreen fault and not fatal.
    • mem_sleep offers s2idle [deep], so PSCI SYSTEM_SUSPEND is available too (inference from the kernel listing “deep”). It is untested.
    • SCMI was clean on every boot (0.1 ms).
    • Idle power stays unmeasured: on the bench the SuzyQ powers the Googlebook, so the battery’s current is charge current.

Open, for the owner

  1. Test the PHY GDSC lead: a local patch moving the four gcc_pcie_N_phy_gdsc to PWRSTS_RET_ON, then one boot. No upstream patch for it was found tonight.
  2. Find the hanging step in PCIe 4’s re-init: dyndbg for pcie-qcom.c and pcie-designware-host.c on one suspend boot.
  3. Try deep (mem_sleep=deep). It takes a different firmware path, and qcom_pcie then keeps the CPU-PCIe interconnect up (pcie-qcom.c:2385).
  4. Keep ADR 0009 as it is. Suspend fails before any idle state matters, so cpuidle is not the blocker yet (inference).

Update: the first complete round trip, with patch 0009 (2026-11-04, 5:01 AM ET)

Attempt 2026-11-04-11: kernel #24 with patch 0008 and local patch 0009, which moves the four gcc_pcie_N_phy_gdsc to PWRSTS_RET_ON. Board DTS, both PCIe links on. The resume completed. Source: netlog 2026-11-04-netlog.txt#6.

  • Timing:
    • PM: suspend entry (s2idle) at 120.03 s;
    • suspend-to-idle from 126.24 s to 140.70 s (the RTC alarm);
    • noirq resume 883 ms, early resume 594 ms, resume 4855 ms (much of it console output from pm_print_times);
    • PM: suspend exit at 147.72 s.
  • Counters: rc=0. /sys/power/suspend_stats: success=1, every failure counter 0.
  • After the resume:
    • cpufreq: 3 policies, with 0 SCMI timeouts since the suspend.
    • Wi-Fi relinked in 8 s, same address, now on 5720 MHz (earlier boots joined on 6 GHz).
    • /init ran to GBOOK: end and sent the netlog.
  • Still to look at:
    • PCIe 4’s genpd_suspend_noirq still takes 3.28 s.
    • ath12k’s pci_pm_resume_early takes 0.54 s.
    • At 142.17 s qrtr logs failed to announce new service / failed while handling packet from 7:-2.
    • suspend-wakeup is empty: no wakeup source counted the wake, although the EC logs the RTC event.
  • What fixed it (inference): retaining the PCIe PHY GDSCs is what changed between -9/-10 and -11.
    • It fits the lead above: a collapsed QMP PCIe PHY loses its configuration, which explains both 6a’s dead kept link and PCIe 4’s hung re-init.
    • One boot proves only that it can work. Repeat boots show whether it is reliable.
  • Console boot #11 booted -10’s image again after -10’s reset, before -11 was written. It reset the same way after the resume (143.47 s), and is recorded with -10.
  • Idle and suspend power stay unmeasured. On the bench the battery reports charging: power_now -2.96 W before and -2.49 W after, sign convention not checked.

Update: 3 of 3 with patch 0009 (2026-11-04, 5:13 AM ET)

Two repeat cold boots of -11’s image (kernel #24, board DTS) were added to -11’s row with attempt.sh add-boots (netlogs 2026-11-04-netlog.txt#7 and #8). Both completed the round trip, the same as -11:

BootEntryPM: suspend exitrcSCMI timeouts sinceWi-Fi relink
netlog #6 (-11)120.03 s147.72 s008 s
netlog #7122.75 s150.30 s008 s
netlog #8119.67 s147.01 s008 s

So with 0008 and 0009 the round trip is 3 of 3 cold boots, against 0 of 7 before (-5 to -10 and console boot #11). Power is still unmeasured.

Update: the -11 loose ends, read from the netlogs (2026-11-04, 5:20 AM ET)

Read offline from 2026-11-04-netlog.txt#6 to #8 and the source at a804c57. No new boots.

  • PCIe 4’s 3.1-3.3 s genpd_suspend_noirq is silent. No kernel line falls between its calling and returned lines, and there is no Timeout waiting for L2 entry! warning, so the 10 ms L2 poll (pcie-designware-host.c:1255-1268) did not time out.
    • What runs after the poll: dw_pcie_stop_link(), then qcom_pcie_host_deinit() (pcie-qcom.c:1453-1470). That asserts PERST, powers the WCN7850 off through pwrctrl (pci_pwrctrl_power_off_devices), powers the PHY off and runs the SoC-specific deinit.
    • The WCN power sequencer has only millisecond delays (pwrseq-qcom-wcn.c:146-149, 234-249). Which step takes 3 s is not known.
    • Next: one suspend boot with dyndbg for pcie-qcom.c, pci-pwrctrl* and pwrseq-qcom-wcn.c. It costs about 3 s of entry time only, so it is not urgent.
  • ath12k’s 0.54 s pci_pm_resume_early is the WCN7850 booting again. pwrctrl powered the chip off at suspend (above), so on resume MHI powers it on (mhi mhi0: Requested to power ON at 141.67 s, Wait for device to enter SBL or Mission mode). ath12k then logs the chip and firmware again (142.20 s). That is expected with pwrctrl power-off (inference).
  • The qrtr line is resume-only. failed to announce new service / failed while handling packet from 7:-2 appears once in each resume boot (netlog #6, #7, #8) and in none of the five boots without suspend (#1 to #5). It comes at 142.17 s, while the WCN firmware is coming back over MHI. It is net/qrtr/ns.c:490-493: the name service could not broadcast a new server announcement (ctrl_cmd_new_server). Wi-Fi relinked in 8 s each time, so it looks harmless (inference).

Phase 3: SCMI binds, the CPUs get cpufreq (2026-11-03)

Attempt 2026-11-03-8. Image ca51a59382dff6c8, kernel #7 (2026-11-03 4:07 PM ET), commit ca81e32. Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt and the AP console log dumps/firstboot/2026-11-03-ap.log (both gitignored).

Why

Every boot up to attempt -7 ended with one deferred device: firmware:scmi … supplier 17430000.mailbox not ready, then arm-scmi: failed to setup channels and failed to setup channel for protocol:0x10. The CPUs stayed at whatever clocks the firmware left them on.

  • mailbox@17430000 is qcom,x1e80100-cpucp-mbox. Its driver is QCOM_CPUCP_MBOX (drivers/mailbox/Kconfig:343, qcom-cpucp-mbox.c:205), =m in defconfig (arch/arm64/configs/defconfig:1614), and the initramfs loads no modules.
  • The stock tree drives the same block: cpucp: qcom,cpucp@18830000 (qcom,cpucp-v2) with the same two register ranges (0x17430000, 0x18830000), an arm,scmi node with mboxes = <cpucp 0 cpucp 2> and shmem in mmio-sram@18b4e000 (entry00-base.dts:384-402, :908-926, :10534-10540). hamoa.dtsi describes the same, and each CPU takes its perf domain from scmi_dvfs (hamoa.dtsi:78-79). So no DTS change. The stock tree also lists a vendor protocol@80 that hamoa.dtsi does not have.

Changes (commit ca81e32)

  • kernel/mica-scmi.config: QCOM_CPUCP_MBOX=y. It also checks that the mailbox, SCMI core, SCMI mailbox transport, SCMI perf domain and SCMI cpufreq options are =y after the merge (they were already).
  • /init: one cpufreq line per policy (CPUs, driver, governor, current, min and max kHz) and an scmi-dmesg capture.

Result: pass

  • SCMI binds over the mailbox: arm-scmi arm-scmi.0.auto: Using scmi_mailbox_transport at 0.358 s, SCMI Protocol v2.0 'Qualcomm:' Firmware version 0x20000, scmi-perf-domain scmi_dev.4: Initialized 3 performance domains at 0.376 s.

  • cpufreq on all three clusters (schedutil governor):

    PolicyCPUsDriverCurrent at ~110 sRange
    policy00-3scmi806.4 MHz710.4 MHz to 3417.6 MHz
    policy44-7scmi2188.8 MHz710.4 MHz to 3417.6 MHz
    policy88-11scmi2188.8 MHz710.4 MHz to 3417.6 MHz
  • Nothing deferred: deferred through deferred5 are all empty, for the first time on mica.

  • Everything else unchanged: both DSPs running at the end, Type-C, PCIe, Wi-Fi (6 GHz, DHCP), eDP 2560x1600, GPU firmware and GMU loaded, netlog sent (1379 lines). reboot: Restarting system at 174.8 s (about 4:11 PM ET).

  • Earlier milestones than attempt -7: CDSP up at 0.79 s (was 1.99 s), ADSP at 6.13 s (was 7.41 s, still after the known 5 s SSCTL wait, cdsp-2026-11-03.md), PCIe Gen4 link at 1.11 s (was 2.28 s). Inference: the CPUs now run faster than the firmware’s default clocks. Not measured directly.

Log lines checked

  • [Firmware Bug]: Failed to add opps_by_lvl at 3417600 for NCC1 - ret:-16 (twice each for NCC1 and NCC2). This is a dev_info from drivers/firmware/arm_scmi/perf.c:381-383 (and :403-405): xa_insert() returned -EBUSY, so the firmware lists the 3.4176 GHz level more than once for those domains. The level still registers as max=3417600. Inference: harmless.
  • region@80a00000 … overlaps with cpucp-log@80e00000 at 0.000 s is not new: it is in every boot’s log on 2026-11-03 (7 of 7) and in 2026-11-02-2. region@80a00000 is not in the mica DTB. Inference: the boot firmware adds it. Not traced further.

Update: attempt -9 (2026-11-03, 4:20 PM ET): SCMI timed out, no cpufreq

Same SCMI config as attempt -8 (the only kernel change was kernel/patches/0001, panel-edp). Sources as above.

  • arm-scmi bound over scmi_mailbox_transport at 0.367 s as before. From 0.437 s on, 10 timed out in resp(caller: do_xfer+0xdc/0x180), each about 36 ms after the previous one (the 30 ms max-rx-timeout).
  • Late answers arrive for older requests: scmi_protocol scmi_dev.1: Message for 7 type 0 is not expected!, then for 9 and for 32. Inference: the CPUCP firmware answered, but after the kernel had given up on each request.
  • Results: SCMI Protocol v2.0 'Qualcomm:' Firmware version 0x0 (was 0x20000, so the version query failed and the quirk_perf_level_get_fc_force quirk was not applied), Failed to query supported version for protocol 0x13, scmi-cpufreq scmi_dev.3: probe with driver scmi-cpufreq failed with error -110, and /init saw cpufreq=none. scmi-perf-domain still initialized 3 performance domains at 0.806 s.
  • Nothing else failed: deferred stayed empty, both DSPs ran, display, GPU and Wi-Fi as in -8.
  • Upstream has seen the same timeout on x1e80100. Review of the series that enabled X1E cpufreq reports arm-scmi firmware:scmi: timed out in resp on the CRD (PATCH V7 0/2 “qcom: x1e80100: Enable CPUFreq”, 2024-11). The mailbox driver at a804c57 already clears each channel’s interrupt under the channel lock (drivers/mailbox/qcom-cpucp-mbox.c, qcom_cpucp_mbox_irq_fn).

Open: with one good boot (-8) and one bad (-9), the cause is not known. Next step: more boots of the same image to get a failure rate, then compare the first SCMI exchange’s timing across good and bad boots.

Update: cold boot vs warm reset (2026-11-03, 5:25 PM ET)

Attempts -10, -11 and -12, plus the EC console log dumps/firstboot/2026-11-03-ec.log (gitignored).

AttemptKernelStart (EC console)SCMI firmware versioncpufreq
-8#7, no patchescold: power button pressed, G3->S5 at EC 10691 s; its reboot 184 s later'Qualcomm:' 0x200003 policies
-9#9, patch 0001warm: AP reset in S0 at EC 11401 s, no G3; reboot 198 s later'Qualcomm:' 0x0none (-110)
-10#9, patch 0001not logged (the consoles were started mid-boot)'' 0x0, 13 timeoutsnone (-110)
-11#10, no patcheswarm: AP reset in S0 at EC 14388 s; reboot 138 s laternone: the first message timed out, arm-scmi probe -110none
-12#10, no patches (the -11 image)cold: stock powered off (power state 0 = G3 at EC 14712 s), then power button'Qualcomm:' 0x20000, both quirks on, no timeouts3 policies

Matching the EC times to boots: each mainline boot’s reboot comes its uptime plus ~9 s of firmware later (-8: 174.8 s uptime, 184 s; -9: 189.5 s, 198 s; -11: 129.5 s, 138 s).

  • Result: SCMI worked in both cold boots and failed in every warm reset from running stock Android, with and without the panel patch (-11 used the plain pin, GBOOK_SKIP_PATCHES=1). The same image failed warm (-11) and worked cold (-12).
  • Inference: stock Android’s kernel leaves the CPUCP in a state that survives a warm reset (the stock tree drives it as qcom,cpucp-v2 with a vendor protocol@80), and in that state it answers mainline’s SCMI late or not at all. How the stock kernel uses it is not traced. With two cold and two logged warm boots, this is a strong pattern, not proof.
  • The failures grew worse from -9 to -11. Inference: each failed boot may leave the firmware in a worse state for the next warm reset (the research note’s quirk hazard, scmi-cpucp-timeouts.md).

Procedure from now on: power stock off fully before each attempt. attempt.sh verify prints that step, attempt.sh watch shows the EC’s G3->S5 line for a cold start, and /init reports the firmware version as the scmi-fw key so compare flags a warm-reset boot. arm,max-rx-timeout-ms is not needed for cold boots; it stays an option if a warm boot must work (for example after a crash reboot).

Update: cold starts fail too (2026-11-03, 5:59 PM ET)

Three later boots each followed an EC G3->S5 (a cold start; EC uptime 15984, 17025 and 17236 s in dumps/firstboot/2026-11-03-ec.log), with the EC work’s images (ec-2026-11-03.md):

BootSCMIFirmware versioncpufreq
2026-11-03-13probe with driver arm-scmi failed with error -110nonenone
repeat of -13timed out in resp, many times from 0.43 s0x0none
2026-11-03-14one timed out in resp at 0.418 s, then bound0x20000all three policies

So a cold start does not always bring SCMI up: the “cold boot only” rule above holds for the warm side (no warm reset has worked) but not as a guarantee on the cold side. Open lead 2 in the handoff (SCMI after a warm reset) widens to SCMI in general; docs/research/scmi-cpucp-timeouts.md ranks arm,max-rx-timeout-ms first.

Update: the 250 ms window and the trace (2026-11-03, about 8 PM ET)

arm,max-rx-timeout-ms = <250> (commit d555a07, unverified diagnostic) took effect on both boots (SCMI max-rx-timeout: 250ms).

  • 2026-11-03-20 (cold; the EC itself reset at power-on): xfers_response_timeout=7 of sent_ok=78 (response_ok=74, err_protocol=1), firmware 0x0, no quirk_perf_level_get_fc_force (the hazard path, scmi-cpucp-timeouts.md section 4), cpufreq on 2 of 3 clusters.
  • 2026-11-03-21 (cold, tracing on): firmware 0x20000, the FC quirk, 3 of 3 clusters, 0 timeouts; the trace’s scmi_xfer_begin to scmi_rx_done latency over 78 replies peaked at 4.0 ms.
  • So when CPUCP answers, it answers within milliseconds; on a bad boot some messages get no reply even in 250 ms. A longer window does not help (inference from 2 boots); the next lever is why CPUCP stops answering (research options 3-4: the guard, arm,no-completion-irq, cpuidle.off=1).

Update: reply latency per message, attempt -22 (2026-11-03, 8:14 PM ET)

Cold start, 250 ms window, tracing on: firmware 0x20000, the FC quirk, 3 of 3 clusters, and 4 timeouts. The trace (scmi_xfer_begin to scmi_rx_done, netlog “trace at probe”):

  • 67 of 75 replies in under 1 ms.
  • The base protocol’s discovery messages (pt=10, msg_id 01 to 06) took 23 to 36 ms each, one after another. msg_id 05 (implementation version, the “Firmware version” line) took 36.0 ms. With the driver’s default 30 ms window that reply is missed, the version stays 0x0 and the FC quirk does not match (inference: it fits every bad boot’s timed out in resp lines about 36 ms apart and Firmware version 0x0).
  • Slower still: base msg_id 07 at 222.4 ms, perf (pt=13) msg_id 02 at 155.2 ms and 0B at 67.9 ms.
  • Timed out at 250 ms: base msg_id 06, perf msg_id 04 and 02 (twice).
  • So the window matters: 250 ms turned this boot good where 30 ms would have failed it. -20 still failed at 250 ms, so some replies take longer or never come. Two good boots (-21, -22) also both had tracing on; that it shifts the timing is unproven (inference).

Update: 1000 ms window, attempts -23 to -25 (2026-11-03, 8:31 PM ET)

arm,max-rx-timeout-ms = <1000> (commit after 927480c), tracing on, three cold starts of one image:

AttemptResultTimeoutsSlow replies (trace)
-23pass: 0x20000, FC quirk, 3 of 31base 03 628 ms, perf 09 872 ms, perf 04 1014 ms (the timeout)
-24pass1base 04 884 ms, perf 02 940 ms, 0B 950 ms, 04 830/950 ms; perf 09 timed out
-25pass0none: 78 replies, all within 3.6 ms
  • Since the window went to 250 ms and tracing on: 5 good cold starts in a row (-21 to -25), against 2 of 7 before with the default 30 ms (-20 at 250 ms failed).
  • Reply time varies by boot: some boots answer everything within milliseconds (-21, -25), others have a tail of single messages at 0.6 to 1 s. Why CPUCP stalls is still open; the window only absorbs it.
  • Not a fix: the property is local and unverified (no stock counterpart), and a boot can still lose a message past 1 s (-23’s perf 04 at 1014 ms). It costs nothing on a fast boot.

Update: cluster_cl5 A/B, attempt -27 (2026-11-03, 9:50 PM ET)

Attempt 2026-11-03-27: kernel #17 (as -26), 1000 ms window, tracing on, plus the diagnostic fragment dts/diag/hamoa-no-cluster-cl5.dtsi (commit 5aa73de): domain-idle-states = <&cluster_cl4> on cluster_pd0-2, so the three CPU clusters never enter cluster_cl5. Source: Jens Glathe’s “arm64: dts: qcom: hamoa: Drop cluster_cl5 idle state from CPU clusters” (https://lore.kernel.org/all/20260604-dc_zva_mitigation-v1-1-d1148c1c0259@oldschoolsolutions.biz/), carried by Omarchy as linux-omarchy-aarch64 patch 2151. Image a563a107aeedf282, DTB 4fa8ee79b09d1ea9; three cold starts of one image (stock powered off, EC G3 before each):

BootResultTimeoutsMax replySlow replies (trace, >= 100 ms)
1pass: 0x20000, FC quirk, 3 of 30943.7 msbase DISCOVER_SUB_VENDOR 111.8 ms, DISCOVER_LIST_PROTOCOLS 496.0 ms, perf DESCRIBE_LEVELS 943.7 ms
2pass03.9 msnone (the AP console’s scmi-latency= line; this boot’s netlog did not arrive)
3pass0812.0 msbase DISCOVER_VENDOR 812.0 ms; perf DESCRIBE_LEVELS 507.2/433.7/222.4 ms, PROTOCOL_MESSAGE_ATTRIBUTES 442.0/362.0 ms, NOTIFY_LIMITS 795.2 ms (SCMI probe 0.37 s to 4.11 s)
  • Against the same 1000 ms window with cluster_cl5 (-23 to -26): 2 of 4 boots stalled (-23, -24), 2 answered within 4 ms (-25, -26). Without cluster_cl5: 2 of 3 stalled. No improvement: the A/B failed. The stalls do not come from the cluster_cl5 idle state (3 boots; a small sample, but the worst probe of the day, boot 3, ran without it).
  • All three boots came up fully, so the 1000 ms window keeps absorbing the stalls; 0 timeouts here.
  • The stalls fall between 0.37 s and 4.1 s of kernel time, during the SCMI probe and before /init runs, so they do not depend on anything done at the keyboard.
  • The fragment is deleted (its text is in commit 5aa73de), per dts/diag/README.md. Open: the other idle-state lever in scmi-cpucp-timeouts.md (cpuidle.off=1, which also covers the CPU idle states the fragment left alone).

Update: cpuidle.off=1 A/B, attempt -28 (2026-11-03, 10:02 PM ET)

Attempt 2026-11-03-28: kernel #17, the board DTB as -26 (f30182513670c4e3, no dts/diag), 1000 ms window, tracing on, plus GBOOK_CMDLINE_EXTRA=cpuidle.off=1 (cpuidle_register_driver() returns -ENODEV while off is set, drivers/cpuidle/driver.c:225 at a804c57, so the idle loop falls back to plain WFI and no PSCI idle state is requested: not the CPU ret state, the cluster states or domain_ss3 on system_pd; the last part is inference from the PSCI cpuidle driver being the only requester). Image 21db583f38e653a3; three cold starts of one image (stock powered off, EC G3 before each):

BootResultTimeoutsMax replyReplies under 1 ms
1pass: 0x20000, FC quirk, 3 of 300.1 ms78 of 78
2pass00.1 ms78 of 78
3pass00.1 ms78 of 78

The trace’s only flagged rows are base DISCOVER_SUB_VENDOR and DISCOVER_AGENT with s=-13 (not supported, 0.0 ms), as on every boot; the SCMI probe ends by 0.38 s.

  • With idle states on (1000 ms window, -23 to -27): 4 of 7 cold boots stalled (-23, -24, -27 boots 1 and 3; replies of 0.5 to 1 s), and even the clean ones peaked at 3.6 to 4.0 ms (-21, -25, -26, -27 boot 2). With cpuidle.off=1: 0 of 3 stalled and every reply within 0.1 ms, a floor 40 times lower.
  • So the CPUCP’s slow replies follow the AP’s idle states. That matches Sibi Sankar’s statement that the CPUCP firmware bug “happens when idle states are enabled” (scmi-cpucp-timeouts.md section 4). Three boots is a small sample: with 4 of 7 stalling, three clean boots in a row by chance is about 8 % (inference).
  • cluster_cl5 alone is ruled out (-27). Left are the CPU ret state (cluster_c4), cluster_cl4 and domain_ss3; the stock tree has the same four states and topology (entry00-base.dts: cluster_pd0-2 with cl4 and cl5, system_pd with system_ss3), so stock Android boots with them (inference: they are not wrong for this board).
  • Not a config to keep: cpuidle.off=1 costs idle power. It is a diagnostic; the 1000 ms window stays.

Update: what the trace cannot tell (2026-11-03, 10:20 PM ET)

The latency in the tables above runs from scmi_xfer_begin to scmi_rx_done, and rx_done is traced in the mailbox interrupt’s handler (driver.c:1164 at a804c57), so it cannot separate a late CPUCP reply from an on-time reply whose interrupt woke an idle AP CPU late. “The slow replies follow the AP’s idle states” (-28) holds either way; which side is slow is open. Sibi Sankar’s “idle states” statement is about PERF_LEVEL_GET hard resets, not reply latency, so it does not decide this (inference). The discriminator is arm,no-completion-irq with idle states on (dts/diag/scmi-no-completion-irq.dtsi): polling skips the interrupt; trace_report.py times a polled reply from begin to end. Stock wires the mailbox the same way (qcom,cpucp-v2 at 0x17430000/0x18830000, GIC SPI 28 level-high, no PDC; SCMI channels 0 and 2, no arm,max-rx-timeout-ms), with a downstream mailbox driver.

Update: arm,no-completion-irq A/B, attempt -29 (2026-11-03, 10:24 PM ET)

Attempt 2026-11-03-29: kernel #17, 1000 ms window, tracing on, idle states on, plus dts/diag/scmi-no-completion-irq.dtsi (polling for every SCMI reply; dmesg: Enabled polling mode TX channel). Image aa0661198276109c; three cold starts of one image (EC G3 before each):

BootResultTimeoutsPolled repliesMax replyapss_cpucp_mbox IRQs
1pass: 0x20000, FC quirk, 3 of 3078 of 780.1 ms0
2pass078 of 780.1 ms0
3pass078 of 780.1 ms0

(trace_report.py times a polled reply from scmi_xfer_begin to scmi_xfer_end; the two s=-13 replies count as replies after a fix the same night, so /init’s on-device line on this image still says 76.)

  • The CPUCP is not slow. With idle states on, it frees the shared-memory channel within 0.1 ms of every request on all three boots, where 4 of 7 interrupt-mode boots stalled for 0.5 to 1 s. The slow part is the completion interrupt.
  • The IRQ count is 0 because a polled message asks for no interrupt (shmem.c:123: the channel’s flag SCMI_SHMEM_FLAG_INTR_ENABLED is written as 0 when poll_completion is set). In interrupt mode every boot of the day counted one apss_cpucp_mbox IRQ per reply (75 to 78), all on CPU 0 (GBOOK: irq-counts=, netlog), stalled boots included: the interrupts arrive, late.
  • Which half is late, the CPUCP raising it or CPU 0 waking for it, is still open. A lead (inference): the stall ceiling tracks the rx window (timeouts about 36 ms apart at 30 ms, a 222 ms reply at 250 ms, 940 to 1014 ms at 1000 ms), which fits a CPU 0 that sleeps through the interrupt until its own timeout timer wakes it, then takes the pending interrupt just before or just after the timeout. GIC SPI 28 has no PDC pin (qcom,pdc-ranges in hamoa.dtsi:6028 maps SPIs 251-255, 480-573, 609-640, 717-728, 816-834), so it cannot wake the SoC from a state that needs the PDC; whether domain_ss3 on system_pd is such a state is unverified.
  • The fragment stays (status: proven as a diagnostic) until the owner decides between polling, the window, or a narrower idle-state change.

Update: what -29 shows and does not (2026-11-03, 10:28 PM ET)

Polling keeps one AP core spinning through the whole SCMI probe, and cpuidle.off=1 (-28) keeps every core out of the PSCI states, so both fast cases share one property: the SoC never reaches its deepest state during the probe. -29 shows that the CPUCP answers within 0.1 ms while one AP core is awake; whether it also does when all cores sleep is untested. Two readings fit: CPU 0 sleeps through an on-time IRQ (SPI 28 has no PDC pin) until a timer wakes it, or the CPUCP defers its reply while the AP is in a deep state. The window-tracking lead favours the first (inference). Next A/B: domain_ss3 dropped from system_pd alone (dts/diag/hamoa-no-system-ss3.dtsi).

Update: domain_ss3 A/B, attempt -30 (2026-11-03, 10:34 PM ET)

Attempt 2026-11-03-30: kernel #17, 1000 ms window, interrupt mode, CPU and cluster idle states as stock, plus dts/diag/hamoa-no-system-ss3.dtsi (system_pd without domain-idle-states; the built DTB’s /psci/power-domain-system has none). Image 8296ea098da5e5b3. One cold start (EC G3 before it):

  • Pass (0x20000, FC quirk, cpufreq 3 of 3), but the worst SCMI boot of the day: 1 timeout (perf DESCRIBE_LEVELS, 1012.1 ms, s=-110), base DISCOVER_AGENT 587.0 ms; 76 replies, 76 apss_cpucp_mbox IRQs, all on CPU 0.
  • Failed: dropping the system-level state does not stop the stalls. One stalled boot settles it, so boots 2 and 3 were skipped. With -27, the cluster cl5 state and the system ss3 state are each ruled out alone; left are the CPU ret state and cluster_cl4, or a combination.
  • Also on this boot, unexplained: the touchscreen probe failed with -110 at 12.8 s (usually -22), with GENI I2C errors on a80000.i2c: 4 transaction timeouts, plus NACK, arbitration-lost and bus-protocol errors (attempt.sh trace i2c touchscreen). The NACK and arbitration errors point at the bus or the device, not at interrupt delivery (inference); see touch-tpm-2026-11-03.md.
  • The fragment is deleted (its text is in commit 9fa5a2d), per dts/diag/README.md.

Update: the wake trace, attempts -31 and -32 (2026-11-03, 11:00 PM ET)

Board DTB, interrupt mode, idle states on, plus trace_event=irq:irq_handler_entry,power:cpu_idle,timer:hrtimer_expire_entry (debug-tools.md, “the wake trace”). /init keeps these events for CPU 0 only.

  • -31 (image 053a4bced558aa41): the default 1.4 MB per-CPU ring wrapped before the probe snapshot and lost the SCMI probe (replies=0). The boot itself stalled (SCMI 1.26 s to 2.30 s), 0 timeouts.
  • -32 (image 5e565f64ce0e9d30, trace_buf_size=8M): the ring still wrapped (2061301 of 2260937 events kept) but kept all 78 SCMI replies. One slow reply: perf PROTOCOL_MESSAGE_ATTRIBUTES, 534.3 ms (request at 0.434542 s, reply interrupt at 0.968882 s); 0 timeouts. Cold start after an EC reset (Reset cause: sysjump hard efs).

What CPU 0 did during those 534 ms (attempt.sh trace wake, and the raw netlog lines):

  • It entered idle state=1 (cpuidle index 1, the PSCI ret state cluster_c4) and left it 26694 times, once every ~20 µs, and took only 3 interrupts: 2 IPIs and 1 arch_timer. Per 100 ms bin: about 4960-5000 idle exits and 0-6 interrupts during the stall; in normal periods (0.0-0.4 s, 2.4-4.0 s) the exits roughly match the interrupts.
  • The stall ended with the arch_timer interrupt at 0.968876 s; it expired four pm_suspend_timer_fn hrtimers, and 6 µs later the apss_cpucp_mbox interrupt ran and the reply was read.
  • The same 20 µs loop with no interrupts covers 1.2 to 2.0 s, when no SCMI message was pending: the loop is not caused by the SCMI request (inference from one boot).

Reading (inference, not proven): CPU 0’s ret entries return early every 20 µs without the kernel taking any interrupt, so something wakes the core that the GIC does not deliver as an interrupt, or delivers only after another interrupt is acknowledged. With cpuidle.off=1 (-28, plain WFI) the replies take 0.1 ms. The other CPUs were not traced (the capture keeps CPU 0 only), so whether they were idle is unknown. Neither tree marks an idle state local-timer-stop, and both have the memory-mapped timer (arm,armv7-timer-mem), so a missing tick broadcast is not the explanation.

Update: precision on the -32 loop (2026-11-03, 11:08 PM ET)

  • state=1 is not only the ret state: its enter callback is psci_enter_domain_idle_state (cpuidle-psci.c:271 at a804c57), which can escalate to a cluster or system domain state when CPU 0 is the last CPU of its domain. The cpu_idle event cannot tell which was requested; power:psci_domain_idle_enter/_exit (power.h:89, :96) can.
  • Each cycle spends ~20 µs inside the state and ~0 µs outside: a PSCI call that returns without holding the state. Two readings fit: the firmware refuses (or immediately ends) the state while an SCMI command is in flight, or something wakes the core that the kernel does not see as an interrupt. Not decided.
  • Two IPIs ran on CPU 0 mid-stall (0.502444 s, 0.538444 s) and the mailbox interrupt still did not follow them: SGIs reached CPU 0 while the SCMI reply’s SPI did not (or was not yet raised).
  • No I2C or GENI events fall in 0.43-2.1 s on -32, so this boot cannot say whether other SPIs stall the same way.
  • Next capture (one boot, no fragment): cpuidle/state*/{usage,rejected,time} per CPU (rejected exists, drivers/cpuidle/sysfs.c:266), pm_genpd_summary (CONFIG_DEBUG_FS=y, CONFIG_PM_GENERIC_DOMAINS=y in the build), the PSCI domain tracepoints, and per-CPU idle-exit counts over the SCMI window.

Update: idle counters, attempt -33 (2026-11-03, 11:21 PM ET)

Attempt 2026-11-03-33 (image fb75f74a4a79d29e, commit 68667c5): as -32 plus power:psci_domain_idle_enter/exit and the /init idle counters, read at the probe snapshot (cumulative since boot, about 95 s in by the genpd times). Cold start after an EC reset. SCMI stalled: firmware version at 1.03 s, a timeout at 4.11 s, perf domains at 4.11 s. The trace ring wrapped (2185344 of 3404814 events kept) and lost the SCMI lines, so the per-window counts are missing (idle-window=no SCMI window); the cumulative counters are complete.

cpuidle/state1 (cpu-sleep-0, the deepest cpuidle state, whose enter can request a domain state):

CPUsusagerejected
cpu021849837
cpu1, cpu2, cpu33022, 2178, 173045, 28, 27
cpu4-cpu7 (cluster 1)983, 1465, 2215, 136022563, 44568, 89639, 94845
cpu8-cpu11 (cluster 2)1636, 1075, 1273, 1291266040, 20354, 23072, 37093

genpd idle_states (debugfs; Usage, Rejected, Above):

  • power-domain-system S0 (domain_ss3): usage 2402, rejected 598159, above 599516.
  • power-domain-cpu-cluster1 S1 (cl5): usage 252401, rejected 7, above 251683; cluster2 S1: 347327, 4, 346622.
  • power-domain-cpu-cluster0 S0/S1: usage 1593/3565, rejected 74/18.

What the counters mean at a804c57: cpuidle’s rejected counts an enter that returned an error, and a PSCI domain state’s genpd rejected counts a CPU_SUSPEND for that composite state that returned an error (pm_genpd_inc_rejected(), cpuidle-psci.c:102; core.c:816). genpd’s above counts a stay shorter than the state’s latency plus residency (genpd_reflect_residency(), core.c:331).

  • The firmware refused the system state about 598000 times. The refusals sit on the last CPU of clusters 1 and 2 (cpu4-11’s cpuidle rejections add up to 598174, matching system_pd’s 598159): each time one of them is the last CPU awake, it asks for ss3, PSCI returns an error at once, and it tries again.
  • CPU 0 is not refused (37): it enters state1 and is woken, 218498 times, the 20 µs loop of -32.
  • Clusters 1 and 2 entered cl5 about 250000 and 350000 times, almost all shorter than its residency.
  • Reading (inference): a storm of refused system-state requests and early wakes runs while the SoC should be idle. A busy CPU (polling, -29) or no PSCI idle (-28) never gets all CPUs idle, so no storm, and the replies take 0.1 ms. That the storm delays the CPUCP’s completion interrupt is not shown. The counters are cumulative to about 95 s, so they do not prove the storm runs during the SCMI stall; -30 (no ss3) still stalled, with no counters to show what ran.

Update: precision on -33 (2026-11-03, 11:30 PM ET)

  • The refused CPUs are not the stalled one. The mailbox interrupt lands on CPU 0, and CPU 0 is entered-and-woken (state1 usage 218498, rejected 37); the refusals are on clusters 1 and 2 asking for ss3. Two coupled loops, causality unknown either way: CPU 0 bouncing could be why the firmware refuses ss3 (cluster 0 is not down), or the refusals could be what wakes CPU 0.
  • The counters are cumulative over about 95 s; -32 showed CPU 0’s loop at about 50000 exits/s during stalls against 1000-6000/s otherwise, so the cumulative numbers understate the rate inside a stall.
  • Mode: psci: OSI mode supported and CPUidle PSCI: Initialized CPU PM domain topology using OSI mode (-33 dmesg).
  • Ruled out as a link: the stock tree’s four qcom,cpucp-enabled flags sit in qcom,memlat monitors (the memory latency governor), not in an idle-state node. The stock psci node matches mainline’s (arm,psci-1.0, smc).
  • Not yet tested: no composite requests at all (domain-idle-states removed from cluster_pd0-2 and system_pd, CPU ret kept). -27 removed only cl5, -30 only ss3.

Decisions (2026-11-03, 11:27 PM ET, owner, grilling round)

  1. One more boot before choosing the fix: every PSCI domain idle state removed (dts/diag/hamoa-no-domain-idle.dtsi), CPU ret kept, interrupt mode, idle counters read early and at the snapshot.
  2. The board DTS keeps arm,max-rx-timeout-ms = <1000> until the cause is known; polling stays a dts/diag fragment (it would hide the stall from every later trace).
  3. Nothing goes to a mailing list until the cause is known and a fix is tested on mica; then one report to linux-arm-msm (SCMI and PSCI maintainers on Cc) with the numbers, the cause and a patch, after the owner reviews it.
  4. Time box: this boot plus at most one follow-up, then the next handoff item (the never-bound devices, then the touchscreen).

Update: no PSCI domain idle states, attempt -34 (2026-11-03, 11:41 PM ET)

Attempt 2026-11-03-34 (image 30354ef5b4fcf76c, commit d458b08): kernel #17, interrupt mode, 1000 ms window, dts/diag/hamoa-no-domain-idle.dtsi (no domain-idle-states on cluster_pd0-2 or system_pd; the 12 per-CPU domains keep cluster_c4, the CPU ret state). Three cold starts of one image (EC G3 before each):

BootResultTimeoutsMax replycpuidle rejected (all CPUs)CPU 0 state1 usage, early / snapshot
1pass: 0x20000, FC quirk, 3 of 304.0 ms019190 at 7.04 s / 72367 at 78.71 s
2pass01.4 ms014284 at 6.96 s / 97553 at 92.82 s
3pass03.6 ms04725 at 6.84 s / 53456 at 78.63 s
  • No stall on any boot, and no PSCI refusal anywhere. With domain idle states present, 8 of 11 interrupt-mode cold boots stalled (-23, -24, -27 boots 1 and 3, -30, -31, -32, -33); three clean boots in a row by chance at that rate is about 6 % (inference). Removing cl5 alone (-27) or ss3 alone (-30) was not enough; removing every composite state is.
  • CPU 0 entered state1 about 700 to 2700 times a second up to the early read, against about 50000 a second inside -32’s stall: no loop.
  • power-domain-system’s default state still counts rejected (23796, 38188, 40386 at the snapshot). These are vetoes from the RPMh controller’s genpd notifier, which refuses GENPD_NOTIFY_PRE_OFF while it is busy or cannot flush (rpmh_rsc_pd_callback(), rpmh-rsc.c:949-951; counted at core.c:1014): kernel-side, no PSCI call.
  • Reading (inference): the stall needs the composite cluster or system states that PSCI refuses in OSI mode (-33); why the firmware refuses them, and how that delays the CPUCP’s interrupt to CPU 0, is still open. What the change costs: the clusters and the SoC never power down when idle (more idle power; suspend is untested either way).

Update: ADR 0008 is not enough, attempts 2026-11-04-1 and -2 (2026-11-04, 12:26 AM ET)

Two more cold boots on the ADR 0008 board DTS (no PSCI domain idle states), kernel #18 then #19 (the second adds only kernel/patches/0008, a GENI I2C timing test for the touchscreen): both stalled.

Boot (all without domain idle states)Max replyTimeoutsCPU 0 state1 usage at the early readcpuidle rejected
-34 boot 14.0 ms019190 at 7.04 s0
-34 boot 21.4 ms014284 at 6.96 s0
-34 boot 33.6 ms04725 at 6.84 s0
-353.9 ms07775 at 6.92 s0
2026-11-04-11019.2 ms2155711 at 9.94 s0
2026-11-04-2652.0 ms (base DISCOVER_IMPLEMENT_VERSION)0132524 at 9.52 s0
  • 2 of 6 stalled, so removing the domain idle states lowers the rate (8 of 11 before) but does not stop it. -34’s 3 of 3 was the lucky case. The 2026-11-04-1 stall was missed live: the touch monitor was not watching SCMI.
  • The stall goes with CPU 0’s state1 loop, not with PSCI refusals. Stalled boots reach 130000-156000 CPU 0 state1 entries by the early read, against 4700-19000 on clean ones (the early read also comes later, because the stall delays the boot). Every CPU’s rejected is 0, and no composite state exists. -33’s refusal storm ran alongside the stall, not as its cause (inference).
  • What is left fits all the data (inference): CPU 0 enters the PSCI ret state (cluster_c4, 0x4), wakes within ~20 µs again and again, and the SCMI completion interrupt is not taken until another interrupt runs (-32). Plain WFI (cpuidle.off=1, -28) and a busy CPU (polling, -29) never stalled (0 of 3 each).
  • ADR 0008 gets a dated update; the board DTS change stays until the owner decides.

Update: precision and two dead ends for the next test (2026-11-04, 12:31 AM ET)

  • The rate claim was too strong. 2 of 6 stalled boots without domain idle states, against 8 of 11 with them, is not a significant difference at these sample sizes (one-sided p about 0.14, inference). Read it as “may reduce the rate; the sample cannot tell”, not as “cut it to 2 of 6”.
  • The window held on the 2-timeout boot. 2026-11-04-1 (2 timeouts, max 1019.2 ms) still came up fully: firmware 0x20000, quirk_perf_level_get_fc_force, cpufreq on all three clusters (netlog, boot 2 of 3 in 2026-11-04-netlog.txt).
  • When the loop starts (-32): the request left at 0.434542 s and CPU 0’s first loop exit came at 0.434606 s, 64 µs later, about when the reply’s interrupt would be asserted (polling replies take ~0.1 ms, -29). Mid-loop CPU 0 took two SGIs (IPIs) but not the SPI, and a PPI (arch_timer) released it. Reading (inference): a pending SPI for CPU 0 wakes the core from ret but is not delivered to its CPU interface; the core re-enters, is woken again every ~20 µs, until a PPI runs. It also fits the 1.2-2.0 s loop with no SCMI pending (another SPI) and -30’s GENI I2C -110 timeouts (that controller’s interrupt is an SPI on CPU 0 too).
  • irqaffinity= cannot move SPI 28 off CPU 0: irq_affinity_setup() always adds the boot CPU to the mask (kernel/irq/irqdesc.c:36 at a804c57).
  • CPU 0 alone cannot lose ret by DT: with no state for one CPU, psci_idle_init_cpu() returns -ENODEV (cpuidle-psci.c:399-401) and psci_cpuidle_probe() fails the whole driver (:437-447), which is cpuidle.off=1 for every CPU. Isolating CPU 0 or the interrupt needs a kernel patch.

Update: the stall follows the interrupt’s CPU, attempt 2026-11-04-3 (2026-11-04, 12:44 AM ET)

Attempt 2026-11-04-3: kernel #20 (kernel/patches/0009, commit cee735d), the ADR 0008 board DTS, interrupt mode, 1000 ms window. The patch calls irq_set_affinity(irq, cpumask_of(1)) in the CPUCP mailbox probe; dmesg shows qcom_cpucp_mbox 17430000.mailbox: gbook: irq 15 affinity to CPU 1: 0 at 0.226519 s. One cold start (EC G3 first).

  • All 78 mailbox interrupts ran on CPU 1: irq-counts= 15: 0 78 0 0 0 0 0 0 0 0 0 0 GICv3 60 Level apss_cpucp_mbox.
  • SCMI stalled: max reply 872.0 ms, 0 timeouts, cpufreq on all three clusters.
  • The loop moved with it. At the early read (9.63 s), CPU 1’s state1 count was 134862. CPU 0 had 6131 and every other CPU had 79 to 471.
  • So CPU 0 is not special. The core that takes the reply’s SPI loops in and out of PSCI ret without taking it, wherever the SPI is routed (one stalled boot; it moved exactly as the reading predicted). With -28 (plain WFI, 0 of 3 stalled) and -29 (polling, 0 of 3), the mechanism reads (inference): on mica, a core in the PSCI ret state (cluster_c4, arm,psci-suspend-param 0x4) is woken by a pending SPI but does not take it until a PPI runs. That fits the other SPIs seen stalling too (-30’s GENI I2C timeouts, -32’s 1.2-2.0 s loop with no SCMI pending).
  • kernel/patches/0009 stays until the owner decides the fix, then goes (a diagnostic, ADR 0007).

Update: ADR 0009 confirmed, attempt 2026-11-04-4 (2026-11-04, 12:57 AM ET)

Attempt 2026-11-04-4: kernel #21 (patches 0001-0007, no diagnostics), the board DTS with ADR 0009 (cluster_c4 deleted from cpu_pd0-11; cluster and system states as stock), interrupt mode, 1000 ms window. Image 161b32cde0296c3c, commit 1f67420. One cold start.

  • No cpuidle driver: every CPU’s cpuidle-early= and cpuidle= line is empty (/sys/devices/system/cpu/cpuN/ cpuidle/ has no states), so cores idle in WFI. The PSCI domain driver still reports CPUidle PSCI: Initialized CPU PM domain topology using OSI mode. That is cpuidle-psci-domain, not the cpuidle driver.
  • SCMI clean: 78 replies, max 0.1 ms, 0 timeouts; the SCMI probe ran 0.376-0.391 s. Firmware 0x20000, FC quirk, cpufreq on all three clusters. All 78 mailbox interrupts were on CPU 0.
  • With -28 (cpuidle.off=1, 0 of 3), plain WFI is now 4 of 4 cold boots without a stall, every reply within 0.1 ms.

Update: the counts from attempt.sh tally (2026-11-04, 2:31 AM ET)

dumps/firstboot/attempts.tsv now carries machine-read boot health (retro 6), backfilled for -20 to 2026-11-04-4. The counts above were hand-made; the tally reads:

  • Domain idle states present, interrupt mode (-23 to -27, -30 to -33): 7 stalled of 10 known boots, plus -31 unknown (its trace ring lost the SCMI lines; its stall is from dmesg timing only). Against no PSCI cpuidle (-28, 2026-11-04-4): 0 of 4, one-sided Fisher p = 0.035. “8 of 11” above counted -31 as a stall.
  • ADR 0008’s DT (-34, -35, 2026-11-04-1 to -3): 3 of 7. Against no PSCI cpuidle: 0 of 4, p = 0.21, not significant on its own. ADR 0009 rests on the mechanism (the stall followed the SPI to CPU 1, 2026-11-04-3) together with the first comparison, not on the boot count against ADR 0008.
  • Polling (-29): 0 of 3. Commands: scripts/attempt.sh tally '^2026-11-03-(2[3-7]|3[0-3])$' '^2026-11-03-28$|^2026-11-04-4$' and scripts/attempt.sh tally '^2026-11-03-3[45]$|^2026-11-04-[123]$' '^2026-11-03-28$|^2026-11-04-4$'.

Update: the tally at ten boots without PSCI cpuidle (2026-11-04, 4:40 AM ET)

The suspend-test boots (2026-11-04-5 to -10, suspend-2026-11-04.md) all ran the no-PSCI-cpuidle board DT and probed SCMI before suspending. Every one was clean at 0.1 ms. With '^2026-11-03-28$|^2026-11-04-([4-9]|10)$' as group 2 in the two commands above, no PSCI cpuidle is 0 of 10 known boots. Against domain states present (7 of 10), the one-sided Fisher p = 0.0015. Against ADR 0008’s DT (3 of 7), p = 0.051, just above 0.05.

Rootfs from USB: the first two ROOT boots (2026-11-04)

Read 2026-11-04 (6:00 PM ET) from the AP console log, /init’s netlogs and the rootfs’s persistent journal (read from the stick’s partition 2 on the build host after boot 1). Attempts 2026-11-04-18 (warm start) and -19 (cold). Kernel #29 7.3.0-gbook-g82c43ed, KERN-A image 645d9f3dc3ab525c (15 tries), rootfs builds of 2026-11-04 (306, then 307 packages; snapshot=none: deb.debian.org and offline builds). MACs, SSIDs and serials redacted; no identifier is quoted.

What worked

  • /init found ROOT by kern_guid (root-lookup: the booted disk’s partition 2 is gbook-root), mounted it (init=/sbin/init), sent its netlog, and exec switch_rooted (GBOOK: switch_root=/dev/sda2), both boots.
  • systemd 262.1 booted Debian sid arm64 from the stick; the journal is persistent on ROOT.
  • Mesa on the GPU (M3 criterion): eglinfo -B: OpenGL 4.6 Core and Compatibility, vendor freedreno, renderer “Adreno X1-85”, Mesa 26.2.6-1; kmscube -D /dev/dri/card2 -c 120: exit 0, OpenGL ES 3.2, renderer “Adreno X1-85”; cage (-d): “EGL driver name: msm”, “GL renderer: Adreno X1-85”, GLES2 renderer on eDP-1 at 2560x1600, 120 Hz. /dev/dri/by-path: platform-3d00000.gpu-card -> card1, -render -> renderD128, platform-ae01000.display-controller-card -> card2 (msm.separate_gpu_kms=1).
  • Ghostty 1.4.0+gbook (the cross build) ran under cage as user gbook with a bash shell; the owner typed in it (which ghostty gave /usr/local/bin/ghostty). Boot 1’s m3-cage=inactive was an ordering bug (After=gbook-m3 held the unit until the capture script ended); boot 2 captured it active with the renderer line.
  • Wi-Fi in userspace (M3 criterion), boot 2: with 10-wlan0.link the interface kept its name; wpa_supplicant@wlan0 (reading /run/gbook/wpa_supplicant.conf) joined: Connected, 6135 MHz, -38 dBm, rx 1297 / tx 3843 Mbit/s 320 MHz EHT; networkd: “State: routable (configured)”, “Online state: online”, a DHCPv4 address via .
  • Audio state known (M3 criterion): aplay -l: “no soundcards found” (no sound node in the DTS; ticket 10).
  • Both boots powered down cleanly on the owner’s command (reboot: Power down, EC G3).

What failed, and the fix in the tree

BootFailureCause (evidence)Fix
1wpa_supplicant@wlan0 never started; wait-online timed out (120 s); gbook-m3 at 260 sudev: wlP4p1s0: renamed from wlan0 (console)/etc/systemd/network/10-wlan0.link keeps wlan0
1tmp, mqueue, hugepages, configfs mounts failed“Unable to locate executable ‘/usr/bin/mount’”: mount is not Essential in sidmount in rootfs/packages.txt
1m3-cage=inactiveAfter=gbook-m3.service on the cage unit (journal: started at 277.9 s, as gbook-m3 ended)dropped
1, 2m3-vulkaninfo empty, then “XDG_RUNTIME_DIR is invalid”, anv ICD noise, a display-plane errorthe service environment; every Mesa ICD installedXDG_RUNTIME_DIR=/run/gbook, VK_DRIVER_FILES = the freedreno ICD
2rootfs netlog “sent 1307 lines … exit=124”, nothing arrivednetworkd’s DUID got a second lease, /init’s udhcpc had the first; the build host’s ufw logged one block from the second to port 5140[DHCPv4] ClientIdentifier=mac
2m3-ping: “ping: not found”no iputils-pingadded
1, 2cage: “Cannot find Xwayland binary”not installed; harmless for Ghostty (Wayland)none

Timings

PhaseBoot 1Boot 2
/init’s sweep to switch_rootabout 138 sabout 138 s
switch_root to gbook-m3 start121 s (wait-online timeout)19 s
gbook-m3 start to end18 s18 s
Power-on to Ghosttyabout 280 sabout 180 s

The sweep is the next target (a fast path when ROOT is present), an owner decision.

Boot 3 (attempt 2026-11-04-20, cold; read 6:54 PM ET)

Rootfs round 3 (321 packages: mount, iputils-ping, openssh-server, sudo; DHCP client id = MAC; the cage unit no longer waits for gbook-m3) and a KERN-A image carrying the ssh keys in the hand-off (ADR 0012 update, decisions 5 and 8):

  • gbook-m3 at 156 s; the rootfs netlog arrived from the device’s first lease (sent 2175 lines ... exit=0), with a 1180-line redacted journal section; ping to the gateway 3 of 3; networkctl routable, online, lease .204.
  • ssh from the build host works (ssh mica, key-only, user gbook, one attempt): hostname = mica, sudo -n runs (warning “unable to resolve host mica”: no /etc/hosts in the apt variant, fixed in the tree); zero failed units.
  • Vulkan on Turnip works: over ssh, vkcube --wsi wayland --c 120 through cage’s socket: “Selected GPU 0: Adreno X1-85, type: IntegratedGpu”, exit 0; the device-select layer lists one device, 5143:43050c01 "Adreno X1-85". vulkaninfo --summary fails on vkGetPhysicalDeviceDisplayPlanePropertiesKHR (ERROR_OUT_OF_HOST_MEMORY) with the GPU-only card, a tool limitation; gbook-m3 captures vkcube instead from the next build. The first vulkaninfo attempt had an empty VK_DRIVER_FILES (the ICD is freedreno_icd.json, no architecture infix): fixed.
  • cage on the Adreno again (GL renderer: Adreno X1-85); m3-ssh=active listening 0.0.0.0:22.
  • The parked touchscreen failure printed as one KNOWN: line per segment (before and after GBOOK: begin).

Release boots (attempts 2026-11-04-21, -22, -23, all cold; read 8:37 PM EST)

The release rootfs, built from snapshot 20261104T000000Z (321 packages, rootfs.ext4 sha256 4b0e2e027c0b2d74…; the afternoon’s deb.debian.org build differed in six package versions), written to ROOT with attempt.sh root-cmd and read back block by block by root-verify (ROOT == rootfs.ext4, 1048576 blocks of 4 KiB); the KERN-A base image 9673d6d782ef4cdb unchanged. Each boot from G3 (the EC’s own reset, then G3->S5), with one ssh mica read:

  • attempt.sh tally '^2026-11-04-2[123]$': boots=3 known=3 stalled=0 clean=3, SCMI replies 0.1 ms, no polls.
  • /init switched to ROOT on each boot; gbook-m3 at 156, 155 and 156 s; m3-end at 177.5, 176.1 and 177.2 s.
  • kmscube, cage and vkcube on “Adreno X1-85” (Mesa 26.2.6-1) on all three; m3-ssh listening; the rootfs netlog arrived each time (2168, 2171 and 3498 lines).
  • Over ssh: hostname mica, getent hosts mica = 127.0.1.1 (the /etc/hosts fix is live), systemctl is-system-running = running, zero failed units, sudo -n runs, wlan0 .204, aplay -l no soundcards (unchanged). The journal’s only error-level lines are sudo’s pam_env “Unable to open env file: /etc/default/locale” from the build host’s ssh sessions and one pam_systemd Varlink ReleaseSession line at logout; harmless, noted.
  • Boot 3’s journal carried 658 i2c_hid_of 0-0010: i2c_hid_get_input: incomplete report lines within 0.1 s at 139.5 s (the switch_root moment), none on the AP console and none on boots 1 and 2: the parked touchscreen (touch-tpm-2026-11-03.md) bursting; no effect on the boot. The KNOWN: row’s pattern does not cover that message yet.
  • Powered off over ssh after each read (sudo systemctl poweroff: “reboot: Power down”, EC to G3), with the owner’s OK.

Fast sweep (attempt 2026-11-04-24, cold; read 9:21 PM EST)

Task #24: /init takes a fast sweep when it finds ROOT (GBOOK: sweep=fast: ROOT found, about 10 s): every instant sysfs read, the Wi-Fi join and the netlog stay; the settles, the owner prompts, the backlight cycle, the standalone scan, the display wait and the *-dmesg greps go. gbook.sweep=full, gbook.suspend=<mode>, a dp-wait image or a stick without ROOT keep the M2 sweep (owner’s four decisions, before the stick write at 9:07 PM EST; .scratch/m3-rootfs/issues/14-fast-sweep.md). Kernel #29, image 6f921ebb, ROOT rewritten first (root-verify found the release boots’ r/w mounts: 4675 blocks differed, journal 1745; after root-cmd: equal).

  • attempt.sh tally '^2026-11-04-24$': boots=1 clean=1 stalled=0, start cold; trace_report.py: replies=78, max 0.1 ms, timeouts 0 (attempt.sh trace scmi itself gave up: its line-count check also counts the rootfs’s later transfer; retro-10 ticket 07).
  • Timeline (AP console): begin 6.9 s; Wi-Fi joined at 28 s (link wait 17 s, no scan); gpu-open=opened wait=1s (GMU firmware loaded at 31.0 s); switch_root 32.9 s; systemd 34.3 s; wlan0: associated again at 51.4 s; m3-begin 53.0 s; kmscube on Adreno X1-85 at 56.1 s; cage and vkcube read at 73 to 74 s; m3-end 74.2 s (177 s on -23). The rootfs side is now the larger half: 20 s to m3-begin, 21 s of captures.
  • The rootfs side, read 9:31 PM EST from the journal and the AP console (41 s, switch_root 32.9 s to m3-end 74.2 s): systemd’s journald at 35.4 s, basic.target 37.0 s, wpa_supplicant@wlan0 started 37.0 s and connected at 51.4 s (its own scan: 14 s; /init’s association was dropped at switch_root), DHCP 51.5 s, IPv6LL and wait-online done 53.0 s, gbook-m3 from 53.0 s (eglinfo, vulkaninfo, kmscube, ping: to 58.1 s), gbook-cage.service started by gbook-m3 at 58.1 s (the unit is not enabled at boot), then its fixed sleep 15 to the m3-cage read at 73.1 s, vkcube and ssh 74.2 s. So Ghostty came up at about 59 s (cage start plus its own start; unmeasured on the panel), and 29 s of the 41 are the second Wi-Fi scan and the capture sleep. Task #26.
  • Over ssh: running, 0 failed units, no error-level journal lines; the rootfs netlog arrived (2181 lines).
  • Reads that differ from the full sweep by timing, not by state: ts-vreg disabled users=0 and gpio88 low at 32 s (the parked touchscreen’s supply; -23 read enabled users=1 at 105 s), no hid-over-i2c IRQ line for the touchscreen at 32 s, trace kept 586 lines (no touch window flooding the ring). The edp-reg line reads before the 33 s regulator cleanup; cage on the panel is the proof the panel survives it.
  • Powered off over ssh after the read (sudo systemctl poweroff, 9:27 PM EST), with the owner’s OK. Power-on to Ghostty on the panel: unmeasured (cage’s lines at 73 s are gbook-m3’s journal read, not when it drew).

The rootfs side (attempt 2026-11-04-25, cold; read 9:59 PM EST)

Ticket 15 (task #26, the owner’s five decisions, every recommendation taken): gbook-cage.service enabled at boot (WantedBy=multi-user.target, After=seatd.service, conditioned on the Ghostty binary), gbook-m3 skips kmscube when Ghostty is there (cage holds the panel’s KMS device; its GL renderer line is the GPU proof) and polls up to 15 s for cage’s socket and renderer line instead of sleep 15, and /init prepends freq_list=<MHz> from its own joined link to the network block it hands over (GBOOK: wifi-handoff=freq_list=6135; the wpa_supplicant reference config: outside a network block, freq_list limits the scan). Kernel #29, image 6a0cc711, rootfs cbd68763… (321 packages, snapshot 20261104T000000Z, lock_changed=0: the release’s packages, the files of ticket 15). attempt.sh tally '^2026-11-04-25$': boots=1 clean=1 stalled=0, start cold; SCMI replies=78 max=0.1ms timeouts=0.

Uptime2026-11-04-242026-11-04-25What (journal, sudo -n journalctl -b -o short-monotonic over ssh)
switch_root32.9 s33.1 s/init: wifi-link-wait=17s both (the first scan of a cold boot)
basic.target37.0 s36.9 sseatd, gbook-cage, wpa_supplicant@wlan0 and ssh start together
cage GL renderer: Adreno X1-85about 59 s37.8 sGhostty on the panel about 38 s (inferred from cage’s renderer line; its draw is untimed)
wpa_supplicant connected51.4 s (14.4 s after start)43.8 s (6.8 s after start)one channel scanned, not the band
DHCPv451.5 s43.9 slease .204 again
network-online53.0 s45.2 sright after Gained IPv6LL both boots (the 1.3 s tail; decision 4: left)
m3-begin53.0 s45.2 s
m3-cage read73.1 s (sleep 15)47.7 s (wait=0s)cage was up 10 s before gbook-m3 asked
m3-end74.2 s48.7 ssystemd-analyze: 33.075 s kernel + 16.029 s userspace

The rootfs side is 15.6 s now (41 s on -24): 25.5 s saved, 29 s expected (the second scan kept 6.8 s on one channel; a 6 GHz AP’s beacons and the supplicant’s own delays, inference). Over ssh: running, 0 failed units, 0 error-level journal lines; the rootfs netlog arrived (2167 lines). compare shows the new keys (wifi-handoff, m3-kmscube=skipped: cage holds the panel, m3-cage=... wait=0s uptime=) once as changed; ts-vreg read enabled users=1 and gpio88 high this time (timing, as on -24). The Xwayland “Cannot find” line is the known harmless one. Powered off over ssh after the read (sudo -n systemctl poweroff, 10:03 PM EST), with the owner’s OK: reboot: Power down, EC S5->G3, G3 at EC 385 s, then G3->S5 and S5 30 s later with no S5->S3: the EC’s soft-off on AC, the same sequence as after boot -24’s power-off (EC 3351 to 3381 s), not a power-on (a power-on continues S5->S3->S0 within a second, as at EC 0.5 s of this boot).

Open

  • release-draft.sh’s bar for M3: met by the release boots above (8:37 PM EST); the owner tags (ADR 0011).
  • The boot time: /init’s sweep is 138 s of the 176 s to Ghostty; a fast path when ROOT is present is an owner decision. 2026-11-04, 9:21 PM EST: done, the fast sweep (“Fast sweep” above): m3-end at 74 s. The rootfs side (20 s to m3-begin, 21 s of captures) is the next target. 2026-11-04, 9:59 PM EST: done, ticket 15 (“The rootfs side” above): m3-end at 48.7 s, Ghostty at about 38 s. Left: the wait-online tail after IPv6LL (1.3 s) and /init’s 17 s first scan (only a fixed scan_freq= in the owner’s wifi.conf shortens it).
  • Journal timestamps read 2026-10-16: no time source in the rootfs (the RTC is unset); harmless, noted.

Recon 2026-11-02

Collected 2026-11-02 20:22 EST by scripts/recon.sh: read-only, adb shell as uid 2000 over wireless debugging. Serials, boot ids and MACs are redacted. denied and absent are findings about what the stock OS exposes.

ItemStatusValue
ro.product.deviceokmica
ro.product.boardempty
ro.boot.hardwareokandroid-desktop
ro.bootloaderokdepthcharge-Google_Mica.16702.59.0/Google_Mica.16702.143.0
ro.boot.hwcempty
ro.build.fingerprintokDell/mica/mica:17/CL3B.260622.276.R2/16560795:user/release-keys
ro.boot.verifiedbootstateokorange
ro.boot.flash.lockedok0
ro.boot.vbmeta.device_stateokunlocked
ro.boot.slot_suffixok_a
ro.boot.dtbo_idxok0,1,2
ro.boot.hypervisor.protected_vm.supportedok1
kernelokLinux localhost 6.12.81-android16-6-g08dd7534bf44-ab16287118-4k #1 SMP PREEMPT Tue Sep 8 10:57:08 UTC 2026 aarch64 Toybox
/proc/cmdlineokconsole=ttynull stack_depot_disable=on cgroup_disable=pressure kasan.stacktrace=off kvm-arm.mode=protected bootconfig clk_ignore_unused pd_ignore_unused efi=noruntime efi=novamap android_arch_task_struct_size=512 id_aa64mmfr1.vh=0 arm64_sw.hvhe=0 irqaffinity=0-3 pcie_ports=compat cpufreq.default_governor=performance kvm-arm.ffa-unmap-on-lend=full,nr_handles=8192 kvm-arm.ffa_max_nr_constituents=16384 firmware_class.path=/vendor/firmware,/vendor/firmware_mnt/image console=null 8250.nr_uarts=4 console_msg_format=syslog log_buf_len=1M cgroup_disable=pressure root=/dev/ram0 reserve_mem=20M:2M:trace trace_instance=boot_mapped^traceoff@trace drm.trace=0x106 module_blacklist=cros_ec_debugfs proc_mem.force_override=ptrace binder.impl=rust no_console_suspend softlockup_panic=1 nmi_watchdog=panic pm.dpm_watchdog_enabled=1 cpufreq.default_governor=performance trace_instance=backup=boot_mapped cpuidle.governor=teo kvm-arm.protected_modules=qnoc-x1e80100.ko,pkvm-smc-filter.ko,rpmh-regulator.ko,clk-rpmh.ko,gcc-x1e80100.ko,gpucc-x1e80100.ko,pkvm-secure-memory-manager.ko,qcom_smmu_v2_v3_dispatcher.ko,pkvm_drng.ko bootconfig
/proc/cpuinfo (head)okprocessor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0x2
CPU part : 0x001
CPU revision : 1

processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
/sys/firmwareokdevicetree
fdt
log
vpd
/proc/device-tree/modelabsentcat: /proc/device-tree/model: No such file or directory
/proc/device-tree/compatibledenied/system/bin/sh: can’t open /proc/device-tree/compatible: Permission denied
/sys/firmware/devicetree/base/compatibledenied/system/bin/sh: can’t open /sys/firmware/devicetree/base/compatible: Permission denied
/sys/firmware/devicetree/base (names)ok#address-cells
#size-cells
symbols
aliases
chosen
cluster-device0
cluster-device1
cluster-device2
cluster-device3
compatible
cpus
firmware
fpmcu-vcc-regulator
interrupt-parent
lpddr-channel0
lpddr-channel1
lpddr-channel2
lpddr-channel3
lpddr-channel4
lpddr-channel5
lpddr-channel6
lpddr-channel7
memory
mmio-sram@18b4e000
name
pmu
psci
qcom,hdcp
regulator-dmic-en
regulator-pp3300-ucam
reserved-memory
rsc@af20000
soc
sram@c3f0000
sys-pm-vx@c320000
timer
touchpad-vdd-regulator
touchscreen-vdd-regulator
wlan-3p3-regulator
/sys/firmware/fdt (bytes)denied/system/bin/sh: can’t open /sys/firmware/fdt: Permission denied
/sys/firmware/log (bytes)denied/system/bin/sh: can’t open /sys/firmware/log: Permission denied
partition namesokKERN-A
KERN-B
ROOT-B
bluetooth_a
bluetooth_b
boot_a
boot_b
desktop_security_persist
desktop_security_storage
dsp_a
dsp_b
dtb_a
dtb_b
dtbo_a
dtbo_b
frp_persist
init_boot_a
init_boot_b
keymaster_a
keymaster_b
metadata
misc
modem_a
modem_b
nvme0n1
ota_recovery_a
ota_recovery_b
persist
pvmfw_a
pvmfw_b
rawdump
recovery_a
recovery_b
super
userdata
vbmeta_a
vbmeta_b
vendor_boot_a
vendor_boot_b
firmware tools on PATHokcrossystem: none
futility: none
gsctool: /system/bin/gsctool
ectool: /system/bin/ectool
vpd: none
flashrom: none
GSC firmware (gsctool -a -f)okstart
device: NT
keyids: RO 0xd4fd1f25, RW 0x405a3825
offsets: backup RO at 0, backup RW at 0x10000
Current versions:
RO 2.0.119
RW 0.33.291
CCD state (gsctool -a -I)okState: Locked
Password: None
Flags: 000000
Capabilities, current and default:
UartGscRxAPTx Y Always
UartGscTxAPRx Y Always
UartGscRxECTx Y Always
UartGscTxECRx - IfOpened
UartGscRxFpmcuTx Y Always
UartGscTxFpmcuRx - IfOpened
FlashAP - IfOpened
FlashEC - IfOpened
OverrideWP - IfOpened
RebootECAP - IfOpened
GscFullConsole - IfOpened
UnlockNoReboot Y Always
UnlockNoShortPP Y Always
OpenNoTPMWipe - IfOpened
OpenNoLongPP - IfOpened
RemoveBatteryBypassPP Y Always
I2C - IfOpened
FlashRead Y Always
OpenNoDevMode - IfOpened
OpenFromUSB - IfOpened
OverrideBatt - IfOpened
AllowUnverifiedRo - Never
CCD caps bitmap: 0x29817
Capabilities are default.
EC version (ectool version)deniedError: Permission denied (os error 13)

Recon 2026-11-02

Collected 2026-11-02 20:11 EST by scripts/recon.sh: read-only, adb shell as uid 2000 over wireless debugging. Serials, boot ids and MACs are redacted. denied and absent are findings about what the stock OS exposes.

ItemStatusValue
ro.product.deviceokmica
ro.product.boardempty
ro.boot.hardwareokandroid-desktop
ro.bootloaderokdepthcharge-Google_Mica.16702.59.0/Google_Mica.16702.143.0
ro.boot.hwcempty
ro.build.fingerprintokDell/mica/mica:17/CL3B.260622.276.R2/16560795:user/release-keys
ro.boot.verifiedbootstateokorange
ro.boot.flash.lockedok0
ro.boot.vbmeta.device_stateokunlocked
ro.boot.slot_suffixok_a
ro.boot.dtbo_idxok0,1,2
ro.boot.hypervisor.protected_vm.supportedok1
kernelokLinux localhost 6.12.81-android16-6-g08dd7534bf44-ab16287118-4k #1 SMP PREEMPT Tue Sep 8 10:57:08 UTC 2026 aarch64 Toybox
/proc/cmdlineokconsole=ttynull stack_depot_disable=on cgroup_disable=pressure kasan.stacktrace=off kvm-arm.mode=protected bootconfig clk_ignore_unused pd_ignore_unused efi=noruntime efi=novamap android_arch_task_struct_size=512 id_aa64mmfr1.vh=0 arm64_sw.hvhe=0 irqaffinity=0-3 pcie_ports=compat cpufreq.default_governor=performance kvm-arm.ffa-unmap-on-lend=full,nr_handles=8192 kvm-arm.ffa_max_nr_constituents=16384 firmware_class.path=/vendor/firmware,/vendor/firmware_mnt/image console=null 8250.nr_uarts=4 console_msg_format=syslog log_buf_len=1M cgroup_disable=pressure root=/dev/ram0 reserve_mem=20M:2M:trace trace_instance=boot_mapped^traceoff@trace drm.trace=0x106 module_blacklist=cros_ec_debugfs proc_mem.force_override=ptrace binder.impl=rust no_console_suspend softlockup_panic=1 nmi_watchdog=panic pm.dpm_watchdog_enabled=1 cpufreq.default_governor=performance trace_instance=backup=boot_mapped cpuidle.governor=teo kvm-arm.protected_modules=qnoc-x1e80100.ko,pkvm-smc-filter.ko,rpmh-regulator.ko,clk-rpmh.ko,gcc-x1e80100.ko,gpucc-x1e80100.ko,pkvm-secure-memory-manager.ko,qcom_smmu_v2_v3_dispatcher.ko,pkvm_drng.ko bootconfig
/proc/cpuinfo (head)okprocessor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0x2
CPU part : 0x001
CPU revision : 1

processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
/sys/firmwareokdevicetree
fdt
log
vpd
/proc/device-tree/modelabsentcat: /proc/device-tree/model: No such file or directory
/proc/device-tree/compatibledenied/system/bin/sh: can’t open /proc/device-tree/compatible: Permission denied
/sys/firmware/devicetree/base/compatibledenied/system/bin/sh: can’t open /sys/firmware/devicetree/base/compatible: Permission denied
/sys/firmware/devicetree/base (names)ok#address-cells
#size-cells
symbols
aliases
chosen
cluster-device0
cluster-device1
cluster-device2
cluster-device3
compatible
cpus
firmware
fpmcu-vcc-regulator
interrupt-parent
lpddr-channel0
lpddr-channel1
lpddr-channel2
lpddr-channel3
lpddr-channel4
lpddr-channel5
lpddr-channel6
lpddr-channel7
memory
mmio-sram@18b4e000
name
pmu
psci
qcom,hdcp
regulator-dmic-en
regulator-pp3300-ucam
reserved-memory
rsc@af20000
soc
sram@c3f0000
sys-pm-vx@c320000
timer
touchpad-vdd-regulator
touchscreen-vdd-regulator
wlan-3p3-regulator
/sys/firmware/fdt (bytes)denied/system/bin/sh: can’t open /sys/firmware/fdt: Permission denied
/sys/firmware/log (bytes)denied/system/bin/sh: can’t open /sys/firmware/log: Permission denied
partition namesokKERN-A
KERN-B
ROOT-B
bluetooth_a
bluetooth_b
boot_a
boot_b
desktop_security_persist
desktop_security_storage
dsp_a
dsp_b
dtb_a
dtb_b
dtbo_a
dtbo_b
frp_persist
init_boot_a
init_boot_b
keymaster_a
keymaster_b
metadata
misc
modem_a
modem_b
nvme0n1
ota_recovery_a
ota_recovery_b
persist
pvmfw_a
pvmfw_b
rawdump
recovery_a
recovery_b
super
userdata
vbmeta_a
vbmeta_b
vendor_boot_a
vendor_boot_b
firmware tools on PATHokcrossystem: none
futility: none
gsctool: /system/bin/gsctool
ectool: /system/bin/ectool
vpd: none
flashrom: none
GSC firmware (gsctool -a -f)okstart
device: NT
keyids: RO 0xd4fd1f25, RW 0x405a3825
offsets: backup RO at 0, backup RW at 0x10000
Current versions:
RO 2.0.119
RW 0.33.291
CCD state (gsctool -a -I)okState: Locked
Password: None
Flags: 000000
Capabilities, current and default:
UartGscRxAPTx Y Always
UartGscTxAPRx Y Always
UartGscRxECTx Y Always
UartGscTxECRx - IfOpened
UartGscRxFpmcuTx Y Always
UartGscTxFpmcuRx - IfOpened
FlashAP - IfOpened
FlashEC - IfOpened
OverrideWP - IfOpened
RebootECAP - IfOpened
GscFullConsole - IfOpened
UnlockNoReboot Y Always
UnlockNoShortPP Y Always
OpenNoTPMWipe - IfOpened
OpenNoLongPP - IfOpened
RemoveBatteryBypassPP Y Always
I2C - IfOpened
FlashRead Y Always
OpenNoDevMode - IfOpened
OpenFromUSB - IfOpened
OverrideBatt - IfOpened
AllowUnverifiedRo - Never
CCD caps bitmap: 0x29817
Capabilities are default.
EC version (ectool version)deniedError: Permission denied (os error 13)

Recon 2026-11-02

Collected 2026-11-02 19:58 EST by scripts/recon.sh: read-only, adb shell as uid 2000 over wireless debugging. Serials, boot ids and MACs are redacted. denied and absent are findings about what the stock OS exposes.

ItemStatusValue
ro.product.deviceokmica
ro.product.boardempty
ro.boot.hardwareokandroid-desktop
ro.bootloaderokdepthcharge-Google_Mica.16702.59.0/Google_Mica.16702.143.0
ro.boot.hwcempty
ro.build.fingerprintokDell/mica/mica:17/CL3B.260622.276.R2/16560795:user/release-keys
ro.boot.verifiedbootstateokorange
ro.boot.flash.lockedok0
ro.boot.vbmeta.device_stateokunlocked
ro.boot.slot_suffixok_b
ro.boot.dtbo_idxok0,1,2
ro.boot.hypervisor.protected_vm.supportedok1
kernelokLinux localhost 6.12.81-android16-6-g08dd7534bf44-ab16287118-4k #1 SMP PREEMPT Tue Sep 8 10:57:08 UTC 2026 aarch64 Toybox
/proc/cmdlineokconsole=ttynull stack_depot_disable=on cgroup_disable=pressure kasan.stacktrace=off kvm-arm.mode=protected bootconfig clk_ignore_unused pd_ignore_unused efi=noruntime efi=novamap android_arch_task_struct_size=512 id_aa64mmfr1.vh=0 arm64_sw.hvhe=0 irqaffinity=0-3 pcie_ports=compat cpufreq.default_governor=performance kvm-arm.ffa-unmap-on-lend=full,nr_handles=8192 kvm-arm.ffa_max_nr_constituents=16384 firmware_class.path=/vendor/firmware,/vendor/firmware_mnt/image console=null 8250.nr_uarts=4 console_msg_format=syslog log_buf_len=1M cgroup_disable=pressure root=/dev/ram0 reserve_mem=20M:2M:trace trace_instance=boot_mapped^traceoff@trace drm.trace=0x106 module_blacklist=cros_ec_debugfs proc_mem.force_override=ptrace binder.impl=rust no_console_suspend softlockup_panic=1 nmi_watchdog=panic pm.dpm_watchdog_enabled=1 cpufreq.default_governor=performance trace_instance=backup=boot_mapped cpuidle.governor=teo kvm-arm.protected_modules=qnoc-x1e80100.ko,pkvm-smc-filter.ko,rpmh-regulator.ko,clk-rpmh.ko,gcc-x1e80100.ko,gpucc-x1e80100.ko,pkvm-secure-memory-manager.ko,qcom_smmu_v2_v3_dispatcher.ko,pkvm_drng.ko bootconfig
/proc/cpuinfo (head)okprocessor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0x2
CPU part : 0x001
CPU revision : 1

processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
/sys/firmwareokdevicetree
fdt
log
vpd
/proc/device-tree/modelabsentcat: /proc/device-tree/model: No such file or directory
/proc/device-tree/compatibledenied/system/bin/sh: can’t open /proc/device-tree/compatible: Permission denied
/sys/firmware/devicetree/base/compatibledenied/system/bin/sh: can’t open /sys/firmware/devicetree/base/compatible: Permission denied
/sys/firmware/devicetree/base (names)ok#address-cells
#size-cells
symbols
aliases
chosen
cluster-device0
cluster-device1
cluster-device2
cluster-device3
compatible
cpus
firmware
fpmcu-vcc-regulator
interrupt-parent
lpddr-channel0
lpddr-channel1
lpddr-channel2
lpddr-channel3
lpddr-channel4
lpddr-channel5
lpddr-channel6
lpddr-channel7
memory
mmio-sram@18b4e000
name
pmu
psci
qcom,hdcp
regulator-dmic-en
regulator-pp3300-ucam
reserved-memory
rsc@af20000
soc
sram@c3f0000
sys-pm-vx@c320000
timer
touchpad-vdd-regulator
touchscreen-vdd-regulator
wlan-3p3-regulator
/sys/firmware/fdt (bytes)denied/system/bin/sh: can’t open /sys/firmware/fdt: Permission denied
/sys/firmware/log (bytes)denied/system/bin/sh: can’t open /sys/firmware/log: Permission denied
partition namesokKERN-A
KERN-B
ROOT-B
bluetooth_a
bluetooth_b
boot_a
boot_b
desktop_security_persist
desktop_security_storage
dsp_a
dsp_b
dtb_a
dtb_b
dtbo_a
dtbo_b
frp_persist
init_boot_a
init_boot_b
keymaster_a
keymaster_b
metadata
misc
modem_a
modem_b
nvme0n1
ota_recovery_a
ota_recovery_b
persist
pvmfw_a
pvmfw_b
rawdump
recovery_a
recovery_b
super
userdata
vbmeta_a
vbmeta_b
vendor_boot_a
vendor_boot_b
firmware tools on PATHokcrossystem: none
futility: none
gsctool: /system/bin/gsctool
ectool: /system/bin/ectool
vpd: none
flashrom: none
GSC firmware (gsctool -a -f)okstart
device: NT
keyids: RO 0xd4fd1f25, RW 0x405a3825
offsets: backup RO at 0, backup RW at 0x10000
Current versions:
RO 2.0.119
RW 0.33.291
CCD state (gsctool -a -I)okState: Locked
Password: None
Flags: 000000
Capabilities, current and default:
UartGscRxAPTx Y Always
UartGscTxAPRx Y Always
UartGscRxECTx Y Always
UartGscTxECRx - IfOpened
UartGscRxFpmcuTx Y Always
UartGscTxFpmcuRx - IfOpened
FlashAP - IfOpened
FlashEC - IfOpened
OverrideWP - IfOpened
RebootECAP - IfOpened
GscFullConsole - IfOpened
UnlockNoReboot Y Always
UnlockNoShortPP Y Always
OpenNoTPMWipe - IfOpened
OpenNoLongPP - IfOpened
RemoveBatteryBypassPP Y Always
I2C - IfOpened
FlashRead Y Always
OpenNoDevMode - IfOpened
OpenFromUSB - IfOpened
OverrideBatt - IfOpened
AllowUnverifiedRo - Never
CCD caps bitmap: 0x29817
Capabilities are default.
EC version (ectool version)deniedError: Permission denied (os error 13)

Recon 2026-11-02

Collected 2026-11-02 19:39 EST by scripts/recon.sh: read-only, adb shell as uid 2000 over wireless debugging. Serials, boot ids and MACs are redacted. denied and absent are findings about what the stock OS exposes.

ItemStatusValue
ro.product.deviceokmica
ro.product.boardempty
ro.boot.hardwareokandroid-desktop
ro.bootloaderokdepthcharge-Google_Mica.16702.59.0/Google_Mica.16702.143.0
ro.boot.hwcempty
ro.build.fingerprintokDell/mica/mica:17/CL3B.260622.276.R2/16560795:user/release-keys
ro.boot.verifiedbootstateokgreen
ro.boot.flash.lockedok1
ro.boot.vbmeta.device_stateoklocked
ro.boot.slot_suffixok_b
ro.boot.dtbo_idxok0,1,2
ro.boot.hypervisor.protected_vm.supportedok1
kernelokLinux localhost 6.12.81-android16-6-g08dd7534bf44-ab16287118-4k #1 SMP PREEMPT Tue Sep 8 10:57:08 UTC 2026 aarch64 Toybox
/proc/cmdlineokconsole=ttynull stack_depot_disable=on cgroup_disable=pressure kasan.stacktrace=off kvm-arm.mode=protected bootconfig clk_ignore_unused pd_ignore_unused efi=noruntime efi=novamap android_arch_task_struct_size=512 id_aa64mmfr1.vh=0 arm64_sw.hvhe=0 irqaffinity=0-3 pcie_ports=compat cpufreq.default_governor=performance kvm-arm.ffa-unmap-on-lend=full,nr_handles=8192 kvm-arm.ffa_max_nr_constituents=16384 firmware_class.path=/vendor/firmware,/vendor/firmware_mnt/image console=null 8250.nr_uarts=4 console_msg_format=syslog log_buf_len=1M cgroup_disable=pressure root=/dev/ram0 reserve_mem=20M:2M:trace trace_instance=boot_mapped^traceoff@trace drm.trace=0x106 module_blacklist=cros_ec_debugfs proc_mem.force_override=ptrace binder.impl=rust no_console_suspend softlockup_panic=1 nmi_watchdog=panic pm.dpm_watchdog_enabled=1 cpufreq.default_governor=performance trace_instance=backup=boot_mapped cpuidle.governor=teo kvm-arm.protected_modules=qnoc-x1e80100.ko,pkvm-smc-filter.ko,rpmh-regulator.ko,clk-rpmh.ko,gcc-x1e80100.ko,gpucc-x1e80100.ko,pkvm-secure-memory-manager.ko,qcom_smmu_v2_v3_dispatcher.ko,pkvm_drng.ko bootconfig
/proc/cpuinfo (head)okprocessor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0x2
CPU part : 0x001
CPU revision : 1

processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
/sys/firmwareokdevicetree
fdt
log
vpd
/proc/device-tree/modelabsentcat: /proc/device-tree/model: No such file or directory
/proc/device-tree/compatibledenied/system/bin/sh: can’t open /proc/device-tree/compatible: Permission denied
/sys/firmware/devicetree/base/compatibledenied/system/bin/sh: can’t open /sys/firmware/devicetree/base/compatible: Permission denied
/sys/firmware/devicetree/base (names)ok#address-cells
#size-cells
symbols
aliases
chosen
cluster-device0
cluster-device1
cluster-device2
cluster-device3
compatible
cpus
firmware
fpmcu-vcc-regulator
interrupt-parent
lpddr-channel0
lpddr-channel1
lpddr-channel2
lpddr-channel3
lpddr-channel4
lpddr-channel5
lpddr-channel6
lpddr-channel7
memory
mmio-sram@18b4e000
name
pmu
psci
qcom,hdcp
regulator-dmic-en
regulator-pp3300-ucam
reserved-memory
rsc@af20000
soc
sram@c3f0000
sys-pm-vx@c320000
timer
touchpad-vdd-regulator
touchscreen-vdd-regulator
wlan-3p3-regulator
/sys/firmware/fdt (bytes)denied/system/bin/sh: can’t open /sys/firmware/fdt: Permission denied
/sys/firmware/log (bytes)denied/system/bin/sh: can’t open /sys/firmware/log: Permission denied
partition namesokKERN-A
KERN-B
ROOT-B
bluetooth_a
bluetooth_b
boot_a
boot_b
desktop_security_persist
desktop_security_storage
dsp_a
dsp_b
dtb_a
dtb_b
dtbo_a
dtbo_b
frp_persist
init_boot_a
init_boot_b
keymaster_a
keymaster_b
metadata
misc
modem_a
modem_b
nvme0n1
ota_recovery_a
ota_recovery_b
persist
pvmfw_a
pvmfw_b
rawdump
recovery_a
recovery_b
super
userdata
vbmeta_a
vbmeta_b
vendor_boot_a
vendor_boot_b
firmware tools on PATHokcrossystem: none
futility: none
gsctool: /system/bin/gsctool
ectool: /system/bin/ectool
vpd: none
flashrom: none
GSC firmware (gsctool -a -f)okstart
device: NT
keyids: RO 0xd4fd1f25, RW 0x405a3825
offsets: backup RO at 0, backup RW at 0x10000
Current versions:
RO 2.0.119
RW 0.33.291
CCD state (gsctool -a -I)okState: Locked
Password: None
Flags: 000000
Capabilities, current and default:
UartGscRxAPTx Y Always
UartGscTxAPRx Y Always
UartGscRxECTx Y Always
UartGscTxECRx - IfOpened
UartGscRxFpmcuTx Y Always
UartGscTxFpmcuRx - IfOpened
FlashAP - IfOpened
FlashEC - IfOpened
OverrideWP - IfOpened
RebootECAP - IfOpened
GscFullConsole - IfOpened
UnlockNoReboot Y Always
UnlockNoShortPP Y Always
OpenNoTPMWipe - IfOpened
OpenNoLongPP - IfOpened
RemoveBatteryBypassPP Y Always
I2C - IfOpened
FlashRead Y Always
OpenNoDevMode - IfOpened
OpenFromUSB - IfOpened
OverrideBatt - IfOpened
AllowUnverifiedRo - Never
CCD caps bitmap: 0x29817
Capabilities are default.
EC version (ectool version)deniedError: Permission denied (os error 13)

Recon 2026-11-02

Collected 2026-11-02 18:59 EST by scripts/recon.sh: read-only, adb shell as uid 2000 over wireless debugging. Serials, boot ids and MACs are redacted. denied and absent are findings about what the stock OS exposes.

ItemStatusValue
ro.product.deviceokmica
ro.product.boardempty
ro.boot.hardwareokandroid-desktop
ro.bootloaderokdepthcharge-Google_Mica.16702.59.0/Google_Mica.16702.143.0
ro.boot.hwcempty
ro.build.fingerprintokDell/mica/mica:17/CL3B.260622.276.R2/16560795:user/release-keys
ro.boot.verifiedbootstateokgreen
ro.boot.flash.lockedok1
ro.boot.vbmeta.device_stateoklocked
ro.boot.slot_suffixok_b
ro.boot.dtbo_idxok0,1,2
ro.boot.hypervisor.protected_vm.supportedok1
kernelokLinux localhost 6.12.81-android16-6-g08dd7534bf44-ab16287118-4k #1 SMP PREEMPT Tue Sep 8 10:57:08 UTC 2026 aarch64 Toybox
/proc/cmdlineokconsole=ttynull stack_depot_disable=on cgroup_disable=pressure kasan.stacktrace=off kvm-arm.mode=protected bootconfig clk_ignore_unused pd_ignore_unused efi=noruntime efi=novamap android_arch_task_struct_size=512 id_aa64mmfr1.vh=0 arm64_sw.hvhe=0 irqaffinity=0-3 pcie_ports=compat cpufreq.default_governor=performance kvm-arm.ffa-unmap-on-lend=full,nr_handles=8192 kvm-arm.ffa_max_nr_constituents=16384 firmware_class.path=/vendor/firmware,/vendor/firmware_mnt/image console=null 8250.nr_uarts=4 console_msg_format=syslog log_buf_len=1M cgroup_disable=pressure root=/dev/ram0 reserve_mem=20M:2M:trace trace_instance=boot_mapped^traceoff@trace drm.trace=0x106 module_blacklist=cros_ec_debugfs proc_mem.force_override=ptrace binder.impl=rust no_console_suspend softlockup_panic=1 nmi_watchdog=panic pm.dpm_watchdog_enabled=1 cpufreq.default_governor=performance trace_instance=backup=boot_mapped cpuidle.governor=teo kvm-arm.protected_modules=qnoc-x1e80100.ko,pkvm-smc-filter.ko,rpmh-regulator.ko,clk-rpmh.ko,gcc-x1e80100.ko,gpucc-x1e80100.ko,pkvm-secure-memory-manager.ko,qcom_smmu_v2_v3_dispatcher.ko,pkvm_drng.ko bootconfig
/proc/cpuinfo (head)okprocessor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0x2
CPU part : 0x001
CPU revision : 1

processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
/sys/firmwareokdevicetree
fdt
log
vpd
/proc/device-tree/modelabsentcat: /proc/device-tree/model: No such file or directory
/proc/device-tree/compatibledenied/system/bin/sh: can’t open /proc/device-tree/compatible: Permission denied
/sys/firmware/devicetree/base/compatibledenied/system/bin/sh: can’t open /sys/firmware/devicetree/base/compatible: Permission denied
/sys/firmware/devicetree/base (names)ok#address-cells
#size-cells
symbols
aliases
chosen
cluster-device0
cluster-device1
cluster-device2
cluster-device3
compatible
cpus
firmware
fpmcu-vcc-regulator
interrupt-parent
lpddr-channel0
lpddr-channel1
lpddr-channel2
lpddr-channel3
lpddr-channel4
lpddr-channel5
lpddr-channel6
lpddr-channel7
memory
mmio-sram@18b4e000
name
pmu
psci
qcom,hdcp
regulator-dmic-en
regulator-pp3300-ucam
reserved-memory
rsc@af20000
soc
sram@c3f0000
sys-pm-vx@c320000
timer
touchpad-vdd-regulator
touchscreen-vdd-regulator
wlan-3p3-regulator
/sys/firmware/fdt (bytes)denied/system/bin/sh: can’t open /sys/firmware/fdt: Permission denied
/sys/firmware/log (bytes)denied/system/bin/sh: can’t open /sys/firmware/log: Permission denied
partition namesokKERN-A
KERN-B
ROOT-B
bluetooth_a
bluetooth_b
boot_a
boot_b
desktop_security_persist
desktop_security_storage
dsp_a
dsp_b
dtb_a
dtb_b
dtbo_a
dtbo_b
frp_persist
init_boot_a
init_boot_b
keymaster_a
keymaster_b
metadata
misc
modem_a
modem_b
nvme0n1
ota_recovery_a
ota_recovery_b
persist
pvmfw_a
pvmfw_b
rawdump
recovery_a
recovery_b
super
userdata
vbmeta_a
vbmeta_b
vendor_boot_a
vendor_boot_b
firmware tools on PATHokcrossystem: none
futility: none
gsctool: /system/bin/gsctool
ectool: /system/bin/ectool
vpd: none
flashrom: none
GSC firmware (gsctool -a -f)okstart
device: NT
keyids: RO 0xd4fd1f25, RW 0x405a3825
offsets: backup RO at 0, backup RW at 0x10000
Current versions:
RO 2.0.119
RW 0.33.291
CCD state (gsctool -a -I)okState: Locked
Password: None
Flags: 000000
Capabilities, current and default:
UartGscRxAPTx Y Always
UartGscTxAPRx Y Always
UartGscRxECTx Y Always
UartGscTxECRx - IfOpened
UartGscRxFpmcuTx Y Always
UartGscTxFpmcuRx - IfOpened
FlashAP - IfOpened
FlashEC - IfOpened
OverrideWP - IfOpened
RebootECAP - IfOpened
GscFullConsole - IfOpened
UnlockNoReboot Y Always
UnlockNoShortPP Y Always
OpenNoTPMWipe - IfOpened
OpenNoLongPP - IfOpened
RemoveBatteryBypassPP Y Always
I2C - IfOpened
FlashRead Y Always
OpenNoDevMode - IfOpened
OpenFromUSB - IfOpened
OverrideBatt - IfOpened
AllowUnverifiedRo - Never
CCD caps bitmap: 0x29817
Capabilities are default.
EC version (ectool version)deniedError: Permission denied (os error 13)

Recon 2026-10-21

Collected 2026-10-21 12:35 EDT by scripts/recon.sh: read-only, adb shell as uid 2000 over wireless debugging. Serials, boot ids and MACs are redacted. denied and absent are findings about what the stock OS exposes.

ItemStatusValue
ro.product.deviceokmica
ro.product.boardempty
ro.boot.hardwareokandroid-desktop
ro.bootloaderokdepthcharge-Google_Mica.16702.59.0/Google_Mica.16702.143.0
ro.boot.hwcempty
ro.build.fingerprintokDell/mica/mica:17/CL3B.260622.276.R2/16560795:user/release-keys
ro.boot.verifiedbootstateokgreen
ro.boot.flash.lockedok1
ro.boot.vbmeta.device_stateoklocked
ro.boot.slot_suffixok_b
ro.boot.dtbo_idxok0,1,2
ro.boot.hypervisor.protected_vm.supportedok1
kernelokLinux localhost 6.12.81-android16-6-g08dd7534bf44-ab16287118-4k #1 SMP PREEMPT Tue Sep 8 10:57:08 UTC 2026 aarch64 Toybox
/proc/cmdlineokconsole=ttynull stack_depot_disable=on cgroup_disable=pressure kasan.stacktrace=off kvm-arm.mode=protected bootconfig clk_ignore_unused pd_ignore_unused efi=noruntime efi=novamap android_arch_task_struct_size=512 id_aa64mmfr1.vh=0 arm64_sw.hvhe=0 irqaffinity=0-3 pcie_ports=compat cpufreq.default_governor=performance kvm-arm.ffa-unmap-on-lend=full,nr_handles=8192 kvm-arm.ffa_max_nr_constituents=16384 firmware_class.path=/vendor/firmware,/vendor/firmware_mnt/image console=null 8250.nr_uarts=4 console_msg_format=syslog log_buf_len=1M cgroup_disable=pressure root=/dev/ram0 reserve_mem=20M:2M:trace trace_instance=boot_mapped^traceoff@trace drm.trace=0x106 module_blacklist=cros_ec_debugfs proc_mem.force_override=ptrace binder.impl=rust no_console_suspend softlockup_panic=1 nmi_watchdog=panic pm.dpm_watchdog_enabled=1 cpufreq.default_governor=performance trace_instance=backup=boot_mapped cpuidle.governor=teo kvm-arm.protected_modules=qnoc-x1e80100.ko,pkvm-smc-filter.ko,rpmh-regulator.ko,clk-rpmh.ko,gcc-x1e80100.ko,gpucc-x1e80100.ko,pkvm-secure-memory-manager.ko,qcom_smmu_v2_v3_dispatcher.ko,pkvm_drng.ko bootconfig
/proc/cpuinfo (head)okprocessor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0x2
CPU part : 0x001
CPU revision : 1

processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint i8mm bf16 rng bti ecv afp rpres wfxt
/sys/firmwareokdevicetree
fdt
log
vpd
/proc/device-tree/modelabsentcat: /proc/device-tree/model: No such file or directory
/proc/device-tree/compatibledenied/system/bin/sh: can’t open /proc/device-tree/compatible: Permission denied
/sys/firmware/devicetree/base/compatibledenied/system/bin/sh: can’t open /sys/firmware/devicetree/base/compatible: Permission denied
/sys/firmware/devicetree/base (names)ok#address-cells
#size-cells
symbols
aliases
chosen
cluster-device0
cluster-device1
cluster-device2
cluster-device3
compatible
cpus
firmware
fpmcu-vcc-regulator
interrupt-parent
lpddr-channel0
lpddr-channel1
lpddr-channel2
lpddr-channel3
lpddr-channel4
lpddr-channel5
lpddr-channel6
lpddr-channel7
memory
mmio-sram@18b4e000
name
pmu
psci
qcom,hdcp
regulator-dmic-en
regulator-pp3300-ucam
reserved-memory
rsc@af20000
soc
sram@c3f0000
sys-pm-vx@c320000
timer
touchpad-vdd-regulator
touchscreen-vdd-regulator
wlan-3p3-regulator
/sys/firmware/fdt (bytes)denied/system/bin/sh: can’t open /sys/firmware/fdt: Permission denied
/sys/firmware/log (bytes)denied/system/bin/sh: can’t open /sys/firmware/log: Permission denied
partition namesokKERN-A
KERN-B
ROOT-B
bluetooth_a
bluetooth_b
boot_a
boot_b
desktop_security_persist
desktop_security_storage
dsp_a
dsp_b
dtb_a
dtb_b
dtbo_a
dtbo_b
frp_persist
init_boot_a
init_boot_b
keymaster_a
keymaster_b
metadata
misc
modem_a
modem_b
nvme0n1
ota_recovery_a
ota_recovery_b
persist
pvmfw_a
pvmfw_b
rawdump
recovery_a
recovery_b
super
userdata
vbmeta_a
vbmeta_b
vendor_boot_a
vendor_boot_b
firmware tools on PATHokcrossystem: none
futility: none
gsctool: /system/bin/gsctool
ectool: /system/bin/ectool
vpd: none
flashrom: none
GSC firmware (gsctool -a -f)okstart
device: NT
keyids: RO 0xd4fd1f25, RW 0x405a3825
offsets: backup RO at 0, backup RW at 0x10000
Current versions:
RO 2.0.119
RW 0.33.291
CCD state (gsctool -a -I)okState: Locked
Password: None
Flags: 000000
Capabilities, current and default:
UartGscRxAPTx Y Always
UartGscTxAPRx Y Always
UartGscRxECTx Y Always
UartGscTxECRx - IfOpened
UartGscRxFpmcuTx Y Always
UartGscTxFpmcuRx - IfOpened
FlashAP - IfOpened
FlashEC - IfOpened
OverrideWP - IfOpened
RebootECAP - IfOpened
GscFullConsole - IfOpened
UnlockNoReboot Y Always
UnlockNoShortPP Y Always
OpenNoTPMWipe - IfOpened
OpenNoLongPP - IfOpened
RemoveBatteryBypassPP Y Always
I2C - IfOpened
FlashRead Y Always
OpenNoDevMode - IfOpened
OpenFromUSB - IfOpened
OverrideBatt - IfOpened
AllowUnverifiedRo - Never
CCD caps bitmap: 0x29817
Capabilities are default.
EC version (ectool version)deniedError: Permission denied (os error 13)

PCIe under mainline on mica (2026-11-02)

Attempt 2026-11-02-6: kernel/mica-pcie.config on top of the USB and ADSP fragments. Source: the AP console over SuzyQ (raw log dumps/firstboot/2026-11-02-ap.log).

Result: both PCIe controllers up, both devices enumerate

AddressDeviceClassLinkDriver
0006:00:00.0root port 17cb:0111 (pcie@1bf8000, gen4x4 PHY 1bfc000)0x06040016 GT/s x4pcieport
0006:01:00.0NVMe SSD 1344:5427 (vendor 0x1344: Micron)0x01080216 GT/s x4none (on purpose)
0004:00:00.0root port 17cb:0111 (pcie@1c08000, gen3x2 PHY 1c0e000)0x0604008 GT/s x2pcieport
0004:01:00.0WCN7850 Wi-Fi 17cb:11070x0280008 GT/s x2none (ath12k probe failed)
  • qcom-pcie 1bf8000.pcie: PCIe Gen.4 x4 link up at 1.6 s; 1c08000.pcie: PCIe Gen.3 x2 link up at 1.75 s.
  • Wi-Fi: the WCN7850 PMU and PCI power control powered the chip; ath12k_wifi7_pci bound, then Direct firmware load for ath12k/WCN7850/hw2.0/amss.bin failed with error -2 → failed to start mhi: -110. No Wi-Fi firmware was shipped.
  • SSD safety: BLK_DEV_NVME is =m and guarded by # forbid-y: in the fragment, so enumeration read config space only; no NVMe command was sent. The SSD is the stock disk and is never written (ADR 0003, ticket 07).
  • Deferred now: only arm-scmi (“failed to setup channels”). PCIe, USB, the GPU SMMU, the DP aux bridges and pmic-glink all probed.

What made it work

The QMP PCIe PHY was =m. The Wi-Fi port also needs PCI_PWRCTRL_PWRSEQ, which has no prompt and is selected only by ATH11K/ATH12K; ATH12K in turn needs MAC80211, CFG80211 and RFKILL built in (scripts/dt-kconfig-map.py, scripts/build-kernel.sh demotion check).

Phase 3: panel-edp knows the mica panel (2026-11-03)

Attempt 2026-11-03-9. Image c360e4af47255421, kernel #9 7.3.0-ga804c5742bff-dirty (2026-11-03 4:17 PM ET), commit c6ef957. Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt and the AP console log dumps/firstboot/2026-11-03-ap.log (both gitignored).

Why

Every boot since the display came up (attempt -4, display-2026-11-03.md) logged a WARNING: drivers/gpu/drm/panel/panel-edp.c:829 at generic_edp_panel_probe backtrace and Unknown panel LGD 0x0804, using conservative timings. The fallback is unprepare 2000 ms and enable 200 ms (panel-edp.c:755-772 at a804c57).

  • No mica source for the timings: the stock tree has no panel node. Its vendor eDP node only names GPIOs and backlight limits (entry01-overlay.dts:119-140). A web search (2026-11-03) found no upstream entry for LGD 0x0804 or “LP134WQ”.
  • Owner’s decisions (2026-11-03, “1a 2a 3b”): carry kernel patches in the repo (ADR 0007), use the LG Display delays, and keep the patch local.

Change (commit c6ef957)

  • kernel/patches/0001-drm-panel-edp-add-lgd-0x0804.patch: EDP_PANEL_ENTRY('L', 'G', 'D', 0x0804, &delay_200_500_e200_d200, "LP134WQ"), after 0x07fe.
    • delay_200_500_e200_d200 (hpd_absent 200, unprepare 500, enable 200, disable 200 ms) is what all 12 LG Display entries use at a804c57. Not from a datasheet.
    • The name does not match the EDID’s cut-off text ("RNYV4?LP134WQ"), so find_edp_panel() matches by panel ID, its second loop (panel-edp.c:2211-2232).
  • scripts/build-kernel.sh applies kernel/patches/ before each build. check.sh check 20 fails when a patch stops applying (red-tested with a broken copy).

Result: pass (panel)

  • panel-simple-dp-aux aux-aea0000.displayport-controller: Detected LGD LP134WQ (0x0804) at 1.675 s. No WARNING and no generic_edp_panel_probe backtrace anywhere in the log.
  • card2-eDP-1 connected at 2560x1600, VREG_EDP_3P3 and VBL9 enabled, backlight 2048/4095.
  • At shutdown (189.5 s) the panel driver logs Skipping disable of already disabled panel and Skipping unprepare of already unprepared panel. Inference: msm had already turned the panel off; not new information about the entry.

Regression seen in the same boot: SCMI timed out

Not caused by this change (the patch touches only panel-edp.c), and attempt -8 with the same config had working SCMI. Details and the open question are in scmi-2026-11-03.md, “Update: attempt -9”.

Phase 3: GPU, the Adreno X1-85 boots its GMU (2026-11-03)

Attempt 2026-11-03-5. Image d862b3e055610ddf, kernel #6 (2026-11-03 2:23 PM ET), commit d67114c. Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt (1308 lines) and the AP console log dumps/firstboot/2026-11-03-ap.log (both gitignored), plus the owner looking at the panel.

Why

In attempt 2026-11-03-4 (display-2026-11-03.md) the display bound only because the command line carried msm.skip_gpu=1. The GPU SMMU 3da0000.iommu stayed deferred (“supplier 3d90000.clock-controller not ready”). scripts/dt-kconfig-map.py on the mica-el2 DTB showed gpucc (CLK_X1E80100_GPUCC) as the only GPU-path driver still at =m.

Changes (commit d67114c)

  • Kernel: kernel/mica-gpu.config sets CLK_X1E80100_GPUCC=y.
  • Command line: msm.separate_gpu_kms=1 replaces msm.skip_gpu=1. The GPU gets its own DRM device through msm_gpu_probe (adreno_device.c:275, msm_drv.c:1102-1121), so a GPU failure can’t unbind the display.
  • Firmware: qcom/gen70500_sqe.fw and qcom/gen70500_gmu.bin (names from a6xx_catalog.c:1573-1577).
    • They come from linux-firmware-qcom 20260916-1, downloaded into ~/.cache/gbook/pkgs, signature-checked against pacman’s keyring and never installed on the build host.
    • sha256 05ae89e6… (sqe) and 5dfba247… (gmu), the same as kernel.org linux-firmware tag 20260916.
    • scripts/stage-firmware.sh gained this “pkg” source.
  • /init: after the display block, it opens the GPU’s render node in a background subshell that is never waited for. That open triggers msm_open → load_gpu.

Result: pass

  • SMMU: arm-smmu 3da0000.iommu probed at 0.506 s (SMMUv2, 25 context banks). 3d00000.gpu and 3d6a000.gmu joined iommu groups 0 and 1. 3da0000.iommu is gone from every deferred list.
  • GPU DRM device: [drm] Initialized msm 1.13.0 for 3d00000.gpu on minor 1 at 0.542 s.
    • /init found it as renderD128, driver adreno.
    • The display is a separate device: msm-kms for ae01000.display-controller on minor 2 (card2).
  • Firmware and GMU, when /init opened the node at 57.8 s:
    • loaded qcom/gen70500_sqe.fw from new location and loaded qcom/gen70500_gmu.bin from new location
    • Loaded GMU firmware v4.3.17
    • Zap shader not enabled - using SECVID_TRUST_CNTL instead. This is the expected EL2 path: x1-el2.dtso disables gpu_zap_shader, and a6xx_gpu.c:1507-1516 falls back.
  • No GPU errors after the load: no GMU timeout, hang or fault in the rest of the log. gpu-open=opened.
  • devfreq: 3d00000.gpu at 300 MHz, max 1250 MHz.
  • Display unchanged from attempt -4:
    • card2-eDP-1 connected at 2560x1600.
    • VREG_EDP_3P3 and VBL9 enabled after 40 s; backlight 2048/4095.
    • The owner saw the panel stay lit with the banner.
  • Panel identity (the edid_id fix 2d2f2b5): mfr=LGD product=0804 text="RNYV4?LP134WQ". That’s an LG Display LP134WQ… 13.4“ panel. The 0xfe text descriptor is cut at 13 bytes, and ? replaces a non-printing byte.
  • Shutdown: clean. /init sent netlog at about 68 s and ran reboot -f at 128 s. The kernel disabled the panel and both SMMUs, then printed reboot: Restarting system at 128.6 s (2:29 PM ET). With the image stick still in, the firmware stayed on its boot screen. Once the owner pulled the stick, stock booted.
  • Deferred at the end: only arm-scmi (mailbox 17430000).

Leftovers (not failures)

  • gcc-x1e80100 and gpucc-x1e80100 report sync_state() pending due to 3d6a000.gmu. Inference: the GMU node has no platform driver of its own (the a6xx code drives it during the GPU bind), so these providers never get their sync_state(). Not checked against the source yet. clk_ignore_unused pd_ignore_unused are on the command line anyway.
  • adreno 3d00000.gpu: supply vdd not found and vddcx not found: the GPU node in hamoa.dtsi names neither supply, so the driver uses dummy regulators.
  • No rendering test: the initramfs has no Mesa or freedreno userspace. The GPU is up as far as the kernel is concerned.

Next for the GPU

  1. A real workload needs a userspace with Mesa freedreno (a rootfs, not this initramfs).
  2. The zap shader under EL1 (stock firmware qcdxkmsuc8380.mbn) only matters for a non-EL2 boot.

Phase 3: the GMU gets a driver, the crypto node goes (2026-11-03)

Attempt 2026-11-03-26 (kernel #17, commits a6e3987 and 63bddc5, image 963fb3004304e941), cold start. Owner pick from the Omarchy review (2026-11-03, “1 2 3”). Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt and the AP console log (gitignored), and Omarchy’s linux-omarchy-aarch64 package (https://github.com/omacom/omarchy-pkgs/tree/master/pkgbuilds/linux-omarchy-aarch64, read 2026-11-03).

Why

Every boot left unbound=3d6a000.gmu and unbound=1dfa000.crypto (the sync_state() pending due to … lines that /init reports since the retro), handoff lead 3 and the gap list’s crypto lead.

  • GMU: nothing binds the GPU’s management unit, so gcc and gpucc never reach sync_state(). Akhil P Oommen’s (Qualcomm) “drm/msm: Attach a driver to the GMU” series binds it as a component of the GPU master; Omarchy carries it as 2170-2174 with its own fix-up 2178.
  • Crypto: the pin makes QCE depend on BROKEN (drivers/crypto/Kconfig:531), so nothing binds the node, and aggre2_noc and mc_virt never reach sync_state(); Omarchy (2210) disables the node.

Changes

  • kernel/patches/0002-0007 (ADR 0007): the six patches unchanged, each with a provenance header. They apply in order to the pin; check 20 now applies the series cumulatively to the pin’s files (0005-0007 build on 0002-0004).
  • dts/x1e80100-google-mica.dts: &crypto { status = "disabled"; }, marked not from the stock tree; qcom,qce left kernel/dt-gaps.txt.

Result: pass

  • adreno 3d00000.gpu: bound 3d6a000.gmu (ops a6xx_gmu_bind_ops) at 0.555 s.
  • unbound= went from 8 to 6: 3d6a000.gmu and 1dfa000.crypto are gone. Left: the four CPU bandwidth-monitor PMUs (24091000, 240b3400, 240b5400, 240b6400) and the video and camera clock controllers (aaf0000, ade0000).
  • The GPU is unchanged on the msm.separate_gpu_kms=1 path that patch 0005 reworks: gpu-node=renderD128 driver=adreno, gpu-open=opened, gpu-freq=300000000 max=1250000000, SQE and GMU firmware loaded, zap off.
  • errors=0 (from 1). SCMI full (0x20000, 3 of 3, 78 replies, max 3.9 ms, 0 timeouts).
  • Not measured yet: whether suspend now reaches CX collapse and DDR self-refresh (the reason Omarchy carries 2210); there is no suspend test on mica yet.

First boot of mainline Linux on mica (2026-11-02)

Redacted excerpt (ADR 0005). Raw logs: dumps/firstboot/2026-11-02-ap.log (AP console over SuzyQ) and dumps/firstboot/2026-11-02-2-lastkmsg.txt (dropbox SYSTEM_LAST_KMSG on the next stock boot). Procedure: first-boot runbook. Attempt 2026-11-02-2.

Result: Gate 2 passes

From the ramoops readback (dropbox entry stamped 2026-11-02 20:19:42, Googlebook clock):

[    0.000000] Linux version 7.3.0-ga804c5742bff (the owner@the build host) (clang version 23.1.3, LLD 23.1.3) #1 SMP PREEMPT_DYNAMIC Mon Nov  2 17:04:02 EST 2026
[    0.000000] Machine model: Dell XPS Googlebook
[    0.039540] CPU: All CPU(s) started at EL2
[   61.158726] reboot: Restarting system
  • Image: mica-usb.img sha256 15a107e5…, KERN-A on a USB 3 stick in the right port, booted with Boot from external drive. torvalds a804c57, defconfig + kernel/mica-firstboot.config, x1e80100-google-mica-el2.dtb.
  • Coreboot table: board-id 00000003; no sku-id and no fw-config node.
  • /init ran to the end: every GBOOK: line printed, then reboot -f after 60 s. No panic, no oops.

Channels

ChannelSeenNote
AP console (-if01, ttyMSM0)yes, from 0.67 searlycon: qcom_geni0 MMIO:0x894000 and the bootconsole registered, but nothing reached the SuzyQ before ttyMSM0 probed. Inference: the firmware does not leave QUP2 SE5 set up for 115200 (ticket 04’s open point), so the banner is not on the AP console
ramoops → dropboxyes, full logramoops: using 0x100000@0x86584000; read on the next stock boot
Panelfb0 upsimpledrm bound simple-framebuffer.0 from the coreboot table (no DRM_COREBOOTDRM, as expected)
EC consolepower states onlywarm reset at the reboot

For Phase 3 (from the same log)

  • arm-smmu 3da0000.iommu probe fails with -110.
  • USB (a200000, a400000, a600000, a800000): dwc3-qcom deferred, DWC3 core not registered.
  • PCIe 1bf8000 and 1c08000: root port parse deferred (-517); vdda/vddpe-3v3 fall back to dummy regulators.
  • arm-scmi: channel setup fails for protocol 0x10.
  • reserved-memory: ta@d8600000 fails to reserve; depthcharge’s ramoops and several firmware-added region@… nodes overlap the DT’s own reservations (warnings only).
  • Firmware log: depthcharge disables the NVMe controller and all USB controllers before the jump (“Exiting depthcharge with code 4”).

Phase 3: the ChromeOS EC, built-in keyboard, keyboard backlight and fans (2026-11-03)

Attempts 2026-11-03-13 (image 17bf703d696856ed, kernel #11, commit f1d817e), a repeat boot of the same image, and 2026-11-03-14 (image e2f2bee81254d578, kernel #12, commit 702de45). Owner round 2026-11-03: target the EC (1a), with its children and captures in one boot (2a), kernel patches back on (3a), other unbound nodes as leads only (4a). Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt, the AP and EC console logs dumps/firstboot/2026-11-03-{ap,ec}.log (all gitignored), and the pinned tree a804c57.

Why

All 10 boots on 2026-11-03 up to attempt -12 logged sync_state() pending due to a94000.spi (77 lines in the netlog) and not one cros_ec line. a94000 is spi13 (hamoa.dtsi:1905), the bus of cros_ec: ec@0 in dts/x1e80100-google-mica.dts. Its driver (qcom,spi-geni, drivers/spi/Kconfig:1013, SPI_QCOM_GENI) is =m in arm64 defconfig, and the initramfs loads no modules. So the EC, the built-in keyboard (cros-ec-keyb) and everything the EC adds had never probed. The keyboards in earlier AP logs were USB ones.

Attempt -13: the SPI bus binds, the EC answers garbage

  • Change: kernel/mica-ec.config builds in SPI_QCOM_GENI, CROS_KBD_LED_BACKLIGHT, PWM_CROS_EC, LEDS_CROS_EC and CROS_EC_CHARDEV, requires the already built-in EC core, and forbids CROS_EC_TYPEC and CROS_EC_UCSI at =y (pmic_glink owns the Type-C ports). /init gained the ec-* captures, a 20 s key-press window and one keyboard-backlight write (max for 5 s, then 0).
  • Result: ec-spi=a94000.spi driver=geni_spi, then cros-ec-spi spi0.0: packet too long (252 bytes, expected 4) and Cannot identify the EC: error -28.
    • -28 (-ENOSPC) comes from the v2 command path (cros_ec_spi.c:611): the v2 hello reply’s length byte read 252 (0xfc). The v3 protocol query before it had failed too, logged only at dev_dbg level (cros_ec_proto.c:389, fallback at :492). Both failing points at the transfer, not the EC (inference).
    • The repeat boot of the same image (the stick reached the Googlebook before the -14 write) failed the same way at 3.94 s.
  • The stock node matches ours (overlay entry01 fragment 48: 3 MHz, interrupt TLMM 66 level-low, no google,cros-ec-spi-pre-delay or -msg-delay). Stock drives CS on gpio55 with function qup1_se5, drive 6, bias-disable (entry00-base.dts:3017-3028), but through Qualcomm’s vendor GENI driver.
  • Mainline precedent: both mainline Qualcomm Chromebooks with the EC on GENI SPI drive CS as a GPIO: trogdor (sc7180-trogdor.dtsi:910-911, cs-gpios = <&tlmm 62 GPIO_ACTIVE_LOW>) and herobrine (sc7280-herobrine.dtsi:542-544, with an output-high init state at :895-899).

Attempt -14: GPIO chip-select, pass

  • Change (commit 702de45): &spi13 takes pinctrl-0 = <&qup_spi13_data_clk>, <&qup_spi13_cs_gpio> and cs-gpios = <&tlmm 55 GPIO_ACTIVE_LOW>; the new qup_spi13_cs_gpio state is gpio55, function gpio, the stock drive 6 and bias-disable, output-high. CONFIG_DYNAMIC_DEBUG=y and an /init re-probe with the cros_ec debug lines on, for a failed EC (not needed: the EC registered at boot). dt-validate output unchanged (322 lines, all the known root compatible warning).
  • EC: cros-ec-spi spi0.0: Chrome EC device registered at 0.747 s; cros-ec-rtc registered as rtc0 at 0.677 s. a94000.spi is gone from the sync_state() pending lines.
  • Built-in keyboard: input: cros_ec (input0, event0) and cros_ec_buttons (input1) at 0.93 s. In the 20 s window the owner typed on it: ec-keys=bytes=9336 events=389 (24-byte input_events).
  • Keyboard backlight: chromeos::kbd_backlight (the cros-keyboard-leds cell, so the EC reports EC_FEATURE_PWM_KEYB; drivers/mfd/cros_ec_dev.c:145-147), max=100. Set to 100, read back 100; set to 0, read back 0. The owner saw the keyboard light up for the 5 s (reported 2026-11-03, about 6:05 PM ET).
  • Fans: hwmon54 name=cros_ec fan1_input=0 fan2_input=0: two fans through the EC, both stopped (cool, idle). The EC runs them on its own; nothing on the AP side drives them.
  • Everything else unchanged; errors=1 (from 17 on the repeat -13 boot).

SCMI on these cold starts (aside)

The EC logged G3->S5 before each of the three boots (EC uptime 15984, 17025 and 17236 s), so all three were cold starts. SCMI still failed on two of them: -13 (-110, no firmware version) and the repeat (timed out in resp, firmware version 0x0, cpufreq=none). It worked on -14 (0x20000, all three policies, one timed out in resp line at 0.418 s). So a cold start does not always bring SCMI up; “cold boot only” in scmi-2026-11-03.md needs a dated update. compare printed start: unknown for these boots (the EC reset spacing did not match boot_start.py’s expectation), although the EC log shows G3->S5.

Open

  • Keyboard layout, Fn keys and the top-row keymap: only key events were counted, not which keys.
  • Fan behaviour under load and fan control from the AP (cros_ec hwmon PWM, if the EC offers it).
  • The EC’s Type-C and UCSI cells stay modules; pmic_glink keeps the ports.
  • Other nodes that never bind (owner round 4a, leads only): crypto@1dfa000, videocc@aaf0000, camcc@ade0000, the CPU PMUs (24091000, 240b3400, 240b5400, 240b6400) and 3d6a000.gmu.
  • SCMI on cold starts: intermittent (above).

Phase 3: external DP over Type-C works (2026-11-03)

Attempt 2026-11-03-6. Image 713bf8d219796e5b, kernel #6 (2026-11-03 2:23 PM ET, unchanged from attempt -5), commits 91c5aa6, 2cda0fd. Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt and the AP console log dumps/firstboot/2026-11-03-ap.log (both gitignored), plus the owner looking at the monitor.

Why

In attempt 2026-11-03-5 the DP chain had already bound: msm_dpu bound all three DP controllers (ae90000, ae98000, aea0000), card2-DP-1 and card2-DP-2 existed, and the aux_bridge … failed to acquire drm_bridge error from usb-2026-11-02.md was gone. Both DP connectors read disconnected because nothing was plugged in. The kernel already builds in every link of the path (a804c57 .config):

  • QCOM_PMIC_GLINK=y, which builds pmic_glink_altmode.o (drivers/soc/qcom/Makefile:14)
  • TYPEC_MUX_PS883X=y (retimers), PHY_QCOM_QMP_COMBO=y, DRM_AUX_HPD_BRIDGE=y

pmic_glink_altmode.c sets the mux and retimer (typec_mux_set, typec_retimer_set) and reports HPD through drm_aux_hpd_bridge_notify (lines 196-377), so TYPEC_DP_ALTMODE=m plays no part. The DTS already matched the 9345: link-frequencies on mdss_dp0_out/dp1_out as in x1e80100-dell-xps13-9345.dts:891, :899, and data-lanes = <0 1 2 3> from hamoa.dtsi:5714, :5803. The test needed only a sink and a capture.

Changes

  • /init (91c5aa6, 2cda0fd): after the eDP block, it polls card*-DP-*/status for up to 60 s. At 10 s with nothing connected, it prints plug in (or replug) the monitor now on the panel. Then it reports each DP connector with its modes and EDID identity (the existing edid_id), the Type-C altmodes and a dp-dmesg capture.
  • attempt.sh watch (d63d869) shows those lines.
  • No kernel, DTS or firmware change.

Setup

The SuzyQ stayed in the left port. The boot stick went in the right port as usual. The initramfs runs from RAM, so at the prompt the owner pulled the stick (usb 4-1: USB disconnect at 66.8 s) and plugged a USB-C monitor into the right port.

Result: pass

  • DP connected: dp-wait=25s, then dp-conn=card2-DP-2 status=connected enabled=enabled modes=1920x720 1920x540 1280x720 1280x720 1280x720.
  • Monitor identity: dp-id=card2-DP-2 mfr=BSL product=9826 name="K3". Its USB side names itself ROYUAN K3 and BJYP-K3, with keyboard and mouse HID interfaces behind a USB hub.
  • The owner saw the console text on the monitor.
  • USB through the same port: the monitor’s hub enumerated at 70.3 s on a800000.usb (usb_1_ss1), at high speed on bus 3 and SuperSpeed on bus 4, so DP and USB 3 run together on the port (inference: 2-lane DP).
  • Altmodes: typec-altmode=port0.0 svid=ff01 mode=1 active=yes and port1.0 svid=ff01 mode=1 active=yes (the ports’ own DP altmodes; the partner directories stay empty, as ucsi_glink leaves partner fields blank).
  • Everything else unchanged from attempt -5: eDP-1 at 2560x1600, backlight 2048/4095, GPU firmware and GMU loaded (Loaded GMU firmware v4.3.17), Wi-Fi on 6 GHz with a DHCP lease and netlog sent.
  • Shutdown: reboot: Restarting system at 146.5 s (about 3:21 PM ET). The stick was already out, so stock booted.
  • Deferred at the end: only arm-scmi (mailbox 17430000).

Which port is which

Three readings put the 9345’s side labels the other way round on mica:

  1. SuzyQ, left port: typec port0 sinks power (source [sink]), as in every earlier boot.
  2. Stick, right port: typec port1 sources power, and in usb-2026-11-02.md the stick enumerated on usb_1_ss1.
  3. Monitor, right port: it came up as DP-2, its USB on a800000.usb (usb_1_ss1).

Inference: typec portN is pmic-glink connector@N, and DP-2 is mdss_dp1 (the second DP connector registered). So connector@0 (retimer on i2c3, usb_1_ss0, mdss_dp0) is the left port and connector@1 (i2c7, usb_1_ss1, mdss_dp1) the right one. DP working on the right port supports this directly: pmic_glink_altmode indexes its ports by each connector’s reg (pmic_glink_altmode.c:597, :615), so it programmed the connector@1 chain (i2c7 retimer, usb_1_ss1 PHY) for the right port. Had the index pointed at connector@0, the i2c3 retimer would have been set and mdss_dp1 would not have linked (inference from the code path, not traced per message). The DTS comments now say so (dts/x1e80100-google-mica.dts); the DTB itself is unchanged.

qcom_pmic_glink pmic-glink: Failed to create device link (0x180) with supplier 5-0008 for /pmic-glink/connector@1 appears on every boot. At a804c57:

  • 0x180 is DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_INFERRED (include/linux/device.h:444-445). fw_devlink makes the link from fw_devlink_create_devlink() (drivers/base/core.c:2219-2245).
  • A sync-state-only link only orders sync_state() callbacks. device_link_add() returns NULL for one if the supplier is not yet registered with PM, or if the consumer has already probed.

Inference, not traced further: the i2c7 retimer registered after pmic-glink had probed. Either way, DP and SuperSpeed both work on connector@1, so the warning blocks nothing.

Next

  • Test the left port (connector@0, mdss_dp0), which needs the SuzyQ out, so netlog only.
  • A panel-edp entry for LGD 0x0804. CDSP. The SCMI mailbox.

Phase 3: display, msm DRM drives the eDP panel (2026-11-03)

Attempt 2026-11-03-4. Image ab87e567a0eb05bd, kernel #5 (2026-11-03 2:09 PM ET), commit 072f347. Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt and the AP console log dumps/firstboot/2026-11-03-ap.log (both gitignored), plus the owner looking at the panel.

Why

Every earlier boot went dark at about 33 s: VREG_EDP_3P3: disabling (attempt 2026-11-03-3, wifi-2026-11-03.md). The mica DTS already described the mainline display path (&mdss, &mdss_dp3 with an edp-panel on its aux-bus, &mdss_dp3_phy, vreg_edp_3p3, pwm-backlight), matching the stock sde_edp (entry01-overlay.dts fragment 11). But every driver on it was =m in defconfig at a804c57, and the initramfs loads no modules. Only simpledrm drove the screen, nothing claimed the panel supply, and the regulator core turned it off as unused.

Changes (commit 072f347)

  • kernel/mica-display.config: DRM_MSM, PHY_QCOM_EDP, CLK_X1E80100_DISPCC, DRM_PANEL_EDP, BACKLIGHT_CLASS_DEVICE, BACKLIGHT_PWM and LEDS_QCOM_LPG set =y, plus the dependency chain: QCOM_LLCC and QCOM_OCMEM (DRM_MSM=y needs them =y or =n), and LEDS_CLASS_MULTICOLOR and QCOM_PBS for the LPG.
  • Command line: msm.skip_gpu=1. Otherwise the GPU would join the MDSS component match (msm_drv.c:1076-1086) and wait on gpucc, whose SMMU 3da0000.iommu is still deferred.
  • DTS: the backlight’s edp_bl_en pin state moves from TLMM 74 (left over from the 9345; stock muxes it to QUP SE18) to PMC8380-3 GPIO 4, the stock qcom,edp-backlight-en-gpio. The PWM pin takes the stock edp_pwm_active config.
  • /init: DRM connectors, the backlight, the panel supplies after uptime 40 s, the panel identity from the EDID (no serials; check.sh test 16), display dmesg, and a banner on tty1.

Result: pass

The panel stayed lit past 40 s and showed the GBOOK: display check banner (the owner saw it).

  • msm binds early:
    • [drm] Initialized msm 1.13.0 for ae01000.display-controller on minor 1 at 1.154 s.
    • dpu hardware revision:0x90020000.
    • All three DP controllers bound (ae90000, ae98000, aea0000).
    • fb0: msmdrmfb frame buffer device at 1.595 s, taking over from simpledrm’s fb0 (0.453 s).
    • no GPU device was found: expected with msm.skip_gpu=1.
  • Connectors: card1-eDP-1 status=connected enabled=enabled modes=2560x1600. card1-DP-1 and card1-DP-2 are disconnected (nothing on Type-C), and card1-Writeback-1 is there too.
  • Panel supply: VREG_EDP_3P3 state=enabled users=1 and VBL9 state=enabled users=1 after 40 s. No VREG_EDP_3P3: disabling line this boot.
  • Backlight: backlight max=4095 actual=2048 power=0, a 12-bit PWM at 50 %. Stock caps the level at 0x190 (fragment 11) and 0x200 (fragment 77); mainline uses the LPG’s full range.
  • Panel: Unknown panel LGD 0x0804, using conservative timings. That’s LG Display, product 0x0804, not in edp_panels[] at a804c57. The WARN_ON splat at panel-edp.c:829 is the driver’s deliberate “add a table entry” signal (comment at :824-828), not a fault. Conservative timings only lengthen the power sequencing delays.
  • Deferred at the end: only arm-scmi (mailbox 17430000). 3da0000.iommu was listed at the first check and not at the last.

Not yet captured

  • GBOOK: panel-id=card1-eDP-1 none: sysfs reports the edid attribute’s size as 0, so [ -s ] skipped a readable file. Fixed in commit 2d2f2b5 (test -r; none on a short read). The next image will print the EDID name and text descriptors.
  • The dropbox readback (attempt.sh readback) didn’t run because adb was down after the stock boot (Wireless debugging). Netlog has the whole log, so nothing is missing.

Next for display

  1. Add an edp_panels[] entry for LGD 0x0804. It needs the panel’s datasheet delays; the EDID name from the next image identifies the part. This is for upstream; the panel works without it.
  2. External DP over Type-C (mdss_dp0/dp1 are bound; aux_bridge still reports “failed to acquire drm_bridge” in usb-2026-11-02.md).
  3. The GPU: CLK_X1E80100_GPUCC=y (unblocks 3da0000.iommu), drop msm.skip_gpu=1, and stage the GPU firmware. Zap is off under EL2.

Phase 3: the CDSP boots from stock firmware (2026-11-03)

Attempt 2026-11-03-7. Image 72eebf1b6e678b3f, kernel #6 (2026-11-03 2:23 PM ET, unchanged), commit 19128c0. Sources: the netlog dumps/firstboot/2026-11-03-netlog.txt and the AP console log dumps/firstboot/2026-11-03-ap.log (both gitignored).

Why

Every boot up to attempt -6 logged remoteproc remoteproc1: Direct firmware load for qcom/x1e80100/google/mica/cdsp.mdt failed with error -2 at about 0.57 s, and /init reported cdsp offline. The DTS already named the stock files (remoteproc_cdsp firmware-name, dts/x1e80100-google-mica.dts), mica-el2.fixups already dropped its iommus (as for the ADSP), and QCOM_Q6V5_PAS=y. Only the firmware was missing from the initramfs.

Changes (commit 19128c0)

  • scripts/stage-firmware.sh: stock_mdt adds cdsp cdsp_dtb, read from the same stock modem_a /image/ (recovery-stick copy) as the ADSP. 16 cdsp files and 4 cdsp_dtb files.
    • No cdsp.b14 exists in /image/. The boot shows the loader does not need it.
    • kernel/firmware.sha256: 20 new hashes, no changed ones (90 files in total).
  • /init: reports every remoteproc again at the end (rproc-late: state, recovery), so a DSP that crashes after the early capture shows up.
  • No kernel, DTS or command-line change.

Result: pass

  • CDSP up: Booting fw image qcom/x1e80100/google/mica/cdsp.mdt at 1.876 s, remote processor cdsp is now up at 1.995 s.
  • Still running at the end: rproc-late=remoteproc1 cdsp running recovery=enabled (and remoteproc0 adsp running), after about 130 s, with no crash or recovery lines in the log.
  • Everything else unchanged: Type-C, PCIe, Wi-Fi (6 GHz, DHCP), eDP at 2560x1600, GPU firmware and GMU loaded, netlog sent (1350 lines). reboot: Restarting system at 189.5 s (about 4:00 PM ET).
  • Deferred at the end: only arm-scmi (mailbox 17430000).

Side effect: the ADSP starts 5 s later

  • In attempt -6 the ADSP was up at 0.931 s. Here: powering up adsp at 1.873 s, then qcom_q6v5_pas 32300000.remoteproc: timeout waiting for subsystem event response at 7.408 s, and remote processor adsp is now up at 7.410 s.
  • Cause, from the source at a804c57: when a remoteproc starts, sysmon_start() sends each already-running peer a “powered up” event (drivers/remoteproc/qcom_sysmon.c:496-530). For SSCTL v2 peers, ssctl_send_event() waits up to 5 s for the reply (qmi_txn_wait(&txn, 5 * HZ), :405) and logs this exact message on timeout (:407). The message names 32300000.remoteproc, the CDSP, so the ADSP’s start waited on the CDSP, which never answered.
  • Not known: why the stock CDSP firmware does not answer the SSCTL event. It costs one 5 s delay per boot (Type-C, battery and USB SuperSpeed come up later), with no lasting effect seen.
  • New in this boot, inference: the later ADSP start shifts probe order, so pmic-glink now also logs Failed to create device link (0x180) for a600000.usb (connector@0) and a800000.usb (connector@1). These are the same sync-state-only links as in dp-2026-11-03.md, “The device-link warning”.

Update 2026-11-03: the SSCTL timeout is not mica-specific

Read the same afternoon, no boot:

  • Which side waits: the message carries sysmon->dev, the remoteproc’s parent device (qcom_sysmon.c:635), so 32300000.remoteproc is the CDSP’s sysmon. The ADSP’s sysmon_start() runs the sysmon_notifiers chain (:509); the CDSP’s sysmon_notify() (:585-605) sees ssctl_version == 2 and sends the event to the CDSP. ssctl_version is set only when a matching SSCTL server appears (:425-440, instance 0x17 from x1e80100_cdsp_resource, qcom_q6v5_pas.c:1282). So the CDSP’s SSCTL service is registered and does not answer.
  • Another X1 laptop shows the same line. A Dell Latitude 5455 (x1p64100) on the Ubuntu Concept kernel logs qcom_q6v5_pas 32300000.remoteproc: timeout waiting for subsystem event response at boot, next to the same pmic-glink … Failed to create device link (0x180) lines (Ubuntu Discourse, “Ubuntu Concept ♥️ Snapdragon X Elite”, page 99, post #2008, 2026-07-09). Its CDSP firmware is Dell’s Windows set, not mica’s stock files. No reply there explains or fixes it.
  • No upstream fix found: a web search for the exact message (2026-11-03) found the driver source, an SDM845 modem report and the Dell post, but no patch for x1e80100.
  • Inference: the X1 CDSP firmware does not answer SSCTL subsystem events, whoever built it. The cost is the one 5 s wait in whichever remoteproc starts after the CDSP; nothing else depends on it.

Decision: no change on mica. A kernel fix (skip the event for a peer that never answers, or a shorter timeout) belongs upstream in qcom_sysmon.c and needs evidence from more than two machines.

Next

  • FastRPC (QCOM_FASTRPC=m, so not loaded): needed before anything can use the CDSP.

M3: the sound card binds (2026-11-04, 11:32 PM EST)

Attempts 2026-11-04-26, -27 and -28, M3 ticket 10 (.scratch/m3-rootfs/issues/10-audio-attempt.md), task #19. TEST IMAGES with GBOOK_DTS_EXTRA=dts/diag/sound.dtsi: kernels #30 (g7072998+, image 5e59c669…), #31 (ge8c45c0, c701044f…) and #32 (gc613c1d, 2947943c…); rootfs c1646b8a… (snapshot 20261104T000000Z, 321 packages) on ROOT for all three. Sources: dumps/firstboot/2026-11-04-netlog.txt boots #24 to #26, 2026-11-04-ap.log, the m3-* captures (both gitignored); attempt.sh tally '^2026-11-04-2[678]$': boots=3 clean=3 stalled=0, all cold starts.

Result

aplay -l on boot -28: card 0: X1E80100DellXPS [X1E80100-Dell-XPS-13-9345], device 1: MultiMedia2 Playback; arecord -l: device 3: MultiMedia4 Capture; alsaucm listcards: 0: hw:0 X1E80100-Dell-XPS-13-9345 (Debian’s Dell XPS 13 9345 profile resolves through the link rootfs/customize.sh adds). No audio device in any deferred read, no tplg firmware loading … failed line, so the staged topology qcom/x1e80100/X1E80100-Dell-XPS-13-9345-tplg.bin loaded. Sound itself is untested: no speaker or microphone test ran (the owner’s call, section “Next”).

BootKernelAudio stateEvidence
-26#304 codecs enumerated, all deferred “Failed to get reset”; card deferred “WSA Playback: codec dai not found”AP: GBOOK: deferred=sdw:4:0:0217:0204:00:0 wsa884x-codec: Failed to get reset (and :1, sdw:1 …); m3-aplay=… no soundcards found
-27#31 (RESET_GPIO=y)reset.gpio.0 and .1 created, deferred “Could not get reset gpios”; codecs and card as on -26AP: GBOOK: deferred=reset.gpio.0 reset_gpio: Could not get reset gpios; m3-aplay=… no soundcards found
-28#32 (GPIO_SHARED_PROXY=y)card 0 bound, playback device 1, capture device 3, UCM resolvesAP: m3-aplay=card 0: X1E80100DellXPS …, m3-arecord=… device 3, m3-alsaucm= 0: hw:0; no deferred= line

Common to the three boots (netlog, kernel timestamps): PDR: Indication received from msm/adsp/audio_pd, state: 0x1fffffff at 1.05 to 1.09 s (the stock ADSP offers the audio protection domain); qcom-apm gprsvc:service:2:1: CMD timeout for [1001021] opcode at 6.12 s (APM_CMD_GET_SPF_STATE, audioreach.h:42: the first readiness poll from a DAI probe times out, later ones answer, the card binds; a lead, not a blocker); wsa_macro …: Driver needs fixing: Unsorted reg_defaults and using zero-initialized flat cache (driver warnings, both macros); snd-x1e80100 sound: ASoC: Parent card not yet available, widget card binding deferred at 6.16 s on -28 (the card’s own deferral while the codecs probe).

What the boots proved about the mapping

  • The SoundWire layout: the four WSA8845 enumerate as sdw:1:0:0217:0204:00:0 and :1 (link 1 = swr0, unique ids 0 and 1) and sdw:4:0:0217:0204:00:0 and :1 (link 4 = swr3), as the stock codecs wsa884x@02170220/1 under wsa_swr_master and wsa2_swr_master said. The LPI pins wsa_swr_active (gpio10/11) and wsa2_swr_active (gpio15/16) carry the buses.
  • The shutdown pins TLMM 204 (bus 0’s pair) and 205 (bus 3’s pair) are the codecs’ resets: the codecs bound once the reset chain had its drivers, and the stock overlay’s pin groups are the same pins.
  • Supplies: vdd-1p8 = L15B as in stock; vdd-io = L12B (every other x1e80100 board, not in stock): accepted by the probes, still unverified as the right rail.
  • DMICs and the mic rail: the VA macro bound with dmic01_default/dmic23_default and vdd-micb-supply = <&vreg_dmic_3p3> (the stock overlay’s EN_PP3300_MIC on TLMM 69); capture device 3 exists; no capture test ran.

The two blockers, both kernel options

At a804c57 the WSA884x driver asks the reset framework first (sound/soc/codecs/wsa884x.c:2022, devm_reset_control_get_optional_shared); the reset core turns a reset-gpios property into an auxiliary reset-gpio device (drivers/reset/core.c:1012-1105), one per distinct pin, and defers the consumer until that device’s driver binds. That driver is CONFIG_RESET_GPIO, =m in the arm64 defconfig. With it built in (kernel #31) the reset-gpio devices deferred themselves: TLMM 204 and 205 are each referenced by two codecs, and this kernel’s shared-GPIO support (CONFIG_GPIO_SHARED=y, drivers/gpio/gpiolib-shared.c) flags such a pin GPIOD_FLAG_SHARED and routes every request for it through a gpio-shared-proxy platform lookup (drivers/gpio/gpiolib.c:4805-4834), whose chip only the CONFIG_GPIO_SHARED_PROXY driver registers (=m by default). Both are =y in kernel/mica-audio.config since kernel #32. A distro kernel loads both as modules, which is why the Yoga Slim 7x never showed this.

Timing (boot -28, from the AP console and the journal)

PointUptimeEvidence
audio_pd up1.09 sAP: PDR: Indication received from msm/adsp/audio_pd
first APM poll timeout6.12 sAP: qcom-apm gprsvc:service:2:1: CMD timeout for [1001021] opcode
card’s widget binding deferred6.16 sAP: snd-x1e80100 sound: ASoC: Parent card not yet available
card boundbefore the first deferred read (empty)AP: no GBOOK: deferred= line; the kernel logs no registration line
m3-aplay52.2 sAP: gbook-m3[2233]: GBOOK: m3-aplay=card 0: …
m3-end55.7 sAP: GBOOK: m3-end=uptime=55.69s

The speaker test (2026-11-04, 11:51 PM EST): two of four units play, and the bus degrades

Owner’s OK given. Over ssh mica as root (the gbook user is not in audio: alsaucm and speaker-test see no card without sudo -n; retro item): alsaucm -c X1E80100-Dell-XPS-13-9345 set _verb HiFi set _enadev Speaker (exit 0), then speaker-test -D hw:0,1 -c 4 -t sine -l 1 (exit 0, 4 channels, 48 kHz; all four PA volumes at the cap of 6, digital volumes 81).

Time (uptime of boot -28)PlayedHeard (owner)Evidence
506 s4-channel sine, one looptwo sounds, one louder, one lower; no left/right differenceboot28-speaker-test2.txt (scratch); AP: 6ab0000.soundwire: no slave alert found.spurious interrupt
619 s-t wav (spoken channel names), then two sine loops“Front Right” and “Rear Right” (channels 1 and 3); four beepsAP: 6ab0000.soundwire: qcom_swrm_irq_handler: SWR bus clsh detected
685 to 708 seach channel alone (-s 1..4), 8 s apartone long loud beep, one long softer beepboot28-speaker-test4.txt
736 to 763 seach channel alone at 250/500/1000/2000 Hzno answer recordedboot28-speaker-test5.txt
957 and 974 s4-channel sine with bus 0 muted, then bus 3 muted (digital volumes by numid)nothing, nothingmute-test2.sh
1018 s4-channel sine, every control restorednothingplain-test.sh

What the device said meanwhile (root reads, files in the session scratchpad boot28-*.txt):

  • /sys/kernel/debug/gpio: TLMM gpio204 and gpio205 “out high” (both resets released; “out low” on boot -27 when the reset chain had no driver), gpio69 “out high” (the mic rail).
  • DAPM during a tone (/sys/kernel/debug/asoc/X1E80100-Dell-XPS-13-9345/*/dapm/*): both macros’ WSA RX0/RX1 paths On, and on all four codecs <name> IN: On and <name> SPKR: On. The drivers believe all four play.
  • /sys/bus/soundwire/devices/sdw:*/status: UNATTACHED for all four, before, during and after a tone (runtime_status active during it); the slaves’ debugfs registers read a constant 0x21 (bus 0) or 0 for every register.
  • With dyndbg on drivers/soundwire/qcom.c (1130 s): SWR new slave attached, Slave status not changed 1; then (1148 s, 1260 s) Slave status not changed 101, SWR unknown interrupt value: 2048 (bit 11, SWRM_INTERRUPT_STATUS_AUTO_ENUM_FAILED, qcom.c:62, unhandled by the driver), SWR bus clsh detected, SWR read FIFO underflow fifo status 0x4000000f, on both masters.
  • The masters’ enumerator registers (/sys/kernel/debug/soundwire/master-*/qualcomm-sdw/qualcomm-registers, decoded per qcom_swrm_enumerate, qcom.c:636-686): on each bus SWRM_MCP_SLV_STATUS = 0x1 (a slave attached at device number 0, never enumerated) and one enumerated slot, slot 1, id 0x02170221: version 2, unique id 1, mfg 0x0217, part 0x0204. No slot carries unique id 0 on either bus.

Reading: on each bus the hardware auto-enumerator numbers only the unique-id-1 amplifier; the other amplifier stays at device number 0 (AUTO_ENUM_FAILED, bus clashes), so the DT’s speaker@0,0 slaves never attach and get no port setup. The audible pair are the two id-1 units: stock names bus 0’s wsa884x@02170221 “SpkrRight” and bus 3’s “Spkr2Right” (the downstream node names are these very register values), so the owner heard the right-side woofer and tweeter: one loud, one soft, no left/right difference. The left pair (stock @02170220, unique id 0) never enumerates. Why is open: both amplifiers on a bus share one reset pin (as in stock), the same rails, the same bus; candidates are an id-0 amplifier that answers the enumeration read late or not at all, two amplifiers strapped to the same unique id (a clash on the id read), or a reset/timing order the stock driver handles and mainline’s does not. Sound then stops altogether after some minutes: the re-enumeration after each bus clock-stop fails the same way, and reads of the id-1 slaves fail too (FIFO underflow), inference from the debug lines at 1148 and 1260 s. sdw status UNATTACHED for the id-1 slaves while they played is unexplained (the sysfs attribute may lag the hardware enumeration; unverified).

The debug boot (2026-11-05, 12:25 AM EST): all four enumerate at boot; the clock-stop exit loses them

Attempt 2026-11-05-1: the same TEST IMAGE with dyndbg="file drivers/soundwire/bus.c +p; file drivers/soundwire/qcom.c +p; file sound/soc/codecs/wsa884x.c +p" (image 22c46be8…, kernel #32), rootfs 6fea12f2… (the audio group, the m3-swrm capture). Cold start, tally clean=1. Evidence: dumps/firstboot/2026-11-05-netlog.txt boot #1, 2026-11-05-ap.log, dumps/firstboot/2026-11-05-ssh-*.txt (the mica-ssh.sh reads).

UptimeWhatEvidence
6.15 to 6.16 sboth buses enumerate both amplifiers: unique_id 0x1 as Slave 1, unique_id 0x0 as Slave 2, “signaling initialization completion” for all fourAP: soundwire sdw-master-4-0: SDW Slave Addr: 210217020400 … 200217020400; wsa884x-codec sdw:4:0:0217:0204:00:0: signaling initialization completion for Slave 2 (and bus 1)
45.96 sthe first aplay -l wakes bus 0 from clock stop: Slave 1/2 state check1: UNATTACHED, two attach interrupts with Slave status not changed 0, then both re-enumerate; a burst of SWR read FIFO underflow followsAP lines at 45.956 to 46.069 s
47.15 sbus 3 wakes the same way, with one SWR bus clsh detected, both re-enumerateAP lines at 47.145 to 47.157 s
48.25 sm3-swrm=1-0: status=0x14 dev1=1 dev2=1 slot1=v2:uid1:0217:0204:00 slot2=v2:uid0:0217:0204:00; 4-0: (the same)AP: GBOOK: m3-swrm=
69.9 sthe first tone wakes both buses: UNATTACHED, SWR new slave attached three times, Slave status not changed 0, and no SDW Slave Addr line: neither bus re-enumerates; the enumerator reads status=0x1 dev0=attached no entry afterwards; owner heard nothing2026-11-05-ssh-audio-state.txt (first run); AP lines at 69.910 to 74.012 s
145.9 sthe second tone: bus 0 re-enumerates both, bus 3 gets SWR unknown interrupt value: 2048 (AUTO_ENUM_FAILED), swrm_wait_for_wr_fifo_avail err write overflow, and its id-0 codec’s resume fails (ASoC error (-61) … pm_runtime_get); owner heard nothingsecond audio-state run; AP lines at 145.86 to 153.46 s
202.7 spower/control=on on both masters (runtime PM forced on): both buses re-enumerate both amplifiers (status=0x14 on both, held through a second tone 27 s later); speaker-test -t wav: the owner heard “front left, front right, rear right, rear left”; the sine: a loud, a mid and a soft long beep2026-11-05-ssh-noclkstop.txt (both enumerator dumps decoded); AP lines at 202.767 to 202.790 s; Parity error detected twice on bus 3’s codecs

Reading: the amplifiers and the mapping are right (all four enumerate at boot, with the unique ids the DT says), and the two silent units of boot -28 were the losers of a race the Qualcomm master runs on every exit from clock stop: the NEW_SLAVE_ATTACHED interrupts arrive before the hardware enumerator has filled its table, qcom_swrm_enumerate finds nothing and completes, and the driver never retries (qcom.c:636-686; AUTO_ENUM_FAILED is unhandled, qcom.c:62). Which bus wins is chance: both at 45.9 s, neither at 69.9 s, one at 145.9 s. With the masters kept clocked (/sys/bus/platform/devices/6b10000.soundwire/power/control and 6ab0000.soundwire set to on) the slaves never detach and all four play. The read FIFO underflow bursts on every wake (the codecs’ regcache_sync reads) and the two Parity error detected lines remain open; they did not stop playback. The stock kernel’s log shows its driver meeting the same class of errors with retries (swr_mstr_interrupt: SWR CMD error … flushing fifo, swrm_check_link_status; dumps/firstboot/2026-11-05-stock-lastkmsg-swr.txt), and assigning devnum 1 to the id-1 units in the saved window; its VPHX EN Status line for 0220 is a register read that needs the device number, so both amplifiers per bus enumerate on stock too, and on AUTO_ENUM_FAILED its driver re-arms the hardware enumerator, which mainline’s does not (asoc-debug-tools.md, “The downstream driver”).

The udev-rule boot (2026-11-05, 1:01 AM EST): the rule’s own write is the first wake, and bus 1 lost it

Second boot of the same TEST IMAGE (22c46be8…, kernel #32; attempt 2026-11-05-1, netlog boot #2, clean), rootfs 59781d60… with /etc/udev/rules.d/90-gbook-soundwire.rules. Cold start, m3-end at 51.9 s. Evidence: dumps/firstboot/2026-11-05-ap.log (the second boot), 2026-11-05-netlog.txt boot #2, 2026-11-05-ssh-audio-state.txt (the run at 12:59 AM EST).

UptimeWhatEvidence
6.15 to 6.16 sall four amplifiers enumerate, as on the first bootAP: SDW Slave Addr: 210217020400 … 200217020400 on sdw-master-1-0 and -4-0
35.93 sthe udev trigger’s power/control=on resumes both masters: a clock-stop exit on each. Bus 4 (6ab0000) re-enumerates both (Slave status not changed 14, then the two SDW Slave Addr lines by 36.00 s). Bus 1 (6b10000) gets SWR unknown interrupt value: 2048 (AUTO_ENUM_FAILED), Slave status not changed 101, three SWR new slave attached and no SDW Slave Addr lineAP lines at 35.926 to 36.049 s
about 7 minaudio-state.sh --tone: masters control=on status=active on both; decode master-1-0: status=0x1 dev0=attached no entry, master-4-0: status=0x14 dev1 dev22026-11-05-ssh-audio-state.txt

Reading: the rule cannot protect the first wake, because its write is that wake (the masters autosuspend after probe, and control=on resumes them through the same clock-stop exit). Bus 1 lost that one race and stays lost: the enumerator stalls as on the first boot, with no later wake to re-roll it. The workaround is a coin toss per bus per boot, so it is not the workaround; patch 0010 (which re-arms the stalled enumerator) is the fix under test next, on the same rootfs.

The patch boot, revision 1 (2026-11-05, 1:14 AM EST): the resume wait reads a stale status; a loss outside the resume path too

Attempt 2026-11-05-2: kernel #33 (7.3.0-gbook-2026.11.04-29-g50ac603-…, patch 0010 revision 1), image adf0c522…, the same extras, rootfs 59781d60… (the udev rule). Cold start, m3-end at 50.4 s. Evidence: dumps/firstboot/2026-11-05-ap.log (third boot), 2026-11-05-ssh-audio-state.txt (runs at 1:06, 1:07 and 1:11 AM EST), 2026-11-05-ssh-pm-auto.txt, 2026-11-05-ssh-swr-journal.txt.

UptimeWhatEvidence
6.16 sall four enumerateAP: the four SDW Slave Addr lines
35.99 sthe rule’s control=on wake: both buses re-enumerate both (no stall this time)AP lines at 35.986 to 36.000 s
about 1:06 AMtone 1, masters on: both buses 0x14; the owner heard the three loudness levelsfirst audio-state run
1:07:42 AMowner OK: power/control=auto on both masters; both suspended within 4 s2026-11-05-ssh-pm-auto.txt
146.11 stone 2’s wake. Bus 1: status 0, then 0x1 on the third attach interrupt, no SDW Slave Addr, no AUTO_ENUM_FAILED, no patch warn. Bus 4: both re-enumerated and initialised by 146.15 s, then at 146.37 s both UNATTACHED, status was 1 again; the dump ends status=0x1, slot1=v0:uid1:0000:0000:00 (a zeroed table entry)journal lines 146.112 to 146.369 s; second audio-state run
1:11 AMtone 3: both buses still 0x1 dev0=attached no entrythird audio-state run

Reading: revision 1’s wait in swrm_runtime_resume never fired. Right after the link comes back the status register still reads the value from before the reset (both peripherals present), so the wait’s condition held at once; the real detach and re-attach come later, through the interrupts. And a loss happened 240 ms after a good re-enumeration, outside the resume path (bus 4 at 146.37 s), which no resume-side wait can see. Revision 2 (the next boot) checks 100 ms after every status change and every resume whether a peripheral still sits at device 0, and re-arms the enumerator then; while the bus is short of its declared peripherals it re-checks up to ten times. Note for the record: with the masters set to auto the buses did not recover by chance across two further wakes.

The patch boot, revision 2 (2026-11-05, 1:25 AM EST): the check sees the stall, the downstream re-arm does not clear it

Attempt 2026-11-05-3: kernel #34 (patch 0010 revision 2), image f8d11625…, the same extras and rootfs. Cold start, m3-end at 51.2 s. Evidence: dumps/firstboot/2026-11-05-ap.log (fourth boot), 2026-11-05-ssh-swr-journal-2.txt, 2026-11-05-ssh-pm-auto-2.txt, 2026-11-05-ssh-audio-state.txt (runs at 1:20 and 1:21 AM EST).

UptimeWhatEvidence
6.16 sall four enumerateAP: the four SDW Slave Addr lines
36.95 to 37.10 sthe rule’s wake: both buses re-enumerate both; the new check reports enumeration check 0: slave status 0x14, enumerator status 0x0, 2 of 2 attached on eachAP lines at 36.953 to 37.100 s
about 1:20 AMtone 1, masters on: both buses 0x14; the owner heard all the beepsfirst audio-state run
1:20:59 AMowner OK: auto on both masters; both suspended within 4 s2026-11-05-ssh-pm-auto-2.txt
116.52 stone 2’s wake. Bus 1: UNATTACHED, SWR bus clsh detected, status 0 then 0x1. Bus 4: Slave status not changed 1, then AUTO_ENUM_FAILED (the new handler: “re-arming the enumerator”). 100 ms later the check on each bus: enumeration stalled (slave status 0x1, enumerator status 0x3, 0 of 2 attached): re-arming the enumerator, enumerator status 0x3 not idle before re-arm; ten checks per bus, 100 ms apart, every one the same; the dump afterwards: both status=0x1 dev0=attachedjournal lines 116.516 to 117.604 s; second audio-state run

Reading: the detection is right and the downstream recipe is not enough here. SWRM_ENUMERATOR_STATUS sits at 0x3 (yesterday 0x6), and writing SWRM_ENUMERATOR_CFG 0 then 1 leaves it there. What has cleared this state every time so far is the boot sequence: SWRM_COMP_SW_RESET, qcom_swrm_init (the CGCR reset, the frame, ping and FIFO configuration, the enumerator enabled, the clock started), then the slaves attach and enumerate (8 of 8 at boot across four boots, and both buses at 35.9 to 37.1 s on every rule wake). The clock-stop exit does only the CGCR reset and the clock restart (qcom.c:1751-1778 at a804c57), and that is the path every wake has taken, because WSA884x sets simple_clk_stop_capable and not clk_stop_mode1 (wsa884x.c:2094); WSA881x sets clk_stop_mode1 (wsa881x.c:1134) and its controllers take the full-init path on every resume. Revision 3 of 0010 escalates a re-arm that leaves the enumerator busy to the reset-and-init sequence; patch 0011 sets clk_stop_mode1 for WSA884x so every resume takes the full-init path in the first place. Both for the next boot.

The patch boot, revision 3 and patch 0011 (2026-11-05, 1:34 AM EST): three wakes, all four amplifiers back each time

Attempt 2026-11-05-4: kernel #35 (7.3.0-gbook-2026.11.04-35-g7c6a1aa-…, patch 0010 revision 3 and patch 0011), image 62a2e345…, the same extras and rootfs (59781d60…, the udev rule). Cold start, m3-end at 50.4 s. Evidence: dumps/firstboot/2026-11-05-ap.log (fifth boot), 2026-11-05-ssh-pm-auto-3.txt, 2026-11-05-ssh-audio-state.txt (runs at 1:31, 1:32 and 1:33 AM EST).

UptimeWhatEvidence
6.15 sall four enumerate; enumeration check 0: … 2 of 2 attached on both buses at 6.26 sAP lines at 6.147 to 6.257 s
36.21 sthe rule’s wake, now through the full-init resume path (0011): both buses 2 of 2AP lines at 36.211 s
about 1:31 AMtone 1, masters on: both buses 0x14; the owner heard all the beepsfirst audio-state run
1:32:43 AMowner OK: auto on both masters; both suspended within 4 s2026-11-05-ssh-pm-auto-3.txt
173.58 stone 2’s wake: both buses UNATTACHED then SDW Slave Addr for both amplifiers within 300 ms (bus 1 by 173.60 s, bus 4 by 173.86 s); 2 of 2 attached on both; no stall, no AUTO_ENUM_FAILED, no escalation; the owner heard all the beepsAP lines at 173.584 to 173.973 s; second audio-state run
212.88 stone 3’s wake, a minute later: the same, 2 of 2 on bothAP lines at 212.877 to 213.137 s; third audio-state run

Reading: with clk_stop_mode1 set on WSA884x (0011) every resume runs SWRM_COMP_SW_RESET, qcom_swrm_init and the enumeration wait, the sequence that has enumerated at every boot, and three wakes in a row came back whole where boots -2 and -3 lost a bus on the first. Revision 3’s escalation (reset and init from the check) did not have to fire; its detection lines (2 of 2 attached) are the evidence that each wake completed. Open: whether 0011 alone is the fix (0010’s escalation is unexercised), the 10-minute idle case from the ticket’s bar, the Parity error detected lines, and the udev rule, which can now go.

The rule-free boot (2026-11-05, 2:05 AM EST): the full bar on kernel #35, no udev rule

Second boot of attempt 2026-11-05-4’s image (kernel #35, patches 0011 and 0010 revision 3), rootfs 6fea12f2… (no udev rule: the masters read control=auto status=suspended before every tone). Cold start, m3-end at 51.8 s. Evidence: dumps/firstboot/2026-11-05-ap.log (sixth boot), 2026-11-05-netlog.txt boot #6, 2026-11-05-ssh-audio-state.txt (runs at 1:53, 1:54 and 2:04 AM EST), 2026-11-05-ssh-speaker-names-2.txt.

UptimeWhatEvidence
6.27 sall four enumerate; 2 of 2 attached on both busesAP lines at 6.265 and 6.273 s
46.5 and 47.6 sthe M3 script’s own reads wake the masters: 2 of 2 eachAP lines at 46.457 and 47.641 s
64.0 stone 1 (1:53 AM): both buses 0x14; the owner heard the beepsAP lines at 63.993 to 64.257 s; first audio-state run
129.4 stone 2 a minute later: both 0x14; the owner counted eight beeps (four channels, two loops)AP lines at 129.427 to 129.685 s; second run
761.8 stone 3 after 10 minutes idle (2:04:39 AM): both 0x14, 2 of 2AP lines at 761.847 to 762.121 s; third run
about 2:05 AMspeaker-test -D hw:0,1 -c 4 -t wav -l 1: Front Left, Front Right, Rear Right, Rear Left (first try -16 busy: the tone was still playing)2026-11-05-ssh-speaker-names-2.txt

Reading: every wake of this boot (five, counting the script’s reads) brought both buses back whole; no stall, no AUTO_ENUM_FAILED, the escalation never fired. The owner’s hearing reports for tone 3 and the names are in ticket 16.

The capture test (2026-11-05, 9:26 AM EST): the four DMICs record, and the owner heard the playback

Boot 7 of the day (netlog #7, attempt 2026-11-05-4’s image, kernel #35, rootfs 6fea12f2…; cold start, clean, SCMI 0.1 ms), the TEST IMAGE still on the stick (dts/diag/sound.dtsi extra; the same node sits in the board DTS since commit 9d120a5). Over ssh: alsaucm -c X1E80100-Dell-XPS-13-9345 set _verb HiFi list _devices lists 0: Speaker and 1: Mic (“Internal microphones”); one invocation enabled both (set _enadev Speaker set _enadev Mic), then arecord -D hw:0,3 -c 2 -f S16_LE -r 48000 -d 10 /tmp/mic.wav while the owner spoke, then aplay -D plughw:0,1 /tmp/mic.wav (exit 0). The owner heard their own voice from the speakers (report at 9:26 AM EST). The file, measured on the build host (dumps/firstboot/2026-11-05-mic.wav, gitignored): 480000 frames, RMS about 900 on the left channel and 1200 on the right, varying second by second (1506 and 2776 in the first second, 444 and 408 in the last), peaks at full scale once per channel (inferred: the start click). The journal has no VA macro, DMIC or LPASS line for the capture. So the mic rail (EN_PP3300_MIC, TLMM 69, as vdd-micb-supply) and the DMIC clock (qcom,dmic-sample-rate = <4800000>, divider 16) work as described; dts/README.md, “Unverified”, records which values stay the Yoga’s. Evidence: dumps/firstboot/2026-11-05-ssh-ucm-devices.txt, -ssh-capture.txt, -ssh-playback.txt, -ssh-va-journal.txt.

The 0011-alone boot (2026-11-05, 12:32 PM EST): patch 0011 carries the fix

Attempt 2026-11-05-6 (kernel #36, patches 0001 to 0009 and 0011, 0010 set aside for this build only and restored in the repo; the release string carries + for the set-aside file; TEST IMAGE bf48ab37…, SoundWire dyndbg; rootfs 6fea12f2…; cold, clean). Tone 1 at 12:29 PM EST (first wake), tone 2 at 12:31 PM EST after both masters read suspended, the names right after: every wake re-enumerated both amplifiers on both buses (master-1-0 and master-4-0 status=0x14 dev1=attached dev2=attached), the journal shows only the read FIFO underflow bursts at each wake, no stall, no AUTO_ENUM_FAILED. The owner heard tone 1, tone 2 and the four names twice (reports at 12:30 and 12:32 PM EST). So the full-init resume path that 0011 selects (clk_stop_mode1 on WSA884x) is the fix; 0010’s stall check and escalation never had to act on kernel #35 and are absent here with the same result. Owner decision (12:38 PM EST): 0010 stays until the M5 patch review, then it is dropped (one patch fewer to defend). The same boot showed /init’s new GBOOK: lock-ro=none: only the stick (sda) (task #36). Evidence: dumps/firstboot/2026-11-05-ssh-audio-state.txt (the last three runs), 2026-11-05-ssh-swr-journal.txt, netlog boot #11.

Next

  • Workaround in the rootfs: not a workaround (“The udev-rule boot”); to be removed from rootfs/customize.sh now that the patches pass.
  • The driver fix passes the ticket’s bar (“The rule-free boot”, 2:05 AM EST): tones a minute apart, a tone after 10 minutes idle, the four names, all on the rootfs without the udev rule. Both patches stay (ticket 16). The 0011-alone boot (“The 0011-alone boot”, 2026-11-05) shows 0011 carries the fix; 0010’s fate is the owner’s call.
  • The reads planned on 2026-11-04 for the left amplifiers ran on 2026-11-05-1 (“The debug boot”); the stock log read is now scripts/recon.sh stock-kmsg '<regex>'.
  • Tools for those reads: asoc-debug-tools.md (research pass, 2026-11-04).
  • The ssh user joins audio (rootfs/customize.sh group loop), so the tests run without sudo -n.
  • The speaker test as run tonight is the regression check: speaker-test -D hw:0,1 -c 4 -t wav -l 1 right after boot, all four names audible. The PA volumes stay at the driver’s cap of 6 (x1e80100.c:42-51).
  • Capture test: passed (“The capture test”, 2026-11-05). The sound node is in the board DTS (commit 9d120a5). Next: a base image, three clean cold boots, the release.
  • The APM first-poll timeout at 6.12 s: read q6apm.c for the poll’s caller and timeout; a lead for boot time, not for function.