pub mod resolver;
mod spec;
use self::spec::FIFO_SIZE;
use self::spec::FifoControlRegister;
use self::spec::FifoState;
use self::spec::InterruptEnableRegister;
use self::spec::InterruptIdentificationRegister;
use self::spec::InterruptSource;
use self::spec::LineControlRegister;
use self::spec::LineStatusRegister;
use self::spec::ModemControlRegister;
use self::spec::ModemStatusRegister;
use self::spec::Register;
use self::spec::RxFifoInterruptTrigger;
use chipset_device::ChipsetDevice;
use chipset_device::io::IoError;
use chipset_device::io::IoResult;
use chipset_device::mmio::MmioIntercept;
use chipset_device::pio::PortIoIntercept;
use chipset_device::poll_device::PollDevice;
use futures::AsyncRead;
use futures::AsyncWrite;
use inspect::Inspect;
use inspect::InspectMut;
use inspect_counters::Counter;
use serial_16550_resources::MmioOrIoPort;
use serial_core::SerialIo;
use std::collections::VecDeque;
use std::io::ErrorKind;
use std::ops::RangeInclusive;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
use std::task::Waker;
use std::task::ready;
use thiserror::Error;
use vmcore::device_state::ChangeDeviceState;
use vmcore::line_interrupt::LineInterrupt;
#[derive(InspectMut)]
pub struct Serial16550 {
#[inspect(skip)]
debug_name: String,
#[inspect(skip)]
io_region: Option<(&'static str, RangeInclusive<u16>)>,
#[inspect(skip)]
mmio_region: Option<(&'static str, RangeInclusive<u64>)>,
register_width: u8,
register_shift: u8,
wait_for_rts: bool,
interrupt: LineInterrupt,
#[inspect(mut)]
io: Box<dyn SerialIo>,
state: State,
#[inspect(skip)]
rx_waker: Option<Waker>,
#[inspect(skip)]
tx_waker: Option<Waker>,
stats: SerialStats,
}
#[derive(Inspect, Default)]
struct SerialStats {
rx_bytes: Counter,
tx_bytes: Counter,
rx_dropped: Counter,
tx_dropped: Counter,
}
#[derive(Inspect)]
struct State {
ier: InterruptEnableRegister,
lcr: LineControlRegister,
mcr: ModemControlRegister,
msr: ModemStatusRegister,
fcr: FifoControlRegister,
#[inspect(hex)]
dll: u8,
#[inspect(hex)]
dlm: u8,
#[inspect(hex)]
scratch: u8,
thr_empty_acknowledged: bool,
rx_overrun: bool,
#[inspect(with = "VecDeque::len")]
tx_buffer: VecDeque<u8>,
#[inspect(with = "VecDeque::len")]
rx_buffer: VecDeque<u8>,
}
const TX_BUFFER_MAX: usize = 256;
const RX_BUFFER_MAX: usize = 256;
#[derive(Debug, Error)]
pub enum ConfigurationError {
#[error("unaligned base address: {0}")]
UnalignedBaseAddress(u64),
#[error("invalid register width: {0}")]
InvalidRegisterWidth(u8),
}
impl Serial16550 {
pub fn new(
debug_name: String,
base: MmioOrIoPort,
register_width: u8,
interrupt: LineInterrupt,
io: Box<dyn SerialIo>,
wait_for_rts: bool,
) -> Result<Self, ConfigurationError> {
let width = 8 * register_width as u64;
let (base_addr, io_region, mmio_region) = match base {
MmioOrIoPort::Mmio(base) => {
if ![1, 2, 4, 8].contains(®ister_width) {
return Err(ConfigurationError::InvalidRegisterWidth(register_width));
}
(base, None, Some(("registers", base..=base + (width - 1))))
}
MmioOrIoPort::IoPort(base) => {
if register_width != 1 {
return Err(ConfigurationError::InvalidRegisterWidth(register_width));
}
(
base.into(),
Some(("registers", base..=base + (width - 1) as u16)),
None,
)
}
};
if base_addr & (width - 1) != 0 {
return Err(ConfigurationError::UnalignedBaseAddress(base_addr));
}
let mut this = Self {
debug_name,
io_region,
mmio_region,
register_width,
register_shift: register_width.trailing_zeros() as u8,
wait_for_rts,
state: State::new(),
interrupt,
io,
rx_waker: None,
tx_waker: None,
stats: Default::default(),
};
if this.io.is_connected() {
this.state.connect();
}
this.sync();
Ok(this)
}
fn sync(&mut self) {
if !self.state.tx_buffer.is_empty() {
if let Some(waker) = self.tx_waker.take() {
waker.wake();
}
}
if self.state.should_poll_rx(self.wait_for_rts)
&& self.state.rx_buffer.len() <= RX_BUFFER_MAX / 2
{
if let Some(waker) = self.rx_waker.take() {
waker.wake();
}
}
self.interrupt
.set_level(self.state.pending_interrupt().is_some() && self.state.mcr.out2());
}
fn poll_tx(&mut self, cx: &mut Context<'_>) -> Poll<()> {
while !self.state.tx_buffer.is_empty() {
if !self.state.msr.dcd() {
self.stats.tx_dropped.add(self.state.tx_buffer.len() as u64);
self.state.tx_buffer.clear();
break;
}
let (buf, _) = self.state.tx_buffer.as_slices();
match ready!(Pin::new(&mut self.io).poll_write(cx, buf)) {
Ok(n) => {
assert_ne!(n, 0);
self.state.tx_buffer.drain(..n);
self.stats.tx_bytes.add(n as u64);
}
Err(err) if err.kind() == ErrorKind::BrokenPipe => {
tracing::info!(
port = self.debug_name,
"serial output broken pipe, disconnecting"
);
self.state.disconnect();
}
Err(err) => {
tracelimit::error_ratelimited!(
port = self.debug_name,
len = buf.len(),
error = &err as &dyn std::error::Error,
"serial write failed, dropping data"
);
self.stats.tx_dropped.add(buf.len() as u64);
self.state.tx_buffer.drain(..buf.len());
}
}
}
self.tx_waker = Some(cx.waker().clone());
Poll::Pending
}
fn poll_rx(&mut self, cx: &mut Context<'_>) -> Poll<()> {
let mut buf = [0; RX_BUFFER_MAX];
loop {
if !self.state.msr.dcd() {
if let Err(err) = ready!(self.io.poll_connect(cx)) {
tracing::info!(
port = self.debug_name,
error = &err as &dyn std::error::Error,
"serial backend failure"
);
break Poll::Ready(());
}
tracing::info!(port = self.debug_name, "serial connected");
self.state.connect();
}
if !self.state.should_poll_rx(self.wait_for_rts) {
self.rx_waker = Some(cx.waker().clone());
if let Err(err) = ready!(self.io.poll_disconnect(cx)) {
tracing::info!(
port = self.debug_name,
error = &err as &dyn std::error::Error,
"serial backend failure"
);
break Poll::Ready(());
}
tracing::info!(port = self.debug_name, "serial disconnected");
self.state.disconnect();
continue;
}
let avail_space = RX_BUFFER_MAX - self.state.rx_buffer.len();
let buf = &mut buf[..avail_space];
match ready!(Pin::new(&mut self.io).poll_read(cx, buf)) {
Ok(0) => {
tracing::info!(port = self.debug_name, "serial disconnected");
self.state.disconnect();
}
Ok(n) => {
self.state.rx_buffer.extend(&buf[..n]);
self.stats.rx_bytes.add(n as u64);
}
Err(err) => {
tracing::error!(
port = self.debug_name,
error = &err as &dyn std::error::Error,
"failed to read serial input, disconnecting"
);
self.state.disconnect();
break Poll::Ready(());
}
}
}
}
fn register(&self, addr: u64) -> Option<Register> {
if addr as u8 & (self.register_width - 1) != 0 {
return None;
}
Some(Register(
((addr & ((8 << self.register_shift) - 1)) >> self.register_shift) as u8,
))
}
fn read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
let Some(register) = self.register(addr) else {
return IoResult::Err(IoError::UnalignedAccess);
};
let dlab = self.state.lcr.dlab();
data.fill(0);
data[0] = match register {
Register::RHR if !dlab => self.state.read_rhr(),
Register::DLL if dlab => self.state.dll,
Register::IER if !dlab => self.state.ier.into(),
Register::DLM if dlab => self.state.dlm,
Register::ISR => self.state.read_isr(),
Register::LCR => self.state.lcr.into(),
Register::MCR => self.state.mcr.into(),
Register::LSR => self.state.read_lsr(),
Register::MSR => self.state.read_msr(),
Register::SPR => self.state.scratch,
_ => return IoResult::Err(IoError::InvalidRegister),
};
self.sync();
IoResult::Ok
}
fn write(&mut self, addr: u64, data: &[u8]) -> IoResult {
let Some(register) = self.register(addr) else {
return IoResult::Err(IoError::UnalignedAccess);
};
let data = data[0];
let dlab = self.state.lcr.dlab();
match register {
Register::THR if !dlab => self.state.write_thr(&mut self.stats, data),
Register::DLL if dlab => self.state.dll = data,
Register::IER if !dlab => self.state.write_ier(data),
Register::DLM if dlab => self.state.dlm = data,
Register::FCR => self.state.write_fcr(&mut self.stats, data),
Register::LCR => self.state.lcr = data.into(),
Register::MCR => self.state.write_mcr(data),
Register::RST => self.state.write_reset(),
Register::MSR => {}
Register::SPR => self.state.scratch = data,
_ => return IoResult::Err(IoError::InvalidRegister),
};
self.sync();
IoResult::Ok
}
}
impl ChangeDeviceState for Serial16550 {
fn start(&mut self) {}
async fn stop(&mut self) {}
async fn reset(&mut self) {
self.state = State::new();
self.state.connect();
self.sync();
}
}
impl ChipsetDevice for Serial16550 {
fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
self.io_region.is_some().then_some(self)
}
fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
self.mmio_region.is_some().then_some(self)
}
fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
Some(self)
}
}
impl PollDevice for Serial16550 {
fn poll_device(&mut self, cx: &mut Context<'_>) {
let _ = self.poll_tx(cx);
let _ = self.poll_rx(cx);
self.sync();
}
}
impl State {
fn new() -> Self {
Self {
ier: InterruptEnableRegister::new(),
lcr: LineControlRegister::new(),
mcr: ModemControlRegister::new(),
msr: ModemStatusRegister::new(),
dll: 0,
dlm: 0,
scratch: 0xff,
thr_empty_acknowledged: false,
rx_overrun: false,
tx_buffer: VecDeque::new(),
rx_buffer: VecDeque::new(),
fcr: FifoControlRegister::new(),
}
}
fn connect(&mut self) {
self.update_msr(|this| {
this.msr.set_dcd(true);
this.msr.set_cts(true);
this.msr.set_dsr(true);
this.msr.set_ri(false);
});
}
fn disconnect(&mut self) {
self.update_msr(|this| {
this.msr.set_dcd(false);
this.msr.set_cts(false);
this.msr.set_dsr(false);
this.msr.set_ri(false);
})
}
fn rx_fifo_trigger(&self) -> usize {
if self.fcr.enable_fifos() {
match RxFifoInterruptTrigger(self.fcr.rx_fifo_int_trigger()) {
RxFifoInterruptTrigger::BYTES_1 => 1,
RxFifoInterruptTrigger::BYTES_4 => 4,
RxFifoInterruptTrigger::BYTES_8 => 8,
RxFifoInterruptTrigger::BYTES_14 => 14,
_ => unreachable!(),
}
} else {
1
}
}
fn fifo_size(&self) -> usize {
if self.fcr.enable_fifos() {
FIFO_SIZE
} else {
1
}
}
fn is_thr_empty(&self) -> bool {
self.tx_buffer.is_empty()
}
fn should_poll_rx(&self, wait_for_rts: bool) -> bool {
if self.mcr.loopback() {
return false;
}
if !self.msr.dcd() {
return false;
}
if wait_for_rts && (!self.mcr.dtr() || !self.mcr.rts()) {
return false;
}
self.rx_buffer.len() < RX_BUFFER_MAX
}
fn pending_interrupt(&self) -> Option<InterruptSource> {
if self.ier.receiver_line_status() && self.rx_overrun {
return Some(InterruptSource::RECEIVER_LINE_STATUS);
}
if self.ier.received_data_avail() && !self.rx_buffer.is_empty() {
if self.rx_buffer.len() >= self.rx_fifo_trigger() {
return Some(InterruptSource::RECEIVED_DATA_AVAIL);
} else {
return Some(InterruptSource::RECEIVE_TIMEOUT);
}
}
if self.ier.thr_empty() && self.is_thr_empty() && !self.thr_empty_acknowledged {
return Some(InterruptSource::THR_EMPTY);
}
if self.ier.modem_status()
&& (self.msr.cts_change()
|| self.msr.dcd_change()
|| self.msr.ri_went_low()
|| self.msr.dcd_change())
{
return Some(InterruptSource::MODEM_STATUS);
}
None
}
fn read_msr(&mut self) -> u8 {
let msr = self.effective_msr();
self.msr.set_cts_change(false);
self.msr.set_dsr_change(false);
self.msr.set_ri_went_low(false);
self.msr.set_dcd_change(false);
msr.into()
}
fn effective_msr(&self) -> ModemStatusRegister {
if self.mcr.loopback() {
self.msr
.with_cts(self.mcr.dtr())
.with_dsr(self.mcr.rts())
.with_ri(self.mcr.out1())
.with_dcd(self.mcr.out2())
} else {
self.msr
}
}
fn update_msr(&mut self, f: impl FnOnce(&mut Self)) {
let old_msr = self.effective_msr();
f(self);
self.post_update_msr(old_msr);
}
fn post_update_msr(&mut self, old_msr: ModemStatusRegister) {
let new_msr = self.effective_msr();
if old_msr.cts() != new_msr.cts() {
self.msr.set_cts_change(true);
}
if old_msr.dsr() != new_msr.dsr() {
self.msr.set_dsr_change(true);
}
if old_msr.ri() && !new_msr.ri() {
self.msr.set_ri_went_low(true);
}
if old_msr.dcd() != new_msr.dcd() {
self.msr.set_dcd_change(true);
}
}
fn read_rhr(&mut self) -> u8 {
self.rx_buffer.pop_front().unwrap_or(0)
}
fn read_isr(&mut self) -> u8 {
let interrupt = self.pending_interrupt();
if interrupt == Some(InterruptSource::THR_EMPTY) {
self.thr_empty_acknowledged = true;
}
let fifo_state = if self.fcr.enable_fifos() {
FifoState::ENABLED
} else {
FifoState::DISABLED
};
InterruptIdentificationRegister::new()
.with_no_interrupt_pending(interrupt.is_none())
.with_source(interrupt.map_or(0, |x| x.0))
.with_fifo_state(fifo_state.0)
.into()
}
fn read_lsr(&mut self) -> u8 {
let lsr = LineStatusRegister::new()
.with_rx_ready(!self.rx_buffer.is_empty())
.with_overrun_error(self.rx_overrun)
.with_parity_error(false)
.with_framing_error(false)
.with_break_signal_received(false)
.with_thr_empty(self.is_thr_empty())
.with_thr_and_tsr_empty(self.is_thr_empty())
.with_fifo_data_error(false);
self.rx_overrun = false;
lsr.into()
}
fn write_thr(&mut self, stats: &mut SerialStats, data: u8) {
self.thr_empty_acknowledged = false;
if self.mcr.loopback() {
let fifo_size = self.fifo_size();
if self.rx_buffer.len() >= fifo_size {
stats
.rx_dropped
.add((self.rx_buffer.len() - fifo_size) as u64);
self.rx_buffer.truncate(fifo_size);
self.rx_overrun = true;
}
self.rx_buffer.push_back(data);
} else {
if self.tx_buffer.len() >= TX_BUFFER_MAX {
tracing::debug!("tx fifo overrun, dropping output data");
stats
.tx_dropped
.add((self.tx_buffer.len() - (TX_BUFFER_MAX - 1)) as u64);
self.tx_buffer.truncate(TX_BUFFER_MAX - 1);
}
self.tx_buffer.push_back(data);
}
}
fn write_mcr(&mut self, data: u8) {
let mcr = ModemControlRegister::from(data).with_reserved(0);
tracing::debug!(?mcr, "mcr update");
self.update_msr(|this| this.mcr = mcr);
}
fn write_ier(&mut self, data: u8) {
self.ier = InterruptEnableRegister::from(data).with_reserved(0);
self.thr_empty_acknowledged = false;
}
fn write_fcr(&mut self, stats: &mut SerialStats, data: u8) {
let fcr = FifoControlRegister::from(data).with_reserved(0);
if fcr.enable_fifos() {
if fcr.clear_rx_fifo() {
tracing::debug!("clearing rx fifo");
stats.rx_dropped.add(self.rx_buffer.len() as u64);
self.rx_buffer.clear();
}
if fcr.clear_tx_fifo() {
tracing::debug!("clearing tx fifo");
stats.tx_dropped.add(self.tx_buffer.len() as u64);
self.tx_buffer.clear();
}
self.fcr = fcr.with_clear_rx_fifo(false).with_clear_tx_fifo(false);
} else {
self.fcr = FifoControlRegister::new();
}
}
fn write_reset(&mut self) {
let Self {
ier,
lcr,
mcr,
msr,
fcr,
dll,
dlm,
scratch,
thr_empty_acknowledged,
rx_overrun,
tx_buffer,
rx_buffer,
} = Self::new();
self.ier = ier;
self.lcr = lcr;
self.mcr = mcr;
self.msr = msr;
self.fcr = fcr;
self.thr_empty_acknowledged = thr_empty_acknowledged;
self.rx_overrun = rx_overrun;
let _ = dll;
let _ = dlm;
let _ = scratch;
let _ = tx_buffer;
let _ = rx_buffer;
}
}
impl PortIoIntercept for Serial16550 {
fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
self.read(io_port.into(), data)
}
fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
self.write(io_port.into(), data)
}
fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
if let Some(region) = &self.io_region {
std::slice::from_ref(region)
} else {
&[]
}
}
}
impl MmioIntercept for Serial16550 {
fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
self.read(addr, data)
}
fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
self.write(addr, data)
}
fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
if let Some(region) = &self.mmio_region {
std::slice::from_ref(region)
} else {
&[]
}
}
}
mod save_restore {
use crate::Serial16550;
use crate::State;
use vmcore::save_restore::RestoreError;
use vmcore::save_restore::SaveError;
use vmcore::save_restore::SaveRestore;
mod state {
use mesh::payload::Protobuf;
use vmcore::save_restore::SavedStateRoot;
#[derive(Protobuf, SavedStateRoot)]
#[mesh(package = "serial.uart16550")]
pub struct SavedState {
#[mesh(1)]
pub(super) ier: u8,
#[mesh(2)]
pub(super) lcr: u8,
#[mesh(3)]
pub(super) mcr: u8,
#[mesh(4)]
pub(super) msr: u8,
#[mesh(5)]
pub(super) fcr: u8,
#[mesh(6)]
pub(super) dll: u8,
#[mesh(7)]
pub(super) dlm: u8,
#[mesh(8)]
pub(super) scratch: u8,
#[mesh(9)]
pub(super) thr_empty_acknowledged: bool,
#[mesh(10)]
pub(super) rx_overrun: bool,
#[mesh(11)]
pub(super) tx_buffer: Vec<u8>,
#[mesh(12)]
pub(super) rx_buffer: Vec<u8>,
}
}
impl SaveRestore for Serial16550 {
type SavedState = state::SavedState;
fn save(&mut self) -> Result<Self::SavedState, SaveError> {
let State {
ier,
lcr,
mcr,
msr,
fcr,
dll,
dlm,
scratch,
thr_empty_acknowledged,
rx_overrun,
tx_buffer,
rx_buffer,
} = &self.state;
Ok(state::SavedState {
ier: (*ier).into(),
lcr: (*lcr).into(),
mcr: (*mcr).into(),
msr: (*msr).into(),
fcr: (*fcr).into(),
dll: *dll,
dlm: *dlm,
scratch: *scratch,
thr_empty_acknowledged: *thr_empty_acknowledged,
rx_overrun: *rx_overrun,
tx_buffer: tx_buffer.clone().into(),
rx_buffer: rx_buffer.clone().into(),
})
}
fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
let state::SavedState {
ier,
lcr,
mcr,
msr,
fcr,
dll,
dlm,
scratch,
thr_empty_acknowledged,
rx_overrun,
tx_buffer,
rx_buffer,
} = state;
self.state = State {
ier: ier.into(),
lcr: lcr.into(),
mcr: mcr.into(),
msr: msr.into(),
fcr: fcr.into(),
dll,
dlm,
scratch,
thr_empty_acknowledged,
rx_overrun,
tx_buffer: tx_buffer.into(),
rx_buffer: rx_buffer.into(),
};
if self.io.is_connected() {
self.state.connect();
} else {
self.state.disconnect();
}
self.sync();
Ok(())
}
}
}