391 lines
12 KiB
Rust
391 lines
12 KiB
Rust
// 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<u8>,
|
|
interrupt_enable: Port<u8>,
|
|
interrupt_id_fifo_control: Port<u8>,
|
|
line_control: Port<u8>,
|
|
modem_control: Port<u8>,
|
|
line_status: Port<u8>,
|
|
modem_status: Port<u8>,
|
|
scratch: Port<u8>,
|
|
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<Self> {
|
|
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> {
|
|
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<ModemControl>) {
|
|
unsafe { self.modem_control.write(control.bits()) };
|
|
}
|
|
/// Get the Modem Control register flags.
|
|
pub fn get_modem_control(&mut self) -> BitFlags<ModemControl> {
|
|
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> {
|
|
LineStatus::from_bits(unsafe { self.line_status.read() }).unwrap()
|
|
}
|
|
|
|
/// Get the Modem Status Register flags.
|
|
pub fn get_modem_status(&mut self) -> BitFlags<ModemStatus> {
|
|
ModemStatus::from_bits(unsafe { self.modem_status.read() }).unwrap()
|
|
}
|
|
}
|