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 deUart::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 chamarwrite_byte
mais tarde porque podemos assumir as precondições necessárias. - Poderíamos ter feito o contrário (tornando
new
seguro, maswrite_byte
inseguro), mas isso seria muito menos conveniente de usar, pois todos os lugares que chamamwrite_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.