Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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",
],
)
55 changes: 55 additions & 0 deletions hal/blocking/flash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Blocking Flash HAL

This HAL provides abstractions for interacting with flash memory. It defines traits for low-level drivers and high-level synchronous interfaces, along with a helper implementation to bridge them.

## Key Abstractions

### `FlashAddress`

A transparent wrapper around a 32-bit offset (`u32`) representing a location in flash memory. It supports basic arithmetic operations (`Add`, `AddAssign`, `BitAnd`, `BitAndAssign`) to facilitate address calculations.

Platform-specific extension traits (like `EarlgreyFlashAddress` in `earlgrey_util`) may use the offset bits to encode additional information (e.g., distinguishing between DATA and INFO partitions using the MSB).

### `FlashDriver` Trait

Defines the low-level interface for flash hardware. It is designed to support both synchronous and asynchronous hardware controllers using a start-poll-complete execution model for erase and program operations:

1. **Start**: Initiated via `start_erase` or `start_program`.
2. **Poll**: Check completion status via `is_busy`, or wait for a hardware interrupt.
3. **Complete**: Finalize the operation and retrieve any execution errors via `complete_op`.

Read operations (`read`) are synchronous for simplicity.

Drivers also define hardware-specific constraints as constants:
* `ERASABLE_SIZES_BITMAP`: A bitmap where bit `i` is set if erasing blocks of size `2^i` is supported.
* `PROGRAM_WINDOW_SIZE`: The maximum size of a single write, and the boundary alignment constraint for writes.
* `MAX_READ_SIZE`: The maximum size of a single read.
* `READ_ALIGNMENT` / `PROGRAM_ALIGNMENT`: Address and size alignment requirements.

### `Flash` Trait

Provides a simplified, synchronous, blocking interface suitable for application use. It abstracts away the low-level start-poll-complete flow and hardware constraints (alignment, windows).

### `BlockingFlash`

A concrete implementation of the `Flash` trait that wraps a `FlashDriver` and a `Blocking` mechanism. It implements the blocking behavior and handles driver constraints automatically:

```mermaid
graph TD
App[Application] -- "Flash::program()" --> BF[BlockingFlash]
BF -- "1. start_program()" --> Driver[FlashDriver]
BF -- "2. wait_for_notification()" --> Blocking[Blocking impl]
Blocking -- "wakes up (IRQ/Poll)" --> BF
BF -- "3. complete_op()" --> Driver
```

#### Read Alignment Handling

If a read request is not aligned to `TDriver::READ_ALIGNMENT`, `BlockingFlash::read` will:
1. Read the aligned block containing the unaligned start address into a temporary buffer.
2. Copy the requested bytes from the temporary buffer.
3. Read the remaining data in aligned chunks directly into the destination buffer.

#### Program Window Handling

Hardware flash controllers often cannot program data that crosses a write window boundary (typically 64 bytes). `BlockingFlash::program` automatically detects these boundaries and splits a large or unaligned write into multiple smaller writes that fit within the program windows.
159 changes: 159 additions & 0 deletions hal/blocking/flash/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! 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 an asynchronous-style execution model using a start-poll-complete pattern:
/// 1. Start the operation (`start_erase`, `start_program`).
/// 2. Poll status (`is_busy`) or wait for interrupt.
/// 3. Finalize and check errors (`complete_op`).
///
/// Read operations (`read`) are assumed to be synchronous and blocking for simplicity.
pub trait FlashDriver {
/// The error type returned by driver operations.
type Error;

/// The default page size in bytes.
const PAGE_SIZE: usize;

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

/// The maximum size of a single read operation.
const MAX_READ_SIZE: usize;

/// The alignment required for read operations (addresses and lengths).
const READ_ALIGNMENT: usize;

/// The alignment required for program operations (addresses and lengths).
const PROGRAM_ALIGNMENT: usize;

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

/// Returns a bitmap of supported erase block sizes.
///
/// Each bit `i` represents a supported erase block size of `2^i` bytes.
/// For example, if bit 11 is set, then 2048-byte erases are supported.
fn erasable_sizes_bitmap(&mut self) -> Result<u32, Self::Error>;

/// Reads data from flash synchronously.
///
/// # Arguments
/// * `start_addr`: The address to start reading from. Must be aligned to `READ_ALIGNMENT`.
/// * `buf`: The buffer to read data into. Must have size <= `MAX_READ_SIZE` and be aligned to `READ_ALIGNMENT`.
fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), Self::Error>;

/// Starts an erase operation asynchronously.
///
/// The operation is not guaranteed to be complete until `complete_op` is called
/// and returns `Ok`.
///
/// # Arguments
/// * `start_addr`: The start address of the block to erase. Must be aligned to the block size.
/// * `size`: The size of the block to erase. Must have its corresponding bit set in `ERASABLE_SIZES_BITMAP`.
fn start_erase(
&mut self,
start_addr: FlashAddress,
size: PowerOf2Usize,
) -> Result<(), Self::Error>;

/// Starts a program operation asynchronously.
///
/// The operation is not guaranteed to be complete until `complete_op` is called
/// and returns `Ok`.
///
/// The programmed region must not cross a `PROGRAM_WINDOW_SIZE` boundary.
///
/// # Arguments
/// * `start_address`: The address to start programming at. Must be aligned to `PROGRAM_ALIGNMENT`.
/// * `data`: The data to program. Must have size <= `PROGRAM_WINDOW_SIZE` and be aligned to `PROGRAM_ALIGNMENT`.
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 erase or program operation and returns the result.
///
/// This should be called after `is_busy` returns false to check for errors and
/// reset the driver state.
fn complete_op(&mut self) -> Result<(), Self::Error>;
}

/// Represents an address in flash memory.
///
/// A flash address consists of an offset within the flash address space.
#[derive(Default, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable, FromBytes, KnownLayout)]
#[repr(transparent)]
pub struct FlashAddress {
offset: u32,
}

impl core::fmt::Display for FlashAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "0x{:08x}", 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 {

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.

impl FlashAddress {
    /// Advances the address by `n` bytes, returning `None` on overflow.
    pub fn checked_add(self, n: usize) -> Option<Self> {
        u32::try_from(n).ok()
            .and_then(|n| self.offset.checked_add(n))
            .map(Self::new)
    }
}

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

/// Returns the offset.
pub fn offset(&self) -> u32 {
self.offset
}
}

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

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.

This is an uncheked add

}
}
}

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

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.

This is an uncheked add

}
}

impl core::ops::BitAnd<usize> for FlashAddress {
type Output = Self;
fn bitand(self, other: usize) -> Self {
Self {
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