Introduce flash traits, client and service#236
Conversation
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>
| ) -> Result<usize, ErrorCode> { | ||
| if false { | ||
| //let _n = N; | ||
| //syscall::channel_transact_iovec(self.0, request, response, deadline) |
There was a problem hiding this comment.
nit: Delete commented out code.
|
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. |
|
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! |
|
|
||
| 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)?; |
There was a problem hiding this comment.
| 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)?; |
|
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. |
| use core::{cmp::min, num::NonZero}; | ||
| pub use hal_flash_driver::FlashAddress; | ||
| use hal_flash_driver::FlashDriver; | ||
| use util_error::ErrorCode; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
| /// * `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>( |
There was a problem hiding this comment.
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> {
|
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 — The fix: make the struct the contractExtract a shared // 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. |
|
The core The opcode space, the server dispatch, and the buffer constants are all built around exactly the four operations Consider 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 |
Signed-off-by: Chris Frantz <cfrantz@google.com>
Introduce flash traits, client and server.
//hal/blocking/flashcontains trait abstractions for accessing flash.//services/flashcontains the flash IPC client and server implementations and user docs in a README.md//target/earlgrey/drivers/eflash_driver.rsis the implementation for earlgrey's internal flash block.I've changed
FlashAddressto be a 2 xu32type that includes adevice_idand anoffset. The type implements arithmetic ops on the offset. Thedevice_idcan 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). Thedevice_idneeds 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.