diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00a2e81..32338ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: include: # Test MSRV - - rust: 1.50.0 + - rust: 1.60.0 TARGET: x86_64-unknown-linux-gnu # Test nightly but don't fail diff --git a/CHANGELOG.md b/CHANGELOG.md index 9225845..b68f9fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add `start()` and `end()` method to the `Region` trait. - Much faster `OverlapIterator`. +- Add `BlockDevice` trait, along with `BlockIdx` and `BlockCount` types. ## [0.3.1] - 2023-12-04 diff --git a/Cargo.toml b/Cargo.toml index c851083..e6ab6fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,11 @@ documentation = "https://docs.rs/embedded-storage" readme = "README.md" keywords = ["storage"] categories = ["embedded", "hardware-support", "no-std"] + +[dependencies] +aligned = "0.4.2" +defmt = { version = "0.3.8", optional = true } + +[features] +default = [] +defmt = ["dep:defmt"] diff --git a/README.md b/README.md index 993f50e..7212f44 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ These issues / PRs will be labeled as `proposal`s in the issue tracker. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.50.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.60.0 and up. It _might_ compile with older versions but that may change in any new patch release. ## License diff --git a/src/block.rs b/src/block.rs new file mode 100644 index 0000000..2d94334 --- /dev/null +++ b/src/block.rs @@ -0,0 +1,228 @@ +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +/// A device which can read and write whole numbers of blocks. +/// +/// Blocks are also referred to as sectors in some contexts. +pub trait BlockDevice { + /// The error type returned by methods on this trait. + type Error; + + /// How aligned do we need the blocks to be? + /// + /// See the [`aligned`] crate for more details. + type Alignment: aligned::Alignment; + + /// Returns the size of the device in blocks. + fn block_count(&self) -> Result; + + /// Creates some stack allocated empty blocks, with suitable alignment. + fn empty_blocks( + &self, + ) -> [aligned::Aligned; N] { + [aligned::Aligned([0u8; BLOCK_SIZE]); N] + } + + /// Reads some blocks from the device, starting at `first_block_index`. + /// + /// The buffer we read into must be suitably aligned. You can create a + /// buffer like: + /// + /// ```rust + /// # use embedded_storage::block::{BlockDevice, BlockIdx}; + /// # fn example(block_device: &mut T) -> Result<(), T::Error> where T: BlockDevice { + /// + /// let mut buffer = block_device.empty_blocks::<4>(); + /// block_device.read(&mut buffer[..], BlockIdx(0))?; + /// + /// # Ok(()) + /// # } + /// ``` + /// + /// You will get an error if you request more blocks than the block device + /// has (i.e. if `first_block_index + blocks.len()` is greater than the size + /// returned by `block_count`). + fn read( + &mut self, + blocks: &mut [aligned::Aligned], + first_block_index: BlockIdx, + ) -> Result<(), Self::Error>; + + /// Writes some number of blocks to the device, starting at + /// `first_block_index`. + /// + /// The buffer we write out must be suitably aligned. You can create a + /// buffer like: + /// + /// ```rust + /// # use embedded_storage::block::{BlockDevice, BlockIdx}; + /// # fn example(block_device: &mut T) -> Result<(), T::Error> where T: BlockDevice { + /// + /// let mut buffer = block_device.empty_blocks::<4>(); + /// for block in buffer.iter_mut() { + /// block.fill(0xCC); + /// } + /// block_device.write(&buffer[..], BlockIdx(0))?; + /// + /// # Ok(()) + /// # } + /// ``` + /// + /// You will get an error if you request more blocks than the block device + /// has (i.e. if first_block_index + blocks.len() is greater than the size + /// returned by block_count). + fn write( + &mut self, + blocks: &[aligned::Aligned], + first_block_index: BlockIdx, + ) -> Result<(), Self::Error>; +} + +/// The linear numeric address of a block (or sector). +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BlockIdx(pub u64); + +impl BlockIdx { + /// Creates an iterator from the current `BlockIdx` through the given number of blocks. + pub fn range(self, num: BlockCount) -> BlockIter { + BlockIter::new(self, self + BlockCount(num.0)) + } +} + +impl From for u64 { + fn from(value: BlockIdx) -> Self { + value.0.into() + } +} + +impl Add for BlockIdx { + type Output = BlockIdx; + + fn add(self, rhs: BlockCount) -> BlockIdx { + BlockIdx(self.0 + rhs.0) + } +} + +impl AddAssign for BlockIdx { + fn add_assign(&mut self, rhs: BlockCount) { + self.0 += rhs.0 + } +} + +impl Sub for BlockIdx { + type Output = BlockIdx; + + fn sub(self, rhs: BlockCount) -> BlockIdx { + BlockIdx(self.0 - rhs.0) + } +} + +impl SubAssign for BlockIdx { + fn sub_assign(&mut self, rhs: BlockCount) { + self.0 -= rhs.0 + } +} + +/// A number of blocks (or sectors). +/// +/// This may be added to a [`BlockIdx`] to get another `BlockIdx`. +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BlockCount(pub u64); + +impl Add for BlockCount { + type Output = BlockCount; + + fn add(self, rhs: BlockCount) -> BlockCount { + BlockCount(self.0 + rhs.0) + } +} + +impl AddAssign for BlockCount { + fn add_assign(&mut self, rhs: BlockCount) { + self.0 += rhs.0 + } +} + +impl Sub for BlockCount { + type Output = BlockCount; + + fn sub(self, rhs: BlockCount) -> BlockCount { + BlockCount(self.0 - rhs.0) + } +} + +impl SubAssign for BlockCount { + fn sub_assign(&mut self, rhs: BlockCount) { + self.0 -= rhs.0 + } +} + +/// An iterator returned from `Block::range`. +pub struct BlockIter { + inclusive_end: BlockIdx, + current: BlockIdx, +} + +impl BlockIter { + /// Creates a new `BlockIter`, from the given start block, through (and including) the given end + /// block. + pub const fn new(start: BlockIdx, inclusive_end: BlockIdx) -> BlockIter { + BlockIter { + inclusive_end, + current: start, + } + } +} + +impl Iterator for BlockIter { + type Item = BlockIdx; + fn next(&mut self) -> Option { + if self.current.0 <= self.inclusive_end.0 { + let this = self.current; + self.current += BlockCount(1); + Some(this) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn block_idx_addition() { + let a = BlockIdx(100); + let len = BlockCount(50); + let b = a + len; + assert_eq!(b, BlockIdx(150)); + } + + #[test] + fn block_idx_subtraction() { + let a = BlockIdx(100); + let len = BlockCount(50); + let b = a - len; + assert_eq!(b, BlockIdx(50)); + } + + #[test] + fn block_iter() { + let mut block_iter = BlockIter::new(BlockIdx(10), BlockIdx(12)); + let expected = [ + Some(BlockIdx(10)), + Some(BlockIdx(11)), + Some(BlockIdx(12)), + None, + ]; + let actual = [ + block_iter.next(), + block_iter.next(), + block_iter.next(), + block_iter.next(), + ]; + assert_eq!(actual, expected); + } +} diff --git a/src/lib.rs b/src/lib.rs index 3070723..851eff6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ #![deny(missing_docs)] #![deny(unsafe_code)] +/// Types and traits for block devices. +pub mod block; /// Currently contains [`OverlapIterator`] pub mod iter; /// Technology specific traits for NOR Flashes