blueline: consolidate wifi bring-up, adopt cellular/MMS path, task audit
wifi: bringup unit now carries the union ordering (slpi/adsp for TZ MSA, rmtfs+tqftpserv for firmware serving — the 2026-07-20 boot proved the old load unit's ordering incomplete) and supersedes blueline-wifi-load; msa-release adopted into the repo (shutdown unload that keeps warm reboots clean). cellular: adopt blueline-clat service+script. clat script now pins a host route to the Fido MMS proxy via the CLAT — with wifi up the wlan default beat the clat default and MMS died off-carrier. mmsd config contract documented (MMS_APN must match the bearer APN ltemobile.apn; shipped as netsvcs which matches nothing). tasks: retire 06 qtpim (no longer the contacts path), archive 10 (done), index catches up on 11 (archived); 01 re-pointed at a contacts-store decision.
This commit is contained in:
parent
011d1ccbdf
commit
7ec2479f83
12 changed files with 533 additions and 9 deletions
35
blueline/cellular/README.md
Normal file
35
blueline/cellular/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# blueline cellular data + MMS path
|
||||
|
||||
Fido's LTE bearer (`ltemobile.apn`, modem netdev `qmapmux0.0`) is
|
||||
IPv6-only with NAT64. IPv4 exists on the phone only through 464XLAT:
|
||||
`blueline-clat.service` runs `blueline-clat.sh`, which derives a CLAT
|
||||
IPv6 address from the bearer's current /64 and runs `clatd` (PLAT prefix
|
||||
via RFC 7050 DNS64 discovery against the carrier's resolvers). The CLAT
|
||||
installs a v4 default at metric 2048, so WiFi (metric 600) stays
|
||||
preferred for ordinary traffic.
|
||||
|
||||
## The MMS routing trap
|
||||
|
||||
MMS is carrier-internal: the MMSC proxy (`205.151.11.13:80`) answers only
|
||||
from inside Fido's network, i.e. only via the CLAT. With WiFi up, the
|
||||
plain routing table sends it out `wlan0` and MMS silently dies. The clat
|
||||
script pins `205.151.11.13/32 dev clat` after clatd brings the device up.
|
||||
If Fido ever changes the proxy (it is set in
|
||||
`~/.mms/modemmanager/mms` `CarrierMMSProxy`), update both places.
|
||||
|
||||
## mmsd-tng config contract (`~/.mms/modemmanager/mms`)
|
||||
|
||||
- `MMS_APN` must equal the APN of the *connected* bearer —
|
||||
`ltemobile.apn`. (It shipped as `netsvcs`, which matches nothing;
|
||||
mmsd-tng then never considers the bearer usable. Fixed 2026-07-20.)
|
||||
- `CarrierMMSC=http://mms.fido.ca`, `CarrierMMSProxy=205.151.11.13:80`.
|
||||
- mmsdtng runs in the user session; chatty is the D-Bus consumer
|
||||
(`org.ofono.mms`).
|
||||
|
||||
## Known-good state (2026-07-20)
|
||||
|
||||
`mmcli -m any`: Fido LTE attached, bearer `ipv4v6` requested, v6-only
|
||||
granted. `curl` to the proxy over the pinned clat route: TCP connect OK
|
||||
from `192.0.0.1`. IPv6 has no WiFi default (LAN is v4-only), so all v6
|
||||
traffic rides the bearer even on WiFi — metered-data caveat, accepted
|
||||
for now.
|
||||
11
blueline/cellular/blueline-clat.service
Normal file
11
blueline/cellular/blueline-clat.service
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=464XLAT CLAT daemon for the cellular bearer
|
||||
After=NetworkManager.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/blueline-clat.sh
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
58
blueline/cellular/blueline-clat.sh
Normal file
58
blueline/cellular/blueline-clat.sh
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/sh
|
||||
# 464XLAT launcher: derive the CLAT IPv6 address from the cellular bearer's
|
||||
# current /64 (changes per attach, so it cannot live in a static config),
|
||||
# then run clatd against it. PLAT prefix is discovered via DNS64 (RFC 7050).
|
||||
DEV="${CLAT_WAN_DEV:-qmapmux0.0}"
|
||||
|
||||
ADDR=""
|
||||
i=0
|
||||
while [ $i -lt 30 ]; do
|
||||
ADDR="$(ip -6 addr show dev "$DEV" scope global 2>/dev/null \
|
||||
| awk '/inet6/ { print $2; exit }' | cut -d/ -f1)"
|
||||
[ -n "$ADDR" ] && break
|
||||
i=$((i + 1))
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ -z "$ADDR" ]; then
|
||||
echo "no global IPv6 on $DEV after 60s, giving up" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CLAT_V6="$(python3 -c "
|
||||
import ipaddress, sys
|
||||
net = ipaddress.IPv6Interface('$ADDR/64').network
|
||||
print(net[0xc1a7])
|
||||
")" || exit 1
|
||||
|
||||
# RFC 7050 PLAT discovery must query the CARRIER's DNS64 servers; the system
|
||||
# resolver may prefer WiFi DNS (no DNS64) and discovery would fail.
|
||||
DNS64="$(nmcli -g IP6.DNS device show "$DEV" 2>/dev/null \
|
||||
| sed -e 's/ | /,/g' -e 's/\\//g')"
|
||||
|
||||
echo "bearer $DEV prefix holds $ADDR; CLAT address $CLAT_V6; DNS64 $DNS64"
|
||||
|
||||
# MMS must ride the CLAT: the Fido MMS proxy (205.151.11.13, from
|
||||
# ~/.mms/modemmanager/mms CarrierMMSProxy) is only reachable from inside the
|
||||
# carrier network, and while WiFi is up its default route (metric 600) beats
|
||||
# the CLAT default (2048) — so without this pin, MMS send/receive dies the
|
||||
# moment WiFi connects. Wait for clatd to create the device, then install a
|
||||
# host route (replace = idempotent; re-runs on every service restart).
|
||||
(
|
||||
i=0
|
||||
while [ $i -lt 30 ]; do
|
||||
if ip link show clat >/dev/null 2>&1; then
|
||||
ip route replace 205.151.11.13/32 dev clat
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "clat device never appeared; MMS proxy route not installed" >&2
|
||||
) &
|
||||
|
||||
# v4-conncheck off: WiFi may hold a v4 default route now but drop later;
|
||||
# the CLAT must exist whenever the bearer does. Route metrics keep WiFi
|
||||
# preferred (clat route is metric 2048).
|
||||
exec /usr/bin/clatd clat-v6-addr="$CLAT_V6" plat-dev="$DEV" \
|
||||
v4-conncheck-enable=0 ${DNS64:+dns64-servers="$DNS64"}
|
||||
|
|
@ -3,8 +3,17 @@ Description=WCN3990 wifi bring-up after DSP firmware services
|
|||
# See blueline-wifi-defer.conf: ath10k_snoc is blacklisted from udev
|
||||
# autoload because probing before rmtfs + tqftpserv serve the DSP firmware
|
||||
# crashes the wifi firmware and wedges the driver for the whole boot.
|
||||
After=rmtfs.service tqftpserv.service
|
||||
# Ordering is the union of every dependency proven to matter:
|
||||
# - rmtfs + tqftpserv: board data / wlanmdsp.mbn (2026-07-20 boot crashed
|
||||
# with only slpi/adsp/rmtfs ordering — tqftpserv was the missing one)
|
||||
# - slpi + adsp: TrustZone MSA arbitration during island boot (the retired
|
||||
# blueline-wifi-load.service's finding, kept here)
|
||||
# This unit supersedes blueline-wifi-load.service (same job, no retry,
|
||||
# incomplete ordering — disabled 2026-07-20). blueline-wifi-msa-release
|
||||
# stays: it owns the shutdown unload that keeps warm reboots clean.
|
||||
After=blueline-slpi.service blueline-adsp.service rmtfs.service tqftpserv.service
|
||||
Wants=rmtfs.service tqftpserv.service
|
||||
Before=NetworkManager.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
|
|
|
|||
19
blueline/wifi-bringup/blueline-wifi-msa-release.service
Normal file
19
blueline/wifi-bringup/blueline-wifi-msa-release.service
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=Release WCN3990 WLAN firmware state before shutdown
|
||||
# Warm reboot with ath10k_snoc loaded leaves the WLAN MSA assigned in TZ;
|
||||
# the next boot's wlfw handshake then fails forever ("host capability
|
||||
# request rejected: 90", later "failed to assign msa map permissions: -22")
|
||||
# and only a cold power-off clears it. ath10k's remove path reassigns the
|
||||
# MSA back to HLOS, so unloading the module at shutdown/reboot prevents it.
|
||||
# (Adopted into the repo 2026-07-20 — this unit predates wifi-bringup and
|
||||
# was only ever installed on the phone.)
|
||||
After=NetworkManager.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/bin/true
|
||||
ExecStop=/usr/bin/modprobe -r ath10k_snoc
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -27,8 +27,18 @@ wake that races the lock can land input on an unlocked surface.
|
|||
else calls `hyprctl dispatch dpms` — when hypridle still had a raw dpms
|
||||
call after the toggle became authority, double-tap wake raced `on-resume`
|
||||
and the touch controller calibrated against a half-ramped panel
|
||||
(Pixel3Arch `73d0cd0`). Any new wake source (power button, dt2w, proximity
|
||||
someday) goes through the toggle, or it will reintroduce the race.
|
||||
(Pixel3Arch `73d0cd0`). Any new wake source (power button, dt2w, proximity)
|
||||
goes through the toggle, or it will reintroduce the race.
|
||||
|
||||
Proximity landed 2026-07-20 (Pixel3Arch): `blueline-screen-toggle` itself
|
||||
gates DT2W wake on `ProximityNear`, and a new `blueline-proximity-lock`
|
||||
user service (same `monitor-sensor` watch pattern as `blueline-autorotate`)
|
||||
blanks an already-on locked screen the moment proximity goes near, via
|
||||
`blueline-screen-toggle off` — no second DPMS authority introduced. Gated
|
||||
on `session state`'s `.locked` from the souveraine IPC; unlocked screens
|
||||
are untouched. `iio-sensor-proxy` is now always-on rather than start/stop
|
||||
on demand (the old wake-lockup hazard tied to it is confirmed gone — see
|
||||
Pixel3Arch `PAF/slpi.md`).
|
||||
|
||||
## 3. The FTS touch controller has opinions
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# TASK 01 — Favorites: wife's name on her number
|
||||
|
||||
**Status:** blocked on TASK-06 (qtpim build → contacts store).
|
||||
**Status:** blocked on a contacts-store decision. qtpim retired
|
||||
2026-07-20 (see archive/06) — the store needs a non-Qt-PIM design
|
||||
(small owned store the Person layer reads directly) before this lands.
|
||||
**Size:** small, once the contacts store exists.
|
||||
|
||||
## Goal
|
||||
|
|
|
|||
202
docs/tasks/12-gaze-reference-face-auth.md
Normal file
202
docs/tasks/12-gaze-reference-face-auth.md
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# TASK 12 — Face auth as a capability factor (Gaze is the reference, not the dep)
|
||||
|
||||
**Status:** open. Reference cloned to `~/Projects/gaze` (GunduLabs/gaze @
|
||||
`c9c8ffb`, MIT, v0.2.6, 2026-07-18). This doc scopes the SouveraineOS-owned
|
||||
face-auth factor modeled on it. We own the source — anything touching the
|
||||
permission/capability gating is in-tree, not vendored.
|
||||
|
||||
## Goal
|
||||
|
||||
A first-class **face-auth factor** that plugs into the existing capability
|
||||
tier model as a step-up / unlock credential, symmetric with fingerprint and
|
||||
PIN. Gaze proves the pipeline is real and fully on-device; we build the
|
||||
SouveraineOS-native equivalent against the primitives we already have
|
||||
(`PamContext`, `StepUpAuth`, `LockContext`, `LockContentPolicy`), not a
|
||||
wrapper around `gazed`.
|
||||
|
||||
Two placements, one mechanism:
|
||||
|
||||
1. **Unlock.** `session.unlock()` is already gated on `LockContext PIN/PAM`
|
||||
(TRUST-BOUNDARY-MATRIX row). Face becomes a PAM factor behind that same
|
||||
gate — a face mint unlocks, exactly as a fingerprint mint would.
|
||||
2. **Step-up / in-place reveal.** The doctrine's open slot: `PamContext`
|
||||
against `souveraine-stepup` "accepting fingerprint mints a short-lived
|
||||
grant," and `LockContentPolicy.allowsOnLock()` gains its third condition
|
||||
(`|| hasFreshGrant('read')`). Face is that same mint for a second factor
|
||||
type — reveal message/notification bodies on the lock surface without
|
||||
unlocking the session. (SESSION-AUTHORITY-DOCTRINE §2, currently "gated
|
||||
on the fingerprint sensor coming online (not yet true on blueline)." Face
|
||||
is unblocked by any working front camera; it does not wait on the FP
|
||||
driver.)
|
||||
|
||||
Face auth is **not** a new trust mechanism. It is one more factor behind the
|
||||
two existing gates. The doctrinal guarantees are unchanged: step-up never
|
||||
unlocks the session, never accepts an agent-supplied boolean, the grant is
|
||||
short-lived / in-memory / action-family-bound.
|
||||
|
||||
## Context — what Gaze does (the reference)
|
||||
|
||||
Cloned at `~/Projects/gaze`. Architecture worth copying, decisions worth
|
||||
re-making:
|
||||
|
||||
- **Pipeline** (all on-device, no network): `Camera → SCRFD detect → align
|
||||
→ ArcFace embed → match → MiniFASNet-V2 liveness`. Optional IR camera for
|
||||
high-security spoof resistance.
|
||||
- **Process split (the part to copy structurally):**
|
||||
- `gazed` — system daemon, owns the camera and embeddings, exposes
|
||||
`com.gundulabs.Gaze` on D-Bus. Stateful: `claim/release`,
|
||||
`verify_start/stop`, `enroll_start/stop`, signals `face_status` /
|
||||
`verify_status` carrying `CaptureStatus` (TooDark / etc.) and
|
||||
`VerifyResult` (Match / NoMatch).
|
||||
- `pam-gaze` — thin C-ABI PAM shim (`pam_sm_authenticate`).
|
||||
- `pam-gaze-core` — talks to the daemon over zbus, returns
|
||||
`Match / NoMatch / Unavailable`. The shim is dumb on purpose.
|
||||
- **Tiered strictness** — 5 levels (`low/medium/high/maximum/custom`) swap
|
||||
detector models (`det_500m.onnx` vs `det_10g.onnx`) and recognizer
|
||||
(`w600k_mbf` vs `w600k_r50`) + liveness threshold. The model-swap-by-tier
|
||||
idea maps cleanly onto our `ambient/personal/stepUp` tiers in reverse:
|
||||
*higher trust decision → stricter pipeline*, not a single global knob.
|
||||
- **Presence gates** — `abort_if_ssh` and `abort_if_lid_closed` refuse to
|
||||
fire when no user is physically present. This is the right instinct and
|
||||
aligns with our fail-closed lock policy: no camera / too dark / SSH
|
||||
session → `Unavailable` → don't unlock, stay secure.
|
||||
- **Storage (reference, not to copy verbatim):** `/var/lib/gaze/users`,
|
||||
config `/etc/gaze/config.toml`. We will not inherit this layout — see
|
||||
"What to build."
|
||||
|
||||
Files of record in the reference:
|
||||
`gaze-core/src/{dbus.rs,config.rs,face.rs,detect.rs,camera.rs}`,
|
||||
`pam-gaze/src/lib.rs:113` (`pam_sm_authenticate`),
|
||||
`pam-gaze-core/src/lib.rs` (D-Bus auth flow, `AuthOutcome`).
|
||||
|
||||
## Context — what SouveraineOS already has
|
||||
|
||||
- `SESSION-AUTHORITY-DOCTRINE.md` §2-3 — `PamContext` is a standalone
|
||||
connection type (not baked into `WlSessionLock`); step-up points a second
|
||||
PAM context at `/etc/pam.d/souveraine-stepup`, accepts password or
|
||||
fingerprint in any order, mints a short-lived grant bound to an action
|
||||
family. Face slots in as a third accepted factor on the same context.
|
||||
- `SESSION-TRUST-ARCHITECTURE.md` — capability tiers `ambient / personal /
|
||||
stepUp`. `personal` gate is `!screenLocked && !screenLockSecure`. `stepUp`
|
||||
requires a recent grant.
|
||||
- `TRUST-BOUNDARY-MATRIX.md` — `session.unlock()` → stepUp, `LockContext
|
||||
PIN/PAM`; `StepUpAuth.requestAuth()` → PAM conversation. Both are the
|
||||
insertion points for a face factor.
|
||||
- `02-lockscreen-rust-system.md` — the glance/lock surface where an
|
||||
in-place reveal (face mint → `hasFreshGrant('read')`) would show
|
||||
notification/message bodies without unlocking.
|
||||
- `11-souveraine-secrets.md` — secrets store; face embeddings are a
|
||||
biometric secret and belong in the same trust/at-rest discipline, not a
|
||||
parallel `/var/lib` tree.
|
||||
|
||||
## What to build (full plan, not V1/V2)
|
||||
|
||||
One owned factor, end to end. No phased feature-stripping — design the
|
||||
final shape, then implement.
|
||||
|
||||
1. **In-tree face daemon (the `gazed` equivalent), SouveraineOS-native.**
|
||||
- Owns the camera, runs SCRFD/ArcFace/MiniFASNet, stores embeddings.
|
||||
- NOT a D-Bus `com.gundulabs.Gaze` clone. Exposed over the existing
|
||||
Souveraine IPC surface (same bus agents and sessiond use), with a
|
||||
`verify_*` / `enroll_*` / status-signal shape derived from Gaze's
|
||||
`CaptureStatus` + `VerifyResult` — because that enum pair is
|
||||
well-thought-out and the `Unavailable` vs `NoMatch` distinction is
|
||||
load-bearing for fail-closed behavior.
|
||||
- Camera/tiling/liveness run as their own service the PAM path can
|
||||
reach; keep the PAM-facing shim dumb (Gaze's split is right).
|
||||
|
||||
2. **PAM factor, not a PAM silo.** A face mint flows through the *existing*
|
||||
`PamContext` → `souveraine-stepup` and the lock's `LockContext`, not a
|
||||
new `pam-gaze` service with its own policy. Add face to the factor list
|
||||
`/etc/pam.d/souveraine-stepup` already accepts. The unlock path
|
||||
(`session.unlock()`) and the reveal path (`StepUpAuth` →
|
||||
`LockContentPolicy.allowsOnLock()` third condition) both consume it
|
||||
identically — one factor, two effects, by virtue of which context it
|
||||
mints into. This is the doctrine's design; we're populating it, not
|
||||
extending it.
|
||||
|
||||
3. **Tiered pipeline strictness.** Reuse Gaze's model-swap pattern but map
|
||||
it onto our capability tiers at the *decision* side: a grant that will
|
||||
unlock the session or satisfy `stepUp` runs the strict pipeline
|
||||
(`det_10g` + IR-if-available + tight liveness); an ambient read-mint can
|
||||
run the fast pipeline. The mapping is policy; the mechanism is two model
|
||||
sets behind the same daemon.
|
||||
|
||||
4. **Presence / fail-closed gates (copy Gaze's instinct, wire to our
|
||||
state).** Refuse to capture when:
|
||||
- session is SSH/remote (our session authority already knows this),
|
||||
- lid closed / no front camera available (device-state manager,
|
||||
TASK-08),
|
||||
- liveness fails or capture is `TooDark`.
|
||||
Every one of these yields `Unavailable`, never `Match`. No unlock, no
|
||||
reveal. This matches the suspend-before-lock fail-closed invariant.
|
||||
|
||||
5. **Enrollment + storage.** Enrollment UI in the shell settings app
|
||||
(SETTINGS-APP-PLAN). Embeddings stored under the souveraine-secrets
|
||||
discipline (TASK-11), at-rest protected, never logged, never transmitted.
|
||||
No `/var/lib/gaze` parallel tree — biometric data lives where secrets
|
||||
live.
|
||||
|
||||
6. **Audit.** Face success/failure/unavailable events hit the existing
|
||||
`SessionAudit` append-only JSONL with hash chain (TRUST-BOUNDARY-MATRIX
|
||||
row "Session audit trail"). A face unlock is a security-relevant event
|
||||
and must be in the same trail as PIN/PAM unlocks — not a separate log.
|
||||
|
||||
7. **Models.** Ship the ONNX models Gaze uses (SCRFD detector, ArcFace
|
||||
recognizer, MiniFASNet liveness) as packaged assets, same provenance
|
||||
discipline as any other binary blob in the rootfs. Pin versions; do not
|
||||
fetch at runtime.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Cold boot to unlock: a face match mints through `souveraine-stepup` /
|
||||
lock `LockContext` and unlocks the session, indistinguishable at the
|
||||
capability layer from a PIN or fingerprint unlock. Same audit row shape.
|
||||
- On the lock surface (post-TASK-02 glance): a face mint reveals message /
|
||||
notification bodies in place via `hasFreshGrant('read')` **without**
|
||||
unlocking the session. Lock is still requested; compositor still secure.
|
||||
- SSH session, lid closed, camera gone, too dark, or liveness fail →
|
||||
`Unavailable` → no unlock, no reveal, no audit "success." Fall through to
|
||||
PIN silently.
|
||||
- Enrollment, model swap by tier, and embedding storage live entirely
|
||||
in-tree under the secrets discipline. No `gazed` process, no
|
||||
`/var/lib/gaze`, no GunduLabs D-Bus name on the bus.
|
||||
|
||||
## Do not
|
||||
|
||||
- **Do not vendor or wrap `gazed`.** It's a reference. We own the source
|
||||
for anything in the permission-gating path. The PAM shim, the daemon, the
|
||||
policy — all in-tree.
|
||||
- **Do not make face a new trust mechanism.** No new tier, no new grant
|
||||
type. It is a factor behind `PamContext` / `StepUpAuth`, exactly as
|
||||
fingerprint is framed in the doctrine. The "two factors" framing is
|
||||
explicitly rejected in SESSION-AUTHORITY-DOCTRINE — don't reintroduce it.
|
||||
- **Do not gate personal content on face alone.** The reveal is
|
||||
`hasFreshGrant('read')`; whether that grant came from face, fingerprint,
|
||||
or PIN is irrelevant to `LockContentPolicy`. Keep the policy
|
||||
factor-agnostic.
|
||||
- **Do not special-case face in the audit trail.** Same JSONL, same hash
|
||||
chain, factor recorded as a field — not a parallel log.
|
||||
|
||||
## Connects to
|
||||
|
||||
- SESSION-AUTHORITY-DOCTRINE §2-3 (the slot this fills — "gated on the
|
||||
fingerprint sensor coming online"; face is unblocked by any camera).
|
||||
- TRUST-BOUNDARY-MATRIX (`session.unlock`, `StepUpAuth.requestAuth`,
|
||||
`Session audit trail` rows).
|
||||
- TASK-02 lockscreen glance (in-place reveal surface).
|
||||
- TASK-08 device-state manager (lid-closed / camera-present gate).
|
||||
- TASK-11 souveraine-secrets (embedding at-rest).
|
||||
- SETTINGS-APP-PLAN (enrollment UI).
|
||||
- **Blocker, per-target:** laptop front camera is straightforward; Pixel 3
|
||||
(blueline) front-camera bring-up is not yet solved — face auth is moot on
|
||||
the phone until a userspace camera device exists for the daemon to grab.
|
||||
Not a reason to scope down; a reason to land laptop-first and have the
|
||||
phone light up when the camera does.
|
||||
|
||||
## Reference provenance
|
||||
|
||||
- Upstream: `https://github.com/GunduLabs/gaze`, commit `c9c8ffb`, MIT.
|
||||
- Local clone: `~/Projects/gaze` (reference only; not a submodule, not a
|
||||
build input).
|
||||
- Cite file:line when borrowing a design decision (not code) from it.
|
||||
161
docs/tasks/13-active-edge-squeeze.md
Normal file
161
docs/tasks/13-active-edge-squeeze.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# TASK 13 — Active Edge (squeeze) as a sensor input
|
||||
|
||||
**Status:** open. Substrate present, bring-up unit not yet drafted. The
|
||||
sensor front-end config and the Android boot recipe both exist in-tree as
|
||||
reference; nothing here is invented.
|
||||
|
||||
## Goal
|
||||
|
||||
Active Edge — the Pixel 3 squeeze gesture — becomes a usable input on
|
||||
blueline: squeeze fires an event the shell (and, via surfacing, the agent)
|
||||
can consume. Symmetric with the other sensor inputs already working over
|
||||
SSC/IIO (accel, proximity, light). It is a sensor, not a keyboard key and
|
||||
not a GPIO button — it must come up through the SSC/SLPI → libssc path
|
||||
that already serves the other sensors.
|
||||
|
||||
## Context — the substrate already exists
|
||||
|
||||
This is the decisive point: the squeeze is **not** a missing kernel input
|
||||
driver. It is a sensor exposed over the Snapdragon Sensor Core (SSC), and
|
||||
the entire transport is already booted and serving sensors on blueline:
|
||||
|
||||
- SLPI remoteproc (remoteproc2) comes up via `blueline-slpi.service`.
|
||||
- `blueline-hexagonrpcd-sdsp.service` serves the FastRPC / HexagonFS doorbell.
|
||||
- `blueline-sns-registry-stage.service` regenerates the SSC sensor registry
|
||||
from durable source under `/usr/share/qcom/sensors-src` (the LineageOS
|
||||
`etc/sensors/` tree) via `sscregistrygen`, filtered by `-p PLAT -s SOC
|
||||
-v PV`.
|
||||
- `90-iio-sensor-proxy-ssc.rules` already exposes ssc-accel / ssc-proximity
|
||||
/ ssc-light. Same IIO bus a squeeze client would read.
|
||||
|
||||
So the heavy lifting is done. What's missing is the **squeeze-specific**
|
||||
pieces: the power rail on the sensor, and a userspace client for the
|
||||
`sns_touch_gesture` SUID.
|
||||
|
||||
## Context — the squeeze front-end and power path
|
||||
|
||||
Two reference files in the tree pin the exact contract:
|
||||
|
||||
- `~/Projects/Pixel3Arch/blobs/los-vendor/etc/sensors/registry/b1_touch_gesture_0.json`
|
||||
— the squeeze sensor config: `controller_id 1, bus_instance 5,
|
||||
slave_address 73 (0x49), irq_num 125, owner sns_touch_gesture,
|
||||
hw_platform [OEM], soc_id [321], platform_version
|
||||
[0x20000:0xFFFF0000]`. This matches blueline (device 0x20028), so the
|
||||
sensor is **not** excluded by the platform-version filter — the registry
|
||||
already admits it once the power rail is up.
|
||||
- `~/Projects/PostMarketOS-Blueline/android-reference/vendor-stack-20260621/vendor/bin/init.edge_sense.sh`
|
||||
— LineageOS's boot recipe: it raises **PM8998 GPIO 2** (the edge-sense
|
||||
power rail) via `/sys/class/gpio/export`, sets direction out, drives
|
||||
high. The comment names the chain explicitly: Elmyra sensor HAL → SLPI
|
||||
driver, powered through PM8998. The script's own TODO notes the rail
|
||||
should ideally be gated on use by the SLPI driver, which "doesn't have
|
||||
direct access to the PM8998 GPIOs" — so userspace powers it on boot.
|
||||
|
||||
The squeeze and the microphone are fully independent: PM8998 GPIO 2 + I²C
|
||||
sensor on SLPI vs. WCD9340 DMIC + MICBIAS over LPASS/SLIMbus. No shared
|
||||
GPIO, I²C bus, registry, DSP process, or clock. Active Edge bring-up must
|
||||
not be folded into the audio boot path — it's a separate variable.
|
||||
|
||||
## Update 2026-07-20 — step 1 is a kernel patch, not a systemd unit
|
||||
|
||||
Verified live on the phone and against kernel source: the vendor script's
|
||||
whole mechanism is dead on this kernel. `/sys/class/gpio` does not exist
|
||||
(`find /sys/class/gpio` — nothing; mainline moved to the `gpiod`
|
||||
character-device ABI, no legacy sysfs export path). A pure rootfs-overlay
|
||||
unit cannot replicate `init.edge_sense.sh` as originally scoped.
|
||||
|
||||
Checked the actual devicetree, both source and live (`blueline-live.dts`
|
||||
in PostMarketOS-Blueline's `dt-work/`, decompiled from the running boot):
|
||||
`pm8998_gpios` (`gpio@c000` under `pmic@1`, `compatible = "qcom,pm8998-gpio",
|
||||
"qcom,spmi-gpio"`) is real and live — `sdm845-google-common.dtsi:716`
|
||||
already uses it for `gpio6` (volume-up). **GPIO 2 (the squeeze rail) has
|
||||
no node at all** — not disabled, never added, on either the kernel source
|
||||
tree (`kernel/linux-blueline-work/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi`)
|
||||
or the live decompile. This is a real gap, not a config/boot-state issue.
|
||||
|
||||
GitHub search turned up `LineageOS/android_kernel_motorola_msm8998:arch/arm/boot/dts/qcom/msm8998-loki.dtsi`
|
||||
(different SoC family, same PMIC generation) with a `gpio@c100 { /*
|
||||
PM8998_GPIO2 */ }` node — confirms GPIO2 addresses at register offset
|
||||
`0xc100` (one 0x100 slot per pin from `gpio@c000`'s base) and that this
|
||||
is a normal per-pin pinctrl subnode. That file uses the legacy CAF
|
||||
`qcom,mode`/`qcom,vin-sel`/`qcom,src-sel` binding style, which is NOT what
|
||||
blueline's mainline-derived tree uses — `vol-up-active-state` (gpio6) uses
|
||||
the modern generic pinconf shape (`function`, `input-enable`,
|
||||
`bias-pull-up`, `qcom,drive-strength`). The GPIO2 node must match that
|
||||
shape, not the CAF one — the Motorola file is useful only for confirming
|
||||
the register-offset/binding-family, not as copy-paste source.
|
||||
|
||||
1. **Devicetree patch (the actual missing piece).** Add a `pm8998_gpios`
|
||||
subnode for GPIO 2 to `sdm845-google-common.dtsi` (or a Pixel3Arch
|
||||
kernel overlay), mirroring `vol-up-active-state`'s binding style:
|
||||
`function = "normal"`, output-enable (not input-enable — this rail
|
||||
needs to be driven, not read), no pull needed if driven push-pull.
|
||||
Whether this should be a plain pinctrl state some other node
|
||||
references, or a `gpio-hog` (drives itself high automatically at
|
||||
kernel boot, zero userspace component, matching what the always-on
|
||||
rail actually needs) is the open design choice — `gpio-hog` is
|
||||
probably right since nothing needs to read this pin, only assert it.
|
||||
Requires a kernel rebuild + deploy cycle on archdev, not a rootfs-only
|
||||
change. If `gpio-hog` doesn't fit the binding, a small `libgpiod`
|
||||
(`gpioset`) oneshot systemd unit is the userspace fallback — targeting
|
||||
`pm8998_gpios` line 2 via the modern char-device API, not
|
||||
`/sys/class/gpio`.
|
||||
|
||||
2. **Confirm the SUID is served.** Once the rail is live and SLPI is up,
|
||||
verify `sns_touch_gesture` appears as a served SUID (libssc client
|
||||
enumerate) and produces a gesture event on squeeze. The registry JSON
|
||||
already admits it; the question is purely whether the SLPI image
|
||||
serves the SUID and the powered sensor answers. First decisive test
|
||||
requires the phone booted live.
|
||||
|
||||
3. **Userspace consumer.** Wire the gesture into the existing IIO/sensor
|
||||
path the other SSC sensors already use (the
|
||||
`90-iio-sensor-proxy-ssc.rules` surface), or a small libssc client if
|
||||
the SUID isn't auto-exposed as IIO. Either way the squeeze becomes a
|
||||
normal shell-consumable input event — not a special case.
|
||||
|
||||
4. **Shell binding + agent surfacing.** The squeeze is an input with a
|
||||
natural home in the keyboard/routing system (see Keyboard System
|
||||
memory): it must register through that system, not as a hardcoded
|
||||
binding, so it doesn't collide. A squeeze can surface the glance
|
||||
(TASK-02), trigger the assistant, or whatever the routing policy maps
|
||||
it to.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Cold boot: `blueline-edge-sense.service` brings the rail up, the unit
|
||||
goes active, and `sns_touch_gesture` is enumerable as a served SUID.
|
||||
- A physical squeeze fires a gesture event visible to the userspace
|
||||
consumer.
|
||||
- The event reaches the shell through the keyboard/routing system as a
|
||||
normal input, bound by policy (not hardcoded).
|
||||
- Bring-up is independent of the audio/mic boot — proven by the two
|
||||
sharing no GPIO, bus, or DSP path.
|
||||
|
||||
## Do not
|
||||
|
||||
- **Do not fold this into the WCD9340 mic work.** Separate variable,
|
||||
separate substrate. The mic kernel build does not touch PM8998 GPIO 2,
|
||||
SLPI sensor registry, or `sns_touch_gesture`.
|
||||
- **Do not write a kernel input driver for squeeze.** It's a sensor over
|
||||
SSC; the transport is already up. A kernel GPIO-key would be the wrong
|
||||
layer and would duplicate the rail handling.
|
||||
- **Do not hardcode the squeeze binding.** Route it through the
|
||||
keyboard/routing system like any other input.
|
||||
- **Do not run the vendor `init.edge_sense.sh` by hand as the steady
|
||||
state.** It's a reference for what the systemd unit must do; the unit
|
||||
replaces it.
|
||||
|
||||
## Connects to
|
||||
|
||||
- `init.edge_sense.sh` (PM8998 GPIO 2 power recipe — the unit's spec).
|
||||
- `b1_touch_gesture_0.json` (sensor front-end; already in registry).
|
||||
- `blueline-slpi.service`, `blueline-hexagonrpcd-sdsp.service`,
|
||||
`blueline-sns-registry-stage.service` (substrate already booted).
|
||||
- `90-iio-sensor-proxy-ssc.rules` (the IIO surface the other SSC sensors
|
||||
use).
|
||||
- Keyboard System memory (routing — squeeze must register as an input,
|
||||
not a hardcoded binding).
|
||||
- TASK-02 lockscreen glance (one plausible policy target for a squeeze).
|
||||
- GPT Sol gap prompt (sent 2026-07-18) covers the same gap from the
|
||||
external side; this task is the local in-tree half.
|
||||
|
|
@ -12,17 +12,20 @@ plus open threads from the 2026-07-17 session.
|
|||
|
||||
| # | Task | Status | Blocks / blocked-by |
|
||||
|---|------|--------|---------------------|
|
||||
| 1 | [Favorites — wife's name on her number](01-favorites-contacts.md) | blocked | needs TASK-09 (qtpim build) |
|
||||
| 1 | [Favorites — wife's name on her number](01-favorites-contacts.md) | blocked | needs a contacts-store decision (qtpim retired — see archive/06) |
|
||||
| 2 | [Lockscreen-as-Rust-system: glance + swipe](02-lockscreen-rust-system.md) | in progress | stopgap shipped 2026-07-17 |
|
||||
| 3 | [Boot timing: fade splash → lock, no extra fixes](03-boot-timing-splash-to-lock.md) | open | pairs with TASK-11 |
|
||||
| 3 | [Boot timing: fade splash → lock, no extra fixes](03-boot-timing-splash-to-lock.md) | open | paired with TASK-11 (now archived) |
|
||||
| 4 | [Notification server (org.freedesktop.Notifications)](04-notification-server.md) | open | — |
|
||||
| 5 | [Crash reporter surfacing](05-crash-reporter.md) | half-landed | pairs with TASK-04 |
|
||||
| 6 | [qtpim aarch64 build → contacts store](06-qtpim-build-contacts.md) | staged | archdev binfmt wall |
|
||||
| 7 | [WCD9340 mic: SLIM TX channel/port contract](07-mic-wcd9340-slim-tx.md) | diagnosed | frontier; pairs with kernel series |
|
||||
| 8 | [Device state manager / power profiles](08-device-state-manager.md) | open | blocked-by TASK-09 suspend item |
|
||||
| 9 | [Suspend-resume FTS calibration race](09-suspend-resume-fts.md) | interim-fix | kernel-side; cold-boot only |
|
||||
| 10 | [Boot chain: pmOS init → mkinitcpio](10-boot-chain-mkinitcpio.md) | open | flash-caution |
|
||||
| 11 | [souveraine-secrets on the phone](11-souveraine-secrets.md) | built, blocked | seed restore decision |
|
||||
| 12 | [Face auth as a capability factor (Gaze reference)](12-gaze-reference-face-auth.md) | open | per-target: phone needs front-cam bring-up |
|
||||
| 13 | [Active Edge (squeeze) as a sensor input](13-active-edge-squeeze.md) | open | substrate up; bring-up unit not drafted |
|
||||
|
||||
Archived (see `archive/`): 06 qtpim build — retired 2026-07-20, qtpim is
|
||||
no longer the contacts path; 10 boot chain — done, we ship our own
|
||||
mkinitcpio; 11 souveraine-secrets — built and committed 2026-07-20.
|
||||
|
||||
## Conventions
|
||||
|
||||
|
|
|
|||
|
|
@ -59,3 +59,12 @@ A contacts query returns from the phone. TASK-01 unblocked.
|
|||
## Connects to
|
||||
|
||||
TASK-01 (favorites), handoff doc item 9 (crossbuild policy).
|
||||
|
||||
---
|
||||
|
||||
**RETIRED 2026-07-20.** qtpim is dead as the contacts path — old,
|
||||
unmaintained, and the archdev binfmt wall made its cross-build a money
|
||||
pit. The contacts store needs a fresh decision (likely a small owned
|
||||
store the Person layer reads directly — EBook/sqlite class, no Qt PIM
|
||||
dependency). TASK-01 (favorites) re-pointed at that decision; nothing
|
||||
else depended on this build.
|
||||
|
|
@ -41,3 +41,8 @@ to the splash. Slot verified before flash; rollback slot intact.
|
|||
|
||||
TASK-03 (boot timing — adjacent; the splash fade is post-initramfs).
|
||||
Pixel3Arch boot tooling.
|
||||
|
||||
---
|
||||
|
||||
**ARCHIVED 2026-07-20** — marked done in the index ("we ship our own
|
||||
mkinitcpio"); moved out of the open queue during the task audit.
|
||||
Loading…
Reference in a new issue