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}