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.
Rust es un lenguaje ideal para la programación de drivers por:
Instalar Rust y herramientas necesarias:
Para desarrollo de drivers del kernel:
// 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
// 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),
}
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)
}
}
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" }
// 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",
}
// 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
}
}
// 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); }
}
}
// 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)
}
// 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));
}
})
}
// 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),
}
}
}
// 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)
}
// 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)
}
}
}
// 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)
// 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",
}
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:
3. Cargar el módulo:
4. Verificar que se cargó:
5. Probar el driver:
6. Descargar el módulo:
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.