Vamos a escribir un controlador de UART
La máquina "virt" de QEMU tiene una UART [PL011]https://developer.arm.com/documentation/ddi0183/g), así que vamos a escribir un controlador para ella.
const FLAG_REGISTER_OFFSET: usize = 0x18; const FR_BUSY: u8 = 1 << 3; const FR_TXFF: u8 = 1 << 5; /// Controlador mínimo para un UART PL011. #[derive(Debug)] pub struct Uart { base_address: *mut u8, } impl Uart { /// Construye una instancia nueva del controlador de UART para un dispositivo PL011 en la /// dirección base proporcionada. /// /// # Seguridad /// /// La dirección base debe apuntar a los 8 registros de control MMIO de un /// dispositivo PL011, que debe asignarse al espacio de direcciones del proceso /// como memoria del dispositivo y no tener ningún otro alias. pub unsafe fn new(base_address: *mut u8) -> Self { Self { base_address } } /// Escribe un solo byte en el UART. pub fn write_byte(&self, byte: u8) { // Espera hasta que haya espacio en el búfer de TX. while self.read_flag_register() & FR_TXFF != 0 {} // Es seguro porque sabemos que la dirección base apunta a los registros // de control de un dispositivo PL011 que está asignado correctamente. unsafe { // Escribe en el búfer de TX. self.base_address.write_volatile(byte); } // Espera hasta que el UART esté libre. while self.read_flag_register() & FR_BUSY != 0 {} } fn read_flag_register(&self) -> u8 { // Es seguro porque sabemos que la dirección base apunta a los registros // de control de un dispositivo PL011 que está asignado correctamente. unsafe { self.base_address.add(FLAG_REGISTER_OFFSET).read_volatile() } } }
- Ten en cuenta que
Uart::new
no es seguro, mientras que los otros métodos sí lo son. Esto se debe a que mientras que el llamador deUart::new
asegure que se cumplan sus requisitos de seguridad (es decir, que solo haya una instancia del controlador para una UART determinada y que nada más asigne alias a su espacio de direcciones), siempre es más seguro llamar awrite_byte
más adelante, ya que podemos asumir\ las condiciones previas necesarias. - Podríamos haberlo hecho al revés (haciendo que
new
fuese seguro ywrite_byte
no seguro), pero\sería mucho menos cómodo de usar, ya que cada lugar que llamase awrite_byte
tendría que pensar en la seguridad - Este es un patrón común para escribir envoltorios seguros de código inseguro: mover la carga de la prueba de seguridad de un gran número de lugares a otro más pequeño.