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)); } }