284 lines
15 KiB
Markdown
284 lines
15 KiB
Markdown
# TASK 63 — the second body: SouveraineOS on the iPhone 7
|
||
|
||
**Status: the shell is on the glass, 2026-08-09.** viewtop, sessiond and
|
||
`qs -c souveraine` all run on a T8010 with no GPU, drawn by llvmpipe, over one
|
||
USB cable. Wallpaper, palette, bar, clock, dock and her quote match the Pixel.
|
||
**Repos:** `souveraine`, `souveraine-viewtop`, `Pixel3Arch`, and whatever owns
|
||
device provisioning next.
|
||
|
||
This task exists because the device is no longer a Hoolock experiment. Hoolock
|
||
is the bootloader and nothing else; what is on `nvme0n1p1` is a first-class
|
||
Souveraine system, and everything below is about making a second body work
|
||
without forking the first.
|
||
|
||
## What runs, measured
|
||
|
||
| | |
|
||
|---|---|
|
||
| board | Apple T8010, `d10ap`, iPhone 7 Global |
|
||
| kernel | `7.2.0-rc1-gb902cb53677b-dirty`, DTB `t8010-d10-nowifi` |
|
||
| display | `DRIVER=simple-framebuffer`, `/chosen/framebuffer@87de5c000`, one mode `750x1334`, **`card0` only — no `renderD128`** |
|
||
| renderer | Mesa 26.1.6, **llvmpipe**, GLES 3.2, `PLATFORM_GBM_KHR` on `card0` |
|
||
| scale | 1.0, source `Density`, dpi 96 — simpledrm publishes no EDID, so the physical-mm path resolves through the DPI fallback rather than dividing by zero |
|
||
| link | NCM `usb0` at `172.16.43.1/24`; the Pixel keeps `172.16.42.0/24`. No wifi, no modem, no battery node |
|
||
|
||
## The one that decides the lane: EGL on a card with no render node
|
||
|
||
`eglinfo`'s GBM platform fails and its surfaceless platform works, which reads
|
||
like software GL being unavailable. It is not. **`MESA_LOADER_DRIVER_OVERRIDE=kms_swrast`
|
||
is what breaks it** — forcing that loader makes Mesa look for a render device to
|
||
pair with the card, find none, and quit:
|
||
|
||
```
|
||
[EGL] 0x3001 NOT_INITIALIZED: DRI2: failed to get compatible render device
|
||
```
|
||
|
||
Drop the override and Mesa finds its own way. Three of four variants come up
|
||
listening: bare `swrast`, `GALLIUM_DRIVER=llvmpipe`, and `zink`. Only the
|
||
`kms_swrast` override dies.
|
||
|
||
**`GALLIUM_DRIVER=llvmpipe` is what runs today**, and that is a stopgap: it must
|
||
not be set on the Pixel. viewtop already logs `no egl render node; advertising
|
||
linux-dmabuf v3` — it *knows* — and should take the software path itself instead
|
||
of needing the environment to tell it. That is the fix, and it is small.
|
||
|
||
## Tearing — the mechanism, so nobody tunes at it
|
||
|
||
Casey, on the glass 2026-08-09: screen tearing artifacts, often.
|
||
|
||
viewtop logs at startup:
|
||
|
||
```
|
||
failed to create signaled syncobj — err=Os { code: 95, Unsupported }
|
||
```
|
||
|
||
simpledrm has no syncobj support, so there is no explicit fence to hold a page
|
||
flip until the frame is finished. On the Pixel the GPU fences it. Here llvmpipe
|
||
writes into a dumb buffer while the panel scans the same memory. This is
|
||
structural, not a setting. Fixing it means either a fence-free damage/copy
|
||
discipline in the KMS path or double-buffering the dumb BO by hand.
|
||
|
||
## Touch, and the display pipe — 2026-08-09 night
|
||
|
||
**Touch works on the glass.** `evtest`-grade proof, not a device node: 2407 events,
|
||
270 `SYN_REPORT`, 13 balanced down/up, X 53..735, Y 107..1333, with
|
||
`MT_TOUCH_MAJOR` and `MT_ORIENTATION`. Kernel-native `apple_z2` — no `hx-touchd`,
|
||
no Sandcastle daemon. Three layers, each hiding the next:
|
||
|
||
1. **`apple/t8010-smartio.bin` was not on the rootfs.** SIO DMA times out
|
||
(`-ETIMEDOUT` after the 60 s sysfs fallback), so `apple-spi` gets no TX DMA
|
||
and the SPI bus never probes. Both blobs were built from the IPSW on
|
||
2026-08-08 into the *install ramdisk only*, and the disk-boot blob carries no
|
||
initramfs. Now `CONFIG_EXTRA_FIRMWARE`, compiled into the Image, which is the
|
||
only placement that beats rootfs to the probe.
|
||
2. **`# CONFIG_REGULATOR is not set`.** That silently dropped
|
||
`REGULATOR_APPLE_PMIC_TOUCH` (`drivers/regulator/apple-pmic-touch-regulator.c`,
|
||
marked `default y if ARCH_APPLE`), so `apple_z2`'s mandatory `hv`/`core`
|
||
supplies resolved to no-op stubs and the controller was never powered. **A
|
||
registered input node with dead rails is indistinguishable from working
|
||
touch** — verify with events, never dmesg.
|
||
3. **Runtime report activation was gated to D11.**
|
||
`apple_z2_enable_d11_reports_locked()` is what switches runtime reports on;
|
||
D10 never called it, so boot frames arrived and the panel went silent about a
|
||
second later. All four `is_d11()` sites now go through
|
||
`apple_z2_is_gen2_reports()` = D10 ∨ D11.
|
||
|
||
**The Apple Display Pipe runs on a T8010 — the first anywhere.**
|
||
`[drm] Initialized adp 0.1.0 for 206200000.display-pipe on minor 1`, with
|
||
`card1-DSI-1` and the panel attached at `206600000.dsi.0`. Every other port on
|
||
this hardware scans out a dumb framebuffer: Sandcastle, pmOS
|
||
(`linux-postmarketos-apple-16k` has no `APPLE_SIO` at all), uPhone
|
||
(`CONFIG_FB_SIMPLE=y`, `TOUCHSCREEN_HX`, an `hx-touchd.service`), and
|
||
qwertyoruiopz's original 5.4.14 boot. That is why the wiki's *"abnormal screen
|
||
color"* has no owner.
|
||
|
||
Addresses came off the D10 ADT and were checked against
|
||
`HoolockLinux/linux:tests/adp` commit `2b3b1a7fc` "t8012 dts: display pipe" —
|
||
T8012 is T2, A10-family, and its `disp0_dart` is literally `apple,t8010-dart`:
|
||
|
||
| node | reg | irq |
|
||
|---|---|---|
|
||
| `disp0` | be `0x06200000/0x9000`, fe `0x06400000/0x4000` | be 154, fe 158 |
|
||
| `disp0_dart` | `0x06304000/0x4000` | 156 |
|
||
| `disp0_mipi` | `0x06600000/0x100000` — **byte-identical to T8012's** | 163 |
|
||
|
||
The be/fe split is not guesswork: ADP binds only `be`/`fe` by name, pokes `be` to
|
||
`0x40f4` and `fe` to `0x100`, and T8012's own IRQ layout has its DART sitting
|
||
*between* the pipe's two — 210/212/214 there, 154/156/158 here. `panel-summit` is
|
||
134 lines with no init sequence because the loader lights the panel, which is
|
||
exactly our situation; it gained a 750×1334 mode under `apple,d10-panel`.
|
||
|
||
**Getting simpledrm out of the way works. The panel is dark anyway.**
|
||
`status = "disabled"` on `framebuffer0` in the DT does nothing — m1n1's `kboot`
|
||
fills and re-enables `/chosen/framebuffer` itself, so the loader wins. udev then
|
||
names simpledrm the primary GPU and `primary_device()` (`kms.rs:447`) hands
|
||
viewtop `card0`. `initcall_blacklist=simpledrm_platform_driver_init` on the
|
||
cmdline does remove it, and then ADP *is* `card0`: viewtop opens it, EGL comes
|
||
up, the CRTC reports `active=1` with `fb=43` and the plane at `750x1334+0+0`,
|
||
the connector is `connected` and `enabled`. Both backlights raised. Still black.
|
||
|
||
So the compositor is producing frames and the panel is not showing them, and the
|
||
two candidates are both in the parts that were *guessed* rather than derived:
|
||
|
||
- **The mode timings are invented.** Every address in this port came off the ADT
|
||
or matched T8012. The raster did not — nothing published one, so
|
||
`panel-summit`'s d10 mode carries porches I made up. m1n1 already programmed
|
||
Apple's real ones; ADP then re-modesets over them.
|
||
- **`panel-summit` never sends a DCS display-on**, and never sets `dsi->lanes`,
|
||
`format` or `mode_flags`. That is deliberate for a Touch Bar, where the loader
|
||
leaves the panel running. D10's ADT says `#lanes = 2`; if `adp-mipi`
|
||
reprograms the host from unset defaults the link comes up wrong.
|
||
|
||
`boot.sh` therefore keeps simpledrm by default and takes `-a` for the ADP-only
|
||
boot. **The pipe is not the daily path until a pixel is seen on it.**
|
||
|
||
Kernel work is committed on archdev as `2e349ddf2` and exported to
|
||
`souveraine-iphone/d10-port/patch/`. That commit also captured the 141-line `t8010-d10.dts`
|
||
port, which had been uncommitted against the 14-line mainline stub.
|
||
|
||
## The session is real now
|
||
|
||
greetd → `/usr/bin/souveraine-session-viewtop` as `casey`, no greeter, same shape
|
||
as blueline. `casey` is uid 1000 with blueline's exact groups
|
||
(`wheel input video seat souveraine`); the stock `alarm` account is gone and its
|
||
home preserved at `/home/alarm.stock-preserved`. sessiond and the shell run as
|
||
supervised user units and are enabled at boot, so §0's `no Display session for
|
||
uid 0` drift is gone — `loginctl` shows a real `seat0`/tty1 session and
|
||
`LockedHint` tracks it.
|
||
|
||
Carried across and worth knowing: the QML config is a **symlink farm with
|
||
absolute targets**, so moving it between homes needs all 432 links repointed, not
|
||
just a copy. Shell tree parity against blueline was verified file-by-file — 1056
|
||
files, one difference, and it is a file the *Pixel* lacks.
|
||
|
||
`souveraine-iphone/boot.sh` is the whole boot: waits for DFU, runs checkm8 → m1n1 → kernel
|
||
handoff, then prints kernel/touch/spi/drm/adp/deferred/session. `./boot.sh` for
|
||
everything, `1`/`2`/`3` for one stage, `-l` to list staged kernels, `-k` to pick.
|
||
|
||
## Open on the d10
|
||
|
||
- **The buttons do nothing.** `gpio-keys` is on event1 and nothing routes it to
|
||
sessiond's `button` verb — the blueline path came from `hyprland.lua` bindings
|
||
ported into the state machine, and no equivalent exists here. The ADT names
|
||
four: `hold`, `volup`, `voldown`, `ringerab` (the mute switch).
|
||
- **`Dim` was device-specific and is fixed but unshipped.** `brightnessctl set 10`
|
||
is an absolute: 4% of blueline's 255-step backlight, 0.5% of the iPhone's
|
||
2047-step Apple DWI, where it reads as fully off. `souveraine eb0292c` makes it
|
||
`4%`; it needs a CI build and `pacman -Syu` to reach the device.
|
||
- **`touchKeypad` binds before its config loads.** `Lock.qml:137` defaults it
|
||
`false` and `:148` binds the surface from that, so the lock came up as the
|
||
keyboard variant with no keypad — and `surfaceComponent` cannot change once the
|
||
lock is live. Patched on-device only (`Lock.qml.bak-touchkeypad` beside it);
|
||
the repo fix is still owed.
|
||
- **`casey` has password `123456`.** It is also the lock-screen PIN.
|
||
- Sensors report nothing and say so honestly: proximity, light and accel are all
|
||
`source-never-reported`, because no reporter exists on this board.
|
||
|
||
## Where the split is
|
||
|
||
The device does not need `souveraine-upower-iphone`. It needs the base to stop
|
||
assuming one board. pmOS's shape: one common base, thin per-device packages
|
||
carrying DT and quirks.
|
||
|
||
**Belongs in the common base:**
|
||
|
||
- panel power. sessiond's `Blank`/`Unblank` shell out to
|
||
**`blueline-screen-toggle`**, a script name that exists on one phone and ships
|
||
only in `rootfs-overlay/`. On the d10 it resolves to nothing, so the panel
|
||
*structurally cannot* sleep. viewtop owns the CRTC, has `power.rs` with the
|
||
EBUSY deferral, and runs on both — the blank belongs there as a verb.
|
||
- render-path selection, per the section above.
|
||
- the shell's `hyprctl` calls: `workspaces`, `monitors`, `layers`, `clients`,
|
||
`activewindow`, `activeworkspace`, `getoption animations:enabled`. Dead on
|
||
both devices under viewtop; the Pixel merely still has the binary lying
|
||
around, so the failure is silent there and loud here.
|
||
|
||
**Belongs in a per-device package:** DTB, the `nowifi` variant, any T8010 quirk,
|
||
and the `PHONE_HOST`/subnet identity.
|
||
|
||
**Neither, and it is a real absence:** no `/sys/class/power_supply` entry and no
|
||
modem on this board. The bar is telling the truth about a different animal, not
|
||
drifting. Do not paper it.
|
||
|
||
## What the provisioning has to do, because none of it was possible
|
||
|
||
That rootfs had **never installed a package in its life**, and could not have:
|
||
|
||
1. **No Landlock.** `/sys/kernel/security` holds only `lsm`. pacman 7.1 aborts
|
||
at *sync*: `restricting filesystem access failed because Landlock is not
|
||
supported by the kernel`. The image carried `#DisableSandboxFilesystem` and
|
||
`#DisableSandboxSyscalls` — **neither is a pacman option**, the same phantom
|
||
pair TASK-25 recorded breaking the Pixel in July, propagated into a second
|
||
image. pacman 7.1 has exactly one: `DisableSandbox`.
|
||
2. **No keyring at all.** `/etc/pacman.d/gnupg` did not exist — not empty,
|
||
absent. Even a successful sync would have aborted at verify.
|
||
|
||
Either one alone is fatal, and both fail in a way that looks like a network
|
||
problem. A first-boot provisioning step owes `pacman-key --init`/`--populate`
|
||
and a kernel-capability check that writes the real option.
|
||
|
||
## What had to be hand-carried, and what that says
|
||
|
||
Every one of these is the overlay-vs-package hole (TASK-27/28) hitting a second
|
||
device:
|
||
|
||
- `/etc/pam.d/souveraine-sessiond` — overlay-only, so sessiond has no PAM stack
|
||
on any machine that did not flash. Same reason the laptop has none.
|
||
- `blueline-screen-toggle` / `blueline-undim` — overlay-only, so no blank.
|
||
- fonts (`ttf-material-symbols-variable` and friends) — AUR-only. Without the
|
||
Material Symbols variable font the bar prints ligature names as literal text:
|
||
`wifi_find`, `5`. That is what missing icons look like.
|
||
- `config.json` with `"phone": true` — without it the shell comes up in desktop
|
||
mode and ii throws its first-run wizard over everything.
|
||
- the generated palette under `~/.local/state/quickshell/user/generated` — the
|
||
colours are derived from the wallpaper, so with no image she renders greyscale.
|
||
|
||
## Root, and closing the door behind us
|
||
|
||
The stock ALARM credential (`root`/`root`) was live over SSH with
|
||
`PermitRootLogin yes`. Casey's `ani` key is now in `/root/.ssh/authorized_keys`.
|
||
**The password path is still open and should be closed** — that is a real
|
||
finding about the provisioning, not about this device.
|
||
|
||
## The lock held, and that is the good news
|
||
|
||
With qs owning the lock, both refusals fired correctly:
|
||
|
||
```
|
||
viewtop: refusing to open a capture session — "no client captures the screen behind a lock screen"
|
||
sessiond: "a live shell owns the session lock; use the shell's lock IPC"
|
||
shell: "unlock is the credential gate; not remotely callable"
|
||
```
|
||
|
||
Root on the box, a key in the account, and the session still would not open for
|
||
a socket. Doctrine §13's user column enforced by three independent layers on a
|
||
device it had never run on before.
|
||
|
||
Consequence for working here: **a screenshot needs the session unlocked at the
|
||
glass.** `grim` behind a lock hangs, and that is the gate doing its job — check
|
||
`locked` in `state` before concluding anything from a capture failure
|
||
(`DUMP-bugs-2026-08-06` item 7, second instance).
|
||
|
||
Also measured: on root there is no logind session, so `lockhint` warns
|
||
`no Display session for uid 0` and falls back to its own tracking, *which
|
||
drifts* — §0's shadow-copy failure, reproduced. The packaged path is greetd plus
|
||
a real user session, and this is why.
|
||
|
||
## Acceptance
|
||
|
||
- `pacman -S` on a fresh device installs a working Souveraine session with no
|
||
hand-carried file, no AUR font, and no environment variable.
|
||
- viewtop selects the software render path from what it already detects.
|
||
- The panel sleeps and wakes on both devices through one verb.
|
||
- The shell asks the compositor for windows and layers, not `hyprctl`.
|
||
- A second device's identity is a package, not an edit.
|
||
|
||
## Connects to
|
||
|
||
TASK-25/27/28 (the overlay-vs-package hole, three more instances),
|
||
TASK-43/51 (viewtop; the render path and the tearing are new lanes),
|
||
TASK-08 (`Blank`/`Unblank` are its actuators),
|
||
`DEVICE-STATE-MACHINE.md` §0 (lockhint drift, reproduced),
|
||
`SESSION-AUTHORITY-DOCTRINE.md` §13 (three layers refused the unlock),
|
||
`souveraine-iphone` (the D10 boot path; Hoolock is its upstream provenance,
|
||
not the project's identity).
|