diff --git a/docs/plans/2026-03-17-usb-passthrough-design.md b/docs/plans/2026-03-17-usb-passthrough-design.md new file mode 100644 index 0000000..80ba4ff --- /dev/null +++ b/docs/plans/2026-03-17-usb-passthrough-design.md @@ -0,0 +1,119 @@ +# USB Device Passthrough Design + +## Overview + +USB device passthrough for vmsilo VMs via crosvm's USB hotplug interface. Supports both persistent attachment (declared in VM config, attached at VM start) and runtime attachment via `vmsilo-usb` CLI. + +## Identifiers + +- **Persistent config:** VID:PID (required) + optional serial number +- **Runtime CLI:** VID:PID or devpath (physical USB port path like `1-3`) + +When VID:PID matches multiple physical devices, all are attached (unless narrowed by serial). + +## NixOS Options + +New per-VM option in `programs.vmsilo.nixosVms.`: + +```nix +usbDevices = [ + { vendorId = "17ef"; productId = "60e0"; } + { vendorId = "046d"; productId = "c52b"; serial = "A02019100900"; } +]; +``` + +A NixOS assertion ensures no two VMs declare the same VID:PID+serial combination. + +## State File + +`/run/vmsilo/usb-state.json` — single global file tracking all USB attachments: + +```json +{ + "1-3": { + "vid": "17ef", + "pid": "60e0", + "serial": null, + "busnum": 1, + "devnum": 2, + "vm": "banking", + "port": 1 + } +} +``` + +Keyed by devpath. All operations acquire an exclusive `flock` on `/run/vmsilo/usb-state.lock` for the full read-modify-crosvm call-write cycle. + +Ephemeral (lives in `/run`), matching the VM lifecycle. + +## Runtime CLI — `vmsilo-usb` + +### `vmsilo-usb` (no args) — list devices + +Displays all host USB devices with attachment status: + +``` +VID:PID Port Serial Manufacturer Product VM +17ef:60ee 1-3 - Lenovo TrackPoint Keyboard II - +05e3:0610 1-6 - - USB2.0 Hub - +26ce:01a2 9-1 A02019100900 ASRock LED Controller untrusted +``` + +Reads sysfs for device info, merges with state file for VM assignments. No root required. + +### `vmsilo-usb attach ` + +1. Resolve identifier to physical device(s) via sysfs +2. Acquire lock, read state file +3. For each matching device: if attached to another VM, detach from old VM first (`crosvm usb detach`) +4. Call `crosvm usb attach` on target VM +5. Update state file, release lock + +Runs via polkit — invokes a systemd oneshot template that runs as root. + +### `vmsilo-usb detach ` + +1. Acquire lock, read state file +2. Find matching entries for that VM +3. Call `crosvm usb detach` +4. Remove entries from state file, release lock + +Same polkit mechanism as attach. + +## Persistent Attachment + +### On VM Start + +`vmsilo--usb-attach.service` — a oneshot unit with `After=` and `Requires=` on the VM service: + +1. Wait for the crosvm control socket to appear +2. For each configured `usbDevices` entry: scan sysfs for matching VID:PID (+ serial if specified) +3. Attach all matches via `crosvm usb attach`, update state file under lock +4. If a device isn't plugged in, log a warning and continue + +### On VM Stop + +ExecStopPost in the VM service removes that VM's entries from the state file under lock. No `crosvm usb detach` needed since the VM is shutting down. + +## Permission Model + +- `vmsilo-usb attach/detach` use polkit, same as `vm-start`/`vm-stop` +- Implemented as systemd oneshot template services authorized via existing vmsilo polkit rules +- `crosvm usb attach` runs as root (needs USB device fd + control socket access) +- `vmsilo-usb` (listing) needs no privileges — reads sysfs and the state file + +## crosvm Interface + +- `crosvm usb attach BUS_ID:ADDR:BUS_NUM:DEV_NUM DEV_PATH SOCKET_PATH` — opens device fd, passes to VM over control socket +- `crosvm usb detach PORT SOCKET_PATH` — detaches by crosvm port number +- `crosvm usb list SOCKET_PATH` — returns `{port, vendor_id, product_id}` per device +- Control socket at `/run/vmsilo/-crosvm-control.socket` +- USB enabled by default in crosvm (no extra flags needed) + +## Files Modified + +- `modules/options.nix` — `usbDevices` option +- `modules/assertions.nix` — duplicate VID:PID+serial assertion +- `modules/scripts.nix` — `vmsilo-usb` script, USB helper functions (sysfs enumeration, state file with flock, resolution logic) +- `modules/services.nix` — `vmsilo--usb-attach.service`, ExecStopPost state cleanup, polkit oneshot templates for runtime +- `modules/package.nix` — symlink `vmsilo-usb` into `$out/bin/`