Playbook: getting at vendor firmware
scope: generic · severity: technique · confidence: proven · subsystem: build
Goal: know what firmware the vendor image contains, which mainline driver consumes each blob, and where it expects to find it.
Done when: you can name every blob a subsystem needs before you try to bring that subsystem up.
Two different questions
Section: Two different questionsFor shipping, postmarketOS packages firmware in a firmware-<device> aport
that fetches from a source pmaports accepts. That is settled policy and not
what this playbook is about.
For development, you need to know what is in the image before you can write that aport at all. Which blobs exist, what they are called, what consumes them. That is this.
Read without mounting, and without root
Section: Read without mounting, and without rootporthole blobs unpack factory.zip --dry-run # a factory image NESTSporthole blobs unsparse vendor.img # Android sparse -> rawporthole blobs ls vendor.raw.img # ext4, via debugfs, no mountporthole blobs inventory vendor.raw.img # what consumes whatTwo things make this pleasant instead of a fight:
debugfs -R "ls -l /firmware" imagereads ext4 without mounting it. No loop device, no root. A loop mount needs privilege, and privilege is what this project spends its time avoiding — see a-long-sudo-cache-is-unlimited-root.- Android sparse images decode from their own header, so
simg2imgis not required. The format is a 28-byte header and four chunk types.
What the file names tell you
Section: What the file names tell youThe names repeat across Qualcomm devices, which is what makes this knowledge worth carrying rather than re-deriving:
| blob | consumed by | where it goes |
|---|---|---|
wlanmdsp.mbn |
ath10k_snoc |
ath10k/WCN3990/hw1.0/ |
bdwlan.bin, bdwlan.b?? |
ath10k_snoc |
board data — see below |
a???_zap.mdt |
msm/adreno |
the GPU stays in secure mode without it |
a???_gpmu.fw2 |
msm/adreno |
the GPU will not clock without it |
adsp.mdt |
q6v5_pas |
no audio without it |
slpi*.mdt |
q6v5_pas |
accel, gyro, proximity route through it |
venus.mbn |
venus |
hardware codecs; software ones work regardless |
*.jsn |
pd-mapper |
protection domain maps |
Four things that cost time
Section: Four things that cost timeThe factory image nests. Outer zip → image-*.zip → the partitions. People
lose an afternoon to the structure alone.
board-2.bin and firmware-5.bin are GENERATED, not shipped. They are
built from bdwlan.bin + bdwlan.b?? with qca-swiss-army-knife. Searching
vendor.img for them finds nothing, which reads as “the image is incomplete”.
Modem firmware is not in vendor.img. It lives on its own modem_a /
modem_b partition, readable read-only from the running device once storage is
up — no recovery image needed. Only ever read those partitions, and back up
modemst1/modemst2/fsg/fsc before anything that writes.
pd-mapper needs two maps that live in different images. modemr.jsn is on
the modem partition; modemuw.jsn is in vendor.img. The second declares the
wlan/fw service that ath10k_snoc blocks on, so with only the first,
pd-mapper exits “no pd maps available” and WiFi never comes up. And pd-mapper
looks for them next to the firmware, honouring firmware_class.path, not in
/lib/firmware.
Size the reserved memory from the blob itself
Section: Size the reserved memory from the blob itselfA remoteproc blob’s own program headers say how much memory it needs. Taimen’s
modem.mdt spans 0x8cc00000..0x94400000 — 120 MiB — while stock reserved
112 MiB, so the mainline override was required rather than cosmetic. Read it
out of the blob rather than copying a number from downstream.
Related: 50-wifi-bt-modem, running-a-device-script-on-the-host, instrument-guilty-until-proven-innocent.
