Skip to content

Commit

Permalink
Use traits for register and uart
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ChocolateLoverRaj committed Feb 25, 2025
1 parent 53b7a1b commit 94fe27e
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 277 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 6 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
167 changes: 39 additions & 128 deletions src/mmio.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
int_en: AtomicPtr<u8>,
fifo_ctrl: AtomicPtr<u8>,
line_ctrl: AtomicPtr<u8>,
modem_ctrl: AtomicPtr<u8>,
line_sts: AtomicPtr<u8>,
}
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<u8>) -> 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<u8, WouldBlockError> {
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<u8>,
stride: usize,
) -> Uart16550Registers<MemoryMappedRegister<'a>> {
#[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)),
}
}
171 changes: 28 additions & 143 deletions src/port.rs
Original file line number Diff line number Diff line change
@@ -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<u8, WouldBlockError> {
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<PortAccessedRegister> {
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),
}
}
Loading

0 comments on commit 94fe27e

Please sign in to comment.