fp6-img/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract

294 lines
10 KiB
Text
Raw Normal View History

aports: extract aw88261_acf.bin on-device instead of shipping it New package fp6-vendor-blobs: a manifest-driven systemd oneshot that copies proprietary blobs out of the stock Android partitions on first boot. pmOS installs flash only boot+userdata, so every installed unit still carries the stock vendor/dsp partitions - the device duplicates a file it already contains, for its own operation, and nothing proprietary is distributed by us. Partitions are mounted read-only (ext4 additionally with noload - not even a journal replay touches the stock data), every copy is sha256-pinned and a miss fails loudly, and the consuming devices are unbound and re-probed afterwards so the feature works the same boot. The rebind is unconditional by design: aw88261 binds on a bare i2c chip-id probe and requests the ACF only at ASoC card init (~21 s, after SoundWire enumeration), so a bound device can still be one that lost the firmware race - and that failed request is never retried. soc-fairphone-fp6-audio (pkgver 4) stops shipping the blob, installs the manifest fragment instead, and re-runs the extractor from post-install/post-upgrade so upgrades restore the file immediately (the old package version removed it on upgrade). License drops back to plain BSD-3-Clause - nothing proprietary left in the package. Verified on the dev phone (fp6 repo journal/blobs/captures/ 2026-08-24-first-boot-extractor-verification.txt): first-boot extraction + same-boot audio, mid-session post-upgrade recovery, and the everyday fast-path no-op; committed files byte-identical to the tested deployment. Assisted-by: Claude:claude-fable-5
2026-08-24 21:00:34 +02:00
#!/bin/sh -u
# fp6-vendor-blobs-extract - copy proprietary blobs out of the stock Android
# partitions instead of distributing them. pmOS installs flash only
# boot+userdata, so every installed FP6 still carries the stock vendor/dsp
# partitions: the device duplicates files it already contains, for its own
# operation - nothing is distributed by us or anyone else. Design, legal
# frame and on-phone verification: fp6 repo journal/blobs/.
#
# Manifest fragments: /usr/share/fp6-vendor-blobs/manifest.d/*.manifest,
# processed in sorted order; '#' comments and blank lines ignored:
#
# file <partition[,partition...]> <path-in-partition> <dest> <sha256>
fp6-vendor-blobs: reassemble a Qualcomm trustlet, not just copy a file The fingerprint matcher is a proprietary OEM-signed trustlet, and it is not shipped as one file: QTEE images live in the modem partition's image/ as an ELF header+hashes file plus one payload per program header, and the loader wants each payload written back at its segment's p_offset. So the existing file directive cannot reach it. An mbn directive does, with the same guarantees file has: the sha256 is of the reassembled image, a mismatch tries the next partition, and an unverified image is never installed. Reassembly is not a concatenation -- segments are page aligned but not contiguous, gaps stay zero, and two pairs of focal64's nine segments share an offset, so they are written in index order and the later one wins, exactly as the bring-up repo's reassemble.py does. Verified on the dev phone against the hash QTEE has actually accepted since August: 3600472 bytes, sha256 1930c490..., reassembled from the phone's own modem_a. The retry path was verified too, with a deliberately wrong first partition -- which is how the variable clobber got caught: POSIX sh has no locals, and reassemble() taking rdir= rewrote its caller's copy to the mount path, so the second partition would have been searched at $MNT/$MNT/... The fast path needed teaching as well: mbn's dest is the fifth field, and a first boot would otherwise have exited early and extracted nothing. Not pushed. The consumer fragment lives in the fingerprintd package.
2026-09-05 03:56:13 +02:00
# mbn <partition[,partition...]> <dir-in-partition> <name> <dest> <sha256>
aports: extract aw88261_acf.bin on-device instead of shipping it New package fp6-vendor-blobs: a manifest-driven systemd oneshot that copies proprietary blobs out of the stock Android partitions on first boot. pmOS installs flash only boot+userdata, so every installed unit still carries the stock vendor/dsp partitions - the device duplicates a file it already contains, for its own operation, and nothing proprietary is distributed by us. Partitions are mounted read-only (ext4 additionally with noload - not even a journal replay touches the stock data), every copy is sha256-pinned and a miss fails loudly, and the consuming devices are unbound and re-probed afterwards so the feature works the same boot. The rebind is unconditional by design: aw88261 binds on a bare i2c chip-id probe and requests the ACF only at ASoC card init (~21 s, after SoundWire enumeration), so a bound device can still be one that lost the firmware race - and that failed request is never retried. soc-fairphone-fp6-audio (pkgver 4) stops shipping the blob, installs the manifest fragment instead, and re-runs the extractor from post-install/post-upgrade so upgrades restore the file immediately (the old package version removed it on upgrade). License drops back to plain BSD-3-Clause - nothing proprietary left in the package. Verified on the dev phone (fp6 repo journal/blobs/captures/ 2026-08-24-first-boot-extractor-verification.txt): first-boot extraction + same-boot audio, mid-session post-upgrade recovery, and the everyday fast-path no-op; committed files byte-identical to the tested deployment. Assisted-by: Claude:claude-fable-5
2026-08-24 21:00:34 +02:00
# rebind <bus> <device>
#
# file: mount the first available listed partition READ-ONLY (ext4 also
# gets -o noload - never a byte written to the stock partitions, not
# even a journal replay), copy <path-in-partition> to <dest>, verify the
# sha256. A missing source or a hash mismatch tries the next listed
# partition; no verified copy on any of them fails the run loudly - an
# unverified blob is never installed and a missing one never silently
# skipped. Dests that already exist are left alone (no hashing: a
# deliberately replaced file stays).
fp6-vendor-blobs: reassemble a Qualcomm trustlet, not just copy a file The fingerprint matcher is a proprietary OEM-signed trustlet, and it is not shipped as one file: QTEE images live in the modem partition's image/ as an ELF header+hashes file plus one payload per program header, and the loader wants each payload written back at its segment's p_offset. So the existing file directive cannot reach it. An mbn directive does, with the same guarantees file has: the sha256 is of the reassembled image, a mismatch tries the next partition, and an unverified image is never installed. Reassembly is not a concatenation -- segments are page aligned but not contiguous, gaps stay zero, and two pairs of focal64's nine segments share an offset, so they are written in index order and the later one wins, exactly as the bring-up repo's reassemble.py does. Verified on the dev phone against the hash QTEE has actually accepted since August: 3600472 bytes, sha256 1930c490..., reassembled from the phone's own modem_a. The retry path was verified too, with a deliberately wrong first partition -- which is how the variable clobber got caught: POSIX sh has no locals, and reassemble() taking rdir= rewrote its caller's copy to the mount path, so the second partition would have been searched at $MNT/$MNT/... The fast path needed teaching as well: mbn's dest is the fifth field, and a first boot would otherwise have exited early and extracted nothing. Not pushed. The consumer fragment lives in the fingerprintd package.
2026-09-05 03:56:13 +02:00
# mbn: the same, for a Qualcomm trustlet, which is not shipped as one file.
# QTEE images live in the modem partition's image/ as an ELF header+hashes
# file (<name>.mdt) plus one payload per program header (<name>.b00, .b01,
# ...), and the loader wants them written back at each segment's p_offset.
# Reassembly is therefore not a concatenation: segments are page aligned
# but not contiguous, gaps stay zero, and two segments may share an offset
# (focal64 has two such pairs), so they are written in index order and the
# later one wins. Same guarantees as file: the sha256 is of the reassembled
# image, a mismatch tries the next partition, and an unverified image is
# never installed.
aports: extract aw88261_acf.bin on-device instead of shipping it New package fp6-vendor-blobs: a manifest-driven systemd oneshot that copies proprietary blobs out of the stock Android partitions on first boot. pmOS installs flash only boot+userdata, so every installed unit still carries the stock vendor/dsp partitions - the device duplicates a file it already contains, for its own operation, and nothing proprietary is distributed by us. Partitions are mounted read-only (ext4 additionally with noload - not even a journal replay touches the stock data), every copy is sha256-pinned and a miss fails loudly, and the consuming devices are unbound and re-probed afterwards so the feature works the same boot. The rebind is unconditional by design: aw88261 binds on a bare i2c chip-id probe and requests the ACF only at ASoC card init (~21 s, after SoundWire enumeration), so a bound device can still be one that lost the firmware race - and that failed request is never retried. soc-fairphone-fp6-audio (pkgver 4) stops shipping the blob, installs the manifest fragment instead, and re-runs the extractor from post-install/post-upgrade so upgrades restore the file immediately (the old package version removed it on upgrade). License drops back to plain BSD-3-Clause - nothing proprietary left in the package. Verified on the dev phone (fp6 repo journal/blobs/captures/ 2026-08-24-first-boot-extractor-verification.txt): first-boot extraction + same-boot audio, mid-session post-upgrade recovery, and the everyday fast-path no-op; committed files byte-identical to the tested deployment. Assisted-by: Claude:claude-fable-5
2026-08-24 21:00:34 +02:00
# rebind: if this fragment's run extracted at least one file, unbind and
# re-probe <device> on <bus> so the consuming driver picks the file up
# in the same boot. Unconditional on purpose: a still-bound consumer may
# have already failed a deferred firmware request that is never retried
# (aw88261 binds on i2c probe but requests the ACF only at card init),
# so only a fresh probe with the file present is a known-good state.
# All of a fragment's devices are unbound first, then re-probed, so a
# shared sound card re-forms once instead of bouncing per device.
#
# Partitions resolve via /dev/mapper (the pmOS initramfs maps the dynamic
# partitions in super on every boot), then /dev/disk/by-partlabel (raw
# partitions like dsp_a), then one make-dynpart-mappings fallback run;
# mappings that run creates are removed again at the end.
#
# --if-device: exit 0 quietly when no stock super partition is visible
# (apk post-install scripts run inside build/CI chroots too; on images
# built there the first-boot service does the real extraction).
MANIFEST_DIR=/usr/share/fp6-vendor-blobs/manifest.d
SUPER=/dev/disk/by-partlabel/super
MNT=
MNT_PART=
CREATED=
TRIED_MAPPING=
log() { echo "fp6-vendor-blobs: $*"; }
unmount_cur() {
if [ -n "$MNT" ]; then
umount "$MNT" 2>/dev/null
rmdir "$MNT" 2>/dev/null
MNT= MNT_PART=
fi
}
cleanup() {
unmount_cur
for name in $CREATED; do
dmsetup remove "$name" 2>/dev/null || true
done
CREATED=
}
fail() {
echo "fp6-vendor-blobs: ERROR: $*" >&2
cleanup
exit 1
}
# resolve a partition name to a block device
part_dev() {
[ -b "/dev/mapper/$1" ] && { echo "/dev/mapper/$1"; return 0; }
[ -b "/dev/disk/by-partlabel/$1" ] && { echo "/dev/disk/by-partlabel/$1"; return 0; }
if [ -z "$TRIED_MAPPING" ] && [ -b "$SUPER" ]; then
TRIED_MAPPING=1
before=$(dmsetup ls 2>/dev/null | awk '{print $1}')
make-dynpart-mappings "$SUPER" 0 2>/dev/null
after=$(dmsetup ls 2>/dev/null | awk '{print $1}')
for name in $after; do
case " $before " in *" $name "*) ;; *) CREATED="$CREATED $name" ;; esac
done
[ -n "$CREATED" ] && log "mapped dynamic partitions:$CREATED"
[ -b "/dev/mapper/$1" ] && { echo "/dev/mapper/$1"; return 0; }
fi
return 1
}
mount_part() {
[ "$MNT_PART" = "$1" ] && return 0
unmount_cur
dev=$(part_dev "$1") || return 1
dir=$(mktemp -d /run/fp6-vendor-blobs.XXXXXX) || fail "mktemp failed"
# noload succeeds on any ext4 and correctly fails on non-ext4, where
# plain ro cannot write anyway (erofs); a dirty ext4 is never replayed
if ! mount -o ro,noload "$dev" "$dir" 2>/dev/null && \
! mount -o ro "$dev" "$dir" 2>/dev/null; then
rmdir "$dir" 2>/dev/null
return 1
fi
MNT=$dir
MNT_PART=$1
}
fp6-vendor-blobs: reassemble a Qualcomm trustlet, not just copy a file The fingerprint matcher is a proprietary OEM-signed trustlet, and it is not shipped as one file: QTEE images live in the modem partition's image/ as an ELF header+hashes file plus one payload per program header, and the loader wants each payload written back at its segment's p_offset. So the existing file directive cannot reach it. An mbn directive does, with the same guarantees file has: the sha256 is of the reassembled image, a mismatch tries the next partition, and an unverified image is never installed. Reassembly is not a concatenation -- segments are page aligned but not contiguous, gaps stay zero, and two pairs of focal64's nine segments share an offset, so they are written in index order and the later one wins, exactly as the bring-up repo's reassemble.py does. Verified on the dev phone against the hash QTEE has actually accepted since August: 3600472 bytes, sha256 1930c490..., reassembled from the phone's own modem_a. The retry path was verified too, with a deliberately wrong first partition -- which is how the variable clobber got caught: POSIX sh has no locals, and reassemble() taking rdir= rewrote its caller's copy to the mount path, so the second partition would have been searched at $MNT/$MNT/... The fast path needed teaching as well: mbn's dest is the fifth field, and a first boot would otherwise have exited early and extracted nothing. Not pushed. The consumer fragment lives in the fingerprintd package.
2026-09-05 03:56:13 +02:00
# Little-endian scalars out of an ELF header. aarch64 is little endian and so
# is the image, so od's host order is the right one.
u64() { od -An -tu8 -j "$2" -N 8 "$1" | tr -d ' '; }
u16() { od -An -tu2 -j "$2" -N 2 "$1" | tr -d ' '; }
# Reassemble <dir>/<name>.mdt + .b0N into a flat image at <out>. Mirrors
# utilities/ta-analysis/reassemble.py in the fp6 bring-up repo, which is where
# the format was worked out and where the known-good hash comes from.
# POSIX sh has no locals, so these names are deliberately distinct from
# extract_mbn's: reassemble() taking rdir= would rewrite its CALLER's copy to
# the mount path, and the next partition in the retry loop would then be
# searched at $MNT/$MNT/...
reassemble() { # <dir> <name> <out>
mdir=$1 mname=$2 mout=$3
mdt="$mdir/$mname.mdt"
[ -f "$mdt" ] || return 1
phoff=$(u64 "$mdt" 32) phentsize=$(u16 "$mdt" 54) phnum=$(u16 "$mdt" 56)
[ -n "$phoff" ] && [ -n "$phentsize" ] && [ -n "$phnum" ] || return 1
[ "$phnum" -gt 0 ] 2>/dev/null || return 1
# The image is as long as the furthest segment reaches; everything no
# segment covers stays zero.
total=0 i=0
while [ "$i" -lt "$phnum" ]; do
o=$((phoff + i * phentsize))
pfsz=$(u64 "$mdt" $((o + 32)))
if [ "$pfsz" -gt 0 ]; then
poff=$(u64 "$mdt" $((o + 8)))
[ $((poff + pfsz)) -gt "$total" ] && total=$((poff + pfsz))
fi
i=$((i + 1))
done
[ "$total" -gt 0 ] || return 1
: > "$mout" || return 1
truncate -s "$total" "$mout" || return 1
i=0
while [ "$i" -lt "$phnum" ]; do
o=$((phoff + i * phentsize))
pfsz=$(u64 "$mdt" $((o + 32)))
if [ "$pfsz" -gt 0 ]; then
poff=$(u64 "$mdt" $((o + 8)))
seg=$(printf '%s/%s.b%02d' "$mdir" "$mname" "$i")
[ -f "$seg" ] || { log "$mname: segment $i missing"; return 1; }
# dd seeks in whole blocks, which is only correct because
# every p_offset in these images is page aligned. Refuse
# rather than silently misplace a segment if that changes.
[ $((poff % 4096)) -eq 0 ] || {
log "$mname: segment $i offset $poff is not page aligned"
return 1
}
dd if="$seg" of="$mout" bs=4096 seek=$((poff / 4096)) \
conv=notrunc 2>/dev/null || return 1
fi
i=$((i + 1))
done
return 0
}
extract_mbn() { # <partition,...> <dir-in-partition> <name> <dest> <sha256>
parts=$1 rdir=$2 rname=$3 dest=$4 want=$5
for part in $(echo "$parts" | tr ',' ' '); do
mount_part "$part" || { log "$part: not mountable, trying next"; continue; }
[ -f "$MNT/$rdir/$rname.mdt" ] || { log "$part: no $rdir/$rname.mdt, trying next"; continue; }
tmp="$dest.fp6-extract.$$"
mkdir -p "${dest%/*}" || fail "cannot create ${dest%/*}"
if ! reassemble "$MNT/$rdir" "$rname" "$tmp"; then
rm -f "$tmp"
log "$part: reassembling $rname failed, trying next"
continue
fi
got=$(sha256sum "$tmp" | awk '{print $1}')
if [ "$got" != "$want" ]; then
rm -f "$tmp"
log "$part:$rdir/$rname sha256 $got != expected, trying next"
continue
fi
chmod 644 "$tmp" && mv "$tmp" "$dest" || { rm -f "$tmp"; fail "installing $dest failed"; }
log "reassembled $part:$rdir/$rname.{mdt,b0N} -> $dest"
return 0
done
fail "no listed partition ($parts) yields $rname with sha256 $want - $dest NOT installed"
}
aports: extract aw88261_acf.bin on-device instead of shipping it New package fp6-vendor-blobs: a manifest-driven systemd oneshot that copies proprietary blobs out of the stock Android partitions on first boot. pmOS installs flash only boot+userdata, so every installed unit still carries the stock vendor/dsp partitions - the device duplicates a file it already contains, for its own operation, and nothing proprietary is distributed by us. Partitions are mounted read-only (ext4 additionally with noload - not even a journal replay touches the stock data), every copy is sha256-pinned and a miss fails loudly, and the consuming devices are unbound and re-probed afterwards so the feature works the same boot. The rebind is unconditional by design: aw88261 binds on a bare i2c chip-id probe and requests the ACF only at ASoC card init (~21 s, after SoundWire enumeration), so a bound device can still be one that lost the firmware race - and that failed request is never retried. soc-fairphone-fp6-audio (pkgver 4) stops shipping the blob, installs the manifest fragment instead, and re-runs the extractor from post-install/post-upgrade so upgrades restore the file immediately (the old package version removed it on upgrade). License drops back to plain BSD-3-Clause - nothing proprietary left in the package. Verified on the dev phone (fp6 repo journal/blobs/captures/ 2026-08-24-first-boot-extractor-verification.txt): first-boot extraction + same-boot audio, mid-session post-upgrade recovery, and the everyday fast-path no-op; committed files byte-identical to the tested deployment. Assisted-by: Claude:claude-fable-5
2026-08-24 21:00:34 +02:00
extract() { # <partition,...> <path-in-partition> <dest> <sha256>
parts=$1 src=$2 dest=$3 want=$4
for part in $(echo "$parts" | tr ',' ' '); do
mount_part "$part" || { log "$part: not mountable, trying next"; continue; }
[ -f "$MNT/$src" ] || { log "$part: no $src, trying next"; continue; }
tmp="$dest.fp6-extract.$$"
mkdir -p "${dest%/*}" || fail "cannot create ${dest%/*}"
cp "$MNT/$src" "$tmp" || { rm -f "$tmp"; fail "copying $part:$src failed"; }
got=$(sha256sum "$tmp" | awk '{print $1}')
if [ "$got" != "$want" ]; then
rm -f "$tmp"
log "$part:$src sha256 $got != expected, trying next"
continue
fi
chmod 644 "$tmp" && mv "$tmp" "$dest" || { rm -f "$tmp"; fail "installing $dest failed"; }
log "extracted $part:$src -> $dest"
return 0
done
fail "no listed partition ($parts) yields $src with sha256 $want - $dest NOT installed"
}
rebind_all() { # <bus/device ...>
for rb in $1; do
bus=${rb%%/*} dev=${rb#*/}
[ -e "/sys/bus/$bus/devices/$dev/driver" ] || continue
if echo "$dev" > "/sys/bus/$bus/devices/$dev/driver/unbind" 2>/dev/null; then
log "unbound $bus $dev"
fi
done
for rb in $1; do
bus=${rb%%/*} dev=${rb#*/}
if [ ! -e "/sys/bus/$bus/devices/$dev" ]; then
log "rebind: no $dev on bus $bus (yet) - its driver will probe on its own"
elif echo "$dev" > "/sys/bus/$bus/drivers_probe" 2>/dev/null; then
log "re-probed $bus $dev"
else
log "rebind: drivers_probe of $dev failed (driver not loaded yet?)"
fi
done
}
if [ "${1:-}" = --if-device ] && [ ! -b "$SUPER" ]; then
log "no stock super partition visible (build chroot?), nothing to do"
exit 0
fi
[ -d "$MANIFEST_DIR" ] || exit 0
# fast path for every boot after the first: all dests already present
missing=
for f in "$MANIFEST_DIR"/*.manifest; do
[ -e "$f" ] || continue
fp6-vendor-blobs: reassemble a Qualcomm trustlet, not just copy a file The fingerprint matcher is a proprietary OEM-signed trustlet, and it is not shipped as one file: QTEE images live in the modem partition's image/ as an ELF header+hashes file plus one payload per program header, and the loader wants each payload written back at its segment's p_offset. So the existing file directive cannot reach it. An mbn directive does, with the same guarantees file has: the sha256 is of the reassembled image, a mismatch tries the next partition, and an unverified image is never installed. Reassembly is not a concatenation -- segments are page aligned but not contiguous, gaps stay zero, and two pairs of focal64's nine segments share an offset, so they are written in index order and the later one wins, exactly as the bring-up repo's reassemble.py does. Verified on the dev phone against the hash QTEE has actually accepted since August: 3600472 bytes, sha256 1930c490..., reassembled from the phone's own modem_a. The retry path was verified too, with a deliberately wrong first partition -- which is how the variable clobber got caught: POSIX sh has no locals, and reassemble() taking rdir= rewrote its caller's copy to the mount path, so the second partition would have been searched at $MNT/$MNT/... The fast path needed teaching as well: mbn's dest is the fifth field, and a first boot would otherwise have exited early and extracted nothing. Not pushed. The consumer fragment lives in the fingerprintd package.
2026-09-05 03:56:13 +02:00
while read -r kind a b c d e; do
case "$kind" in
file) [ -e "$c" ] || missing=1 ;;
mbn) [ -e "$d" ] || missing=1 ;;
esac
aports: extract aw88261_acf.bin on-device instead of shipping it New package fp6-vendor-blobs: a manifest-driven systemd oneshot that copies proprietary blobs out of the stock Android partitions on first boot. pmOS installs flash only boot+userdata, so every installed unit still carries the stock vendor/dsp partitions - the device duplicates a file it already contains, for its own operation, and nothing proprietary is distributed by us. Partitions are mounted read-only (ext4 additionally with noload - not even a journal replay touches the stock data), every copy is sha256-pinned and a miss fails loudly, and the consuming devices are unbound and re-probed afterwards so the feature works the same boot. The rebind is unconditional by design: aw88261 binds on a bare i2c chip-id probe and requests the ACF only at ASoC card init (~21 s, after SoundWire enumeration), so a bound device can still be one that lost the firmware race - and that failed request is never retried. soc-fairphone-fp6-audio (pkgver 4) stops shipping the blob, installs the manifest fragment instead, and re-runs the extractor from post-install/post-upgrade so upgrades restore the file immediately (the old package version removed it on upgrade). License drops back to plain BSD-3-Clause - nothing proprietary left in the package. Verified on the dev phone (fp6 repo journal/blobs/captures/ 2026-08-24-first-boot-extractor-verification.txt): first-boot extraction + same-boot audio, mid-session post-upgrade recovery, and the everyday fast-path no-op; committed files byte-identical to the tested deployment. Assisted-by: Claude:claude-fable-5
2026-08-24 21:00:34 +02:00
done < "$f"
done
[ -z "$missing" ] && exit 0
for f in "$MANIFEST_DIR"/*.manifest; do
[ -e "$f" ] || continue
extracted=
rebinds=
fp6-vendor-blobs: reassemble a Qualcomm trustlet, not just copy a file The fingerprint matcher is a proprietary OEM-signed trustlet, and it is not shipped as one file: QTEE images live in the modem partition's image/ as an ELF header+hashes file plus one payload per program header, and the loader wants each payload written back at its segment's p_offset. So the existing file directive cannot reach it. An mbn directive does, with the same guarantees file has: the sha256 is of the reassembled image, a mismatch tries the next partition, and an unverified image is never installed. Reassembly is not a concatenation -- segments are page aligned but not contiguous, gaps stay zero, and two pairs of focal64's nine segments share an offset, so they are written in index order and the later one wins, exactly as the bring-up repo's reassemble.py does. Verified on the dev phone against the hash QTEE has actually accepted since August: 3600472 bytes, sha256 1930c490..., reassembled from the phone's own modem_a. The retry path was verified too, with a deliberately wrong first partition -- which is how the variable clobber got caught: POSIX sh has no locals, and reassemble() taking rdir= rewrote its caller's copy to the mount path, so the second partition would have been searched at $MNT/$MNT/... The fast path needed teaching as well: mbn's dest is the fifth field, and a first boot would otherwise have exited early and extracted nothing. Not pushed. The consumer fragment lives in the fingerprintd package.
2026-09-05 03:56:13 +02:00
while read -r kind a b c d e; do
aports: extract aw88261_acf.bin on-device instead of shipping it New package fp6-vendor-blobs: a manifest-driven systemd oneshot that copies proprietary blobs out of the stock Android partitions on first boot. pmOS installs flash only boot+userdata, so every installed unit still carries the stock vendor/dsp partitions - the device duplicates a file it already contains, for its own operation, and nothing proprietary is distributed by us. Partitions are mounted read-only (ext4 additionally with noload - not even a journal replay touches the stock data), every copy is sha256-pinned and a miss fails loudly, and the consuming devices are unbound and re-probed afterwards so the feature works the same boot. The rebind is unconditional by design: aw88261 binds on a bare i2c chip-id probe and requests the ACF only at ASoC card init (~21 s, after SoundWire enumeration), so a bound device can still be one that lost the firmware race - and that failed request is never retried. soc-fairphone-fp6-audio (pkgver 4) stops shipping the blob, installs the manifest fragment instead, and re-runs the extractor from post-install/post-upgrade so upgrades restore the file immediately (the old package version removed it on upgrade). License drops back to plain BSD-3-Clause - nothing proprietary left in the package. Verified on the dev phone (fp6 repo journal/blobs/captures/ 2026-08-24-first-boot-extractor-verification.txt): first-boot extraction + same-boot audio, mid-session post-upgrade recovery, and the everyday fast-path no-op; committed files byte-identical to the tested deployment. Assisted-by: Claude:claude-fable-5
2026-08-24 21:00:34 +02:00
case "$kind" in
''|'#'*) ;;
file)
[ -n "$d" ] || fail "$f: malformed file line"
[ -e "$c" ] && continue
extract "$a" "$b" "$c" "$d" </dev/null
extracted=1
;;
fp6-vendor-blobs: reassemble a Qualcomm trustlet, not just copy a file The fingerprint matcher is a proprietary OEM-signed trustlet, and it is not shipped as one file: QTEE images live in the modem partition's image/ as an ELF header+hashes file plus one payload per program header, and the loader wants each payload written back at its segment's p_offset. So the existing file directive cannot reach it. An mbn directive does, with the same guarantees file has: the sha256 is of the reassembled image, a mismatch tries the next partition, and an unverified image is never installed. Reassembly is not a concatenation -- segments are page aligned but not contiguous, gaps stay zero, and two pairs of focal64's nine segments share an offset, so they are written in index order and the later one wins, exactly as the bring-up repo's reassemble.py does. Verified on the dev phone against the hash QTEE has actually accepted since August: 3600472 bytes, sha256 1930c490..., reassembled from the phone's own modem_a. The retry path was verified too, with a deliberately wrong first partition -- which is how the variable clobber got caught: POSIX sh has no locals, and reassemble() taking rdir= rewrote its caller's copy to the mount path, so the second partition would have been searched at $MNT/$MNT/... The fast path needed teaching as well: mbn's dest is the fifth field, and a first boot would otherwise have exited early and extracted nothing. Not pushed. The consumer fragment lives in the fingerprintd package.
2026-09-05 03:56:13 +02:00
mbn)
[ -n "$e" ] || fail "$f: malformed mbn line"
[ -e "$d" ] && continue
extract_mbn "$a" "$b" "$c" "$d" "$e" </dev/null
extracted=1
;;
aports: extract aw88261_acf.bin on-device instead of shipping it New package fp6-vendor-blobs: a manifest-driven systemd oneshot that copies proprietary blobs out of the stock Android partitions on first boot. pmOS installs flash only boot+userdata, so every installed unit still carries the stock vendor/dsp partitions - the device duplicates a file it already contains, for its own operation, and nothing proprietary is distributed by us. Partitions are mounted read-only (ext4 additionally with noload - not even a journal replay touches the stock data), every copy is sha256-pinned and a miss fails loudly, and the consuming devices are unbound and re-probed afterwards so the feature works the same boot. The rebind is unconditional by design: aw88261 binds on a bare i2c chip-id probe and requests the ACF only at ASoC card init (~21 s, after SoundWire enumeration), so a bound device can still be one that lost the firmware race - and that failed request is never retried. soc-fairphone-fp6-audio (pkgver 4) stops shipping the blob, installs the manifest fragment instead, and re-runs the extractor from post-install/post-upgrade so upgrades restore the file immediately (the old package version removed it on upgrade). License drops back to plain BSD-3-Clause - nothing proprietary left in the package. Verified on the dev phone (fp6 repo journal/blobs/captures/ 2026-08-24-first-boot-extractor-verification.txt): first-boot extraction + same-boot audio, mid-session post-upgrade recovery, and the everyday fast-path no-op; committed files byte-identical to the tested deployment. Assisted-by: Claude:claude-fable-5
2026-08-24 21:00:34 +02:00
rebind)
[ -n "$b" ] || fail "$f: malformed rebind line"
rebinds="$rebinds $a/$b"
;;
*) fail "$f: unknown directive '$kind'" ;;
esac
done < "$f"
if [ -n "$extracted" ] && [ -n "$rebinds" ]; then
rebind_all "$rebinds" </dev/null
fi
done
cleanup
exit 0