From 0f7a50ab24fc3a307dcbed61b71b2c93b24a26c3 Mon Sep 17 00:00:00 2001 From: Patrick Mooney Date: Sat, 25 Nov 2023 13:31:22 -0600 Subject: [PATCH] Modernize 16550 UART The 16550 emulation was one of the first bits of emulation sketched out in propolis, and it shows. While there is more work to be done in improving the fidelity of emulation, the use of bitflags and the like should make this more readable. --- lib/propolis/src/hw/uart/uart16550.rs | 679 ++++++++++++++++++-------- 1 file changed, 475 insertions(+), 204 deletions(-) diff --git a/lib/propolis/src/hw/uart/uart16550.rs b/lib/propolis/src/hw/uart/uart16550.rs index f0a8df213..8bb9d4563 100644 --- a/lib/propolis/src/hw/uart/uart16550.rs +++ b/lib/propolis/src/hw/uart/uart16550.rs @@ -2,44 +2,50 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +//! 16550 UART +//! +//! Host -> Device Data Path: +//! The host writes data to the UART via its Transmitter Holding Register (THR), +//! which is backed by tx_fifo. After this data is received out of the THR, the +//! UART will raise the Transmitter Holding Register Empty interrupt, which +//! notifies the host that it can write more data. + use std::collections::VecDeque; +use std::convert::AsRef; use crate::migrate::MigrateStateError; use serde::{Deserialize, Serialize}; - -use bits::*; - -/* - * 16550 UART - * - * Host -> Device Data Path: - * The host writes data to the UART via its Transmitter Holding Register (THR), - * which is backed by tx_fifo. After this data is received out of the THR, the - * UART will raise the Transmitter Holding Register Empty interrupt, which - * notifies the host that it can write more data. - */ +use strum::{AsRefStr, FromRepr}; #[usdt::provider(provider = "propolis")] mod probes { fn uart_reg_read(offset: u8, is_dlab: u8, val: u8) {} fn uart_reg_write(offset: u8, is_dlab: u8, data: u8) {} + fn uart_tx_discard(data: u8) {} + fn uart_ign_write(offset: u8, is_dlab: u8, data: u8) {} + fn uart_ign_read(offset: u8, is_dlab: u8) {} } pub struct Uart { - reg_intr_enable: u8, - reg_intr_status: u8, + reg_intr_enable: IntrEnaReg, + reg_intr_ident: IntrIdentReg, // TODO: add FIFO support // reg_fifo_ctrl: u8, - reg_line_ctrl: u8, - reg_line_status: u8, - reg_modem_ctrl: u8, + reg_line_ctrl: LineCtrlReg, + reg_line_status: LineStatusReg, + reg_modem_ctrl: ModemCtrlReg, reg_modem_status: u8, reg_scratch: u8, reg_div_low: u8, reg_div_high: u8, - thre_intr: bool, // Transmitter Holding Register Empty interrupt + /// Transmitter-Holding-Register-Empty interrupt status + /// + /// Since reading IIR while THRE is asserted will clear the THRE interrupt, + /// but leave the associated THRE/TEMT bits asserted in RLS, we must track + /// the interrupt state seperately. + thre_intr: bool, intr_pin: bool, rx_fifo: Fifo, @@ -48,12 +54,12 @@ pub struct Uart { impl Uart { pub fn new() -> Self { Uart { - reg_intr_enable: 0, - reg_intr_status: ISRC_NONE, + reg_intr_enable: IntrEnaReg::default(), + reg_intr_ident: IntrIdentReg::default(), // reg_fifo_ctrl: 0, - reg_line_ctrl: 0, - reg_line_status: LSR_THRE | LSR_TEMT, - reg_modem_ctrl: 0, + reg_line_ctrl: LineCtrlReg::default(), + reg_line_status: LineStatusReg::default(), + reg_modem_ctrl: ModemCtrlReg::default(), reg_modem_status: 0, reg_scratch: 0, reg_div_low: 0, @@ -68,103 +74,129 @@ impl Uart { } /// Read UART register pub fn reg_read(&mut self, offset: u8) -> u8 { - let val = match (offset, self.is_dlab()) { - (REG_RHR, false) => { + let is_dlab = self.is_dlab(); + + let val = match UartReg::for_read(offset, is_dlab) { + Some(UartReg::DivisorLow) => self.reg_div_low, + Some(UartReg::DivisorHigh) => self.reg_div_high, + + Some(UartReg::RecvHold) => { if let Some(d) = self.rx_fifo.read() { self.update_dr(); self.update_isr(); d } else { - 0u8 + 0 } } - (REG_IER, false) => self.reg_intr_enable, - (REG_ISR, _) => { - let isr = self.reg_intr_status; - if isr & MASK_ISRC == ISRC_THRE { + Some(UartReg::IntrEnable) => self.reg_intr_enable.bits(), + Some(UartReg::IntrIdent) => { + let val = self.reg_intr_ident; + if let Some(IntrIdent::THRE) = val.get_intr() { // Reading the ISR can clear the THRE interrupt. // The flag will remain in RLS, though. self.thre_intr = false; self.update_isr(); } - isr + val.bits() + } + Some(UartReg::LineCtrl) => self.reg_line_ctrl.bits(), + Some(UartReg::ModemCtrl) => self.reg_modem_ctrl.bits(), + Some(UartReg::LineStatus) => { + let val = self.reg_line_status; + self.reg_line_status.remove(LineStatusReg::OE); + self.update_isr(); + + val.bits() + } + Some(UartReg::ModemStatus) => self.reg_modem_status, + Some(UartReg::Scratch) => self.reg_scratch, + Some(reg) => { + assert!(!reg.is_readable()); + panic!( + "uart reg {} should not decode to be readable", + reg.as_ref() + ); } - (REG_LCR, _) => self.reg_line_ctrl, - (REG_MCR, _) => self.reg_modem_ctrl, - (REG_LSR, _) => self.reg_line_status, - (REG_MSR, _) => self.reg_modem_status, - (REG_SPR, _) => self.reg_scratch, - // DLAB=1 - (REG_DLL, true) => self.reg_div_low, - (REG_DLH, true) => self.reg_div_high, - _ => { - probes::uart_reg_read!(|| (offset, self.is_dlab() as u8, 0)); - panic!(); + None => { + probes::uart_ign_read!(|| (offset, is_dlab as u8)); + 0 } }; - probes::uart_reg_read!(|| (offset, self.is_dlab() as u8, val)); + probes::uart_reg_read!(|| (offset, is_dlab as u8, val)); val } + /// Write UART register pub fn reg_write(&mut self, offset: u8, data: u8) { - probes::uart_reg_write!(|| (offset, self.is_dlab() as u8, data)); - match (offset, self.is_dlab()) { - (REG_THR, false) => { + let is_dlab = self.is_dlab(); + + probes::uart_reg_write!(|| (offset, is_dlab as u8, data)); + match UartReg::for_write(offset, is_dlab) { + Some(UartReg::DivisorLow) => { + self.reg_div_low = data; + } + Some(UartReg::DivisorHigh) => { + self.reg_div_high = data; + } + Some(UartReg::TransmitHold) => { if !self.is_loopback() { - self.tx_fifo.write(data); + if !self.tx_fifo.write(data) { + // There is no error flag for when the TX buffer is + // overrun, but we can at least fire a probe. + probes::uart_tx_discard!(|| { data }); + } self.set_thre(false); } else { if !self.rx_fifo.write(data) { - self.reg_line_status |= LSR_OE; + self.reg_line_status.insert(LineStatusReg::OE); } self.update_dr(); self.set_thre(true); } } - (REG_IER, false) => { + Some(UartReg::IntrEnable) => { let old = self.reg_intr_enable; - self.reg_intr_enable = data; - // Although not specified in the datasheet, some consumers expect a THRE - // interrupt to be raised when toggling that on in IER. - if old & IER_ETBEI == 0 && data & IER_ETBEI != 0 { + let new = IntrEnaReg::from_bits_truncate(data); + self.reg_intr_enable = new; + // Although not specified in the datasheet, some consumers + // expect a THRE interrupt to be raised when toggling that on in + // IER. + if !old.contains(IntrEnaReg::ETBEI) + && new.contains(IntrEnaReg::ETBEI) + { if self.tx_fifo.is_empty() { self.thre_intr = true } } self.update_isr(); } - (REG_FCR, _) => { + Some(UartReg::FifoCtrl) => { // TODO: add FIFO support // self.reg_fifo_ctrl = ?; } - (REG_LCR, _) => { + Some(UartReg::LineCtrl) => { // Accept any line control configuration. // We don't pay heed to anything but DLAB - self.reg_line_ctrl = data; - } - (REG_MCR, _) => { - self.reg_modem_ctrl = data & MASK_MCR; - } - (REG_LSR, _) => { - // ignore writes to read-only line-status + self.reg_line_ctrl = LineCtrlReg::from_bits_retain(data); } - (REG_MSR, _) => { - // ignore writes to read-only modem-status + Some(UartReg::ModemCtrl) => { + self.reg_modem_ctrl = ModemCtrlReg::from_bits_truncate(data); } - (REG_SPR, _) => { + Some(UartReg::Scratch) => { self.reg_scratch = data; } - // DLAB=1 - (REG_DLL, true) => { - self.reg_div_low = data; - } - (REG_DLH, true) => { - self.reg_div_high = data; + Some(reg) => { + assert!(!reg.is_writable()); + panic!( + "uart reg {} should not decode to be writable", + reg.as_ref() + ); } - _ => { - panic!(); + None => { + probes::uart_ign_read!(|| (offset, is_dlab as u8, data)); } } } @@ -201,12 +233,12 @@ impl Uart { } pub fn reset(&mut self) { - self.reg_intr_enable = 0; - self.reg_intr_status = ISRC_NONE; + self.reg_intr_enable = IntrEnaReg::default(); + self.reg_intr_ident = IntrIdentReg::default(); // self.reg_fifo_ctrl = 0; - self.reg_line_ctrl = 0; - self.reg_line_status = LSR_THRE | LSR_TEMT; - self.reg_modem_ctrl = 0; + self.reg_line_ctrl = LineCtrlReg::default(); + self.reg_line_status = LineStatusReg::default(); + self.reg_modem_ctrl = ModemCtrlReg::default(); self.reg_modem_status = 0; self.reg_scratch = 0; self.reg_div_low = 0; @@ -221,75 +253,62 @@ impl Uart { #[inline(always)] fn is_dlab(&self) -> bool { - (self.reg_line_ctrl & LCR_DLAB) != 0 + self.reg_line_ctrl.contains(LineCtrlReg::DLAB) } #[inline(always)] fn is_loopback(&self) -> bool { - (self.reg_modem_ctrl & MCR_LOOP) != 0 + self.reg_modem_ctrl.contains(ModemCtrlReg::LOOP) } - fn next_intr(&self) -> u8 { - if self.reg_intr_enable & IER_ELSI != 0 - && self.reg_line_status & LSR_OE != 0 + fn next_intr(&self) -> Option { + if self.reg_intr_enable.contains(IntrEnaReg::ELSI) + && self.reg_line_status.contains(LineStatusReg::OE) { // This ignores Parity Error, Framing Error, and Break - ISRC_RLS - } else if self.reg_intr_enable & IER_ERBFI != 0 - && self.reg_line_status & LSR_DR != 0 + Some(IntrIdent::RLS) + } else if self.reg_intr_enable.contains(IntrEnaReg::ERBFI) + && self.reg_line_status.contains(LineStatusReg::DR) { - ISRC_DR - } else if self.reg_intr_enable & IER_ETBEI != 0 && self.thre_intr { - ISRC_THRE - } else if self.reg_intr_enable & IER_EDSSI != 0 + Some(IntrIdent::DR) + } else if self.reg_intr_enable.contains(IntrEnaReg::ETBEI) + && self.thre_intr + { + Some(IntrIdent::THRE) + } else if self.reg_intr_enable.contains(IntrEnaReg::EDSSI) && self.reg_modem_status != 0 { // This ignores that MSR is fixed to 0 - ISRC_MDM + Some(IntrIdent::MDM) } else { - ISRC_NONE + None } } fn update_isr(&mut self) { - let old = self.reg_intr_status; - let old_isrc = old & MASK_ISRC; - let new_isrc = self.next_intr(); - - debug_assert!(new_isrc & !MASK_ISRC == 0); - self.reg_intr_status = (old & !MASK_ISRC) | new_isrc; - - if old_isrc == ISRC_NONE && new_isrc != ISRC_NONE { - self.intr_pin = true; - } else if old_isrc != ISRC_NONE && new_isrc == ISRC_NONE { - self.intr_pin = false; - } + let new_isr = self.next_intr(); + self.reg_intr_ident.set_intr(new_isr); + self.intr_pin = new_isr.is_some(); } fn set_thre(&mut self, state: bool) { - if state { - self.reg_line_status |= LSR_THRE | LSR_TEMT; - } else { - self.reg_line_status &= !(LSR_THRE | LSR_TEMT); - } + self.reg_line_status + .set(LineStatusReg::THRE | LineStatusReg::TEMT, state); if self.thre_intr != state { self.thre_intr = state; - self.update_isr(); } + self.update_isr(); } fn update_dr(&mut self) { - if !self.rx_fifo.is_empty() { - self.reg_line_status |= LSR_DR; - } else { - self.reg_line_status &= !LSR_DR; - } + self.reg_line_status.set(LineStatusReg::DR, !self.rx_fifo.is_empty()) } + pub(super) fn export(&self) -> migrate::Uart16550V1 { migrate::Uart16550V1 { - intr_enable: self.reg_intr_enable, - intr_status: self.reg_intr_status, - line_ctrl: self.reg_line_ctrl, - line_status: self.reg_line_status, - modem_ctrl: self.reg_modem_ctrl, + intr_enable: self.reg_intr_enable.bits(), + intr_status: self.reg_intr_ident.bits(), + line_ctrl: self.reg_line_ctrl.bits(), + line_status: self.reg_line_status.bits(), + modem_ctrl: self.reg_modem_ctrl.bits(), modem_status: self.reg_modem_status, scratch: self.reg_scratch, div_low: self.reg_div_low, @@ -299,7 +318,6 @@ impl Uart { tx_fifo: self.tx_fifo.buf.clone().into(), } } - pub(super) fn import( &mut self, state: &migrate::Uart16550V1, @@ -317,11 +335,15 @@ impl Uart { ))); } - self.reg_intr_enable = state.intr_enable; - self.reg_intr_status = state.intr_status; - self.reg_line_ctrl = state.line_ctrl; - self.reg_line_status = state.line_status; - self.reg_modem_ctrl = state.modem_ctrl; + self.reg_intr_enable = + IntrEnaReg::from_bits_truncate(state.intr_enable); + self.reg_intr_ident = + IntrIdentReg::from_bits_truncate(state.intr_status); + self.reg_line_ctrl = LineCtrlReg::from_bits_retain(state.line_ctrl); + self.reg_line_status = + LineStatusReg::from_bits_truncate(state.line_status); + self.reg_modem_ctrl = + ModemCtrlReg::from_bits_truncate(state.modem_ctrl); self.reg_modem_status = state.modem_status; self.reg_scratch = state.scratch; self.reg_div_low = state.div_low; @@ -329,6 +351,10 @@ impl Uart { self.thre_intr = state.thre_state; self.rx_fifo.buf = state.rx_fifo.clone().into(); self.tx_fifo.buf = state.tx_fifo.clone().into(); + + // synthesize interrupt pin state like update_isr() + self.intr_pin = self.reg_intr_ident.get_intr().is_some(); + Ok(()) } } @@ -392,87 +418,293 @@ pub mod migrate { } } -mod bits { - #![allow(unused)] - - /* - * Register offsets from base - */ - pub const REG_RHR: u8 = 0b000; // Receiver Buffer Register (RO) - pub const REG_THR: u8 = 0b000; // Transmitter Holding Register (WO) - pub const REG_IER: u8 = 0b001; // Interrupt Enable Register (RW) - pub const REG_ISR: u8 = 0b010; // Interrupt Ident Register (RO) - pub const REG_FCR: u8 = 0b010; // FIFO Control Register (WO) - pub const REG_LCR: u8 = 0b011; // Line Control Register (RW) - pub const REG_MCR: u8 = 0b100; // Modem Control Register (RW) - pub const REG_LSR: u8 = 0b101; // Line Status Register (RO) - pub const REG_MSR: u8 = 0b110; // Modem Status Register (RO) - pub const REG_SPR: u8 = 0b111; // Scratch Register (RW) - pub const REG_DLL: u8 = 0b000; // Divisor Latch LSB (RW when DLAB=1) - pub const REG_DLH: u8 = 0b001; // Divisor Latch MSB (RW when DLAB=1) - - /* - * Interrupt Enable Register (IER) bits - */ - pub const IER_ERBFI: u8 = 1 << 0; // enable received data available intr - pub const IER_ETBEI: u8 = 1 << 1; // enable xmit holding register empty intr - pub const IER_ELSI: u8 = 1 << 2; // enable receiver line status intr - pub const IER_EDSSI: u8 = 1 << 3; // enable modem status intr - - /* - * Possible values of Interrupt Identification Register - */ - pub const ISRC_NONE: u8 = 0b0001; // no interrupt - pub const ISRC_RLS: u8 = 0b0110; // receiver line status - pub const ISRC_DR: u8 = 0b0100; // data ready - pub const ISRC_TMO: u8 = 0b1100; // character timeout - pub const ISRC_THRE: u8 = 0b0010; // transmitter holding register empty - pub const ISRC_MDM: u8 = 0b0000; // modem status - - /* - * FIFO Control Register (FCR) bits - */ - pub const FCR_ENA: u8 = 1 << 0; // enable transmitter/receive FIFOs - pub const FCR_RXRST: u8 = 1 << 1; // clear bytes and count in receiver FIFO - pub const FCR_TXRST: u8 = 1 << 2; // clear bytes and count in transmit FIFO - pub const FCR_DMAMD: u8 = 1 << 3; - pub const FCR_TRGR: u8 = 0b11000000; - - /* - * Modem Control Register (MCR) bits - */ - pub const MCR_LOOP: u8 = 1 << 4; // loopback - - /* - * Line Status Register (LSR) bits - */ - pub const LSR_DR: u8 = 1 << 0; // Data Ready - pub const LSR_OE: u8 = 1 << 1; // Overrun Error - pub const LSR_THRE: u8 = 1 << 5; // THRE indicator - pub const LSR_TEMT: u8 = 1 << 6; // Transmitter Empty indicator - pub const LCR_DLAB: u8 = 0b10000000; // Divisor Latch Access Bit - - pub const MASK_PCD: u8 = 0b00001111; - pub const MASK_MCR: u8 = 0b00011111; - pub const MASK_IER: u8 = 0b00001111; - pub const MASK_FCR: u8 = 0b11001111; - pub const MASK_ISRC: u8 = 0b00001111; +bitflags! { + /// Interrupt Enable Register (IER) + #[derive(Default, Copy, Clone)] + struct IntrEnaReg: u8 { + /// Receiver Data Available interrupt enable + const ERBFI = 1 << 0; + /// Transmit Holding Register Empty interrupt enable + const ETBEI = 1 << 1; + /// Receiver Line Status interrupt enable + const ELSI = 1 << 2; + /// Modem Status interrupt + const EDSSI = 1 << 3; + } + + /// Interrupt Identification Register (IIR) + #[derive(Copy, Clone)] + struct IntrIdentReg: u8 { + /// Interrupt Pending (clear when interrupt pending) + const NOPEND = 1; + + /// Mask of potential interrupt IDs + const INTID = 0b1110; + } + + /// FIFO Control Register (FCR) + #[derive(Default, Copy, Clone)] + struct FifoCtrlReg: u8 { + /// enable transmitter/receive FIFOs + const ENA = 1 << 0; + /// clear bytes and count in receiver FIFO + const RXRST = 1 << 1; + /// clear bytes and count in transmit FIFO + const TXRST = 1 << 2; + const DMAMD = 1 << 3; + const TRGR = 0b11000000; + } + + /// Modem Control Register (MCR) + #[derive(Default, Copy, Clone)] + struct ModemCtrlReg: u8 { + /// Loopback enabled + const LOOP = 1 << 4; + } + + /// Line Status Register (LSR) + #[derive(Copy, Clone)] + struct LineStatusReg: u8 { + /// Data Ready + const DR = 1 << 0; + /// Overrun Error + const OE = 1 << 1; + /// Transmit Hold Register Empty + const THRE = 1 << 5; + /// Transmitter Empty + const TEMT = 1 << 6; + } + + /// Line Control Register (LSR) + #[derive(Default, Copy, Clone)] + struct LineCtrlReg: u8 { + /// Word Length Select (0b11 = 8 bits) + const WLS = 0b11; + /// Stop Bits + const STB = 1 << 2; + /// Parent Enable + const PEN = 1 << 3; + /// Even Parity Select + const EPS = 1 << 4; + /// Stick Parity + const SP = 1 << 5; + /// Break Condition + const BC = 1 << 6; + /// Divisor Latch Access Bit + const DLAB = 1 << 7; + } +} + +impl Default for LineStatusReg { + fn default() -> Self { + LineStatusReg::TEMT | LineStatusReg::THRE + } +} + +impl IntrIdentReg { + fn set_intr(&mut self, intr_id: Option) { + // Clear any existing interrupt bits + self.remove(IntrIdentReg::INTID); + if let Some(id) = intr_id { + self.0 .0 |= id as u8; + self.remove(IntrIdentReg::NOPEND); + } else { + self.insert(IntrIdentReg::NOPEND); + } + } + fn get_intr(&self) -> Option { + if self.contains(IntrIdentReg::NOPEND) { + None + } else { + IntrIdent::from_repr(self.intersection(IntrIdentReg::INTID).bits()) + } + } +} + +impl Default for IntrIdentReg { + fn default() -> Self { + IntrIdentReg::NOPEND + } +} + +#[repr(u8)] +#[derive(Copy, Clone, FromRepr)] +enum IntrIdent { + /// MODEM Status, priority 4 (lowest) + MDM = 0b0000, + /// Transmitter Hold Register Empty, priority 3 + THRE = 0b0010, + /// Data Ready, priority 2 + DR = 0b0100, + /// Receiver Line Status, priority 1 (highest) + RLS = 0b0110, + /// Character Timeout, priority 2 + CTMO = 0b1100, +} + +#[derive(Clone, Copy, AsRefStr)] +enum UartReg { + /// Receiver Holding Register (RHR), RO + RecvHold, + /// Transmitter Holding Register (THR), WO + TransmitHold, + /// Interrupt Enable Register (IER), RW + IntrEnable, + /// Interrupt Identification Register (IIR), RO + IntrIdent, + /// FIFO Control Register (FCR), WO + FifoCtrl, + /// Line Control Register (LCR), RW + LineCtrl, + /// Modem Control Register (MCR), RW + ModemCtrl, + /// Line Status Register (LCR), RO + LineStatus, + /// Modem Status Register (MSR), RO + ModemStatus, + /// Scratch Register (SPR), RW + Scratch, + /// Divisor Latch LSB (DLL), RW + DivisorLow, + /// Divisor Latch MSB (DLH), RW + DivisorHigh, +} + +impl UartReg { + const fn for_write(off: u8, dlab_status: bool) -> Option { + match (off, dlab_status) { + (0, true) => Some(Self::DivisorLow), + (0, false) => Some(Self::TransmitHold), + (1, true) => Some(Self::DivisorHigh), + (1, false) => Some(Self::IntrEnable), + (2, _) => Some(Self::FifoCtrl), + (3, _) => Some(Self::LineCtrl), + (4, _) => Some(Self::ModemCtrl), + (5, _) => None, + (6, _) => None, + (7, _) => Some(Self::Scratch), + _ => None, + } + } + + const fn for_read(off: u8, dlab_status: bool) -> Option { + match (off, dlab_status) { + (0, true) => Some(Self::DivisorLow), + (0, false) => Some(Self::RecvHold), + (1, true) => Some(Self::DivisorHigh), + (1, false) => Some(Self::IntrEnable), + (2, _) => Some(Self::IntrIdent), + (3, _) => Some(Self::LineCtrl), + (4, _) => Some(Self::ModemCtrl), + (5, _) => Some(Self::LineStatus), + (6, _) => Some(Self::ModemStatus), + (7, _) => Some(Self::Scratch), + _ => None, + } + } + + const fn is_readable(self) -> bool { + match self { + UartReg::RecvHold + | UartReg::IntrEnable + | UartReg::IntrIdent + | UartReg::LineCtrl + | UartReg::ModemCtrl + | UartReg::LineStatus + | UartReg::ModemStatus + | UartReg::Scratch + | UartReg::DivisorLow + | UartReg::DivisorHigh => true, + _ => false, + } + } + + const fn is_writable(self) -> bool { + match self { + UartReg::TransmitHold + | UartReg::IntrEnable + | UartReg::FifoCtrl + | UartReg::LineCtrl + | UartReg::ModemCtrl + | UartReg::Scratch + | UartReg::DivisorLow + | UartReg::DivisorHigh => true, + _ => false, + } + } } #[cfg(test)] mod tests { + + mod bits { + #![allow(unused)] + + // Register offsets from base + pub const REG_RHR: u8 = 0b000; // Receiver Buffer Register (RO) + pub const REG_THR: u8 = 0b000; // Transmitter Holding Register (WO) + pub const REG_IER: u8 = 0b001; // Interrupt Enable Register (RW) + pub const REG_ISR: u8 = 0b010; // Interrupt Ident Register (RO) + pub const REG_FCR: u8 = 0b010; // FIFO Control Register (WO) + pub const REG_LCR: u8 = 0b011; // Line Control Register (RW) + pub const REG_MCR: u8 = 0b100; // Modem Control Register (RW) + pub const REG_LSR: u8 = 0b101; // Line Status Register (RO) + pub const REG_MSR: u8 = 0b110; // Modem Status Register (RO) + pub const REG_SPR: u8 = 0b111; // Scratch Register (RW) + pub const REG_DLL: u8 = 0b000; // Divisor Latch LSB (RW when DLAB=1) + pub const REG_DLH: u8 = 0b001; // Divisor Latch MSB (RW when DLAB=1) + + // Interrupt Enable Register (IER) bits + pub const IER_ERBFI: u8 = 1 << 0; // enable received-data-available-intr + pub const IER_ETBEI: u8 = 1 << 1; // enable xmit-holding-reg-empty-intr + pub const IER_ELSI: u8 = 1 << 2; // enable receiver-line-status + pub const IER_EDSSI: u8 = 1 << 3; // enable modem-status-intr + + // Possible values of Interrupt Identification Register + pub const ISRC_NONE: u8 = 0b0001; // no interrupt + pub const ISRC_RLS: u8 = 0b0110; // receiver line status + pub const ISRC_DR: u8 = 0b0100; // data ready + pub const ISRC_TMO: u8 = 0b1100; // character timeout + pub const ISRC_THRE: u8 = 0b0010; // transmitter holding register empty + pub const ISRC_MDM: u8 = 0b0000; // modem status + + // FIFO Control Register (FCR) bits + pub const FCR_ENA: u8 = 1 << 0; // enable xmit/receive FIFOs + pub const FCR_RXRST: u8 = 1 << 1; // clear bytes/count in recv FIFO + pub const FCR_TXRST: u8 = 1 << 2; // clear bytes/count in xmit FIFO + pub const FCR_DMAMD: u8 = 1 << 3; + pub const FCR_TRGR: u8 = 0b11000000; + + // Modem Control Register (MCR) bits + pub const MCR_LOOP: u8 = 1 << 4; // loopback + + // Line Status Register (LSR) bits + pub const LSR_DR: u8 = 1 << 0; // Data Ready + pub const LSR_OE: u8 = 1 << 1; // Overrun Error + pub const LSR_THRE: u8 = 1 << 5; // THRE indicator + pub const LSR_TEMT: u8 = 1 << 6; // Transmitter Empty indicator + pub const LCR_DLAB: u8 = 0b10000000; // Divisor Latch Access Bit + + pub const MASK_PCD: u8 = 0b00001111; + pub const MASK_MCR: u8 = 0b00011111; + pub const MASK_IER: u8 = 0b00001111; + pub const MASK_FCR: u8 = 0b11001111; + pub const MASK_ISRC: u8 = 0b00001111; + } + use super::*; + use bits::*; #[test] fn reset_state() { let mut uart = Uart::new(); - assert_eq!(uart.reg_read(REG_IER), 0u8); - assert_eq!(uart.reg_read(REG_ISR), 1u8); + assert_eq!(uart.reg_read(REG_IER), 0); + assert_eq!(uart.reg_read(REG_ISR), 1); // TI datasheet notes the state of this register, despite it being WO - // assert_eq!(uart.reg_fifo_ctrl, 0u8); - assert_eq!(uart.reg_read(REG_LCR), 0u8); - assert_eq!(uart.reg_read(REG_MCR), 0u8); - assert_eq!(uart.reg_read(REG_LSR), 0b01100000u8); + // assert_eq!(uart.reg_fifo_ctrl, 0); + assert_eq!(uart.reg_read(REG_LCR), 0); + assert_eq!(uart.reg_read(REG_MCR), 0); + assert_eq!(uart.reg_read(REG_LSR), 0b01100000); } #[test] fn intr_thre_on_etbei_toggle() { @@ -496,7 +728,7 @@ mod tests { #[test] fn intr_dr_on_incoming() { let mut uart = Uart::new(); - let tval: u8 = 0x20; + let tval = 0x20; uart.reg_write(REG_IER, IER_ERBFI); assert_eq!(uart.intr_state(), false); @@ -511,7 +743,7 @@ mod tests { #[test] fn intr_thre_on_outgoing() { let mut uart = Uart::new(); - let tval: u8 = 0x20; + let tval = 0x20; uart.reg_write(REG_IER, 0); assert_eq!(uart.intr_state(), false); @@ -531,17 +763,56 @@ mod tests { let mut uart = Uart::new(); for i in 0..=7 { - let _: u8 = uart.reg_read(i); + let _ = uart.reg_read(i); } for i in 0..=7 { - uart.reg_write(i, 0xffu8); + uart.reg_write(i, 0xff); } + // With DLAB=1 now true, make sure the divisor registers are fine + let _ = uart.reg_read(0); + let _ = uart.reg_read(1); + let _ = uart.reg_write(0, 0xff); + let _ = uart.reg_write(1, 0xff); } #[test] - #[should_panic] - fn invalid_offset() { + fn interrupt_codes() { let mut uart = Uart::new(); - uart.reg_read(8); + // Enable interrupts we're going to test + uart.reg_write(REG_IER, IER_ERBFI | IER_ETBEI | IER_ELSI | IER_EDSSI); + + // Since no data has been sent, the TX register is empty + assert_eq!(uart.reg_read(REG_ISR), ISRC_THRE); + + // Since triggering overflow requires us to use loopback mode (since the + // data_write() path refuses to allow overflow), configure the uart for + // loopback. That state can be used for the data-ready intr as well. + uart.reg_write(REG_MCR, MCR_LOOP); + + // Loop back data to assert the data-ready interrupt + let rval = 0x20; + uart.reg_write(REG_THR, rval); + // data-ready interrupt should take precedence + assert_eq!(uart.reg_read(REG_ISR), ISRC_DR); + + // Now overrun the read register + uart.reg_write(REG_THR, rval); + // receiver-line-status interrupt should take precedence + assert_eq!(uart.reg_read(REG_ISR), ISRC_RLS); + + // Read RLS to clear RLS intr + assert!((uart.reg_read(REG_LSR) & LSR_OE) != 0); + assert_eq!(uart.reg_read(REG_ISR), ISRC_DR); + + // Read pending data to clear DR intr + assert_eq!(uart.reg_read(REG_RHR), rval); + assert_eq!(uart.reg_read(REG_ISR), ISRC_THRE); + + // Clear loopback mode and queue outgoing data in TX register + uart.reg_write(REG_MCR, 0); + let tval = 0x40; + uart.reg_write(REG_THR, tval); + assert_eq!(uart.reg_read(REG_ISR), ISRC_NONE); + assert_eq!(uart.data_read(), Some(tval)); } }