ddevmem
Safe and ergonomic Rust library for accessing physical memory via /dev/mem,
with volatile read/write semantics suitable for memory-mapped I/O (MMIO).
Features
| Feature | Default | Description |
|---|---|---|
device |
✓ | Real /dev/mem backend via memmap2. |
emulator |
Heap-backed Vec<u8> for testing without hardware. |
|
register-map |
✓ | Declarative register_map! macro with optional bitfields and typed accessors. |
web |
Web UI for viewing/editing registers via axum (optional auth). |
Note: enable exactly one of
deviceoremulator. When both are enabled simultaneously, theemulatorbackend takes precedence.
Installation
[]
= "0.4.1"
Or with specific features:
[]
= { = "0.4.1", = false, = ["emulator", "register-map"] }
With the web UI:
[]
= { = "0.4.1", = ["web"] }
= { = "1", = ["full"] }
Quick start
Raw DevMem access
use DevMem;
let devmem = unsafe ;
// Volatile read
let value: u32 = devmem.read.unwrap;
// Volatile write
devmem.write.unwrap;
// Read-modify-write
devmem..unwrap;
// Bulk operations
let mut buf = ;
devmem.read_slice;
devmem.write_slice;
Register map with bitfields
use Arc;
use ;
register_map!
let devmem = unsafe ;
let mut regs = unsafe ;
// Full-register access
let status = regs.status;
regs.set_command;
regs.modify_control;
// Bitfield access
let enabled: u32 = regs.control_enable; // single-bit → value from bit 0
let mode: u32 = regs.control_mode; // bits 1..=3
regs.set_control_mode; // read-modify-write only the mode bits
Typed bitfields
Bitfields can carry an as <type> suffix to change the getter/setter types.
Three forms are supported: as bool, as <integer>, and as enum.
use Arc;
use ;
register_map!
let devmem = unsafe ;
let mut timer = unsafe ;
timer.set_cr_enable; // bool
timer.set_cr_psc; // u8
timer.set_cr_mode; // enum
assert_eq!;
assert_eq!;
assert_eq!;
Documented register map
Doc comments (/// ...) can be placed on the struct, on individual registers
(after =>), and on individual bitfields. Comments are forwarded to generated
Rust doc and displayed in the web UI when the web feature is enabled.
use Arc;
use ;
register_map!
let devmem = unsafe ;
let mut spi = unsafe ;
// Wait until TX FIFO is empty, then send a byte
while spi.sr_txe == 0
spi.set_dr;
// Configure: CPOL=1, CPHA=0, chip-select 2, enable
spi.set_cr_cpol;
spi.set_cr_cpha;
spi.set_cr_cs;
spi.set_cr_enable;
register_map! syntax reference
register_map! {
/// Optional struct-level doc comment.
$vis unsafe map $Name ($bus_width) {
$offset =>
/// Optional register doc comment.
$kind $name: $type {
/// Optional bitfield doc comment.
field: bits,
...
},
...
}
}
| Element | Description |
|---|---|
$vis |
Visibility (pub, pub(crate), etc.). |
$Name |
Name of the generated struct. |
($bus_width) |
Optional bus type (e.g. u32). All accesses use this width. |
$offset |
Byte offset of the register (0x00, 0x04, …). |
$kind |
rw (read-write), ro (read-only), or wo (write-only). |
$name |
Register name — drives the generated method names. |
$type |
Register type (u8, u16, u32, u64). |
Bitfield syntax:
field_name: bit // single bit
field_name: lo..=hi // inclusive range (recommended)
field_name: lo..hi // exclusive upper bound (Rust convention)
A bitfield can carry an as <type> suffix to produce typed getters/setters:
field: bit as bool // getter → bool, setter accepts bool
field: lo..=hi as u8 // getter → u8, setter accepts u8 (any int type)
field: lo..=hi as enum Name { // getter → Name, setter accepts Name
Variant = value, // #[derive(Debug, Clone, Copy, PartialEq, Eq)]
..., // with from_raw() / to_raw() methods
}
Bits not covered by any field declaration are left untouched during read-modify-write — there is no need to declare reserved gaps.
Register arrays. A register declared as [T; N] becomes a contiguous
run of N identical registers at offset, offset + size_of::<bus>(), ….
Accessors take an extra idx: usize parameter, and any bitfields on the
array entry get the same treatment:
0x10 =>
rw fifo: [u32; 8], // -> fifo(i), set_fifo(i, v), modify_fifo(i, f), fifo_len()
0x40 =>
rw chan: [u32; 4] { // -> chan(i), set_chan(i, v), chan_len()
enable: 0 as bool, // -> chan_enable(i), set_chan_enable(i, b)
prio: 1..=3 as u8 // -> chan_prio(i), set_chan_prio(i, n)
}
A complete example using the array API:
use Arc;
use ;
register_map!
let devmem = unsafe ;
let mut dma = unsafe ;
// Whole-register access by index.
for i in 0..dma.fifo_len
let head: u32 = dma.fifo;
// Bitfield access on each array element.
for i in 0..dma.chan_len
assert!;
assert_eq!;
Generated methods per register:
| Kind | Method | Description |
|---|---|---|
| all | name_offset() |
Byte offset within DevMem. |
| all | name_address() |
Physical address (base + offset). |
rw / ro |
name() |
Volatile read. |
rw / wo |
set_name(value) |
Volatile write. |
rw |
modify_name(f) |
Volatile read-modify-write. |
Generated methods per bitfield:
| Kind | Method | Description |
|---|---|---|
rw / ro |
reg_field() |
Extract field bits. |
rw / wo |
set_reg_field(value) |
Read-modify-write only the field bits. |
When a type suffix is present the return / argument type changes accordingly:
| Suffix | Getter returns | Setter accepts |
|---|---|---|
| (none) | register type | register type |
as bool |
bool |
bool |
as u8 (etc.) |
u8 |
u8 |
as enum Name |
Name |
Name |
Web UI (web feature)
The web feature adds a browser-based interface for viewing and editing
registers at runtime. It is powered by axum and requires tokio.
When web is enabled, register_map! auto-implements the
RegisterMapInfo trait, which exposes register metadata (names, offsets,
access types, bitfield descriptions, doc strings) and raw read/write access.
use Arc;
use Mutex;
use ;
register_map!
async
With HTTP Basic authentication:
Security note. HTTP Basic transmits credentials
base64-encoded, not encrypted — always run the server behind TLS (e.g.nginx,caddy,axum-server+rustls) for anything beyond a trusted local network. Compare secrets in constant time withct_eqinstead of==to avoid leaking the password through response timing, and use bitwise&(not&&) so both comparisons run unconditionally.
with_auth takes an async callback Fn(String, String) -> Future<Output = bool>,
so the closure body must be an async move { ... } block. This lets the
check perform I/O (e.g. database lookup) without blocking the runtime.
use Arc;
use Mutex;
use ;
use ;
register_map!
async
The web UI provides:
- Live register values with auto-refresh
- Per-register and per-bitfield read/write controls
- Documentation strings from
/// ...comments - JSON API for integration with external tools
- Nestable router — mount the web UI at any prefix on a larger server
The returned Router has no root path baked in.
Use axum::Router::nest() to mount it wherever you need:
use Arc;
use Mutex;
use ;
use WebUi;
register_map!
async
API endpoints (relative to mount point):
| Method | Path | Body | Response |
|---|---|---|---|
| GET | / |
— | HTML single-page app |
| GET | /api/maps |
— | { title?: string, maps: [{ slug, name }, ...] } |
| GET | /api/{slug}/info |
— | { name, bus_width, base_address, registers: [...] } |
| POST | /api/{slug}/read |
{ "offset": 0 } |
{ "value": 12345 } |
| POST | /api/{slug}/write |
{ "offset": 0, "value": 42 } |
200 OK |
Custom page title:
The heading shown in the browser tab and the UI-Shell header defaults to
ddevmem — Register Maps (or the map's own name in single-map mode).
Override it with WebUi::with_title:
use Arc;
use Mutex;
use ;
use WebUi;
register_map!
async
Hosting multiple register maps on one page:
The same WebUi builder accepts several .add(slug, regs) calls.
All maps are displayed together on a single page.
use Arc;
use Mutex;
use ;
use WebUi;
register_map!
register_map!
async
Using the emulator for testing
The emulator feature replaces /dev/mem with a zero-initialized heap buffer,
allowing you to test register map logic without hardware:
// Cargo.toml:
// ddevmem = { version = "0.4.1", default-features = false, features = ["emulator", "register-map"] }
use Arc;
use ;
register_map!
// DevMem backed by Vec<u8> — no /dev/mem needed
let devmem = unsafe ;
let mut regs = unsafe ;
regs.set_data;
assert_eq!;
regs.set_ctrl_run;
assert_eq!;
assert_eq!; // other bits untouched
Migration from 0.3
ddevmem 0.4 is a breaking release. Key changes:
| 0.3 | 0.4 |
|---|---|
*reg.get() / *reg.get_mut() = v |
reg.read() / reg.write(v) |
reg.get_mut() dereference |
reg.modify(|v| …) |
black_box-based access |
read_volatile / write_volatile |
| No bitfield support | register_map! with bitfields |
| No bus-width control | register_map!(… (u32) { … }) |
| No doc comment support | /// … on registers & bitfields |
| No typed bitfield support | as bool / as u8 / as enum |
| No register-array support | rw fifo: [u32; 8] (indexed API) |
| No web UI | web feature with axum server |
Examples
The crate ships several runnable examples under examples/.
Each one enables the emulator feature, so they work without /dev/mem.
| File | Topic |
|---|---|
default_bus.rs |
Minimal register map with rw / ro / wo access. |
bitfield.rs |
Plain numeric bitfields, doc comments. |
typed_bitfield.rs |
Typed bitfields: as bool, as u8, as enum. |
array_regs.rs |
Register arrays ([T; N]) with per-element bitfields. |
web_server.rs |
Single map served via the web feature. |
web_auth.rs |
Web UI behind HTTP Basic auth (constant-time ct_eq). |
web_same_map.rs |
Two instances of the same map at different base addresses. |
web_showcase.rs |
Full-feature showcase: 4 peripherals, every bitfield kind, arrays. |
Run any of them with:
License
ddevmem is distributed under the terms of the MIT license. See LICENSE-MIT for details.