diff --git a/hal/blocking/flash/BUILD.bazel b/hal/blocking/flash/BUILD.bazel new file mode 100644 index 00000000..6962a9c3 --- /dev/null +++ b/hal/blocking/flash/BUILD.bazel @@ -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", + ], +) diff --git a/hal/blocking/flash/README.md b/hal/blocking/flash/README.md new file mode 100644 index 00000000..55823ea9 --- /dev/null +++ b/hal/blocking/flash/README.md @@ -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. diff --git a/hal/blocking/flash/driver.rs b/hal/blocking/flash/driver.rs new file mode 100644 index 00000000..378152bf --- /dev/null +++ b/hal/blocking/flash/driver.rs @@ -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; + + /// 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; + + /// 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 { + /// 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 for FlashAddress { + type Output = Self; + fn add(self, other: usize) -> Self { + Self { + offset: self.offset + other as u32, + } + } +} + +impl core::ops::AddAssign for FlashAddress { + fn add_assign(&mut self, other: usize) { + self.offset += other as u32; + } +} + +impl core::ops::BitAnd for FlashAddress { + type Output = Self; + fn bitand(self, other: usize) -> Self { + Self { + offset: self.offset & other as u32, + } + } +} + +impl core::ops::BitAndAssign for FlashAddress { + fn bitand_assign(&mut self, other: usize) { + self.offset &= other as u32; + } +} diff --git a/hal/blocking/flash/flash.rs b/hal/blocking/flash/flash.rs new file mode 100644 index 00000000..072e83c7 --- /dev/null +++ b/hal/blocking/flash/flash.rs @@ -0,0 +1,430 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! High-level blocking flash interface. + +#![cfg_attr(not(test), no_std)] + +use core::{cmp::min, num::NonZero}; +pub use hal_flash_driver::FlashAddress; +use hal_flash_driver::FlashDriver; +use util_io::RandomRead; +use util_types::{Blocking, PowerOf2Usize}; + +/// High-level flash interface. +/// +/// This trait provides a simplified, synchronous, blocking interface for flash operations. +/// It abstracts away the asynchronous execution model and hardware alignment/window +/// constraints of the underlying driver. +pub trait Flash { + /// The error type returned by flash operations. + type Error; + + /// Returns the geometry of the flash. + /// + /// # Returns + /// A tuple containing: + /// 1. The total size of the flash in bytes. + /// 2. The default/smallest page size (erase block size) in bytes. + /// 3. A bitmap of all supported erase block sizes. + fn geometry(&mut self) -> Result<(NonZero, PowerOf2Usize, u32), Self::Error>; + + /// Reads data from flash into the provided buffer. + /// + /// This method handles unaligned read addresses by performing partial reads + /// into a temporary buffer if necessary. + /// + /// # 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>; + + /// Erases a block of flash. + /// + /// This is a blocking operation that waits for the hardware erase to complete. + /// + /// # Arguments + /// * `start_addr`: The start address of the block to erase. Must be aligned to `size`. + /// * `size`: The size of the block to erase. Must be one of the supported sizes in `geometry().2`. + fn erase(&mut self, start_addr: FlashAddress, size: PowerOf2Usize) -> Result<(), Self::Error>; + + /// Programs data into flash. + /// + /// This is a blocking operation that waits for the hardware program to complete. + /// It automatically handles programming data that spans across hardware program + /// window boundaries by splitting it into multiple aligned writes. + /// + /// # Arguments + /// * `start_addr`: The address to start programming at. + /// * `data`: The data to program. + fn program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), Self::Error>; + + /// Returns a `RandomRead` implementation for this flash. + fn random_reader(&mut self) -> impl RandomRead + where + Self: Sized, + { + FlashRandomReader(self) + } +} + +impl Flash for &mut F { + type Error = F::Error; + #[inline(always)] + fn geometry(&mut self) -> Result<(NonZero, PowerOf2Usize, u32), Self::Error> { + (**self).geometry() + } + #[inline(always)] + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), Self::Error> { + (**self).read(start_addr, buf) + } + #[inline(always)] + fn program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), Self::Error> { + (**self).program(start_addr, data) + } + #[inline(always)] + fn erase(&mut self, start_addr: FlashAddress, size: PowerOf2Usize) -> Result<(), Self::Error> { + (**self).erase(start_addr, size) + } +} + +/// A trait that can be used to constrain the page-size of the flash. +/// +/// If you just need to read the page size at runtime, use `Flash::geometry()` instead. +pub trait FlashPageSize { + /// The size of a flash page in bytes. + const PAGE_SIZE: usize; +} + +/// A blocking flash implementation that wraps a `FlashDriver`. +/// +/// This struct implements the high-level `Flash` trait by wrapping a low-level +/// `FlashDriver` and using a `Blocking` mechanism (e.g., waiting for an interrupt +/// or polling) to block the calling thread until asynchronous driver operations +/// complete. +/// +/// It also handles address alignment for reads and program window constraints for writes. +pub struct BlockingFlash { + /// The underlying flash driver. + pub driver: TDriver, + /// The blocking mechanism used to wait for operations. + pub blocking: TBlocking, +} + +impl FlashPageSize + for BlockingFlash +{ + /// The default page size. + const PAGE_SIZE: usize = TDriver::PAGE_SIZE; +} + +impl Flash for BlockingFlash { + type Error = TDriver::Error; + fn geometry(&mut self) -> Result<(NonZero, PowerOf2Usize, u32), Self::Error> { + let bitmap = self.driver.erasable_sizes_bitmap()?; + let page_size = PowerOf2Usize::new(1 << (bitmap.trailing_zeros())).unwrap(); + Ok((self.driver.size(), page_size, bitmap)) + } + /// Reads data from flash. + /// + /// Handles unaligned `start_addr` by reading the aligned block containing it + /// into a temporary buffer first, copying the relevant bytes, and then reading + /// the remaining data in aligned chunks. + fn read(&mut self, start_addr: FlashAddress, mut buf: &mut [u8]) -> Result<(), Self::Error> { + let mut addr = start_addr; + let align_skip_len = (addr.offset() & (TDriver::READ_ALIGNMENT as u32 - 1)) as usize; + if (align_skip_len) != 0 { + // Read prefix up to alignment boundary + assert!(TDriver::READ_ALIGNMENT <= 16); + let mut tmp = [0_u8; 16]; + let prefix_count = min(TDriver::READ_ALIGNMENT - align_skip_len, buf.len()); + self.driver + .read(addr & !(TDriver::READ_ALIGNMENT - 1), &mut tmp)?; + buf[..prefix_count].copy_from_slice(&tmp[align_skip_len..][..prefix_count]); + buf = &mut buf[prefix_count..]; + addr += prefix_count; + } + // Read remaining aligned chunks + for buf_chunk in buf.chunks_mut(TDriver::MAX_READ_SIZE) { + self.driver.read(addr, buf_chunk)?; + addr += buf_chunk.len(); + } + Ok(()) + } + /// Erases a block of flash. + /// + /// Starts the asynchronous erase operation and blocks the thread using `self.blocking` + /// until the operation completes. + fn erase(&mut self, start_addr: FlashAddress, size: PowerOf2Usize) -> Result<(), Self::Error> { + self.driver.start_erase(start_addr, size)?; + self.blocking.wait_for_notification(); + self.driver.complete_op() + } + /// Programs data into flash. + /// + /// Splits the data into chunks that fit within the hardware's `PROGRAM_WINDOW_SIZE` + /// 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> { + assert!( + TDriver::PROGRAM_WINDOW_SIZE.count_ones() == 1, + "TDriver::PROGRAM_WINDOW_SIZE must be a power of 2" + ); + let window_mask = TDriver::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), + )]; + self.driver.start_program(addr, chunk)?; + self.blocking.wait_for_notification(); + self.driver.complete_op()?; + data = &data[chunk.len()..]; + addr += chunk.len(); + } + Ok(()) + } +} + +struct FlashRandomReader<'a, F: Flash>(&'a mut F); +impl RandomRead for FlashRandomReader<'_, F> { + type Error = F::Error; + fn read(&mut self, start_addr: usize, buf: &mut [u8]) -> Result<(), Self::Error> { + self.0.read(FlashAddress::new(start_addr as u32), buf) + } + fn size(&mut self) -> Result { + Ok(self.0.geometry()?.0.get()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + pub struct FakeBlocking(); + impl Blocking for FakeBlocking { + fn wait_for_notification(&self) {} + } + + #[derive(Debug, Clone, Copy)] + pub struct FakeDriverError; + #[derive(Clone)] + pub struct FakeFlashDriver { + pub data: Vec, + pub check_err_result: Result<(), FakeDriverError>, + } + impl FakeFlashDriver { + pub fn new(data: Vec) -> Self { + Self { + data, + check_err_result: Ok(()), + } + } + } + impl FlashDriver for FakeFlashDriver { + type Error = FakeDriverError; + const PAGE_SIZE: usize = 2048; + const PROGRAM_WINDOW_SIZE: usize = 64; + const MAX_READ_SIZE: usize = 4096; + const READ_ALIGNMENT: usize = 4; + const PROGRAM_ALIGNMENT: usize = 8; + + fn erasable_sizes_bitmap(&mut self) -> Result { + Ok(1 << 11) + } + fn size(&self) -> NonZero { + NonZero::new(self.data.len()).unwrap() + } + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), Self::Error> { + let start_addr = start_addr.offset() as usize; + assert!(start_addr.checked_add(buf.len()).unwrap() <= self.data.len()); + assert!(buf.len() <= Self::MAX_READ_SIZE); + assert!(start_addr % Self::READ_ALIGNMENT == 0); + buf.copy_from_slice(&self.data[start_addr..][..buf.len()]); + Ok(()) + } + fn start_erase( + &mut self, + start_addr: FlashAddress, + size: PowerOf2Usize, + ) -> Result<(), Self::Error> { + let start_addr = start_addr.offset() as usize; + assert_eq!(size.get(), 2048); + assert!(start_addr.checked_add(size.get()).unwrap() <= self.data.len()); + assert!(start_addr % size.get() == 0); + self.data[start_addr..][..size.get()].fill(0xff); + Ok(()) + } + fn start_program( + &mut self, + start_addr: FlashAddress, + data: &[u8], + ) -> Result<(), Self::Error> { + let start_addr = start_addr.offset() as usize; + assert!(start_addr.checked_add(data.len()).unwrap() <= self.data.len()); + assert!( + data.len() <= Self::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, + "Program window violation" + ); + for (dest, src) in self.data[start_addr..end_addr].iter_mut().zip(data) { + *dest &= *src; + } + Ok(()) + } + fn is_busy(&mut self) -> bool { + false + } + fn complete_op(&mut self) -> Result<(), Self::Error> { + self.check_err_result + } + } + + #[test] + #[should_panic(expected = "Program window violation")] + pub fn test_fake_flash_program_window_violation_0() { + let mut flash_driver = FakeFlashDriver::new((0..255).collect()); + flash_driver + .start_program(FlashAddress::new(0x3c), &[0x42; 5]) + .unwrap(); + } + + #[test] + #[should_panic(expected = "Program window violation")] + pub fn test_fake_flash_program_window_violation_1() { + let mut flash_driver = FakeFlashDriver::new((0..255).collect()); + flash_driver + .start_program(FlashAddress::new(0x0), &[0; 68]) + .unwrap(); + } + + #[test] + pub fn test_fake_flash_full_program_window() { + let mut flash_driver = FakeFlashDriver::new((0..255).collect()); + flash_driver + .start_program(FlashAddress::new(0x40), &[0; 0x40]) + .unwrap(); + assert_eq!(flash_driver.data[0x40..0x80], [0; 0x40]); + } + + #[test] + pub fn test_size() { + let flash_driver = FakeFlashDriver::new((0..255).collect()); + let mut flash = BlockingFlash { + driver: flash_driver, + blocking: FakeBlocking(), + }; + + assert_eq!(flash.geometry().unwrap().0.get(), 255); + let mut reader = flash.random_reader(); + assert_eq!(reader.size().unwrap(), 255); + } + + #[test] + pub fn test_read() { + let flash_driver = FakeFlashDriver::new((0..255).collect()); + + let mut flash = BlockingFlash { + driver: flash_driver, + blocking: FakeBlocking(), + }; + + let mut buf = [0_u8; 4]; + flash.read(FlashAddress::new(0), &mut buf).unwrap(); + assert_eq!(buf, [0_u8, 1, 2, 3]); + + let mut buf = [0_u8; 4]; + flash.read(FlashAddress::new(1), &mut buf).unwrap(); + assert_eq!(buf, [1, 2, 3, 4]); + + let mut buf = [0_u8; 4]; + flash.read(FlashAddress::new(2), &mut buf).unwrap(); + assert_eq!(buf, [2, 3, 4, 5]); + + { + let mut reader = flash.random_reader(); + let mut buf = [0_u8; 4]; + reader.read(2, &mut buf).unwrap(); + assert_eq!(buf, [2, 3, 4, 5]); + } + + let mut buf = [0_u8; 4]; + flash.read(FlashAddress::new(3), &mut buf).unwrap(); + assert_eq!(buf, [3, 4, 5, 6]); + + let mut buf = [0_u8; 6]; + flash.read(FlashAddress::new(3), &mut buf).unwrap(); + assert_eq!(buf, [3, 4, 5, 6, 7, 8]); + + for i in 0..32 { + let mut buf = [0_u8; 32]; + flash.read(FlashAddress::new(0), &mut buf[..i]).unwrap(); + assert_eq!(&buf[..i], &flash.driver.data[..i]); + } + + for i in 0..32 { + let mut buf = [0_u8; 32]; + flash + .read(FlashAddress::new(32 - i as u32), &mut buf[..i]) + .unwrap(); + assert_eq!(&buf[..i], &flash.driver.data[32 - i..32]); + } + } + + #[test] + pub fn test_erase() { + let mut flash = BlockingFlash { + driver: FakeFlashDriver::new(vec![0x42; 0x4000]), + blocking: FakeBlocking(), + }; + flash + .erase(FlashAddress::new(0x0800), PowerOf2Usize::new(2048).unwrap()) + .unwrap(); + assert_eq!(flash.driver.data[0x0000..0x0800], [0x42; 0x0800]); + assert_eq!(flash.driver.data[0x0800..0x1000], [0xff; 0x0800]); + assert_eq!(flash.driver.data[0x1000..0x4000], [0x42; 0x3000]); + + flash + .erase(FlashAddress::new(0x3000), PowerOf2Usize::new(2048).unwrap()) + .unwrap(); + assert_eq!(flash.driver.data[0x0000..0x0800], [0x42; 0x0800]); + assert_eq!(flash.driver.data[0x0800..0x1000], [0xff; 0x0800]); + assert_eq!(flash.driver.data[0x1000..0x3000], [0x42; 0x2000]); + assert_eq!(flash.driver.data[0x3000..0x3800], [0xff; 0x0800]); + assert_eq!(flash.driver.data[0x3800..0x4000], [0x42; 0x0800]); + } + + #[test] + pub fn test_program() { + let mut flash = BlockingFlash { + driver: FakeFlashDriver::new(vec![0xff; 8192]), + blocking: FakeBlocking(), + }; + + flash + .program( + FlashAddress::new(0x3c), + &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15], + ) + .unwrap(); + assert_eq!( + flash.driver.data[0x38..0x44], + [0xff, 0xff, 0xff, 0xff, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0xff, 0xff] + ); + + flash + .program(FlashAddress::new(0x40), &[0x24, 0x25]) + .unwrap(); + assert_eq!( + flash.driver.data[0x38..0x44], + [0xff, 0xff, 0xff, 0xff, 0x10, 0x11, 0x12, 0x13, 0x04, 0x05, 0xff, 0xff] + ); + } +} diff --git a/services/flash/BUILD.bazel b/services/flash/BUILD.bazel new file mode 100644 index 00000000..e4a6babd --- /dev/null +++ b/services/flash/BUILD.bazel @@ -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", + ], +) diff --git a/services/flash/README.md b/services/flash/README.md new file mode 100644 index 00000000..e352c54a --- /dev/null +++ b/services/flash/README.md @@ -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, 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] +``` diff --git a/services/flash/client.rs b/services/flash/client.rs new file mode 100644 index 00000000..fbd6fec1 --- /dev/null +++ b/services/flash/client.rs @@ -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, + 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 { + 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, 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) + } +} diff --git a/services/flash/opcode.rs b/services/flash/opcode.rs new file mode 100644 index 00000000..ef6805ee --- /dev/null +++ b/services/flash/opcode.rs @@ -0,0 +1,51 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Shared flash IPC opcodes and data structures. + +#![no_std] + +use hal_flash::FlashAddress; +use util_types::Opcode; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +/// IPC opcode for erasing a flash block. +pub const IPC_OP_FLASH_ERASE: Opcode = Opcode::new(*b"FLET"); +/// IPC opcode for programming flash. +pub const IPC_OP_FLASH_PROGRAM: Opcode = Opcode::new(*b"FLWR"); +/// IPC opcode for reading from flash. +pub const IPC_OP_FLASH_READ: Opcode = Opcode::new(*b"FLRD"); +/// IPC opcode for retrieving flash information. +pub const IPC_OP_FLASH_GET_INFO: Opcode = Opcode::new(*b"FLIN"); + +/// Information about the flash device. +#[derive(FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct FlashInfo { + /// The size of a single flash page in bytes. + pub page_size: u32, + /// The total size of the flash in bytes. + pub total_size: u32, + /// A bitmap of supported erase block sizes. + pub erasable_sizes_bitmap: u32, +} + +/// Arguments for the `IPC_OP_FLASH_ERASE` request. +#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct EraseOp { + /// The start address of the block to erase. + pub address: FlashAddress, + /// The size of the block to erase in bytes. + pub size: u32, +} + +/// Arguments for the `IPC_OP_FLASH_READ` request. +#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct ReadOp { + /// The start address to read from. + pub address: FlashAddress, + /// The number of bytes to read. + pub length: u32, +} diff --git a/services/flash/server.rs b/services/flash/server.rs new file mode 100644 index 00000000..296bbbe8 --- /dev/null +++ b/services/flash/server.rs @@ -0,0 +1,134 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Flash IPC server implementation. + +#![no_std] + +use hal_flash::{Flash, FlashAddress}; +use services_flash_opcode::*; +use util_error::{self as error, ErrorCode}; +use util_ipc::{IpcChannel, IpcHandle}; +use util_types::{Opcode, PowerOf2Usize}; +use zerocopy::{FromBytes, IntoBytes}; + +/// A flash server that handles flash IPC requests. +/// +/// This struct wraps an object implementing the `Flash` trait and provides +/// an IPC interface to it. +pub struct FlashIpcServer { + flash: TFlash, +} + +impl> FlashIpcServer { + /// Creates a new `FlashIpcServer` wrapping the given flash implementation. + pub fn new(flash: TFlash) -> Self { + Self { flash } + } + + /// Handles the `IPC_OP_FLASH_GET_INFO` request. + /// + /// Writes the flash geometry into the provided buffer and returns it. + fn handle_geometry<'a>( + &mut self, + data: &'a mut [u8], + reqsz: usize, + ) -> Result<&'a [u8], ErrorCode> { + if reqsz != 0 { + return Err(error::IPC_ERROR_BAD_REQ_LEN); + } + let (info, _rest) = + FlashInfo::mut_from_prefix(data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + let (total_size, page_size, erasable_sizes_bitmap) = self.flash.geometry()?; + info.page_size = page_size.get() as u32; + info.total_size = total_size.get() as u32; + info.erasable_sizes_bitmap = erasable_sizes_bitmap; + Ok(info.as_bytes()) + } + + /// Handles the `IPC_OP_FLASH_ERASE` request. + /// + /// Parses the `EraseOp` from the input data and erases the specified block. + fn handle_erase<'a>( + &mut self, + data: &'a mut [u8], + reqsz: usize, + ) -> Result<&'a [u8], ErrorCode> { + let req_data = data.get(..reqsz).ok_or(error::IPC_ERROR_BAD_REQ_LEN)?; + let op = EraseOp::read_from_bytes(req_data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + let Some(size) = PowerOf2Usize::new(op.size as usize) else { + return Err(error::FLASH_GENERIC_ERASE_INVALID_SIZE); + }; + self.flash.erase(op.address, size)?; + Ok(&data[0..0]) + } + + /// Handles the `IPC_OP_FLASH_PROGRAM` request. + /// + /// Parses the start address and data from the input, then programs it. + fn handle_program<'a>( + &mut self, + data: &'a mut [u8], + reqsz: usize, + ) -> Result<&'a [u8], ErrorCode> { + let req_data = data.get(..reqsz).ok_or(error::IPC_ERROR_BAD_REQ_LEN)?; + let (addr, program_data) = + FlashAddress::read_from_prefix(req_data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + self.flash.program(addr, program_data)?; + Ok(&data[0..0]) + } + + /// Handles the `IPC_OP_FLASH_READ` request. + /// + /// Parses the `ReadOp` from the input, reads the data from flash into the + /// buffer, and returns the read slice. + fn handle_read<'a>(&mut self, data: &'a mut [u8], reqsz: usize) -> Result<&'a [u8], ErrorCode> { + let req_data = data.get(..reqsz).ok_or(error::IPC_ERROR_BAD_REQ_LEN)?; + let op = ReadOp::read_from_bytes(req_data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + let length = op.length as usize; + if length > data.len() { + return Err(error::FLASH_GENERIC_INVALID_SIZE); + } + self.flash.read(op.address, &mut data[..length])?; + Ok(&data[..length]) + } + + fn handle_op<'a>( + &mut self, + opcode: Opcode, + data: &'a mut [u8], + reqsz: usize, + ) -> Result<&'a [u8], ErrorCode> { + match opcode { + IPC_OP_FLASH_GET_INFO => self.handle_geometry(data, reqsz), + IPC_OP_FLASH_ERASE => self.handle_erase(data, reqsz), + IPC_OP_FLASH_PROGRAM => self.handle_program(data, reqsz), + IPC_OP_FLASH_READ => self.handle_read(data, reqsz), + _ => Err(error::IPC_ERROR_UNKNOWN_OP), + } + } + + /// Handles a single IPC request. + /// + /// This method performs a non-blocking read on the IPC handle. The caller + /// must ensure the handle is readable (e.g., by calling `syscall::object_wait`) + /// before calling this method. + pub fn handle_one(&mut self, ipc: &IpcHandle, data: &mut [u8]) -> Result<(), ErrorCode> { + let len = ipc.read(0, data).map_err(ErrorCode::kernel_error)?; + let (opcode, reqrsp) = data.split_at_mut(core::mem::size_of::()); + let opcode = Opcode::read_from_bytes(opcode).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + let len = len.saturating_sub(core::mem::size_of::()); + + let mut status = 0u32; + let result = match self.handle_op(opcode, reqrsp, len) { + Ok(result) => result, + Err(e) => { + status = e.0.get(); + &[] + } + }; + ipc.respond(&[status.as_bytes(), result]) + .map_err(ErrorCode::kernel_error)?; + Ok(()) + } +} diff --git a/util/io/BUILD.bazel b/util/io/BUILD.bazel new file mode 100644 index 00000000..9c680766 --- /dev/null +++ b/util/io/BUILD.bazel @@ -0,0 +1,23 @@ +# 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 = "io", + srcs = [ + "io.rs", + ], + crate_name = "util_io", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//util/error", + "@pigweed//pw_status/rust:pw_status", + ], +) + +rust_test( + name = "io_test", + crate = ":io", +) diff --git a/util/io/io.rs b/util/io/io.rs new file mode 100644 index 00000000..1b834b80 --- /dev/null +++ b/util/io/io.rs @@ -0,0 +1,78 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Input/Output traits and utilities. + +#![no_std] + +use util_error::{ErrorCode, ErrorModule}; + +/// The generic IO error module. +pub const IO_GENERIC: ErrorModule = ErrorModule::new(0x494F); // ascii: IO +/// The read operation is out of bounds. +pub const IO_GENERIC_READ_OUT_OF_BOUNDS: ErrorCode = + IO_GENERIC.from_pw(1, pw_status::Error::OutOfRange); + +/// Trait for random access read operations. +pub trait RandomRead { + type Error; + /// Reads data from the source into the destination buffer. + /// + /// # Arguments + /// * `start_addr`: The offset from which to start reading. + /// * `dst`: The buffer to read data into. + fn read(&mut self, start_addr: usize, dst: &mut [u8]) -> Result<(), Self::Error>; + + /// Returns the total size of the readable data in bytes. + fn size(&mut self) -> Result; +} + +impl RandomRead for &[u8] { + type Error = ErrorCode; + fn read(&mut self, start_addr: usize, dst: &mut [u8]) -> Result<(), Self::Error> { + // Explicit wrapping add. Overflows are expected to + // be detected in the indexing operation + let end_addr = start_addr.wrapping_add(dst.len()); + let src = self + .get(start_addr..end_addr) + .ok_or(IO_GENERIC_READ_OUT_OF_BOUNDS)?; + dst.copy_from_slice(src); + Ok(()) + } + fn size(&mut self) -> Result { + Ok(self.len()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn should_read() { + let mut src: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut dst: [u8; 3] = [0; 3]; + assert!(src.read(6, &mut dst).is_ok()); + assert_eq!(&dst, &[7, 8, 9]); + assert_eq!(RandomRead::size(&mut src).unwrap(), 9); + } + + #[test] + fn should_fail() { + let mut src: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut dst: [u8; 3] = [0; 3]; + assert!(src.read(7, &mut dst).is_err()); + } + + #[test] + fn invalid_start_address_should_not_panic() { + let mut src: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8]; + let mut dst: [u8; 4] = [0; 4]; + // Set `start_addr` so that adding `dst.len()` causes it to + // wrap around and become smaller than `src.len()` + let start_addr: usize = usize::MAX - (dst.len() - 1); + // Should not panic + let result = src.read(start_addr, &mut dst); + assert!(result.is_err()); + } +} diff --git a/util/ipc/BUILD.bazel b/util/ipc/BUILD.bazel new file mode 100644 index 00000000..c748e921 --- /dev/null +++ b/util/ipc/BUILD.bazel @@ -0,0 +1,26 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") + +rust_library( + name = "ipc", + srcs = [ + "host.rs", + "lib.rs", + "target.rs", + ], + crate_name = "util_ipc", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "@pigweed//pw_status/rust:pw_status", + ] + select({ + "@platforms//os:none": [ + "@pigweed//pw_kernel/userspace", + ], + "//conditions:default": [ + "@pigweed//pw_time/rust:pw_time", + ], + }), +) diff --git a/util/ipc/host.rs b/util/ipc/host.rs new file mode 100644 index 00000000..9abb9caf --- /dev/null +++ b/util/ipc/host.rs @@ -0,0 +1,121 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use super::{IpcChannel, IpcHandle}; + +pub trait AsSyscallBuffer { + fn as_raw(&self) -> (*const u8, usize); + fn as_raw_mut(&mut self) -> (*mut u8, usize); + fn total_size(&self) -> usize; +} + +// Converts a simple u8 slice. +impl AsSyscallBuffer for [u8] { + fn as_raw(&self) -> (*const u8, usize) { + (self.as_ptr(), self.len()) + } + fn as_raw_mut(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr(), self.len()) + } + fn total_size(&self) -> usize { + self.len() + } +} + +// Converts a simple u8 array. +impl AsSyscallBuffer for [u8; N] { + fn as_raw(&self) -> (*const u8, usize) { + (self.as_ptr(), self.len()) + } + fn as_raw_mut(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr(), self.len()) + } + fn total_size(&self) -> usize { + self.len() + } +} + +// Converts a slice of u8 slices. +impl AsSyscallBuffer for [&[u8]] { + fn as_raw(&self) -> (*const u8, usize) { + (self.as_ptr().cast::(), self.len().wrapping_neg()) + } + fn as_raw_mut(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr().cast::(), self.len().wrapping_neg()) + } + fn total_size(&self) -> usize { + self.iter().fold(0, |total, item| total + item.len()) + } +} + +impl AsSyscallBuffer for [&mut [u8]] { + fn as_raw(&self) -> (*const u8, usize) { + (self.as_ptr().cast::(), self.len().wrapping_neg()) + } + fn as_raw_mut(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr().cast::(), self.len().wrapping_neg()) + } + fn total_size(&self) -> usize { + self.iter().fold(0, |total, item| total + item.len()) + } +} + +// Converts an array of u8 slices. +impl AsSyscallBuffer for [&[u8]; N] { + fn as_raw(&self) -> (*const u8, usize) { + (self.as_ptr().cast::(), self.len().wrapping_neg()) + } + fn as_raw_mut(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr().cast::(), self.len().wrapping_neg()) + } + fn total_size(&self) -> usize { + self.iter().fold(0, |total, item| total + item.len()) + } +} + +impl AsSyscallBuffer for [&mut [u8]; N] { + fn as_raw(&self) -> (*const u8, usize) { + (self.as_ptr().cast::(), self.len().wrapping_neg()) + } + fn as_raw_mut(&mut self) -> (*mut u8, usize) { + (self.as_mut_ptr().cast::(), self.len().wrapping_neg()) + } + fn total_size(&self) -> usize { + self.iter().fold(0, |total, item| total + item.len()) + } +} + +pub type Instant = pw_time::Instant; + +impl IpcChannel for IpcHandle { + fn transact( + &self, + _send_data: &BufSend, + _recv_data: &mut BufRecv, + _deadline: Instant, + ) -> pw_status::Result + where + BufSend: AsSyscallBuffer + ?Sized, + BufRecv: AsSyscallBuffer + ?Sized, + { + panic!("IpcHandle cannot be used on host"); + } + + fn read(&self, _offset: usize, _buffer: &mut Buf) -> pw_status::Result + where + Buf: AsSyscallBuffer + ?Sized, + { + panic!("IpcHandle cannot be used on host"); + } + + fn respond(&self, _buffer: &Buf) -> pw_status::Result<()> + where + Buf: AsSyscallBuffer + ?Sized, + { + panic!("IpcHandle cannot be used on host"); + } + + fn set_peer_user_signal(&self, _set: bool) -> pw_status::Result<()> { + panic!("IpcHandle cannot be used on host"); + } +} diff --git a/util/ipc/lib.rs b/util/ipc/lib.rs new file mode 100644 index 00000000..7861452b --- /dev/null +++ b/util/ipc/lib.rs @@ -0,0 +1,53 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use pw_status::Result; + +/// Trait wrapping basic IPC operations on a channel. +pub trait IpcChannel { + fn transact( + &self, + send_data: &BufSend, + recv_data: &mut BufRecv, + deadline: Instant, + ) -> Result + where + BufSend: AsSyscallBuffer + ?Sized, + BufRecv: AsSyscallBuffer + ?Sized; + + fn read(&self, offset: usize, buffer: &mut Buf) -> Result + where + Buf: AsSyscallBuffer + ?Sized; + + fn respond(&self, buffer: &Buf) -> Result<()> + where + Buf: AsSyscallBuffer + ?Sized; + + /// Set (set=true) or clear (set=false) Signals::USER on the paired peer. + fn set_peer_user_signal(&self, set: bool) -> Result<()>; +} + +/// Transparent wrapper around a raw IPC handle. +#[repr(transparent)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct IpcHandle { + pub handle: u32, +} + +impl IpcHandle { + pub const fn new(handle: u32) -> Self { + Self { handle } + } +} + +#[cfg(target_os = "none")] +mod target; +#[cfg(target_os = "none")] +pub use target::{AsSyscallBuffer, Instant}; + +#[cfg(not(target_os = "none"))] +mod host; +#[cfg(not(target_os = "none"))] +pub use host::{AsSyscallBuffer, Instant}; diff --git a/util/ipc/target.rs b/util/ipc/target.rs new file mode 100644 index 00000000..d278d073 --- /dev/null +++ b/util/ipc/target.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use super::{IpcChannel, IpcHandle}; + +pub use userspace::buffer::AsSyscallBuffer; +pub use userspace::time::Instant; + +impl IpcChannel for IpcHandle { + fn transact( + &self, + send_data: &BufSend, + recv_data: &mut BufRecv, + deadline: Instant, + ) -> pw_status::Result + where + BufSend: AsSyscallBuffer + ?Sized, + BufRecv: AsSyscallBuffer + ?Sized, + { + userspace::syscall::channel_transact(self.handle, send_data, recv_data, deadline) + } + + fn read(&self, offset: usize, buffer: &mut Buf) -> pw_status::Result + where + Buf: AsSyscallBuffer + ?Sized, + { + userspace::syscall::channel_read(self.handle, offset, buffer) + } + + fn respond(&self, buffer: &Buf) -> pw_status::Result<()> + where + Buf: AsSyscallBuffer + ?Sized, + { + userspace::syscall::channel_respond(self.handle, buffer) + } + + fn set_peer_user_signal(&self, set: bool) -> pw_status::Result<()> { + userspace::syscall::object_set_peer_user_signal(self.handle, set) + } +}