From 94fe27e3c0352e673ad9e79551d488b7b8b38fea Mon Sep 17 00:00:00 2001 From: Rajas Paranjpe <52586855+ChocolateLoverRaj@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:19:49 -0800 Subject: [PATCH 1/2] Use traits for register and uart Contain breaking changes Advantages: - Reduces the amount of code - Lets people using this library use a uart with `dyn Uart16550` - It will be very easy to add another method in addition to port and mmio --- Cargo.toml | 1 + src/lib.rs | 12 ++-- src/mmio.rs | 167 +++++++++++--------------------------------- src/port.rs | 171 ++++++++-------------------------------------- src/register.rs | 7 ++ src/uart_16550.rs | 117 +++++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+), 277 deletions(-) create mode 100644 src/register.rs create mode 100644 src/uart_16550.rs diff --git a/Cargo.toml b/Cargo.toml index 4d4a578..510fdf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" [dependencies] bitflags = "2" rustversion = "1.0.5" +volatile = "0.6.1" [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] x86 = "0.52" diff --git a/src/lib.rs b/src/lib.rs index c985e58..41b3be2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,14 +78,14 @@ macro_rules! retry_until_ok { } /// Memory mapped implementation -mod mmio; +pub mod mmio; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] /// Port asm commands implementation -mod port; - -pub use crate::mmio::MmioSerialPort; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -pub use crate::port::SerialPort; +pub mod port; +/// Trait for accessing a 16550's register +pub mod register; +/// Trait for using a 16550 compatible interface regardless of how it's connected +pub mod uart_16550; bitflags! { /// Interrupt enable flags diff --git a/src/mmio.rs b/src/mmio.rs index 090d337..4374559 100644 --- a/src/mmio.rs +++ b/src/mmio.rs @@ -1,143 +1,54 @@ -use core::{ - fmt, - sync::atomic::{AtomicPtr, Ordering}, -}; +use core::ptr::NonNull; -use crate::{LineStsFlags, WouldBlockError}; +use volatile::VolatileRef; -/// A memory-mapped UART. -#[derive(Debug)] -pub struct MmioSerialPort { - data: AtomicPtr, - int_en: AtomicPtr, - fifo_ctrl: AtomicPtr, - line_ctrl: AtomicPtr, - modem_ctrl: AtomicPtr, - line_sts: AtomicPtr, -} +use crate::{register::Uart16550Register, uart_16550::Uart16550Registers}; -impl MmioSerialPort { - /// Creates a new UART interface on the given memory mapped address. - /// - /// This function is unsafe because the caller must ensure that the given base address - /// really points to a serial port device. - #[rustversion::attr(since(1.61), const)] - pub unsafe fn new(base: usize) -> Self { - Self::new_with_stride(base, 1) - } +/// Basically a pointer to a memory mapped register of a 16550 +pub struct MemoryMappedRegister<'a> { + volatile_ref: VolatileRef<'a, u8>, +} - /// Creates a new UART interface on the given memory mapped address with a given - /// register stride. - /// - /// This function is unsafe because the caller must ensure that the given base address - /// really points to a serial port device. - #[rustversion::attr(since(1.61), const)] - pub unsafe fn new_with_stride(base: usize, stride: usize) -> Self { - let base_pointer = base as *mut u8; +impl MemoryMappedRegister<'_> { + unsafe fn new(ptr: NonNull) -> Self { Self { - data: AtomicPtr::new(base_pointer), - int_en: AtomicPtr::new(base_pointer.add(1 * stride)), - fifo_ctrl: AtomicPtr::new(base_pointer.add(2 * stride)), - line_ctrl: AtomicPtr::new(base_pointer.add(3 * stride)), - modem_ctrl: AtomicPtr::new(base_pointer.add(4 * stride)), - line_sts: AtomicPtr::new(base_pointer.add(5 * stride)), - } - } - - /// Initializes the memory-mapped UART. - /// - /// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used. - pub fn init(&mut self) { - let self_int_en = self.int_en.load(Ordering::Relaxed); - let self_line_ctrl = self.line_ctrl.load(Ordering::Relaxed); - let self_data = self.data.load(Ordering::Relaxed); - let self_fifo_ctrl = self.fifo_ctrl.load(Ordering::Relaxed); - let self_modem_ctrl = self.modem_ctrl.load(Ordering::Relaxed); - unsafe { - // Disable interrupts - self_int_en.write(0x00); - - // Enable DLAB - self_line_ctrl.write(0x80); - - // Set maximum speed to 38400 bps by configuring DLL and DLM - self_data.write(0x03); - self_int_en.write(0x00); - - // Disable DLAB and set data word length to 8 bits - self_line_ctrl.write(0x03); - - // Enable FIFO, clear TX/RX queues and - // set interrupt watermark at 14 bytes - self_fifo_ctrl.write(0xC7); - - // Mark data terminal ready, signal request to send - // and enable auxilliary output #2 (used as interrupt line for CPU) - self_modem_ctrl.write(0x0B); - - // Enable interrupts - self_int_en.write(0x01); - } - } - - fn line_sts(&mut self) -> LineStsFlags { - unsafe { LineStsFlags::from_bits_truncate(*self.line_sts.load(Ordering::Relaxed)) } - } - - /// Sends a byte on the serial port. - pub fn send(&mut self, data: u8) { - match data { - 8 | 0x7F => { - self.send_raw(8); - self.send_raw(b' '); - self.send_raw(8); - } - data => { - self.send_raw(data); - } - } - } - - /// Sends a raw byte on the serial port, intended for binary data. - pub fn send_raw(&mut self, data: u8) { - retry_until_ok!(self.try_send_raw(data)) - } - - /// Tries to send a raw byte on the serial port, intended for binary data. - pub fn try_send_raw(&mut self, data: u8) -> Result<(), WouldBlockError> { - if self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) { - let self_data = self.data.load(Ordering::Relaxed); - unsafe { - self_data.write(data); - } - Ok(()) - } else { - Err(WouldBlockError) + volatile_ref: VolatileRef::new(ptr), } } +} - /// Receives a byte on the serial port. - pub fn receive(&mut self) -> u8 { - retry_until_ok!(self.try_receive()) +impl Uart16550Register for MemoryMappedRegister<'_> { + fn read(&self) -> u8 { + self.volatile_ref.as_ptr().read() } - /// Tries to receive a byte on the serial port. - pub fn try_receive(&mut self) -> Result { - if self.line_sts().contains(LineStsFlags::INPUT_FULL) { - let self_data = self.data.load(Ordering::Relaxed); - let data = unsafe { self_data.read() }; - Ok(data) - } else { - Err(WouldBlockError) - } + fn write(&mut self, value: u8) { + self.volatile_ref.as_mut_ptr().write(value); } } -impl fmt::Write for MmioSerialPort { - fn write_str(&mut self, s: &str) -> fmt::Result { - for byte in s.bytes() { - self.send(byte); - } - Ok(()) +/// ## Safety +/// +/// - The pointer must map to the base register of a correctly memory mapped 16550. +/// - The stride must match the actual stride that is memory mapped. +/// - The pointer must be properly aligned. +/// - It must be “dereferenceable” in the sense defined in the [`core::ptr`] documentation. +/// - The pointer must point to an initialized instance of T. +/// - You must enforce Rust’s aliasing rules, since the returned lifetime 'a is arbitrarily +/// chosen and does not necessarily reflect the actual lifetime of the data. In particular, +/// while this `VolatileRef` exists, the memory the pointer points to must not get accessed +/// (_read or written_) through any other pointer. +pub unsafe fn new<'a>( + base_pointer: NonNull, + stride: usize, +) -> Uart16550Registers> { + #[allow(clippy::identity_op)] + Uart16550Registers { + data: MemoryMappedRegister::new(base_pointer), + int_en: MemoryMappedRegister::new(base_pointer.add(1 * stride)), + fifo_ctrl: MemoryMappedRegister::new(base_pointer.add(2 * stride)), + line_ctrl: MemoryMappedRegister::new(base_pointer.add(3 * stride)), + modem_ctrl: MemoryMappedRegister::new(base_pointer.add(4 * stride)), + line_sts: MemoryMappedRegister::new(base_pointer.add(5 * stride)), } } diff --git a/src/port.rs b/src/port.rs index 8dff982..90ca18e 100644 --- a/src/port.rs +++ b/src/port.rs @@ -1,156 +1,41 @@ -use core::fmt; +use crate::{register::Uart16550Register, uart_16550::Uart16550Registers}; -use crate::{LineStsFlags, WouldBlockError}; - -/// A x86 I/O port-mapped UART. -#[cfg_attr(docsrs, doc(cfg(any(target_arch = "x86", target_arch = "x86_64"))))] -#[derive(Debug)] -pub struct SerialPort(u16 /* base port */); - -impl SerialPort { - /// Base port. - fn port_base(&self) -> u16 { - self.0 - } - - /// Data port. - /// - /// Read and write. - fn port_data(&self) -> u16 { - self.port_base() - } - - /// Interrupt enable port. - /// - /// Write only. - fn port_int_en(&self) -> u16 { - self.port_base() + 1 - } - - /// Fifo control port. - /// - /// Write only. - fn port_fifo_ctrl(&self) -> u16 { - self.port_base() + 2 - } - - /// Line control port. - /// - /// Write only. - fn port_line_ctrl(&self) -> u16 { - self.port_base() + 3 - } - - /// Modem control port. - /// - /// Write only. - fn port_modem_ctrl(&self) -> u16 { - self.port_base() + 4 - } +/// A x86 I/O port-mapped 16550 register +pub struct PortAccessedRegister { + port: u16, +} - /// Line status port. - /// - /// Read only. - fn port_line_sts(&self) -> u16 { - self.port_base() + 5 +impl PortAccessedRegister { + unsafe fn new(port: u16) -> Self { + Self { port } } +} - /// Creates a new serial port interface on the given I/O base port. - /// - /// This function is unsafe because the caller must ensure that the given base address - /// really points to a serial port device and that the caller has the necessary rights - /// to perform the I/O operation. - pub const unsafe fn new(base: u16) -> Self { - Self(base) +impl Uart16550Register for PortAccessedRegister { + fn read(&self) -> u8 { + unsafe { x86::io::inb(self.port) } } - /// Initializes the serial port. - /// - /// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used. - pub fn init(&mut self) { + fn write(&mut self, value: u8) { unsafe { - // Disable interrupts - x86::io::outb(self.port_int_en(), 0x00); - - // Enable DLAB - x86::io::outb(self.port_line_ctrl(), 0x80); - - // Set maximum speed to 38400 bps by configuring DLL and DLM - x86::io::outb(self.port_data(), 0x03); - x86::io::outb(self.port_int_en(), 0x00); - - // Disable DLAB and set data word length to 8 bits - x86::io::outb(self.port_line_ctrl(), 0x03); - - // Enable FIFO, clear TX/RX queues and - // set interrupt watermark at 14 bytes - x86::io::outb(self.port_fifo_ctrl(), 0xc7); - - // Mark data terminal ready, signal request to send - // and enable auxilliary output #2 (used as interrupt line for CPU) - x86::io::outb(self.port_modem_ctrl(), 0x0b); - - // Enable interrupts - x86::io::outb(self.port_int_en(), 0x01); - } - } - - fn line_sts(&mut self) -> LineStsFlags { - unsafe { LineStsFlags::from_bits_truncate(x86::io::inb(self.port_line_sts())) } - } - - /// Sends a byte on the serial port. - pub fn send(&mut self, data: u8) { - match data { - 8 | 0x7F => { - self.send_raw(8); - self.send_raw(b' '); - self.send_raw(8); - } - data => { - self.send_raw(data); - } - } - } - - /// Sends a raw byte on the serial port, intended for binary data. - pub fn send_raw(&mut self, data: u8) { - retry_until_ok!(self.try_send_raw(data)) - } - - /// Tries to send a raw byte on the serial port, intended for binary data. - pub fn try_send_raw(&mut self, data: u8) -> Result<(), WouldBlockError> { - if self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) { - unsafe { - x86::io::outb(self.port_data(), data); - } - Ok(()) - } else { - Err(WouldBlockError) - } - } - - /// Receives a byte on the serial port. - pub fn receive(&mut self) -> u8 { - retry_until_ok!(self.try_receive()) - } - - /// Tries to receive a byte on the serial port. - pub fn try_receive(&mut self) -> Result { - if self.line_sts().contains(LineStsFlags::INPUT_FULL) { - let data = unsafe { x86::io::inb(self.port_data()) }; - Ok(data) - } else { - Err(WouldBlockError) + x86::io::outb(self.port, value); } } } -impl fmt::Write for SerialPort { - fn write_str(&mut self, s: &str) -> fmt::Result { - for byte in s.bytes() { - self.send(byte); - } - Ok(()) +/// Creates a new serial port interface on the given I/O base port. +/// +/// # Safety +/// This function is unsafe because the caller must ensure that the given base address +/// really points to a serial port device and that the caller has the necessary rights +/// to perform the I/O operation. +pub unsafe fn new(base: u16) -> Uart16550Registers { + Uart16550Registers { + data: PortAccessedRegister::new(base), + int_en: PortAccessedRegister::new(base + 1), + fifo_ctrl: PortAccessedRegister::new(base + 2), + line_ctrl: PortAccessedRegister::new(base + 3), + modem_ctrl: PortAccessedRegister::new(base + 4), + line_sts: PortAccessedRegister::new(base + 5), } } diff --git a/src/register.rs b/src/register.rs new file mode 100644 index 0000000..5f8216b --- /dev/null +++ b/src/register.rs @@ -0,0 +1,7 @@ +/// Trait for accessing a 16550's register +pub trait Uart16550Register { + #[allow(missing_docs)] + fn read(&self) -> u8; + #[allow(missing_docs)] + fn write(&mut self, value: u8); +} diff --git a/src/uart_16550.rs b/src/uart_16550.rs new file mode 100644 index 0000000..114ac95 --- /dev/null +++ b/src/uart_16550.rs @@ -0,0 +1,117 @@ +use core::fmt; + +use crate::{register::Uart16550Register, LineStsFlags, WouldBlockError}; + +/// Trait for using a 16550 compatible interface regardless of how it's connected +pub trait Uart16550: fmt::Write { + /// Initializes the UART. + /// + /// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used. + fn init(&mut self); + + /// Sends a byte on the serial port. + fn send(&mut self, data: u8); + + /// Sends a raw byte, intended for binary data. + fn send_raw(&mut self, data: u8) { + retry_until_ok!(self.try_send_raw(data)) + } + + /// Tries to send a raw byte, intended for binary data. + fn try_send_raw(&mut self, data: u8) -> Result<(), WouldBlockError>; + + /// Receives a byte. + fn receive(&mut self) -> u8 { + retry_until_ok!(self.try_receive()) + } + + /// Tries to receive a byte. + fn try_receive(&mut self) -> Result; +} + +/// A struct with all the 16550 registers needed to send and receive data +pub struct Uart16550Registers +where + R: Uart16550Register, +{ + pub(crate) data: R, + pub(crate) int_en: R, + pub(crate) fifo_ctrl: R, + pub(crate) line_ctrl: R, + pub(crate) modem_ctrl: R, + pub(crate) line_sts: R, +} + +impl Uart16550Registers { + fn line_sts(&mut self) -> LineStsFlags { + LineStsFlags::from_bits_truncate(self.line_sts.read()) + } +} + +impl Uart16550 for Uart16550Registers { + fn init(&mut self) { + // Disable interrupts + self.int_en.write(0x00); + + // Enable DLAB + self.line_ctrl.write(0x80); + + // Set maximum speed to 38400 bps by configuring DLL and DLM + self.data.write(0x03); + self.int_en.write(0x00); + + // Disable DLAB and set data word length to 8 bits + self.line_ctrl.write(0x03); + + // Enable FIFO, clear TX/RX queues and + // set interrupt watermark at 14 bytes + self.fifo_ctrl.write(0xC7); + + // Mark data terminal ready, signal request to send + // and enable auxilliary output #2 (used as interrupt line for CPU) + self.modem_ctrl.write(0x0B); + + // Enable interrupts + self.int_en.write(0x01); + } + + fn send(&mut self, data: u8) { + match data { + 8 | 0x7F => { + self.send_raw(8); + self.send_raw(b' '); + self.send_raw(8); + } + data => { + self.send_raw(data); + } + } + } + + fn try_send_raw(&mut self, data: u8) -> Result<(), WouldBlockError> { + if self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) { + self.data.write(data); + Ok(()) + } else { + Err(WouldBlockError) + } + } + + fn try_receive(&mut self) -> Result { + if self.line_sts().contains(LineStsFlags::INPUT_FULL) { + let data = self.data.read(); + Ok(data) + } else { + Err(WouldBlockError) + } + } +} + +impl fmt::Write for Uart16550Registers { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + self.send(byte); + } + Ok(()) + } +} From cb281d32bbb7f929a79670c6b3dab09c449299c2 Mon Sep 17 00:00:00 2001 From: Rajas Paranjpe <52586855+ChocolateLoverRaj@users.noreply.github.com> Date: Tue, 25 Feb 2025 18:19:26 -0800 Subject: [PATCH 2/2] Make port fns const --- src/port.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/port.rs b/src/port.rs index 90ca18e..d8907d4 100644 --- a/src/port.rs +++ b/src/port.rs @@ -6,7 +6,7 @@ pub struct PortAccessedRegister { } impl PortAccessedRegister { - unsafe fn new(port: u16) -> Self { + const unsafe fn new(port: u16) -> Self { Self { port } } } @@ -29,7 +29,7 @@ impl Uart16550Register for PortAccessedRegister { /// This function is unsafe because the caller must ensure that the given base address /// really points to a serial port device and that the caller has the necessary rights /// to perform the I/O operation. -pub unsafe fn new(base: u16) -> Uart16550Registers { +pub const unsafe fn new(base: u16) -> Uart16550Registers { Uart16550Registers { data: PortAccessedRegister::new(base), int_en: PortAccessedRegister::new(base + 1),