Appendix · story

Work log: SD slot died, phone migrated to eMMC, full omfreebdy bringup

An overnight session that started with a working phone and a routine reboot, ended with FreeBSD living on the eMMC and a one-command install profile.

The night started with a sudo reboot to test a one-line rc.conf change. The phone never came back. After ~12 hours of debugging — three different kernels, every level of cache flushed, two complete SD reflashes — it turned out the SD slot or the SD card itself was the variable. Migrating FreeBSD to the phone’s internal eMMC made everything work again, and we wound up rebuilding the full desktop environment on top of the Apr-8 baseline image. By the end we had a one-command install path so the next migration can’t burn another night.

2026-05-03 evening — the SD wedge

Tested the WiFi-DHCP rc.conf fix from 4e6ffe4 overlay/rc.conf: switch wlan0 to background DHCP with a sudo reboot. Phone went silent. U-Boot reached the FreeBSD loader’s “Autoboot in 1 seconds” prompt every time, then the kernel handoff produced no display output and SSH never came up.

Ruled out, in order:

  • Stale linker.hints and modules — full make installkernel DESTDIR=/mnt rebuild produced 723 fresh modules with matching SHAs, no help.
  • The rk_power_domain driver from 227e58b rk_power_domain: sysctl-only RK3399 PMU power-domain controller — disabled it and rebuilt; same hang.
  • console="comconsole,efi" interaction with the CRU hack — switched to console="efi" only, same hang on screen.
  • Filesystem corruption — fsck on the SD reported clean; UFS clean flag was set after unmount.
  • rc.conf content — the only userspace change between “boot worked” and “boot didn’t work” was the WiFi DHCP one-liner; not load-bearing for the kernel.

Same kernel binary that booted at 04:43 ran for 5 hours, was sudo reboot’d, never booted again. Same disk content, two binaries, neither boots from this SD post-shutdown.

The dd if=/dev/da1 ... | gzip forensic backup of the bad SD landed at forensics/sd-2026-05-03-stuck.img.gz (32 GB compressed of 60 GB raw) before any further attempts.

2026-05-03 night — proving the phone is fine

Per-the-handoff: ruled the phone fine by booting postmarketOS from the eMMC. Linux came up immediately. Phone hardware is healthy; SD card or slot is the variable.

Decision: migrate FreeBSD to the eMMC. dd-of-e (unknown commit) isn’t a thing — this is all dd work outside the repo:

  1. Backed up the eMMC’s pmOS install: sudo dd if=/dev/sde of=forensics/emmc-pmos-2026-05-03.img bs=4M — 123 GB raw, 3.3 hours over USB. (The first attempt with | gzip -1 got OOM-killed because gzip + 4M block size buffered too much; uncompressed dd worked fine.)
  2. Wrote freebsd-ppp.img (the Apr-8 baseline, 26 GB) onto /dev/sde. eMMC GPT showed [CORRUPT] because the image’s secondary GPT lives at sector 26 GB but the eMMC is 115 GB; gpart recover on first boot fixes it.
  3. Pulled the SD card out of the phone so U-Boot’s SD-first boot order would fall through to eMMC.
  4. Phone booted FreeBSD from eMMC immediately on first try.

That alone would have been the win for the night. The phone hadn’t booted from SD all night; eMMC worked first try.

2026-05-04 — getting our kernel + drivers back

The Apr-8 baseline image is #0 from 2026-04-08 23:37. Our session work — magnetometer reset-gpio, rk_tsadc trim, sgm3140 dtbo, fusb302 prepared-sink contract — needed to be re-layered. Did it incrementally:

  1. Deploy our #145 kernel (sha a69a60918d…, no rk_power_domain) via KERNEL_DEPLOY_REBOOT=1 mise run kernel:deploy:phone. First deploy didn’t take because of a tee + immediate sudo reboot race — a manual sync before the reboot fixed it. (Filed as a deploy-script TODO; the script’s mv followed by reboot doesn’t fsync.)
  2. Push our dtb + sgm3140 dtbo via cat | sudo tee — 32bf3535… for the dtb, sgm3140 overlay 0x747 bytes.
  3. Update /boot/loader.conf with fdt_overlays="sgm3140" (dropped bcm-hostwake since the dtbo isn’t built; cosmetic for now) and the then-current hw.rk_tsadc.trim_offset_mc=-30000 workaround. That trim line was removed on 2026-05-05 after the TSADC q-select fix.
  4. Push bwfm_sdio.ko matching our kernel via mise run module:deploy:phone bwfm_sdio.
  5. rsync the overlay tree/boot/firmware/brcm/* (WiFi/BT firmware), /usr/local/sbin/bt_* (Bluetooth scripts), /usr/local/etc/rc.d/bwfm_sdio (rc.d hook).
  6. Reboot.

Verified live: dev.rk_tsadc.0.cpu_temp: 31875 (sensible), dev.magnetometer.0.chip_variant: af8133j, dev.fusb302.0.vbus_present: 1, /dev/led/flash and /dev/led/torch both present.

2026-05-04 — omfreebdy bringup, the long way

Apr-8 image had Sway + xorg + 576 packages but no Hyprland, no firefox, no svkbd, no omfreebdy theme stack. Cloned omfreebdy onto the phone via rsync from MediaVolume and ran OMFREEBDY_PROFILE=wayland ./install.sh.

Hit a chain of papercuts:

  1. /etc/resolv.conf was a dangling symlink to /tmp/bsdinstall_etc/resolv.conf (left over from bsdinstall). pkg update failed Name could not be resolved. Fixed: rm + write a real file with nameserver 1.1.1.1.
  2. pkg+https://pkg.FreeBSD.org/FreeBSD:15:aarch64/kmods_latest doesn’t exist as a SRV mirror. pkg update errored on the FreeBSD-ports-kmods repo. Fixed: dropped a /usr/local/etc/pkg/repos/no-kmods.conf disabling that repo.
  3. mise isn’t in FreeBSD pkg. omfreebdy’s iso/packages/base.txt lists it; pkg install -y aborts the whole transaction on the unresolvable name. Fixed: filtered mise out of the package list at install time.
  4. First pkg install hung mid-way with pkg install -y libreoffice running and another pkg holding the database lock from a SIGKILL’d parent ssh session. kill -9 on the orphan, retry, ~99 packages installed (1149 total).
  5. install.sh aborted in the middle of optional builds because cliamp Go build needs librespot/flac which has no FreeBSD-tagged source files. Worked around by sourcing install.sh with OMFREEBDY_SOURCED=1 and running the post-package phases manually (setup_repo, setup_directories, install_configs, etc.).

After that: ~/.config/{hypr,foot,fuzzel,ghostty,mako,omfreebdy,picom,rofi,starship.toml,...} all in place, sudoers configs (omfreebdy-phone, omfreebdy-wifi, wheel), services enabled (dbus, seatd, avahi, powerd, bwfm_sdio).

2026-05-04 — sway-side build-from-source

omfreebdy’s pkg list doesn’t include the suckless / sourcehut things the phone Sway env needs:

svkbd — built from https://dl.suckless.org/tools/svkbd-0.4.2.tar.gz. Quirks:

  • fetch failed cert verification on dl.suckless.org; switched to curl -kL.
  • FreeBSD’s mini-memstick image lacks libgcc.a and libc_nonshared.a; built empty stubs via clang -c /dev/null + llvm-ar rcs.
  • config.mk paths needed /usr/X11R6/usr/local.
  • Missing -lfontconfig -lfreetype on the link line; added with sed.
  • Built with PATH=/usr/local/llvm19/bin:$PATH CPPFLAGS=-I/usr/local/include/freetype2 make CC=clang LAYOUT=mobile-intl.

lisgd — gesture daemon for swipe-to-show-keyboard. Quirks:

  • git.sr.ht/~mil/lisgd/archive/v0.4.0.tar.gz returns HTML (1.5 KB). The right URL is archive/0.4.0.tar.gz (no v prefix); that returns the actual gzip archive. master.tar.gz works too but has Linux-only deps not in 0.4.0.
  • Source uses <sys/prctl.h> and CLOCK_MONOTONIC_RAW, neither on FreeBSD. Patched: stub out prctl(PR_SET_PDEATHSIG, ...), s/CLOCK_MONOTONIC_RAW/CLOCK_MONOTONIC/g.
  • Makefile uses GNU ifndef/endif; FreeBSD bmake rejects it. Build with gmake.

End state: /usr/local/bin/{sway,svkbd-mobile-intl,lisgd,Hyprland,foot,fuzzel,waybar,mako,starship,...} all present. ~/bin/start-sway script for launching the phone-flavoured sway env.

2026-05-04 — three more papercuts before the phone was actually usable

After full install + reboot, phone could ping but not ssh. Confirmed via the serial console (printf 'root\n' > /dev/ttyUSB1, then commands) — sshd was running, listening on *:22, but every TCP SYN dropped before reaching it.

Found pf enabled with the Apr-8 baseline pf.conf:

pass out all flags S/SA keep state
block drop in all
pass in inet proto icmp all keep state
pass in quick on egress proto tcp all flags A/A keep state

ue0 (USB-Ethernet) isn’t in the egress group, so nothing matched. ICMP had its own rule, hence ping working. Disabled pf (sysrc pf_enable=NO).

Also caught the CRU BLOCKED write infinite loop on warm reboot. powerd was making PLL writes that our rk_cru_write_allowed() filter blocks unconditionally for addresses < 0x100, then retrying forever. Disabled powerd (sysrc powerd_enable=NO). Warm reboot now completes in ~30 s without any spam.

Both of these landed in the omfreebdy “phone” profile — see below.

2026-05-04 — omfreebdy phone profile

Codified all the night’s quirks as OMFREEBDY_PROFILE=phone in the omfreebdy repo (ab825a8):

  • iso/packages/phone.txtllvm19, gmake, pkgconf, libXft, libXinerama, libinput, freetype2, fontconfig. Pulled in alongside base.txt + wayland.txt.
  • phone_preflight() — replaces dangling /etc/resolv.conf, drops no-kmods.conf to disable the broken FreeBSD-ports-kmods repo. Idempotent.
  • install_packages mise filter — strips mise from the package list at install time (chosen over editing base.txt so non-phone profiles aren’t affected).
  • phone_build_native() — creates /usr/lib/{libgcc,libc_nonshared}.a stubs, fetches and builds svkbd-mobile-intl (with all the sed patches), fetches and builds lisgd (with prctl and CLOCK_MONOTONIC_RAW patches). All steps skip cleanly if the binary already exists.
  • enable_services overrides for phonepf_enable=NO (ue0 isn’t in egress), powerd_enable=NO (CRU filter incompatibility, see below).

Next migration (to a fresh PinePhone Pro from baseline) is now OMFREEBDY_PROFILE=phone ./install.sh. None of tonight’s papercuts re-bite.

2026-05-04 — net state for the next session

Reboot loop on eMMC is stable with powerd off:

  • uname -a → kernel #145 from a69a60918d… (post-rk_power_domain-disable kernel)
  • uptime shows clean 1-minute boot to multi-user
  • dev.rk_tsadc.0.cpu_temp: 36666 (idle, trim applied)
  • ssh, getty on serial, all userland services — all work
  • WiFi firmware still doesn’t load post-eMMC-flash (fn2 I/O ready timeout, same blob bytes that worked yesterday on SD); separate investigation
  • The CRU filter’s coarseness is now the gating issue for re-enabling powerd / panfrost cpufreq — design plan in project_cru_granular_whitelist_plan (next session)

Forensic artifacts retained:

  • forensics/sd-2026-05-03-stuck.img.gz — 32 GB compressed dump of the SD that wouldn’t boot from any kernel after the warm reboot.
  • forensics/emmc-pmos-2026-05-03.img — 123 GB raw dump of the postmarketOS install before we wrote FreeBSD over it.
2026-05-04 afternoon — WiFi back, CRU per-PLL whitelist, powerd on

Three follow-ups in the afternoon pulled the open list down by three.

WiFi: fn2 enable timeout — 4f93cc4 bwfm: extend fn2-enable timeout to 5 s, log mbdata on timeout

The fn2 I/O ready timeout, IOR=0x02 message was the visible failure since the eMMC migration. The morning’s hypothesis ranking blamed the chip (sticky pmOS firmware state, missing clk32k, NVRAM mismatch). A debug.bwfm_dump_state=1 dump told a different story:

state[sysctl]: clk=AVAIL fn2=0 init=0 sr=0 tx_seq=0 tx_max=0 credits=0
  txq=0 last_int=0x00800000 mb=0x00040002 host=0x00040002

Decoding mb and host (both reads of SDPCMD_TOHOSTMAILBOXDATA):

  • bit 1 (SDPCMD_TOHOSTMAILBOXDATA_DEVREADY) = 1 → firmware booted and announced ready
  • upper 16 bits = 0x0004 → SDPCM protocol version 4 (matches our SDPCM_PROT_VERSION)

So the chip was alive and on protocol — the failure was downstream. The loop in bwfm_sdio.c:2266 waits 500 ms for IOR.fn2 to come up after the host writes IOE |= 0x04. Linux’s brcmf_sdio_bus_init waits up to 5 s. On a cold boot from eMMC the chip’s fn2 fabric routinely takes >500 ms, so the host gives up, prints PROCEEDING anyway, and every later fn2 transaction times out. 4f93cc4 bwfm: extend fn2-enable timeout to 5 s, log mbdata on timeout raises the cap to 5 s and adds a device_printf of mbdata + intstatus on timeout so the next regression doesn’t need the sysctl tour. Post-fix:

bwfm_sdio0: CLM version: API: 12.2
wlan0: Ethernet address: 90:e8:68:72:f8:df
state[sysctl]: clk=AVAIL fn2=1 init=1 sr=0 tx_seq=37 tx_max=77 credits=40

wpa_supplicant associates to a WPA2 AP, DHCP returns 192.168.1.142, ping 1.1.1.1 is 24 ms. Sustained throughput is still gated by SDIO function-1 IRQ delivery — irqmode=irq-armed-poll-fallback, ~40 KB/s on a 5.7 MiB pull — but that’s a separate thread, tracked under component-bcm43455.

rk_cru per-PLL whitelist — 13e94af rk_cru: per-PLL whitelist for RK3399 PLL register window

Coarse addr < 0x100 → block retired in favor of per-PLL windows:

OffsetPLLAllow?
0x00-0x17LPLL (CRU) / PPLL (PMUCRU)yes
0x20-0x37BPLLyes
0x40-0x57DPLLNO
0x60-0x77CPLLNO
0x80-0x97GPLLyes
0xA0-0xB7NPLLyes
0xC0-0xD7VPLLNO

vop_lit_dclk_src muxes between VPLL and CPLL, so both stay frozen; DPLL is DRAM, never touch. Everything else is writable.

Verified live on kernel #150:

$ sysctl dev.cpu.0.freq_levels
dev.cpu.0.freq_levels: 1416/-1 1200/-1 1008/-1 816/-1 600/-1 408/-1
$ sudo sysctl dev.cpu.0.freq=1416
dev.cpu.0.freq: 600 -> 1416
$ sudo service powerd onestart
$ sysctl dev.cpu.0.freq dev.cpu.4.freq
dev.cpu.0.freq: 1008
dev.cpu.4.freq: 1008

No BLOCKED write lines after the freq change. Both clusters scale together under load. Init-time CPLL writes at 0x60-0x6c (from rk3399_cru’s clock-graph init for display rates) still log as blocked during boot, which is intentional.

powerd back on by default

overlay/etc/rc.conf now ships powerd_enable="YES" and powerd_flags="-a adaptive". Phone profile in the omfreebdy repo will follow on the next install pass. Detail in component-rk-cru.

2026-05-04 evening — sway+GPU env unblocked, panel still black

The eMMC migration left sway unable to launch with WLR_BACKENDS=drm,libinput. wlroots’ first call into Mesa, eglQueryDevicesEXT(0, NULL, &num_devices), returned EGL_BAD_PARAMETER: "Missing num_devices pointer". Pixman fallback worked but the screenshot was a 720x1440 black PNG.

Tracing the EGL failure

Tried env permutations: MESA_LOADER_DRIVER_OVERRIDE=panfrost, WLR_DRM_DEVICES=/dev/dri/card0, EGL_PLATFORM=gbm, LD_PRELOAD=libEGL_mesa.so.0. None changed the error. Set MESA_LOADER_DEBUG=1 and the loader-side reported “MESA-LOADER: failed to retrieve device information” — Mesa was receiving 0 devices from libdrm.

Wrote a tiny test program:

#include <xf86drm.h>
int main() {
    drmDevicePtr devs[16];
    int n = drmGetDevices2(0, devs, 16);
    printf("drmGetDevices2 returned %d\n", n);
    return 0;
}

drmGetDevices2 returned 0. So the kernel-side hw.dri.N.busid / hw.dri.N.name sysctls (already set up by drm_sysctl_init() in our overlay) were correct, but libdrm wasn’t reading them.

The libdrm patch was already in tree, just not deployed

ports/graphics/libdrm/files/patch-xf86drm.c (pinned in b3c2366 DRM sysctl + libdrm platform device support ) has three hunks:

  • drmParseSubsystemType — return DRM_BUS_PLATFORM if busid begins with platform:
  • drmParseOFBusInfo — copy the part after platform: into the device’s fullname
  • drmParseOFDeviceInfo — read hw.dri.N.name, strip trailing hex ("rockchip 0x7a""rockchip"), use as compatible string

Honor’s poudriere had a built aarch64-default pkg from 2026-04-06 that contained only the first two hunks — the third (the hw.dri.%d.name lookup) was missing from the deployed binary. Confirmed:

$ strings /usr/local/poudriere/data/packages/aarch64-default/.latest/All/libdrm-2.4.131,1.pkg | grep hw.dri
hw.dri.%d.modesetting
hw.dri.%d.busid
# (no hw.dri.%d.name — third hunk missing)

Honor also has a manual cross-compile setup at /tmp/libdrm-build/ (meson + clang -target aarch64-unknown-freebsd15.0 against the poudriere jail’s sysroot). The binary it produces does have all three hunks. So the source tree was current, the deployed pkg was just stale.

Repackaging the aarch64 pkg without poudriere bulk

Honor doesn’t have qemu-user-static set up, so poudriere bulk -j aarch64 graphics/libdrm fails. Workaround:

# On honor
cd /tmp && rm -rf libdrm-aarch64-pkg && mkdir libdrm-aarch64-pkg && cd libdrm-aarch64-pkg
tar xf /usr/local/poudriere/data/packages/aarch64-default/.latest/All/libdrm-2.4.131,1.pkg
cp /tmp/libdrm-build/libdrm-2.4.131/build/libdrm.so.2.131.0 usr/local/lib/
strip usr/local/lib/libdrm.so.2.131.0
pkg create -M +MANIFEST -r . -o /tmp/
sudo cp /tmp/libdrm-2.4.131,1.pkg /usr/local/poudriere/data/packages/aarch64-default/.latest/All/

# Pipe to phone (file-based; pkg install would name-resolve and pull
# the OLD pkg from FreeBSD-ports)
cat .../libdrm-2.4.131,1.pkg | ssh pinephone 'cat > /tmp/libdrm.pkg && sudo pkg add -f /tmp/libdrm.pkg'

Post-deploy:

$ strings /usr/local/lib/libdrm.so.2 | grep -E 'hw\.dri|platform:'
hw.dri.%d.busid
platform:
hw.dri.%d.name
hw.dri.%d.modesetting

$ /tmp/drm_test
drmGetDevices2 returned 2
dev 0: bustype=2 fullname=rk_drm0
  node[0]: /dev/dri/card0
  node[2]: /dev/dri/renderD128
dev 1: bustype=2 fullname=panfrost0
  node[0]: /dev/dri/card1

Sway launches but the panel stays black

With Mesa happy, sway launches with full GPU env, no EGL errors, swaybg + swaybar + sway running, DSI-1 active per swaymsg -t get_outputs at 720x1440 @60 Hz. Plane atomic_update fires with correct dst/src rects (verified by temporary device_printf instrumentation in rk_plane.c):

rk_vop0: plane_atomic_update: dst=(0,0)-(720,1440) 720x1440 src=(0,0)-(720,1440) 720x1440
rk_vop0: plane_atomic_update: writing YRGB_MST id=0 paddr=0xca800000 pitch=2880

VOP registers post-commit are programmed correctly:

WIN0_CTRL0    = 0x3a0000a1    (EN bit set)
WIN0_VIR      = 0x000002d0    (pitch = 720 words ✓)
WIN0_YRGB_MST = 0xca800000    (sway's framebuffer ✓)
WIN0_ACT_INFO = 0x059f02cf    (720x1440 ✓)
WIN0_DSP_INFO = 0x059f02cf    (720x1440 ✓)
WIN0_DSP_ST   = 0x000e0056    (display start position ✓)

But the screen remains black. Two specific signals say VOP isn’t actively scanning:

  • VOP_STATUS @0x2a4 changes only once in 50,000 reads — the line counter isn’t advancing at frame rate
  • DSI PHY_STATUS @0xb0 = 0x1529 — all four data lanes in stopstate, no high-speed video transmission

And every sway-allocated GBM buffer reads as zeros from /dev/mem:

$ /tmp/fb_read 0xca800000
addr=0xca800000 nonzero=0 (0/16384 words)

Three live hypotheses:

  1. VOP DCLK is gated. Some interaction with the new CRU per-PLL whitelist, or a clk_disable somewhere in the atomic helpers, leaves VOP without a pixel clock. Without DCLK, VOP stops scanning, DSI lanes drop to stopstate.
  2. dma_buf cache coherency. Panfrost renders to GBM buffers via CPU mappings; data lives in CPU caches. VOP DMA reads from physical DRAM and gets zeros. Need WC mappings or explicit cpu_dcache_wbinv_range on PRIME export.
  3. DSI panel state. Some sway commit path reset HX8394 without re-running its init sequence; panel is in low-power / display-off state.

Diagnostic plan for next session:

  1. clk_get_freq on dclk_vop1 before/after sway commits — distinguishes (1) from (2/3).
  2. Kill sway, manually reset VOP MST to U-Boot’s framebuffer address, see whether the kernel boot output reappears — distinguishes (3) from (1/2).
  3. Add explicit cache flush on PRIME-imported buffer attach, see whether sway content lands on screen — confirms (2).
2026-05-04 late — scanout import falsified, modeset wedge confirmed

The first continuation test was a diagnostic kernel from d7bd9b5 drm: instrument panfrost scanout imports . It added Panfrost allocation counters and a Rockchip PRIME-import log that reports when an imported dma-buf is not physically contiguous.

On kernel #153, Sway came up with the GPU path active and the diagnostic counters were clean:

dev.panfrost.0.counters:
  gem_alloc_contig=18
  gem_alloc_scattered=0
  gem_alloc_contig_fail=0

There were no rockchip_gem_prime_import_sg_table: non-contiguous PRIME import lines in dmesg. That falsifies the neat theory that Panfrost was exporting scattered pages while VOP scanned only the first physical page. The imported scanout buffers are contiguous on this boot.

The phone then reproduced the known DRM atomic wedge. The screen showed the kernel console and the familiar cascade:

WARNING !list_empty(&lock->head) failed at .../drm_modeset_lock.c:268
WARNING drm_modeset_is_locked(&crtc->mutex) failed at .../drm_atomic_helper.c:617
WARNING drm_modeset_is_locked(&dev->mode_config.connection_mutex) failed at .../drm_atomic_helper.c:667
WARNING drm_modeset_is_locked(&plane->mutex) failed at .../drm_atomic_helper.c:892
[drm] ERROR [CRTC:33:crtc-0] hw_done timed out
[drm] ERROR [CRTC:33:crtc-0] flip_done timed out
[drm] ERROR [CONNECTOR:35:DSI-1] hw_done timed out
[drm] ERROR [PLANE:31:plane-0] hw_done timed out

That moves the next fix back to the atomic commit path, not the GBM import path. a830a08 rk_vop: restore atomic vblank wait makes the local overlay match two pieces of the cross-driver audit: rk_drm_atomic_commit_tail() waits for vblanks before cleanup_planes, and rk_vop_intr() clears only the interrupt status bits it actually observed instead of writing ~0 to INTR_CLEAR0.

The driver-side event latch described in the audit is still open; the site previously implied it was already in tree, which was wrong.

2026-05-04 late — Sway autostart isolated

Kernel #154 from a830a08 rk_vop: restore atomic vblank wait deployed cleanly to the phone:

phone  472f3624035a2a56c2bf3c15dba3c26ebfe63fc4f930856a6a0a1f7582c128f8  pinephone:/boot/kernel/kernel

After boot, Sway still blanked the panel, but the failure shape changed from the photographed atomic wedge. There were no hw_done timed out or flip_done timed out messages. Instead, Sway was alive, asleep in select, holding /dev/dri/card0 and /dev/dri/renderD128, with an IPC socket under /var/run/xdg/jadams/, while the panel stayed black.

The serial console proved the machine was not hard-wedged. Killing the Sway session restored visible console output, so the immediate usability problem was the ttyv1 autostart path:

# /home/jadams/.profile on the phone
if [ ! -e "$HOME/.disable-autosway" ] && [ -z "$WAYLAND_DISPLAY" ] && [ "$(tty)" = "/dev/ttyv1" ]; then
    exec ~/bin/start-sway
fi

For now /home/jadams/.disable-autosway exists on the phone, so rebooting lands at a serial/video console instead of relaunching Sway into a black screen. Remove that file, or run ~/bin/start-sway manually, only when testing the compositor path.

The remaining kernel-side signal on a console-only boot is:

WARNING cur_vblank != vblank->last failed at /usr/src/sys/dev/drm/core/drm_vblank.c:348

That keeps the next display work focused on VOP vblank/event accounting rather than PRIME-import contiguity.

2026-05-05 early — eMMC Sway userland drift repaired

Kernel #157 from aced94d rk_vop: use crtc drm device for event lock fixed the VOP event-lock panic and brought Sway back on the eMMC install: DSI-1 active at 720×1440, Panfrost scanout buffers contiguous, no hw_done / flip_done timeout cascade, and VOP latch counters idle. The remaining breakage was userland drift from the old SD image.

The old SD card had ~/.local/bin/start-lisgd-sway.sh as a symlink to the restartable start-lisgd-sway wrapper. The eMMC overlay only had the older .sh script, hard-coded /dev/input/event2, discarded logs, and called /home/jadams/omfreebdy/bin/omfreebdy-menu, which does not exist on the eMMC install. The overlay now carries both names: start-lisgd-sway.sh forwards to start-lisgd-sway, and the real launcher repairs SWAYSOCK, XDG_RUNTIME_DIR, and WAYLAND_DISPLAY from /tmp/runtime-jadams/sway-ipc.*.sock when restarted over SSH.

Two more “already solved” phone UI details were restored:

  • 7baa2cc ports: add fuzzel 1.14.1 port (fixes touch tap coordinate scaling bug #686) was the fuzzel tap fix. The eMMC image had fuzzel 1.13.1; the old solved state needed fuzzel 1.14.1 for the touch tap coordinate scaling fix. Built 1.14.1 on the phone from the verified Codeberg distfile and installed /usr/local/bin/fuzzel (SHA256 = 7e45a0957fe34f27c4ba3e59bc90f28521c489ee843e5d98a6819cdb01403573). scripts/build-fuzzel-touch.sh records the no-network build path so the omfreebdy phone profile can call it instead of rediscovering the version pin.
  • e6f5455 overlay: use solid color swaybg (gdk-pixbuf2 broken on aarch64) pointed at the right layer but the final cause was sharper: image-backed swaybg -i current-background failed because the eMMC image had gdk-pixbuf JPEG/PNG loaders installed but no /usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache. Regenerating that cache made the same JPEG work. theme-set and post-theme-set now use image-backed swaybg when the cache exists and fall back to swaybg -c "$background" -m solid_color only when it does not.

Theme switching also needed explicit post-reload process repair. Sway reload does not reliably preserve a visible bar, and exec_always lisgd restarts race with the old daemon while layer-shell clients churn. The phone hook now daemonizes swaybg, replaces swaybar -b bar-0, and restarts start-lisgd-sway after every theme change. theme-set also registers that hook as an EXIT / signal trap as soon as the requested theme is validated, so killing theme-set halfway through no longer leaves the phone without gestures, bar, or background. Verified live by sending TERM after the GTK phase:

swaybg -i /home/jadams/.config/omfreebdy/current-background -m fill
swaybar -b bar-0
lisgd -d /dev/input/event2 -w 720 -h 1440 -m 400 ...
/bin/sh /home/jadams/.local/bin/swaybar-status

The executable diff against the old SD userland was small: eMMC was missing only phone-system-menu.sh and start-lisgd-hyprland.sh from the old-SD ~/.local/bin set. Both are back in the overlay and deployed live.

May 5 follow-up: the apparent “missing apps” in fuzzel was not a package install failure. nautilus-47.6_1 was installed and /usr/local/share/applications/org.gnome.Nautilus.desktop existed; fuzzel was just cramped enough that the app list looked truncated, and the phone launch environment did not consistently carry FreeBSD’s XDG data paths. The overlay now exports XDG_DATA_DIRS=$HOME/.local/share:/usr/local/share:/usr/share from ~/.profile, ~/bin/start-sway, Sway’s $menu, and the lisgd-spawned app launcher paths. The fuzzel phone geometry is now width=34, lines=12, font size=14, and fields=name,generic,comment,categories,filename,keywords, so “Files” is visible and nautilus can match via org.gnome.Nautilus.desktop while still leaving side gutters for edge gestures.

One more sharp edge: the failed theme switch was not a DRM regression. theme-set and post-theme-set were calling bare daemon; when the phone PATH omitted /usr/sbin, the repair hook printed daemon: not found and never restarted swaybg, swaybar, or lisgd. The hook now prepends /usr/local/sbin:/usr/sbin:/sbin, stores DAEMON=/usr/sbin/daemon, and theme-set uses /usr/sbin/daemon directly for swaybg.

Final May 5 cleanup: full-width fuzzel blocked the right-edge close gesture, so the phone geometry was backed off from width=42 to width=34. lisgd also exposed one more parser trap: the restart wrapper was embedding DBUS_SESSION_BUS_ADDRESS=unix:path=...,guid=... inside each -g action string. lisgd uses commas as field separators, so the dbus address split the action and /bin/sh reported Syntax error: end of file unexpected (expecting "}"). The wrapper now lets lisgd inherit the dbus address at process level and only passes comma-free Wayland/Sway/XDG variables inside the action strings.

Open work after this session

  1. Sway phone userland packaging — the live eMMC install is repaired, but omfreebdy’s phone profile needs to absorb fuzzel 1.14.1, the restartable lisgd launcher, the daemonized post-theme hook, XDG app-data exports, and the dbus-safe lisgd gesture strings so a fresh install gets the same behavior.
  2. rk_spi CTRLR1 refactor — pre-existing JEDEC RDID skew on the boot SPI flash; tried bef5863 rk_spi: write CTRLR1 frame count before enable_chip , harmless but ineffective; reverted in c593dc3 Revert "rk_spi: write CTRLR1 frame count before enable_chip" .
  3. SDIO function-1 IRQ delivery — bwfm runs entirely on the 100 Hz poll fallback. ~40 KB/s ceiling. Bench predicate is dev.rockchip_dwmmc.0.sdio_intrs > 0. Separate from today’s fn2 fix.
  4. rk_power_domain framework — replace the sysctl-only first cut with pwr_domain_if.m + simplebus property parsing so peer drivers can request domains via DT.
  5. VOP event/vblank parity — the driver-side event latch from aced94d rk_vop: use crtc drm device for event lock stops the modeset wedge, but kernel #157 still trips cur_vblank != vblank->last once during Sway startup.
  6. renderD128 wrong device assignment — libdrm’s id -= 128 for renderD128 maps to hw.dri.0 (rk_drm0), not panfrost0. Cosmetic for sway but breaks available_nodes enumeration. Open.