Newer
Older
diff --git a/boards/nordic/nrf52dk_base/src/lib.rs b/boards/nordic/nrf52dk_base/src/lib.rs
index fe493727..105f7120 100644
--- a/boards/nordic/nrf52dk_base/src/lib.rs
+++ b/boards/nordic/nrf52dk_base/src/lib.rs
@@ -104,6 +104,7 @@ pub struct Platform {
// The nRF52dk does not have the flash chip on it, so we make this optional.
nonvolatile_storage:
Option<&'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>>,
+ nvmc: &'static nrf52::nvmc::SyscallDriver,
}
impl kernel::Platform for Platform {
@@ -128,6 +129,7 @@ impl kernel::Platform for Platform {
capsules::nonvolatile_storage_driver::DRIVER_NUM => {
f(self.nonvolatile_storage.map_or(None, |nv| Some(nv)))
}
+ nrf52::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
_ => f(None),
}
@@ -405,6 +407,14 @@ pub unsafe fn setup_board<I: nrf52::interrupt_service::InterruptService>(
);
nrf52::acomp::ACOMP.set_client(analog_comparator);
+ let nvmc = static_init!(
+ nrf52::nvmc::SyscallDriver,
+ nrf52::nvmc::SyscallDriver::new(
+ &nrf52::nvmc::NVMC,
+ board_kernel.create_grant(&memory_allocation_capability)
+ )
+ );
+
// Start all of the clocks. Low power operation will require a better
// approach than this.
nrf52::clock::CLOCK.low_stop();
@@ -439,6 +449,7 @@ pub unsafe fn setup_board<I: nrf52::interrupt_service::InterruptService>(
analog_comparator: analog_comparator,
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
nonvolatile_storage: nonvolatile_storage,
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
+ nvmc: nvmc,
};
platform.pconsole.start();
diff --git a/chips/nrf52/src/nvmc.rs b/chips/nrf52/src/nvmc.rs
index 5abd2d84..5a726fdb 100644
--- a/chips/nrf52/src/nvmc.rs
+++ b/chips/nrf52/src/nvmc.rs
@@ -3,6 +3,7 @@
//! Used in order read and write to internal flash.
use core::cell::Cell;
+use core::convert::TryFrom;
use core::ops::{Index, IndexMut};
use kernel::common::cells::OptionalCell;
use kernel::common::cells::TakeCell;
@@ -11,7 +12,7 @@ use kernel::common::deferred_call::DeferredCall;
use kernel::common::registers::{register_bitfields, ReadOnly, ReadWrite};
use kernel::common::StaticRef;
use kernel::hil;
-use kernel::ReturnCode;
+use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared};
use crate::deferred_call_tasks::DeferredCallTask;
@@ -141,7 +142,13 @@ register_bitfields! [u32,
static DEFERRED_CALL: DeferredCall<DeferredCallTask> =
unsafe { DeferredCall::new(DeferredCallTask::Nvmc) };
+type WORD = u32;
+const WORD_SIZE: usize = core::mem::size_of::<WORD>();
const PAGE_SIZE: usize = 4096;
+const MAX_WORD_WRITES: usize = 2;
+const MAX_PAGE_ERASES: usize = 10000;
+const WORD_MASK: usize = WORD_SIZE - 1;
+const PAGE_MASK: usize = PAGE_SIZE - 1;
/// This is a wrapper around a u8 array that is sized to a single page for the
/// nrf. Users of this module must pass an object of this type to use the
@@ -215,6 +222,11 @@ impl Nvmc {
}
}
+ pub fn configure_readonly(&self) {
+ let regs = &*self.registers;
+ regs.config.write(Configuration::WEN::Ren);
+ }
+
/// Configure the NVMC to allow writes to flash.
pub fn configure_writeable(&self) {
let regs = &*self.registers;
@@ -230,7 +242,7 @@ impl Nvmc {
let regs = &*self.registers;
regs.config.write(Configuration::WEN::Een);
while !self.is_ready() {}
- regs.erasepage.write(ErasePage::ERASEPAGE.val(0x10001000));
+ regs.eraseuicr.write(EraseUicr::ERASEUICR::ERASE);
while !self.is_ready() {}
}
@@ -314,7 +326,7 @@ impl Nvmc {
// Put the NVMC in write mode.
regs.config.write(Configuration::WEN::Wen);
- for i in (0..data.len()).step_by(4) {
+ for i in (0..data.len()).step_by(WORD_SIZE) {
let word: u32 = (data[i + 0] as u32) << 0
| (data[i + 1] as u32) << 8
| (data[i + 2] as u32) << 16
@@ -374,3 +386,170 @@ impl hil::flash::Flash for Nvmc {
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
self.erase_page(page_number)
}
}
+
+/// Provides access to the writeable flash regions of the application.
+///
+/// The purpose of this driver is to provide low-level access to the embedded flash of nRF52 boards
+/// to allow applications to implement flash-aware (like wear-leveling) data-structures. The driver
+/// only permits applications to operate on their writeable flash regions. The API is blocking since
+/// the CPU is halted during write and erase operations.
+///
+/// Supported boards:
+/// - nRF52840 (tested)
+/// - nRF52833
+/// - nRF52811
+/// - nRF52810
+///
+/// The maximum number of writes for the nRF52832 board is not per word but per block (512 bytes)
+/// and as such doesn't exactly fit this API. However, it could be safely supported by returning
+/// either 1 for the maximum number of word writes (i.e. the flash can only be written once before
+/// being erased) or 8 for the word size (i.e. the write granularity is doubled). In both cases,
+/// only 128 writes per block are permitted while the flash supports 181.
+///
+/// # Syscalls
+///
+/// - COMMAND(0): Check the driver.
+/// - COMMAND(1, 0): Get the word size (always 4).
+/// - COMMAND(1, 1): Get the page size (always 4096).
+/// - COMMAND(1, 2): Get the maximum number of word writes between page erasures (always 2).
+/// - COMMAND(1, 3): Get the maximum number page erasures in the lifetime of the flash (always
+/// 10000).
+/// - COMMAND(2, ptr): Write the allow slice to the flash region starting at `ptr`.
+/// - `ptr` must be word-aligned.
+/// - The allow slice length must be word aligned.
+/// - The region starting at `ptr` of the same length as the allow slice must be in a writeable
+/// flash region.
+/// - COMMAND(3, ptr): Erase a page.
+/// - `ptr` must be page-aligned.
+/// - The page starting at `ptr` must be in a writeable flash region.
+/// - ALLOW(0): The allow slice for COMMAND(2).
+pub struct SyscallDriver {
+ nvmc: &'static Nvmc,
+ apps: Grant<App>,
+}
+
+pub const DRIVER_NUM: usize = 0x50003;
+
+#[derive(Default)]
+pub struct App {
+ /// The allow slice for COMMAND(2).
+ slice: Option<AppSlice<Shared, u8>>,
+}
+
+impl SyscallDriver {
+ pub fn new(nvmc: &'static Nvmc, apps: Grant<App>) -> SyscallDriver {
+ SyscallDriver { nvmc, apps }
+ }
+}
+
+fn is_write_needed(old: u32, new: u32) -> bool {
+ // No need to write if it would not modify the current value.
+ old & new != old
+}
+
+impl SyscallDriver {
+ /// Writes a word-aligned slice at a word-aligned address.
+ ///
+ /// Words are written only if necessary, i.e. if writing the new value would change the current
+ /// value. This can be used to simplify recovery operations (e.g. if power is lost during a
+ /// write operation). The application doesn't need to check which prefix has already been
+ /// written and may repeat the complete write that was interrupted.
+ ///
+ /// # Safety
+ ///
+ /// The words in this range must have been written less than `MAX_WORD_WRITES` since their last
+ /// page erasure.
+ ///
+ /// # Errors
+ ///
+ /// Fails with `EINVAL` if any of the following conditions does not hold:
+ /// - `ptr` must be word-aligned.
+ /// - `slice.len()` must be word-aligned.
+ fn write_slice(&self, ptr: usize, slice: &[u8]) -> ReturnCode {
+ if ptr & WORD_MASK != 0 || slice.len() & WORD_MASK != 0 {
+ return ReturnCode::EINVAL;
+ }
+ self.nvmc.configure_writeable();
+ for (i, chunk) in slice.chunks(WORD_SIZE).enumerate() {
+ // `unwrap` cannot fail because `slice.len()` is word-aligned (see above).
+ let val = WORD::from_ne_bytes(<[u8; WORD_SIZE]>::try_from(chunk).unwrap());
+ let loc = unsafe { &*(ptr as *const VolatileCell<u32>).add(i) };
+ if is_write_needed(loc.get(), val) {
+ loc.set(val);
+ }
+ }
+ while !self.nvmc.is_ready() {}
+ self.nvmc.configure_readonly();
+ ReturnCode::SUCCESS
+ }
+
+ /// Erases a page at a page-aligned address.
+ ///
+ /// # Errors
+ ///
+ /// Fails with `EINVAL` if any of the following conditions does not hold:
+ /// - `ptr` must be page-aligned.
+ fn erase_page(&self, ptr: usize) -> ReturnCode {
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
+ if ptr & PAGE_MASK != 0 {
+ return ReturnCode::EINVAL;
+ }
+ self.nvmc.erase_page_helper(ptr / PAGE_SIZE);
+ self.nvmc.configure_readonly();
+ ReturnCode::SUCCESS
+ }
+}
+
+impl Driver for SyscallDriver {
+ fn subscribe(&self, _: usize, _: Option<Callback>, _: AppId) -> ReturnCode {
+ ReturnCode::ENOSUPPORT
+ }
+
+ fn command(&self, cmd: usize, arg: usize, _: usize, appid: AppId) -> ReturnCode {
+ match (cmd, arg) {
+ (0, _) => ReturnCode::SUCCESS,
+
+ (1, 0) => ReturnCode::SuccessWithValue { value: WORD_SIZE },
+ (1, 1) => ReturnCode::SuccessWithValue { value: PAGE_SIZE },
+ (1, 2) => ReturnCode::SuccessWithValue {
+ value: MAX_WORD_WRITES,
+ },
+ (1, 3) => ReturnCode::SuccessWithValue {
+ value: MAX_PAGE_ERASES,
+ },
+ (1, _) => ReturnCode::EINVAL,
+
+ (2, ptr) => self
+ .apps
+ .enter(appid, |app, _| {
+ let slice = match app.slice.take() {
+ None => return ReturnCode::EINVAL,
+ Some(slice) => slice,
+ };
+ self.write_slice(ptr, slice.as_ref())
+ (3, ptr) => self.erase_page(ptr),
+
+ _ => ReturnCode::ENOSUPPORT,
+ }
+ }
+
+ fn allow(
+ &self,
+ appid: AppId,
+ allow_num: usize,
+ slice: Option<AppSlice<Shared, u8>>,
+ ) -> ReturnCode {
+ match allow_num {
+ 0 => self
+ .apps
+ .enter(appid, |app, _| {
+ app.slice = slice;
+ ReturnCode::SUCCESS
+ })
+ .unwrap_or_else(|err| err.into()),
+ _ => ReturnCode::ENOSUPPORT,
+ }
+ }
+}
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
diff --git a/kernel/src/process.rs b/kernel/src/process.rs
index eb00f274..35c19d15 100644
--- a/kernel/src/process.rs
+++ b/kernel/src/process.rs
@@ -1604,6 +1604,31 @@ impl<C: 'static + Chip> Process<'a, C> {
return Ok((None, 0));
}
+ // Allocate MPU region for storage.
+ const STORAGE_PTR: usize = 0xc0000;
+ const STORAGE_LEN: usize = 0x40000;
+ if chip
+ .mpu()
+ .allocate_region(
+ STORAGE_PTR as *const u8,
+ STORAGE_LEN,
+ STORAGE_LEN,
+ mpu::Permissions::ReadOnly,
+ &mut mpu_config,
+ )
+ .is_none()
+ {
+ if config::CONFIG.debug_load_processes {
+ debug!(
+ "[!] flash=[{:#010X}:{:#010X}] process={:?} - couldn't allocate flash region",
+ STORAGE_PTR,
+ STORAGE_PTR + STORAGE_LEN,
+ process_name
+ );
+ }
+ return Ok((None, 0));
+ }
+
// Determine how much space we need in the application's
// memory space just for kernel and grant state. We need to make
// sure we allocate enough memory just for that.