Expand description
A no_std, no-alloc shadow register table for embedded systems.
This crate provides efficient shadow register management with dirty tracking, suitable for memory-mapped I/O, peripheral register caching, and state synchronization between application and hardware layers.
§Features
- Zero heap allocation - All storage statically allocated
- Block-based dirty tracking - Efficiently track modifications
- Dual access patterns - Host (application) and Kernel (hardware) views
- Flexible policies - Customizable access control and persistence
- Transactional writes - Optional staging with commit/rollback
§Architecture
The shadow registry uses a one-way dirty tracking model:
┌──────────────────┐ ┌──────────────────────────┐
│ Host (App) │ │ Kernel (HW) │
│ │ │ │
│ write_range() │────────▶│ for_each_dirty_block() │
│ (marks dirty) │ dirty │ (reads dirty) │
│ │ bits │ │
│ │◀────────│ clear_dirty() │
│ │ reset │ write_range() │
│ │ │ (no dirty mark) │
└──────────────────┘ └──────────────────────────┘- Host writes mark blocks as dirty and may trigger persistence
- Kernel reads dirty state to sync changes to hardware
- Kernel writes update the shadow (e.g., after reading from hardware) without marking dirty
- Kernel clears dirty bits after syncing
This design enables efficient one-way synchronization from application to hardware.
§Example
use embedded_shadow::prelude::*;
// Create storage: 1KB total, 64-byte blocks, 16 blocks
let storage = ShadowStorageBuilder::new()
.total_size::<1024>()
.block_size::<64>()
.block_count::<16>()
.default_access()
.no_persist()
.build();
// Load factory defaults at boot (doesn't mark dirty)
storage.load_defaults(|write| {
write(0x000, &[0x01, 0x02, 0x03, 0x04])?;
write(0x100, &[0xAA, 0xBB, 0xCC, 0xDD])?;
Ok(())
}).unwrap();
// host_shadow() and kernel_shadow() return short-lived references,
// typically constructed each iteration and passed via context, e.g.:
// fn update(ctx: &mut HostContext) { ctx.shadow.with_view(|view| { ... }); }
// fn run_once(ctx: &mut KernelContext) { ctx.shadow.with_view_unchecked(|view| { ... }); }
// Host side (main loop): use with_view for critical section safety
storage.host_shadow().with_view(|view| {
view.write_range(0x100, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
let mut buf = [0u8; 4];
view.read_range(0x100, &mut buf).unwrap();
});
// Kernel side (ISR): use with_view_unchecked since ISR already has exclusive access
unsafe {
storage.kernel_shadow().with_view_unchecked(|view| {
view.for_each_dirty_block(|addr, data| {
// Write to hardware registers here
Ok(())
}).unwrap();
view.clear_dirty();
});
}Re-exports§
pub use builder::ShadowStorageBuilder;pub use error::ShadowError;pub use persist::NoPersist;pub use persist::PersistTrigger;pub use policy::AccessPolicy;pub use policy::AllowAllPolicy;pub use policy::NoPersistPolicy;pub use policy::PersistPolicy;pub use shadow::HostShadow;pub use shadow::KernelShadow;pub use staged::PatchStagingBuffer;pub use storage::ShadowStorage;pub use storage::WriteFn;pub use types::StagingBuffer;pub use view::HostView;pub use view::HostViewStaged;pub use view::KernelView;