Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d2ea598
chore: Roll pigweed
davexroth Aug 24, 2026
d11fdb4
orchestrator: Add the LockdownLatch terminal capability trait
chrysh Aug 19, 2026
7d2b1df
orchestrator: Test the at-rest guarantee end to end
chrysh Aug 17, 2026
03d3130
Rename VerifierBroken to VerifierError in tests
chrysh Aug 21, 2026
aad2549
util: IPC abstraction
cfrantz Jun 6, 2026
680f361
services: flash client/server
cfrantz Jun 7, 2026
bd319b5
util/error: add ErrorCode::check_status
rusty1968 Aug 19, 2026
9f04f58
target/ast10x0: allow intra-page program starts in SpiNorFlash
rusty1968 Aug 19, 2026
45c7494
util/error: add AST10x0 flash error module
rusty1968 Aug 19, 2026
b5e2876
target/ast10x0: add FMC flash service backend
rusty1968 Aug 19, 2026
7466760
target/ast10x0: add flash service server image scaffolding
rusty1968 Aug 19, 2026
72b6903
target/ast10x0: add flash service client test and system image
rusty1968 Aug 19, 2026
018d9c4
target/ast10x0: move FMC pinmux from flash backend to kernel target init
rusty1968 Aug 19, 2026
05eea90
ast10x0: run flash service test under QEMU via fmc-model=w25q64 + see…
rusty1968 Aug 20, 2026
6434e50
ast10x0: make flash test non-destructive with sector backup/restore (…
rusty1968 Aug 20, 2026
c9ab6ed
util/sfdp: add JESD216 SFDP parser with datasheet-derived known-good …
JesseMelon Aug 25, 2026
c6af36d
hal/flash: add runtime geometry accessors to FlashDriver
JesseMelon Sep 8, 2026
9d680b1
util/sfdp: harden parser; add ConstParamTy and public decode_geometry
JesseMelon Sep 1, 2026
bb0bac9
ast10x0/smc: config passed at init; derive per-CS geometry from SFDP;…
JesseMelon Sep 1, 2026
03617b0
ast10x0/smc: port tests to SFDP-derived config; add sfdp discovery test
JesseMelon Sep 1, 2026
38da579
ast10x0: flash service backend and QEMU FMC model on discovered geometry
JesseMelon Sep 8, 2026
7d4ded2
ast10x0/tests/flash: flash service test with UART sentinel completion
JesseMelon Sep 1, 2026
c0258dd
ast10x0/smc: report geometry through GeometrySource so Pinned folds t…
JesseMelon Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ local_path_override(

git_override(
module_name = "pigweed",
commit = "6f0aac30f8313cbd5103686097a1f6b3098e8aac", # roll:pigweed
commit = "f540ae6c33424230ed2d388599efa89b6baacf80",
patch_args = ["-p1"],
patches = [
# Program the VeeR external-interrupt redirect table (MEIVT) in
Expand Down
23 changes: 20 additions & 3 deletions hal/blocking/flash/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,18 @@ pub trait FlashDriver {
/// The error type returned by driver operations.
type Error;

/// The default page size in bytes.
const PAGE_SIZE: usize;
/// The page size in bytes.
///
/// Prefer the [`page_size`](Self::page_size) method which is flexible to serve
/// statically defined or runtime discovered values.
const PAGE_SIZE: usize = 0;

/// The maximum size of a single program operation (write window).
/// Program operations cannot span across boundaries aligned to this size.
const PROGRAM_WINDOW_SIZE: usize;
///
/// Prefer the [`program_window_size`](Self::program_window_size) method
/// which is flexible to serve statically defined or runtime discovered values.
const PROGRAM_WINDOW_SIZE: usize = 0;

/// The maximum size of a single read operation.
const MAX_READ_SIZE: usize;
Expand All @@ -42,6 +48,17 @@ pub trait FlashDriver {
/// Returns the total size of the flash in bytes.
fn size(&self) -> NonZero<usize>;

/// Page size in bytes.
fn page_size(&self) -> usize {
Self::PAGE_SIZE
}

/// The maximum size of a single program operation (write window).
/// Program operations cannot span across boundaries aligned to this size.
fn program_window_size(&self) -> usize {
Self::PROGRAM_WINDOW_SIZE
}

/// Returns a bitmap of supported erase block sizes.
///
/// Each bit `i` represents a supported erase block size of `2^i` bytes.
Expand Down
15 changes: 8 additions & 7 deletions hal/blocking/flash/flash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,18 @@ impl<TDriver: FlashDriver, TBlocking: Blocking> Flash for BlockingFlash<TDriver,
/// and do not cross window boundaries. Each chunk is programmed asynchronously,
/// and the thread blocks until it completes before starting the next chunk.
fn program(&mut self, start_addr: FlashAddress, mut data: &[u8]) -> Result<(), Self::Error> {
let program_window_size = self.driver.program_window_size();
assert!(
TDriver::PROGRAM_WINDOW_SIZE.count_ones() == 1,
"TDriver::PROGRAM_WINDOW_SIZE must be a power of 2"
program_window_size.count_ones() == 1,
"program_window_size() must be a power of 2"
);
let window_mask = TDriver::PROGRAM_WINDOW_SIZE - 1;
let window_mask = program_window_size - 1;
let mut addr = start_addr;
while !data.is_empty() {
// Calculate bytes remaining in the current program window
let chunk = &data[..min(
data.len(),
TDriver::PROGRAM_WINDOW_SIZE - ((addr.offset() & window_mask as u32) as usize),
program_window_size - ((addr.offset() & window_mask as u32) as usize),
)];
self.driver.start_program(addr, chunk)?;
self.blocking.wait_for_notification();
Expand Down Expand Up @@ -263,15 +264,15 @@ mod test {
data: &[u8],
) -> Result<(), Self::Error> {
let start_addr = start_addr.offset() as usize;
let program_window_size = self.program_window_size();
assert!(start_addr.checked_add(data.len()).unwrap() <= self.data.len());
assert!(
data.len() <= Self::PROGRAM_WINDOW_SIZE,
data.len() <= program_window_size,
"Program window violation"
);
let end_addr = start_addr.wrapping_add(data.len());
assert!(
start_addr / Self::PROGRAM_WINDOW_SIZE
== (end_addr - 1) / Self::PROGRAM_WINDOW_SIZE,
start_addr / program_window_size == (end_addr - 1) / program_window_size,
"Program window violation"
);
for (dest, src) in self.data[start_addr..end_addr].iter_mut().zip(data) {
Expand Down
58 changes: 58 additions & 0 deletions services/flash/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

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

package(default_visibility = ["//visibility:public"])

rust_library(
name = "opcode",
srcs = [
"opcode.rs",
],
crate_name = "services_flash_opcode",
edition = "2024",
deps = [
"//hal/blocking/flash",
"//util/types",
"@rust_crates//:zerocopy",
],
)

rust_library(
name = "client",
srcs = [
"client.rs",
],
crate_name = "services_flash_client",
edition = "2024",
deps = [
":opcode",
"//hal/blocking/flash",
"//util/error",
"//util/ipc",
"//util/types",
"@pigweed//pw_kernel/userspace",
"@pigweed//pw_log/rust:pw_log",
"@rust_crates//:zerocopy",
],
)

rust_library(
name = "server",
srcs = [
"server.rs",
],
crate_name = "services_flash_server",
edition = "2024",
deps = [
":opcode",
"//hal/blocking/flash",
"//util/error",
"//util/ipc",
"//util/types",
"@pigweed//pw_kernel/userspace",
"@pigweed//pw_log/rust:pw_log",
"@rust_crates//:zerocopy",
],
)
88 changes: 88 additions & 0 deletions services/flash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Flash Service

The Flash Service provides a centralized interface for userspace applications to interact with on-chip and external flash memory. This is achieved via an IPC-based client-server architecture.

## Overview

Applications interact with flash through the `Flash` trait, typically using the `FlashIpcClient` implementation. All operations are blocking from the perspective of the caller.

### Key Features
- **Partition Support**: Access to both primary Data partitions and auxiliary Info partitions.
- **Flexible Erase**: Support for multiple erase granularities (e.g., page vs. block) as reported by the hardware.
- **Unified Addressing**: A logical `FlashAddress` system that abstracts hardware-specific bank and page layouts.

## Usage

To use the flash service, initialize a `FlashIpcClient` with a handle to the flash service:

```rust
use hal_flash::{Flash, FlashAddress};
use services_flash_client::FlashIpcClient;
use util_ipc::IpcHandle;

// 1. Connect to the flash service
let mut flash = FlashIpcClient::new(IpcHandle::new(FLASH_SERVICE_HANDLE))?;

// 2. Retrieve device geometry
let (total_size, page_size, erasable_bitmap) = flash.geometry();

// 3. Erase a block (using the default page size)
let addr = FlashAddress::new(0x1000);
flash.erase(addr, page_size)?;

// 4. Program data
flash.program(addr, b"Hello, Flash!")?;

// 5. Read data back
let mut buf = [0u8; 13];
flash.read(addr, &mut buf)?;
```

## The `Flash` Trait

The primary interface for flash operations:

- `geometry() -> (NonZero<usize>, PowerOf2Usize, u32)`: Returns the total capacity, the default/smallest page size, and a bitmap of all supported erase block sizes.
- `read(addr, buf)`: Reads data from the specified address.
- `erase(addr, size)`: Erases a block of the specified size. The size must be one of the values supported in the `erasable_bitmap`.
- `program(addr, data)`: Writes data to the specified address. Flash must be erased before programming.

### Understanding `erasable_bitmap`
The `erasable_bitmap` is a `u32` where each set bit `i` indicates that an erase block size of `2^i` bytes is supported.
- Bit 11 set (`0x800`) -> 2048-byte erase supported.
- Bit 16 set (`0x10000`) -> 64KB erase supported.

## Addressing

Flash memory is addressed using the `FlashAddress` type, which wraps a single 32-bit `offset`.

On platforms like Earlgrey, the most significant bit (MSB) of this offset is used to distinguish between different partitions:
- **DATA partition**: MSB is 0 (offset < 0x80000000).
- **INFO partition**: MSB is 1 (offset >= 0x80000000).

The `EarlgreyFlashAddress` trait (from `earlgrey_util`) provides helper methods to construct and inspect addresses:
- `FlashAddress::data(offset)`: Accesses the main data partition.
- `FlashAddress::info(bank, page, offset)`: Accesses specific info pages.

## Implementation Details

The service is built on several layers of abstraction:

### IPC Layer
- **`FlashIpcServer`**: Wraps a hardware-backed `Flash` implementation and dispatches IPC requests.
- **`FlashIpcClient`**: Implements the `Flash` trait by proxying calls to the server.

### Hardware Abstraction
- **`FlashDriver` Trait**: Defines the low-level, often asynchronous, interface for hardware drivers.
- **`BlockingFlash`**: A wrapper that converts a `FlashDriver` into a synchronous `Flash` implementation using a provided blocking mechanism.

### Component Diagram

```mermaid
graph TD
Client[Userspace Application] -- "Flash Trait" --> IPC_Client[FlashIpcClient]
IPC_Client -- "IPC" --> IPC_Server[FlashIpcServer]
IPC_Server -- "Flash Trait" --> BlockingFlash[BlockingFlash]
BlockingFlash -- "FlashDriver Trait" --> HardwareDriver[e.g., EmbeddedFlash]
HardwareDriver --> HW[Flash Controller]
```
109 changes: 109 additions & 0 deletions services/flash/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! Flash IPC client implementation.

#![no_std]
use core::num::NonZero;

use hal_flash::{Flash, FlashAddress};
use services_flash_opcode::*;
use userspace::time::Instant;
use util_error::{self as error, ErrorCode};
use util_ipc::{IpcChannel, IpcHandle};
use util_types::PowerOf2Usize;
use zerocopy::{FromZeros, IntoBytes};

/// An IPC-based client for the flash service.
///
/// This struct implements the `Flash` trait by proxying requests to a remote
/// flash server via an IPC handle.
pub struct FlashIpcClient {
ipc: IpcHandle,
page_size: PowerOf2Usize,
total_size: NonZero<usize>,
erasable_sizes_bitmap: u32,
}

impl FlashIpcClient {
/// Creates a new `FlashIpcClient` using the provided IPC handle.
///
/// This constructor will perform an IPC transaction to retrieve flash
/// geometry and capabilities from the server.
pub fn new(ipc: IpcHandle) -> Result<Self, ErrorCode> {
let mut info = FlashInfo::new_zeroed();
let mut result = 0u32;

ipc.transact(
&[IPC_OP_FLASH_GET_INFO.as_bytes()],
&mut [result.as_mut_bytes(), info.as_mut_bytes()],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
ErrorCode::check_status(result)?;

let Some(page_size) = PowerOf2Usize::new(info.page_size as usize) else {
return Err(error::FLASH_GENERIC_INVALID_PAGE_SIZE);
};
let Some(total_size) = NonZero::new(info.total_size as usize) else {
return Err(error::FLASH_GENERIC_INVALID_SIZE);
};
Ok(Self {
ipc,
page_size,
total_size,
erasable_sizes_bitmap: info.erasable_sizes_bitmap,
})
}
}

impl Flash for FlashIpcClient {
type Error = ErrorCode;
fn geometry(&mut self) -> Result<(NonZero<usize>, PowerOf2Usize, u32), ErrorCode> {
Ok((self.total_size, self.page_size, self.erasable_sizes_bitmap))
}

fn erase(&mut self, start_addr: FlashAddress, size: PowerOf2Usize) -> Result<(), ErrorCode> {
let mut result = 0u32;
let op = EraseOp {
address: start_addr,
size: size.get() as u32,
};
self.ipc
.transact(
&[IPC_OP_FLASH_ERASE.as_bytes(), op.as_bytes()],
&mut [result.as_mut_bytes()],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
ErrorCode::check_status(result)
}

fn program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), ErrorCode> {
let mut result = 0u32;
self.ipc
.transact(
&[IPC_OP_FLASH_PROGRAM.as_bytes(), start_addr.as_bytes(), data],
&mut [result.as_mut_bytes()],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
ErrorCode::check_status(result)
}

fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), ErrorCode> {
let mut result = 0u32;
let op = ReadOp {
address: start_addr,
length: buf.len() as u32,
};
self.ipc
.transact(
&[IPC_OP_FLASH_READ.as_bytes(), op.as_bytes()],
&mut [result.as_mut_bytes(), buf],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
ErrorCode::check_status(result)
}
}
Loading