Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/saf/device/evidence/build-pmos-era.md
Fimeg bde961c6f2 saf: one spine — device, state, and work under the index
PAF becomes saf/device (history kept), STATE.md dissolves
into saf/state.md with the dated era archived, the substrate
SAF moves up from souveraine, and every agreement points at
saf/INDEX.md and nowhere else. one map, nothing to remember
2026-08-18 09:47:30 -04:00

9.1 KiB

PAF · Build & packaging (pmbootstrap, the proper path)

How the blueline kernel + device package are actually built and shipped. This is the canonical build layer — earlier sessions wasted hours on host make and on undocumented pmbootstrap behaviours. Build with pmbootstrap; it is what every other sdm845-mainline contributor uses and what produces installable APKs. Host make ARCH=arm64 LLVM=1 is not the path (it needs llvm-readelf/nm/ objcopy on the host and does not produce a package).

The two packages — do not conflate them

  • linux-postmarketos-qcom-sdm845 (device/community/) — the KERNEL package. Kernel image (vmlinuz/Image.gz), all modules, the blueline DTB. Userspace never goes here.
  • device-google-blueline (device/testing/) — the DEVICE package. Device userspace: firmware deps, hexagonrpcd/q6voiced confs, the systemd preset, and now the modem PDC self-provisioner (qcril-autoselect.py + .service, enabled via the preset). This is where per-device userspace belongs — modelled on how q6voiced.conf is already installed.

Unified 7.1.1 kernel build (2026-06-22)

Base is the sdm845-mainline tree at tag 85f1df2a (= 7.1.1), checked out at references/linux-7.1.1-sdm845-CANONICAL. All accumulated fixes are now commits in that tree, not scattered source= patches:

  • SLPI keep-proxy-power-on (the sar.cc:27 crash-loop fix — see slpi.md)
  • SLPI enabled in the blueline DTS
  • fastrpc SDSP hyp-assign tolerance

Built via source override:

cd ~/pmos/pmaports
pmbootstrap build --src=$REPO/references/linux-7.1.1-sdm845-CANONICAL \
    linux-postmarketos-qcom-sdm845

APK lands in ~/.local/var/pmbootstrap/packages/edge/aarch64/.

pmbootstrap build gotchas (each cost real time — RECORD)

  1. The in-tree arch/arm64/configs/sdm845.config is merged LAST and silently overrides our pmaports config. prepare() runs merge_config.sh -m <pmaports config-*.aarch64> arch/$_carch/configs/sdm845.config — and merge_config -m is last-wins. So any symbol set in the in-tree fragment beats the same symbol in the pmaports config, even though the pmaports config "looks" authoritative. This silently broke wifi (2026-06-29): pmaports set CONFIG_QCOM_PD_MAPPER=m, but the upstream sdm845-mainline in-tree fragment carried =n (their "use userspace daemon, poco-f1 mic race" default), so every build shipped pd-mapper OFF → no wlan PD → ath10k_snoc probe defers → no wlan0. The =n rode in via the vanilla import commit 310c17cc6, not a deliberate choice. Fix: set the symbol in the in-tree fragment (references/linux-7.1.1-sdm845-CANONICAL/arch/arm64/configs/sdm845.config), not just pmaports. Audit rule: when a built kernel disagrees with the pmaports config, grep the in-tree fragment first — it wins. Verify on-device with zcat /proc/config.gz | grep <SYMBOL>.

  2. olddefconfig is mandatory on a version bump. 7.1.1 added Kconfig symbols not in the rc1 fragment config; abuild's build runs non-interactive, so a bare make hits the oldconfig EOF prompt and dies at prepare0. The APKBUILD build() now runs make ARCH=$_carch LLVM=1 olddefconfig first, then a serial make ... -j1 prepare, then the parallel build.

  3. The *.bc gitignore drops a tracked kernel source file. pmbootstrap's source-override rsync is gitignore-aware but honors *.bc (line 16) WITHOUT the negation !kernel/time/timeconst.bc (line 180). So timeconst.bc — a real bc(1) source needed by prepare0 for include/generated/timeconst.h — is silently excluded → No rule to make target 'kernel/time/timeconst.bc'. Same class of breakage hits the negated dotfiles (.clang-format, etc.). Fix: the kernel APKBUILD prepare() restores these from the source mount (/mnt/pmbootstrap/source-override/) after default_prepare.

  4. The persistent build copy poisons incremental builds. pmbootstrap rsyncs the override into /tmp/pmbootstrap-local-source-copy (inside the native chroot) WITHOUT --delete. A prior interrupted build leaves stale host-tool binaries (scripts/basic/fixdep, scripts/mod/mk_elfconfig) + .cmd files; the next build's make then trusts the stale stamps, never recompiles them, and fails mk_elfconfig: not found at prepare0. If you see host-tool "not found" with no preceding HOSTCC line, the copy is dirty. Recovery: let pmbootstrap recreate it cleanly (delete the copy dir; a fresh rsync from the clean host tree rebuilds host tools from scratch). Note: git clean -dfx on the HOST tree does NOT touch this chroot copy.

Kernel-swap / boot.img (from modem.md, kept here for the build flow)

The modem stack is fully modular (qcom_q6v5_mss, qrtr*, qcom_glink_smem, mhi* all =m), so a kernel swap needs the matching lib/modules/<kver>/ on the rootfs or the modem silently no-loads (a false 52). UFS + USB-gadget-NCM are =y, so a module-mismatched boot still reaches rootfs + USB.

Boot image: cat vmlinuz <blueline>.dtb + the standing initramfs, mkbootimg header v0, base 0x0, kernel_off 0x8000, ramdisk_off 0x1000000, tags 0x100, real cmdline quiet splash … pmos_boot_uuid=2CE8-836F pmos_root_uuid=26733d78…. Rootfs is found by pmos_root_uuid (the ext4 FS UUID), so the slot LETTER only decides which block device we write into — the cmdline is slot-agnostic.

SLOT MAP (verified live via fastboot getvar current-slot 2026-06-26): Android = slot B (current-slot: b, slot-successful:b: yes). pmOS = slot A. Flash ONLY boot_a (fastboot flash boot_a …; fastboot --set-active=a) and write the rootfs to system_a. NEVER touch boot_b / system_b (that is Android now), EFS(modemst1/2), userdata (single SHARED partition, ~119 GB), or TZ/XBL/ABL. (Earlier revs of this doc had the slots reversed — they were wrong; the device is authoritative.)

FLASH GOTCHAS (each cost a boot this session — RECORD)

  1. system_a rejects bootloader-fastboot flash → must use TWRP cat (or fastbootd). fastboot flash system_a from the bootloader fails Partition should be flashed in fastbootd. This fastboot/bootloader gates system writes to userspace. We DON'T have a working reboot fastbootd (binary says "unknown reboot target"), so the path is: fastboot boot ~/Downloads/twrp-3.7.1_12-0-blueline.img, wait for adb recovery, then adb shell -T "cat > /dev/block/by-name/system_a; sync" < raw.img (system_a/dev/block/sda5, a real 2.95 GB physical partition, NOT dynamic).

  2. pmbootstrap's google-blueline.img is ANDROID-SPARSE, not raw — un-sparse before cat. THE bug that gave a kernel-7.1.0rc1 debug-shell boot (initramfs can't find root). deviceinfo has deviceinfo_flash_sparse="true", so the final image starts with the sparse magic 3a ff 26 ed, not a partition table. cat'ing it raw onto system_a writes a sparse container, so there's no GPT/ext4 on the partition → initramfs findfs UUID= fails → debug shell. Fix: simg2img google-blueline.img raw.img first, THEN cat raw.img. Verify: a correct raw image starts with 0000, reads as GPT @ 4096-byte sectors (fdisk -b 4096 -l: p1 ≈236M EFI/boot, p2 ext4 root at byte 255852544), and has the ext4 magic 53ef at 255852544+1080. This matches the known-good slotb-backup byte-for-byte. (UFS is 4096-sector — fdisk default 512 shows NO partitions on the raw image; that's a red herring, not corruption.)

  3. Quarantine stale _p<timestamp> kernel apks before install, or apk grabs the wrong one. A --src build auto-versions as 7.1.1_p20260625…, which apk sorts ABOVE the tagged 7.1.1-r2 (the _p post-suffix outranks the bare tag). pmbootstrap install then silently pulls the OLD kernel. Move the _p apks out of ~/.local/var/pmbootstrap/packages/edge/aarch64/, rebuild the index (apk index *.apk + abuild-sign in the native chroot — the pmbootstrap index wrapper failed on its own quoting), then pmbootstrap zap (the rootfs chroot has the _p kernel installed and apk won't downgrade) and re-install.

The libqmi fork — packaged as the temp/libqmi override

qcril-autoselect.py needs the openimsd libqmi fork (PDC Set Feature msg 0x2E) via GObject-introspection; stock libqmi lacks both the message and the typelib. It is packaged as temp/libqmi (pkgname=libqmi, source gitlab.postmarketos.org/modem/openimsd/libqmi commit b683efb, built with -Dintrospection=true, provides libqmi + qmi-utils). Because the aport's pkgname is libqmi, it shadows Alpine's stock libqmi, and device-google-blueline pulls it in via its libqmi / qmi-utils deps — a fresh pmbootstrap install lays down kernel + provisioner + fork as one unit. The built apk carries usr/lib/girepository-1.0/Qmi-1.0.typelib (verified).

Watch-item: the override is pinned at 1.39.0-r0, equal to current Alpine edge. If Alpine bumps libqmi above this, apk could prefer stock at install — bump the override's pkgver/pkgrel to stay ahead. (references/openimsd-libqmi + tools/build-openimsd-libqmi.sh remain as the on-device overlay fallback.)