capsules_extra/app_loader.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 2024.
4
5//! This capsule provides an interface between a dynamic loading userspace
6//! app and the kernel.
7//!
8//! This is an initial implementation that gets the app size from the
9//! userspace app and sets up the flash region in which the app will be
10//! written. Then the app is actually written to flash. Finally, the
11//! the userspace app sends a request for the app to be loaded.
12//!
13//!
14//! Here is a diagram of the expected stack with this capsule:
15//! Boxes are components and between the boxes are the traits that are the
16//! interfaces between components.
17//!
18//! ```text
19//! +-----------------------------------------------------------------+
20//! | |
21//! | userspace |
22//! | |
23//! +-----------------------------------------------------------------+
24//! kernel::SyscallDriver
25//! +-----------------------------------------------------------------+
26//! | |
27//! | capsules::app_loader::AppLoader (this) |
28//! | |
29//! +-----------------------------------------------------------------+
30//! kernel::dynamic_binary_storage::DynamicBinaryStore
31//! kernel::dynamic_binary_storage::DynamicProcessLoad
32//! +-----------------------------------------------------------------+
33//! | | |
34//! | Physical Nonvolatile Storage | Kernel |
35//! | | |
36//! +-----------------------------------------------------------------+
37//! hil::nonvolatile_storage::NonvolatileStorage
38//! ```
39//!
40//! Example instantiation:
41//!
42//! ```rust, ignore
43//! # use kernel::static_init;
44//!
45//! type NonVolatilePages = components::dynamic_binary_storage::NVPages<nrf52840::nvmc::Nvmc>;
46//! type DynamicBinaryStorage<'a> = kernel::dynamic_binary_storage::SequentialDynamicBinaryStorage<
47//! 'static,
48//! nrf52840::chip::NRF52<'a, Nrf52840DefaultPeripherals<'a>>,
49//! kernel::process::ProcessStandardDebugFull,
50//! NonVolatilePages,
51//! >;
52//!
53//! let dynamic_app_loader = components::app_loader::AppLoaderComponent::new(
54//! board_kernel,
55//! capsules_extra::app_loader::DRIVER_NUM,
56//! dynamic_binary_storage,
57//! dynamic_binary_storage,
58//! ).finalize(components::app_loader_component_static!(
59//! DynamicBinaryStorage<'static>,
60//! DynamicBinaryStorage<'static>,
61//! ));
62//!
63//! NOTE:
64//! 1. This capsule is not virtualized, and can only serve one app at a time.
65//! 2. This implementation currently only loads new apps. It does not update apps.
66//! ```
67
68use core::cell::Cell;
69use core::cmp;
70
71use kernel::dynamic_binary_storage;
72use kernel::errorcode::into_statuscode;
73use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
74use kernel::process::ProcessLoadError;
75use kernel::processbuffer::ReadableProcessBuffer;
76use kernel::syscall::{CommandReturn, SyscallDriver};
77use kernel::utilities::cells::{OptionalCell, TakeCell};
78use kernel::utilities::leasable_buffer::SubSliceMut;
79use kernel::{ErrorCode, ProcessId};
80
81/// Syscall driver number.
82use capsules_core::driver;
83pub const DRIVER_NUM: usize = driver::NUM::AppLoader as usize;
84
85/// IDs for subscribed upcalls.
86mod upcall {
87 /// Setup Done callback
88 pub const SETUP_DONE: usize = 0;
89 /// Write done callback.
90 pub const WRITE_DONE: usize = 1;
91 /// Finalize done callback.
92 pub const FINALIZE_DONE: usize = 2;
93 /// Load done callback.
94 pub const LOAD_DONE: usize = 3;
95 /// Abort done callback.
96 pub const ABORT_DONE: usize = 4;
97 /// Number of upcalls.
98 pub const COUNT: u8 = 5;
99}
100
101// Ids for read-only allow buffers
102mod ro_allow {
103 /// Setup a buffer to write bytes to the nonvolatile storage.
104 pub const WRITE: usize = 0;
105 /// The number of allow buffers the kernel stores for this grant
106 pub const COUNT: u8 = 1;
107}
108
109pub const BUF_LEN: usize = 512;
110
111#[derive(Default)]
112pub struct App {
113 pending_command: bool,
114}
115
116pub struct AppLoader<
117 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
118 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
119> {
120 // The underlying driver for the process flashing and loading.
121 storage_driver: &'static S,
122 load_driver: &'static L,
123 // Per-app state.
124 apps: Grant<
125 App,
126 UpcallCount<{ upcall::COUNT }>,
127 AllowRoCount<{ ro_allow::COUNT }>,
128 AllowRwCount<0>,
129 >,
130
131 // Internal buffer for copying appslices into.
132 buffer: TakeCell<'static, [u8]>,
133 // What issued the currently executing call.
134 current_process: OptionalCell<ProcessId>,
135 new_app_length: Cell<usize>,
136}
137
138impl<
139 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
140 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
141 > AppLoader<S, L>
142{
143 pub fn new(
144 grant: Grant<
145 App,
146 UpcallCount<{ upcall::COUNT }>,
147 AllowRoCount<{ ro_allow::COUNT }>,
148 AllowRwCount<0>,
149 >,
150 storage_driver: &'static S,
151 load_driver: &'static L,
152 buffer: &'static mut [u8],
153 ) -> AppLoader<S, L> {
154 AppLoader {
155 apps: grant,
156 storage_driver,
157 load_driver,
158 buffer: TakeCell::new(buffer),
159 current_process: OptionalCell::empty(),
160 new_app_length: Cell::new(0),
161 }
162 }
163
164 /// Copy data from the shared buffer with app and request kernel to
165 /// write the app data to flash.
166 fn write(&self, offset: usize, length: usize, processid: ProcessId) -> Result<(), ErrorCode> {
167 // Userspace sees memory that starts at address 0 even if it
168 // is offset in the physical memory.
169 match offset.checked_add(length) {
170 Some(result) => {
171 if result > self.new_app_length.get() {
172 // this means the app is out of bounds
173 return Err(ErrorCode::INVAL);
174 }
175 }
176 None => {
177 return Err(ErrorCode::INVAL);
178 }
179 }
180
181 self.apps
182 .enter(processid, |_app, kernel_data| {
183 let mut active_len = 0;
184
185 let result = kernel_data
186 .get_readonly_processbuffer(ro_allow::WRITE)
187 .and_then(|write| {
188 write.enter(|app_buffer| {
189 self.buffer
190 .map(|kernel_buffer| {
191 // Get the length of the allowed buffer
192 let allow_buf_len = app_buffer.len();
193
194 // Check that the buffer length is not zero
195 if allow_buf_len == 0 {
196 return Err(ErrorCode::RESERVE);
197 }
198
199 // Shorten the length if the application did not give us
200 // enough bytes in the allowed buffer.
201 active_len = cmp::min(length, allow_buf_len);
202
203 // copy data into the kernel buffer!
204 let write_len = cmp::min(active_len, kernel_buffer.len());
205 let () = app_buffer[..write_len]
206 .copy_to_slice(&mut kernel_buffer[..write_len]);
207
208 Ok(())
209 })
210 .unwrap_or(Err(ErrorCode::RESERVE))
211 })
212 });
213
214 if result.is_err() {
215 return Err(ErrorCode::RESERVE);
216 }
217
218 self.buffer
219 .take()
220 .map_or(Err(ErrorCode::RESERVE), |buffer| {
221 let mut write_buffer = SubSliceMut::new(buffer);
222 // should be the length supported by the app
223 // (currently only powers of 2 work)
224 write_buffer.slice(..length);
225 let res = self.storage_driver.write(write_buffer, offset);
226 match res {
227 Ok(()) => Ok(()),
228 Err(e) => Err(e),
229 }
230 })
231 })
232 .unwrap_or_else(|err| Err(err.into()))
233 }
234}
235
236impl<
237 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
238 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
239 > dynamic_binary_storage::DynamicBinaryStoreClient for AppLoader<S, L>
240{
241 /// Let the requesting app know we are done setting up for the new app
242 fn setup_done(&self, result: Result<(), ErrorCode>) {
243 // Switch on which user of this capsule generated this callback.
244 self.current_process.map(|processid| {
245 let _ = self.apps.enter(processid, move |app, kernel_data| {
246 app.pending_command = false;
247 // Signal the app.
248 kernel_data
249 .schedule_upcall(upcall::SETUP_DONE, (into_statuscode(result), 0, 0))
250 .ok();
251 });
252 });
253 }
254
255 /// Let the app know we are done writing the block of data
256 fn write_done(&self, result: Result<(), ErrorCode>, buffer: &'static mut [u8], length: usize) {
257 // Switch on which user of this capsule generated this callback.
258 self.current_process.map(|processid| {
259 let _ = self.apps.enter(processid, move |app, kernel_data| {
260 // Replace the buffer we used to do this write.
261 self.buffer.replace(buffer);
262 app.pending_command = false;
263
264 // And then signal the app.
265 kernel_data
266 .schedule_upcall(upcall::WRITE_DONE, (into_statuscode(result), length, 0))
267 .ok();
268 });
269 });
270 }
271
272 /// Let the app know we are done finalizing, and are ready to load
273 fn finalize_done(&self, result: Result<(), ErrorCode>) {
274 self.current_process.map(|processid| {
275 let _ = self.apps.enter(processid, move |app, kernel_data| {
276 // And then signal the app.
277 app.pending_command = false;
278
279 self.current_process.take();
280 kernel_data
281 .schedule_upcall(upcall::FINALIZE_DONE, (into_statuscode(result), 0, 0))
282 .ok();
283 });
284 });
285 }
286
287 /// Let the app know we have aborted the new app writing process
288 fn abort_done(&self, result: Result<(), ErrorCode>) {
289 self.current_process.map(|processid| {
290 let _ = self.apps.enter(processid, move |app, kernel_data| {
291 // And then signal the app.
292 app.pending_command = false;
293
294 self.current_process.take();
295 kernel_data
296 .schedule_upcall(upcall::ABORT_DONE, (into_statuscode(result), 0, 0))
297 .ok();
298 });
299 });
300 }
301}
302
303impl<
304 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
305 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
306 > dynamic_binary_storage::DynamicProcessLoadClient for AppLoader<S, L>
307{
308 /// Let the requesting app know we are done loading the new process
309 ///
310 /// Error Type Mapping.
311 ///
312 /// This method converts `ProcessLoadError` to `ErrorCode` so it can be
313 /// passed to userspace.
314 ///
315 /// Currently,
316 /// 1. ProcessLoadError::NotEnoughMemory <==> ErrorCode::NOMEM
317 /// 2. ProcessLoadError::MpuInvalidFlashLength <==> ErrorCode::INVAL
318 /// 3. ProcessLoadError::InternalError <==> ErrorCode::OFF
319 /// 4. All other ProcessLoadError types <==> ErrorCode::FAIL
320
321 fn load_done(&self, result: Result<(), ProcessLoadError>) {
322 let status_code = match result {
323 Ok(()) => Ok(()),
324 Err(e) => match e {
325 ProcessLoadError::NotEnoughMemory => Err(ErrorCode::NOMEM),
326 ProcessLoadError::MpuInvalidFlashLength => Err(ErrorCode::INVAL),
327 ProcessLoadError::MpuConfigurationError => Err(ErrorCode::FAIL),
328 ProcessLoadError::MemoryAddressMismatch { .. } => Err(ErrorCode::FAIL),
329 ProcessLoadError::NoProcessSlot => Err(ErrorCode::FAIL),
330 ProcessLoadError::BinaryError(_) => Err(ErrorCode::FAIL),
331 ProcessLoadError::CheckError(_) => Err(ErrorCode::FAIL),
332 // This error is usually a result of bug in the kernel
333 // so we return Powered OFF error, because that is unlikely.
334 ProcessLoadError::InternalError => Err(ErrorCode::OFF),
335 },
336 };
337
338 self.current_process.map(|processid| {
339 let _ = self.apps.enter(processid, move |app, kernel_data| {
340 app.pending_command = false;
341 // Signal the app.
342 self.current_process.take();
343 kernel_data
344 .schedule_upcall(upcall::LOAD_DONE, (into_statuscode(status_code), 0, 0))
345 .ok();
346 });
347 });
348 }
349}
350
351/// Provide an interface for userland.
352impl<
353 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
354 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
355 > SyscallDriver for AppLoader<S, L>
356{
357 /// Command interface.
358 ///
359 /// The driver returns ErrorCode::BUSY if:
360 /// - The kernel has already dedicated this driver to another process.
361 /// - The kernel is busy executing another command for this process.
362 ///
363 /// Currently, this capsule is not virtualized and can only be used by one
364 /// application at a time.
365 ///
366 /// Commands are selected by the lowest 8 bits of the first argument.
367 ///
368 /// ### `command_num`
369 ///
370 /// - `0`: Return Ok(()) if this driver is included on the platform.
371 /// - `1`: Request kernel to setup for loading app.
372 /// - Returns appsize if the kernel has available space
373 /// - Returns ErrorCode::FAIL if the kernel is unable to allocate space for the new app
374 /// - `2`: Request kernel to write app data to the nonvolatile_storage
375 /// - Returns Ok(()) when write is successful
376 /// - Returns ErrorCode::INVAL when the app is violating bounds
377 /// - Returns ErrorCode::FAIL when the write fails
378 /// - `3`: Signal to the kernel that the writing is done.
379 /// - Returns Ok(()) if the kernel successfully verified it and
380 /// set the stage for `load()`.
381 /// - Returns ErrorCode::FAIL if:
382 /// a. The kernel needs to write a leading padding app but is unable to.
383 /// b. The command is called during setup or load phases.
384 /// - `4`: Request kernel to load app.
385 /// - Returns Ok(()) when the process is successfully loaded
386 /// - Returns ErrorCode::FAIL if:
387 /// a. The kernel is unable to create a process object for the application
388 /// - `5`: Request kernel to abort setup/write operation.
389 /// - Returns Ok(()) when the operation is cancelled successfully
390 /// - Returns ErrorCode::BUSY when the abort fails
391 /// (due to padding app being unable to be written, so try again)
392 /// - Returns ErrorCode::FAIL if the driver is not dedicated to this process
393 ///
394 /// The driver returns ErrorCode::INVAL if any operation is called before the
395 /// preceeding operation was invoked. For example, `write()` cannot be called before
396 /// `setup()`, and `load()` cannot be called before `write()` (for this implementation).
397 fn command(
398 &self,
399 command_num: usize,
400 arg1: usize,
401 arg2: usize,
402 processid: ProcessId,
403 ) -> CommandReturn {
404 // Check if this driver is free, or already dedicated to this process.
405 let match_or_nonexistent = self.current_process.map_or(true, |current_process| {
406 self.apps
407 .enter(current_process, |_, _| current_process == processid)
408 .unwrap_or_else(|_e| {
409 let _ = self.storage_driver.abort();
410 self.new_app_length.set(0);
411 self.current_process.take();
412 false
413 })
414 });
415 if match_or_nonexistent {
416 self.current_process.set(processid);
417 let _ = self.apps.enter(processid, |app, _| {
418 if app.pending_command {
419 CommandReturn::failure(ErrorCode::BUSY);
420 } else {
421 app.pending_command = true;
422 }
423 });
424 } else {
425 return CommandReturn::failure(ErrorCode::BUSY);
426 }
427
428 match command_num {
429 0 => {
430 // Remove ownership from the current process so
431 // other processes can discover this driver
432 self.current_process.take();
433 CommandReturn::success()
434 }
435
436 1 => {
437 // Request kernel to allocate resources for
438 // an app with size passed via `arg1`.
439 let res = self.storage_driver.setup(arg1);
440 match res {
441 Ok(app_len) => {
442 self.new_app_length.set(app_len);
443 CommandReturn::success()
444 }
445 Err(e) => {
446 self.new_app_length.set(0);
447 self.current_process.take();
448 CommandReturn::failure(e)
449 }
450 }
451 }
452
453 2 => {
454 // Request kernel to write app to flash.
455 let res = self.write(arg1, arg2, processid);
456 match res {
457 Ok(()) => CommandReturn::success(),
458 Err(e) => {
459 let command_result = if let Some(buffer) = self.buffer.take() {
460 self.buffer.replace(buffer);
461 self.new_app_length.set(0);
462 self.current_process.take();
463 CommandReturn::failure(e)
464 } else {
465 CommandReturn::failure(ErrorCode::RESERVE)
466 };
467 command_result
468 }
469 }
470 }
471
472 3 => {
473 // Signal to kernel writing is done.
474 let result = self.storage_driver.finalize();
475 match result {
476 Ok(()) => CommandReturn::success(),
477 Err(e) => {
478 self.new_app_length.set(0);
479 self.current_process.take();
480 CommandReturn::failure(e)
481 }
482 }
483 }
484
485 4 => {
486 // Request kernel to load the new app.
487 let res = self.load_driver.load();
488 match res {
489 Ok(()) => {
490 self.new_app_length.set(0);
491 CommandReturn::success()
492 }
493 Err(e) => {
494 self.new_app_length.set(0);
495 self.current_process.take();
496 CommandReturn::failure(e)
497 }
498 }
499 }
500
501 5 => {
502 // Request kernel to abort setup/write operation.
503 let result = self.storage_driver.abort();
504 match result {
505 Ok(()) => {
506 self.new_app_length.set(0);
507 CommandReturn::success()
508 }
509 Err(e) => {
510 self.new_app_length.set(0);
511 self.current_process.take();
512 CommandReturn::failure(e)
513 }
514 }
515 }
516 // Unsupported command numbers.
517 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
518 }
519 }
520
521 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
522 self.apps.enter(processid, |_, _| {})
523 }
524}