capsules_extra/isolated_nonvolatile_storage_driver.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2025.
4
5//! This provides userspace access to nonvolatile storage.
6//!
7//! This driver provides isolation between individual userland applications.
8//! Each application only has access to its region of nonvolatile memory and
9//! cannot read/write to nonvolatile memory of other applications.
10//!
11//! Each app is assigned a fixed amount of nonvolatile memory. This amount is
12//! set at compile time.
13//!
14//! ## Storage Layout
15//!
16//! Example nonvolatile storage layout (note that `|` indicates bitwise
17//! concatenation):
18//!
19//! ```text
20//! ╒════════ ← Start of nonvolatile region
21//! ├──────── ← Start of App 1's region header
22//! │ Region version number (8 bits) | Region length (24 bits)
23//! │ App 1's ShortID (u32)
24//! │ XOR of previous two u32 fields (u32)
25//! ├──────── ← Start of App 1's Region ═╗
26//! │ ║
27//! │
28//! │ region 1
29//! │ length
30//! │
31//! │ ║
32//! │ ═╝
33//! ├──────── ← Start of App 2's region header
34//! │ Region version number (8 bits) | Region length (24 bits)
35//! │ App 2's ShortID (u32)
36//! │ XOR of previous two u32 fields (u32)
37//! ├──────── ← Start of App 2's Region ═╗
38//! │ ║
39//! │
40//! │
41//! │ region 2
42//! │ length
43//! │
44//! │
45//! │ ║
46//! ... ═╝
47//! ╘════════ ← End of userspace region
48//! ```
49//!
50//! ## Storage Initialization
51//!
52//! This capsule caches the location of an application's storage region in
53//! grant. This cached location is set on the first usage of this capsule.
54//!
55//! Here is a general high-level overview of what happens when an app makes its
56//! first syscall:
57//! 1. App engages with the capsule by making any syscall.
58//! 2. Capsule searches through storage to see if that app has an existing
59//! region.
60//! 3. a. If the capsule finds a matching region:
61//! - Cache the app's region information in its grant.
62//! b. If the capsule DOESN'T find a matching region:
63//! - Allocate a new region for that app.
64//! - Erase the region's usable area.
65//! 4. Handle the syscall that the app originally made.
66//! 5. When the syscall finishes, notify the app via upcall.
67//!
68//! ## Example Software Stack
69//!
70//! Here is a diagram of the expected stack with this capsule: Boxes are
71//! components and between the boxes are the traits that are the interfaces
72//! between components. This capsule only provides a userspace interface.
73//!
74//! ```text
75//! +------------------------------------------------------------------------+
76//! | |
77//! | userspace |
78//! | |
79//! +------------------------------------------------------------------------+
80//! kernel::Driver
81//! +------------------------------------------------------------------------+
82//! | |
83//! | isolated_nonvolatile_storage_driver::IsolatedNonvolatileStorage (this) |
84//! | |
85//! +------------------------------------------------------------------------+
86//! hil::nonvolatile_storage::NonvolatileStorage
87//! +------------------------------------------------------------------------+
88//! | |
89//! | Physical nonvolatile storage driver |
90//! | |
91//! +------------------------------------------------------------------------+
92//! ```
93//!
94
95use core::cmp;
96
97use kernel::errorcode::into_statuscode;
98use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
99use kernel::hil;
100use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer};
101use kernel::syscall::{CommandReturn, SyscallDriver};
102use kernel::utilities::cells::{OptionalCell, TakeCell};
103use kernel::utilities::copy_slice::CopyOrErr;
104use kernel::{ErrorCode, ProcessId};
105
106use capsules_core::driver;
107
108pub const DRIVER_NUM: usize = driver::NUM::IsolatedNvmStorage as usize;
109
110/// Recommended size for the buffer provided to this capsule.
111///
112/// This is enough space for a buffer to be used for reading/writing userspace
113/// data.
114pub const BUF_LEN: usize = 512;
115
116/// IDs for subscribed upcalls.
117mod upcall {
118 /// Get storage size done callback.
119 pub const GET_SIZE_DONE: usize = 0;
120 /// Read done callback.
121 pub const READ_DONE: usize = 1;
122 /// Write done callback.
123 pub const WRITE_DONE: usize = 2;
124 /// Number of upcalls.
125 pub const COUNT: u8 = 3;
126}
127
128/// Ids for read-only allow buffers
129mod ro_allow {
130 /// Setup a buffer to write bytes to the nonvolatile storage.
131 pub const WRITE: usize = 0;
132 /// The number of allow buffers the kernel stores for this grant
133 pub const COUNT: u8 = 1;
134}
135
136/// Ids for read-write allow buffers
137mod rw_allow {
138 /// Setup a buffer to read from the nonvolatile storage into.
139 pub const READ: usize = 0;
140 /// The number of allow buffers the kernel stores for this grant
141 pub const COUNT: u8 = 1;
142}
143
144#[derive(Clone, Copy, PartialEq, Debug)]
145#[repr(u8)]
146enum HeaderVersion {
147 V1 = 0x01,
148}
149
150// Current header version to allocate new regions with.
151const CURRENT_HEADER_VERSION: HeaderVersion = HeaderVersion::V1;
152
153/// Describes a region of nonvolatile memory that is assigned to a certain app.
154#[derive(Clone, Copy, Debug, PartialEq)]
155pub struct AppRegion {
156 /// The version is based on the capsule version and layout format in use
157 /// when the region was created. This is set to a fixed value for all new
158 /// regions. An existing region may have been created with a newer or
159 /// earlier version of this capsule and therefore might have a different
160 /// version than what we currently initialize new regions with.
161 version: HeaderVersion,
162 /// Absolute address to describe where an app's nonvolatile region starts.
163 /// Note that this is the address FOLLOWING the region's header.
164 absolute_address: usize,
165 /// How many bytes allocated to a certain app. Note that this describes the
166 /// length of the usable storage region and does not include the region's
167 /// header.
168 length: usize,
169}
170
171// Metadata to be written before every app's region to describe the owner and
172// size of the region.
173#[derive(Clone, Copy, Debug)]
174struct AppRegionHeader {
175 /// An 8 bit version number concatenated with a 24 bit length value.
176 version_and_length: u32,
177 /// Unique per-app identifier. This comes from the Fixed variant of the
178 /// ShortID type.
179 shortid: u32,
180 /// xor between `version_and_length` and `shortid` fields. This serves as a
181 /// checksum.
182 xor: u32,
183}
184/// The size of the `AppRegionHeader` stored in the nonvolatile storage.
185const REGION_HEADER_LEN: usize = 3 * core::mem::size_of::<u32>();
186
187impl AppRegionHeader {
188 fn new(version: HeaderVersion, shortid: u32, length: usize) -> Option<Self> {
189 // check that length will fit in 3 bytes
190 if length > (2 << 23) {
191 return None;
192 }
193
194 let version_and_length = ((version as u8 as u32) << 24) | length as u32;
195
196 let xor = version_and_length ^ shortid;
197
198 Some(AppRegionHeader {
199 version_and_length,
200 shortid,
201 xor,
202 })
203 }
204
205 fn from_bytes(bytes: [u8; REGION_HEADER_LEN]) -> Option<Self> {
206 // first 4 bytes are split between a 8 bit version and 24 bit length
207 let version = bytes[0];
208 let length_slice = &bytes[1..4];
209 let version_and_length_slice = [version, length_slice[0], length_slice[1], length_slice[2]];
210 let version_and_length = u32::from_le_bytes(version_and_length_slice);
211
212 let shortid_slice = bytes[4..8].try_into().ok()?;
213 let shortid = u32::from_le_bytes(shortid_slice);
214
215 let xor_slice = bytes[8..12].try_into().ok()?;
216 let xor = u32::from_le_bytes(xor_slice);
217
218 Some(AppRegionHeader {
219 version_and_length,
220 shortid,
221 xor,
222 })
223 }
224
225 fn to_bytes(self) -> [u8; REGION_HEADER_LEN] {
226 let mut header_slice = [0; REGION_HEADER_LEN];
227
228 // copy version and length
229 let version_and_length_slice = u32::to_le_bytes(self.version_and_length);
230 let version_and_length_start_idx = 0;
231 let version_and_length_end_idx = version_and_length_slice.len();
232 header_slice[version_and_length_start_idx..version_and_length_end_idx]
233 .copy_from_slice(&version_and_length_slice);
234
235 // copy shortid
236 let shortid_slice = u32::to_le_bytes(self.shortid);
237 let shortid_start_idx = version_and_length_end_idx;
238 let shortid_end_idx = shortid_start_idx + shortid_slice.len();
239 header_slice[shortid_start_idx..shortid_end_idx].copy_from_slice(&shortid_slice);
240
241 // copy version and length
242 let xor_slice = u32::to_le_bytes(self.xor);
243 let xor_start_idx = shortid_end_idx;
244 let xor_end_idx = xor_start_idx + xor_slice.len();
245 header_slice[xor_start_idx..xor_end_idx].copy_from_slice(&xor_slice);
246
247 header_slice
248 }
249
250 fn is_valid(&self) -> bool {
251 self.version().is_some() && self.xor == (self.version_and_length ^ self.shortid)
252 }
253
254 fn version(&self) -> Option<HeaderVersion> {
255 // Need to do this since we can't pattern match against a method call.
256 const HEADER_V1: u8 = HeaderVersion::V1 as u8;
257
258 // Extract the 8 most significant bits from the concatenated version and
259 // length.
260 match (self.version_and_length >> 24) as u8 {
261 HEADER_V1 => Some(HeaderVersion::V1),
262 _ => None,
263 }
264 }
265
266 fn length(&self) -> u32 {
267 // Extract the 24 least significant bits from the concatenated version
268 // and length.
269 self.version_and_length & 0x00ffffff
270 }
271}
272
273/// Operation referencing a particular region.
274#[derive(Clone, Copy, PartialEq, Debug)]
275pub enum ManagerTask {
276 /// Read the contents of the header in the region. The `usize` is the
277 /// address of the start of the header.
278 DiscoverRegions(usize),
279 /// Write a valid header to the storage.
280 WriteHeader(ProcessId, AppRegion),
281 /// Erase the contents of a region. This supports using multiple nonvolatile
282 /// storage operations to complete the entire erase.
283 EraseRegion {
284 processid: ProcessId,
285 next_erase_start: usize,
286 remaining_bytes: usize,
287 },
288}
289
290/// What is currently using the underlying nonvolatile storage driver.
291#[derive(Clone, Copy, Debug)]
292pub enum User {
293 /// The operation is from a userspace process.
294 App { processid: ProcessId },
295 /// The operation is from this capsule.
296 RegionManager(ManagerTask),
297}
298
299/// The operation the process requested.
300#[derive(Clone, Copy, Debug)]
301pub enum NvmCommand {
302 GetSize,
303 Read { offset: usize },
304 Write { offset: usize },
305}
306
307impl NvmCommand {
308 fn offset(&self) -> usize {
309 match self {
310 NvmCommand::Read { offset } => *offset,
311 NvmCommand::Write { offset } => *offset,
312 NvmCommand::GetSize => 0,
313 }
314 }
315
316 fn upcall(&self) -> usize {
317 match self {
318 Self::GetSize => upcall::GET_SIZE_DONE,
319 Self::Write { offset: _ } => upcall::WRITE_DONE,
320 Self::Read { offset: _ } => upcall::READ_DONE,
321 }
322 }
323}
324
325/// State stored in the grant region on behalf of each app.
326#[derive(Default)]
327pub struct App {
328 /// Describe the location and size of an app's region (if it has been
329 /// initialized).
330 region: Option<AppRegion>,
331 /// Operation that will be handled once init sequence is complete.
332 pending_operation: Option<NvmCommand>,
333}
334
335/// Helper function to convert create a full, single usize value from two 32-bit
336/// values stored in usizes.
337///
338/// In C this would look like:
339///
340/// ```c
341/// size_t v = (hi << 32) | (uint32_t) lo;
342/// ```
343///
344/// This is useful when passing a machine-sized value (i.e. a `size_t`) via the
345/// system call interface in two 32-bit usize values. On a 32-bit machine this
346/// essentially has no effect; the full value is stored in the `lo` usize. On a
347/// 64-bit machine, this creates a usize by concatenating the hi and lo 32-bit
348/// values.
349///
350/// TODO
351/// ----
352///
353/// This can be more succinctly implemented using
354/// [`unbounded_shl()`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.unbounded_shl).
355/// However, that method is currently a nightly-only feature.
356#[inline]
357pub const fn usize32s_to_usize(lo: usize, hi: usize) -> usize {
358 if usize::BITS <= 32 {
359 // Just return the lo value since it has the bits we need.
360 lo
361 } else {
362 // Create a 64-bit value.
363 (lo & 0xFFFFFFFF) | (hi << 32)
364 }
365}
366
367pub struct IsolatedNonvolatileStorage<'a, const APP_REGION_SIZE: usize> {
368 /// The underlying physical storage device.
369 driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'a>,
370 /// Per-app state.
371 apps: Grant<
372 App,
373 UpcallCount<{ upcall::COUNT }>,
374 AllowRoCount<{ ro_allow::COUNT }>,
375 AllowRwCount<{ rw_allow::COUNT }>,
376 >,
377
378 /// Internal buffer for copying appslices into.
379 buffer: TakeCell<'static, [u8]>,
380 /// What issued the currently executing call. This can be an app or the
381 /// kernel.
382 current_user: OptionalCell<User>,
383
384 /// The first byte that is accessible from userspace.
385 userspace_start_address: usize,
386 /// How many bytes allocated to userspace.
387 userspace_length: usize,
388
389 /// Absolute address of the header of the next region of userspace that's
390 /// not allocated to an app yet. Each time an app uses this capsule, a new
391 /// region of storage will be handed out and this address will point to the
392 /// header of a new unallocated region.
393 next_unallocated_region_header_address: OptionalCell<usize>,
394}
395
396impl<'a, const APP_REGION_SIZE: usize> IsolatedNonvolatileStorage<'a, APP_REGION_SIZE> {
397 pub fn new(
398 driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'a>,
399 grant: Grant<
400 App,
401 UpcallCount<{ upcall::COUNT }>,
402 AllowRoCount<{ ro_allow::COUNT }>,
403 AllowRwCount<{ rw_allow::COUNT }>,
404 >,
405 userspace_start_address: usize,
406 userspace_length: usize,
407 buffer: &'static mut [u8],
408 ) -> Self {
409 Self {
410 driver,
411 apps: grant,
412 buffer: TakeCell::new(buffer),
413 current_user: OptionalCell::empty(),
414 userspace_start_address,
415 userspace_length,
416 next_unallocated_region_header_address: OptionalCell::empty(),
417 }
418 }
419
420 // Start reading app region headers.
421 fn start_region_traversal(&self) -> Result<(), ErrorCode> {
422 if self.current_user.is_some() {
423 // Can't traverse the regions right now because the underlying
424 // driver is already in use.
425 return Err(ErrorCode::BUSY);
426 }
427
428 let res = self.read_region_header(self.userspace_start_address);
429 match res {
430 Ok(()) => {
431 // Mark that we started the discover operation.
432 self.current_user
433 .set(User::RegionManager(ManagerTask::DiscoverRegions(
434 self.userspace_start_address,
435 )));
436 Ok(())
437 }
438 Err(e) => {
439 // We did not successfully start the discover, return the error.
440 Err(e)
441 }
442 }
443 }
444
445 fn allocate_app_region(&self, processid: ProcessId) -> Result<(), ErrorCode> {
446 // Can't allocate a region if we haven't previously traversed existing
447 // regions and found where they stop.
448 let new_header_addr = self
449 .next_unallocated_region_header_address
450 .get()
451 .ok_or(ErrorCode::FAIL)?;
452
453 // Get an app's write_id (same as ShortID) for saving to region header.
454 // Note that if an app doesn't have the valid permissions, it will be
455 // unable to create storage regions.
456 let write_id = processid
457 .get_storage_permissions()
458 .ok_or(ErrorCode::NOSUPPORT)?
459 .get_write_id()
460 .ok_or(ErrorCode::NOSUPPORT)?;
461
462 let region = AppRegion {
463 version: CURRENT_HEADER_VERSION,
464 // Have this region start where all the existing regions end.
465 // Note that the app's actual region starts after the region header.
466 absolute_address: new_header_addr + REGION_HEADER_LEN,
467 length: APP_REGION_SIZE,
468 };
469
470 // fail if new region is outside userspace area
471 if region.absolute_address > self.userspace_start_address + self.userspace_length
472 || region.absolute_address + region.length
473 > self.userspace_start_address + self.userspace_length
474 {
475 return Err(ErrorCode::NOMEM);
476 }
477
478 let Some(header) = AppRegionHeader::new(region.version, write_id, region.length) else {
479 return Err(ErrorCode::FAIL);
480 };
481
482 // Write this new region header to the end of the existing regions.
483 let res = self.write_region_header(®ion, &header, new_header_addr);
484 match res {
485 Ok(()) => {
486 // Mark that we started the initialize region task.
487 self.current_user
488 .set(User::RegionManager(ManagerTask::WriteHeader(
489 processid, region,
490 )));
491 Ok(())
492 }
493 Err(e) => {
494 // We did not successfully start the region initialization,
495 // return the error.
496 Err(e)
497 }
498 }
499 }
500
501 // Read the header of an app's storage region. The region_header_address
502 // argument describes the start of the **header** and not the usable region
503 // itself.
504 fn read_region_header(&self, region_header_address: usize) -> Result<(), ErrorCode> {
505 self.check_header_access(region_header_address, APP_REGION_SIZE)?;
506
507 self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
508 self.driver
509 .read(buffer, region_header_address, REGION_HEADER_LEN)
510 })
511 }
512
513 // Write the header of an app's storage region. The region_header_address
514 // argument describes the start of the **header** and not the usable region
515 // itself.
516 fn write_region_header(
517 &self,
518 region: &AppRegion,
519 region_header: &AppRegionHeader,
520 region_header_address: usize,
521 ) -> Result<(), ErrorCode> {
522 self.check_header_access(region.absolute_address, region.length)?;
523
524 let header_slice = region_header.to_bytes();
525
526 self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
527 let _ = buffer
528 .get_mut(0..REGION_HEADER_LEN)
529 .ok_or(ErrorCode::NOMEM)?
530 .copy_from_slice_or_err(
531 header_slice
532 .get(0..REGION_HEADER_LEN)
533 .ok_or(ErrorCode::NOMEM)?,
534 );
535
536 self.driver
537 .write(buffer, region_header_address, REGION_HEADER_LEN)
538 })
539 }
540
541 fn erase_region_content(
542 &self,
543 offset: usize,
544 length: usize,
545 ) -> Result<(usize, usize), ErrorCode> {
546 self.check_header_access(offset, length)?;
547
548 self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
549 let active_len = cmp::min(length, buffer.len());
550
551 // Clear the erase buffer in case there was any data
552 // remaining from a previous operation.
553 for c in buffer.iter_mut() {
554 *c = 0xFF;
555 }
556
557 // how many more bytes to erase after this operation
558 let remaining_len = if length > buffer.len() {
559 length - buffer.len()
560 } else {
561 0
562 };
563
564 let next_erase_start = offset + active_len;
565
566 self.driver
567 .write(buffer, offset, active_len)
568 .and(Ok((next_erase_start, remaining_len)))
569 })
570 }
571
572 // Returns `Ok()` with the address of the next header to be read if a new
573 // header read was started.
574 fn header_read_done(&self, region_header_address: usize) -> Result<Option<usize>, ErrorCode> {
575 // Cases when a header read completes:
576 // 1. Read a valid header
577 // - The valid header belongs to a Tock app (might not be currently
578 // running).
579 // - Search for the owner of the region within the apps.
580 // - Find the owner of the region that has a matching shortid (from
581 // the header).
582 // - Then, startup another read operation to read the header of the
583 // next storage region.
584 // 2. Read an invalid header
585 // - We've reached the end of all previously allocated regions.
586 // - Allocate new app region here.
587
588 let header = self.buffer.map_or(Err(ErrorCode::NOMEM), |buffer| {
589 // Need to copy over bytes since we need to convert a &[u8] into a
590 // [u8; REGION_HEADER_LEN]. The &[u8] refers to a slice of size
591 // BUF_LEN (which could be different than REGION_HEADER_LEN). Using
592 // buffer.try_into() will fail at runtime since the underlying
593 // buffer is not the same length as what we're trying to convert
594 // into.
595 let mut header_buffer = [0; REGION_HEADER_LEN];
596 header_buffer
597 .copy_from_slice_or_err(&buffer[..REGION_HEADER_LEN])
598 .or(Err(ErrorCode::FAIL))?;
599
600 // reconstruct header from bytes we just read
601 AppRegionHeader::from_bytes(header_buffer).ok_or(ErrorCode::FAIL)
602 })?;
603
604 if header.is_valid() {
605 // Find the app with the corresponding shortid.
606 for app in self.apps.iter() {
607 let processid = app.processid();
608 // Skip an app if it doesn't have the proper storage
609 // permissions.
610 let write_id = match processid.get_storage_permissions() {
611 Some(perms) => match perms.get_write_id() {
612 Some(write_id) => write_id,
613 None => continue,
614 },
615 None => continue,
616 };
617 if write_id == header.shortid {
618 app.enter(|app, _kernel_data| {
619 if app.region.is_none() {
620 let version = header.version().ok_or(ErrorCode::FAIL)?;
621 let region = AppRegion {
622 version,
623 // The app's actual region starts after the
624 // region header.
625 absolute_address: region_header_address + REGION_HEADER_LEN,
626 length: header.length() as usize,
627 };
628 app.region.replace(region);
629 }
630 Ok::<(), ErrorCode>(())
631 })?;
632 break;
633 }
634 }
635
636 let next_header_address =
637 region_header_address + REGION_HEADER_LEN + header.length() as usize;
638 // Kick off another read for the next region.
639 self.read_region_header(next_header_address)
640 .and(Ok(Some(next_header_address)))
641 } else {
642 // This is the end of the region traversal. If a header is invalid,
643 // we've reached the end of all previously allocated regions.
644
645 // Save this region header address so that we can allocate new
646 // regions here later.
647 self.next_unallocated_region_header_address
648 .set(region_header_address);
649
650 Ok(None)
651 }
652 }
653
654 fn check_userspace_perms(
655 &self,
656 processid: ProcessId,
657 command: NvmCommand,
658 ) -> Result<(), ErrorCode> {
659 let perms = processid
660 .get_storage_permissions()
661 .ok_or(ErrorCode::NOSUPPORT)?;
662 let write_id = perms.get_write_id().ok_or(ErrorCode::NOSUPPORT)?;
663 match command {
664 NvmCommand::Read { offset: _ } => perms
665 .check_read_permission(write_id)
666 .then_some(())
667 .ok_or(ErrorCode::NOSUPPORT),
668 NvmCommand::Write { offset: _ } => perms
669 .check_modify_permission(write_id)
670 .then_some(())
671 .ok_or(ErrorCode::NOSUPPORT),
672 NvmCommand::GetSize => {
673 // If we have a `write_id` then we can return the size.
674 Ok(())
675 }
676 }
677 }
678
679 fn check_userspace_access(
680 &self,
681 offset: usize,
682 length: usize,
683 region: &AppRegion,
684 ) -> Result<(), ErrorCode> {
685 // Check that access is within this app's isolated nonvolatile region.
686 // This is to prevent an app from reading/writing to another app's
687 // nonvolatile storage.
688
689 if offset >= region.length || length > region.length || offset + length > region.length {
690 return Err(ErrorCode::INVAL);
691 }
692
693 Ok(())
694 }
695
696 fn check_header_access(&self, offset: usize, length: usize) -> Result<(), ErrorCode> {
697 // Check that we're within the entire userspace region.
698 if offset < self.userspace_start_address
699 || offset >= self.userspace_start_address + self.userspace_length
700 || length > self.userspace_length
701 || offset + length >= self.userspace_start_address + self.userspace_length
702 {
703 return Err(ErrorCode::INVAL);
704 }
705
706 Ok(())
707 }
708
709 // Check so see if we are doing something. If not, go ahead and do this
710 // command. If so, this is queued and will be run when the pending command
711 // completes.
712 fn enqueue_userspace_command(
713 &self,
714 command: NvmCommand,
715 processid: ProcessId,
716 ) -> Result<(), ErrorCode> {
717 self.check_userspace_perms(processid, command)?;
718
719 self.apps
720 .enter(processid, |app, _kernel_data| {
721 if app.pending_operation.is_some() {
722 return Err(ErrorCode::BUSY);
723 }
724 app.pending_operation = Some(command);
725 Ok(())
726 })
727 .unwrap_or_else(|err| Err(err.into()))?;
728
729 self.check_queue();
730 Ok(())
731 }
732
733 fn check_queue(&self) {
734 if self.current_user.is_some() {
735 // If the driver is busy we can't start a new operation and do not
736 // need to check the queue.
737 return;
738 }
739
740 // If this is none, we haven't traversed the existing regions yet.
741 if self.next_unallocated_region_header_address.is_none() {
742 match self.start_region_traversal() {
743 Ok(()) => {
744 // We started an operation so we can return and let that
745 // operation finish.
746 return;
747 }
748 Err(_e) => {
749 // We did not start the traversal which is a problem. This
750 // shouldn't happen, but if it does then we could overwrite
751 // existing regions.
752 return;
753 }
754 }
755 }
756
757 // Iterate apps and run an operation if one is pending.
758 for app in self.apps.iter() {
759 let processid = app.processid();
760 let started = app.enter(|app, kernel_data| {
761 match app.pending_operation {
762 Some(nvm_command) => {
763 if app.region.is_none() {
764 // This app needs its region allocated.
765 self.allocate_app_region(processid).is_ok()
766 } else {
767 let res = self.handle_syscall(nvm_command, processid, app, kernel_data);
768 match res {
769 Ok(started_operation) => started_operation,
770 Err(e) => {
771 app.pending_operation = None;
772 let _ = kernel_data.schedule_upcall(
773 nvm_command.upcall(),
774 (into_statuscode(Err(e)), 0, 0),
775 );
776
777 false
778 }
779 }
780 }
781 }
782 None => false,
783 }
784 });
785 if started {
786 break;
787 }
788 }
789 }
790
791 fn handle_syscall(
792 &self,
793 command: NvmCommand,
794 processid: ProcessId,
795 app: &mut App,
796 kernel_data: &kernel::grant::GrantKernelData,
797 ) -> Result<bool, ErrorCode> {
798 match command {
799 NvmCommand::GetSize => {
800 match app.region {
801 Some(region) => {
802 // clear pending syscall
803 app.pending_operation = None;
804 // signal app with the result
805 let _ = kernel_data.schedule_upcall(
806 upcall::GET_SIZE_DONE,
807 (into_statuscode(Ok(())), region.length, 0),
808 );
809 Ok(false)
810 }
811 None => Err(ErrorCode::NOMEM),
812 }
813 }
814
815 NvmCommand::Read { offset: _ } | NvmCommand::Write { offset: _ } => {
816 // Get the length of the correct allowed buffer.
817 let allow_buf_len = match command {
818 NvmCommand::Read { offset: _ } => kernel_data
819 .get_readwrite_processbuffer(rw_allow::READ)
820 .map_or(0, |read| read.len()),
821 NvmCommand::Write { offset: _ } => kernel_data
822 .get_readonly_processbuffer(ro_allow::WRITE)
823 .map_or(0, |read| read.len()),
824 NvmCommand::GetSize => 0,
825 };
826
827 // Check that the matching allowed buffer exists.
828 if allow_buf_len == 0 {
829 return Err(ErrorCode::RESERVE);
830 }
831
832 // Fail if the app doesn't have a region assigned to it.
833 let Some(app_region) = &app.region else {
834 return Err(ErrorCode::NOMEM);
835 };
836
837 let command_offset = command.offset();
838
839 self.check_userspace_access(command_offset, allow_buf_len, app_region)?;
840
841 // Need to copy bytes if this is a write!
842 if let NvmCommand::Write { offset: _ } = command {
843 let _ = kernel_data
844 .get_readonly_processbuffer(ro_allow::WRITE)
845 .and_then(|write| {
846 write.enter(|app_buffer| {
847 self.buffer.map(|kernel_buffer| {
848 // Check that the internal buffer and
849 // the buffer that was allowed are long
850 // enough.
851 let write_len = cmp::min(allow_buf_len, kernel_buffer.len());
852
853 let d = &app_buffer[0..write_len];
854 for (i, c) in kernel_buffer[0..write_len].iter_mut().enumerate()
855 {
856 *c = d[i].get();
857 }
858 });
859 })
860 });
861 }
862
863 // Calculate where we want to actually read from in the
864 // physical storage. Note that the offset for this
865 // command is with respect to the app's region address
866 // space. This means that userspace accesses start at 0
867 // which is the start of the app's region.
868 let physical_address = app_region.absolute_address + command_offset;
869
870 let res = self
871 .buffer
872 .take()
873 .map_or(Err(ErrorCode::RESERVE), |buffer| {
874 // Check that the internal buffer and the buffer that was
875 // allowed are long enough.
876 let active_len_buf = cmp::min(allow_buf_len, buffer.len());
877
878 match command {
879 NvmCommand::Read { offset: _ } => self
880 .driver
881 .read(buffer, physical_address, active_len_buf)
882 .or(Err(ErrorCode::FAIL)),
883 NvmCommand::Write { offset: _ } => self
884 .driver
885 .write(buffer, physical_address, active_len_buf)
886 .or(Err(ErrorCode::FAIL)),
887 NvmCommand::GetSize => Err(ErrorCode::FAIL),
888 }
889 });
890 match res {
891 Ok(()) => {
892 self.current_user.set(User::App { processid });
893 Ok(true)
894 }
895 Err(e) => Err(e),
896 }
897 }
898 }
899 }
900}
901
902/// This is the callback client for the underlying physical storage driver.
903impl<const APP_REGION_SIZE: usize> hil::nonvolatile_storage::NonvolatileStorageClient
904 for IsolatedNonvolatileStorage<'_, APP_REGION_SIZE>
905{
906 fn read_done(&self, buffer: &'static mut [u8], length: usize) {
907 // Switch on which user of this capsule generated this callback.
908 self.current_user.take().map(|user| {
909 match user {
910 User::RegionManager(state) => {
911 self.buffer.replace(buffer);
912 if let ManagerTask::DiscoverRegions(address) = state {
913 let res = self.header_read_done(address);
914 match res {
915 Ok(addr) => match addr {
916 Some(next_header_address) => {
917 self.current_user.set(User::RegionManager(
918 ManagerTask::DiscoverRegions(next_header_address),
919 ));
920 }
921 None => {
922 // We finished the scan of existing
923 // regions. Now we can check the queue
924 // to see if there is any work to be
925 // done.
926 self.check_queue();
927 }
928 },
929 Err(_e) => {
930 // Not clear what to do here.
931 self.check_queue();
932 }
933 }
934 }
935 }
936 User::App { processid } => {
937 let _ = self.apps.enter(processid, move |app, kernel_data| {
938 // Need to copy in the contents of the buffer
939 let read_len = kernel_data
940 .get_readwrite_processbuffer(rw_allow::READ)
941 .and_then(|read| {
942 read.mut_enter(|app_buffer| {
943 let read_len = cmp::min(app_buffer.len(), length);
944
945 let d = &app_buffer[0..read_len];
946 for (i, c) in buffer[0..read_len].iter().enumerate() {
947 d[i].set(*c);
948 }
949 read_len
950 })
951 })
952 .unwrap_or(0);
953
954 // Replace the buffer we used to do this read.
955 self.buffer.replace(buffer);
956
957 // clear pending syscall
958 app.pending_operation = None;
959 // And then signal the app.
960 let _ = kernel_data.schedule_upcall(
961 upcall::READ_DONE,
962 (into_statuscode(Ok(())), read_len, 0),
963 );
964 });
965
966 self.check_queue();
967 }
968 }
969 });
970 }
971
972 fn write_done(&self, buffer: &'static mut [u8], length: usize) {
973 // Replace the buffer we used to do this write.
974 self.buffer.replace(buffer);
975
976 // Switch on which user of this capsule generated this callback.
977 self.current_user.take().map(|user| {
978 match user {
979 User::RegionManager(state) => {
980 match state {
981 ManagerTask::WriteHeader(processid, region) => {
982 // Now that we have written the header for the app
983 // we can store its region in its grant.
984 let _ = self.apps.enter(processid, |app, _kernel_data| {
985 // set region data in app's grant
986 app.region.replace(region);
987 });
988
989 // Update our metadata about where the next
990 // unallocated region is.
991 let next_header_addr = region.absolute_address + region.length;
992 self.next_unallocated_region_header_address
993 .set(next_header_addr);
994
995 // Erase the userspace accessible content of the region
996 // before handing it off to an app.
997 let res =
998 self.erase_region_content(region.absolute_address, region.length);
999 match res {
1000 Ok((next_erase_start, remaining_bytes)) => {
1001 self.current_user.set(User::RegionManager(
1002 // need to pass on where the next erase should start
1003 // how long it should be.
1004 ManagerTask::EraseRegion {
1005 processid,
1006 next_erase_start,
1007 remaining_bytes,
1008 },
1009 ));
1010 }
1011 Err(_e) => {
1012 // Not clear what to do here.
1013 self.current_user.clear();
1014 self.check_queue();
1015 }
1016 }
1017 }
1018 ManagerTask::EraseRegion {
1019 processid,
1020 next_erase_start,
1021 remaining_bytes,
1022 } => {
1023 if remaining_bytes > 0 {
1024 // We still have more to erase, so kick off
1025 // another one where we left off.
1026 let res =
1027 self.erase_region_content(next_erase_start, remaining_bytes);
1028 match res {
1029 Ok((next_erase_start, remaining_bytes)) => {
1030 self.current_user.set(User::RegionManager(
1031 ManagerTask::EraseRegion {
1032 processid,
1033 next_erase_start,
1034 remaining_bytes,
1035 },
1036 ));
1037 }
1038 Err(_e) => {
1039 // Not clear what to do here.
1040 self.current_user.clear();
1041 self.check_queue();
1042 }
1043 }
1044 } else {
1045 // Done erasing entire region. Can go on with
1046 // normal tasks.
1047 self.current_user.clear();
1048 self.check_queue();
1049 }
1050 }
1051 _ => {}
1052 }
1053 }
1054 User::App { processid } => {
1055 let _ = self.apps.enter(processid, move |app, kernel_data| {
1056 // clear pending syscall
1057 app.pending_operation = None;
1058 // Notify app that its write has completed.
1059 let _ = kernel_data.schedule_upcall(
1060 upcall::WRITE_DONE,
1061 (into_statuscode(Ok(())), length, 0),
1062 );
1063 });
1064 self.current_user.clear();
1065 self.check_queue();
1066 }
1067 }
1068 });
1069 }
1070}
1071
1072/// Provide an interface for userland.
1073impl<const APP_REGION_SIZE: usize> SyscallDriver
1074 for IsolatedNonvolatileStorage<'_, APP_REGION_SIZE>
1075{
1076 /// Command interface.
1077 ///
1078 /// Commands are selected by the lowest 8 bits of the first argument.
1079 ///
1080 /// ### `command_num`
1081 ///
1082 /// - `0`: Return Ok(()) if this driver is included on the platform.
1083 /// - `1`: Return the number of bytes available to each app.
1084 /// - `2`: Start a read from the nonvolatile storage.
1085 /// - `3`: Start a write to the nonvolatile_storage.
1086 fn command(
1087 &self,
1088 command_num: usize,
1089 offset_lo: usize,
1090 offset_hi: usize,
1091 processid: ProcessId,
1092 ) -> CommandReturn {
1093 match command_num {
1094 0 => CommandReturn::success(),
1095
1096 // For the get_bytes, read, and write syscalls we need to first
1097 // initialize the app's isolated nonvolatile storage. This involves
1098 // searching the storage area for an existing region that belongs to
1099 // this app. If we don't find an existing region we allocate a new
1100 // one. Only once the initialization is complete, can we service the
1101 // original syscall. So, we can store the syscall data in the app's
1102 // grant and handle it when initialization finishes.
1103 1 | 2 | 3 => {
1104 // We want to handle both 64-bit and 32-bit platforms, but on
1105 // 32-bit platforms shifting `offset_hi` doesn't make sense.
1106 let offset: usize = usize32s_to_usize(offset_lo, offset_hi);
1107 let nvm_command = match command_num {
1108 1 => NvmCommand::GetSize,
1109 2 => NvmCommand::Read { offset },
1110 3 => NvmCommand::Write { offset },
1111 _ => return CommandReturn::failure(ErrorCode::NOSUPPORT),
1112 };
1113
1114 // Enqueue the operation for the app.
1115 let res = self.enqueue_userspace_command(nvm_command, processid);
1116 match res {
1117 Ok(()) => CommandReturn::success(),
1118 Err(e) => CommandReturn::failure(e),
1119 }
1120 }
1121 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
1122 }
1123 }
1124
1125 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
1126 self.apps.enter(processid, |_, _| {})
1127 }
1128}