Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ hashes are given where a change is easiest to understand by reading the commit.
the Indigo2's second controller fold pending CHD diffs on a clean exit as the
first controller always did.

### Serial ports

- The O2 and Octane serial ports share one 16550A core ported from QEMU. On the
O2 this adds the receive FIFO with trigger levels and the character timeout;
O2 snapshots from before are refused (snapshot schema 5).
- Serial host backends moved out of the Z85C30 into `src/dev/serial_host.rs`.

### Host efficiency on small machines

- **Threads that have nothing to do now sleep in milliseconds, not
Expand Down
9 changes: 9 additions & 0 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ channel), `FifoDevice` (serial byte streams), `InterruptController`/`IrqLine`
(below), `GfxDisplay` (a graphics board's screen, for the window and
screenshots), `CpuDevice` (what `Machine` holds for the CPU).

Serial lines have two halves. A UART model owns the registers: the Indy's
`Z85c30`, and `Ns16550` (`src/dev/ns16550.rs`), the 16550A core that the O2's
`MaceUart` (lane decode, interrupt line, monitor) and the Octane's IOC3 wrap.
The host end is a `SerialBackend` from `src/dev/serial_host.rs`, which the
machine constructors pick with `console_backends`: the CI backend, a TCP
console, or a log tee. A UART's transmit is instant and its receive side
pulls from the backend with back-pressure (`pump`), so nothing in the model
needs a thread of its own beyond the service loop's call.

### The physical address map

`Physical` (`src/physical.rs`) is the physical bus, and it belongs to no
Expand Down
2 changes: 1 addition & 1 deletion src/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use serde_json::Value;

use crate::workstation::{MachineSlot, Workstation};
use crate::gfx_display::GfxDisplay;
use crate::dev::z85c30::CiSerialBackend;
use crate::dev::serial_host::CiSerialBackend;

/// Set at `start_server`; consulted by `quit` so the socket file is cleaned up
/// before `std::process::exit` (which skips Drop).
Expand Down
240 changes: 32 additions & 208 deletions src/dev/ioc3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ use crate::dev::ioc3_port::Ioc3EthPort;
use crate::dev::ps2::Ps2Controller;
use crate::dev::w1;
use crate::dev::w1::sgi::{self, BoardRecord};
use crate::dev::z85c30::{null_serial_backend, SerialBackend};
use crate::dev::ns16550::Ns16550;
use crate::dev::serial_host::SerialBackend;
use crate::irq::{InterruptController, IrqLine};
use crate::pci::{Bar, PciBusDevice, PciConfig, PciDma, PciDmaStatus, PCI_INTA};
use crate::hptimer::{TimerId, TimerManager, TimerReturn};
Expand Down Expand Up @@ -144,9 +145,7 @@ pub mod int {

mod uart_reg {
pub const RBR_THR: u32 = 0;
pub const IER: u32 = 1;
pub const IIR_FCR: u32 = 2;
pub const LCR: u32 = 3;
pub const MCR: u32 = 4;
pub const LSR: u32 = 5;
pub const MSR: u32 = 6;
Expand All @@ -157,7 +156,6 @@ const LSR_OE: u8 = 0x02;
const LSR_PE: u8 = 0x04;
const LSR_FE: u8 = 0x08;
const LSR_BI: u8 = 0x10;
const LSR_THRE_TEMT: u8 = 0x60;
const MCR_DTR: u8 = 0x01;
const MCR_RTS: u8 = 0x02;
const MCR_OUT1: u8 = 0x04;
Expand All @@ -167,27 +165,22 @@ const MCR_AFE: u8 = 0x20;
const MSR_DCTS: u8 = 0x01;
const MSR_CTS: u8 = 0x10;
const MSR_DCD: u8 = 0x80;
const LCR_DLAB: u8 = 0x80;
const UART_FIFO: usize = 16;
const LSR_TEST_MASK: u8 = 0x7f;
const MSR_TEST_MASK: u8 = 0x0f;
/// The reference clock of the UART crystal: the pre-divider divides it, and
/// the divisor latch divides the result by 16 again.
const UART_CRYSTAL_HZ: u32 = 22_118_400;

/// A 16550 as IOC3 fits it. Transmit is instantaneous (the byte goes to the
/// A 16550 as IOC3 fits it: the shared core (`ns16550.rs`) with the chip's
/// own additions around it. Transmit is instantaneous (the byte goes to the
/// backend inside the write), so the transmitter always reads empty.
///
/// The additions: the auto-flow bit in modem control, which the core's modem
/// control masks away; factory-test writes to line and modem status; and the
/// scratch register doubling as the clock pre-divider while the divisor latch
/// is open. Ported from hw/misc/sgi-ioc3.c in jamesbraid/qemu-sgi-origami.
struct Uart {
backend: Arc<dyn SerialBackend>,
rx: VecDeque<u8>,
ier: u8,
fifo: bool,
lcr: u8,
mcr: u8,
scr: u8,
dll: u8,
dlm: u8,
lsr_err: u8,
msr_delta: u8,
msr_prev: u8,
thr_int: bool,
core: Ns16550,
// IOC3 specifics, kept beside the 16550.
predivider: u8,
mcr_afe: u8,
Expand All @@ -197,159 +190,37 @@ struct Uart {

impl Uart {
fn new() -> Uart {
let mut u = Uart {
backend: null_serial_backend(),
rx: VecDeque::new(),
ier: 0,
fifo: false,
lcr: 0,
mcr: MCR_OUT2,
scr: 0,
dll: 0x0c,
dlm: 0,
lsr_err: 0,
msr_delta: 0,
msr_prev: 0xb0,
thr_int: false,
predivider: 0,
mcr_afe: 0,
lsr_test: 0,
msr_test: 0,
};
u.reset();
u
Uart { core: Ns16550::new(115200), predivider: 0, mcr_afe: 0, lsr_test: 0, msr_test: 0 }
}

fn reset(&mut self) {
self.rx.clear();
self.ier = 0;
self.fifo = false;
self.lcr = 0;
self.mcr = MCR_OUT2;
self.scr = 0;
self.dll = 0x0c;
self.dlm = 0;
self.lsr_err = 0;
self.msr_delta = 0;
self.msr_prev = self.msr_levels();
self.thr_int = false;
self.core.reset();
self.mcr_afe = 0;
self.lsr_test = 0;
self.msr_test = 0;
// The pre-divider keeps its value across a reset; no reset value is
// specified for it.
}

fn dlab(&self) -> bool {
self.lcr & LCR_DLAB != 0
}

fn loopback(&self) -> bool {
self.mcr & MCR_LOOP != 0
}

/// The modem status input levels (bits 4..7). The port comes up with
/// carrier and clear-to-send asserted; in loopback the outputs wire back.
fn msr_levels(&self) -> u8 {
if self.loopback() {
((self.mcr & (MCR_OUT1 | MCR_OUT2)) << 4)
| ((self.mcr & MCR_RTS) << 3)
| ((self.mcr & MCR_DTR) << 5)
} else {
0xb0
}
}

fn lsr_now(&self) -> u8 {
let mut v = LSR_THRE_TEMT | self.lsr_err;
if !self.rx.is_empty() {
v |= LSR_DR;
}
v
}

fn iir(&self) -> u8 {
let id = if self.ier & 4 != 0 && self.lsr_err & (LSR_OE | LSR_PE | LSR_FE | LSR_BI) != 0 {
0x06
} else if self.ier & 1 != 0 && !self.rx.is_empty() {
0x04
} else if self.ier & 2 != 0 && self.thr_int {
0x02
} else if self.ier & 8 != 0 && self.msr_delta & 0x0f != 0 {
0x00
} else {
0x01
};
id | if self.fifo { 0xc0 } else { 0 }
self.core.loopback()
}

/// The interrupt line: low IIR bit clear.
fn irq(&self) -> bool {
self.iir() & 1 == 0
}

fn push_rx(&mut self, b: u8) {
let cap = if self.fifo { UART_FIFO } else { 1 };
if self.rx.len() >= cap {
self.lsr_err |= LSR_OE;
} else {
self.rx.push_back(b);
}
self.core.irq()
}

/// Move whatever the host end has typed into the receive FIFO.
/// Returns whether anything arrived.
/// Move whatever the host end has typed into the receive FIFO and let
/// the receive timeout run out. Returns whether anything changed.
fn pump(&mut self) -> bool {
let cap = if self.fifo { UART_FIFO } else { 1 };
let mut moved = false;
while self.rx.len() < cap {
match self.backend.recv_byte() {
Ok(b) => {
self.rx.push_back(b);
moved = true;
}
Err(_) => break,
}
}
moved
let moved = self.core.pump();
self.core.check_timeout(Instant::now()) || moved
}

/// The 16550 proper: what the ring engine sees.
fn read_core(&mut self, r: u32) -> u8 {
match r {
uart_reg::RBR_THR => {
if self.dlab() {
self.dll
} else {
self.pump();
self.rx.pop_front().unwrap_or(0)
}
}
uart_reg::IER => if self.dlab() { self.dlm } else { self.ier },
uart_reg::IIR_FCR => {
let v = self.iir();
if v & 0x0f == 0x02 {
self.thr_int = false;
}
v
}
uart_reg::LCR => self.lcr,
uart_reg::MCR => self.mcr,
uart_reg::LSR => {
self.pump();
// Reading clears the error latches.
let v = self.lsr_now();
self.lsr_err = 0;
v
}
uart_reg::MSR => {
let v = self.msr_levels() | self.msr_delta;
self.msr_delta = 0;
self.msr_prev = self.msr_levels();
v
}
_ => self.scr,
}
self.core.read(r)
}

/// A guest's byte-wide read, with the chip's additions: the auto-flow
Expand All @@ -374,67 +245,20 @@ impl Uart {

fn write(&mut self, r: u32, v: u8) {
match r {
uart_reg::RBR_THR => {
if self.dlab() {
self.dll = v;
} else {
self.transmit(v);
}
}
uart_reg::IER => {
if self.dlab() {
self.dlm = v;
} else {
let was = self.ier;
self.ier = v & 0x0f;
// Enabling the transmit interrupt with the holding
// register empty raises it at once.
if was & 2 == 0 && self.ier & 2 != 0 {
self.thr_int = true;
}
}
}
uart_reg::IIR_FCR => {
self.fifo = v & 1 != 0;
if v & 2 != 0 {
self.rx.clear();
}
}
uart_reg::LCR => self.lcr = v,
uart_reg::MCR => {
self.mcr_afe = v & MCR_AFE;
self.mcr = v & 0x1f;
let now = self.msr_levels();
let changed = (now ^ self.msr_prev) >> 4;
// CTS, DSR change bits straight; RI only on its trailing
// edge; carrier changes.
let mut delta = changed & 0x0b;
if self.msr_prev & 0x40 != 0 && now & 0x40 == 0 {
delta |= 0x04;
}
self.msr_delta |= delta;
self.msr_prev = now;
}
uart_reg::MCR => self.mcr_afe = v & MCR_AFE,
uart_reg::LSR => self.lsr_test = v & LSR_TEST_MASK,
uart_reg::MSR => self.msr_test = v & MSR_TEST_MASK,
_ => {
self.scr = v;
uart_reg::SCR => {
// Scratch writes with the divisor latch up program the clock
// pre-divider; twice the divide wanted. 0 and 1 mean nothing.
if self.dlab() && v / 2 != 0 {
if self.core.dlab() && v / 2 != 0 {
self.predivider = v;
self.core.set_baudbase(UART_CRYSTAL_HZ / (v / 2) as u32 / 16);
}
}
_ => {}
}
}

fn transmit(&mut self, b: u8) {
if self.loopback() {
self.push_rx(b);
} else {
self.backend.send_byte(b);
}
self.thr_int = true;
self.core.write(r, v);
}

/// Modem status as the ring engine reads it with flow control looped back.
Expand Down Expand Up @@ -1414,9 +1238,9 @@ impl St {
/// A passive mirror of the 16550's lines; no read side effects.
fn sdma_shadow(&self, p: usize) -> u32 {
let u = &self.uart[p];
let lsr = u.lsr_now();
let mcr = u.mcr;
let mut msr = if u.loopback() { u.msr_levels() } else { 0xb0 | u.msr_delta };
let lsr = u.core.lsr();
let mcr = u.core.mcr();
let mut msr = u.core.msr();
if u.flow_cts(self.sdma_ctl(p)) {
msr |= MSR_CTS;
}
Expand Down Expand Up @@ -2590,7 +2414,7 @@ impl Ioc3 {
/// Wire the host end of serial port `port` (0 is the console): a TCP
/// listener, a log tee, or nothing.
pub fn set_serial_backend(&self, port: usize, backend: Arc<dyn SerialBackend>) {
self.sh.st.lock().uart[port].backend = backend;
self.sh.st.lock().uart[port].core.set_backend(backend);
}

/// Where parallel-port output goes. Without one it is dropped. No machine
Expand Down
5 changes: 3 additions & 2 deletions src/dev/ioc3_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ fn the_scratch_register_programs_the_predivider_under_dlab() {
r.w8(reg::SERIAL_A + 7, 6);
assert_eq!(r.r8(reg::SERIAL_A + 7), 6, "reads back what was written");
assert_eq!(r.c.sh.st.lock().uart[0].predivider, 6);
assert_eq!(r.c.sh.st.lock().uart[0].core.baudbase(), 22_118_400 / 3 / 16, "the divisor's clock follows it");
// Written 0 or 1 means nothing.
r.w8(reg::SERIAL_A + 7, 1);
assert_eq!(r.c.sh.st.lock().uart[0].predivider, 6);
Expand Down Expand Up @@ -1359,9 +1360,9 @@ fn stop_joins_the_threads_and_removes_the_timers() {
// Typed input waits in the backend while stopped.
r.wire[0].inp.lock().unwrap().extend(b"x");
std::thread::sleep(Duration::from_millis(20));
assert!(r.c.sh.st.lock().uart[0].rx.is_empty());
assert!(r.c.sh.st.lock().uart[0].core.rx_pending() == 0);
r.c.start();
assert!(within_two_seconds(|| !r.c.sh.st.lock().uart[0].rx.is_empty()));
assert!(within_two_seconds(|| r.c.sh.st.lock().uart[0].core.rx_pending() != 0));
assert!(r.c.sh.armed.lock()[K_INT_OUT].is_some(), "start arms the deadlines the chip holds");
r.c.stop();
}
Expand Down
Loading
Loading