git:20260920.86ae6d3 to git:20260921.e3f6d5c

37 added, 7 removed. Audit A to A.

---
name: linux-phone-porting
description: Use for every hardware bring-up or debug session that ports mainline Linux to a phone or tablet. Retail-unlock targets require an already-unlocked bootloader; locked retail bootloaders are out of scope. Exploit-booted and firmware-booted targets require a demonstrated boot path; the skill never provides unlock or exploit steps.
---
# Linux Phone Porting
A debug iteration costs a build + flash + boot (5-10 min); blind fixes get refuted more often than they land. Follow this order strictly.
+ **The chain drives itself; the operator owns the decisions.** When a phase's exit condition is met, start the next phase's entry yourself: setup complete → evidence on the first symptom; evidence captured → the phase 2 sweep opens its ledger; ledger full → hypotheses ranked with falsifiers into phase 3's gate; gate passed → the cheapest decisive experiment, then verification, acceptance and cleanup; three refutations → back to phase 2, wider. Do not wait to be told the next step, and do not stop at a phase boundary to summarize — report compactly and continue. Ask, never guess, at the decision points that belong to the operator, and those are the only stops: unresolved target identity or an unspecified interface/session (phase 0 asks), a boot path the operator must complete (locked bootloader, missing payload demonstration — resume when done), the loop's refutation budget, anything that can destroy excluded userdata or otherwise unrecoverable state, physical hands, and pause/resume prerequisites. Everything else advances.
+
Scope: mainline Linux bring-up on phones and tablets plus integration of the selected OS userspace — a method, not certification of any OS/device combination. The userspace need not be GNU/Linux; non-Linux kernels are out of scope.
## 0. Set up the port
Once, before the first flash. The stock system is the highest-authority research source for this board, and parts stop being readable once overwritten.
- **Establish the target** (every later answer depends on it):
+ **Establish the target** (every later answer depends on it; every decision here is asked and user-confirmed — an existing repo, monorepo layer, staged OS image, prior context file or sibling device's choice near the project is a candidate to offer, not an answer; artifacts establish what is present, never what is wanted, and one target OS inferred from surrounding layout had to be superseded by the operator's actual selection):
- - **Exact device identity** — marketing name, codename, SoC, regional/storage variant. Sibling variants of one name differ in panel, touch, modem; a fix researched against the wrong variant looks plausible and fails. Record sibling codenames — phase 2 enumerates their ports.
+ - **Exact device identity** — marketing name, codename, SoC, regional/storage variant. Sibling variants of one name differ in panel, touch, modem; a fix researched against the wrong variant looks plausible and fails. Record sibling codenames — phase 2 enumerates their ports. On firmware-boot targets DMI/SMBIOS cannot even be trusted for the SoC generation: one family's DMI string appeared in no documentation while one marketing name spanned two SoC generations with zero portability (DT, patches, community tooling all generation-specific). Discriminate by the stock OS's CPU identity string plus per-core max frequency as tiebreaker, and verify later against the running system's board description; a "no port exists" sweep verdict must name the CPU generation it searched for, or it may be reporting the sibling's ecosystem — on one device all community port effort sat on the newer generation.
- **Engineering stage** — EVT/DVT/PVT, board identity, carrier/SKU, bootloader product + build fingerprints. Reconcile before choosing images: one presumed retail handset was an EVT board for a different product while Android reported `user/release-keys`. Build labels do not establish production hardware.
- **Target OS/platform and integration contract** — chosen release, required kernel lineage, startup/service manager, hardware-service interfaces, build/image/update model, evidence/control tools; verify against the target project's current docs. Linux-based ≠ mainline-compatible; a working Linux driver ≠ target-framework or application support.
- **Target user interface/session** — ask when unspecified; never assume. Prefer the platform's supported interface/session stack over replacing it during bring-up.
- **Boot-model class** — classify before anything else about boot; the class selects the write gate, the backup route and the board-authority artefact (boot-image DTB / ACPI tables / DT inside the vendor kernel image):
- **Retail-unlock** — OEM unlock, boot images, A/B slots. **Unlocked bootloader only** — no unlocking steps; point at the OEM's instructions and resume when unlocked.
- **Exploit-booted** — bootROM-vulnerable devices; no unlock state exists. Gate: a payload boot path demonstrated on this device; the payload environment is a candidate control channel and recovery path — prove both. No exploit-landing steps; point at the payload project's own instructions.
+ - **Session mechanics — every naive liveness signal false-positives.** Enumeration shows the right VID:PID for a zombie session; the exploit tool's own verifier can print success without running its stage machine on a stale session; a session contaminated by dozens of failed races reads back as pwned while untrustworthy (one session burned ~3 h this way). Working gate: a USB node number different from the one recorded at last session close, a GETSTATUS-class control transfer answered within a short timeout (timeout = zombie), and the full stage trace in the exploit log — a success line with zero stages _is_ the false-positive signature. Bound exploit attempts per session; after repeated failed races, re-enter clean DFU instead of continuing on the contaminated one.
+ - **The pwn exists only inside the live session.** An idle payload session dies on its own; "pwned then later gone" means the session died — check what re-enumerated before theorizing a physical decay (one such theory was refuted in seconds by a raw descriptor read). Pre-stage everything downstream so exploit-success → payload-load is seconds, not minutes (one 25-minute grind was dead ~9 minutes later); record node + verified state at session close.
+ - **Success announces itself as USB re-enumeration to a payload-specific PID** — the device leaves the exploit PID entirely, and different payload builds present different PIDs; watching the wrong PID reads as "loader silently does nothing" (one attempt wrongly concluded the whole approach dead). Record the expected PID map per build beside the udev rules; cover every chain stage's VID:PID up front (each stage re-enumerates). The `uaccess` tag can silently grant nothing (logind seat mismatch) — fall back to a deterministic group rule; and a newly installed rule does not apply to the already-plugged node, which is exactly the mid-chain node — replug or manual chgrp before the session continues. Interactive payload-console clients block forever without piped stdin: drive them as `printf '…\n' | client`.
+ - **Never diagnose device state from a host tool's parsed rendering of a USB string descriptor** — read the raw descriptor with an oversized buffer. Host tools, and even the exploit suite itself, have read a truncated serial with the payload marker cut off, concluded "not pwned", and re-exploited an already-pwned session on every attempt; the misdiagnosis fed a physical "pwn decay" theory for hours before a raw-byte read retired it. The wrapper-corrupts-evidence rule extends into descriptor space, and the corrupting reader can be your own tool.
+ - **Mine a working project's boot scripts for the success signal and undocumented prerequisite steps** — do not derive the flow from docs alone. A sibling project's flash script yielded steps no documentation mentioned (an explicit reset between exploit and payload load, missed by every doc-derived attempt). Pre-register the falsifier for the first tethered boot: one clean-session roll with a re-enumeration-poll timeout and named fallback flows.
+ - **Chain binaries get full provenance or the chain is unreproducible.** Third-party CI-built and locally built payloads that no package manager provides: per-file origin (exact feed/zip), SHA-256, local build recipe with the pinned source revision; the tool environment defaults to the hashed copies via variables so no invocation silently picks up an untracked binary.
+ - **Entitlement-gated refusal is not absence.** A readable retail OS may refuse its own diagnostic surfaces — an empty registry dump or a named storage-info failure proves the surface exists but is gated; the verdict is "unknown, evidence-backed", not "not exposed". Capture stdout and stderr as sibling files so refusals are first-class evidence, run every stock-side diagnostic **before** exploit work starts (the payload environment is the fallback surface and only exists after the chain works), and pre-register standing operational facts the stock OS already revealed — one end-of-life battery (cycle count + corroborating brown-out panic) became "expect spontaneous shutdowns, keep tethered, treat uncommanded power loss as routine" before any chain work; a capacity field returning a constant is a stub, not data.
- **Firmware-boot** — UEFI or equivalent; lock state is often irrelevant to booting external media, but record it where the chain has one — some UEFI-lineage chains gate writes through their own unlock state and service tooling; where it gates writes, apply the retail-unlock gate to it. Gate: a demonstrated boot path. The board description may be ACPI tables, not a DTB.
+ - **Filesystem encryption is a phase 0 gate, before wipe/resize decisions.** Record per-volume encryption status; the other OS can never read the recovery key for Linux and a resize of an encrypted volume fails until decryption fully completes (measured 10–60 min for a half-full 256 GB SSD). Record only _that a key was exported and where_ — never the key. Record the wipe-vs-dual-boot decision and which partitions (e.g. factory recovery images) are intentionally forfeited before any destructive step.
+ - **The stock OS is the pre-install control channel.** Its built-in ssh server replaces per-iteration operator hands: streaming harvests (driver/firmware stores, ACPI dumps) and even staging boot files onto the install medium, hashes verified host-side. The session's elevation follows the stock OS's own UAC configuration — know which you have. `wmic`-era tools are gone on current builds (use the OS's current management API). Do not route transfers through the install medium: its data partitions may be unreadable from the stock OS and its system partition may reject writes without elevation.
+ - **Harvest the Linux firmware before the wipe.** On Windows-on-ARM targets the WLAN/BT/modem/DSP/GPU firmware lives in the driver store (`System32/DriverStore/FileRepository`) and is unobtainable afterwards — harvest filtered by SoC-number substring (one device: 99 package dirs, 726 MB / 1083 files), keep duplicate retained versions, file with dirlist + hashes + a README mapping each payload to its future firmware-path consumer, marked "reference only, not a restore source".
+ - **Harvest the ACPI board authority from the stock OS** with the platform's dump/disassemble toolchain; if the toolchain mirrors are unreachable, the binaries ship inside GUI utilities. Disassemble the DSDT naming _every_ SSDT so cross-table externals resolve. Then read absences as architecture: zero battery/AC method objects means the whole power path runs through an embedded controller on a bus the SoC dtsi may not even instantiate — and a mainline driver whose binding names this SoC may never have been tested on it: bindings text ≠ hardware validation.
+ - **Map physical connector → controller wiring from the stock OS before writing any DT node**: with the device you will boot from plugged into the port you intend to use, walk the device's parent chain up to the first ACPI node — that instance names the owning controller (confirm its MMIO base in the DSDT). The same pass classifies unwired controllers: present-and-healthy-with-no-devices = dead on this board. One board's convention-based port guess cost two boot rounds before the stock-OS walk resolved the ports differently.
- **Boot topology** — A/B or not, slot layout. Decode slot state from the on-disk structure, not documented layouts (one SoC kept it in GPT partition-entry attribute bits, not the documented control partition). After a crash-loop, refill the tries-remaining budget before interpreting the next failure.
- **Recovery** — custom recovery installed? Convenience, not requirement.
Vendor A/B bank names do not establish seamless-update/slot/fallback semantics — establish the implementation before manipulating banks. Empty security properties = unknown, not unlocked. Same-SoC trees/firmware/signed programmers are candidates, not certified recovery paths; an unreleased prototype's only installed system may have no compatible replacement — preserve it, restorable backup before destructive experiments.
**Back up every readable partition except userdata** — a class that exposes no partitions is backed up through its demonstrated route (payload environment, preboot environment, full-storage image) with coverage recorded as it is:
- Userdata excluded: bulk, private — leave a DO-NOT-RESTORE note in the backup. Exclusion does not authorize its loss; before any operation that may wipe it, explain consequences + preservation options and obtain separate informed authorization.
- **Device-unique partitions — back up, never publish.** Modem NV/EFS, persist, calibration siblings carry IMEI, radio calibration, sensor trim; unreproducible; corruption leaves a phone that cannot register. Critical AND private.
- **Private calibration stays in a protected runtime path.** Validate provenance, length, integrity checks before use. Values must not enter source, DT, build inputs/outputs, arguments, logs or public reproductions (one audio integration kept them out of the build store and process args; a lens integration read them at runtime, not compiled in). Missing/invalid calibration blocks the operation — never borrow another handset's values or guess.
- Hash every image; record hashes beside the partition table. Only a hash answers "is the device still what I backed up."
- Verify restorable before the first risky flash, not after.
+ - **Record what the backup route excludes by design, with counts and the size expectation.** Unencrypted mobile-OS backups omit the credential store entirely and never include app binaries — a large-storage device yielding a few hundred megabytes and ~a thousand files is success, not truncation. A coverage record without the by-design exclusions gets a good backup re-diagnosed as failed.
- **EDL is the fallback backup route when boot-based channels cannot read — prove the channel, don't assume it.** A signed firehose programmer (OEM EDL package or custom-ROM bundle) grants raw storage; its rawprogram XML doubles as an independent partition-map cross-check. Programmers are per-variant; a mismatched one can configure cleanly and still not stream.
- **Bulk dump loops abort at the first zero-byte or short read.** A run whose partition-table read itself returned zero bytes still recorded ~50 void per-partition failures. Probe the smallest read (GPT) first; diagnose the loader/programmer instead of iterating a void list.
**Research value of the backup:**
- **Stock board description — boot-image DTB, ACPI tables, or the DT carried inside the vendor kernel image, per boot-model class** — board-specific configuration authority over a sibling SoC's dtsi once the variant/overlay is established; not proof of fitted silicon, physical wiring or measured voltages. Preserve conflicts until resolved.
- **Firmware blobs + load order**, **vendor kernel cmdline + boot-image layout where the class has one** (offsets, header version, args), **exact kernel version string** (`uname -a` / `/proc/version` — selects the right GPL OEM release in phase 2; vendors ship several, they differ), **vendor configs** (sensor/modem/HAL interfaces mainline must satisfy).
**Check storage health before trusting device reports:**
- Record the wear/lifetime report in phase 0 (UFS life-time %, eMMC health register). Storage is the classic misdiagnosis: I/O errors read as a dying drive; the wear % proved it healthy and one more stock-ROM flash cleared the fault. The % separates "hardware dying" from "software state bad" — opposite next steps.
- A media-failure verdict that would retire the device must survive **re-probe in a later session**: one flash region refused writes twice (cold power-off included), then passed 3/3 the next day against a full zero-error read scan. Absent wear fields = unknown; coarse values exclude nothing.
**Inventory + harvest while the stock system is present:**
- **Inventory every component the stock system names** — panel, touch, sensors, cameras, modem + RF config, WLAN/BT, charger + fuel gauge, audio — and compare against this variant's official spec sheets: `find-docs` for component docs, `wigolo` for web-found material (its cache matters across sessions); else plain web search. Sibling variants differ exactly here.
- **Evidence level** — declared / enumerated / driver-bound / exercised, no implied progression. A HAL name can reflect software ancestry, not silicon; installed firmware may cover unfitted components. One camera advertised 1080p30 in USB descriptors while Android exposed at most 1024×768 and 27 fps; no frames captured, so throughput was never measured.
- **Readable retail beats a ROM image.** On Android that is root: `getprop`, `/proc/config.gz`, mounted vendor/odm trees, HAL/sensor configs, calibration artefacts, factory field-test modes. Other retail OSes have their own readable surfaces — enumerate what this one exposes and harvest it through them. Privilege steps are the user's — no rooting or jailbreaking steps; resume when access exists.
- **Record the newest community custom OS** (on Android: custom ROM) as an optional development source: the most responsive OS ever run on one device was an unofficial recent-Android build, and its boot image shares the stock downstream lineage, so its DTB cross-checks the phase 0 extraction. A bundled EDL package also carries the signed programmer + rawprogram map.
**Keep a persistent component–wiring–document inventory**, seeded from stock, extended in phase 2, in the project's existing format. One research pack linked 134 component/subsystem records to 47 document records — not 134 fitted chips. Per component record:
| Field | Content |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identity | Stable ID, function, manufacturer, exact/partial part; distinguish silicon, package suffix, module, flex/board marking. |
| Evidence | Source location + applicability; measured chip ID, stock/schematic declaration, external photo, alternative, inference, unknown. Preserve contradictions + what would resolve them. |
| Connections | Bus/address, endpoints, muxes, clocks, supplies, reset/IRQ; claim → evidence link; unresolved routes marked. |
| Documents | Linked document IDs, pages/sections, questions answered, gaps. |
| Port status | Driver/binding/firmware dependencies; separate declared/enumerated/driver-bound/exercised results. Identity ≠ functional qualification. |
Integrated functions are not separate packages; alternative populations are not extra fitted chips; a driver-family name need not equal the physical part (one controller shipped both 720×1280 and 720×1440 panel programs — the name alone did not select initialization). Keep unknowns unknown; an inventory is not permission to invent a BOM or blindly probe buses.
**Working conventions.** Resolve the device project root first. Multi-device reference layout (not a restructuring order):
```text
kernel/<version>/ Clean upstream source + generic builder
soc/<vendor-soc>/ SoC-common patches, configuration, packages
os/<platform>/ Device-neutral OS integration and tools
devices/<model>/ Device assembly, patches, calibration, state
```
Shared bases never import device policy; device facts, private captures, per-device agent context stay with the device. Mark a shared kernel tree used for experiments as patched — never pristine upstream.
- **Project layout.** Everything large/private/device-derived lives under gitignored `artifacts/` at the device project root: `private/` (device-unique; partition backup first), `firmware-harvest/` (blobs pending redaction), `android/` (stock-ROM packages, rooted captures), `debug-evidence/` (irreplaceable captures, preregistrations), `reference/` (reading copies). Publishable firmware → sibling `firmware-publishable/` repo with its own history, derived only after redacting device-unique identifiers. Device-local `logs/`: one subdir per boot/deploy + `LATEST-*` links. An `artifacts/` README maps paths to class with hashes, acquisition, coverage — provenance, not a substitute for integrity checks.
- **Publication hygiene runs at every boundary, not only upstream.** Whatever leaves the project — an upstream payload, a published repo or install guide, a status report, an issue/PR/forum reply — is swept first for operator-supplied secrets (Wi-Fi SSIDs and passwords, accounts and app passwords, tokens) and device-unique identifiers (IMEI, serial and MAC addresses, board/asset ids, calibration values, private captures, local hostnames and addresses). Redact or generalize in the payload itself, then verify by scanning the exact bytes that leave the machine — a sweep run from memory misses the value it forgot.
+ - **Prose holds no volatile numbers — the artifact does.** Hand-copied hashes, counts and ratios drift the moment the next flash or run lands, and a stale hand-copied hash is worse than none: it reads as a mismatch and invites a pointless reflash (one project was bitten three times; one stale pair survived six sessions after being called out). Point at the authority — the verify tool, the ledger, the build file — and query it. The same goes for derived indexes: a convenience enumeration in prose that has gone stale is deleted rather than re-synced, because a wrong index is how a wrong patch gets quoted.
- **Control channels.** Before any flash, prove reachability through the target's available control/recovery tools + at least one post-boot channel into target Linux. `adb`, fastboot, USB-gadget Ethernet, serial/ACM, ssh are examples where supported, not individually mandatory. Record working channels, selectors, supported operations — a channel assumed but never tested is discovered missing mid-wedge. Provision missing host tools through the host OS's own package route — on declarative hosts via its shell/profile mechanism; on container/WSL setups, prove USB passthrough before a workflow depends on it. Missing a tool does not waive operation-level proof or recovery prerequisites.
- **Bind every operation to the target.** Record handset↔transport mapping; select the transport explicitly per command; re-establish after re-enumeration; verify product + slot immediately before writes. A second connected phone must never become an implicit target. The binding constrains the host too: a probe must never mutate host network config — one absent gadget MAC made a control script rewrite an unrelated USB Ethernet dock's IP settings. Inspect interface identity + route first; refuse ambiguous matches; host network setup is a separate explicit command on an owned, uniquely verified interface.
- **Prove the operation, not just the connection.** Record whether required operations are actually supported before building around them: one unlocked bootloader supported neither RAM boot nor partition fetch, stranding a built rescue image. Deeper: a firehose that connects, configures and advertises the full `read` set still truncated every streamed read to 16 bytes of a 17,408-byte request — stock tool and two custom drivers alike. Advertised commands ≠ working data path; self-test-read a known-content region and verify bytes before scripting bulk transfer.
- Note which recovery path is known to work.
Write into the project's agent context files: all work touching this device goes through this ruleset; no device action outside it. Existing backup? Confirm coverage + hashes still verify.
**Keep the retail OS bootable for data gathering** (Android in the founding port; same principle wherever the class retains one). Read a working vendor driver's probe/firmware sequence live next to a mainline failure; exercise hardware mainline cannot yet drive. Never conclude mainline behaviour from retail-OS behaviour without a cross-check.
## 1. Gather evidence from the live device first
Capture the failing run's full dmesg before changing anything. Enable debug knobs before reproducing — a second reproduction costs another boot. Keep them relevant and understood: do not arm unassessed probes indiscriminately, and never increase physical stress to compensate for unverified protection telemetry.
Evidence interpretation is kernel work: invoke `linux-kernel-development` if installed, else `linux-kernel-crash-debug`, else a subsystem kernel skill; else reason from sources + `Documentation/`. Do not stall.
### Acquisition and claim boundaries
Per artifact record: origin, exact revision/build, acquisition method, coverage, access failures, integrity hashes. A running sysfs DT may include runtime fixups — not an untouched boot-image DTB (one capture: 2,740 property files preserved, 2,222 verified byte-identical, 518 redundant names accounted; all 2,839 manifest files passed — still not a restorable backup). A firmware path inventory does not establish load order.
Photographic identification: bind each observation to a named image/page/crop AND the photographed sample — not output order, not the target handset by assumption. One parallel-image association and one presumed manufacturer marking were withdrawn after original-byte inspection. Preserve uncertain transcriptions; public regional samples do not prove this handset's population.
Carry phase 0 evidence labels into findings. Record running vs deployed identities separately; service readiness ≠ end-to-end behaviour. Registered sound card + silent playback ≠ acoustic qualification; mark untested checks untested.
### Evidence channels, roughly by reliability
- **Live shell** (network/USB) — cheapest; anything the device survives.
- **Bootable retail OS** (Android where retained) — boot deliberately when a question is best answered there; cross-check before transferring conclusions.
- **pstore.** Identify the active crash-record collector, its config and archive destination. Inspect the mounted kernel interface (`/sys/fs/pstore/` where used) and any collector archive — when `systemd-pstore.service` has collected records its configured archive must be harvested too (one system moved everything to `/var/lib/systemd/pstore/` early in boot). Merely running systemd proves nothing about collection; an empty kernel directory alone proves no absence of a crash. Console region is usually a single slot — copy to host first or the next crash overwrites it.
- **ramoops.** Lossy: DRAM charge retention decays unpowered — measured ~6.5 % of bits on one device, enough to fail ECC on zone headers and lose records — and warm reboots rot it too (tens of blocks). Decay follows the power cycle. Grep fuzzily. Let a wedged device log while the console zone holds, then power-cycle and back promptly; a 4 MiB zone absorbs hours of watchdog spam first — check zone size.
- **Kernel ACM console** (`CONFIG_U_SERIAL_CONSOLE=y` + `console=ttyGS0`) — the only channel that survives a wedge, but verify it survives the reporting context: payload is emitted inside `panic()` with IRQs off and other CPUs stopped; a workqueue-deferred console never prints. Test free with a crash injector (LKDTM `/sys/kernel/debug/provoke-crash/DIRECT` where present): synthetic lockup, exact geometry, no flash. A userspace getty yields zero bytes while still enumerating. Both config halves required.
- **On compositor targets the screen is an evidence channel.** A screenshot pulled over the demonstrated channel returned the live UI state in seconds, and a loopback VNC server reached through the existing ssh tunnel added touch/keyboard injection — no new network exposure. Read the panel instead of inferring UI state from logs; the UI is part of the runtime surface.
### Rules that turn inference into measurement
- - **Confirm what is flashed by reading back** — never notes or build logs. `iflag=direct` so the read doesn't perturb. Size-only comparisons prove nothing (boot images are padded). Hash the payload.
+ - **Confirm what is flashed by reading back** — never notes or build logs. `iflag=direct` so the read doesn't perturb. Size-only comparisons prove nothing (boot images are padded). Hash the payload. But validate the readback method itself once against known content: on one host kernel `iflag=direct` against USB storage returned a deterministic _wrong_ buffer for any freshly written image — the hazard that would have condemned good media. Verify install media cross-method (drop caches, buffered `cmp` device-vs-image, plus a hash of a file read back through a mount), and condemn hardware only on physical evidence — mid-write bus dropout, re-enumeration as a different identity-less device, disagreement across independent reads — never on a hash mismatch alone.
- **Reading a debug interface can manufacture the symptom.** Sampling DRM/GPU crash-state nodes during active submission synthesised fault/recovery lines that never occurred (five reads, five fault/recover pairs; none unobserved). Check whether your own observation produced the log line.
- **Validate stimulus + observation path before interpreting silence.** Confirm the actual register protocol, effective post-init state, physical receiver route and required clocks throughout sampling. The claim that unchanged camera TX-on/off status bytes proved a static bank is **withdrawn**: the TX-off control used the wrong protocol, stream startup rewrote the setting, and earlier silence measurements targeted the wrong PHY. Unchanged bytes prove neither latch nor transmitter failure; a sensor/top-level runtime reference does not retain per-PHY clocks through teardown.
- **Capture a rejection's raw response before interpreting the mapped errno.** A firmware `EINVAL` was ambiguous — wrong encoding or refused policy — until dynamic debug exposed well-formed words carrying explicit refusal. Opposite verdicts, opposite next steps. Classify every rejection: encoding / policy / not-found. Mirror: a clean negative is positive proof — a not-found from a correct lookup validates the encoding and kills a plan branch without touching the loader.
- **A measurement racing an uncontrolled condition returns a plausible void number.** One benchmark scored 55 in two seconds — the compositor held DRM master, every scene failed. The valid 132 → 485 comparison needed the compositor stopped for both runs. Name the voiding condition; verify it held every run.
- **Control ownership through the whole measurement.** One camera reopen failed EPIPE after another client changed the media graph; configure + capture under the clients' compatible lock passed a 90-frame burst without stopping audio. Establish which clients cooperate. For power cleanup, identify open owners before calling an active supply a leak: last-owner close/open/close measured votes 0→1→0; a restored camera monitor legitimately reopened the device.
- **Measure controls on linear data, not enhanced previews.** Fixed region + processing scale, account for black level and clipping, bracket with baselines for scene drift. Exposure doubling measured 1.990×/1.995× signal; 2×/4× gain 1.984×/3.957×; baseline drift <0.23%. Accepted writes and auto-contrasted images are not proof. Physical-unit calibration, per-frame delay, autofocus, color remain separate qualifications.
- **Prove protection before increasing stress.** Enable bits + successful init ≠ limiter intervention. Establish telemetry identity, units, update behavior, failure handling before using it to justify higher output. All five retained candidate temperature values in one investigation were zero; the OEM getter ignored the return code. Never heat/drive harder to compensate for missing observability. Keep output restricted when protection is unverified — one speaker path muted output and revoked access on provisioning loss rather than falling back unprotected. A read-only debug interface is not automatically harmless: an observer-enabled boot locked up, causation unresolved.
- **Classify one-direction-dead USB networking by counters, not symptoms.** Gadget still enumerated + host ARP failing is ambiguous: ping from the host while reading the device's interface TX/RX counters over a surviving channel — counters rising only inbound proved the device could receive but not transmit. Check the rootfs first, too: a read-only remount after a failed resume kills sshd and networking with identical outward silence.
- **A host-side USB id proves neither the running OS nor a failed boot.** One enumerated vendor id plus dead ssh was repeatedly logged as an "Android fallback" while the target OS was up and responsive; host-visible signals establish channel state only. Obtain OS identity through a working channel; until then record a channel failure, not a boot verdict.
- **A session or compositor swap re-opens its layer's defaults.** The previous session claimed the power key (screen lock); the new one claimed nothing, so the logind stock handler turned one short press into a clean shutdown — journal `-b -1` shows the keypress, pstore stays empty: a shutdown masquerading as a crash. Config-variable errors can render as a near-invisible error banner instead of the UI, and autostarted input methods fail silently. After a session change verify: config loads clean (reload success), key/suspend handlers claimed, autostarts actually running, scale/orientation/idle behavior intact.
- **Record rules you withdraw.** Folklore goes stale with hardware revisions. Keep the overturned version next to the current one with what changed.
+ - **Attribute arch-only crashes by running the same pinned revision on the other architecture.** Same closure/revision crashing on x86_64 ⇒ upstream or packaging (miscompile class); running clean past the faulting constructor ⇒ port/display-stack specific. Strip variables before layer fixes: confirm the fault reproduces with no display stack (minimal platform), decode the core from its thread + file notes (a null vtable disassembly names the layer). Three single-layer fixes were refuted on one crash before the cross-arch run pointed at the build.
+ - **Decompose the compositing tax before any "GPU is slow" conclusion.** Matched-scene ladder, cheapest first: raw EGL surface → direct KMS (compositor stopped; the DRM-master voiding condition named and verified every run) → composited session. Check compositor CPU% to rule out starvation, A/B compositors, and verify direct-scanout engagement mechanically — compositor CPU time frozen while exactly one KMS plane is bound (it engages only for a single opaque fullscreen client with chrome hidden on some stacks). Perceived smoothness can be frame pacing, not throughput.
+ - **Long multi-session hunts need infrastructure, not memory.** Commit the smallest deterministic reproducer as a tool with a measured pass count; reconcile the incident-numbering ledger before comparing counts across sessions; pre-register predictions per experiment batch; and read back what the instrument actually received before trusting a control — one post-fix run whose gate parameter was silently dropped produced a withdrawn row (the printed received-range exposed it) and a corrected rerun reversed the conclusion. A sha256-pinned evidence manifest plus an offline replay tool that re-derives recorded metrics from the private captures closes the loop: drift is fatal, conflicts are the output.
### Host-side gotchas
- `timeout N ssh` kills the local client only; the remote process keeps running — kill it explicitly.
- `pgrep -f`/`pkill -f` over that ssh path self-match their own remote shell — filter the matcher or match by PID.
- `find` needs `-L` when the start path is a symlink, or it returns silently empty.
- Any wrapper that rewrites output — token filters, pagers, formatters — corrupts evidence, sometimes inverting a test. Use the raw command for findings.
- A detached recovery unit (`systemd-run`) runs with a minimal PATH: one script's `sleep`s silently no-opped, it raced its own readiness check, and its reboot safety net fired on a false negative — destroying the state it existed to diagnose. Absolute paths; every step logged with timestamps; a destructive fallback gated on preconditions verified inside the same script.
+ - **Verification harnesses: a missing answer is a FAIL, never a silent pass.** Empty output, timeout or transport failure on a required probe = FAIL (or an explicitly modeled skip); exit nonzero when required evidence is missing; capture the producer's exit status _before_ filtering (failed log acquisition is not a clean zero-match); set pipefail so a pipeline with a failing producer fails; bound cleanup so it preserves the original failure classification and reports its own uncertainty separately. Never kill by process-name pattern — a matcher can self-match or catch an unrelated process.
### Operator hands are a step, not an assumption
- **Authorization is scoped; pauses are binding.** Record the permitted action, target, limits, pause/resume prerequisite. Inspect ≠ restart-service ≠ capture ≠ actuate ≠ reboot. A paused experiment stays paused until its prerequisite is satisfied and required operator authorization confirmed; only explicitly permitted remaining work may continue. Reachable transport and an unlimited iteration budget do not expand permission (one lens session allowed same-position lifecycle checks while optical movement stayed deferred). A higher-output exception expires with its named test, not tomorrow's default.
- **Physical actions**: name the exact action + direction, then verify via an observable signal — appearance (mount, re-enum, slot change) or disappearance (power-off, USB drop, silence). Disappearance is ambiguous: watch the drop as an event, or require done-confirmation + verified post-absence probe failure. Poll briefly; no signal within a bound ⇒ re-ask.
- **Total channel loss after suspend/resume has three branches, not two.** Host-side, live-phone-with-dead-driver is indistinguishable from hung/dead — one "suspended and never came back" was wrong: suspend/resume worked 10.5 more hours; only the USB gadget failed (`HS-PHY not in L2`). After a replug, read the device's journal before declaring a hang.
- **A hard reset during suspend entry masquerades as a resumed-but-dead device.** One "gadget never survives resume" was the phone rebooting mid-entry — no panic, no pstore; `boot_id` + journal continuity is the discriminator. Bisect entry with the `pm_test` stages (freezer → devices → platform → processors → core): surviving all five puts the fault after driver work, in the SoC power-collapse transition — missing wake-controller node, absent parent collapse domain, firmware-inconsistent idle StateIDs — not in any device driver.
- **Check charge state before a boot-failure verdict on a silent, dim device.** One gadget-USB drop stopped charging; successive reboot attempts then drained the battery until the device showed a dim console and no enumeration — read as a boot hang until the cable was re-seated and the device returned, exonerating the software change. Charging evidence (LED, current) is cheaper than a reflash.
## 2. Research before implementing — all of these sources
Research precedes implementation. Input: a failure → phase 1 evidence (capturing first; researching an unread symptom is guessing with citations). A new capability → phase 0 artefacts (stock board description, vendor configs, firmware layout) are the device data, as authoritative as a crash log.
+ **The sweep self-starts.** Once that precondition holds, open the sweep ledger and begin — no operator prompt, no named sources, no permission needed. Waiting to be told "search X" or "use wigolo" is a protocol failure: invoking the companion skills and walking every family below is the sweep's own first act, not a response to a request. This section is executable as written; the `/source-sweep` and `/port-research` commands are convenience entry points, not triggers.
+
+ **Run it as a ledger, not a stroll.** One row per open question × source family: consulted revision/query, finding + applicability, or a recorded gap (source + access failure). A question closes only when every applicable family row for it carries a finding or a recorded gap — the first plausible hit closes nothing, because an unconsulted family silently truncates the evidence the eventual hypothesis rests on. Keep the ledger in the project's research notes so the next session inherits coverage instead of re-searching.
+
Before writing anything:
1. **Reason about the subsystem** — identify the driver/binding/firmware interface owning the failure (or capability). Invoke `linux-kernel-development` if installed, else `linux-kernel-crash-debug`, else a subsystem skill; else reason directly. Invoke `find-docs` for kernel-side docs — binding + dt-schema, Kconfig, subsystem docs — for the **exact kernel version being built**; else read `Documentation/`. A recalled binding constraint or Kconfig gate is a guess; stale bindings are what phase 3's battery catches late.
**Graft navigation optional** when installed + indexed: locate implementation, trace dependencies across layers; verify decisive details against the exact revision. An index miss is not proof of absence — check coverage, then direct search/LSP/reads. Graft navigates; it is not an evidence source.
- 2. **Look up the target platform, userspace libs and tools** — current docs, not recalled APIs. Consult the selected OS release's docs, implementation, hardware adaptations, contribution rules, plus relevant projects discovered during research (`find-docs` per item; else the project's docs over the web). The named families below are research destinations, not a compatibility list or exhaustive set. Android-app compatibility is a target capability, not an afterthought: enumerate the candidate layers (waydroid, Android-runtime ports such as lepton, and peers), gate each on its kernel requirements — binder, PSI/memfd, GPU stack — against the exact kernel configuration being built, then race the survivors on the device and benchmark the winner against the native session: one port measured the Android container faster than the phone session it ran under.
- 3. **Web research across every source family** (parallel subagents work well). Invoke `wigolo` for the sweep (cache matters); else plain web search. All of these, in order:
+ 2. **Look up the target platform, userspace libs and tools** — current docs, not recalled APIs. Consult the selected OS release's docs, implementation, hardware adaptations, contribution rules, plus relevant projects discovered during research (`find-docs` per item; else the project's docs over the web). The named families below are research destinations, not a compatibility list or exhaustive set. Android-app compatibility is a target capability, not an afterthought: enumerate the candidate layers (waydroid, Android-runtime ports such as lepton, and peers), gate each on its kernel requirements — binder, PSI/memfd, GPU stack — against the exact kernel configuration being built, **and on CPU architecture features, not just kernel requirements**: one image statically emitted ARMv8.1+ LSE atomics and SIGILL'd on ARMv8.0 cores, which have no kernel emulation — kernel-compatible, every binary dead. Root-cause from the faulting instruction in the core dump, reproduce in a plain chroot to exonerate the container machinery, and verify the binder mount's fstype naming against the kernel tree. Then race the survivors on the device and benchmark the winner against the native session: one port measured the Android container faster than the phone session it ran under.
+ 3. **Web research across every source family** — fan out one parallel subagent per family by default; a serial sweep stops at the first plausible hit and silently drops the families after it. Invoke `wigolo` for the sweep (cache matters); else plain web search. All of these, in order:
- **SoC vendor's mainline collaboration project** (e.g. Qualcomm's — search the vendor name + "mainline"; don't assume a host). Mainline-first vendor work: updated dtsi, bindings, enablement, tooling, often ahead. Check before authoring a missing node/driver yourself — the vendor may have landed it; its trees show intended mainline driving, the most authoritative template.
- **LKML archives (lore.kernel.org) + Patchwork** — the kernel's own patch review: threads carry rationale, hardware context and register detail that commit messages drop, show reworks and maintainer objections, and distinguish merged, in-flight, reworked or rejected — resolve a fix's state there before porting or re-deriving it, and check it landed in the lineage being built, else it is a backport candidate, not an existing fix. Subsystem trees and linux-next show what the next release carries. With the optional `lei` CLI installed, query the same archive locally — diff prefixes (`dfn:` filename, `dfhh:` hunk header) reach the threads touching an exact driver file or function, and saved searches (`lei q` / `lei up`) follow a subsystem while the port iterates; without it, the lore web interface is the fallback.
- - **GPL-published OEM kernel.** An exact release for this device usually exists: OEM open-source portal (Samsung, Sony, Xiaomi, Fairphone, OnePlus/OPPO, Motorola), GitHub/GitLab mirrors, XDA archives when portals die. Select the tarball matching phase 0's `/proc/version` — they differ. Compliance varies: treat the tree as an oracle to mine (board dts + defconfig; out-of-tree touch/panel/charger/modem drivers with register sequences and firmware handshakes), not something that builds. No exact release? A sibling's usually carries the SoC dtsi — but better is **the stock DTB pulled off the device** (`dd` untouched boot slot, scan `d00dfeed`, `dtc -I dtb -O dts`): ground truth for this board, has corrected wrong sibling-SoC guesses including a SMMU stream ID.
+ - **GPL-published OEM kernel.** An exact release for this device usually exists: OEM open-source portal (Samsung, Sony, Xiaomi, Fairphone, OnePlus/OPPO, Motorola), GitHub/GitLab mirrors, XDA archives when portals die. Select the tarball matching phase 0's `/proc/version` — they differ. Compliance varies: treat the tree as an oracle to mine (board dts + defconfig; out-of-tree touch/panel/charger/modem drivers with register sequences and firmware handshakes), not something that builds. No exact release? A sibling's usually carries the SoC dtsi — but better is **the stock DTB pulled off the device** (`dd` untouched boot slot, scan `d00dfeed`, `dtc -I dtb -O dts`): ground truth for this board, has corrected wrong sibling-SoC guesses including a SMMU stream ID. Classes with no OEM drop at all: the stock crash log's verbatim kernel fingerprint maps onto the open-source kernel's release tags and becomes the phase 2 oracle — bulk-harvest the crash-log store early, it also yields the OS upgrade history and corroborating hardware events (one battery verdict leaned on a captured brown-out panic).
- **AOSP / Android platform source** — android.googlesource.com, source.android.com: HAL interfaces, framework/services, init + VINTF, SELinux policy, boot-image tooling, native libraries. The retail config, not convention, selects which of several HAL/transport variants is live — port the selected one (one board's FM HAL factory chose the shared-Bluetooth UART transport over the commonly suggested radio driver). Record exact branch/tag. Complements, not replaces, the OEM source + stock DTB + vendor implementation.
- **Apple-platform mainline enablement** — Asahi Linux, `m1n1` and its device forks, the checkm8/pongoOS payload ecosystems, and the lineage that booted Linux on iBoot-class phones (Project Sandcastle): how the payload chain brings up hardware, where the platform's DT lives, storage access, and what the kernel work assumed. On exploit-booted targets this is usually the only precedent.
- **Windows-on-ARM / UEFI ARM enablement** — vendor and Linaro mainline work for compute-platform tablets and laptops, ACPI enablement, EFI-stub bring-up: on firmware-boot targets the same silicon is often enabled here first.
- **Windows-Phone-lineage ports** — the conversion and unlock tooling ecosystems around that boot chain (service programmers, bootloader-unlock services, W10M-to-Android conversion guides) and their postmarketOS device packages: usually the only precedent for the loader handoff, DT delivery and modem bring-up on those boards.
- **postmarketOS** — pmaports device packages + APKBUILDs, merge requests, wiki device pages, SoC-mainline tree.
- **Halium / UBports** — `halium/android_device_*`, `hybris-boot` configs, UBports ports. Best source for how vendor firmware expects to be driven: blob paths + load order, properties + sockets, sensor/modem HAL configs. Firmware handshake failing? Usually written down here.
- **Mobian** — the Debian device repos.
- **General-purpose and desktop distributions** — NixOS (mobile-nixos patterns, nixpkgs packaging) among them: inspect ARM platform enablement, kernel patches/config, package sources, integration changes, bug/review threads — not only mobile editions. Follow fixes to their owning upstream project and exact revision; distro copies are not independent corroboration. Graphics/media/audio/power fixes may be architecture-independent — do not exclude a change because it was packaged or tested on x86. An ARM image alone proves neither this phone's SoC/GPU/firmware support nor boot-chain compatibility; establish applicability against actual interfaces before adoption.
4. **Map component documents to the inventory.** `find-docs` + `wigolo` as above. Search manufacturer documentation, OEM/ODM board schematics + service material, FCC exhibits, repair archives, **Scribd** original-document search. Use exact part/package/module suffixes, board identifiers, internal codenames — alongside retail names. Whole-device inventory covers every record; a symptom sweep refreshes affected records. Missing documents are gaps, not reasons to abandon source-derived implementation.
Each document record: stable ID linked to components; original title/publisher; part/package + board applicability; revision; document kind (full datasheet/register map vs short brief vs clock calculator vs configuration sequence vs module drawing vs board schematic); relevant pages/sections; questions answered + limitations; URL/document ID; access actually obtained; local path, size/hash; acquisition failures. A C-PHY sequence is not a D-PHY mode; another vendor's module pinout is not the fitted module's wiring.
Validate original cover/body over generated titles — Scribd titles misidentified phone schematics. Landing page, preview, readable text capture, full PDF = different acquisition states; a listed confidential filename is only a lead. Keep a deduplicated index, acquisition manifest, unresolved-download queue; reuse existing downloads. Two differently named board downloads were byte-identical — one source, not corroboration. Legitimate access only: gaps do not authorize account changes, purchases, uploads, bypass. Preserve third-party markings; keep collections local under ignored reference artifacts — share URLs/index, not the collection.
**Reconcile wiring before tuning a silent peripheral.** Compare selected stock DT/overlay, vendor config, actual schematic sheets, current implementation end-to-end — mux controls, supplies, clocks. Match physical controller addresses, not downstream index names: one audit found reversed rear/depth PHY routes despite successful probes; the camera mux served neither path. Correcting routes still yielded zero bytes — necessary, not sufficient; earlier wrong-receiver silence did not establish sensor failure.
Match board revision; inspect rendered wiring: one schematic's stale contents page named a different SoC; visual inspection corrected a pull-up assigned to the wrong GPIO. Functional reconstructions are not verified electrical schematics: DT supply references are not exact board nets; configured voltages are not measured voltages or safe bench settings; functional boxes need not be separate chips. Label inferred connections and unknown pinouts; neither stock declarations nor a plausible schematic erase conflicting evidence.
**Separate timing clocks from data clocks.** Trace each domain and its effective rate before another timing sweep. One receiver's settle timer already ran at 200 MHz while its separate data clock sat at 19.2 MHz; changing only the latter to a supported 320 MHz produced a complete 15,000,000-byte real-scene RAW frame. An internal generator had captured 2,400,000 bytes first — downstream path proven at that format, not physical receiver input. Measured settings for one configuration, not portable prescriptions.
+ **Diff the reserved-memory map region-by-region against the downstream/vendor tree before hunting unexplained single-CPU hard lockups.** An under-reserved firmware window backed by XPU protection, handed to the buddy allocator by the SoC dtsi, makes a CPU store never return: no tick, no IPI, silent under NMI, with RCU stalls and IPI failures as secondary noise — and fresh page-cache workloads hit it while `O_DIRECT` (the same few pages) does not, which reads as a workload bug rather than a map bug. One board was 28 MiB short; MemTotal delta matched the shortfall exactly, and ~340 expected wedges at the old rate went to zero post-fix. The proposed mechanism must retro-explain the whole incident ledger and list what it still fails to explain; adopt the vendor map only as a separately verified change — moving a region can break a working peripheral.
+
**Verify what you carry against every downstream DT you hold.** Patches go stale: one port re-verified against the newest stock OTA DT, the original backup, a custom ROM tree — four confirmed, one CPU-capacities discrepancy no single source showed. Read the vendor flash script before trusting it: at least one ships slot-blind, hard-coding the wrong image for the active slot.
**Sweep sister devices.** Enumerate ports of the same family — sibling models under one marketing name, near-neighbours on the same board/SoC generation — in pmaports, Halium/UBports trees, XDA, the OEM tree's `dts/`. Diff board files (dts, defconfig, firmware blob lists) component-by-component against the phase 0 inventory: match = proven driver+firmware combination; divergence = where the family split, itself a hypothesis. A sister device is a lead, not a precedent — adopt only what the diff confirms against this variant's spec sheet + stock DTB.
**Search internal codenames and OEM/ODM archives.** An alternate-system image can reveal vendor implementation absent from retail docs. Preserve archive provenance; inspect board identity, config, firmware lineage. Archive contents are not evidence the handset runs the image, is compatible, or that flashing authorization/recovery exist.
### Security findings exposed by bring-up
- **A bulletin is not proof of a fix in your tree.** Compare advisory, fixing commits, upstream implementation, AOSP against the exact revision built; map source to executed binary. One FastRPC investigation reproduced an unchecked DSP-length path against a recent AOSP release despite an older bulletin describing the class. Similar mechanism + affected-chip list ≠ exact CVE identity; qualify until technical evidence or maintainer confirmation.
- **Firmware data crosses a trust boundary.** Trace raw length words, decoding, signedness, arithmetic, cursor advancement, copy bounds before blaming firmware/hardware. One listener crash combined an unchecked decoded length with a remaining-space calculation that narrowed and wrapped positive. Protocol desync never justifies unsafe parsing.
- **Make the shared defect reproducible upstream.** Reduce to the real parser + smallest portable reproducer, not a handset workaround. One gate exercised five lengths — captured value, remaining+1, `0x7fffffff`, `0x80000000`, `0xffffffff` — SIGSEGV before fix, containment after. Submit affected revisions, field evidence, reproduction prerequisites, minimal patch, before/after results to the owning project. Redact; follow its disclosure policy. `/port-upstream` owns destination research and the submission ledger. Submitted/accepted/merged/released are distinct states, each with evidence — a report or PR is not evidence consumers have the fix.
- **Thin results are the normal case.** Report "no applicable work found in the searched sources" with revisions, coverage, access failures — not "nobody published it." Derive from primary artefacts: the out-of-tree vendor driver already driving the peripheral, the stock DTB node, the vendor HAL config, the closest mainline driver as structural template. Read, derive, write the missing piece — DT node, quirk, small driver. Authoring is the ordinary terminal state of bring-up; phase 0's backup + one-variable experiments bound software risk, not physical-damage risk.
- **Empty sweep ≠ empty scan.** Empty sweep = no applicable result within recorded coverage; inaccessible sources are gaps, not negative evidence. Empty scan = void until the method found a known positive with verified coverage (file counts, segment lists, glob matches). Measured near-misses: a glob matched 9 of 27 firmware segments and nearly recorded a false "the firmware omits it"; a library scan became trustworthy only once it found a known chip id. Read success claims in context: one forum's "got it working" concerned provisioning, not root or alternative-OS bring-up.
- **Downstream identifiers are downstream's private numbering.** Never copy a vendor id/enum into a mainline node. One downstream thermal trip pointed at "sensor 5"; mainline sensor 5 read constant 0 and the real tracker was sensor 6 — settled by stimulus (heated the cluster, watched which zone moved). A copied mapping reads dead ⇒ suspect the numbering; settle by stimulus.
## 3. Implement only with a promising, evidence-backed hypothesis
- **An authorized reboot is yours** when a demonstrated channel exists (ssh, USB gadget, fastboot, target control tool): reboot within the permitted experiment and continue. Reachability does not override a pause or expand permission. Operator hands only for what no channel reaches: battery pull, key combo, moving media.
- **Define the physical-test envelope before output.** Before sound, actuator motion, charging changes or other physical output: record device-specific permitted limits, units, duration, stop conditions, final safe state. Apply and read back limits on the actual execution path; bypassed or unverified enforcement ⇒ do not use it. One audio test distinguished 15% desktop slider from 15% linear amplitude — the latter required separate approval. Factory lens endpoints are operational calibration bounds, not certified mechanical limits. No universal safe level. Stop at the bound; verify the agreed safe state and resource release — a host timeout alone does not stop a remote process.
- **Invoke `find-docs` for the selected build/image tooling before assembling** — kernel build system + config fragments, target image format and builder, update/install procedure; current docs, not recalled flags; else the tool's own docs. `mkbootimg`/`mkdtboimg` constraints apply to those formats; pmbootstrap/mobile-nixos/Nix flakes are platform examples, not required tooling. A wrongly recalled flag burns a full rebuild+flash cycle.
- **One variable per flash, falsifier before build.** State the hypothesis; write the falsifier — the smallest experiment that kills the plan either way — and build it first: one single module load returned the refusal that killed a 9.4k-line port premise. Order by cost per information, not plan order.
- **Prefer runtime tests over reflashes:** tar-pipe, unbind/rebind, `insmod` — seconds vs minutes. But probes leave state: failed init can leave the platform device registered (reboot per retry); a crashed probe can wedge the board. Stage inputs, verify hashes host-side, make it idempotent, expect one reboot per failed attempt. Staged inputs persist across reboots — re-verify after any kernel change. Never probe through the channel the probe rebinds — one unbind/rebind test severed its own USB control channel.
- **Module load order is an experiment variable.** Camera/lens modules in `modules-load` ahead of the audio DSP's bring-up broke speaker-path registration on every boot; loading the same modules at runtime after audio init kept both paths working — order-dependence, not module presence. Isolate with a closure A/B (offending modules removed) against a runtime load, then defer the modules behind an ordered service on the subsystem they must not race.
- **Minimize the successful experiment before making it permanent.** A working configuration does not prove every accumulated workaround necessary. Remove them one at a time under the same acceptance check: one camera still captured 15,000,000 bytes after removing a forced settle override, and again without experimental receiver power hacks. Replace necessary helpers with native initialization and resource ownership; verify reboot, idle/reopen, released owned votes without manual helpers. Retain recovery evidence; runtime success ≠ boot persistence ≠ camera-application readiness.
- **Device-tree-only changes are cheap** — build just the DTB when possible, then check the runtime surface: a clean-building DT can bind to nothing (one thermal driver registered one zone per sensor id and silently dropped a duplicate — the zone list, not the build log, was the verdict).
+ - **Firmware-boot bring-up has its own gates — check each delivery link before building.** The bootloader's DTB-injection command is refused under kernel lockdown — disable Secure Boot first and verify the toggle path works on the first boot; the distro's automatic-DTB database may not carry the device even when the SoC is supported (one had two entries for the SoC, both the other vendor); a generic dtbloader's device list must be checked per device. Plan installed-boot DT persistence (loader-spec `devicetree` field + same cmdline) at install time, not just the live path; echo each loader stage so failures are visible at the menu, not inferred from silence. **Never hand-author a memory node**: the EFI stub overrides memory from the firmware map, and a wrong fixed `reg` silently poisons memblock — instant, console-less death indistinguishable from every other silent failure; upstream firmware-boot DTs for the same SoC ship no memory node at all. **Separate delivery from content when a hand-authored DTB dies silently**: boot the same kernel+initrd with a sibling board's known-good DTB — a _different_ failure signature proves the handoff works and isolates the fault to board-DT content. **One-shot boot-menu selection is BootNext semantics**: any early reset lands in the default OS, so "rebooted back into the stock install" is the expected crash outcome on a dual-boot firmware target, not evidence against the entry or the media. **Declare the verdict surface before the first boot on a serial-less device**: bare `earlycon` activates UART autodetect and prints nothing where no UART exists (identical silent death, with earlycon set); if the boot-menu render proved the firmware console works, the firmware framebuffer is the early-console candidate — and dmesg-over-network is the verdict surface, not the screen. **Run the non-destructive falsifier ladder on stock live media before any port work**: boot menu + unsigned-media boot; Secure Boot state and toggle; the boot _menu render itself is a free display test_ that answers the installer-display question and can demote the panel to a stretch goal; keep a stock-firmware, no-DTB entry as a known-behavior baseline. Kill gate: menu or toggle failure ⇒ research the firmware first.
+ - **Before suspend experiments, prove the wake source standalone and abandon the watchdog.** Arm the RTC alarm +30 s _without_ suspending and require the alarm interrupt count to increment; mind time-base skew — NTP-corrected wall clock vs unsynced/read-only RTC (one device "slept" exactly its wall-clock timeout against a +75 s alarm). Mainline watchdog drivers stop the watchdog in late suspend by design, so successful entry is unrecoverable from the watchdog — it is not a safety net. A USB-only control channel makes suspend a one-way trip until operator hands, even when resume works.
+ - **Remote-processor shared memory is DMA-API territory, and remove-entry is not ownership-end.** Stage with `dma_alloc_coherent` (pages + IOVA + cache discipline, `dma_wmb` before the doorbell), never vmap, manual IOMMU maps or assumed contiguity; a driver's remove callback running does not prove the remote processor finished with the RAM — retain the backing until the unmap is acknowledged; a timeout does not establish ownership end. Validate packet builders by executing the construction on the host and inspecting the receiver-visible wire fields: the driver's logged address is not the wire address, and one double-counted header had the receiver reading zeros.
+ - **Preserve vendor read-only hardware policies.** A PMIC RTC whose writes the hardware rejects stays read-only: enabling writes yields a denied-write flood and does not fix the epoch discrepancy — withdraw the property. A DT overlay remove/reload is not an idempotent reset (refcount underflow) — between overlay candidates, preserve evidence and reboot.
- **Never edit-and-hope.** Read the exact current lines, write the exact replacement. Tool can't edit precisely ⇒ stop, re-read. Hand-repairing self-inflicted breakage is a defect, not a workflow — and on a port the sloppy edit can reach a flash before a build.
- **Separate restructuring from upgrades.** Hold upstream version, patches, config fixed while moving layers; compare built payloads before/after (one split preserved `Image` + `System.map` bytes). Upgrade separately. Verify downstream DTB + boot-image assembly too: kernel equivalence ≠ complete image still builds.
- **Start the patch battery pristine.** Fresh disposable tree at the exact intended revision; apply patches in series order so later checks see prerequisites. Never classify an apply failure as "already upstream" from a used tree — one contaminated rebase check dropped a required header on that premise; the build failure forced restoration. Establish absorption from upstream code/history before retiring a patch.
- **A rebuild is the most expensive step — verify before paying.** Measured: four consecutive rebuilds burned on unchecked missing symbols. Before any full rebuild: every patch applies cleanly (`git apply --check`); every referenced symbol defined where the patch sees it; every config option a patch/defconfig sets exists in this kernel's Kconfig (a nonexistent option is silently ignored — a clean config read is not evidence it took effect); compile touched directories/units first.
- **Build the artifact you selected; report per artifact.** One bundled job hid three of four finished targets behind a still-compiling patched desktop and reported only "still running." Select explicitly; keep progress + cancellation separate; status queries must never start a build. A finished store output survives a sibling's cancellation.
- **Source preparation is transactional or it lies.** Derive readiness from the actual assembly: one helper searched for declarations the current flake no longer contained, applied zero of 47 patches, stamped itself ready. Apply each patch against its predecessors' result; missing files/check failures fatal; success marker only after full success; fingerprint readiness on source identity + ordered patch contents. Partial/failed preparation leaves the previous ready tree usable.
- **After 3 refuted fixes: stop guessing, not stop working.** Three misses = the failure model is wrong; a fourth tweak misses too. Return to step 2 wider — different layer, or primary artefacts — and return with findings stacked. Worked shape: five blind DT tweaks failed; five researched findings stacked — trustzone memory mode, vendor firmware paths, host-capability quirk, SMMU stream ID off the stock DTB, one obsolete property dropped. No single tweak would have got there.
- Before writing, recheck phase 0's handset/transport mapping, product, slot. **A deployment is a state machine.** Separately observe: artifact preparation, transfer/installation, next-boot selection, live activation (where supported), boot. Map to the target's actual update model; do not invent profiles/slots/live activation where absent. No earlier state proves a later one. Nix-style: a closure present ≠ activation; a matching profile symlink does not prove activation completed; an explicit activation request reruns even when nothing needed copying. Only a separately observed reboot marks booted — never flash or reboot to make recorded states agree. After the authorized deployment: readback, required reboot through an available channel, identify running kernel/modules/system. Recheck boot-time init + runtime functions: one upgrade lost sound-card registration to module coldplug ordering though later manual init worked. Service readiness, silent playback, operator-confirmed acoustics are separate results; retain deferred/untested criteria explicitly. Revalidation honesty: on a required probe, empty output, timeout, or transport failure is a **FAIL, never a SKIP** — one checker scored a timed-out partial output PASS and a dead transport SKIP, then exited 0 — and a verification tool must never trigger an implicit build for a missing image argument.
+ Before writing, recheck phase 0's handset/transport mapping, product, slot. **Stage the first rootfs where removal is the escape hatch.** Removable media first — card pull is a zero-flash exit and was the _primary_ hatch on one port; keep the stock OS on the untouched slot as the recovery OS; whitelist flash writes to the boot/verification images on one slot; record the known-good image path before each variant; fail the build loudly as the boot image approaches its partition size. **A deployment is a state machine.** Separately observe: artifact preparation, transfer/installation, next-boot selection, live activation (where supported), boot. Map to the target's actual update model; do not invent profiles/slots/live activation where absent. No earlier state proves a later one. Nix-style: a closure present ≠ activation; a matching profile symlink does not prove activation completed; an explicit activation request reruns even when nothing needed copying. Only a separately observed reboot marks booted — never flash or reboot to make recorded states agree. After the authorized deployment: readback, required reboot through an available channel, identify running kernel/modules/system. Recheck boot-time init + runtime functions: one upgrade lost sound-card registration to module coldplug ordering though later manual init worked. Service readiness, silent playback, operator-confirmed acoustics are separate results; retain deferred/untested criteria explicitly. Revalidation honesty lives in the host-side gotchas: a required probe's empty output, timeout or transport failure is a **FAIL, never a SKIP**, never a silent pass — and a verification tool must never trigger an implicit build for a missing image argument.
**Reconcile completion and resume records.** Before reporting completion or resuming: make current target, tested revision, acceptance results, limitations, authorization state and next action mutually consistent. Preserve superseded checkpoints with transition evidence; do not erase history or treat completion as renewed permission. One result carried passing optical measurements while retaining paused/not-verified fields — such contradictions block automatic continuation until reconciled. Target met with an unresolved new fault is not clean integration: a passing optical test did not explain a new boot-time controller timeout.
**Cleanup only after the new deployment passes real reboot + runtime checks.** Inventory obsolete kernels, module trees, images/packages, boot entries, staging — include generations/roots where the platform uses them. Preserve the running system, next-boot target, a complete known-good recovery path with dependencies — a saved boot image alone may not recover the system. Archive needed historical images/symbols/logs on the host before retiring device copies. Select retired deployments explicitly; remove through the target's supported mechanism — package/image/slot management, or scoped generation/GC for Nix-style systems; never manual deletion of managed files or blanket deletion. Record space before/after, mechanism-reported reclamation, retained recovery targets. One operator reported substantial GC recovery with no precise total — never attribute total GC savings to kernels without separate accounting.