Давайте напишемо драйвер UART

Машина QEMU 'virt' має PL011 UART, тож давайте напишемо для нього драйвер.

const FLAG_REGISTER_OFFSET: usize = 0x18;
const FR_BUSY: u8 = 1 << 3;
const FR_TXFF: u8 = 1 << 5;

/// Мінімальний драйвер для PL011 UART.
#[derive(Debug)]
pub struct Uart {
    base_address: *mut u8,
}

impl Uart {
    /// Створює новий екземпляр драйвера UART для пристрою PL011
    /// за заданою базовою адресою.
    ///
    /// # Безпека
    ///
    /// Задана базова адреса повинна вказувати на 8 керуючих регістрів MMIO пристрою 
    /// PL011, які повинні бути відображені в адресному просторі процесу
    /// як пам'ять пристрою і не мати ніяких інших псевдонімів.
    pub unsafe fn new(base_address: *mut u8) -> Self {
        Self { base_address }
    }

    /// Записує один байт до UART.
    pub fn write_byte(&self, byte: u8) {
        // Чекаємо, поки не звільниться місце в буфері TX.
        while self.read_flag_register() & FR_TXFF != 0 {}

        // БЕЗПЕКА: ми знаємо, що базова адреса вказує на регістри
        // керування пристрою PL011, які відповідним чином відображені.
        unsafe {
            // Записуємо в буфер TX.
            self.base_address.write_volatile(byte);
        }

        // Чекаємо, поки UART більше не буде зайнято.
        while self.read_flag_register() & FR_BUSY != 0 {}
    }

    fn read_flag_register(&self) -> u8 {
        // БЕЗПЕКА: ми знаємо, що базова адреса вказує на регістри
        // керування пристрою PL011, які відповідним чином відображені.
        unsafe { self.base_address.add(FLAG_REGISTER_OFFSET).read_volatile() }
    }
}
  • Зауважте, що Uart::new є небезпечним, тоді як інші методи є безпечними. Це пов'язано з тим, що доки викликач Uart::new гарантує, що його вимоги безпеки дотримано (тобто, що існує лише один екземпляр драйвера для даного UART, і ніщо інше не змінює його адресний простір), доти безпечно викликати write_byte пізніше, оскільки ми можемо припустити, що виконано необхідні передумови.
  • Ми могли б зробити це навпаки (зробити new безпечним, але write_byte небезпечним), але це було б набагато менш зручно використовувати, оскільки кожне місце, яке викликає write_byte, мало б міркувати про безпеку
  • Це загальний шаблон для написання безпечних оболонок небезпечного коду: перенесення тягаря доведення правильності з великої кількості місць на меншу кількість місць.