// Copyright (c) 2025 shibedrill // SPDX-License-Identifier: MIT #![allow(dead_code)] use enumflags2::{BitFlag, BitFlags}; use intbits::Bits; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use x86_64::instructions::port::Port; use crate::arch::asm::nop; /// Represents an x86 port-mapped serial port. pub struct SerialPort { base_port: Port, interrupt_enable: Port, interrupt_id_fifo_control: Port, line_control: Port, modem_control: Port, line_status: Port, modem_status: Port, scratch: Port, fifo_register: u8, pub crlf: Crlf, pub wait_tx_clear: bool, } #[derive(PartialEq, Eq)] pub enum Crlf { Lf, Crlf, } #[derive(FromPrimitive)] #[repr(u8)] pub enum DataBits { Five = 0, Six = 1, Seven = 2, Eight = 3, } #[derive(FromPrimitive)] #[repr(u8)] pub enum ParityBits { None = 0b000, Odd = 0b001, Even = 0b011, Mark = 0b101, Space = 0b111, } #[enumflags2::bitflags] #[repr(u8)] #[derive(Clone, Copy, Debug)] pub enum InterruptEnable { ModemStatus = 0b1000, ReceiverLineStatus = 0b100, TransmitterHoldingRegisterEmpty = 0b10, ReceivedDataAvail = 0b1, } #[enumflags2::bitflags] #[repr(u8)] #[derive(Clone, Copy, Debug)] pub enum ModemControl { DataTerminalReady = 0b00000001, RequestToSend = 0b00000010, Out1 = 0b00000100, Out2 = 0b00001000, Loop = 0b00010000, } #[repr(u8)] #[derive(FromPrimitive)] pub enum TriggerLevel { OneByte = 0b00, FourBytes = 0b01, EightBytes = 0b10, FourteenBytes = 0b11, } #[repr(u8)] #[derive(FromPrimitive, PartialEq, Eq)] pub enum InterruptState { ModemStatus = 0b00, TransmitterEmpty = 0b01, ReceivedDataAvail = 0b10, ReceiverLineStatus = 0b11, } #[repr(u8)] #[derive(FromPrimitive)] pub enum FifoState { NoFifo = 0b00, FifoEnabledUnusable = 0b01, FifoEnabled = 0b10, } #[repr(u8)] #[enumflags2::bitflags] #[derive(Clone, Copy, Debug)] pub enum LineStatus { DataReady = 0b00000001, OverrunError = 0b00000010, ParityError = 0b00000100, FramingError = 0b00001000, BreakIndicator = 0b00010000, TransmitterHoldingRegisterEmpty = 0b00100000, TransmitterEmpty = 0b01000000, ImpendingError = 0b10000000, } #[repr(u8)] #[enumflags2::bitflags] #[derive(Clone, Copy, Debug)] pub enum ModemStatus { DeltaClearToSend = 0b00000001, DeltaDataSetReady = 0b00000010, TrailingEdgeRingIndicator = 0b00000100, DeltaDataCarrierDetect = 0b00001000, ClearToSend = 0b00010000, DataSetReady = 0b00100000, RingIndicator = 0b01000000, DataCarrierDetect = 0b10000000, } impl SerialPort { pub fn log_write(&mut self, msg: &str) { if self.crlf == Crlf::Crlf { for c in msg.chars() { if c == '\n' { self.write_char('\r'); } self.write_char(c); } } else { for c in msg.chars() { self.write_char(c); } } } /// Get whether the transmit buffer is empty. pub fn is_transmit_empty(&mut self) -> bool { self.get_line_status() .contains(LineStatus::TransmitterEmpty) } fn block_until_transmit_empty(&mut self) { while !self.is_transmit_empty() { nop(); } } /// Write a message with a newline appended to the serial port. pub fn log_writeln(&mut self, msg: &str) { self.log_write(msg); self.log_write("\n"); } /// Create a new port-mapped serial port with the base address `port_addr`. pub unsafe fn from_port_unchecked(port_addr: u16) -> Self { let mut port = SerialPort { base_port: Port::new(port_addr), interrupt_enable: Port::new(port_addr + 1), interrupt_id_fifo_control: Port::new(port_addr + 2), line_control: Port::new(port_addr + 3), modem_control: Port::new(port_addr + 4), line_status: Port::new(port_addr + 5), modem_status: Port::new(port_addr + 6), scratch: Port::new(port_addr + 7), fifo_register: 0, crlf: Crlf::Crlf, wait_tx_clear: true, }; // ensure this is false port.set_dlab(false); port } pub fn try_from_port(port_addr: u16) -> Option { let mut port = unsafe { SerialPort::from_port_unchecked(port_addr) }; // Assert that port scratch register is writable unsafe { port.scratch.write(192) }; if unsafe { port.scratch.read() } != 192 { return None; } // Enable loopback mode let mut port_modem = port.get_modem_control(); port_modem.insert(ModemControl::Loop); port.set_modem_control(port_modem); port.write_char(0xae as char); // Assert that loopback mode worked if port.read_char() != (0xae as u8) { return None; } // Disable loopback mode port_modem.remove(ModemControl::Loop); port.set_modem_control(port_modem); Some(port) } fn get_line_control(&mut self) -> u8 { unsafe { self.line_control.read() } } fn set_line_control(&mut self, value: u8) { unsafe { self.line_control.write(value) }; } fn get_dlab(&mut self) -> bool { self.get_line_control().bit(7) } fn set_dlab(&mut self, dlab: bool) { let mut new_lcr = unsafe { self.line_control.read() }; new_lcr.set_bit(7, dlab); unsafe { self.line_control.write(new_lcr) }; } /// Read a single character from the RX buffer. pub fn read_char(&mut self) -> u8 { unsafe { self.base_port.read() } } /// Write a single character to the TX buffer. pub fn write_char(&mut self, c: char) { // Wait for the TX Buffer to be empty if self.wait_tx_clear { self.block_until_transmit_empty(); } unsafe { self.base_port.write(c as u8) }; } /// Get the baud rate divisor. pub fn get_divisor(&mut self) -> u16 { self.set_dlab(true); let result: u16 = unsafe { self.base_port.read() as u16 | ((self.interrupt_enable.read() as u16) << 8) }; self.set_dlab(false); result } /// Set the baud rate divisor. pub fn set_divisor(&mut self, divisor: u16) { self.set_dlab(true); unsafe { self.base_port.write((divisor & 0b11111111) as u8); self.interrupt_enable .write(((divisor & 0b1111111100000000) >> 8) as u8); } self.set_dlab(false); } /// Get the number of bits in a character. pub fn get_data_bits(&mut self) -> DataBits { DataBits::from_u8(self.get_line_control().bits(0..=1)).unwrap() } /// Set the number of bits in a character. pub fn set_data_bits(&mut self, bits: DataBits) { let new_value = self.get_line_control().bits(2..=7) | bits as u8; self.set_line_control(new_value); } /// Get the number of bits sent after each character. /// A value of False means only 1 bit will be sent. /// A value of True means 2 bits will be sent. /// If the character length is 5 bits, a value of True means 1.5 bits will be sent. pub fn get_stop_bits(&mut self) -> bool { self.get_line_control().bit(2) } /// Set the number of bits sent after each character. /// A value of False means only 1 bit will be sent. /// A value of True means 2 bits will be sent. /// If the character length is 5 bits, a value of True means 1.5 bits will be sent. pub fn set_stop_bits(&mut self, bit: bool) { let mut new_value = self.get_line_control(); new_value.set_bit(2, bit); self.set_line_control(new_value); } /// Get the parity strategy. pub fn get_parity_bits(&mut self) -> ParityBits { ParityBits::from_u8(self.get_line_control().bits(3..=5) >> 3).unwrap() } /// Set the parity strategy. pub fn set_parity_bits(&mut self, parity: ParityBits) { let mut new_value = self.get_line_control(); new_value.set_bits(3..=5, parity as u8); self.set_line_control(new_value); } /// Get the Interrupt Enable control register. pub fn get_interrupt_enable(&mut self) -> BitFlags { InterruptEnable::from_bits(unsafe { self.interrupt_enable.read().bits(0..=3) }).unwrap() } /// Set the Interrupt Enable control register. pub fn set_interrupt_enable(&mut self, interrupt: InterruptEnable) { unsafe { self.interrupt_enable.write(interrupt as u8) }; } /// Set the FIFO control register. pub fn set_fifo_control(&mut self, value: u8) { unsafe { self.interrupt_id_fifo_control.write(value) }; } /// Clear the transmitter FIFO. pub fn clear_tx_fifo(&mut self) { let mut new_value = self.fifo_register; new_value.set_bit(2, true); self.set_fifo_control(new_value); } /// Clear the receiver FIFO. pub fn clear_rx_fifo(&mut self) { let mut new_value = self.fifo_register; new_value.set_bit(1, true); self.set_fifo_control(new_value); } /// Enable or disable FIFOs. pub fn set_fifo_enable(&mut self, enable: bool) { self.fifo_register.set_bit(0, enable); self.set_fifo_control(self.fifo_register); } /// Get whether FIFOs were enabled using this API. pub fn get_fifo_enable(&mut self) -> bool { self.fifo_register.bit(0) } /// Set the DMA mode of the serial controller. pub fn set_dma_mode(&mut self, enable: bool) { self.fifo_register.set_bit(3, enable); self.set_fifo_control(self.fifo_register); } /// Get whether DMA mode was enabled using this API. pub fn get_dma_mode(&mut self) -> bool { self.fifo_register.bit(3) } /// Get the Interrupt Trigger Level. pub fn get_interrupt_trigger_level(&mut self) -> TriggerLevel { TriggerLevel::from_u8(self.fifo_register.bits(6..=7) >> 6).unwrap() } /// Set the Interrupt Trigger Level. pub fn set_interrupt_trigger_level(&mut self, level: TriggerLevel) { self.fifo_register.set_bits(6..=7, level as u8); self.set_fifo_control(self.fifo_register); } /// Get the Interrupt ID status register. pub fn get_interrupt_id(&mut self) -> u8 { unsafe { self.interrupt_id_fifo_control.read() } } /// Get the state of the FIFO buffers. pub fn get_fifo_buffer_state(&mut self) -> FifoState { FifoState::from_u8(self.get_interrupt_id().bits(6..=7) >> 6).unwrap() } /// Get whether a timeout interrupt is pending. Only used on UART 16550 chips, otherwise reserved. pub fn get_timeout_pending(&mut self) -> bool { self.get_interrupt_id().bit(3) } /// Get the Interrupt State of the controller. pub fn get_interrupt_state(&mut self) -> InterruptState { InterruptState::from_u8(self.get_interrupt_id().bits(1..=2) >> 1).unwrap() } /// Get whether an interrupt is pending. pub fn get_interrupt_pending(&mut self) -> bool { !self.get_interrupt_id().bit(0) } /// Set the Modem Control register flags. /// This OVERWRITES all held flags. To set specific flags, first store the current /// value using `get_modem_control()`, then modify the flags you want to change. pub fn set_modem_control(&mut self, control: BitFlags) { unsafe { self.modem_control.write(control.bits()) }; } /// Get the Modem Control register flags. pub fn get_modem_control(&mut self) -> BitFlags { ModemControl::from_bits(unsafe { self.modem_control.read() & 0b0001111 }).unwrap() } /// Get the Line Status Register flags. pub fn get_line_status(&mut self) -> BitFlags { LineStatus::from_bits(unsafe { self.line_status.read() }).unwrap() } /// Get the Modem Status Register flags. pub fn get_modem_status(&mut self) -> BitFlags { ModemStatus::from_bits(unsafe { self.modem_status.read() }).unwrap() } }