feat(nix): add tejo hardware support and per-model auto-upgrade

- Add tejo-specific udev rules for both UP Board and UP4000 variants
  (serial symlinks ttyJ4/J5/J7, camera, LED SPI, I2C, USB autosuspend)
- Add USB serial kernel modules (usbserial, ftdi_sio, cp210x) for tejo
- Add tejo serial console kernel params (ttyS4 debug UART)
- Fix upboard.nix: hardware.graphics → hardware.opengl (NixOS 24.05)
- Add tejo-installed nixosConfiguration with model-specific auto-upgrade
- Parameterize auto-upgrade flake URL per machine model

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-03-06 08:54:50 -05:00
commit 143ae9ff0b
3 changed files with 121 additions and 51 deletions

View file

@ -1,55 +1,51 @@
# UP Board Hardware Configuration # Tejo (UP Board / UP4000) Hardware Configuration
# Supports UP Board, UP Squared, and similar Intel Atom/Celeron boards # Intel Atom/Celeron boards used in Lamassu Tejo ATM machines.
# commonly used in Lamassu ATM machines # Supports both UP Board and UP4000 variants — udev rules for both
# are included since they match different kernel paths and don't conflict.
#
# Serial port mapping:
# ttyJ4 = Printer (Nippon NP-2511D-2)
# ttyJ5 = Validator (iVizion, ID003 protocol)
# ttyJ7 = Dispenser (Fujitsu F53/F56)
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
{ {
# Boot configuration for UP Board
# UP Board uses 64-bit CPU but some models have 32-bit UEFI
boot = { boot = {
loader = { loader = {
# Use systemd-boot for UEFI systems
systemd-boot.enable = true; systemd-boot.enable = true;
efi.canTouchEfiVariables = true; efi.canTouchEfiVariables = true;
# Timeout for boot menu (useful for remote debugging)
timeout = 3; timeout = 3;
}; };
# Kernel modules needed for UP Board hardware
initrd.availableKernelModules = [ initrd.availableKernelModules = [
"xhci_pci" # USB 3.0 "xhci_pci"
"ahci" # SATA "ahci"
"usb_storage" # USB mass storage "usb_storage"
"sd_mod" # SCSI disk "sd_mod"
"sdhci_pci" # SD card (eMMC) "sdhci_pci"
"i915" # Intel graphics "i915"
]; ];
kernelModules = [ kernelModules = [
"kvm-intel" # Virtualization (if needed) "kvm-intel"
"i2c-dev" # I2C for hardware control "i2c-dev"
"spi-dev" # SPI for hardware control "spi-dev"
"usbserial" # USB-to-serial adapters
"ftdi_sio" # FTDI USB serial
"cp210x" # CP210x USB serial
]; ];
# UP Board specific kernel parameters
kernelParams = [ kernelParams = [
# Intel graphics
"i915.enable_psr=0" "i915.enable_psr=0"
# Serial console for debugging (UP Board has debug UART)
"console=ttyS4,115200n8" "console=ttyS4,115200n8"
"console=tty0" "console=tty0"
# Quiet boot for kiosk mode
"quiet" "quiet"
"splash" "splash"
]; ];
}; };
# Filesystem configuration # Disk layout: GPT with ESP + ext4 root (eMMC on UP Board)
# Adjust these to match your actual disk layout
fileSystems."/" = { fileSystems."/" = {
device = "/dev/disk/by-label/nixos"; device = "/dev/disk/by-label/nixos";
fsType = "ext4"; fsType = "ext4";
@ -60,30 +56,24 @@
fsType = "vfat"; fsType = "vfat";
}; };
# Hardware-specific packages # NixOS 24.05 uses hardware.opengl (not hardware.graphics)
hardware = { hardware = {
# Intel GPU support opengl = {
graphics = {
enable = true; enable = true;
extraPackages = with pkgs; [ extraPackages = with pkgs; [
intel-media-driver # VAAPI driver for newer Intel intel-media-driver
vaapiIntel # VAAPI driver (legacy) vaapiIntel
vaapiVdpau vaapiVdpau
libvdpau-va-gl libvdpau-va-gl
]; ];
}; };
# Enable firmware for Intel hardware
enableRedistributableFirmware = true; enableRedistributableFirmware = true;
# CPU microcode updates
cpu.intel.updateMicrocode = true; cpu.intel.updateMicrocode = true;
}; };
# Power management
powerManagement = { powerManagement = {
enable = true; enable = true;
cpuFreqGovernor = "performance"; # ATM should be responsive cpuFreqGovernor = "performance";
}; };
# Disable suspend/hibernate for kiosk # Disable suspend/hibernate for kiosk
@ -94,14 +84,45 @@
hybrid-sleep.enable = false; hybrid-sleep.enable = false;
}; };
# Serial port access for bill validator/dispenser # Serial port permissions + tejo-specific symlinks
# UP Board GPIO/UART pins services.udev.extraRules = lib.mkAfter ''
services.udev.extraRules = '' # Generic serial port permissions
# UP Board serial ports
KERNEL=="ttyS[0-9]*", MODE="0666" KERNEL=="ttyS[0-9]*", MODE="0666"
# USB serial adapters (common for bill validators)
KERNEL=="ttyUSB[0-9]*", MODE="0666" KERNEL=="ttyUSB[0-9]*", MODE="0666"
KERNEL=="ttyACM[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)
KERNEL=="ttyS1", 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"
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-6", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-front"
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-3", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-front"
# ── LED SPI / peripherals ──────────────────────────────────────────
KERNEL=="spidev1.0", SYMLINK+="ledspi"
KERNEL=="spidev2.0", SYMLINK+="ledspi"
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"
''; '';
} }

View file

@ -82,6 +82,10 @@ in
"kvm-intel" "kvm-intel"
"i2c-dev" "i2c-dev"
"spi-dev" "spi-dev"
] ++ lib.optionals (machineModel == "tejo") [
"usbserial" # USB-to-serial adapters (ttyUSB0/1 for validator/printer)
"ftdi_sio" # FTDI USB serial (common in ATM peripherals)
"cp210x" # CP210x USB serial (alternative adapter)
]; ];
kernelParams = [ kernelParams = [
@ -91,6 +95,10 @@ in
] ++ lib.optionals (machineModel == "douro") [ ] ++ lib.optionals (machineModel == "douro") [
# Bay Trail: preserve BIOS display init (matches working kernel 5.4 config) # Bay Trail: preserve BIOS display init (matches working kernel 5.4 config)
"vt.handoff=7" "vt.handoff=7"
] ++ lib.optionals (machineModel == "tejo") [
# UP Board debug UART for serial console
"console=ttyS4,115200n8"
"console=tty0"
]; ];
}; };
@ -194,10 +202,53 @@ in
# Allow SSH with password for initial setup on the live system # Allow SSH with password for initial setup on the live system
services.openssh.settings.PasswordAuthentication = lib.mkForce true; services.openssh.settings.PasswordAuthentication = lib.mkForce true;
# Serial port udev rules (from hardware/upboard.nix) # Serial port udev rules — generic permissions for all models
services.udev.extraRules = lib.mkAfter '' services.udev.extraRules = lib.mkAfter (''
KERNEL=="ttyS[0-9]*", MODE="0666" KERNEL=="ttyS[0-9]*", MODE="0666"
KERNEL=="ttyUSB[0-9]*", MODE="0666" KERNEL=="ttyUSB[0-9]*", MODE="0666"
KERNEL=="ttyACM[0-9]*", MODE="0666" KERNEL=="ttyACM[0-9]*", MODE="0666"
''; '' + lib.optionalString (machineModel == "tejo") ''
# ── Tejo serial port symlinks ──────────────────────────────────────
# Both UP Board and UP4000 rules are included so one ISO works on either.
# Rules match different kernel paths so they don't conflict.
# Printer (ttyJ4): UP Board via USB hub path, UP4000 via ttyUSB0
KERNELS=="1-7.2:1.0", SYMLINK+="ttyJ4"
KERNEL=="ttyUSB0", SYMLINK+="ttyJ4"
# Validator (ttyJ5): UP Board via USB hub path, UP4000 via ttyUSB1
KERNELS=="1-7.3:1.0", SYMLINK+="ttyJ5"
KERNEL=="ttyUSB1", SYMLINK+="ttyJ5"
# Dispenser (ttyJ7): UP Board uses ttyS1, UP4000 uses ttyS5
KERNEL=="ttyS1", SYMLINK+="ttyJ7"
KERNEL=="ttyS5", SYMLINK+="ttyJ7"
# Legacy ttyAMA0 alias
SUBSYSTEM=="tty", KERNEL=="ttyS1", SYMLINK+="ttyAMA0", GROUP="dialout"
# ── Camera devices ─────────────────────────────────────────────────
# QR scanner camera (A4tech USB, vendor 0ac8:0345)
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"
# Front-facing camera
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-6", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-front"
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-3", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-front"
# ── LED SPI / peripherals ──────────────────────────────────────────
KERNEL=="spidev1.0", SYMLINK+="ledspi"
KERNEL=="spidev2.0", SYMLINK+="ledspi"
# SPI and I2C group permissions
SUBSYSTEM=="spidev", GROUP="spi", MODE="0660"
SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660"
# UP Board FPGA LED permissions
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"
'');
} }

View file

@ -121,10 +121,10 @@
}; };
# Auto-upgrade: pulls latest flake and runs nixos-rebuild switch # Auto-upgrade: pulls latest flake and runs nixos-rebuild switch
# To update manually: sudo nixos-rebuild switch --flake git+ssh://forgejo@git.atitlan.io/aiolabs/lamassu-next.git#douro-installed # To update manually: sudo nixos-rebuild switch --flake git+ssh://forgejo@git.atitlan.io/aiolabs/lamassu-next.git#<model>-installed
system.autoUpgrade = { system.autoUpgrade = {
enable = true; enable = true;
flake = "git+ssh://forgejo@git.atitlan.io/aiolabs/lamassu-next.git#douro-installed"; flake = "git+ssh://forgejo@git.atitlan.io/aiolabs/lamassu-next.git#${machineModel}-installed";
dates = "04:00"; # daily at 4am dates = "04:00"; # daily at 4am
allowReboot = false; allowReboot = false;
}; };
@ -212,9 +212,7 @@
# Installed-to-disk configs (proper GPT + systemd-boot, supports nixos-rebuild) # Installed-to-disk configs (proper GPT + systemd-boot, supports nixos-rebuild)
douro-installed = mkInstalledConfig "douro" ./deploy/nixos/hardware/douro.nix; douro-installed = mkInstalledConfig "douro" ./deploy/nixos/hardware/douro.nix;
tejo-installed = mkInstalledConfig "tejo" ./deploy/nixos/hardware/upboard.nix;
# UP Board (tejo) installed config
lamassu-atm = mkInstalledConfig "tejo" ./deploy/nixos/hardware/upboard.nix;
}; };
# ── Standalone NixOS module ─────────────────────────────────── # ── Standalone NixOS module ───────────────────────────────────