From 7e90719508e17bf0c7385e9d4a8edd797ac0ef47 Mon Sep 17 00:00:00 2001 From: Padreug Date: Thu, 2 Jul 2026 18:40:12 +0200 Subject: [PATCH] refactor(deploy): share UP Board serial hardware between installed + live ISO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sintra live ISO (live.nix) had no serial support — ftdi_sio and the ttyJ5/ttyJ7 udev symlinks were only in hardware/upboard.nix (installed), so booting iso-sintra on real hardware failed on the validator + F56 dispenser while the disk image worked. The two definitions had already drifted (live's tejo block lacked ttyS4). Extract the UP Board serial peripherals (usbserial/ftdi_sio/cp210x, the ttyJ4/ttyJ5/ttyJ7 udev symlinks + permissions, console=tty0) into hardware/upboard-serial.nix and import it from both upboard.nix (installed tejo + sintra) and live.nix (sintra only). Single source of truth — the two artifacts can't drift again. Named upboard-serial (not sintra-serial) since upboard.nix serves both tejo-installed and sintra-installed. Camera + LED/SPI rules stay inline in upboard.nix (installed-specific; the pairing camera works via getUserMedia without the scanner symlink). Verified by eval: live sintra now carries ftdi_sio + console=tty0 + ttyJ7; installed sintra/tejo unchanged (serial present, camera present, no console dupe). Co-Authored-By: Claude Opus 4.8 --- deploy/nixos/hardware/upboard-serial.nix | 61 ++++++++++++++++++++++++ deploy/nixos/hardware/upboard.nix | 52 ++++---------------- deploy/nixos/live.nix | 7 ++- 3 files changed, 75 insertions(+), 45 deletions(-) create mode 100644 deploy/nixos/hardware/upboard-serial.nix diff --git a/deploy/nixos/hardware/upboard-serial.nix b/deploy/nixos/hardware/upboard-serial.nix new file mode 100644 index 0000000..dfbf947 --- /dev/null +++ b/deploy/nixos/hardware/upboard-serial.nix @@ -0,0 +1,61 @@ +# UP Board serial peripherals — the validator / dispenser / printer wiring +# shared by the INSTALLED configs (hardware/upboard.nix, used by both +# tejo-installed and sintra-installed) AND the sintra live ISO (live.nix). +# Single source of truth so the two artifacts can't drift — the earlier bug +# was exactly this drift (the sintra live ISO lacked ftdi_sio + the ttyJ7 +# symlink, so the F56 dispenser failed while the installed image worked). +# +# Sintra IS a UP Board, so these are the UP Board rules; ttyS1/ttyS5 cover the +# older UP Board / UP4000 (Tejo) dispenser nodes and ttyS4 covers the Sintra +# (Apollo Lake) where the F56 is on the SoC MMIO UART. Only the device that +# actually exists at runtime gets the symlink, so all three coexist safely. +# +# Serial port mapping: +# ttyJ4 = Printer (Nippon NP-2511D-2) +# ttyJ5 = Validator (iVIZION, ID003) +# ttyJ7 = Dispenser (Fujitsu F53/F56) +{ lib, ... }: + +{ + boot.kernelModules = [ + "usbserial" # USB-to-serial adapters + "ftdi_sio" # FTDI USB serial (the iVIZION validator bridge) + "cp210x" # CP210x USB serial (alternative adapter) + ]; + + boot.kernelParams = [ + # Do NOT route the kernel console through ttyS4 on Sintra. ttyS4 is the + # SoC's MMIO 16550A (the only real UART besides the legacy ttyS0 at I/O + # 0x3f8) and is wired to the Fujitsu F56 dispenser's RS-232 header. Holding + # it as console prevents userspace opening it at 9600 baud and HAL fails + # with "Input/output error setting custom baud rate of 9600". For serial + # debug, point console at ttyS0 instead. + "console=tty0" + ]; + + services.udev.extraRules = lib.mkAfter '' + # Generic serial port permissions (so the non-root HAL user can open them) + KERNEL=="ttyS[0-9]*", MODE="0666" + KERNEL=="ttyUSB[0-9]*", MODE="0666" + KERNEL=="ttyACM[0-9]*", MODE="0666" + + # Printer (ttyJ4) + KERNELS=="1-7.2:1.0", SYMLINK+="ttyJ4" + KERNEL=="ttyUSB0", SYMLINK+="ttyJ4" + + # Validator (ttyJ5) + KERNELS=="1-7.3:1.0", SYMLINK+="ttyJ5" + KERNEL=="ttyUSB1", SYMLINK+="ttyJ5" + + # Dispenser (ttyJ7). ttyS1/ttyS5 = older UP Board / UP4000; ttyS4 = Sintra. + KERNEL=="ttyS1", SYMLINK+="ttyJ7" + KERNEL=="ttyS4", SYMLINK+="ttyJ7" + KERNEL=="ttyS5", SYMLINK+="ttyJ7" + + # Legacy ttyAMA0 alias + SUBSYSTEM=="tty", KERNEL=="ttyS1", SYMLINK+="ttyAMA0", GROUP="dialout" + + # Disable USB autosuspend (prevents serial adapters from sleeping) + ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="on" + ''; +} diff --git a/deploy/nixos/hardware/upboard.nix b/deploy/nixos/hardware/upboard.nix index b3fe1f3..d213ff7 100644 --- a/deploy/nixos/hardware/upboard.nix +++ b/deploy/nixos/hardware/upboard.nix @@ -11,6 +11,10 @@ { config, lib, pkgs, ... }: { + # Serial peripherals (validator/dispenser/printer modules + udev symlinks + + # console=tty0) are shared with the live ISO via ./upboard-serial.nix. + imports = [ ./upboard-serial.nix ]; + boot = { loader = { systemd-boot.enable = true; @@ -42,21 +46,12 @@ "kvm-intel" "i2c-dev" "spi-dev" - "usbserial" # USB-to-serial adapters - "ftdi_sio" # FTDI USB serial - "cp210x" # CP210x USB serial + # Serial modules (usbserial/ftdi_sio/cp210x) → ./upboard-serial.nix. ]; kernelParams = [ "i915.enable_psr=0" - # NOTE: do NOT route the kernel console through ttyS4 on Sintra. - # ttyS4 is the SoC's MMIO 16550A (the only real UART besides the - # legacy ttyS0 at I/O 0x3f8) and is wired to the Fujitsu F56 - # dispenser's RS-232 header on Sintra. Holding it as console - # prevents userspace from opening it at 9600 baud and HAL fails - # with "Input/output error setting custom baud rate of 9600". - # If you want serial debug, point console at ttyS0 instead. - "console=tty0" + # console=tty0 (keeps ttyS4 free for the F56) → ./upboard-serial.nix. "quiet" "splash" ]; @@ -104,37 +99,9 @@ hybrid-sleep.enable = false; }; - # Serial port permissions + tejo-specific symlinks + # Camera + LED/SPI peripherals. The serial rules (validator/dispenser/printer + # symlinks + permissions) are shared with the live ISO in ./upboard-serial.nix. services.udev.extraRules = lib.mkAfter '' - # Generic serial port permissions - KERNEL=="ttyS[0-9]*", MODE="0666" - KERNEL=="ttyUSB[0-9]*", MODE="0666" - KERNEL=="ttyACM[0-9]*", MODE="0666" - - # ── Tejo serial port symlinks ────────────────────────────────────── - # Both UP Board and UP4000 rules included (match different kernel paths) - - # Printer (ttyJ4) - KERNELS=="1-7.2:1.0", SYMLINK+="ttyJ4" - KERNEL=="ttyUSB0", SYMLINK+="ttyJ4" - - # Validator (ttyJ5) - KERNELS=="1-7.3:1.0", SYMLINK+="ttyJ5" - KERNEL=="ttyUSB1", SYMLINK+="ttyJ5" - - # Dispenser (ttyJ7). - # ttyS1 / ttyS5 cover earlier UP Board variants where the dispenser - # lands on those kernel-enumerated serial nodes; ttyS4 covers the - # Sintra (UP Board Atom/Apollo Lake) where the dispenser is wired - # to the SoC's MMIO UART. Whichever device actually exists at - # runtime gets the ttyJ7 symlink. - KERNEL=="ttyS1", SYMLINK+="ttyJ7" - KERNEL=="ttyS4", SYMLINK+="ttyJ7" - KERNEL=="ttyS5", SYMLINK+="ttyJ7" - - # Legacy ttyAMA0 alias - SUBSYSTEM=="tty", KERNEL=="ttyS1", SYMLINK+="ttyAMA0", GROUP="dialout" - # ── Camera devices ───────────────────────────────────────────────── SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-5", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-scan" SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-2", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-scan" @@ -147,8 +114,5 @@ SUBSYSTEM=="spidev", GROUP="spi", MODE="0660" SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660" SUBSYSTEM=="leds", KERNEL=="upboard:*", ACTION=="add|change", RUN+="${pkgs.findutils}/bin/find /sys$devpath -type f -exec ${pkgs.coreutils}/bin/chmod g+u {} + -exec ${pkgs.coreutils}/bin/chown :leds {} +" - - # Disable USB autosuspend (prevents serial adapters from sleeping) - ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="on" ''; } diff --git a/deploy/nixos/live.nix b/deploy/nixos/live.nix index 7f0a190..44255e9 100644 --- a/deploy/nixos/live.nix +++ b/deploy/nixos/live.nix @@ -48,7 +48,12 @@ in # Reuse ATM systemd service module ./bitspire-atm.nix - ]; + ] + # Sintra: share the UP Board serial hardware (validator/dispenser/printer + # modules + udev symlinks + console=tty0) with the installed image so the + # live ISO drives the same hardware. Safe to import here — unlike upboard.nix + # it declares no fileSystems, so there's no live-boot mount conflict. + ++ lib.optionals (machineModel == "sintra") [ ./hardware/upboard-serial.nix ]; # ISO image settings image.fileName = "bitspire-${machineModel}-live.iso";