A dynamic driver management framework for embedded systems written in Rust.
Hardware abstraction interface crates with Any
for dynamic dispatch:
rdif-base
: Core driver traits and error typesrdif-intc
: Interrupt controller interfacerdif-clk
: Clock management interfacerdif-serial
: Serial communication interfacerdif-timer
: Timer/counter interfacerdif-block
: Block device interfacerdif-power
: Power management interfacerdif-systick
: System tick interfacerdif-net
: Network interface
Procedural macros to simplify driver registration and module generation.
The main driver container responsible for:
- Driver registration and discovery
- Device probing and initialization
- Device lifecycle management
- Type-safe device access
Devices are managed through a long-term borrowing model using Device<T>
wrappers. Unlike Mutex<T>
, when a task borrows a device, it gains exclusive ownership. Other tasks attempting to borrow will receive an error with the owner's task ID, enabling forced task termination if needed.
Key features:
- Lock-free operations once ownership is acquired
- Weak pointer support for interrupt handlers
- Cloning
Device<T>
creates weak references for fast indexing
Drivers can be registered from different crates using the module_driver!
macro:
use rdrive::{
module_driver,
register::{ProbeKind, ProbeLevel, ProbePriority, FdtInfo},
probe::OnProbeError,
PlatformDevice,
DriverGeneric,
};
struct GicV3Driver {
// Driver implementation
}
impl DriverGeneric for GicV3Driver {
fn open(&mut self) -> Result<(), rdrive::KError> {
// Initialize hardware
Ok(())
}
fn close(&mut self) -> Result<(), rdrive::KError> {
// Cleanup hardware
Ok(())
}
}
impl rdrive::driver::intc::Interface for GicV3Driver {
// Implement interrupt controller interface
fn enable_irq(&mut self, irq: rdrive::IrqId) -> Result<(), rdrive::driver::intc::IntcError> {
// Enable interrupt implementation
Ok(())
}
// ... other required methods
}
fn probe_gicv3(fdt: FdtInfo<'_>, dev: PlatformDevice) -> Result<(), OnProbeError> {
let node = fdt.node;
let mut reg = node.reg().ok_or("No reg property")?;
let gicd_reg = reg.next().ok_or("Missing GICD register")?;
let gicr_reg = reg.next().ok_or("Missing GICR register")?;
let driver = GicV3Driver::new(
gicd_reg.address as usize,
gicr_reg.address as usize,
);
dev.register(driver);
Ok(())
}
module_driver! {
name: "GICv3",
level: ProbeLevel::PreKernel,
priority: ProbePriority::INTC,
probe_kinds: &[ProbeKind::Fdt {
compatibles: &["arm,gic-v3"],
on_probe: probe_gicv3,
}],
}
Add the following section to your linker script:
.driver.register : ALIGN(4K) {
_sdriver = .;
*(.driver.register .driver.register.*)
_edriver = .;
. = ALIGN(4K);
}
use rdrive::register::DriverRegisterSlice;
fn driver_registers() -> &'static [u8] {
unsafe extern "C" {
fn _sdriver();
fn _edriver();
}
unsafe {
core::slice::from_raw_parts(
_sdriver as *const u8,
_edriver as usize - _sdriver as usize
)
}
}
fn get_driver_registers() -> DriverRegisterSlice {
DriverRegisterSlice::from_raw(driver_registers())
}
use rdrive::{Platform, probe_pre_kernel, probe_all};
use core::ptr::NonNull;
fn init_drivers() {
// Initialize with device tree
let fdt_addr = /* device tree address */;
let platform = Platform::Fdt {
addr: NonNull::new(fdt_addr).unwrap(),
};
// Initialize the driver framework
rdrive::init(platform).unwrap();
// Register all discovered drivers
rdrive::register_append(get_driver_registers().as_slice());
// Probe critical drivers first (interrupt controllers, etc.)
rdrive::probe_pre_kernel().unwrap();
// Initialize interrupt system
// irq::init_main_cpu();
// Probe remaining drivers
rdrive::probe_all(false).unwrap(); // false = don't stop on failures
}
use rdrive::{get_list, get_one, driver::Intc};
fn use_devices() {
// Get all interrupt controllers
let intc_list = get_list::<Intc>();
for intc in intc_list {
println!("Found INTC: {:?}", intc.descriptor());
}
// Get the first available interrupt controller
if let Some(intc) = get_one::<Intc>() {
// Use the device - this gives exclusive access
match intc.try_lock() {
Ok(mut locked_intc) => {
// Safe to use locked_intc without additional synchronization
locked_intc.enable_irq(42.into()).unwrap();
}
Err(owner_id) => {
println!("Device busy, owned by task: {:?}", owner_id);
}
}
}
}
- Device Tree (FDT): Automatic device discovery from device tree
- Static Configuration: Manual device registration (planned)
See the examples/
directory for complete usage examples:
examples/enumerate/
: Basic driver enumeration and device tree parsing
For complete integration examples, see: