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 {}

        // Seguro 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 {
        // Seguro 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.