Skip to content

serial: one 16550A core for the O2 and Octane UARTs - #359

Merged
techomancer merged 3 commits into
techomancer:mainfrom
jamesbraid:serial-16550
Oct 30, 2026
Merged

techomancer merged 3 commits into
techomancer:mainfrom
jamesbraid:serial-16550

Conversation

@jamesbraid

@jamesbraid jamesbraid commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

The O2's 16550 was "just enough to be written to", and the Octane's IOC3 had a separate, fuller 16550 of its own. Both now run on one 16550A core in ns16550.rs, a port of QEMU's hw/char/serial.c (MIT, notice kept).

  • The host-side backends (TCP, CI, tee, null) move out of z85c30.rs into serial_host.rs, with the console wiring the O2 and Octane each repeated done once.
  • The O2's UARTs get a real FIFO, trigger levels, the character timeout, overrun and loopback.
  • The IOC3 wraps the same core and keeps only what the chip adds around it.

Transmit stays instant, with no baud pacing. The Indy's Z85C30 is unchanged.

The O2 UARTs now save the core's state, so the snapshot schema goes to v5 and older O2 snapshots are refused.

Tested on the O2 and Octane (serial console login, and the IOC3 register tests from before the swap pass unchanged), with no change in boot time.

…le wiring

SerialBackend, the TCP, CI, tee and null backends lived in the Z85C30's
file, so the O2's MACE UARTs and the Octane's IOC3 imported them from a
chip they do not contain. Move them to dev/serial_host.rs.

The O2 and Octane constructors each carried ~40 lines choosing the CI or
TCP console, teeing it to the serial log and binding the second port.
console_backends() does that once; open_log() opens the log (fresh per
boot for those two, appended for the Indy as before) and is the Indy's
only use of the helper, since its console is the Z85C30's existing
channel B listener.

No guest-visible change. Log messages now carry the machine name
("Indy" for the Indy's two), and the Octane's port-unavailable message
gains the O2's "is another iris using it" hint.
The O2's 16550 was "just enough to be written to": the line status
always said ready, the interrupt cause was a guess from the enable bits,
there was one byte of receive queue with no overrun, and loopback only
reached the modem status. The Octane's IOC3 carried a separate, fuller
16550 of its own.

ns16550.rs is now a port of QEMU's hw/char/serial.c (MIT, notice kept):
the register file, the 16-byte receive FIFO and its trigger levels,
interrupt causes in priority order, the character timeout, line and modem
status, loopback and overrun. Transmit stays instant, with no baud
pacing; the module doc lists every place it departs from QEMU. The MACE
shell (the lane decode, interrupt line and monitor) moves to
mace_uart.rs, and the serial DMA engine keeps its own code, taking its
characters from the core through dma_write, dma_read and
dma_modem_control.

Snapshots: each O2 UART now saves the core's state, so the schema
version goes to 5 and an O2 snapshot from before is refused. Nothing else
changed shape.

Guest-visible changes on the O2 serial: the FIFO, the trigger levels and
the timeout work as on a 16550A; the receive-data interrupt waits for the
trigger level; the line status reports overruns and the transmit
interrupt follows the holding register.
The IOC3 carried its own 16550 next to the O2's. It now wraps ns16550.rs
and keeps only what the chip adds around it: the pre-divider on the
scratch register, the auto-flow bit in modem control, the factory-test
writes to line and modem status, and the register layout with its ring
DMA. The pre-divider sets the core's reference clock, so the receive
timeout follows it. Ported from hw/misc/sgi-ioc3.c in
jamesbraid/qemu-sgi-origami.

The register-level tests written before the swap pass unchanged. To keep
one of them (an empty receive register reads zero, as both earlier
models answered) the core returns zero rather than QEMU's stale
character; the module doc lists it.

Guest-visible changes: with the FIFOs on, the receive interrupt waits for
the trigger level and characters below it raise the timeout cause after
four character times; toggling the FIFO enable flushes both FIFOs.
@jamesbraid
jamesbraid force-pushed the serial-16550 branch 2 times, most recently from bccf291 to 1378781 Compare October 30, 2026 03:20
@techomancer
techomancer merged commit 24c28ae into techomancer:main Oct 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants