use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{Budget, Consumed};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::host::chardev::{CharDevice, ports};
use crate::machine::realize::Instance;
use super::dt::{DtSource, NodeSpec};
pub const CLASS_NAME: &str = "uart.ns16550";
const STATE_VERSION: u32 = 1;
pub const REGISTER_WINDOW_LEN: u64 = 0x100;
pub const FIFO_DEPTH: usize = 16;
const DEFAULT_PORT: &str = "console";
const DEFAULT_FREQUENCY_HZ: u32 = 3_686_400;
const LSR_DR: u8 = 0x01;
const LSR_OE: u8 = 0x02;
const LSR_THRE: u8 = 0x20;
const LSR_TEMT: u8 = 0x40;
const IER_ERBFI: u8 = 0x01;
const IER_ETBEI: u8 = 0x02;
const IER_ELSI: u8 = 0x04;
const IER_MASK: u8 = 0x0f;
const IIR_NONE: u8 = 0x01;
const IIR_RLS: u8 = 0x06;
const IIR_RDA: u8 = 0x04;
const IIR_THRE: u8 = 0x02;
const IIR_FIFO_ENABLED: u8 = 0xc0;
const LCR_DLAB: u8 = 0x80;
const MCR_LOOP: u8 = 0x10;
const MCR_MASK: u8 = 0x1f;
#[derive(Debug, Default)]
struct State {
rx: VecDeque<u8>,
tx_hold: Option<u8>,
ier: u8,
lcr: u8,
mcr: u8,
fcr: u8,
scr: u8,
divisor: u16,
errors: u8,
thre_latch: bool,
}
struct Registers {
state: Mutex<State>,
out: Mutex<Option<WireSource>>,
port: Arc<dyn CharDevice>,
port_name: String,
frequency_hz: u32,
irq_wire: Mutex<Option<crate::core::wire::WireId>>,
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Registers");
s.field("port", &self.port_name);
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
#[derive(Debug)]
pub struct Uart16550 {
regs: Arc<Registers>,
region: RegionRef,
}
impl Uart16550 {
pub fn new(props: &Props) -> Result<Uart16550> {
let mut r = props.reader();
let port_name = r.or("port", String::from(DEFAULT_PORT))?;
let frequency = r.or_range(
"frequency",
u64::from(DEFAULT_FREQUENCY_HZ),
1..=u64::from(u32::MAX),
)?;
r.finish()?;
Ok(Uart16550::with_port(
ports::attach(props, &port_name)?,
port_name,
frequency as u32,
))
}
#[must_use]
pub fn with_port(port: Arc<dyn CharDevice>, port_name: String, frequency_hz: u32) -> Uart16550 {
let regs = Arc::new(Registers {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
out: Mutex::with_rank(LockRank::LEAF, None),
port,
port_name,
frequency_hz,
irq_wire: Mutex::with_rank(LockRank::LEAF, None),
});
let region: RegionRef = Arc::new(Region::io(
"uart.ns16550",
REGISTER_WINDOW_LEN,
Arc::clone(®s) as Arc<dyn MemOps>,
));
Uart16550 { regs, region }
}
#[must_use]
pub fn port_name(&self) -> &str {
&self.regs.port_name
}
#[must_use]
pub fn frequency_hz(&self) -> u32 {
self.regs.frequency_hz
}
pub fn pump(&self) {
self.regs.pump();
}
#[must_use]
pub fn irq_asserted(&self) -> bool {
Registers::interrupt(&self.regs.state.lock()) != IIR_NONE
}
}
impl Registers {
fn interrupt(state: &State) -> u8 {
if state.ier & IER_ELSI != 0 && state.errors != 0 {
return IIR_RLS;
}
if state.ier & IER_ERBFI != 0 && !state.rx.is_empty() {
return IIR_RDA;
}
if state.ier & IER_ETBEI != 0 && state.thre_latch {
return IIR_THRE;
}
IIR_NONE
}
fn drive(&self, asserted: bool) {
let out = self.out.lock().clone();
if let Some(out) = out {
out.set(Level::from_bool(asserted));
}
}
fn refresh(&self) {
let asserted = Self::interrupt(&self.state.lock()) != IIR_NONE;
self.drive(asserted);
}
fn flush_tx(&self, state: &mut State) -> bool {
let Some(byte) = state.tx_hold else {
return false;
};
if state.mcr & MCR_LOOP != 0 {
state.tx_hold = None;
Self::receive(state, byte);
state.thre_latch = true;
return true;
}
if !self.port.write_byte(byte) {
return false;
}
state.tx_hold = None;
state.thre_latch = true;
true
}
fn receive(state: &mut State, byte: u8) {
if state.rx.len() >= FIFO_DEPTH {
state.errors |= LSR_OE;
return;
}
state.rx.push_back(byte);
}
fn pump(&self) {
{
let mut state = self.state.lock();
self.flush_tx(&mut state);
if state.mcr & MCR_LOOP == 0 {
while state.rx.len() < FIFO_DEPTH {
let Some(byte) = self.port.read_byte() else {
break;
};
state.rx.push_back(byte);
}
}
}
self.refresh();
}
fn lsr(state: &State) -> u8 {
let mut lsr = state.errors;
if !state.rx.is_empty() {
lsr |= LSR_DR;
}
if state.tx_hold.is_none() {
lsr |= LSR_THRE | LSR_TEMT;
}
lsr
}
fn read_register(&self, index: u8, debug: bool) -> u8 {
let mut state = self.state.lock();
let dlab = state.lcr & LCR_DLAB != 0;
match index {
0 if dlab => state.divisor as u8,
0 => {
if debug {
return state.rx.front().copied().unwrap_or(0);
}
state.rx.pop_front().unwrap_or(0)
}
1 if dlab => (state.divisor >> 8) as u8,
1 => state.ier,
2 => {
let iir = Self::interrupt(&state);
if !debug && iir == IIR_THRE {
state.thre_latch = false;
}
let fifo = if state.fcr & 1 != 0 {
IIR_FIFO_ENABLED
} else {
0
};
iir | fifo
}
3 => state.lcr,
4 => state.mcr,
5 => {
let lsr = Self::lsr(&state);
if !debug {
state.errors = 0;
}
lsr
}
6 => {
if state.mcr & MCR_LOOP != 0 {
let mcr = state.mcr;
(mcr & 0x01) << 4 | (mcr & 0x02) << 4 | (mcr & 0x04) << 4 | (mcr & 0x08) << 4
} else {
0
}
}
_ => state.scr,
}
}
fn write_register(&self, index: u8, value: u8) {
{
let mut state = self.state.lock();
let dlab = state.lcr & LCR_DLAB != 0;
match index {
0 if dlab => state.divisor = (state.divisor & 0xff00) | u16::from(value),
0 => {
if state.tx_hold.is_some() {
state.tx_hold = Some(value);
} else {
state.tx_hold = Some(value);
self.flush_tx(&mut state);
}
state.thre_latch = state.tx_hold.is_none();
}
1 if dlab => {
state.divisor = (state.divisor & 0x00ff) | (u16::from(value) << 8);
}
1 => {
let was = state.ier;
state.ier = value & IER_MASK;
if state.ier & IER_ETBEI != 0 && was & IER_ETBEI == 0 && state.tx_hold.is_none()
{
state.thre_latch = true;
}
}
2 => {
state.fcr = value;
if value & 0x02 != 0 {
state.rx.clear();
}
if value & 0x04 != 0 {
state.tx_hold = None;
state.thre_latch = true;
}
}
3 => state.lcr = value,
4 => {
state.mcr = value & MCR_MASK;
self.flush_tx(&mut state);
}
5 | 6 => {}
_ => state.scr = value,
}
}
self.refresh();
}
}
impl MemOps for Registers {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
*byte = self.read_register((offset & 7) as u8, attrs.debug);
if !attrs.debug {
self.refresh();
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
self.write_register((offset & 7) as u8, *value);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U8, Endian::Little)
}
}
impl DtSource for Registers {
fn dt_spec(&self) -> NodeSpec {
let mut spec = NodeSpec::peripheral("serial", &["ns16550a"])
.with_cells("clock-frequency", alloc::vec![self.frequency_hz])
.with_cells("reg-shift", alloc::vec![0])
.with_cells("reg-io-width", alloc::vec![1]);
spec.irq_wire = *self.irq_wire.lock();
spec
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "16550 UART with FIFOs, on a character port",
properties: &[
PropertySpec {
name: "port",
kind: ValueKind::Str,
required: false,
summary: "the character port to attach to, by name (default \"console\")",
},
PropertySpec {
name: "frequency",
kind: ValueKind::Uint,
required: false,
summary: "the reference clock the device tree reports, in Hz (default 3686400)",
},
],
construct: |props| Ok(Box::new(Uart16550::new(props)?)),
};
impl Device for Uart16550 {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, ctx: &mut RealizeCtx<'_>) -> Result<()> {
super::dt::publish(
ctx.hosts(),
&self.region,
Arc::downgrade(&self.regs) as Weak<dyn DtSource>,
)
}
fn reset(&self, _kind: ResetKind) {
{
let mut state = self.regs.state.lock();
*state = State::default();
}
self.regs.drive(false);
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != "irq" {
return Err(Error::Config {
at: port.to_string(),
message: String::from("a 16550 drives one pin, `irq`"),
});
}
*self.regs.irq_wire.lock() = Some(source.id());
*self.regs.out.lock() = Some(source);
Ok(())
}
fn announce(&self, port: &str) {
if port == "irq" {
self.regs.refresh();
}
}
fn is_runnable(&self) -> bool {
true
}
fn run(&self, budget: Budget) -> Consumed {
self.regs.pump();
Consumed::new(budget.ticks)
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.regs.state.lock();
w.write_seq_len(state.rx.len() as u64)?;
for byte in &state.rx {
w.write_u8(*byte)?;
}
match state.tx_hold {
None => w.write_bool(false)?,
Some(byte) => {
w.write_bool(true)?;
w.write_u8(byte)?;
}
}
for byte in [
state.ier,
state.lcr,
state.mcr,
state.fcr,
state.scr,
state.errors,
] {
w.write_u8(byte)?;
}
w.write_u16(state.divisor)?;
w.write_bool(state.thre_latch)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let count = r.read_seq_len(1)? as usize;
if count > FIFO_DEPTH {
return Err(Error::State(alloc::format!(
"snapshot has {count} byte(s) in a {FIFO_DEPTH}-byte receive FIFO"
)));
}
let mut state = State::default();
for _ in 0..count {
state.rx.push_back(r.read_u8()?);
}
state.tx_hold = if r.read_bool()? {
Some(r.read_u8()?)
} else {
None
};
state.ier = r.read_u8()?;
state.lcr = r.read_u8()?;
state.mcr = r.read_u8()?;
state.fcr = r.read_u8()?;
state.scr = r.read_u8()?;
state.errors = r.read_u8()?;
state.divisor = r.read_u16()?;
state.thre_latch = r.read_bool()?;
*self.regs.state.lock() = state;
self.regs.refresh();
Ok(())
}
}
impl Instance for Uart16550 {}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(Uart16550::new(props)?)))
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("port", ValueKind::Str))
.prop(PropSchema::new("frequency", ValueKind::Uint).range(1, u64::from(u32::MAX)))
.region("")
.region("regs")
.port("irq", PortDir::Out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::sync::{AtomicU32, Ordering};
use crate::core::wire::{Wire, WireId, WireIdAllocator, WireSink};
use crate::host::chardev::CharPort;
fn wired() -> (Uart16550, Arc<CharPort>) {
let port = Arc::new(CharPort::new());
let uart = Uart16550::with_port(
Arc::clone(&port) as Arc<dyn CharDevice>,
"test".to_string(),
DEFAULT_FREQUENCY_HZ,
);
(uart, port)
}
fn peek(u: &Uart16550, index: u64) -> u8 {
let mut byte = [0u8; 1];
u.regs
.read(index, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
byte[0]
}
fn poke(u: &Uart16550, index: u64, value: u8) {
u.regs
.write(index, &[value], MemAttrs::DEFAULT)
.expect("a byte write is legal");
}
#[derive(Debug, Default)]
struct Probe {
level: AtomicU32,
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.level
.store(u32::from(level.is_high()), Ordering::Relaxed);
}
}
fn with_irq() -> (Uart16550, Arc<CharPort>, Arc<Probe>) {
let (uart, port) = wired();
let ids = WireIdAllocator::new();
let id = ids.alloc();
let probe = Arc::new(Probe::default());
let wire = Wire::builder()
.source(id)
.sink(Arc::clone(&probe) as Arc<dyn WireSink>, 0)
.build_shared();
uart.connect("irq", WireSource::new(wire, id))
.expect("a 16550 drives irq");
(uart, port, probe)
}
#[test]
fn a_byte_written_to_thr_reaches_the_host() {
let (uart, port) = wired();
poke(&uart, 0, b'A');
assert_eq!(port.drain(), b"A".to_vec());
assert_eq!(peek(&uart, 5) & (LSR_THRE | LSR_TEMT), LSR_THRE | LSR_TEMT);
}
#[test]
fn a_byte_fed_by_the_host_is_read_from_rbr_once() {
let (uart, port) = wired();
port.feed(b"hi");
uart.pump();
assert_eq!(peek(&uart, 5) & LSR_DR, LSR_DR);
assert_eq!(peek(&uart, 0), b'h');
assert_eq!(peek(&uart, 0), b'i');
assert_eq!(peek(&uart, 5) & LSR_DR, 0);
assert_eq!(peek(&uart, 0), 0, "an empty FIFO reads as zero");
}
#[test]
fn the_registers_repeat_because_only_three_address_lines_are_decoded() {
let (uart, _port) = wired();
poke(&uart, 7, 0x5a);
assert_eq!(peek(&uart, 7), 0x5a);
assert_eq!(peek(&uart, 0x0f), 0x5a, "offset 15 is offset 7");
assert_eq!(peek(&uart, 0xff), 0x5a);
}
#[test]
fn the_scratch_register_is_how_software_finds_the_chip() {
let (uart, _port) = wired();
for probe in [0x00u8, 0xa5, 0xff] {
poke(&uart, 7, probe);
assert_eq!(peek(&uart, 7), probe);
}
}
#[test]
fn dlab_swaps_the_first_two_registers_for_the_divisor() {
let (uart, port) = wired();
poke(&uart, 3, LCR_DLAB);
poke(&uart, 0, 0x0c);
poke(&uart, 1, 0x01);
assert_eq!(peek(&uart, 0), 0x0c);
assert_eq!(peek(&uart, 1), 0x01);
assert!(port.drain().is_empty(), "and nothing was transmitted");
poke(&uart, 3, 0x03);
poke(&uart, 0, b'X');
assert_eq!(port.drain(), b"X".to_vec());
}
#[test]
fn received_data_raises_the_interrupt_and_reading_it_clears_it() {
let (uart, port, probe) = with_irq();
port.feed(b"z");
uart.pump();
assert_eq!(probe.level.load(Ordering::Relaxed), 0, "not enabled yet");
poke(&uart, 1, IER_ERBFI);
assert_eq!(probe.level.load(Ordering::Relaxed), 1);
assert_eq!(peek(&uart, 2) & 0x0f, IIR_RDA);
assert_eq!(peek(&uart, 0), b'z');
assert_eq!(probe.level.load(Ordering::Relaxed), 0);
assert_eq!(peek(&uart, 2) & 0x0f, IIR_NONE);
}
#[test]
fn enabling_the_transmit_interrupt_with_an_empty_holding_register_raises_one() {
let (uart, _port, probe) = with_irq();
poke(&uart, 1, IER_ETBEI);
assert_eq!(probe.level.load(Ordering::Relaxed), 1);
assert_eq!(peek(&uart, 2) & 0x0f, IIR_THRE);
assert_eq!(probe.level.load(Ordering::Relaxed), 0);
poke(&uart, 0, b'q');
assert_eq!(probe.level.load(Ordering::Relaxed), 1);
}
#[test]
fn line_status_beats_received_data_beats_transmitter_empty() {
let (uart, port, _probe) = with_irq();
poke(&uart, 1, IER_ERBFI | IER_ETBEI | IER_ELSI);
port.feed(b"x");
uart.pump();
assert_eq!(peek(&uart, 2) & 0x0f, IIR_RDA, "data beats THRE");
poke(&uart, 4, MCR_LOOP);
for _ in 0..=FIFO_DEPTH {
poke(&uart, 0, b'y');
}
assert_eq!(peek(&uart, 2) & 0x0f, IIR_RLS, "and line status beats both");
assert_eq!(peek(&uart, 5) & LSR_OE, LSR_OE);
assert_eq!(peek(&uart, 5) & LSR_OE, 0, "reading LSR clears it");
}
#[test]
fn a_host_that_will_not_take_a_byte_stalls_the_transmitter() {
let (uart, port) = wired();
while port.writable() {
port.write(b".");
}
poke(&uart, 0, b'A');
assert_eq!(peek(&uart, 5) & LSR_THRE, 0, "the holding register is full");
let _ = port.drain();
uart.pump();
assert_eq!(peek(&uart, 5) & LSR_THRE, LSR_THRE);
assert_eq!(port.drain(), b"A".to_vec(), "and the byte was not lost");
}
#[test]
fn loopback_ties_the_transmitter_to_the_receiver() {
let (uart, port) = wired();
poke(&uart, 4, MCR_LOOP);
poke(&uart, 0, b'L');
assert!(port.drain().is_empty(), "nothing reaches the host");
assert_eq!(peek(&uart, 5) & LSR_DR, LSR_DR);
assert_eq!(peek(&uart, 0), b'L');
poke(&uart, 4, MCR_LOOP | 0x01);
assert_eq!(peek(&uart, 6) & 0x10, 0x10, "RTS appears as CTS");
}
#[test]
fn the_fifo_control_register_reports_a_16550a() {
let (uart, _port) = wired();
assert_eq!(peek(&uart, 2) & IIR_FIFO_ENABLED, 0, "FIFOs start off");
poke(&uart, 2, 0x01);
assert_eq!(peek(&uart, 2) & IIR_FIFO_ENABLED, IIR_FIFO_ENABLED);
}
#[test]
fn clearing_the_receive_fifo_discards_what_was_in_it() {
let (uart, port) = wired();
port.feed(b"abc");
uart.pump();
poke(&uart, 2, 0x01 | 0x02);
assert_eq!(peek(&uart, 5) & LSR_DR, 0);
}
#[test]
fn a_debug_read_pops_nothing_and_a_debug_write_is_refused() {
let (uart, port) = wired();
port.feed(b"k");
uart.pump();
let mut byte = [0u8; 1];
uart.regs.read(0, &mut byte, MemAttrs::DEBUG).unwrap();
assert_eq!(byte[0], b'k');
assert_eq!(peek(&uart, 5) & LSR_DR, LSR_DR, "still there");
assert_eq!(peek(&uart, 0), b'k');
assert!(uart.regs.write(0, b"x", MemAttrs::DEBUG).is_err());
assert!(port.drain().is_empty());
}
#[test]
fn an_access_that_is_not_a_single_byte_is_refused() {
let (uart, _port) = wired();
assert!(uart.regs.read(0, &mut [0u8; 2], MemAttrs::DEFAULT).is_err());
assert!(uart.regs.write(0, &[0u8; 4], MemAttrs::DEFAULT).is_err());
}
#[test]
fn a_snapshot_round_trips_the_register_file_and_the_fifo() {
let (saved, port) = wired();
port.feed(b"abc");
saved.pump();
poke(&saved, 3, LCR_DLAB);
poke(&saved, 0, 0x0c);
poke(&saved, 3, 0x1b);
poke(&saved, 1, IER_ERBFI);
poke(&saved, 7, 0x77);
let mut shape = MachineShape::new();
shape.add_device("uart", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("uart", CLASS.name, CLASS.version).unwrap();
saved.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let (restored, _other) = wired();
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load("uart", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
restored.load(&mut chunk.reader()).unwrap();
assert_eq!(peek(&restored, 3), 0x1b);
assert_eq!(peek(&restored, 7), 0x77);
assert_eq!(peek(&restored, 1), IER_ERBFI);
assert_eq!(peek(&restored, 0), b'a', "and the FIFO came back");
assert!(restored.irq_asserted());
}
#[test]
fn properties_are_checked_rather_than_ignored() {
let uart = Uart16550::new(&Props::new().with("frequency", 1_843_200u64))
.expect("a frequency is legal");
assert_eq!(uart.frequency_hz(), 1_843_200);
assert_eq!(uart.port_name(), DEFAULT_PORT);
assert!(Uart16550::new(&Props::new().with("prot", "x")).is_err());
}
}