Driver
Now let’s use the new Registers
struct in our driver.
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() } }
UniqueMmioPointer
is a wrapper around a raw pointer to an MMIO device or register. The caller ofUniqueMmioPointer::new
promises that it is valid and unique for the given lifetime, so it can provide safe methods to read and write fields.- Note that
Uart::new
is now safe;UniqueMmioPointer::new
is unsafe instead. - These MMIO accesses are generally a wrapper around
read_volatile
andwrite_volatile
, though on aarch64 they are instead implemented in assembly to work around a bug where the compiler can emit instructions that prevent MMIO virtualisation. - The
field!
andfield_shared!
macros internally use&raw mut
and&raw const
to get pointers to individual fields without creating an intermediate reference, which would be unsound.