From 2572a14c61c8389712c7a6cc2aba1c93552f1e8f Mon Sep 17 00:00:00 2001 From: Padreug Date: Fri, 25 Sep 2026 22:09:36 +0200 Subject: [PATCH] =?UTF-8?q?fix(rpi4):=20enable=20pcscd=20=E2=80=94=20witho?= =?UTF-8?q?ut=20it=20the=20kiosk=20hangs=20before=20drawing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the white screen. Not graphics, not the bundle, not pairing. The app constructs @pokusew/pcsclite at startup. That calls SCardEstablishContext(), which calls SCardCheckDaemonAvailability(), which — finding no pcscd — BUSY-LOOPS in fstatat64 at ~92% CPU rather than returning an error. It runs on Electron's main thread before the BrowserWindow is created, so no window is ever made and the panel stays white. It is a hang, not a crash, which is why it presented so badly. Nothing throws. Nothing is logged after "[StateStore] Initialized database". The process looks healthy: systemd reports the service active, Electron is running, and there is even a gpu-process. But a setInterval registered before startup never fires once in 32 seconds, the main thread sits in state R, and the remote debugger reports zero page targets. V8's own tooling cannot see it either, because the thread never yields to the inspector: Debugger.pause returns nothing and Profiler.stop times out. A native backtrace was the only thing that worked: #0 fstatat64 libc #1 SCardCheckDaemonAvailability libpcsclite #2 SCardEstablishContext libpcsclite #3 PCSCLite::PCSCLite() pcsclite.node #4 PCSCLite::New(...) Ruled out along the way, each by direct test on the machine: graphics (it fails identically with the GPU fully disabled), Electron on aarch64 (a minimal app renders fine), the Vue bundle (loading the real index.html from a minimal main process mounts the app and reaches "[ATM] State machine initialized"), the preload script, the CSP, kiosk and fullscreen window options, better-sqlite3, /dev/shm, memory, page size, X authorisation, and isDev. upboard.nix and batm3.nix both enable pcscd for their real readers, which is why no x86 machine has ever hit this. This module did not, and that was the entire difference. pcscd with no reader attached simply idles, so enabling it costs nothing. Worth noting for the wider fleet: any future board that omits pcscd inherits this, and it presents as a blank screen with a healthy-looking service. The robust fix is for the app to not block its main thread on a card-reader handshake at all — the NFC path is already documented as best-effort — but that is an app change and this unblocks the hardware. --- deploy/nixos/hardware/raspberry-pi-4.nix | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/deploy/nixos/hardware/raspberry-pi-4.nix b/deploy/nixos/hardware/raspberry-pi-4.nix index f32bf69..5b136dc 100644 --- a/deploy/nixos/hardware/raspberry-pi-4.nix +++ b/deploy/nixos/hardware/raspberry-pi-4.nix @@ -122,6 +122,41 @@ hardware.enableRedistributableFirmware = true; + # pcscd MUST be enabled, and not because this board has a card reader. + # + # The app constructs @pokusew/pcsclite at startup. That calls + # SCardEstablishContext(), which calls SCardCheckDaemonAvailability(), which + # — when there is no pcscd to find — BUSY-LOOPS in fstatat64 at ~92% CPU + # instead of returning an error. It runs on Electron's main thread, before + # the BrowserWindow is created, so the window never appears and the panel + # stays white forever. Nothing is logged, nothing throws, and V8's own + # inspector cannot be serviced because the thread never yields: CDP + # Debugger.pause and Profiler.stop both hang. It took a native gdb backtrace + # to see it at all: + # + # #0 fstatat64 libc + # #1 SCardCheckDaemonAvailability libpcsclite + # #2 SCardEstablishContext libpcsclite + # #3 PCSCLite::PCSCLite() pcsclite.node + # + # The x86 machines never hit this because upboard.nix and batm3.nix both + # enable pcscd for their actual readers. This module did not, which is the + # entire difference. A running pcscd with no reader attached just idles, so + # this is cheap insurance rather than a claim about the hardware. + services.pcscd.enable = true; + + # pcscd gates client access via polkit; without a rule the `bitspire` service + # user is "Rejected unauthorized PC/SC client". Same wiring as upboard.nix. + security.polkit.extraConfig = '' + polkit.addRule(function(action, subject) { + if ((action.id == "org.debian.pcsc-lite.access_pcsc" || + action.id == "org.debian.pcsc-lite.access_card") && + subject.user == "bitspire") { + return polkit.Result.YES; + } + }); + ''; + # Kiosk: never sleep. systemd.targets = { sleep.enable = false;