Imported from angelwzr/linux-phone-porting (
SKILL.md). Install upstream withnpx skills add angelwzr/linux-phone-porting. Copyright stays with the author.
Linux Phone Porting
On a phone under mainline bring-up, a debug iteration costs a build plus a flash plus a boot (5-10 min), and blind fixes get refuted far more often than they land. Follow this order strictly.
0. Set up the port
Do this once, before the first flash. Two things make it phase 0 rather than paperwork: the stock system is the highest-authority research source you will ever have for this board, and parts of it stop being readable the moment you start overwriting partitions.
Establish the target first, because every later answer depends on it:
- Exact device identity — marketing name, codename, SoC, and the regional or storage variant. Sibling variants of the same marketing name routinely differ in panel, touch controller, or modem, and a fix researched against the wrong variant looks plausible and fails. Record the sibling models' codenames and model numbers alongside this one — phase 2's sweep enumerates their ports.
- Engineering stage — record EVT/DVT/PVT revision where exposed, board identity, carrier/SKU, bootloader product and build fingerprints. Reconcile disagreements before selecting sources or images: one presumed retail handset identified as an EVT board for a different product while Android still reported
user/release-keys. Software build labels do not establish production hardware. - Target distro and init — postmarketOS, Mobian, NixOS, plain Debian, something else. This decides packaging, image layout, and which of the phase 2 sources is closest to home.
- Target userspace shell — ask the user which desktop environment or shell they want; never assume a default. If the target OS ships its own shell (a UBports port carries its own, for example), strongly recommend sticking with it — a port that fights the distro's compositor and session stack pays for the fight on every rebuild.
- Boot topology — A/B or non-A/B, slot layout, and bootloader lock state. On A/B devices, decode the slot state from the on-disk structure rather than trusting documented layouts — one SoC kept it in GPT partition-entry attribute bits, not the documented bootloader-control partition — and after any crash-loop, refill the tries-remaining budget (nothing marks the slot successful in that path) before interpreting the next boot failure. Unlocked bootloaders only. If the bootloader is locked, stop: this skill does not unlock bootloaders and does not provide unlocking steps — point the user at their OEM's own instructions and resume once the device is unlocked.
- Recovery — whether a custom recovery is installed. Not a requirement: a convenience for backing up and restoring.
Vendor A/B bank names do not establish Android seamless-update, slot-control or fallback semantics. Establish the implementation before manipulating banks. Empty security properties mean unknown, not unlocked. Same-SoC device trees, firmware and signed recovery programmers are candidates, not certified recovery paths; an unreleased prototype's only installed system may have no compatible replacement. Preserve it and obtain a restorable backup before destructive experiments.
Then take a full backup of every partition except userdata:
- Skip userdata: it is the bulk of the space, it is the user's data, and it is the one partition you can afford to lose.
- Back up the device-unique partitions and never publish them. Modem NV/EFS, persist, and their calibration siblings carry the IMEI, radio calibration, and sensor trim for this individual handset. They are not reproducible from any image on the internet, and a port that corrupts them leaves a phone that cannot register on a network. Treat them as both critical and private.
- Hash every image and record the hashes with the partition table alongside them. Later you will need to answer "is what is on the device still what I backed up", and only a hash answers it.
- Verify the backup is actually restorable before the first risky flash, not after one.
What the backup is worth as research, beyond recovery:
- The stock DTB — ground truth for this board's device tree, and the tiebreaker whenever the sibling SoC's dtsi disagrees with it.
- Firmware blobs and their load order — what the vendor stack actually ships and where it expects to find it.
- The vendor kernel cmdline and boot image layout — offsets, header version, and the arguments the stock kernel was given.
- The exact kernel version string —
uname -aor/proc/versionfrom the stock ROM, while it can still be read (also unpackable later from the backed-up boot image). It is the fingerprint that selects the right GPL-published OEM source release in phase 2; vendors ship several builds and they differ. - Vendor configs — sensor, modem, and HAL configuration describing interfaces the mainline driver will have to satisfy.
Before trusting anything the device reports, check its health:
- Read the storage's own wear/lifetime report during phase 0 and record it (UFS life-time percentage, eMMC health register — whatever this storage exposes). Storage is the classic misdiagnosis: I/O errors read as a failing drive, the wear percentage proved it healthy, and the real fault cleared with one more fastboot flash of the stock ROM. The percentage separates "the hardware is dying" from "the software state is bad" — opposite next steps. And a media-failure verdict that would retire the device must survive a re-probe in a later session before it drives that decision: one port recorded a flash region refusing writes twice, cold power-off included, and the same write passed three of three the next day against a full zero-error read scan.
Absent wear fields mean health is unknown; coarse wear values do not exclude other storage faults. The earlier healthy-storage finding above overturned a wear diagnosis on that device, not every possible failure mode.
Then inventory and harvest the stock system while it is present:
- Inventory every component the stock system will name — panel, touch controller, sensor set, camera sensors, modem and its RF configuration, WLAN/BT chip, charger and fuel gauge, audio path — and compare against the official spec sheets for this variant: invoke the
find-docsskill for each component's documentation; if it is not installed, use plain web search. Sibling variants differ exactly here; the comparison catches "researched the wrong variant" before it costs a flash. - Evidence level — label each capability as declared, enumerated, driver-bound or exercised. A HAL name can reflect software ancestry rather than silicon; installed firmware may support an unfitted component. One camera advertised 1080p at 30 fps in USB descriptors while Android exposed at most 1024×768 and 27 fps; no frames were captured, so neither was measured capture throughput.
- Rooted stock beats a ROM image as a source. A backup preserves bytes; root keeps the stock system running and readable: the property dump (
getprop), the stock kernel config (/proc/config.gz), the mounted vendor/odm trees, HAL and sensor configs, calibration artefacts, factory field-test modes. Assume the user roots the device themselves; like bootloader unlocking, do not provide rooting steps — one wrong step here bricks — and resume once root is available. - Record the newest community custom ROM as an optional, up-to-date development source. Ask whether recent custom ROMs exist and note where they live. Measured: the most responsive OS ever run on one device was an unofficial recent-Android custom build, not the stock ROM — and its boot image shares the stock downstream lineage, so its DTB doubles as an independent cross-check against the phase 0 extraction.
Then set the working conventions the rest of the session assumes. Resolve the device project root first. For a multi-device workspace, this is the reference layout, not a requirement to restructure a single-device port:
kernel/<version>/ Clean upstream source + generic builder
soc/<vendor-soc>/ SoC-common patches, configuration and packages
os/<distro>/ Device-neutral OS integration and tools
devices/<model>/ Device assembly, patches, calibration and state
Shared bases never import device policy. Put changes in their owning layer; device facts, private captures and per-device agent context stay with the device. A shared kernel working tree used for experiments must be identified as patched, not mistaken for pristine upstream.
- Project layout. Everything large, private, or device-derived lives under
artifacts/at the device project root, not the multi-device workspace root — gitignored, never committed, never unignored — and is classified by what it is:private/for device-unique material that identifies the handset and never leaves the machine (the partition backup first of all, with a DO-NOT-RESTORE note for userdata inside it),firmware-harvest/for extracted blobs pending redaction,android/for reproducible stock-ROM packages and rooted captures,debug-evidence/for irreplaceable captures and preregistrations,reference/for reading copies of upstream sources. Publishable firmware goes to a siblingfirmware-publishable/repository with its own git history, derived from the harvest only after redaction of device-unique identifiers. Device-locallogs/takes one subdirectory per boot or deploy, plusLATEST-*symlinks so the newest capture is findable by name. A README insideartifacts/maps every path to its class and records hashes, acquisition method and coverage — it preserves provenance, not a substitute for integrity checks when needed. - Device control channels. Before any flash, prove the agent can reach the device:
adbandfastbooton the host, exercised against this device, and at least one post-boot control channel into the target Linux — USB-gadget Ethernet or a serial ACM console, ssh over network or over USB. Record which channels work and their addresses; a channel that is assumed but never tested is discovered missing in the middle of a wedge, which is the worst possible time. - Bind every operation to the target. Record the mapping between handset and transport identifiers, use an explicitly selected transport for every device command, and re-establish that mapping after re-enumeration. Verify product and target slot immediately before writes; a second connected phone must never become an implicit target. Keep identifying captures private. The binding constrains the host too: a reachability probe must never mutate host network configuration as a side effect. A successful ping is not target identity, and a name-based fallback can reconfigure the wrong interface — one absent gadget MAC made a control script modify an unrelated USB Ethernet dock's active IP settings. Inspect interface identity and route first, refuse missing or ambiguous matches, and make any host network setup a separate explicit command that touches only an owned, uniquely verified interface.
- Prove the operation, not just the connection. Record whether required backup and loading operations are actually supported before building around them. One unlocked bootloader supported neither RAM boot nor partition fetch, leaving a built rescue image unusable through its intended channel. Unsupported operations are not permission to test an unverified image by flashing instead; research a compatible, non-destructive route.
- A note of which recovery path is known to work.
Write an explicit rule into the project's agent context files — CLAUDE.md, AGENTS.md, and any equivalent — that all work touching this device goes through this skill's ruleset and no device action is taken outside it. If a backup already exists from an earlier attempt, confirm it covers these partitions and that its hashes still verify, rather than assuming it does.
Keep a bootable Android around for data gathering. While the stock ROM or a rooted custom ROM still boots, it stays an evidence channel for the whole port: read a working vendor driver's probe and firmware sequence live next to a mainline failure, and exercise hardware mainline cannot yet drive to prove the silicon itself is fine. Boot it deliberately when a question is best answered there — a mainline boot failure never forbids an Android boot for data; it only forbids concluding anything about mainline behaviour from Android behaviour without a cross-check.
1. Gather evidence from the live device first
Capture the failing run's full dmesg before changing anything. Enable the relevant debug knobs before reproducing (e.g. echo 0xffffffff > /sys/module/<driver>/parameters/debug_mask), because a second reproduction costs another boot.
Record acquisition and claim boundaries
For each artifact, record origin (live capture or archive), exact revision/build, acquisition method, coverage, access failures and integrity hashes. A running sysfs device tree is useful but may include runtime fixups: it is not an untouched boot-image DTB. One capture preserved 2,740 property files, verified 2,222 emitted properties byte-identical and accounted for 518 redundant name properties. All 2,839 manifest-listed files passed integrity checks; the inventory was still not a restorable partition backup. A firmware path inventory does not establish actual load order.
Carry phase 0's declared/enumerated/driver-bound/exercised labels into findings. Record running and deployed identities separately, and distinguish service readiness from end-to-end hardware behaviour. A registered sound card and successful silent playback do not establish acoustic qualification; mark listening, full-power, headset or microphone checks untested when they were not exercised.
Evidence channels, in rough order of reliability
- Live shell over the network or USB. Cheapest channel; use it for anything the device survives.
- A still-bootable Android. If the phase 0 harvest left the stock or a rooted custom ROM bootable, boot it deliberately when a question is best answered there: read a working vendor driver's probe and firmware sequence live next to the mainline failure, exercise hardware mainline cannot yet drive. Never conclude anything about mainline behaviour from Android behaviour without a cross-check.
- pstore. Once systemd is up, harvest from
/var/lib/systemd/pstore/, not/sys/fs/pstore/—systemd-pstore.servicemoves every record out early in boot, so an empty/sys/fs/pstoreis not evidence that no crash was recorded. The console region is typically a single slot: copy it to the host before doing anything else, or the next crash overwrites it in the archive too. - ramoops. Treat as lossy. DRAM charge retention decays across the unpowered interval — measured at roughly 6.5 % of bits on one device, enough to fail ECC on the zone headers and lose a record entirely — and warm reboots rot the region too (tens of blocks measured), so the decay follows the power cycle, not just the unpowered interval. Grep records fuzzily. What rots the region is time spent unpowered, not time spent spamming before you cut power, so let a wedged device log for as long as the console zone can hold, then power-cycle and power back on promptly. Check the zone size before assuming you must hurry: a 4 MiB console zone absorbs hours of watchdog spam before it wraps.
- Kernel-level USB ACM console (
CONFIG_U_SERIAL_CONSOLE=yplusconsole=ttyGS0on the cmdline). The only channel that survives a wedge — but verify it survives the reporting context before spending the flash. The payload you want is emitted insidepanic(), with local IRQs disabled and the other CPUs stopped; a console whose write path defers to a workqueue cannot run there, so the facility arms while the messages it exists for never print. Test the premise for free first: a kernel crash injector (where present, LKDTM's/sys/kernel/debug/provoke-crash/DIRECT) produces a deterministic synthetic lockup with the exact reporting geometry, no flash needed. A userspace ACM getty does not survive a wedge either — it needs a schedulable CPU and yields zero bytes while still enumerating fine over USB. Both config halves are required: the symbol alone leaves the facility inert.
Rules that turn inference into measurement
- Confirm what is flashed by reading the partition back, never from notes or a build log. Read with
iflag=directso the read itself does not perturb what you are debugging. Size-only comparisons prove nothing — boot images are padded to a fixed size. Hash the payload. - Reading a debug interface can manufacture the symptom. Sampling DRM/GPU crash-state nodes while the GPU is actively submitting synthesises fault and recovery lines that never occurred (measured: five reads, five fault/recover pairs; none unobserved). Before trusting a log line, check whether your own observation produced it.
- A status register that never changes under stimulus is a latch, not telemetry. Before trusting a polled hardware status bank, establish liveness with a value known to change: one camera link bring-up read status bytes byte-identical with the transmitter on and off, and only that proved the bank was static rather than the link healthy.
- Capture a rejection's raw response before interpreting the mapped errno. A firmware call's
EINVALwas ambiguous — wrong encoding or refused policy — until dynamic debug in the call wrapper exposed the raw response words: a well-formed call carrying an explicit refusal. Policy, not encoding — the opposite verdict, the opposite next step. Classify every firmware rejection as wrong encoding, refused by policy, or not found before acting. Mirror rule: a clean, well-formed negative is positive proof — a not-found from a correctly-formed lookup validates the encoding and eliminates a branch of the plan 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, and the number looked real until the condition was checked. The valid comparison, 132 → 485, needed the compositor stopped for both runs. Before trusting a benchmark, name the condition that could void it and verify it held for every run.
- Record the rules you withdraw. Bring-up accumulates folklore fast, and a rule that was right for one hardware revision or one buffer size becomes actively misleading after it changes. Keep the overturned version next to the current one with what changed, so the next session knows which advice has been stress-tested.
Host-side gotchas worth pre-empting
timeout N ssh <device> '<cmd>'kills the local client only; the remote process keeps running. Follow up with an explicit kill.pgrep -fandpkill -frun over that same ssh path self-match their own remote shell. Filter the matcher out, or match by PID.findneeds-Lwhen the start path is a symlink, or it returns silently empty.- Any wrapper or hook that rewrites command output — token filters, pagers, formatters — will corrupt evidence, sometimes by inverting a test rather than obviously breaking it. Use the raw command for anything you will commit as a finding.
Operator hands are a step, not an assumption
- When a step needs the operator's physical action — moving a card between host and device, replugging a cable, holding buttons, confirming something on the device screen — name the exact action and its direction ("move the SD card from the host to the phone"), then verify it happened before continuing: an observable signal, not the fact that you asked. A port session loses minutes to hours when the agent proceeds on an instruction the operator never registered as an action item. The signal can be an appearance (a mount appears, USB re-enumerates, slot state changes) or a disappearance (the device powers off, USB drops, a channel goes silent) — and a disappearance is ambiguous: "not done yet" and "done, the device is off now" look identical to a poller. Disambiguate it: watch the channel closely at the moment the action is due so the drop is caught as an event rather than inferred from a later silence; where the drop cannot be watched, ask the operator to confirm when it is done and treat that confirmation plus a verified absence (a probe to the channel still failing afterwards) as the signal. Either way, never wait passively: poll on a short interval, and if the expected signal has not arrived within a stated bound, re-ask the operator — a session that freezes on a request the operator never registered as an action item is the same loss, just slower.
- A total channel loss after a suspend/resume experiment has three branches, not two. From the host, a live phone with a dead channel driver is indistinguishable from a hung or dead device — one port's "device suspended and never came back" was wrong: suspend and resume had worked and run for another 10.5 hours; only the USB gadget failed on resume (
HS-PHY not in L2). After a replug, read the device's own journal from its last boot before declaring a hang — the verdict "it died" and "it survived but its driver did not" have opposite next steps.
2. Research before implementing — all of these sources
Research always precedes implementation. What it consumes depends on what you are doing:
- Investigating a failure — the phase 1 evidence. Capture it first; researching a symptom you have not actually read is guessing with citations.
- Adding a capability — a driver, a peripheral, a subsystem that has never come up — the phase 0 artefacts are the device data. The stock DTB, the vendor configs and the firmware layout describe how this board expects the thing to be driven, and they are as authoritative here as a crash log is there.
Either way, before writing anything:
-
Reason about the subsystem — identify which driver, binding, or firmware interface owns the failure, or would own the new capability. Then read a kernel-level skill by name: invoke
linux-kernel-developmentif installed, elselinux-kernel-crash-debug, else any subsystem-specific kernel skill you have — its register-level and binding detail is exactly what this step needs. If none of them resolves, reason it through directly; do not stall on the missing skill.Indexed local navigation is optional. Invoke the
graftskill when installed and the relevant repositories are indexed, to locate implementation and trace dependencies across kernel, SoC, OS and device layers. Verify decisive details against the exact source revision being built. An index miss is not proof that support is absent: check coverage, then use direct source search, language-server navigation and file reads. Missing tooling or indexes never block research. Graft helps navigate sources; it is not an independent evidence source. -
Look up userspace libs and tools involved — current docs, not recalled API. Invoke the
find-docsskill for each library or tool; if it is not installed, fall back to reading the project's current documentation over the web. -
Web research across every source family (parallel subagents work well here). Invoke the
wigoloskill for the sweep — its cache matters, since sessions re-read the same pages — and if it is not installed, run the sweep with plain web search instead.- The SoC vendor's mainline collaboration project — for Qualcomm, github.com/qualcomm-linux. This is the vendor publishing mainline-first kernel work itself: updated dtsi, bindings, subsystem enablement and tooling, often ahead of the nearest mainline release tree. Check it before treating a missing node or driver as work you must author — the vendor may have landed it since, and its trees show how the vendor intends the hardware to be driven under mainline, which is the most authoritative template there is.
- Downstream vendor kernel — the GPL-published OEM source. GPLv2 obliges Android OEMs to publish the kernel sources they shipped, so an exact release for this device usually exists: the OEM's open-source portal (Samsung, Sony, Xiaomi, Fairphone, OnePlus/OPPO and Motorola all run one), their GitHub/GitLab mirrors, and community archives such as XDA when a portal link has died. Select the tarball whose version string matches
/proc/versionfrom phase 0 — vendors publish several, and they differ. Compliance quality varies, so treat the tree as an oracle to mine — the board dts and defconfig for this exact device, and the out-of-tree vendor drivers (touch, panel, charger, modem glue) with their register sequences and firmware handshakes — rather than assuming it builds. When no exact release surfaces, a sibling device's release usually still carries the SoC dtsi. Better still: the stock DTB pulled off the device itself (ddthe untouched boot slot, scan ford00dfeed,dtc -I dtb -O dts). That is ground truth for this board and has corrected wrong sibling-SoC guesses more than once — including a SMMU stream ID that the sibling SoC got wrong and the stock DTB got right. - AOSP / Android platform source — android.googlesource.com and source.android.com: HAL interfaces, framework/services, init and VINTF configuration, SELinux policy, boot-image tooling and shared native libraries. Select the relevant branch or tag and record its exact revision. Platform contracts complement, not replace, the exact OEM source, stock DTB and proprietary vendor implementation for board-specific behaviour.
- postmarketOS — pmaports device packages and APKBUILDs, merge requests, wiki device pages, and the SoC-mainline tree.
- Halium / UBports —
halium/android_device_*trees,hybris-bootconfigs, and UBports device ports. Best source for how the vendor firmware expects to be driven: firmware blob paths and load order, the properties and sockets daemons expect, sensor and modem HAL configs. When a mainline driver probes but the firmware handshake fails — or you need to know what handshake a not-yet-supported peripheral expects — this is usually where it is written down. - Mobian — the Debian device repos.
- NixOS — mobile-nixos patterns and nixpkgs packaging.
Verify what you already carry against every downstream DT you hold. Existing patches go stale against new ground truth: one port re-verified its patch set against the newest stock OTA's DT, the original phase 0 backup, and a custom ROM's tree — four patches confirmed, one CPU-capacities discrepancy surfaced that no single source would have shown. And read the vendor flash script before trusting it: at least one ships slot-blind, hard-coding the wrong system image for the active slot.
- Sweep sister devices, not just this one. Within those sources, enumerate ports of the same device family — sibling models sold under the same marketing name (the codenames phase 0 recorded), and near-neighbours on the same board or SoC generation. pmaports device pages, Halium/UBports device trees, XDA device forums, and the OEM kernel tree's own
dtsdirectory are where they surface. Diff each port's board files — dts, defconfig, firmware blob lists — component-by-component against the phase 0 inventory: a match yields a proven driver-plus-firmware combination to adopt; a divergence marks where the family split, and is itself a hypothesis. The comparison is also the guard — a sister device is a lead, not a precedent. Adopt only what the diff confirms against this variant's spec sheet and stock DTB: sibling variants differ exactly in panel, touch, and modem.
Search internal product codenames and OEM/ODM archives too. An extracted alternate-system image can reveal vendor implementation under a name absent from retail documentation. Preserve archive provenance and inspect its board identity, configuration and firmware lineage. Archive contents are not evidence that the physical handset runs the image, that it is compatible, or that flashing authorization and recovery exist.
Security findings exposed by bring-up
A bulletin is not proof of a fix in your tree. Compare advisories, available fixing commits, the upstream implementation and AOSP against the exact revision being built; map that source to the executed binary where possible. One FastRPC investigation reproduced an unchecked DSP-length parsing path against a recent AOSP release despite an older bulletin describing that vulnerability class. Similar mechanism and an affected-chip list alone do not establish exact CVE identity: qualify attribution until technical evidence or maintainer confirmation establishes the match.
Firmware data crosses a trust boundary. Trace raw length words, decoding, signedness, arithmetic, cursor advancement and copy bounds before blaming firmware or hardware. One listener crash combined an unchecked decoded length with a remaining-space calculation that narrowed and wrapped positive. A protocol desynchronization may explain malformed input; it does not justify unsafe parsing.
Make the shared defect reproducible upstream. Reduce the field failure to the real parser and the smallest portable reproducer, not a handset-specific workaround. One gate exercised five lengths — the captured value, remaining space plus one, 0x7fffffff, 0x80000000 and 0xffffffff — recording SIGSEGV before the fix and containment afterwards. Submit affected revisions, field evidence, reproduction prerequisites, minimal patch and before/after results to the owning project's appropriate reporting channel. Redact identifiers and private material; follow its security-disclosure policy for sensitive details. Record submitted, accepted, merged and released separately; a report or PR is not evidence that consumers have the fix.
Thin results are the normal case, not a blocker. Most of what a port needs has never been done on this board, and often not on this SoC at all. Every source reporting nothing means nobody has published the work — it never means the work should stop or be handed back. When precedent is absent, the answers are still in the primary artefacts: the out-of-tree vendor driver that already drives the peripheral (register sequences, firmware handshake, GPIO and regulator wiring), the stock DTB's own node for it, the vendor HAL config naming what userspace expects, and the closest mainline driver for the same device class as a structural template. Read those, derive the design, and write the missing piece — a DT node, a quirk, a small driver. Authoring that piece is the ordinary terminal state of a bring-up, not an exception to be justified; the phase 0 backup and the one-variable-per-flash rule exist precisely so that a wrong derivation costs one boot and one ledger line, the same as any other refutation.
An empty sweep and an empty scan are different findings. An empty sweep means nobody has published the work. An empty scan — an extraction that returns nothing — is void until the same method has been run against a known positive and its coverage verified: file counts, segment lists, what the glob actually matched. 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 as its positive control.
Downstream identifiers are downstream's private numbering. An id or enum in the vendor tree is that tree's own bookkeeping — never copy it into a mainline node. One downstream thermal trip pointed at "sensor 5"; on mainline, sensor 5 read a constant 0, and the real tracker was sensor 6 — settled by stimulus, heating the suspect cluster and watching which zone moved. When a copied mapping reads dead, suspect the numbering first and settle it by stimulus, not by more copying.
3. Implement only with a promising, evidence-backed hypothesis
-
A reboot is yours to do when a channel exists. If the device is reachable — ssh, USB gadget, fastboot — reboot it yourself and continue; asking the operator to press buttons wastes a round trip. Reserve operator hands for what no channel reaches: a battery pull, a hardware key combo, physically moving media.
-
One variable per flash, falsifier before build. State the hypothesis before flashing — and write the falsifier before writing any code: the smallest experiment whose result kills the plan either way, built first. One of them — a single module load — returned the refusal that killed a 9.4k-line port premise. Order experiments by cost per information, not plan order: the cheapest decisive experiment runs first.
-
Prefer runtime tests over reflashes: push files with a tar pipe, unbind and rebind drivers,
insmoda module. Seconds instead of minutes. But a probe leaves state: a failed init can leave its platform device registered, so every retry costs a reboot, and a crashed probe can wedge the board into a physical power-cycle. Stage the probe's inputs and verify their hashes host-side before firing, make the probe idempotent, and expect one reboot per failed attempt. Two more ways state bites: staged inputs can persist across reboots and load stale against a new kernel — re-verify what you fire after any kernel change; and never probe through the channel the probe rebinds — one unbind/rebind test run over that same USB link severed its own control channel. -
Device-tree-only changes are cheap — build just the DTB when the toolchain allows it, then check the runtime surface, not just the build: a DT that builds clean can still bind to nothing (one thermal driver registers one zone per sensor id and silently drops a duplicate, so the zone list, not the build log, is the verdict).
-
Never edit-and-hope. Read the exact current lines and write the exact replacement — no pattern-guessing, then "repairing" the syntax errors the first attempt produced. An agent that announces it is fixing its own broken edit has already cost a loop iteration, and on a port the sloppy edit can reach a flash before it reaches a build. If a tool cannot make the edit precisely, stop and re-read; hand-repair of self-inflicted breakage is a defect, not a workflow.
-
Separate restructuring from upgrades. Hold the upstream version, patch content and configuration fixed while moving layers. Compare the relevant built payloads before and after; one split preserved kernel
ImageandSystem.mapbytes. Then upgrade as a separate change. Verify all downstream DTB and boot-image assembly paths too: kernel equivalence does not establish that the complete image still builds, and a successful build does not identify what is flashed. -
Start the patch battery pristine. Use a fresh disposable tree at the exact intended upstream revision, then check and apply each patch in series order so later checks see their prerequisites. Do not classify an apply failure as “already upstream” from a used working tree. One contaminated rebase check dropped a required header on that premise; the build failure forced its restoration. Establish absorption from upstream code/history before retiring a patch.
-
A rebuild is the most expensive step in the loop — verify the change before paying for it. A full kernel rebuild costs tens of minutes, and a build that dies 15 minutes in on a missing symbol taught you what a 10-second host-side check would have. Measured: four consecutive rebuilds were burned on missing symbols in patches that no one checked before building. Before any full rebuild, run the cheap battery host-side: every patch applies cleanly to the exact tree (
git apply --check); every symbol a patch references is defined somewhere the patch can see (grep the tree — a symbol renamed upstream or only present in another directory kills the build late); every config option a patch or defconfig sets actually exists in this kernel's Kconfig — an option that does not exist is silently ignored, so a clean config read is not evidence it took effect; and compile the touched directories or translation units first, so the expensive full build only ever runs on code that already compiled alone. -
Build the artifact you selected, and report per artifact. A job that bundles kernel, module, DTB and full system under one process state conceals completed deliverables: three of four targets had finished while a patched desktop still compiled through emulation, and the combined status reported only "still running". Select the target explicitly, keep its progress and cancellation separate, and keep status queries free of side effects — asking what is built must never start a build. A finished store output stays finished when a sibling target is cancelled; only completed outputs are reusable.
-
Source preparation is transactional or it lies. Readiness must be derived from the actual assembly, never from a hand-maintained list that can silently stop matching: one helper searched for declarations the current flake no longer contained, applied zero of 47 patches, and stamped itself ready. Apply each patch against the state left by its predecessors; missing files, check and apply failures are fatal; a success marker is published only after every step succeeded; and readiness fingerprints the source identity plus the ordered patch contents, so changed inputs report stale instead of reusing a tree. A partial or failed preparation must leave the previous ready tree usable.
-
After 3 refuted fixes: stop guessing, not stop working. Three misses from the same model of the failure mean the model is wrong, and a fourth tweak from it will miss too. Return to step 2 with a wider scope — a different layer of the stack, or the primary artefacts directly where precedent is absent — and come back with findings stacked (worked example below).
A worked shape, from a Wi-Fi bring-up that took this path: five blind device-tree tweaks all failed. The fix was five separate findings stacked, every one of them from research rather than guessing — a trustzone memory mode, vendor firmware paths, a host-capability quirk, the SMMU stream ID off the stock DTB, and one property that mainline had made obsolete and needed dropping. No single tweak would have got there.
Before writing, recheck phase 0's explicit handset/transport mapping, product and slot. A deployment is a state machine, not one command. Copying the closure, selecting the next-boot profile, switching the running system and booting are separate observed postconditions: a closure already present on the device is not an activation, a matching profile symlink does not prove a previous activation completed, and only a separately observed reboot marks the system booted — never flash or reboot automatically to make states agree. An explicit activation request reruns even when nothing needed copying. After deployment, verify the flashed payload by readback, reboot through the available channel, and identify the running kernel, matching modules and intended system. Recheck boot-time initialization and required runtime functions: one upgrade lost sound-card registration through module coldplug ordering even though later manual initialization could work. Service readiness, silent playback and operator-confirmed acoustic behaviour are separate results; retain deferred or untested acceptance criteria explicitly rather than declaring them passed. The same honesty governs revalidation: on a required probe, empty output, a timeout or a transport failure is a FAIL, never a SKIP — one checker scored a timed-out partial output as PASS and a dead transport as SKIP, then exited 0 overall — and a verification tool must never trigger an implicit build to obtain a missing image argument.
Only after the new deployment passes its real reboot and required runtime checks, inventory obsolete kernels, module trees, system generations, deployment roots and staging files. Preserve the running system, next-boot target and a complete known-good recovery path, including their dependencies; a saved boot image alone may not recover the system. Archive required historical images, symbols and logs on the host before retiring device copies. Select retired deployments explicitly and remove them through the distro's package manager or garbage collector, never by manually deleting managed files or blanket deletion of old generations. Record filesystem space before/after, collector-reported reclamation and retained recovery targets. One port's operator reported substantial device-space recovery through GC; no precise total was retained here, and total GC savings must not be attributed to kernels without separate accounting.