device-driver

A toolkit to write better
device drivers, faster

Define your driver using DDSL, a simple custom specification language.

Then generate Rust code using the compiler or proc macro.


Intro

Define your registers, commands & buffers using the device-driver specification language. These form the user API of the driver.

Create fieldsets composed of many fields. Make them impossible to use incorrectly with access control, type conversions & enum-indexed repeats.

Use custom enums to represent closed sets. Or define an extern when you want to use more complex types implemented in your own code.

Document everything using the /// doc comments.

device Foo {
    register-address-type: u8,

    /// Doc comments get reflected in the output code!
    register Bar {
        address: 0,
        
        fields: fieldset BarFields {
            size-bytes: 1,

            field xena 1:0 -> u8 as enum Xena {
                A: _,
                B: _,
                C: default 3,
            },
            field quux 7:2 -> u8,
        }
    }
}

error: invalid fieldset reference
   ╭▸ input.ddsl:21:17
   
 5      register Bar {
                 ─── reference points to non-fieldset object
   
21          fields: Bar
   ╰╴                ━━━ no fieldset found with this name

Errors

Everything is checked by the compiler for correctness. If not a helpful error message will explain what's wrong.


Rust API

Use a familiar svd2rust/chiptool inspired API to interact with the driver. Everything can be called both blocking and async.

Read, write and modify registers, either per register or in bulk.

Commands can be dispatched with optional input and output data.

Buffers are written and read with byte slices.

Fields can have conversion types so values are always correct.

The driver is driven entirely by the functions you call, no runtime requirements. It won't get in your way.

// Create device instance
let mut device = MyDevice::new(DeviceInterface::new());

// Write a register
device.foo().write(|reg| reg.set_value_1(MyEnum::B))?;

// Anything can be used async
device.foo().read_async().await?;

// Dispatch commands
device.simple_command().dispatch()?;

// Operate on registers in bulk
let (foo, bar) = device
    .multi_read()
    .with(|d| d.foo().plan())
    .with(|d| d.bar().plan())
    .execute()?;

// Write and read buffers
device.wo_buf().write(&[0, 1, 2, 3])?;
let len = device.ro_buf().read(&mut buffer)?;

Curious yet?

Continue by reading the tutorials & spec in the book and playing around in the playground.

For issues, PRs & discussions, head over to github.

Copyright © 2020-2026 Dion Dokter & device-driver contributors.