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
109// Discussion regarding this buffer size value
110// can be found at: https://github.com/tock/tock/pull/4520
111pub const BUF_LEN: usize = 4096;
112
113#[derive(Default)]
114pub struct App {
115 pending_command: bool,
116}
117
118pub struct AppLoader<
119 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
120 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
121> {
122 // The underlying driver for the process flashing and loading.
123 storage_driver: &'static S,
124 load_driver: &'static L,
125 // Per-app state.
126 apps: Grant<
127 App,
128 UpcallCount<{ upcall::COUNT }>,
129 AllowRoCount<{ ro_allow::COUNT }>,
130 AllowRwCount<0>,
131 >,
132
133 // Internal buffer for copying appslices into.
134 buffer: TakeCell<'static, [u8]>,
135 // What issued the currently executing call.
136 current_process: OptionalCell<ProcessId>,
137 new_app_length: Cell<usize>,
138}
139
140impl<
141 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
142 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
143 > AppLoader<S, L>
144{
145 pub fn new(
146 grant: Grant<
147 App,
148 UpcallCount<{ upcall::COUNT }>,
149 AllowRoCount<{ ro_allow::COUNT }>,
150 AllowRwCount<0>,
151 >,
152 storage_driver: &'static S,
153 load_driver: &'static L,
154 buffer: &'static mut [u8],
155 ) -> AppLoader<S, L> {
156 AppLoader {
157 apps: grant,
158 storage_driver,
159 load_driver,
160 buffer: TakeCell::new(buffer),
161 current_process: OptionalCell::empty(),
162 new_app_length: Cell::new(0),
163 }
164 }
165
166 /// Copy data from the shared buffer with app and request kernel to
167 /// write the app data to flash.
168 fn write(&self, offset: usize, length: usize, processid: ProcessId) -> Result<(), ErrorCode> {
169 // Userspace sees memory that starts at address 0 even if it
170 // is offset in the physical memory.
171 match offset.checked_add(length) {
172 Some(result) => {
173 if result > self.new_app_length.get() {
174 // this means the app is out of bounds
175 return Err(ErrorCode::INVAL);
176 }
177 }
178 None => {
179 return Err(ErrorCode::INVAL);
180 }
181 }
182
183 self.apps
184 .enter(processid, |_app, kernel_data| {
185 let mut active_len = 0;
186
187 let result = kernel_data
188 .get_readonly_processbuffer(ro_allow::WRITE)
189 .and_then(|write| {
190 write.enter(|app_buffer| {
191 self.buffer
192 .map(|kernel_buffer| {
193 // Get the length of the allowed buffer
194 let allow_buf_len = app_buffer.len();
195
196 // Check that the buffer length is not zero
197 if allow_buf_len == 0 {
198 return Err(ErrorCode::RESERVE);
199 }
200
201 // Shorten the length if the application did not give us
202 // enough bytes in the allowed buffer.
203 active_len = cmp::min(length, allow_buf_len);
204
205 // copy data into the kernel buffer!
206 let write_len = cmp::min(active_len, kernel_buffer.len());
207 let () = app_buffer[..write_len]
208 .copy_to_slice(&mut kernel_buffer[..write_len]);
209
210 Ok(())
211 })
212 .unwrap_or(Err(ErrorCode::RESERVE))
213 })
214 });
215
216 if result.is_err() {
217 return Err(ErrorCode::RESERVE);
218 }
219
220 self.buffer
221 .take()
222 .map_or(Err(ErrorCode::RESERVE), |buffer| {
223 let mut write_buffer = SubSliceMut::new(buffer);
224 // should be the length supported by the app
225 // (currently only powers of 2 work)
226 write_buffer.slice(..length);
227 let res = self.storage_driver.write(write_buffer, offset);
228 match res {
229 Ok(()) => Ok(()),
230 Err(e) => Err(e),
231 }
232 })
233 })
234 .unwrap_or_else(|err| Err(err.into()))
235 }
236}
237
238impl<
239 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
240 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
241 > dynamic_binary_storage::DynamicBinaryStoreClient for AppLoader<S, L>
242{
243 /// Let the requesting app know we are done setting up for the new app
244 fn setup_done(&self, result: Result<(), ErrorCode>) {
245 // Switch on which user of this capsule generated this callback.
246 self.current_process.map(|processid| {
247 let _ = self.apps.enter(processid, move |app, kernel_data| {
248 app.pending_command = false;
249 // Signal the app.
250 kernel_data
251 .schedule_upcall(upcall::SETUP_DONE, (into_statuscode(result), 0, 0))
252 .ok();
253 });
254 });
255 }
256
257 /// Let the app know we are done writing the block of data
258 fn write_done(&self, result: Result<(), ErrorCode>, buffer: &'static mut [u8], length: usize) {
259 // Switch on which user of this capsule generated this callback.
260 self.current_process.map(|processid| {
261 let _ = self.apps.enter(processid, move |app, kernel_data| {
262 // Replace the buffer we used to do this write.
263 self.buffer.replace(buffer);
264 app.pending_command = false;
265
266 // And then signal the app.
267 kernel_data
268 .schedule_upcall(upcall::WRITE_DONE, (into_statuscode(result), length, 0))
269 .ok();
270 });
271 });
272 }
273
274 /// Let the app know we are done finalizing, and are ready to load
275 fn finalize_done(&self, result: Result<(), ErrorCode>) {
276 self.current_process.map(|processid| {
277 let _ = self.apps.enter(processid, move |app, kernel_data| {
278 // And then signal the app.
279 app.pending_command = false;
280
281 self.current_process.take();
282 kernel_data
283 .schedule_upcall(upcall::FINALIZE_DONE, (into_statuscode(result), 0, 0))
284 .ok();
285 });
286 });
287 }
288
289 /// Let the app know we have aborted the new app writing process
290 fn abort_done(&self, result: Result<(), ErrorCode>) {
291 self.current_process.map(|processid| {
292 let _ = self.apps.enter(processid, move |app, kernel_data| {
293 // And then signal the app.
294 app.pending_command = false;
295
296 self.current_process.take();
297 kernel_data
298 .schedule_upcall(upcall::ABORT_DONE, (into_statuscode(result), 0, 0))
299 .ok();
300 });
301 });
302 }
303}
304
305impl<
306 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
307 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
308 > dynamic_binary_storage::DynamicProcessLoadClient for AppLoader<S, L>
309{
310 /// Let the requesting app know we are done loading the new process
311 ///
312 /// Error Type Mapping.
313 ///
314 /// This method converts `ProcessLoadError` to `ErrorCode` so it can be
315 /// passed to userspace.
316 ///
317 /// Currently,
318 /// 1. ProcessLoadError::NotEnoughMemory <==> ErrorCode::NOMEM
319 /// 2. ProcessLoadError::MpuInvalidFlashLength <==> ErrorCode::INVAL
320 /// 3. ProcessLoadError::InternalError <==> ErrorCode::OFF
321 /// 4. All other ProcessLoadError types <==> ErrorCode::FAIL
322
323 fn load_done(&self, result: Result<(), ProcessLoadError>) {
324 let status_code = match result {
325 Ok(()) => Ok(()),
326 Err(e) => match e {
327 ProcessLoadError::NotEnoughMemory => Err(ErrorCode::NOMEM),
328 ProcessLoadError::MpuInvalidFlashLength => Err(ErrorCode::INVAL),
329 ProcessLoadError::MpuConfigurationError => Err(ErrorCode::FAIL),
330 ProcessLoadError::MemoryAddressMismatch { .. } => Err(ErrorCode::FAIL),
331 ProcessLoadError::NoProcessSlot => Err(ErrorCode::FAIL),
332 ProcessLoadError::BinaryError(_) => Err(ErrorCode::FAIL),
333 ProcessLoadError::CheckError(_) => Err(ErrorCode::FAIL),
334 // This error is usually a result of bug in the kernel
335 // so we return Powered OFF error, because that is unlikely.
336 ProcessLoadError::InternalError => Err(ErrorCode::OFF),
337 },
338 };
339
340 self.current_process.map(|processid| {
341 let _ = self.apps.enter(processid, move |app, kernel_data| {
342 app.pending_command = false;
343 // Signal the app.
344 self.current_process.take();
345 kernel_data
346 .schedule_upcall(upcall::LOAD_DONE, (into_statuscode(status_code), 0, 0))
347 .ok();
348 });
349 });
350 }
351}
352
353/// Provide an interface for userland.
354impl<
355 S: dynamic_binary_storage::DynamicBinaryStore + 'static,
356 L: dynamic_binary_storage::DynamicProcessLoad + 'static,
357 > SyscallDriver for AppLoader<S, L>
358{
359 /// Command interface.
360 ///
361 /// The driver returns ErrorCode::BUSY if:
362 /// - The kernel has already dedicated this driver to another process.
363 /// - The kernel is busy executing another command for this process.
364 ///
365 /// Currently, this capsule is not virtualized and can only be used by one
366 /// application at a time.
367 ///
368 /// Commands are selected by the lowest 8 bits of the first argument.
369 ///
370 /// ### `command_num`
371 ///
372 /// - `0`: Return Ok(()) if this driver is included on the platform.
373 /// - `1`: Request kernel to setup for loading app.
374 /// - Returns appsize if the kernel has available space
375 /// - Returns ErrorCode::FAIL if the kernel is unable to allocate space for the new app
376 /// - `2`: Request kernel to write app data to the nonvolatile_storage
377 /// - Returns Ok(()) when write is successful
378 /// - Returns ErrorCode::INVAL when the app is violating bounds
379 /// - Returns ErrorCode::FAIL when the write fails
380 /// - `3`: Signal to the kernel that the writing is done.
381 /// - Returns Ok(()) if the kernel successfully verified it and
382 /// set the stage for `load()`.
383 /// - Returns ErrorCode::FAIL if:
384 /// a. The kernel needs to write a leading padding app but is unable to.
385 /// b. The command is called during setup or load phases.
386 /// - `4`: Request kernel to load app.
387 /// - Returns Ok(()) when the process is successfully loaded
388 /// - Returns ErrorCode::FAIL if:
389 /// a. The kernel is unable to create a process object for the application
390 /// - `5`: Request kernel to abort setup/write operation.
391 /// - Returns Ok(()) when the operation is cancelled successfully
392 /// - Returns ErrorCode::BUSY when the abort fails
393 /// (due to padding app being unable to be written, so try again)
394 /// - Returns ErrorCode::FAIL if the driver is not dedicated to this process
395 ///
396 /// The driver returns ErrorCode::INVAL if any operation is called before the
397 /// preceeding operation was invoked. For example, `write()` cannot be called before
398 /// `setup()`, and `load()` cannot be called before `write()` (for this implementation).
399 fn command(
400 &self,
401 command_num: usize,
402 arg1: usize,
403 arg2: usize,
404 processid: ProcessId,
405 ) -> CommandReturn {
406 // Check if this driver is free, or already dedicated to this process.
407 let match_or_nonexistent = self.current_process.map_or(true, |current_process| {
408 self.apps
409 .enter(current_process, |_, _| current_process == processid)
410 .unwrap_or_else(|_e| {
411 let _ = self.storage_driver.abort();
412 self.new_app_length.set(0);
413 self.current_process.take();
414 false
415 })
416 });
417 if match_or_nonexistent {
418 self.current_process.set(processid);
419 let _ = self.apps.enter(processid, |app, _| {
420 if app.pending_command {
421 CommandReturn::failure(ErrorCode::BUSY);
422 } else {
423 app.pending_command = true;
424 }
425 });
426 } else {
427 return CommandReturn::failure(ErrorCode::BUSY);
428 }
429
430 match command_num {
431 0 => {
432 // Remove ownership from the current process so
433 // other processes can discover this driver
434 self.current_process.take();
435 CommandReturn::success()
436 }
437
438 1 => {
439 // Request kernel to allocate resources for
440 // an app with size passed via `arg1`.
441 let res = self.storage_driver.setup(arg1);
442 match res {
443 Ok(app_len) => {
444 self.new_app_length.set(app_len);
445 CommandReturn::success()
446 }
447 Err(e) => {
448 self.new_app_length.set(0);
449 self.current_process.take();
450 CommandReturn::failure(e)
451 }
452 }
453 }
454
455 2 => {
456 // Request kernel to write app to flash.
457 let res = self.write(arg1, arg2, processid);
458 match res {
459 Ok(()) => CommandReturn::success(),
460 Err(e) => {
461 let command_result = if let Some(buffer) = self.buffer.take() {
462 self.buffer.replace(buffer);
463 self.new_app_length.set(0);
464 self.current_process.take();
465 CommandReturn::failure(e)
466 } else {
467 CommandReturn::failure(ErrorCode::RESERVE)
468 };
469 command_result
470 }
471 }
472 }
473
474 3 => {
475 // Signal to kernel writing is done.
476 let result = self.storage_driver.finalize();
477 match result {
478 Ok(()) => CommandReturn::success(),
479 Err(e) => {
480 self.new_app_length.set(0);
481 self.current_process.take();
482 CommandReturn::failure(e)
483 }
484 }
485 }
486
487 4 => {
488 // Request kernel to load the new app.
489 let res = self.load_driver.load();
490 match res {
491 Ok(()) => {
492 self.new_app_length.set(0);
493 CommandReturn::success()
494 }
495 Err(e) => {
496 self.new_app_length.set(0);
497 self.current_process.take();
498 CommandReturn::failure(e)
499 }
500 }
501 }
502
503 5 => {
504 // Request kernel to abort setup/write operation.
505 let result = self.storage_driver.abort();
506 match result {
507 Ok(()) => {
508 self.new_app_length.set(0);
509 CommandReturn::success()
510 }
511 Err(e) => {
512 self.new_app_length.set(0);
513 self.current_process.take();
514 CommandReturn::failure(e)
515 }
516 }
517 }
518 // Unsupported command numbers.
519 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
520 }
521 }
522
523 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
524 self.apps.enter(processid, |_, _| {})
525 }
526}