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.
181 lines
9.3 KiB
Nix
181 lines
9.3 KiB
Nix
# Raspberry Pi 4 hardware module (aarch64).
|
|
#
|
|
# The Pi 4 twin of raspberry-pi-5.nix. Kernel, firmware, bootloader and device
|
|
# tree come from the nixos-hardware `raspberry-pi-4` module (paired with this
|
|
# file in flake.nix's piBoards); here we set only the bitSpire-specific
|
|
# hardware glue: serial for the bill validators, the kiosk display driver, and
|
|
# no-suspend. The wiring notes in raspberry-pi-5.nix apply unchanged — same
|
|
# validators over USB-serial, same QR scanner, same touchscreen options.
|
|
#
|
|
# What differs from the Pi 5:
|
|
# - GPU/KMS: the Pi 5 module enables vc4/v3d modesetting by default; on the
|
|
# Pi 4 it is an opt-in (`fkms-3d`) that also injects the CMA + vc4 device
|
|
# tree overlays. Without it X falls back to the plain framebuffer and
|
|
# Electron renders in software.
|
|
# - Memory: 4 GB is the floor for Electron + the kiosk; 8 GB is comfortable.
|
|
# The shared Pi runtime's MemoryMax=2G leaves headroom on either.
|
|
# - No PCIe (the Pi 5's NVMe path); boot/root is SD or USB-SATA only.
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
{
|
|
# aarch64 target. (The flake instantiates this config with aarch64 pkgs; this
|
|
# line documents/asserts it.)
|
|
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
|
|
|
|
# Mainline kernel, NOT the Raspberry Pi vendor one.
|
|
#
|
|
# nixos-hardware's raspberry-pi/4 module mkDefaults boot.kernelPackages to
|
|
# the vendor kernel (linux-rpi, via common/kernel.nix). That kernel is in no
|
|
# binary cache — Hydra does not build nixos-hardware's overlays, and it is
|
|
# not in aiolabs.cachix.org either — so every Pi compiles a kernel from
|
|
# source, on an SD card, and recompiles on every bump. The first bring-up
|
|
# attempt spent hours on `CC [M] fs/overlayfs/inode.o` before anyone noticed
|
|
# what it was doing.
|
|
#
|
|
# Mainline aarch64 kernels are cached, and mainline demonstrably boots a
|
|
# Pi 4: it is what the stock NixOS aarch64 SD image runs. The vendor kernel's
|
|
# Pi-specific patches buy nothing this kiosk needs — display, USB serial and
|
|
# WiFi are all mainline, and vc4/v3d KMS has been mainline for years.
|
|
#
|
|
# Watch the graphics path when changing this. fkms-3d below is a
|
|
# nixos-hardware overlay built around the vendor kernel's firmware-KMS route;
|
|
# the mainline equivalent is full KMS (vc4-kms-v3d). If X ends up on the
|
|
# framebuffer with Electron rendering in software, that overlay is where to
|
|
# look — not the kernel choice, which is worth keeping either way.
|
|
boot.kernelPackages = pkgs.linuxPackages;
|
|
|
|
# Bootloader: the aarch64 sd-image uses the extlinux-compatible generator;
|
|
# nixos-hardware's rpi4 module wires the firmware/u-boot. No systemd-boot.
|
|
boot.loader.grub.enable = lib.mkDefault false;
|
|
boot.loader.generic-extlinux-compatible.enable = lib.mkDefault true;
|
|
|
|
# Primary UART (GPIO 14/15) available for a GPIO-wired validator. Keep the
|
|
# serial console OFF it so the validator owns the line — mirrors upboard.nix
|
|
# keeping ttyS4 free for the dispenser. USB-serial adapters are unaffected.
|
|
#
|
|
# Not mkDefault: kernelParams is list-merged, and only definitions at the
|
|
# highest priority survive. nixpkgs sets loglevel/lsm at normal priority, so
|
|
# a mkDefault list here is dropped entirely — and with no console= at all
|
|
# the kernel falls back to the device tree's stdout-path, i.e. this UART.
|
|
# cma=256M: the vc4 display pipeline allocates its framebuffer from the
|
|
# contiguous memory area, and the default reservation on this board is 32MiB
|
|
# with about 11MiB free. A 3840x1080 framebuffer is ~16.6MB before double
|
|
# buffering, so X got as far as picking the mode and then died:
|
|
#
|
|
# Output HDMI-1 using initial mode 3840x1080 +0+0
|
|
# (EE) AddScreen/ScreenInit failed for driver 0
|
|
#
|
|
# nixos-hardware's fkms-3d injected a CMA overlay alongside the display one;
|
|
# this replaces that half of it. 256M is generous for any panel an ATM will
|
|
# carry and trivial against 4-8GB of RAM.
|
|
boot.kernelParams = [ "console=tty0" "cma=256M" ];
|
|
|
|
# ── REQUIRES A MANUAL STEP ON THE FIRMWARE PARTITION ────────────────
|
|
# This board boots the FIRMWARE's vendor DTB, not the DTBs NixOS builds.
|
|
# Confirmed on the CM4: the live device tree carries __symbols__ and the
|
|
# mainline DTBs in dtbs-filtered do not, and U-Boot found no FDTDIR match for
|
|
# compatible "raspberrypi,4-compute-module" so it passed the firmware's DTB
|
|
# through. That means hardware.deviceTree.overlays cannot reach the running
|
|
# device tree, and the display has to be enabled by the firmware instead.
|
|
#
|
|
# In the vendor DTB every display node (hvs, gpu, all pixelvalves, both hdmi)
|
|
# ships `disabled`. So /boot/firmware/config.txt needs:
|
|
#
|
|
# dtoverlay=vc4-kms-v3d,noaudio
|
|
#
|
|
# and /boot/firmware/overlays/ needs to be populated from raspberrypifw --
|
|
# the NixOS sd-image writes the DTBs there but NOT the overlays, so the
|
|
# directory ships empty and the dtoverlay line fails silently. Copy the whole
|
|
# directory (2MB, 356 files); copying only vc4-kms-v3d.dtbo is not enough
|
|
# because the firmware remaps that to vc4-kms-v3d-pi4.dtbo on this board.
|
|
#
|
|
# `noaudio` is required, not cosmetic. With HDMI audio enabled vc4_hdmi cannot
|
|
# register its PCM component, returns -517 (EPROBE_DEFER) forever, and the DRM
|
|
# device never registers -- so X finds no card at all. We removed the audio
|
|
# stack anyway, so there is nothing to lose.
|
|
#
|
|
# This is a reflash-losing manual step and it should be folded into the image
|
|
# builder. Tracked as a follow-up; noted here so the next person does not
|
|
# rediscover it from a blank screen.
|
|
|
|
# Kiosk display: mainline full KMS, NOT nixos-hardware's fkms-3d.
|
|
#
|
|
# fkms-3d applies the rpi4-cma-overlay and rpi4-vc4-fkms-v3d-overlay device
|
|
# tree overlays. Those target nodes that exist in the Raspberry Pi VENDOR
|
|
# kernel's DTBs and not in mainline's, so with the mainline kernel above the
|
|
# overlay step fails outright:
|
|
#
|
|
# Applying overlay rpi4-vc4-fkms-v3d-overlay
|
|
# libfdt.FdtException: pylibfdt error -1: FDT_ERR_NOTFOUND
|
|
#
|
|
# It is also unnecessary. "fkms" is FIRMWARE KMS, the older route where the
|
|
# VideoCore firmware owns the display and Linux drives it at arm's length.
|
|
# Mainline does full KMS instead, and mainline's own bcm2711-rpi-4-b.dtb
|
|
# already describes the hardware — it carries brcm,bcm2711-vc5 and
|
|
# brcm,2711-v3d nodes, checked with dtc. The vc4 and v3d drivers bind to
|
|
# those directly with no overlay involved.
|
|
#
|
|
# fkms-3d used to set services.xserver.videoDrivers as a side effect. Nothing
|
|
# needs to replace it: the shared configuration.nix already declares
|
|
# modesetting, which is the correct driver for full KMS and what the x86
|
|
# machines use. Setting it again here only produced a duplicate entry.
|
|
|
|
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;
|
|
suspend.enable = false;
|
|
hibernate.enable = false;
|
|
hybrid-sleep.enable = false;
|
|
};
|
|
|
|
# Stable device symlinks for USB-serial bill-validator adapters, so the ATM
|
|
# config can point at /dev/ttyValidator0 regardless of enumeration order.
|
|
# Identical to the Pi 5 module — same adapters, same bridges. If two adapters
|
|
# of the SAME chip are used, disambiguate by KERNELS/serial instead — tune
|
|
# during bring-up.
|
|
services.udev.extraRules = lib.mkAfter ''
|
|
# FTDI (e.g. FT232R) → ttyValidator0
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="ttyValidator0"
|
|
# Silicon Labs CP210x → ttyValidator1
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="ttyValidator1"
|
|
# WCH CH340 → ttyValidator2
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ttyValidator2"
|
|
'';
|
|
}
|