real-hw-test: init - #71
Conversation
1de6fe5 to
9bc95d0
Compare
phip1611
left a comment
There was a problem hiding this comment.
Almost. Let's drop the ci feature. The crate should not know anything required for a CI run. IF this means a QEMU CI run is not feasible than drop that entirely. If it is possible, keep the QEMU CI run.
d8f46d9 to
128bac2
Compare
| @@ -0,0 +1,177 @@ | |||
| #!/usr/bin/env bash | |||
| set -euo pipefail | |||
|
|
|||
There was a problem hiding this comment.
Lot's of complexity here. do you see chance for simpliciation / less code?
There was a problem hiding this comment.
Some, but most of the weight is load-bearing. Roughly: ~35 lines per-arch machine/firmware setup, ~25 per-arch assertions, ~40 failure diagnostics (fail_run/dump_logs — what makes a red CI run debuggable from the job log alone), ~25 the two-phase key driving. The phases themselves are semantic, not accidental: no key may be sent before the first prompt (Escape during BDS would enter firmware setup), and the final extraction must happen after QEMU quits for the FAT state to be committed.
Cuttable, at a cost: the tool preflight loop (-8 lines, cryptic late failures instead of one clear message), the CI_RUN_DIR rm-guard (-4, safety), mid-run log polling could poll the console log instead of the FAT image (-0, and it would couple the gate to firmware console mirroring, which aarch64 does not have for the app output). I would leave it as is; happy to trim any of the above if you prefer less code over the diagnostics.
MMIO addresses do not describe a Rust allocation, so use the wrapping pointer primitive rather than an in-bounds pointer offset. The UART constructor already rejects a register range that would wrap.
A minimal bootable UEFI application gives every later commit a testable vehicle, staged under the removable-media file name that firmware loads without configuration. The artifact target always reruns cargo so a stale image is never deployed. All UEFI symbols are reached through a crate-local module so a later commit can reroute diagnostics without touching call sites.
Booting the image under QEMU with OVMF gives a fast iteration loop that needs no physical machine or USB stick. COM1 is wired to the launching terminal, and an additional PCI serial device exposes an independently discovered UART through a PTY.
The dev shell supplies QEMU, OVMF, and rustup. The .envrc enables direnv integration.
Candidates from every discovery path land in one inventory deduplicated by address, so a UART described by several sources is still tested only once. Firmware serial controllers are disconnected first because the firmware and the driver under test must never program a UART concurrently; the firmware baseline is recorded on screen beforehand. COM1 at 0x3f8 is registered unconditionally: the targeted machines are required to expose it, so its absence must surface as a test failure rather than as silent non-discovery.
A minimal register accessor that shares no code with the driver under test lets discovery and later checks observe the hardware without trusting the code being validated. The conventional COM2-COM4 addresses are only accepted when a scratch-register test responds like a UART, because reading an absent port yields junk; the test is non-destructive and restores the old scratch value since firmware may use it diagnostically. COM1 stays registered unconditionally.
The Serial Port Console Redirection table is how firmware names its console UART on machines without ISA-conventional COM ports, which is the norm on headless servers and on non-x86 platforms. It also carries the address space, access width, and clock, so the port can be driven without guessing; MMIO-mapped register blocks become testable here. Only 16450/16550-compatible interface types with byte-wide access are accepted; everything else (for example a PL011) is reported and skipped rather than programmed blindly.
Serial add-in cards and paravirtual devices such as QEMU pci-serial live behind BARs, so neither fixed-address probing nor SPCR sees them. Enumerate serial-class endpoints through the UEFI PCI root bridges and accept only an unambiguous 16550-compatible programming interface with a usable BAR0; vendor-specific layouts are reported but not touched.
Raw register checks run before any driver code touches a candidate: initialization at 9600 8N1, single- and 16-byte loopback, register invariants, and a recognizable transmit payload, all through the independent accessor. A failure here points at the hardware, wiring, or discovery result rather than at the driver, and only candidates that pass are meaningful subjects for the driver tests that follow.
Run the public driver API on every candidate that passed preflight: init, the register values it must leave behind, test_loopback with a restored configuration afterwards, DSR/CTS connection signals, and the try_send_byte/send_bytes/send_bytes_exact paths. The preceding raw phase makes these results attributable: a UART that passed barebones checks but fails here indicts the driver, not the hardware. Absent DSR/CTS is only a warning because three-wire and USB serial cables legitimately omit modem-control lines.
Automatic checks cannot prove that a real cable to a remote terminal works. On operator request each passing UART offers register dumps, a transmit line to the remote side, connection-signal inspection, another loopback, and receive-with-echo of typed characters. Escape, locally or as serial 0x1b, skips a UART that has no remote connected; the skip is recorded as a warning instead of a failure.
A helper script prepares a mounted USB drive to boot the test: it copies the built artifact to the removable-media path after verifying that the target is the exact mount point of a FAT32 partition on a GPT disk. The script never partitions, formats, or mounts anything itself, so a mistyped path stops with a diagnostic instead of writing into the wrong filesystem. The README documents the full hardware workflow around it.
Manual hardware checks can wait forever for an operator. Disable the UEFI image watchdog so a long session does not reset the machine. Report firmware failures on screen.
Mirror every test diagnostic to a dated file below /uart_16550_test_logs while retaining UEFI console output. Keep one flushed FAT file handle so a failed write is reported as critical and aborts the test instead of losing failure evidence.
A real UART can still be draining the byte accepted by try_send_byte when send_bytes is called. Retry the nonblocking API for one second instead of treating temporary backpressure as a driver failure. Preserve an earlier modem-signal warning if a later driver check fails.
Report the dated USB-drive log path before each normal test exit. This keeps the result on screen when the automated checks fail as well as when they complete successfully.
Run the unmodified interactive image headlessly under TCG. The harness answers the operator prompts through QEMU-monitor sendkey and judges the run by the log persisted on the boot volume plus both serial captures. Require automatic checks for legacy COM1 and a PCI UART.
Port I/O instructions, legacy COM probing, and the PIO driver backend exist only on x86. Compiler-enforced cfg gates keep every port-address path out of the builds of other architectures.
Without x86 port instructions, PCI I/O space is a memory-mapped window behind the root bridge. Firmware hides the CPU-side base inside its protocol implementation, but AML resource templates embed plain address space descriptors, so a strictly validated DSDT byte scan recovers the translated window without an AML interpreter. Firmware also leaves the decoding of endpoints it never binds disabled; an assigned BAR of an unambiguous UART is therefore enabled explicitly.
ARCH selects the Rust target, the removable-media file name, and the matching QEMU machine: q35 with OVMF on x86_64, virt with pflash EDK2, ramfb, and a USB keyboard on aarch64. The dev shell switches to the full QEMU because qemu_kvm only carries the host architecture.
The aarch64 virt machine has no 16550 except the PCI serial device, so the run must reject the PL011 console via SPCR and drive the PCI UART through the translated I/O window with the MMIO backend.
One USB stick can carry runs from several machines; the architecture in the log file name and the on-screen banner keeps them apart.
'make artifacts' cross-compiles all supported architectures, and the USB install copies every artifact under build/ to its removable-media path, so one stick boots the test on any supported machine.
128bac2 to
96925f9
Compare
Init a new crate member that builds a EFI file that can be easily booted on real hardware.