Vamos escrever um driver UART

A máquina 'virt' do QEMU possui um UART PL011, então vamos escrever um driver para isso.

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

/// Driver mínimo para um UART PL011.
#[derive(Debug)]
pub struct Uart {
    base_address: *mut u8,
}

impl Uart {
    /// Constrói uma nova instância do driver UART para um dispositivo PL011 no endereço
    /// base fornecido.
    ///
    /// # Segurança
    ///
    /// O endereço base fornecido deve apontar para os 8 registradores de controle MMIO de um
    /// dispositivo PL011, que deve ser mapeado no espaço de endereços do processo
    /// como memória de dispositivo e não ter nenhum outro alias.
    pub unsafe fn new(base_address: *mut u8) -> Self {
        Self { base_address }
    }

    /// Grava um único byte no UART.
    pub fn write_byte(&self, byte: u8) {
        // Aguarde até que haja espaço no buffer TX.
        while self.read_flag_register() & FR_TXFF != 0 {}

        // SEGURANÇA: porque sabemos que o endereço base aponta para o controle
        // registradores de um dispositivo PL011 que está mapeado adequadamente.
        unsafe {
            // Escreva no buffer TX.
            self.base_address.write_volatile(byte);
        }

        // Aguarde até que o UART não esteja mais ocupado.
        while self.read_flag_register() & FR_BUSY != 0 {}
    }

    fn read_flag_register(&self) -> u8 {
        // SEGURANÇA: porque sabemos que o endereço base aponta para o controle
        // registradores de um dispositivo PL011 que está mapeado adequadamente.
        unsafe { self.base_address.add(FLAG_REGISTER_OFFSET).read_volatile() }
    }
}
  • Observe que Uart::new não é seguro, enquanto os outros métodos são seguros. Isso ocorre porque, desde que o chamador de Uart::new garanta que seus requisitos de segurança sejam atendidos (ou seja, que haja apenas uma instância do driver para um determinado UART e nada mais que faça alias do seu espaço de endereço), então é sempre seguro chamar write_byte mais tarde porque podemos assumir as precondições necessárias.
  • Poderíamos ter feito o contrário (tornando new seguro, mas write_byte inseguro), mas isso seria muito menos conveniente de usar, pois todos os lugares que chamam write_byte precisariam raciocinar sobre a segurança
  • Este é um padrão comum para escrever invólucros seguros de código inseguro: transferir o ônus da prova de correção de um grande número de lugares para um número menor de lugares.