Manual Completo de Rust para Programación de Drivers en Linux

Este manual cubre desde los fundamentos de Rust hasta conceptos avanzados para desarrollar drivers de kernel Linux, con ejemplos prácticos y consideraciones de seguridad.

1. Introducción a Rust para Drivers Linux

1.1 ¿Por qué Rust para drivers Linux?

Rust es un lenguaje ideal para la programación de drivers por:

1.2 Configuración del entorno

Instalación en Linux

Instalar Rust y herramientas necesarias:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
sudo apt install build-essential libssl-dev pkg-config

Para desarrollo de drivers del kernel:

sudo apt install linux-headers-$(uname -r)
rustup component add rust-src
cargo install bindgen

2. Fundamentos de Rust para Drivers

2.1 Conceptos clave

Ownership y Borrowing

// Ownership - cada valor tiene un único dueño
let s = String::from("hello"); // s es el dueño
// let s2 = s; // Error: ownership transferido

// Borrowing - préstamo inmutable
fn print_string(s: &String) {
    println!("{}", s);
}
print_string(&s); // Préstamo inmutable

// Borrowing mutable
fn append_world(s: &mut String) {
    s.push_str(", world");
}
append_world(&mut s); // Préstamo mutable

Tipos y seguridad

// Enums y pattern matching
enum DeviceState {
    Enabled,
    Disabled,
    Fault(String),
}

let state = DeviceState::Fault("IO Error".to_string());

match state {
    DeviceState::Enabled => println!("Device is enabled"),
    DeviceState::Disabled => println!("Device is disabled"),
    DeviceState::Fault(reason) => println!("Device fault: {}", reason),
}

// Option y Result - manejo seguro de errores
fn read_register(addr: u32) -> Result<u8, String> {
    // Simulación de lectura de hardware
    if addr < 0x100 {
        Ok(0x42)
    } else {
        Err("Invalid address".to_string())
    }
}

match read_register(0x50) {
    Ok(value) => println!("Register value: {:02x}", value),
    Err(e) => println!("Error: {}", e),
}

2.2 Unsafe Rust

Los drivers necesitan unsafe para interactuar con hardware:

// Ejemplo: acceso directo a memoria
fn read_memory(addr: *const u32) -> u32 {
    unsafe {
        // Solo seguro si addr es válido y alineado
        return *addr;
    }
}

// Ejemplo: llamada a función C
extern "C" {
    fn c_function(arg: i32) -> i32;
}

fn call_c_function(arg: i32) -> i32 {
    unsafe {
        c_function(arg)
    }
}

3. Estructura de un Driver Linux en Rust

3.1 Configuración del proyecto

Crear un proyecto para driver
cargo new --lib linux_driver
cd linux_driver

Editar Cargo.toml:

[package]
name = "linux_driver"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib"]  # Para enlazar con el kernel

[dependencies]
kernel = { git = "https://github.com/Rust-for-Linux/linux" }

3.2 Módulo básico del kernel

// src/lib.rs
#![no_std]  // No usar la stdlib estándar
#![feature(allocator_api, global_asm)]

use kernel::{
    file::File,
    file_operations::FileOperations,
    module_misc_device,
    prelude::*,
    str::CStr,
    user_ptr::UserSlicePtrWriter,
};

// Estructura que representa nuestro dispositivo
struct RustDevice;

// Implementación de operaciones de archivo
#[vtable]
impl FileOperations for RustDevice {
    type Data = Box<Self>;

    fn open(_data: &Arc<Self>, _file: &File) -> Result<Self::Data> {
        Ok(Box::try_new(RustDevice)?)
    }

    fn write(
        _this: &Self,
        _file: &File,
        data: UserSlicePtrWriter,
        _offset: u64,
    ) -> Result<usize> {
        // Aquí procesaríamos datos del userspace
        Ok(data.len())
    }
}

// Registro del módulo
struct RustModule;

impl kernel::Module for RustModule {
    fn init(name: &'static CStr, _module: &'static kernel::ThisModule) -> Result<Self> {
        // Registrar dispositivo misceláneo
        let _reg = module_misc_device(
            name,
            None,
            Arc::try_new(RustDevice)?,
        )?;

        Ok(RustModule)
    }
}

// Macro para declarar el módulo del kernel
module! {
    type: RustModule,
    name: "rust_driver",
    author: "Tu Nombre",
    description: "Un driver simple en Rust",
    license: "GPL",
}

4. Interacción con el Hardware

4.1 Mapeo de memoria

// Ejemplo: mapeo de registros de hardware
use kernel::{
    io_mem::Resource,
    device::Device,
    address_space::AddressSpace,
};

struct HardwareRegisters {
    // Puntero a los registros mapeados
    regs: *mut u32,
}

impl HardwareRegisters {
    fn new(dev: &dyn Device, res: Resource) -> Result<Self> {
        // Mapear la región de memoria física
        let addr_space = AddressSpace::new(dev)?;
        let virt_addr = addr_space.map(res)?;
        
        // Convertir a puntero mutable
        let regs = virt_addr.as_ptr() as *mut u32;
        
        Ok(Self { regs })
    }
    
    unsafe fn read_reg(&self, offset: usize) -> u32 {
        // Leer registro con offset
        *self.regs.add(offset)
    }
    
    unsafe fn write_reg(&mut self, offset: usize, value: u32) {
        // Escribir registro con offset
        *self.regs.add(offset) = value;
    }
}

// Implementar Drop para liberar recursos
impl Drop for HardwareRegisters {
    fn drop(&mut self) {
        // Aquí deberíamos desmapear la memoria
        // En un driver real usaríamos los mecanismos del kernel
    }
}

4.2 Acceso a puertos E/S

// Ejemplo: acceso a puertos x86
use kernel::io_port::Port;

struct IoPortDriver {
    port: Port<u8>,
}

impl IoPortDriver {
    fn new(port_num: u16) -> Result<Self> {
        // Solicitar acceso al puerto
        let port = unsafe { Port::new(port_num) };
        Ok(Self { port })
    }
    
    fn read(&self) -> u8 {
        unsafe { self.port.read() }
    }
    
    fn write(&mut self, value: u8) {
        unsafe { self.port.write(value); }
    }
}

5. Interrupciones y Concurrencia

5.1 Manejo de interrupciones

// Ejemplo: registro de handler de interrupción
use kernel::{
    interrupt,
    device::irq::Irq,
};

struct InterruptHandler {
    irq: Irq,
    shared_data: Arc<Mutex<u32>>,
}

impl interrupt::Handler for InterruptHandler {
    fn handle(&self, _irq: &dyn interrupt::IrqData) -> interrupt::Result {
        // Bloquear los datos compartidos
        let mut data = self.shared_data.lock();
        *data += 1;
        
        // Registrar que manejamos la interrupción
        interrupt::Result::Handled
    }
}

fn register_interrupt(irq_num: u32, dev: &dyn Device) -> Result<InterruptHandler> {
    // Crear datos compartidos
    let shared_data = Arc::new(Mutex::new(0));
    
    // Solicitar IRQ
    let irq = Irq::try_new(irq_num, dev)?;
    
    // Crear handler
    let handler = InterruptHandler {
        irq,
        shared_data,
    };
    
    // Registrar handler
    unsafe {
        interrupt::register_handler(&handler)?;
    }
    
    Ok(handler)
}

5.2 Concurrencia segura

// Ejemplo: uso de Mutex y Arc
use kernel::{
    sync::{Mutex, Arc},
    task::Task,
};

struct DeviceData {
    counter: Mutex<u32>,
    config: Mutex<DeviceConfig>,
}

impl DeviceData {
    fn new() -> Self {
        Self {
            counter: Mutex::new(0),
            config: Mutex::new(DeviceConfig::default()),
        }
    }
    
    fn increment(&self) {
        let mut counter = self.counter.lock();
        *counter += 1;
    }
}

// Ejemplo de uso en múltiples hilos
fn spawn_worker(data: Arc<DeviceData>) -> Result<Task> {
    Task::spawn("worker_thread", move || {
        loop {
            data.increment();
            kernel::schedule_timeout(Duration::from_secs(1));
        }
    })
}

6. Comunicación con Userspace

6.1 Operaciones de archivo

// Implementación completa de FileOperations
#[vtable]
impl FileOperations for RustDevice {
    type Data = Box<Self>;

    fn open(_data: &Arc<Self>, _file: &File) -> Result<Self::Data> {
        Ok(Box::try_new(RustDevice)?)
    }

    fn read(
        _this: &Self,
        _file: &File,
        data: UserSlicePtrWriter,
        _offset: u64,
    ) -> Result<usize> {
        // Datos que queremos enviar a userspace
        let response = "Hello from kernel!";
        
        // Escribir en el buffer de usuario
        data.write_slice(response.as_bytes())?;
        
        Ok(response.len())
    }

    fn write(
        _this: &Self,
        _file: &File,
        data: UserSlicePtrWriter,
        _offset: u64,
    ) -> Result<usize> {
        // Leer datos de userspace
        let mut buffer = Vec::new();
        data.read_all(&mut buffer)?;
        
        // Procesar datos (aquí simplemente los imprimimos)
        if let Ok(s) = core::str::from_utf8(&buffer) {
            println!("Received from userspace: {}", s);
        }
        
        Ok(buffer.len())
    }

    fn ioctl(
        _this: &Self,
        _file: &File,
        cmd: u32,
        arg: usize,
    ) -> Result<usize> {
        // Implementar comandos IOCTL personalizados
        match cmd {
            // Comando para resetear dispositivo
            0x1234 => {
                println!("Received RESET command");
                Ok(0)
            }
            _ => Err(kernel::error::Error::EINVAL),
        }
    }
}

6.2 Sysfs y Debugfs

// Ejemplo: creación de atributos en sysfs
use kernel::{
    sysfs::{Attribute, AttributeGroup, AttributeOps},
    str::CStr,
};

struct CounterAttr;

impl AttributeOps for CounterAttr {
    fn show(
        &self,
        data: kernel::c_types::c_void,
        buf: &mut kernel::sysfs::SysfsBuffer,
    ) -> Result<usize> {
        // Obtener el contador desde los datos
        let counter = unsafe { &mut *(data as *mut u32) };
        
        // Escribir el valor en el buffer
        let s = format!("{}\n", counter);
        buf.write(s.as_bytes())
    }
    
    fn store(
        &self,
        data: kernel::c_types::c_void,
        buf: &[u8],
    ) -> Result<usize> {
        // Parsear el nuevo valor
        let s = core::str::from_utf8(buf).map_err(|_| kernel::error::Error::EINVAL)?;
        let new_val = s.trim().parse().map_err(|_| kernel::error::Error::EINVAL)?;
        
        // Actualizar el contador
        let counter = unsafe { &mut *(data as *mut u32) };
        *counter = new_val;
        
        Ok(buf.len())
    }
}

// Registrar grupo de atributos
fn register_sysfs(dev: &dyn Device, counter: &mut u32) -> Result<AttributeGroup> {
    // Crear atributo
    let attr = Attribute::new(
        unsafe { CStr::from_bytes_with_nul_unchecked("counter\0".as_bytes()) },
        0o644,
        counter as *mut u32 as kernel::c_types::c_void,
        CounterAttr,
    );
    
    // Crear grupo de atributos
    let group = AttributeGroup::new(
        unsafe { CStr::from_bytes_with_nul_unchecked("rust_group\0".as_bytes()) },
        Some(&[attr]),
    );
    
    // Registrar con el dispositivo
    dev.register_attribute_group(&group)?;
    
    Ok(group)
}

7. Manejo de Errores y Depuración

7.1 Manejo de errores en el kernel

// Ejemplo: manejo robusto de errores
fn init_device(dev: &dyn Device) -> Result<Box<DeviceData>> {
    // Paso 1: Mapear registros
    let res = dev.request_resource("registers")?;
    let regs = HardwareRegisters::new(dev, res)?;
    
    // Paso 2: Configurar interrupción
    let irq = dev.request_irq()?;
    let handler = register_interrupt(irq, dev)?;
    
    // Paso 3: Crear estructura principal
    let data = Box::try_new(DeviceData {
        regs,
        handler,
        config: Mutex::new(DeviceConfig::default()),
    })?;
    
    // Paso 4: Inicializar hardware
    match data.init_hardware() {
        Ok(()) => Ok(data),
        Err(e) => {
            // Limpiar recursos en caso de error
            if let Err(cleanup_err) = data.cleanup() {
                println!("Error during cleanup: {:?}", cleanup_err);
            }
            Err(e)
        }
    }
}

7.2 Depuración y logging

// Uso del sistema de logging del kernel
use kernel::pr_info;

fn probe(dev: &dyn Device) -> Result<Box<DeviceData>> {
    pr_info!("Probando dispositivo Rust\n");
    
    let data = init_device(dev)?;
    
    match data.check_status() {
        Ok(status) => {
            pr_info!("Estado del dispositivo: {:?}\n", status);
            Ok(data)
        }
        Err(e) => {
            pr_info!("Error al verificar estado: {:?}\n", e);
            Err(e)
        }
    }
}

// Niveles de log disponibles:
// pr_emerg!()   - Nivel de emergencia (sistema inutilizable)
// pr_alert!()   - Requiere acción inmediata
// pr_crit!()    - Condición crítica
// pr_err!()     - Condición de error
// pr_warn!()    - Advertencia
// pr_notice!()  - Condición normal pero significativa
// pr_info!()    - Mensaje informativo
// pr_debug!()   - Mensaje de depuración (requiere DEBUG)

8. Ejemplo Completo: Driver de Carácter

8.1 Implementación completa

// src/lib.rs
#![no_std]
#![feature(allocator_api, global_asm)]

use kernel::{
    file::File,
    file_operations::{FileOperations, FileOpener},
    module_misc_device,
    prelude::*,
    str::CStr,
    sync::Mutex,
    user_ptr::UserSlicePtrWriter,
};

struct RustDriver {
    counter: Mutex<u32>,
}

impl RustDriver {
    fn new() -> Result<Self> {
        Ok(Self {
            counter: Mutex::new(0),
        })
    }
}

#[vtable]
impl FileOperations for RustDriver {
    type Data = Box<Self>;

    fn open(_data: &Arc<Self>, _file: &File) -> Result<Self::Data> {
        RustDriver::new()
    }

    fn read(
        &self,
        _file: &File,
        data: UserSlicePtrWriter,
        _offset: u64,
    ) -> Result<usize> {
        let mut counter = self.counter.lock();
        *counter += 1;
        
        let response = format!("Counter: {}\n", counter);
        data.write_slice(response.as_bytes())?;
        
        Ok(response.len())
    }

    fn write(
        &self,
        _file: &File,
        data: UserSlicePtrWriter,
        _offset: u64,
    ) -> Result<usize> {
        let mut buffer = Vec::new();
        data.read_all(&mut buffer)?;
        
        if let Ok(s) = core::str::from_utf8(&buffer) {
            if let Ok(num) = s.trim().parse::<u32>() {
                let mut counter = self.counter.lock();
                *counter = num;
            }
        }
        
        Ok(buffer.len())
    }
}

struct RustModule;

impl kernel::Module for RustModule {
    fn init(name: &'static CStr, _module: &'static kernel::ThisModule) -> Result<Self> {
        pr_info!("Inicializando driver Rust\n");
        
        // Registrar dispositivo misceláneo
        let _reg = module_misc_device(
            name,
            None,
            Arc::try_new(RustDriver::new()?)?,
        )?;
        
        Ok(RustModule)
    }
}

impl Drop for RustModule {
    fn drop(&mut self) {
        pr_info!("Descargando driver Rust\n");
    }
}

module! {
    type: RustModule,
    name: "rust_driver",
    author: "Tu Nombre",
    description: "Un driver de carácter simple en Rust",
    license: "GPL",
}

8.2 Compilación y carga

Compilar y cargar el driver

1. Crear un Makefile:

KDIR ?= /lib/modules/$(shell uname -r)/build

obj-m := rust_driver.o

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

2. Compilar el módulo:

make

3. Cargar el módulo:

sudo insmod rust_driver.ko

4. Verificar que se cargó:

dmesg | tail
lsmod | grep rust_driver

5. Probar el driver:

echo "42" | sudo tee /dev/rust_driver
sudo cat /dev/rust_driver

6. Descargar el módulo:

sudo rmmod rust_driver

9. Buenas Prácticas para Drivers en Rust

9.1 Seguridad y robustez

9.2 Performance

10. Recursos Adicionales

10.1 Documentación oficial

10.2 Herramientas

Este manual proporciona una base sólida para desarrollar drivers Linux en Rust. Para proyectos reales, consulta la documentación específica del hardware y las APIs del kernel.