1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-05-23 10:50:18 +02:00

Use safe-mmio crate for PL011 UART driver example.

This commit is contained in:
Andrew Walbran 2025-05-16 13:32:09 +00:00
parent 77b201c4cb
commit cb94a34cbf
9 changed files with 88 additions and 64 deletions

View File

@ -11,5 +11,7 @@ with bitflags.
- The `bitflags!` macro creates a newtype something like `Flags(u16)`, along - The `bitflags!` macro creates a newtype something like `Flags(u16)`, along
with a bunch of method implementations to get and set flags. with a bunch of method implementations to get and set flags.
- We need to derive `FromBytes` and `IntoBytes` for use with `safe-mmio`, which
we'll see on the next page.
</details> </details>

View File

@ -8,7 +8,16 @@ Now let's use the new `Registers` struct in our driver.
<details> <details>
- Note the use of `&raw const` / `&raw mut` to get pointers to individual fields - `UniqueMmioPointer` is a wrapper around a raw pointer to an MMIO device or
without creating an intermediate reference, which would be unsound. register. The caller of `UniqueMmioPointer::new` promises that it is valid and
unique for the given lifetime, so it can provide safe methods to read and
write fields.
- These MMIO accesses are generally a wrapper around `read_volatile` and
`write_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!` and `field_shared!` macros internally use `&raw mut` and
`&raw const` to get pointers to individual fields without creating an
intermediate reference, which would be unsound.
</details> </details>

View File

@ -1,6 +1,8 @@
# Multiple registers # Multiple registers
We can use a struct to represent the memory layout of the UART's registers. We can use a struct to represent the memory layout of the UART's registers,
using types from the `safe-mmio` crate to wrap ones which can be read or written
safely.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
@ -15,5 +17,12 @@ We can use a struct to represent the memory layout of the UART's registers.
rules as C. This is necessary for our struct to have a predictable layout, as rules as C. This is necessary for our struct to have a predictable layout, as
default Rust representation allows the compiler to (among other things) default Rust representation allows the compiler to (among other things)
reorder fields however it sees fit. reorder fields however it sees fit.
- There are a number of different crates providing safe abstractions around MMIO
operations; we recommend the `safe-mmio` crate.
- The difference between `ReadPure` or `ReadOnly` (and likewise between
`ReadPureWrite` and `ReadWrite`) is whether reading a register can have
side-effects which change the state of the device. E.g. reading the data
register pops a byte from the receive FIFO. `ReadPure` means that reads have
no side-effects, they are purely reading data.
</details> </details>

View File

@ -31,8 +31,10 @@ dependencies = [
"arm-pl011-uart", "arm-pl011-uart",
"bitflags", "bitflags",
"log", "log",
"safe-mmio",
"smccc", "smccc",
"spin", "spin",
"zerocopy",
] ]
[[package]] [[package]]

View File

@ -12,8 +12,10 @@ aarch64-rt = "0.1.3"
arm-pl011-uart = "0.3.1" arm-pl011-uart = "0.3.1"
bitflags = "2.9.0" bitflags = "2.9.0"
log = "0.4.27" log = "0.4.27"
safe-mmio = "0.2.5"
smccc = "0.2.0" smccc = "0.2.0"
spin = "0.10.0" spin = "0.10.0"
zerocopy = "0.8.25"
[[bin]] [[bin]]
name = "improved" name = "improved"

View File

@ -21,7 +21,7 @@ use spin::mutex::SpinMutex;
static LOGGER: Logger = Logger { uart: SpinMutex::new(None) }; static LOGGER: Logger = Logger { uart: SpinMutex::new(None) };
struct Logger { struct Logger {
uart: SpinMutex<Option<Uart>>, uart: SpinMutex<Option<Uart<'static>>>,
} }
impl Log for Logger { impl Log for Logger {
@ -43,7 +43,10 @@ impl Log for Logger {
} }
/// Initialises UART logger. /// Initialises UART logger.
pub fn init(uart: Uart, max_level: LevelFilter) -> Result<(), SetLoggerError> { pub fn init(
uart: Uart<'static>,
max_level: LevelFilter,
) -> Result<(), SetLoggerError> {
LOGGER.uart.lock().replace(uart); LOGGER.uart.lock().replace(uart);
log::set_logger(&LOGGER)?; log::set_logger(&LOGGER)?;

View File

@ -22,19 +22,22 @@ mod pl011;
use crate::pl011::Uart; use crate::pl011::Uart;
use core::fmt::Write; use core::fmt::Write;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use core::ptr::NonNull;
use log::error; use log::error;
use safe_mmio::UniqueMmioPointer;
use smccc::psci::system_off; use smccc::psci::system_off;
use smccc::Hvc; use smccc::Hvc;
/// Base address of the primary PL011 UART. /// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _; const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
NonNull::new(0x900_0000 as _).unwrap();
// SAFETY: There is no other global function of this name. // SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) { extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
// SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
// nothing else accesses that address range. // nothing else accesses that address range.
let mut uart = unsafe { Uart::new(PL011_BASE_ADDRESS) }; let mut uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap(); writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();

View File

@ -22,19 +22,22 @@ mod pl011;
use crate::pl011::Uart; use crate::pl011::Uart;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use core::ptr::NonNull;
use log::{error, info, LevelFilter}; use log::{error, info, LevelFilter};
use safe_mmio::UniqueMmioPointer;
use smccc::psci::system_off; use smccc::psci::system_off;
use smccc::Hvc; use smccc::Hvc;
/// Base address of the primary PL011 UART. /// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _; const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
NonNull::new(0x900_0000 as _).unwrap();
// SAFETY: There is no other global function of this name. // SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) { extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
// SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
// nothing else accesses that address range. // nothing else accesses that address range.
let uart = unsafe { Uart::new(PL011_BASE_ADDRESS) }; let uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
logger::init(uart, LevelFilter::Trace).unwrap(); logger::init(uart, LevelFilter::Trace).unwrap();
info!("main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})"); info!("main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})");

View File

@ -17,12 +17,15 @@ use core::fmt::{self, Write};
// ANCHOR: Flags // ANCHOR: Flags
use bitflags::bitflags; use bitflags::bitflags;
use zerocopy::{FromBytes, IntoBytes};
/// Flags from the UART flag register.
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, FromBytes, IntoBytes, PartialEq)]
struct Flags(u16);
bitflags! { bitflags! {
/// Flags from the UART flag register. impl Flags: u16 {
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Flags: u16 {
/// Clear to send. /// Clear to send.
const CTS = 1 << 0; const CTS = 1 << 0;
/// Data set ready. /// Data set ready.
@ -45,11 +48,13 @@ bitflags! {
} }
// ANCHOR_END: Flags // ANCHOR_END: Flags
/// Flags from the UART Receive Status Register / Error Clear Register.
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, FromBytes, IntoBytes, PartialEq)]
struct ReceiveStatus(u16);
bitflags! { bitflags! {
/// Flags from the UART Receive Status Register / Error Clear Register. impl ReceiveStatus: u16 {
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct ReceiveStatus: u16 {
/// Framing error. /// Framing error.
const FE = 1 << 0; const FE = 1 << 0;
/// Parity error. /// Parity error.
@ -62,70 +67,64 @@ bitflags! {
} }
// ANCHOR: Registers // ANCHOR: Registers
use safe_mmio::fields::{ReadPure, ReadPureWrite, ReadWrite, WriteOnly};
#[repr(C, align(4))] #[repr(C, align(4))]
struct Registers { pub struct Registers {
dr: u16, dr: ReadWrite<u16>,
_reserved0: [u8; 2], _reserved0: [u8; 2],
rsr: ReceiveStatus, rsr: ReadPure<ReceiveStatus>,
_reserved1: [u8; 19], _reserved1: [u8; 19],
fr: Flags, fr: ReadPure<Flags>,
_reserved2: [u8; 6], _reserved2: [u8; 6],
ilpr: u8, ilpr: ReadPureWrite<u8>,
_reserved3: [u8; 3], _reserved3: [u8; 3],
ibrd: u16, ibrd: ReadPureWrite<u16>,
_reserved4: [u8; 2], _reserved4: [u8; 2],
fbrd: u8, fbrd: ReadPureWrite<u8>,
_reserved5: [u8; 3], _reserved5: [u8; 3],
lcr_h: u8, lcr_h: ReadPureWrite<u8>,
_reserved6: [u8; 3], _reserved6: [u8; 3],
cr: u16, cr: ReadPureWrite<u16>,
_reserved7: [u8; 3], _reserved7: [u8; 3],
ifls: u8, ifls: ReadPureWrite<u8>,
_reserved8: [u8; 3], _reserved8: [u8; 3],
imsc: u16, imsc: ReadPureWrite<u16>,
_reserved9: [u8; 2], _reserved9: [u8; 2],
ris: u16, ris: ReadPure<u16>,
_reserved10: [u8; 2], _reserved10: [u8; 2],
mis: u16, mis: ReadPure<u16>,
_reserved11: [u8; 2], _reserved11: [u8; 2],
icr: u16, icr: WriteOnly<u16>,
_reserved12: [u8; 2], _reserved12: [u8; 2],
dmacr: u8, dmacr: ReadPureWrite<u8>,
_reserved13: [u8; 3], _reserved13: [u8; 3],
} }
// ANCHOR_END: Registers // ANCHOR_END: Registers
// ANCHOR: Uart // ANCHOR: Uart
use safe_mmio::{field, field_shared, UniqueMmioPointer};
/// Driver for a PL011 UART. /// Driver for a PL011 UART.
#[derive(Debug)] #[derive(Debug)]
pub struct Uart { pub struct Uart<'a> {
registers: *mut Registers, registers: UniqueMmioPointer<'a, Registers>,
} }
impl Uart { impl<'a> Uart<'a> {
/// Constructs a new instance of the UART driver for a PL011 device at the /// Constructs a new instance of the UART driver for a PL011 device with the
/// given base address. /// given set of registers.
/// pub fn new(registers: UniqueMmioPointer<'a, Registers>) -> Self {
/// # Safety Self { registers }
///
/// The given base address must point to the 8 MMIO control registers of a
/// PL011 device, which must be mapped into the address space of the process
/// as device memory and not have any other aliases.
pub unsafe fn new(base_address: *mut u32) -> Self {
Self { registers: base_address as *mut Registers }
} }
/// Writes a single byte to the UART. /// Writes a single byte to the UART.
pub fn write_byte(&self, byte: u8) { pub fn write_byte(&mut self, byte: u8) {
// Wait until there is room in the TX buffer. // Wait until there is room in the TX buffer.
while self.read_flag_register().contains(Flags::TXFF) {} while self.read_flag_register().contains(Flags::TXFF) {}
// SAFETY: We know that self.registers points to the control registers
// of a PL011 device which is appropriately mapped.
unsafe {
// Write to the TX buffer. // Write to the TX buffer.
(&raw mut (*self.registers).dr).write_volatile(byte.into()); field!(self.registers, dr).write(byte.into());
}
// Wait until the UART is no longer busy. // Wait until the UART is no longer busy.
while self.read_flag_register().contains(Flags::BUSY) {} while self.read_flag_register().contains(Flags::BUSY) {}
@ -133,27 +132,23 @@ impl Uart {
/// Reads and returns a pending byte, or `None` if nothing has been /// Reads and returns a pending byte, or `None` if nothing has been
/// received. /// received.
pub fn read_byte(&self) -> Option<u8> { pub fn read_byte(&mut self) -> Option<u8> {
if self.read_flag_register().contains(Flags::RXFE) { if self.read_flag_register().contains(Flags::RXFE) {
None None
} else { } else {
// SAFETY: We know that self.registers points to the control let data = field!(self.registers, dr).read();
// registers of a PL011 device which is appropriately mapped.
let data = unsafe { (&raw const (*self.registers).dr).read_volatile() };
// TODO: Check for error conditions in bits 8-11. // TODO: Check for error conditions in bits 8-11.
Some(data as u8) Some(data as u8)
} }
} }
fn read_flag_register(&self) -> Flags { fn read_flag_register(&self) -> Flags {
// SAFETY: We know that self.registers points to the control registers field_shared!(self.registers, fr).read()
// of a PL011 device which is appropriately mapped.
unsafe { (&raw const (*self.registers).fr).read_volatile() }
} }
} }
// ANCHOR_END: Uart // ANCHOR_END: Uart
impl Write for Uart { impl Write for Uart<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result { fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.as_bytes() { for c in s.as_bytes() {
self.write_byte(*c); self.write_byte(*c);
@ -161,7 +156,3 @@ impl Write for Uart {
Ok(()) Ok(())
} }
} }
// Safe because it just contains a pointer to device memory, which can be
// accessed from any context.
unsafe impl Send for Uart {}