Skip to content

Commit c2199e3

Browse files
ayodejiigeAyodeji Igejerrysxie
authored
Add non-blocking partition access via PartitionGuard (#793)
### Summary Introduces a PartitionGuard type to the partition-manager crate that provides both async (lock) and non-blocking (try_lock) access to partition storage, along with hardened arithmetic to prevent integer overflow. ## Motivation When a task panics while holding a partition's mutex, the lock remains held. If the panic handler itself needs to access the same partition (e.g., to persist crash logs or diagnostic data), calling `lock().await` would deadlock. The handler is waiting on a lock that will never be released. By exposing `try_lock()`, callers like panic handlers can attempt non-blocking access and gracefully handle failure instead of deadlocking the system. ## Changes - Added `PartitionGuard` struct with `lock()` (async) and `try_lock()` (non-blocking) constructors on Partition - Implemented `BlockDevice`, `ReadNorFlash`, and `NorFlash` traits for PartitionGuard - Re-exported `TryLockError` from embassy_sync for caller convenience - Hardened all partition offset/size arithmetic with checked operations to prevent integer overflow - Added unit tests covering guard-based read/write/erase, out-of-bounds access, overflow edge cases, and lock contention via `try_lock()` --------- Co-authored-by: Ayodeji Ige <ayodeji.ige@microsoft.com> Co-authored-by: Jerry Xie <139205137+jerrysxie@users.noreply.github.com>
1 parent 9cd6f9e commit c2199e3

7 files changed

Lines changed: 288 additions & 43 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

partition-manager/partition-manager/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ partition-manager-macros = { path = "../macros", features = [
2626
defmt = { workspace = true, optional = true }
2727

2828
[features]
29-
default = ["esa", "bdd", "macros", "defmt"]
29+
default = ["esa", "bdd", "macros"]
3030

3131
macros = ["dep:partition-manager-macros"]
3232

@@ -37,3 +37,4 @@ defmt = ["dep:defmt"]
3737

3838
[dev-dependencies]
3939
embassy-futures.workspace = true
40+
critical-section = { workspace = true, features = ["std"] }

partition-manager/partition-manager/src/ext/bdd.rs

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@ use aligned::Aligned;
44
use block_device_driver::BlockDevice;
55
use embassy_sync::blocking_mutex::raw::RawMutex;
66

7-
use crate::{Error, Partition, RO, RW};
7+
use crate::{Error, Partition, PartitionGuard, RO, RW};
88

9-
impl<F, MARKER, M: RawMutex> Partition<'_, F, MARKER, M> {
10-
/// Returns the block number on the parent storage medium, given a block size.
11-
///
12-
/// Will not be able to return a value of the partition is not aligned to a single block.
9+
impl<F, MARKER, M: RawMutex> PartitionGuard<'_, F, MARKER, M> {
1310
const fn start_block(&self, block_size: u32) -> Option<u32> {
1411
if self.offset % block_size != 0 {
1512
None
@@ -18,7 +15,6 @@ impl<F, MARKER, M: RawMutex> Partition<'_, F, MARKER, M> {
1815
}
1916
}
2017

21-
/// Check if data access for a block address and set of blocks lies completely within this partition.
2218
const fn check_access<const SIZE: usize>(
2319
&self,
2420
block_address: u32,
@@ -27,9 +23,21 @@ impl<F, MARKER, M: RawMutex> Partition<'_, F, MARKER, M> {
2723
where
2824
F: BlockDevice<SIZE>,
2925
{
30-
let offset = block_address * SIZE as u32;
31-
let size = (data.len() * SIZE) as u32;
32-
if !self.within_bounds(offset, size) {
26+
const { assert!(SIZE <= u32::MAX as usize) };
27+
28+
let Some(offset) = block_address.checked_mul(SIZE as u32) else {
29+
return Err(Error::OutOfBounds);
30+
};
31+
32+
if data.len() > u32::MAX as usize {
33+
return Err(Error::OutOfBounds);
34+
}
35+
36+
let Some(size) = (data.len() as u32).checked_mul(SIZE as u32) else {
37+
return Err(Error::OutOfBounds);
38+
};
39+
40+
if !self.within_bounds(offset, size as usize) {
3341
Err(Error::OutOfBounds)
3442
} else {
3543
Ok(())
@@ -41,6 +49,60 @@ impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for
4149
type Error = Error<F::Error>;
4250
type Align = F::Align;
4351

52+
async fn read(
53+
&mut self,
54+
block_address: u32,
55+
data: &mut [Aligned<Self::Align, [u8; SIZE]>],
56+
) -> Result<(), Self::Error> {
57+
let mut guard = self.lock().await;
58+
guard.read(block_address, data).await
59+
}
60+
61+
async fn write(
62+
&mut self,
63+
_block_address: u32,
64+
_data: &[Aligned<Self::Align, [u8; SIZE]>],
65+
) -> Result<(), Self::Error> {
66+
Err(Error::ReadOnly)
67+
}
68+
69+
async fn size(&mut self) -> Result<u64, Self::Error> {
70+
Ok(self.size as u64)
71+
}
72+
}
73+
74+
impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for Partition<'_, F, RW, M> {
75+
type Error = Error<F::Error>;
76+
type Align = F::Align;
77+
78+
async fn read(
79+
&mut self,
80+
block_address: u32,
81+
data: &mut [Aligned<Self::Align, [u8; SIZE]>],
82+
) -> Result<(), Self::Error> {
83+
let mut guard = self.lock().await;
84+
guard.read(block_address, data).await
85+
}
86+
87+
async fn write(
88+
&mut self,
89+
block_address: u32,
90+
data: &[Aligned<Self::Align, [u8; SIZE]>],
91+
) -> Result<(), Self::Error> {
92+
let mut guard = self.lock().await;
93+
guard.write(block_address, data).await
94+
}
95+
96+
async fn size(&mut self) -> Result<u64, Self::Error> {
97+
Ok(self.size as u64)
98+
}
99+
}
100+
101+
// PartitionGuard trait implementations
102+
impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for PartitionGuard<'_, F, RO, M> {
103+
type Error = Error<F::Error>;
104+
type Align = F::Align;
105+
44106
async fn read(
45107
&mut self,
46108
block_address: u32,
@@ -49,8 +111,10 @@ impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for
49111
self.check_access(block_address, data)?;
50112
let start_block = self.start_block(SIZE as u32).ok_or(Error::NotAligned)?;
51113

52-
let mut storage = self.storage.lock().await;
53-
Ok(storage.read(start_block + block_address, data).await?)
114+
self.guard
115+
.read(start_block.checked_add(block_address).ok_or(Error::OutOfBounds)?, data)
116+
.await
117+
.map_err(Error::Inner)
54118
}
55119

56120
async fn write(
@@ -66,7 +130,7 @@ impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for
66130
}
67131
}
68132

69-
impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for Partition<'_, F, RW, M> {
133+
impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for PartitionGuard<'_, F, RW, M> {
70134
type Error = Error<F::Error>;
71135
type Align = F::Align;
72136

@@ -75,7 +139,13 @@ impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for
75139
block_address: u32,
76140
data: &mut [Aligned<Self::Align, [u8; SIZE]>],
77141
) -> Result<(), Self::Error> {
78-
self.readonly().read(block_address, data).await
142+
self.check_access(block_address, data)?;
143+
let start_block = self.start_block(SIZE as u32).ok_or(Error::NotAligned)?;
144+
145+
self.guard
146+
.read(start_block.checked_add(block_address).ok_or(Error::OutOfBounds)?, data)
147+
.await
148+
.map_err(Error::Inner)
79149
}
80150

81151
async fn write(
@@ -86,8 +156,10 @@ impl<const SIZE: usize, F: BlockDevice<SIZE>, M: RawMutex> BlockDevice<SIZE> for
86156
self.check_access(block_address, data)?;
87157
let start_block = self.start_block(SIZE as u32).ok_or(Error::NotAligned)?;
88158

89-
let mut storage = self.storage.lock().await;
90-
Ok(storage.write(start_block + block_address, data).await?)
159+
self.guard
160+
.write(start_block.checked_add(block_address).ok_or(Error::OutOfBounds)?, data)
161+
.await
162+
.map_err(Error::Inner)
91163
}
92164

93165
async fn size(&mut self) -> Result<u64, Self::Error> {

partition-manager/partition-manager/src/ext/esa.rs

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Embedded Storage Async
22
3-
use crate::{Error, Partition, RO, RW};
3+
use crate::{Error, Partition, PartitionGuard, RO, RW};
44
use core::fmt::Debug;
55
use embassy_sync::blocking_mutex::raw::RawMutex;
66
use embedded_storage_async::nor_flash::{
@@ -26,12 +26,8 @@ impl<F: ReadNorFlash, M: RawMutex> ReadNorFlash for Partition<'_, F, RO, M> {
2626
const READ_SIZE: usize = F::READ_SIZE;
2727

2828
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
29-
if !self.within_bounds(offset, bytes.len() as u32) {
30-
return Err(Error::OutOfBounds);
31-
}
32-
33-
let mut storage = self.storage.lock().await;
34-
Ok(storage.read(offset + self.offset, bytes).await?)
29+
let mut guard = self.lock().await;
30+
guard.read(offset, bytes).await
3531
}
3632

3733
fn capacity(&self) -> usize {
@@ -43,7 +39,8 @@ impl<F: ReadNorFlash, M: RawMutex> ReadNorFlash for Partition<'_, F, RW, M> {
4339
const READ_SIZE: usize = F::READ_SIZE;
4440

4541
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
46-
self.readonly().read(offset, bytes).await
42+
let mut guard = self.lock().await;
43+
guard.read(offset, bytes).await
4744
}
4845

4946
fn capacity(&self) -> usize {
@@ -56,22 +53,69 @@ impl<F: NorFlash, M: RawMutex> NorFlash for Partition<'_, F, RW, M> {
5653
const ERASE_SIZE: usize = F::ERASE_SIZE;
5754

5855
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
59-
if !self.within_bounds(from, to.saturating_sub(from)) {
56+
let mut guard = self.lock().await;
57+
guard.erase(from, to).await
58+
}
59+
60+
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
61+
let mut guard = self.lock().await;
62+
guard.write(offset, bytes).await
63+
}
64+
}
65+
66+
impl<F: MultiwriteNorFlash, M: RawMutex> MultiwriteNorFlash for Partition<'_, F, RW, M> {}
67+
68+
impl<F: ReadNorFlash, MARKER, M: RawMutex> ErrorType for PartitionGuard<'_, F, MARKER, M> {
69+
type Error = Error<F::Error>;
70+
}
71+
72+
impl<F: ReadNorFlash, MARKER, M: RawMutex> ReadNorFlash for PartitionGuard<'_, F, MARKER, M> {
73+
const READ_SIZE: usize = F::READ_SIZE;
74+
75+
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
76+
if !self.within_bounds(offset, bytes.len()) {
77+
return Err(Error::OutOfBounds);
78+
}
79+
80+
self.guard
81+
.read(offset.checked_add(self.offset).ok_or(Error::OutOfBounds)?, bytes)
82+
.await
83+
.map_err(Error::Inner)
84+
}
85+
86+
fn capacity(&self) -> usize {
87+
self.size as usize
88+
}
89+
}
90+
91+
impl<F: NorFlash, M: RawMutex> NorFlash for PartitionGuard<'_, F, RW, M> {
92+
const WRITE_SIZE: usize = F::WRITE_SIZE;
93+
const ERASE_SIZE: usize = F::ERASE_SIZE;
94+
95+
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
96+
if !self.within_bounds(from, to.checked_sub(from).ok_or(Error::OutOfBounds)? as usize) {
6097
return Err(Error::OutOfBounds);
6198
}
6299

63-
let mut storage = self.storage.lock().await;
64-
Ok(storage.erase(from + self.offset, to + self.offset).await?)
100+
self.guard
101+
.erase(
102+
from.checked_add(self.offset).ok_or(Error::OutOfBounds)?,
103+
to.checked_add(self.offset).ok_or(Error::OutOfBounds)?,
104+
)
105+
.await
106+
.map_err(Error::Inner)
65107
}
66108

67109
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
68-
if !self.within_bounds(offset, bytes.len() as u32) {
110+
if !self.within_bounds(offset, bytes.len()) {
69111
return Err(Error::OutOfBounds);
70112
}
71113

72-
let mut storage = self.storage.lock().await;
73-
Ok(storage.write(offset + self.offset, bytes).await?)
114+
self.guard
115+
.write(offset.checked_add(self.offset).ok_or(Error::OutOfBounds)?, bytes)
116+
.await
117+
.map_err(Error::Inner)
74118
}
75119
}
76120

77-
impl<F: MultiwriteNorFlash, M: RawMutex> MultiwriteNorFlash for Partition<'_, F, RW, M> {}
121+
impl<F: MultiwriteNorFlash, M: RawMutex> MultiwriteNorFlash for PartitionGuard<'_, F, RW, M> {}

partition-manager/partition-manager/src/lib.rs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ pub use partition_manager_macros as macros;
77
use core::{fmt::Debug, marker::PhantomData};
88
use embassy_sync::{
99
blocking_mutex::raw::{NoopRawMutex, RawMutex},
10-
mutex::Mutex,
10+
mutex::{Mutex, MutexGuard},
1111
};
1212

13+
pub use embassy_sync::mutex::TryLockError;
14+
1315
mod ext;
1416

1517
#[cfg(test)]
@@ -43,18 +45,38 @@ impl<'a, F, MARKER, M: RawMutex> Partition<'a, F, MARKER, M> {
4345
_marker: PhantomData,
4446
}
4547
}
46-
}
4748

48-
impl<F, M: RawMutex> Partition<'_, F, RW, M> {
49-
/// Temporarily convert a reference to a writable partition into a read-only partition.
50-
pub const fn readonly(&mut self) -> Partition<'_, F, RO, M> {
51-
Partition {
52-
storage: self.storage,
49+
/// Lock the underlying storage and return a guard that allows direct operations.
50+
pub async fn lock(&self) -> PartitionGuard<'_, F, MARKER, M> {
51+
PartitionGuard {
52+
guard: self.storage.lock().await,
5353
offset: self.offset,
5454
size: self.size,
5555
_marker: PhantomData,
5656
}
5757
}
58+
59+
/// Attempt to lock the underlying storage without blocking.
60+
pub fn try_lock(&self) -> Result<PartitionGuard<'_, F, MARKER, M>, TryLockError> {
61+
Ok(PartitionGuard {
62+
guard: self.storage.try_lock()?,
63+
offset: self.offset,
64+
size: self.size,
65+
_marker: PhantomData,
66+
})
67+
}
68+
}
69+
70+
/// A guard that provides exclusive access to a partition's underlying storage.
71+
///
72+
/// Obtained via [`Partition::lock`] or [`Partition::try_lock`].
73+
/// The underlying mutex is held for the lifetime of this guard.
74+
#[allow(unused)]
75+
pub struct PartitionGuard<'a, F, MARKER, M: RawMutex = NoopRawMutex> {
76+
guard: MutexGuard<'a, M, F>,
77+
offset: u32,
78+
size: u32,
79+
_marker: PhantomData<MARKER>,
5880
}
5981

6082
/// A partition configuration definition.
@@ -89,11 +111,15 @@ impl<F, M: RawMutex> PartitionManager<F, M> {
89111
}
90112
}
91113

92-
impl<F, MARKER, M: RawMutex> Partition<'_, F, MARKER, M> {
114+
impl<F, MARKER, M: RawMutex> PartitionGuard<'_, F, MARKER, M> {
93115
/// Checks whether an address range lies within the partition.
94116
#[allow(unused)]
95-
const fn within_bounds(&self, offset: u32, size: u32) -> bool {
96-
if let Some(end) = offset.checked_add(size) {
117+
const fn within_bounds(&self, offset: u32, size: usize) -> bool {
118+
if size > u32::MAX as usize {
119+
return false;
120+
}
121+
122+
if let Some(end) = offset.checked_add(size as u32) {
97123
end <= self.size
98124
} else {
99125
false

0 commit comments

Comments
 (0)