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
128bac2 to
96925f9
Compare
98646e6 to
bbf58c3
Compare
Please note that the whole "real-hw-test" commit series was mainly created by Codex and Claude, but with significant handholding and various iterations by me. The build is parameterized by ARCH from the start: the x86_64 and aarch64 UEFI targets with their removable-media file names, 'make artifacts' to cross-compile all of them in one step, and the architecture in the on-screen banner.
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. QEMU follows ARCH: q35 with OVMF on x86_64, virt with pflash EDK2, ramfb, and a USB keyboard on aarch64, where TCG is the default because the development host is typically x86_64.
The dev shell supplies QEMU, OVMF, and rustup. The .envrc enables direnv integration. The full QEMU package is used because qemu_kvm carries only the host architecture's system emulator; the aarch64 firmware paths are exported next to OVMF.
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. Port I/O exists only on x86, so the port address form and the COM1 source are cfg-gated from the start.
The conventional COM2-COM4 addresses are only accepted when the crate's own presence check, Uart16550::check_present(), answers, because reading an absent port yields junk. It is the same scratch-register test that init() runs first, so discovery and the later driver tests agree on what counts as a device. COM1 stays registered unconditionally. The legacy probe is x86-only and cfg-gated accordingly.
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. Without x86 port instructions, a System I/O SPCR is reported and skipped.
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.
Run the public driver API on every discovered candidate: 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. init() carries the crate's presence check, so an absent UART fails there rather than in a later step. Absent DSR/CTS is only a warning because three-wire and USB serial cables legitimately omit modem-control lines. The PIO backend exists only on x86; its driver variant is cfg-gated.
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.
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. The file name carries the architecture so one stick can hold runs from several machines.
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.
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. With the I/O window in place the aarch64 target builds, so 'make check' lints both targets.
Describe what the application discovers and checks, the recommended hardware and cable setup, how to build it and run it under QEMU, how to read its output, and the current state of architecture support.
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.
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.
Point readers to the UEFI application for real hardware and to the VM-based integration test.
bbf58c3 to
763dae3
Compare
The candidate list only dumped the discovery enum, so an operator could not tell an on-board UART from an add-in card, or see that SPCR and PCI enumeration described the same function. Classify every candidate by location - built-in legacy port, built-in platform UART, or a PCI function with vendor/device IDs and whether it sits on the root bus or behind a bridge - separately from the paths that found it. SPCR's PCI identity fields (revision 2 and later) are parsed so a firmware console that is a PCI device is classified as one. PCI evidence wins when several paths describe the same address, and only enumeration knows the attachment. Known QEMU serial devices are named.
763dae3 to
094020d
Compare
| qemu | ||
| rustup | ||
| ]; | ||
| env.OVMF = "${pkgs.OVMF.fd}/FV/OVMF.fd"; |
There was a problem hiding this comment.
let's use env = {}; here
|
|
||
| extern crate uefi as uefi_rs; | ||
|
|
||
| /// Routes every UEFI diagnostic through one crate-local indirection point. |
There was a problem hiding this comment.
I think we used to have an additional sentence explaining what is going on here. I don't really understand this redirection/rename.
| @@ -0,0 +1,169 @@ | |||
| //! Conservative PCI serial-controller discovery through UEFI root bridges. | |||
There was a problem hiding this comment.
Please add another sentence telling what conservative means here andd why we did it this way
| return; | ||
| } | ||
|
|
||
| let candidate = if bar0 & 1 != 0 { |
There was a problem hiding this comment.
Please add named consts here or comments. whychecking lowest bit? why checking lowest 3 bits? keep it short/minimal
| Some(Address::Port(base as u16)) | ||
| } | ||
| } else { | ||
| let memory_type = (bar0 >> 1) & 0x3; |
There was a problem hiding this comment.
perhaps use helper functions for if and else branch here with descriptive names.
| @@ -0,0 +1,242 @@ | |||
| # uart_16550 UEFI real-hardware test | |||
|
|
|||
| This subproject builds a UEFI application (x86_64 by default, aarch64 via | |||
There was a problem hiding this comment.
Please slightly shorten the doc changes in real-hw-test: document the hardware test
| @@ -0,0 +1,174 @@ | |||
| #!/usr/bin/env bash | |||
There was a problem hiding this comment.
in commit real-hw-test: "make install" to install on a USB stick extend thecommit message how this isintendedto be used. short motivational writeup
|
|
||
| ### Headless CI smoke test | ||
|
|
||
| `make ci-qemu` boots the same artifact as `make artifact` headlessly with QEMU |
There was a problem hiding this comment.
make section Headless CI smoke test shorted/more concise
| jobs: | ||
| qemu: | ||
| name: Headless QEMU TCG | ||
| name: Headless QEMU TCG (${{ matrix.arch }}) |
There was a problem hiding this comment.
commit message body of ci: smoke-test aarch64 UEFI UART paths in QEMU could have 1-2 sentences more high level context
| assert_log "$persisted_log" -F 'PIO 0x03f8' | ||
| assert_log "$persisted_log" -F 'RequiredCom1' | ||
| assert_log "$persisted_log" -E 'sources=\[.*Pci' | ||
| assert_log "$persisted_log" -F 'location: built-in legacy port' |
There was a problem hiding this comment.
commit real-hw-test: report where each UART lives and how it was found should be split into multiple andbe folded into earlier commits
Init a new crate member that builds a EFI file that can be easily booted on real hardware.