Напишем драйвер 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) с большого числа мест на меньшее число мест.