Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Напишем драйвер UART

В машине QEMU ‘virt’ есть UART PL011, поэтому напишем для него драйвер.

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

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

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

impl Uart {
    /// Создаёт новый экземпляр драйвера UART для устройства PL011 по
    /// заданному базовому адресу.
    ///
    /// # Safety
    ///
    /// Заданный базовый адрес должен указывать на 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 {}

        // SAFETY: Мы знаем, что базовый адрес указывает на управляющие
        // регистры устройства PL011, которое корректно отображено.
        unsafe {
            // Записываем в буфер передачи (TX).
            self.base_address.write_volatile(byte);
        }

        // Ждём, пока UART больше не занят.
        while self.read_flag_register() & FR_BUSY != 0 {}
    }

    fn read_flag_register(&self) -> u8 {
        // SAFETY: Мы знаем, что базовый адрес указывает на управляющие
        // регистры устройства PL011, которое корректно отображено.
        unsafe { self.base_address.add(FLAG_REGISTER_OFFSET).read_volatile() }
    }
}
  • Обратите внимание, что Uart::new небезопасна, а остальные методы безопасны. Это потому, что если вызывающий Uart::new гарантирует выполнение её требований безопасности (то есть что для данного UART существует только один экземпляр драйвера и ничто другое не использует его адресное пространство через алиасы), то write_byte всегда безопасно вызывать позже, поскольку мы можем считать необходимые предусловия выполненными.
  • Можно было бы сделать наоборот (сделать new безопасной, а write_byte небезопасной), но тогда использовать это было бы гораздо неудобнее, так как каждое место, где вызывается write_byte, пришлось бы обосновывать безопасность
  • Это распространённый паттерн для написания безопасных обёрток над небезопасным кодом: перенос бремени доказательства корректности (soundness) с большого числа мест на меньшее число мест.