Driver
Now let’s use the new Registers struct in our driver.
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
use safe_mmio::{UniqueMmioPointer, field, field_shared};
/// Driver for a PL011 UART.
#[derive(Debug)]
pub struct Uart<'a> {
registers: UniqueMmioPointer<'a, Registers>,
}
impl<'a> Uart<'a> {
/// Constructs a new instance of the UART driver for a PL011 device with the
/// given set of registers.
pub fn new(registers: UniqueMmioPointer<'a, Registers>) -> Self {
Self { registers }
}
/// Writes a single byte to the UART.
pub fn write_byte(&mut self, byte: u8) {
// Wait until there is room in the TX buffer.
while self.read_flag_register().contains(Flags::TXFF) {}
// Write to the TX buffer.
field!(self.registers, dr).write(byte.into());
// Wait until the UART is no longer busy.
while self.read_flag_register().contains(Flags::BUSY) {}
}
/// Reads and returns a pending byte, or `None` if nothing has been
/// received.
pub fn read_byte(&mut self) -> Option<u8> {
if self.read_flag_register().contains(Flags::RXFE) {
None
} else {
let data = field!(self.registers, dr).read();
// TODO: Check for error conditions in bits 8-11.
Some(data as u8)
}
}
fn read_flag_register(&self) -> Flags {
field_shared!(self.registers, fr).read()
}
}
- The driver no longer needs any unsafe code!
UniqueMmioPointeris a wrapper around a raw pointer to an MMIO device or register. The caller ofUniqueMmioPointer::newpromises that it is valid and unique for the given lifetime, so it can provide safe methods to read and write fields.- Note that
Uart::newis now safe;UniqueMmioPointer::newis unsafe instead. - These MMIO accesses are generally a wrapper around
read_volatileandwrite_volatile, though on aarch64 they are instead implemented in assembly to work around a bug where the compiler can emit instructions that prevent MMIO virtualization. - The
field!andfield_shared!macros internally use&raw mutand&raw constto get pointers to individual fields without creating an intermediate reference, which would be unsound. field!needs a mutable reference to aUniqueMmioPointer, and returns aUniqueMmioPointerthat allows reads with side effects and writes.field_shared!works with a shared reference to either aUniqueMmioPointeror aSharedMmioPointer. It returns aSharedMmioPointerthat only allows pure reads.