Skip to content
42 changes: 42 additions & 0 deletions hal/blocking/flash/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")

rust_library(
name = "driver",
srcs = [
"driver.rs",
],
crate_name = "hal_flash_driver",
edition = "2024",
visibility = ["//visibility:public"],
deps = [
"//util/types",
"@rust_crates//:zerocopy",
],
)

rust_library(
name = "flash",
srcs = [
"flash.rs",
],
crate_name = "hal_flash",
edition = "2024",
visibility = ["//visibility:public"],
deps = [
":driver",
"//util/io",
"//util/types",
],
)

rust_test(
name = "flash_test",
crate = ":flash",
rustc_flags = [
"-C",
"debug-assertions",
],
)
139 changes: 139 additions & 0 deletions hal/blocking/flash/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//! Low-level flash driver interface.

#![no_std]

use core::num::NonZero;

use util_types::PowerOf2Usize;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

/// Low-level flash driver interface.
///
/// This trait defines the interface for interacting with flash hardware at a low level.
/// It supports asynchronous-style operations (start/is_busy/complete) but can also be
/// implemented for synchronous drivers.
pub trait FlashDriver {
type Error;
/// A bitmap of supported erase block sizes.
///
/// Each bit i represents a supported erase block size of 2^i bytes.
const ERASABLE_SIZES_BITMAP: u32;
/// The maximum size of a single program operation.
const PROGRAM_WINDOW_SIZE: usize;
/// The maximum size of a single read operation.
const MAX_READ_SIZE: usize;
/// The alignment required for read operations.
const READ_ALIGNMENT: usize;
/// The alignment required for program operations.
const PROGRAM_ALIGNMENT: usize;

/// Returns the total size of the flash in bytes.
fn size(&self) -> NonZero<usize>;

/// Reads data from flash.
///
/// # Arguments
/// * `start_addr`: The address to start reading from.
/// * `buf`: The buffer to read data into.
fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), Self::Error>;

/// Starts an erase operation.
///
/// # Arguments
/// * `start_addr`: The start address of the block to erase.
/// * `size`: The size of the block to erase.
fn start_erase(
&mut self,
start_addr: FlashAddress,
size: PowerOf2Usize,
) -> Result<(), Self::Error>;

/// Starts a program operation.
///
/// # Arguments
/// * `start_address`: The address to start programming at.
/// * `data`: The data to program.
fn start_program(
&mut self,
start_address: FlashAddress,
data: &[u8],
) -> Result<(), Self::Error>;

/// Returns whether the driver is currently busy with an operation.
fn is_busy(&mut self) -> bool;

/// Completes a pending operation and returns the result.
fn complete_op(&mut self) -> Result<(), Self::Error>;
}

/// Represents an address in flash memory.
///
/// A flash address consists of a device identifier and an offset within that
/// device's address space.
#[derive(Default, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable, FromBytes, KnownLayout)]
#[repr(C)]
pub struct FlashAddress {

@rusty1968 rusty1968 May 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The routing key concept should leverage the PW capability model

The flash service protocol carries a `device_id field in every address, implying
multi-device support.

It follows that routing key is client-supplied — it lives inside the message payload.
A client can set device_id to any value. Nothing in the current design prevents a task
that should only access one flash device from addressing another. Enforcement would have to be a software check inside the server, applied after the message is already received.

Nothing stops one task from accessing another task's flash device. The kernel's capability model is bypassed at the device-selection boundary.
Device-level access control is delegated to software rather than enforced by the kernel.

device_id: u32,
offset: u32,
}

impl core::fmt::Display for FlashAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}:0x{:08x}", self.device_id, self.offset)
}
}

impl core::fmt::Debug for FlashAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}

impl FlashAddress {
/// Creates a new `FlashAddress`.
pub const fn new(device_id: u32, offset: u32) -> Self {
Self { device_id, offset }
}

/// Returns the device identifier.
pub fn device_id(&self) -> u32 {
self.device_id
}

/// Returns the offset within the device's address space.
pub fn offset(&self) -> u32 {
self.offset
}
}

impl core::ops::Add<usize> for FlashAddress {
type Output = Self;
fn add(self, other: usize) -> Self {
Self {
device_id: self.device_id,
offset: self.offset + other as u32,
}
}
}

impl core::ops::AddAssign<usize> for FlashAddress {
fn add_assign(&mut self, other: usize) {
self.offset += other as u32;
}
}

impl core::ops::BitAnd<usize> for FlashAddress {
type Output = Self;
fn bitand(self, other: usize) -> Self {
Self {
device_id: self.device_id,
offset: self.offset & other as u32,
}
}
}

impl core::ops::BitAndAssign<usize> for FlashAddress {
fn bitand_assign(&mut self, other: usize) {
self.offset &= other as u32;
}
}
Loading
Loading