Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@
- Documentation improvements
- Internal safety fixes (there was no UB, just making the internal code more
robust)
- `Uart16550::new_mmio()` and `Uart16550Ttty::new_mmio()` now accept a
`NonNull<u8>` instead of a `*mut u8`. The recommended way to construct the
MMIO address is to use: \
```rust
fn main() {
let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
let mmio_address = NonNull::new(mmio_address).unwrap();
let mut uart = unsafe { Uart16550::new_mmio(mmio_address, 4).unwrap() };
}
```

## 0.5.0 - 2026-03-20

Expand Down
5 changes: 0 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ use core::fmt::Display;
/// [NUM_REGISTERS]: crate::spec::NUM_REGISTERS
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum InvalidAddressError<A: RegisterAddress> {
/// The specified address is null.
Null,
/// The given base address is invalid, e.g., it cannot accommodate
/// <code>[NUM_REGISTERS] - 1</code> consecutive addresses.
///
Expand All @@ -33,9 +31,6 @@ pub enum InvalidAddressError<A: RegisterAddress> {
impl<A: RegisterAddress> Display for InvalidAddressError<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => {
write!(f, "address is null")
}
Self::InvalidBaseAddress(addr) => {
write!(f, "invalid register address: {addr:x?}")
}
Expand Down
59 changes: 41 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,13 @@
//!
//! ```rust,no_run
//! use uart_16550::{Config, Uart16550};
//! use core::ptr::{self, NonNull};
//!
//! let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
//! let mmio_address = NonNull::new(mmio_address).unwrap();
//!
//! // SAFETY: The address is valid and we have exclusive access.
//! let mut uart = unsafe { Uart16550::new_mmio(0x1000 as *mut _, 4).unwrap() };
//! let mut uart = unsafe { Uart16550::new_mmio(mmio_address, 4).unwrap() };
//! uart.init(Config::default()).expect("should init device successfully");
//! uart.send_bytes_exact(b"hello world!");
//! ```
Expand All @@ -88,9 +92,13 @@
//!
//! ```rust,no_run
//! use uart_16550::{Config, Uart16550};
//! use core::ptr::{self, NonNull};
//!
//! let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
//! let mmio_address = NonNull::new(mmio_address).unwrap();
//!
//! // SAFETY: The address is valid and we have exclusive access.
//! let mut uart = unsafe { Uart16550::new_mmio(0x1000 as *mut _, 4).expect("should be valid port") };
//! let mut uart = unsafe { Uart16550::new_mmio(mmio_address, 4).expect("should be valid port") };
//! // ^ or `new_port(0x3f8)`
//! uart.init(Config::default()).expect("should init device successfully");
//! uart.test_loopback().expect("should have working loopback mode");
Expand Down Expand Up @@ -214,9 +222,13 @@ mod tty;
///
/// ```rust,no_run
/// use uart_16550::{Config, Uart16550};
/// use core::ptr::{self, NonNull};
///
/// let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
/// let mmio_address = NonNull::new(mmio_address).unwrap();
///
/// // SAFETY: The address is valid and we have exclusive access.
/// let mut uart = unsafe { Uart16550::new_mmio(0x1000 as *mut _, 4).unwrap() };
/// let mut uart = unsafe { Uart16550::new_mmio(mmio_address, 4).unwrap() };
/// uart.init(Config::default()).expect("should init device successfully");
/// uart.send_bytes_exact(b"hello world!");
/// ```
Expand All @@ -225,9 +237,13 @@ mod tty;
///
/// ```rust,no_run
/// use uart_16550::{Config, Uart16550};
/// use core::ptr::{self, NonNull};
///
/// let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
/// let mmio_address = NonNull::new(mmio_address).unwrap();
///
/// // SAFETY: The address is valid and we have exclusive access.
/// let mut uart = unsafe { Uart16550::new_mmio(0x1000 as *mut _, 4).expect("should be valid port") };
/// let mut uart = unsafe { Uart16550::new_mmio(mmio_address, 4).expect("should be valid port") };
/// // ^ or `new_port(0x3f8)`
/// uart.init(Config::default()).expect("should init device successfully");
/// uart.test_loopback().expect("should have working loopback mode");
Expand Down Expand Up @@ -333,10 +349,9 @@ impl Uart16550<MmioBackend> {
/// the **whole lifetime** of the device. Further, all [`NUM_REGISTERS`]
/// registers must be safely reachable from the base address.
pub unsafe fn new_mmio(
base_address: *mut u8,
base_address: NonNull<u8>,
stride: u8,
) -> Result<Self, InvalidAddressError<MmioAddress>> {
let base_address = NonNull::new(base_address).ok_or(InvalidAddressError::Null)?;
let base_address = MmioAddress(base_address);

if stride == 0 || !stride.is_power_of_two() {
Expand Down Expand Up @@ -989,6 +1004,7 @@ impl ConfigRegisterDump {
#[cfg(test)]
mod tests {
use super::*;
use core::ptr;

#[test]
fn constructors() {
Expand All @@ -1004,34 +1020,37 @@ mod tests {

// SAFETY: We just test the constructor but do not access the device.
unsafe {
assert2::assert!(let Ok(_) = Uart16550::new_mmio(0x1000 as *mut _, 1));
assert2::assert!(let Ok(_) = Uart16550::new_mmio(0x1000 as *mut _, 2));
assert2::assert!(let Ok(_) = Uart16550::new_mmio(0x1000 as *mut _, 4));
assert2::assert!(let Ok(_) = Uart16550::new_mmio(0x1000 as *mut _, 8));
let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
let mmio_address = NonNull::new(mmio_address).unwrap();

assert2::assert!(let Ok(_) = Uart16550::new_mmio(mmio_address, 1));
assert2::assert!(let Ok(_) = Uart16550::new_mmio(mmio_address, 2));
assert2::assert!(let Ok(_) = Uart16550::new_mmio(mmio_address, 4));
assert2::assert!(let Ok(_) = Uart16550::new_mmio(mmio_address, 8));

assert2::assert!(
let Err(InvalidAddressError::InvalidStride(0)) =
Uart16550::new_mmio(0x1000 as *mut _, 0)
Uart16550::new_mmio(mmio_address, 0)
);
assert2::assert!(
let Err(InvalidAddressError::InvalidStride(3)) =
Uart16550::new_mmio(0x1000 as *mut _, 3)
Uart16550::new_mmio(mmio_address, 3)
);
assert2::assert!(
let Err(InvalidAddressError::InvalidStride(5)) =
Uart16550::new_mmio(0x1000 as *mut _, 5)
Uart16550::new_mmio(mmio_address, 5)
);
assert2::assert!(
let Err(InvalidAddressError::InvalidStride(6)) =
Uart16550::new_mmio(0x1000 as *mut _, 6)
Uart16550::new_mmio(mmio_address, 6)
);
assert2::assert!(
let Err(InvalidAddressError::InvalidStride(7)) =
Uart16550::new_mmio(0x1000 as *mut _, 7)
Uart16550::new_mmio(mmio_address, 7)
);
assert2::assert!(
let Err(InvalidAddressError::InvalidStride(9)) =
Uart16550::new_mmio(0x1000 as *mut _, 9)
Uart16550::new_mmio(mmio_address, 9)
);
}
}
Expand Down Expand Up @@ -1062,8 +1081,10 @@ mod tests {
// Unblock init()
memory[offsets::LSR] = LSR::TRANSMITTER_EMPTY.bits();

let mmio_address = NonNull::new(memory.as_mut_ptr()).unwrap();

// SAFETY: We are operating on valid memory.
let mut uart = unsafe { Uart16550::new_mmio(memory.as_mut_ptr(), STRIDE as u8) }
let mut uart = unsafe { Uart16550::new_mmio(mmio_address, STRIDE as u8) }
.expect("should be able to create the dummy MMIO");

uart.init(config.clone())
Expand All @@ -1083,8 +1104,10 @@ mod tests {
// Unblock init()
memory[offsets::LSR * STRIDE] = LSR::TRANSMITTER_EMPTY.bits();

let mmio_address = NonNull::new(memory.as_mut_ptr()).unwrap();

// SAFETY: We are operating on valid memory.
let mut uart = unsafe { Uart16550::new_mmio(memory.as_mut_ptr(), STRIDE as u8) }
let mut uart = unsafe { Uart16550::new_mmio(mmio_address, STRIDE as u8) }
.expect("should be able to create the dummy MMIO");

uart.init(config)
Expand Down
9 changes: 7 additions & 2 deletions src/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::backend::{PioBackend, PortIoAddress};
use crate::{Config, InitError, InvalidAddressError, LoopbackError, Uart16550};
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::ptr::NonNull;

/// Errors that [`Uart16550Tty::new_port()`] and [`Uart16550Tty::new_mmio()`] may
/// return.
Expand Down Expand Up @@ -88,9 +89,13 @@ impl<A: RegisterAddress + 'static> Error for Uart16550TtyError<A> {
/// ```rust,no_run
/// use uart_16550::{Config, Uart16550Tty};
/// use core::fmt::Write;
/// use core::ptr::{self, NonNull};
///
/// let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
/// let mmio_address = NonNull::new(mmio_address).unwrap();
///
/// // SAFETY: The address is valid and we have exclusive access.
/// let mut uart = unsafe { Uart16550Tty::new_mmio(0x1000 as *mut _, 4, Config::default()).expect("should initialize device") };
/// let mut uart = unsafe { Uart16550Tty::new_mmio(mmio_address, 4, Config::default()).expect("should initialize device") };
/// uart.write_str("hello world\nhow's it going?");
/// ```
///
Expand Down Expand Up @@ -164,7 +169,7 @@ impl Uart16550Tty<MmioBackend> {
///
/// [`NUM_REGISTERS`]: crate::spec::NUM_REGISTERS
pub unsafe fn new_mmio(
base_address: *mut u8,
base_address: NonNull<u8>,
stride: u8,
config: Config,
) -> Result<Self, Uart16550TtyError<MmioAddress>> {
Expand Down
26 changes: 26 additions & 0 deletions tests/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use core::ptr::{self, NonNull};
use uart_16550::Uart16550;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use uart_16550::backend::PioBackend;
use uart_16550::backend::{Backend, MmioBackend};

/// This ensures that all necessary helper types to create bindings are publicly
/// exported.
///
/// This test succeeds if it compiles.
#[test]
fn test_public_api() {
fn consume(_device: Uart16550<impl Backend>) {}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
// SAFETY: This is a synthetic example and the hardware is not accessed.
let device: Uart16550<PioBackend> = unsafe { Uart16550::new_port(0x3f8) }.unwrap();
consume(device);
}
let mmio_address = ptr::with_exposed_provenance_mut::<u8>(0x1000);
let mmio_address = NonNull::new(mmio_address).unwrap();
// SAFETY: This is a synthetic example and the hardware is not accessed.
let device: Uart16550<MmioBackend> = unsafe { Uart16550::new_mmio(mmio_address, 1) }.unwrap();
consume(device);
}