Skip to content

Introduce flash traits, client and service#236

Draft
cfrantz wants to merge 12 commits into
OpenPRoT:mainfrom
cfrantz:flash_impl
Draft

Introduce flash traits, client and service#236
cfrantz wants to merge 12 commits into
OpenPRoT:mainfrom
cfrantz:flash_impl

Conversation

@cfrantz

@cfrantz cfrantz commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Introduce flash traits, client and server.

  • //hal/blocking/flash contains trait abstractions for accessing flash.
  • //services/flash contains the flash IPC client and server implementations and user docs in a README.md
  • //target/earlgrey/drivers/eflash_driver.rs is the implementation for earlgrey's internal flash block.

I've changed FlashAddress to be a 2 x u32 type that includes a device_id and an offset. The type implements arithmetic ops on the offset. The device_id can be used to encode both @rusty1968 's idea of a "routing key" to represent a specific device or Earlgrey's notion of separate DATA and INFO address spaces (or both). The device_id needs a bit more development as the current flash service doesn't look at it, but we might want it to examine it.

I've adjusted my (actually kor's) original traits to adopt @rusty1968 's notion of flash geometry.

There are several preparatory commits before the HALs and implementations. We can also discuss the content of those commits as well.

cfrantz added 11 commits May 21, 2026 15:04
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
Signed-off-by: Chris Frantz <cfrantz@google.com>
@cfrantz cfrantz requested review from moidx and rusty1968 May 21, 2026 22:19
Comment thread util/ipc/lib.rs
) -> Result<usize, ErrorCode> {
if false {
//let _n = N;
//syscall::channel_transact_iovec(self.0, request, response, deadline)

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.

nit: Delete commented out code.

@rusty1968

Copy link
Copy Markdown
Collaborator

There are too many pieces introduced in a single PR. Maxiimum benefit can be achieved by introducing flash traits, error crate and other impactful pieces in smaller chunks.

@cfrantz

cfrantz commented May 21, 2026

Copy link
Copy Markdown
Collaborator Author

I did not explain myself well in the description: this is not meant to be submittable as-is, or even reviewed in a serious line-by-line way. This is a working example of the flash traits, client, server and low-level drivers on earlgrey. It's built in a way that I think should be compatible with other system and meets some of the requirements from our last discussion.

This is meant to be able to look at the big picture of how the flash traits fit together with the other pieces and to have a discussion.

I will, of course, send smaller PRs when we've worked out the high-level details.

@rusty1968

Copy link
Copy Markdown
Collaborator

I did not explain myself well in the description: this is not meant to be submittable as-is, or even reviewed in a serious line-by-line way. This is a working example of the flash traits, client, server and low-level drivers on earlgrey. It's built in a way that I think should be compatible with other system and meets some of the requirements from our last discussion.

This is meant to be able to look at the big picture of how the flash traits fit together with the other pieces and to have a discussion.

I will, of course, send smaller PRs when we've worked out the high-level details.

Excellent!

Comment thread services/flash/server.rs

fn handle_read<'a>(&mut self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> {
let addr =
FlashAddress::read_from_bytes(&data[0..8]).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?;

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.

Suggested change
FlashAddress::read_from_bytes(&data[0..8]).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?;
FlashAddress::read_from_bytes(
data.get(..8).ok_or(error::IPC_ERROR_BAD_REQ_LEN)?
).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?;

@rusty1968

Copy link
Copy Markdown
Collaborator

One of the aspects that Hubris did well and we kept it when porting our stacks to PW was to keep the request/response wire protocol format (encoded using IDL or done manually) from the IPC transport mechanism itself. We would like to see the same in the flash stack. This allowed host-based tests to be performed.

Comment thread hal/blocking/flash/flash.rs Outdated
use core::{cmp::min, num::NonZero};
pub use hal_flash_driver::FlashAddress;
use hal_flash_driver::FlashDriver;
use util_error::ErrorCode;

@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.

hal/blockingshould be kept as a pure set of seams — no Pigweed, no OS imports. Pigweed is being brought in by the way ofutil_error which depends on@pigweed//pw_status/rust:pw_status.

/// 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.

Comment thread util/ipc/lib.rs
/// * `request`: A slice of buffers containing the request data.
/// * `response`: A slice of mutable buffers to receive the response data.
/// * `deadline`: The time by which the transaction must complete.
pub fn transaction<const N: usize>(

@rusty1968 rusty1968 May 28, 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.

replace transaction < const N: usize > with caller-supplied buffer

/// * `buffer`: Caller-supplied scratch buffer; must be large enough to hold
///   the gathered request **and** the full response.
pub fn transaction(
    &self,
    request: &[&[u8]],
    response: &mut [&mut [u8]],
    buffer: &mut [u8],
    deadline: Instant,
) -> Result<usize, ErrorCode> {

@rusty1968

Copy link
Copy Markdown
Collaborator

Right now the flash request/response protocol exists only as convention — the client writes bytes at offsets it chose, and the server reads bytes at offsets it also chose, and they happen to agree. There is no single place that says "a flash IPC message is a 12-byte header followed by an operation-specific payload." If they ever diverge, the compiler won't notice.

// services/flash/client.rs
self.ipc.transaction::<16>(
    &[
        IPC_OP_FLASH_ERASE.as_bytes(),   // bytes [0..4]   opcode
        start_addr.as_bytes(),           // bytes [4..12]  FlashAddress (8 bytes)
        size_val.as_bytes(),             // bytes [12..16] u32
    ],
    &mut [result.as_mut_bytes()],
    Instant::MAX,
)?;
// services/flash/server.rs
// `data` arrives here with the opcode already stripped — offset accounting
// is split across handle_one() and handle_erase().
fn handle_erase<'a>(&mut self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> {
    let (addr, data) = FlashAddress::read_from_prefix(data)   // bytes [0..8]
        .map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?;
    let (size, _)   = u32::read_from_prefix(data)             // bytes [8..12]
        .map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?;
    ...
}

The contract — FlashAddress occupies bytes 4–12, size occupies bytes 12–16
— lives entirely in the protocol designer's head, duplicated across two files.

The fix: make the struct the contract

Extract a shared FlashEraseRequest in a crate both sides depend on:

// services/flash/wire/lib.rs
#[derive(FromBytes, Immutable, IntoBytes, KnownLayout)]
#[repr(C)]
pub struct FlashEraseRequest {
    pub addr: WireAddress,   // 8 bytes
    pub size: u32,           // 4 bytes
}

The client encodes by value:

let req = FlashEraseRequest { addr: addr.into(), size: size_val };
// req.as_bytes() produces exactly the same 12 bytes — but derived from
// the struct layout, not from manually stacked slices.

The server decodes symmetrically:

let (req, _) = FlashEraseRequest::read_from_prefix(data)
    .map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?;
// req.addr and req.size are now typed fields, not offset arithmetic.

@rusty1968

rusty1968 commented May 28, 2026

Copy link
Copy Markdown
Collaborator

The core Flash trait definition is well-designed — a clean, minimal interface with the right operations at the right level of abstraction.The problem is not the trait. The problem is that the wire protocol has no room to grow beyond it.

The opcode space, the server dispatch, and the buffer constants are all built around exactly the four operations Flash defines today. Adding a new operation, e.g., exposing Flash + FlashExOp over a single IPC handle, or versioning the protocol independently of the trait all require changes that the current design has no natural place for.

Consider FlashExOp — a supertrait that adds vendor-specific extended operations on top of Flash:

pub trait FlashExOp: Flash {
    fn ex_op(&mut self, code: u16, input: &[u8], output: &mut [u8]) -> Result<(), ErrorCode>;
}

Generic code that needs extended operations simply bounds on F: Flash + FlashExOp. The trait design is clean. The problem is what happens when you try to expose FlashExOp over IPC.

Signed-off-by: Chris Frantz <cfrantz@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants