diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 77076a3510..ece4bdd847 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -28,6 +28,7 @@ jobs: PASS: ${{ secrets.MARFRIT_REPO_PASSPHRASE }} run: | set -e + gpgconf --homedir /root/.gnupg --kill all 2>/dev/null || true rm -rf /root/.gnupg /root/repo_pass mkdir -m700 -p /root/.gnupg printf '%s' "$PASS" > /root/repo_pass @@ -193,6 +194,7 @@ jobs: PASS: ${{ secrets.MARFRIT_REPO_PASSPHRASE }} run: | set -e + gpgconf --homedir /root/.gnupg --kill all 2>/dev/null || true rm -rf /root/.gnupg /root/repo_pass mkdir -m700 -p /root/.gnupg printf '%s' "$PASS" > /root/repo_pass @@ -293,6 +295,7 @@ jobs: PASS: ${{ secrets.MARFRIT_REPO_PASSPHRASE }} run: | set -e + gpgconf --homedir /root/.gnupg --kill all 2>/dev/null || true rm -rf /root/.gnupg /root/repo_pass mkdir -m700 -p /root/.gnupg printf '%s' "$PASS" > /root/repo_pass @@ -371,6 +374,368 @@ jobs: if: always() run: rm -f /root/repo_pass /root/.ssh/id_ed25519 + # ------------------------------------------------------------------------- + # ffmpeg-v4l2-request-fourier (aarch64 only) — FFmpeg with V4L2 Request API + # hwaccel for Rockchip / Allwinner stateless decoders. Used by the Fourier + # umbrella (ohm, fresnel, ampere). Long-running build, so failures don't + # block downstream debian jobs. + # ------------------------------------------------------------------------- + ffmpeg-v4l2-request-aarch64: + needs: claude-his-any + runs-on: arch-aarch64 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: bootstrap runner (idempotent) + run: | + set -e + retry() { for i in 1 2 3; do "$@" && return 0; rc=$?; echo "retry $i (exit=$rc)" >&2; sleep $((i*5)); done; return 1; } + retry pacman -Syu --noconfirm --needed base-devel git rsync gnupg openssh sudo nasm + + - name: import signing key + env: + PRIV: ${{ secrets.MARFRIT_REPO_PRIVATE_KEY }} + PASS: ${{ secrets.MARFRIT_REPO_PASSPHRASE }} + run: | + set -e + gpgconf --homedir /root/.gnupg --kill all 2>/dev/null || true + rm -rf /root/.gnupg /root/repo_pass + mkdir -m700 -p /root/.gnupg + printf '%s' "$PASS" > /root/repo_pass + chmod 600 /root/repo_pass + printf '%s\n' "$PRIV" | gpg --batch --import + echo "92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C:6:" | gpg --import-ownertrust + + - name: install deploy ssh key + env: + KEY: ${{ secrets.MARFRIT_REPO_DEPLOY_KEY }} + run: | + mkdir -m700 -p /root/.ssh + printf '%s\n' "$KEY" > /root/.ssh/id_ed25519 + chmod 600 /root/.ssh/id_ed25519 + ssh-keyscan -t ed25519 nc.reauktion.de > /root/.ssh/known_hosts 2>/dev/null + + - name: makepkg ffmpeg-v4l2-request-fourier + run: | + set -e + rm -rf /tmp/build-ffmpeg-v4l2 + cp -r arch/ffmpeg-v4l2-request-fourier /tmp/build-ffmpeg-v4l2 + chown -R builder:builder /tmp/build-ffmpeg-v4l2 + cd /tmp/build-ffmpeg-v4l2 + # Parallelise wide; distcc-avahi distributes compiles across the + # zeroconf pool (tesla, dcc1, dcc2, boltzmann ≈ 60 logical cores). + sudo -u builder -H env MAKEFLAGS="-j60" \ + makepkg --nocheck --noconfirm --syncdeps --cleanbuild + ls -la *.pkg.tar.* | grep -v "\.sig$" + + - name: sign ffmpeg-v4l2-request-fourier + run: | + set -e + cd /tmp/build-ffmpeg-v4l2 + for f in *.pkg.tar.xz *.pkg.tar.zst *.pkg.tar.gz; do + [ -f "$f" ] || continue + gpg --batch --pinentry-mode loopback --passphrase-file /root/repo_pass \ + --detach-sign --yes -u 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C "$f" + done + + - name: update aarch64 repo db + run: | + set -e + mkdir -p /tmp/arch-stage-ffmpeg + cd /tmp/arch-stage-ffmpeg + rm -f * + for f in marfrit.db.tar.gz marfrit.db.tar.gz.sig marfrit.files.tar.gz marfrit.files.tar.gz.sig; do + curl -sSLf "https://packages.reauktion.de/arch/aarch64/$f" -o "$f" || rm -f "$f" + done + for ext in xz zst gz; do + ls /tmp/build-ffmpeg-v4l2/*.pkg.tar.$ext 2>/dev/null && \ + mv /tmp/build-ffmpeg-v4l2/*.pkg.tar.$ext /tmp/build-ffmpeg-v4l2/*.pkg.tar.$ext.sig . + done || true + export GNUPGHOME=/root/.gnupg + printf 'pinentry-mode loopback\npassphrase-file /root/repo_pass\n' > /root/.gnupg/gpg.conf + printf 'allow-loopback-pinentry\n' > /root/.gnupg/gpg-agent.conf + gpg-connect-agent reloadagent /bye + pkgs=() + for ext in xz zst gz; do + for f in *.pkg.tar.$ext; do [ -f "$f" ] && pkgs+=("$f"); done + done + if [ -f marfrit.db.tar.gz ]; then + for f in "${pkgs[@]}"; do + name=$(echo "$f" | sed -E 's/-[0-9].*//') + repo-remove --sign --key 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C \ + marfrit.db.tar.gz "$name" 2>/dev/null || true + done + fi + repo-add --new --sign --key 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C \ + --verify marfrit.db.tar.gz "${pkgs[@]}" + ln -sf marfrit.db.tar.gz marfrit.db + ln -sf marfrit.files.tar.gz marfrit.files + ln -sf marfrit.db.tar.gz.sig marfrit.db.sig + rm -f marfrit.files.sig + + - name: publish to aarch64 + run: | + set -e + retry() { for i in 1 2 3; do "$@" && return 0; rc=$?; echo "retry $i (exit=$rc)" >&2; sleep $((i*5)); done; return 1; } + cd /tmp/arch-stage-ffmpeg + retry rsync -avL --copy-unsafe-links \ + -e 'ssh -i /root/.ssh/id_ed25519' \ + ./ mfritsche@nc.reauktion.de:arch/aarch64/ + + - name: wipe secrets + if: always() + run: rm -f /root/repo_pass /root/.ssh/id_ed25519 + + # ------------------------------------------------------------------------- + # libva-v4l2-request-fourier (aarch64 only) — VA-API V4L2-stateless backend, + # multiplanar fork. Successor to libva-v4l2-request-ohm-gl-fix (never + # published). Tracks the campaign fork's git tip directly; pinned commit + # is the libva-multiplanar iter8 close. + # ------------------------------------------------------------------------- + libva-v4l2-request-fourier-aarch64: + needs: ffmpeg-v4l2-request-aarch64 + runs-on: arch-aarch64 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: bootstrap runner (idempotent) + run: | + set -e + retry() { for i in 1 2 3; do "$@" && return 0; rc=$?; echo "retry $i (exit=$rc)" >&2; sleep $((i*5)); done; return 1; } + retry pacman -Syu --noconfirm --needed base-devel git rsync gnupg openssh sudo + + - name: import signing key + env: + PRIV: ${{ secrets.MARFRIT_REPO_PRIVATE_KEY }} + PASS: ${{ secrets.MARFRIT_REPO_PASSPHRASE }} + run: | + set -e + gpgconf --homedir /root/.gnupg --kill all 2>/dev/null || true + rm -rf /root/.gnupg /root/repo_pass + mkdir -m700 -p /root/.gnupg + printf '%s' "$PASS" > /root/repo_pass + chmod 600 /root/repo_pass + printf '%s\n' "$PRIV" | gpg --batch --import + echo "92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C:6:" | gpg --import-ownertrust + + - name: install deploy ssh key + env: + KEY: ${{ secrets.MARFRIT_REPO_DEPLOY_KEY }} + run: | + mkdir -m700 -p /root/.ssh + printf '%s\n' "$KEY" > /root/.ssh/id_ed25519 + chmod 600 /root/.ssh/id_ed25519 + ssh-keyscan -t ed25519 nc.reauktion.de > /root/.ssh/known_hosts 2>/dev/null + + - name: makepkg libva-v4l2-request-fourier + run: | + set -e + rm -rf /tmp/build-libva-v4l2 + cp -r arch/libva-v4l2-request-fourier /tmp/build-libva-v4l2 + chown -R builder:builder /tmp/build-libva-v4l2 + cd /tmp/build-libva-v4l2 + sudo -u builder -H env MAKEFLAGS="-j60" \ + makepkg --nocheck --noconfirm --syncdeps --cleanbuild + ls -la *.pkg.tar.* | grep -v "\.sig$" + + - name: sign libva-v4l2-request-fourier + run: | + set -e + cd /tmp/build-libva-v4l2 + for f in *.pkg.tar.xz *.pkg.tar.zst *.pkg.tar.gz; do + [ -f "$f" ] || continue + gpg --batch --pinentry-mode loopback --passphrase-file /root/repo_pass \ + --detach-sign --yes -u 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C "$f" + done + + - name: update aarch64 repo db + run: | + set -e + mkdir -p /tmp/arch-stage-libva + cd /tmp/arch-stage-libva + rm -f * + for f in marfrit.db.tar.gz marfrit.db.tar.gz.sig marfrit.files.tar.gz marfrit.files.tar.gz.sig; do + curl -sSLf "https://packages.reauktion.de/arch/aarch64/$f" -o "$f" || rm -f "$f" + done + for ext in xz zst gz; do + ls /tmp/build-libva-v4l2/*.pkg.tar.$ext 2>/dev/null && \ + mv /tmp/build-libva-v4l2/*.pkg.tar.$ext /tmp/build-libva-v4l2/*.pkg.tar.$ext.sig . + done || true + export GNUPGHOME=/root/.gnupg + printf 'pinentry-mode loopback\npassphrase-file /root/repo_pass\n' > /root/.gnupg/gpg.conf + printf 'allow-loopback-pinentry\n' > /root/.gnupg/gpg-agent.conf + gpg-connect-agent reloadagent /bye + pkgs=() + for ext in xz zst gz; do + for f in *.pkg.tar.$ext; do [ -f "$f" ] && pkgs+=("$f"); done + done + if [ -f marfrit.db.tar.gz ]; then + for f in "${pkgs[@]}"; do + name=$(echo "$f" | sed -E 's/-[0-9].*//') + repo-remove --sign --key 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C \ + marfrit.db.tar.gz "$name" 2>/dev/null || true + done + fi + repo-add --new --sign --key 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C \ + --verify marfrit.db.tar.gz "${pkgs[@]}" + ln -sf marfrit.db.tar.gz marfrit.db + ln -sf marfrit.files.tar.gz marfrit.files + ln -sf marfrit.db.tar.gz.sig marfrit.db.sig + rm -f marfrit.files.sig + + - name: publish to aarch64 + run: | + set -e + retry() { for i in 1 2 3; do "$@" && return 0; rc=$?; echo "retry $i (exit=$rc)" >&2; sleep $((i*5)); done; return 1; } + cd /tmp/arch-stage-libva + retry rsync -avL --copy-unsafe-links \ + -e 'ssh -i /root/.ssh/id_ed25519' \ + ./ mfritsche@nc.reauktion.de:arch/aarch64/ + + - name: wipe secrets + if: always() + run: rm -f /root/repo_pass /root/.ssh/id_ed25519 + + # ------------------------------------------------------------------------- + # mpv-fourier (aarch64 only) — mpv with fourier-umbrella patches. + # Patch slot exists for the vo_dmabuf_wayland plane-semantics fix per + # marfrit/dmabuf-modifier-triage#1. Initial scaffold builds vanilla + # mpv 0.41.0 — bump pkgrel and add patch to source=() when iter1 lands. + # ------------------------------------------------------------------------- + mpv-fourier-aarch64: + needs: libva-v4l2-request-fourier-aarch64 + runs-on: arch-aarch64 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: bootstrap runner (idempotent) + run: | + set -e + retry() { for i in 1 2 3; do "$@" && return 0; rc=$?; echo "retry $i (exit=$rc)" >&2; sleep $((i*5)); done; return 1; } + retry pacman -Syu --noconfirm --needed base-devel git rsync gnupg openssh sudo + + - name: import signing key + env: + PRIV: ${{ secrets.MARFRIT_REPO_PRIVATE_KEY }} + PASS: ${{ secrets.MARFRIT_REPO_PASSPHRASE }} + run: | + set -e + gpgconf --homedir /root/.gnupg --kill all 2>/dev/null || true + rm -rf /root/.gnupg /root/repo_pass + mkdir -m700 -p /root/.gnupg + printf '%s' "$PASS" > /root/repo_pass + chmod 600 /root/repo_pass + printf '%s\n' "$PRIV" | gpg --batch --import + echo "92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C:6:" | gpg --import-ownertrust + + - name: install deploy ssh key + env: + KEY: ${{ secrets.MARFRIT_REPO_DEPLOY_KEY }} + run: | + mkdir -m700 -p /root/.ssh + printf '%s\n' "$KEY" > /root/.ssh/id_ed25519 + chmod 600 /root/.ssh/id_ed25519 + ssh-keyscan -t ed25519 nc.reauktion.de > /root/.ssh/known_hosts 2>/dev/null + + - name: configure [marfrit] repo + pre-install ffmpeg-v4l2-request-fourier + run: | + set -e + # mpv-fourier links libavcodec at build time. If the build host pulls + # stock arch's ffmpeg, the resulting binary's libavcodec ABI version + # drifts from the marfrit ffmpeg-v4l2-request-fourier that consumer + # hosts (ohm) install. Force build-time consistency by configuring + # [marfrit] on fermi and pre-installing our ffmpeg before makepkg. + curl -sLo /tmp/marfrit.gpg https://packages.reauktion.de/marfrit.gpg + pacman-key --add /tmp/marfrit.gpg + pacman-key --lsign-key 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C + rm -f /tmp/marfrit.gpg + if ! grep -q '^\[marfrit\]' /etc/pacman.conf; then + printf '\n[marfrit]\nServer = https://packages.reauktion.de/arch/$arch\nSigLevel = Required\n' >> /etc/pacman.conf + fi + pacman -Sy --noconfirm + # Drop any cached ffmpeg-v4l2-request-fourier that may be corrupt + # from a prior interrupted build (the ccache misadventure left + # one such file behind). + rm -f /var/cache/pacman/pkg/ffmpeg-v4l2-request-fourier-*-aarch64.pkg.tar.* + # Stock arch ffmpeg may already be installed from a prior fermi job; + # pacman -S --noconfirm defaults the [y/N] conflict prompt to N. + # Use printf (finite stream, exits 0 cleanly) — `yes y | ...` would + # work but fails under bash pipefail when yes catches SIGPIPE. + # Three y's covers: "Remove conflict?", "Proceed?", any keyring prompt. + printf 'y\ny\ny\n' | pacman -S marfrit/ffmpeg-v4l2-request-fourier + + - name: makepkg mpv-fourier + run: | + set -e + rm -rf /tmp/build-mpv + cp -r arch/mpv-fourier /tmp/build-mpv + chown -R builder:builder /tmp/build-mpv + cd /tmp/build-mpv + sudo -u builder -H env MAKEFLAGS="-j60" \ + makepkg --nocheck --noconfirm --syncdeps --cleanbuild + ls -la *.pkg.tar.* | grep -v "\.sig$" + + - name: sign mpv-fourier + run: | + set -e + cd /tmp/build-mpv + for f in *.pkg.tar.xz *.pkg.tar.zst *.pkg.tar.gz; do + [ -f "$f" ] || continue + gpg --batch --pinentry-mode loopback --passphrase-file /root/repo_pass \ + --detach-sign --yes -u 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C "$f" + done + + - name: update aarch64 repo db + run: | + set -e + mkdir -p /tmp/arch-stage-mpv + cd /tmp/arch-stage-mpv + rm -f * + for f in marfrit.db.tar.gz marfrit.db.tar.gz.sig marfrit.files.tar.gz marfrit.files.tar.gz.sig; do + curl -sSLf "https://packages.reauktion.de/arch/aarch64/$f" -o "$f" || rm -f "$f" + done + for ext in xz zst gz; do + ls /tmp/build-mpv/*.pkg.tar.$ext 2>/dev/null && \ + mv /tmp/build-mpv/*.pkg.tar.$ext /tmp/build-mpv/*.pkg.tar.$ext.sig . + done || true + export GNUPGHOME=/root/.gnupg + printf 'pinentry-mode loopback\npassphrase-file /root/repo_pass\n' > /root/.gnupg/gpg.conf + printf 'allow-loopback-pinentry\n' > /root/.gnupg/gpg-agent.conf + gpg-connect-agent reloadagent /bye + pkgs=() + for ext in xz zst gz; do + for f in *.pkg.tar.$ext; do [ -f "$f" ] && pkgs+=("$f"); done + done + if [ -f marfrit.db.tar.gz ]; then + for f in "${pkgs[@]}"; do + name=$(echo "$f" | sed -E 's/-[0-9].*//') + repo-remove --sign --key 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C \ + marfrit.db.tar.gz "$name" 2>/dev/null || true + done + fi + repo-add --new --sign --key 92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C \ + --verify marfrit.db.tar.gz "${pkgs[@]}" + ln -sf marfrit.db.tar.gz marfrit.db + ln -sf marfrit.files.tar.gz marfrit.files + ln -sf marfrit.db.tar.gz.sig marfrit.db.sig + rm -f marfrit.files.sig + + - name: publish to aarch64 + run: | + set -e + retry() { for i in 1 2 3; do "$@" && return 0; rc=$?; echo "retry $i (exit=$rc)" >&2; sleep $((i*5)); done; return 1; } + cd /tmp/arch-stage-mpv + retry rsync -avL --copy-unsafe-links \ + -e 'ssh -i /root/.ssh/id_ed25519' \ + ./ mfritsche@nc.reauktion.de:arch/aarch64/ + + - name: wipe secrets + if: always() + run: rm -f /root/repo_pass /root/.ssh/id_ed25519 + # ------------------------------------------------------------------------- # claude-his-agent Debian (Architecture: all) — same dpkg-deb pattern as # lmcp-debian; publishes to bookworm + trixie via hertz reprepro. diff --git a/arch/chromium-fourier/KWIN_PIVOT.md b/arch/chromium-fourier/KWIN_PIVOT.md new file mode 100644 index 0000000000..55731a64c4 --- /dev/null +++ b/arch/chromium-fourier/KWIN_PIVOT.md @@ -0,0 +1,408 @@ +# KWin pivot — fix the chrome-on-KWin video stall + +> **2026-04-28 update part 3 — campaign closed end-to-end.** Three +> patches landed on ohm in sequence: qt6-base-fourier (GL_ALPHA → +> GL_R8), kwin-fourier (watchDmaBuf no-op), chromium-fourier patch +> 4/4 (V4L2 capture pool floor at 16). Each unsticks one layer. +> Together they produce smooth 1080p30 H.264 playback under KDE +> Plasma 6.6.4 Wayland on the box where stock chromium previously +> stalled in 3 seconds. Combined chrome CPU ~81 % steady, KWin ~9 %, +> zero GL_INVALID_VALUE in the journal during playback. Brave's +> YouTube on the same session feels markedly snappier independently +> — kwin-fourier is a general-purpose latency reduction for every +> wp_linux_dmabuf client on this hardware, not a chrome-specific +> fix. **The kernel-side architectural hole — vb2 / hantro / rga not +> populating `dma_resv` exclusive fences for V4L2 producers — is the +> right upstream-correct fix and the planned next move.** kwin-fourier +> in its current shape (blanket bypass) is a working *diagnostic +> instrument*; the upstream MR will be the kernel-side per-driver +> patch (3 commits: vb2 helper API, hantro opt-in, rga opt-in) plus +> a parallel KWin commit using `poll(POLLIN)` directly on the dmabuf +> fd instead of the `EXPORT_SYNC_FILE`+`QSocketNotifier` roundtrip. + +> **2026-04-28 update part 2 — qt6-base-fourier landed, validated, did +> not fix the chrome stall.** The Qt 6 GL_ALPHA bug (qopengltextureglyphcache.cpp, +> qrhigles2.cpp, qopengltextureuploader.cpp) is real, the patch is +> correct, the journal noise is gone — but the chromium-fourier 149-r4 +> playback under KWin still deadlocks at ~6 seconds (vs ~3 seconds +> pre-patch — so the GL_ALPHA churn was contributing some overhead, +> just not the primary cause). Vindication came from a clean weston +> A/B: same chrome v4 binary, same panfrost mesa, same V4L2 driver, +> same hardware → swapping KWin for weston turns the stall off. Chrome +> plays through under weston (with elevated CPU because weston falls +> back to LINEAR composite). KWin has a *second* bug, structurally +> deeper than the Qt one. **Phase 4 below — write/ship/upstream the Qt +> fix — was completed today.** This document now pivots again to the +> remaining KWin investigation. + +> **2026-04-28 update part 1 — Phase 2 collapsed onto Phase 1: not KWin +> for the GL_ALPHA part.** Source-grep nailed the offender on the +> first pass. Real culprit: Qt 6's `QOpenGLTextureGlyphCache` +> (`src/opengl/qopengltextureglyphcache.cpp:111-117`) and +> `QRhiGles2::toGlTextureFormat` (`src/gui/rhi/qrhigles2.cpp:1373-1378`). +> KWin's own GL paths use `GL_R8` correctly (`src/opengl/gltexture.cpp:61`, +> `src/scene/shadowitem.cpp:494`). The Qt-fourier patch series shipped +> as `marfrit-packages/arch/qt6-base-fourier/` and was validated on +> ohm — zero `GL_INVALID_VALUE` in a fresh-session journal. + +## Triangulation table after today's work + +| Path | Result | +|---|---| +| `ffmpeg -hwaccel v4l2request -f null` | ✓ 36 fps, clean | +| `mpv --vo=null --hwdec=v4l2request` | ✓ decode-only, clean | +| `mpv --vo=drm --hwdec=v4l2request` (KMS scanout, no compositor) | ✓ 0.7% drops in 19 s | +| **chrome v4 under weston** | **✓ plays through; ~96 % CPU** | +| chrome v4 under KWin (post-Qt-fix) | ✗ stall @ ~6 s, ⏸ icon, audio clock advances | +| `mpv --vo=gpu-next --hwdec=v4l2request` under KWin | ✗ ~76 % drops, slideshow | +| **chrome v4 under KWin pre-Qt-fix** | **✗ stall @ ~3 s** (GL_ALPHA spam adds load) | + +Decode + display hardware path is fully capable. Wayland *as a +protocol* is fine (weston works). The wall is **specifically KWin's +compositor scheduling and presentation pipeline on this stack** — +panfrost ES 3.2 + V4L2 stateless NV12 dmabuf clients. + +The chromium-fourier patch series is correct. The qt6-base-fourier +patch series is correct. The KWin bug is the third independent +problem on this hardware, exposed by the prior two fixes. + +## What we know and what we don't + +**Known:** +- The stall is *not* in chrome's V4L2VideoDecoder (works under weston). +- The stall is *not* in panfrost's dmabuf import (works under weston). +- The stall is *not* a GL error (no `GL_INVALID_VALUE` after qt6 fix). +- The stall is *not* a thread parked in `vb2`/`v4l2`/`dma_fence` wchan + (kwin/chrome/audio threads all sit in `futex_do_wait` / + `poll_schedule_timeout` / `unix_stream_read_generic`). +- The stall *does* idle the audio output socket (renderer audio + thread blocks reading from the audio service unix socket → audio + drains the last ALSA buffer, static, silence). +- The stall *does* leave the `