kernel/
upcall.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 2022.
4
5//! Data structure for storing an upcall from the kernel to a process.
6
7use crate::ErrorCode;
8use crate::config;
9use crate::debug;
10use crate::process;
11use crate::process::ProcessId;
12use crate::syscall::SyscallReturn;
13use crate::utilities::capability_ptr::CapabilityPtr;
14use crate::utilities::machine_register::MachineRegister;
15use core::fmt::Debug;
16
17/// Type to uniquely identify an upcall subscription across all drivers.
18///
19/// This contains the driver number and the subscribe number within the driver.
20#[derive(Copy, Clone, Eq, PartialEq)]
21pub struct UpcallId {
22    /// The [`SyscallDriver`](crate::syscall_driver::SyscallDriver)
23    /// implementation this upcall corresponds to.
24    pub driver_num: usize,
25    /// The subscription index the upcall corresponds to. Subscribe numbers
26    /// start at 0 and increment for each upcall defined for a particular
27    /// [`SyscallDriver`](crate::syscall_driver::SyscallDriver).
28    pub subscribe_num: usize,
29}
30
31impl Debug for UpcallId {
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        f.debug_struct("UpcallId")
34            .field("driver_num", &format_args!("{:#x}", &self.driver_num))
35            .field("subscribe_num", &self.subscribe_num)
36            .finish()
37    }
38}
39
40/// Errors which can occur when scheduling a process Upcall.
41///
42/// Scheduling a null-Upcall (which will not be delivered to a process) is
43/// deliberately not an error, given that a null-Upcall is a well-defined Upcall
44/// to be set by a process. It behaves essentially the same as if the process
45/// would set a proper Upcall, and would ignore all invocations, with the
46/// benefit that no task is inserted in the process' task queue.
47#[derive(Copy, Clone, Debug)]
48pub enum UpcallError {
49    /// The passed `subscribe_num` exceeds the number of Upcalls available for
50    /// this process.
51    ///
52    /// For a [`Grant`](crate::grant::Grant) with `n` upcalls, this error is
53    /// returned when
54    /// [`GrantKernelData::schedule_upcall`](crate::grant::GrantKernelData::schedule_upcall)
55    /// is invoked with `subscribe_num >= n`.
56    ///
57    /// No Upcall has been scheduled, the call to
58    /// [`GrantKernelData::schedule_upcall`](crate::grant::GrantKernelData::schedule_upcall)
59    /// had no observable effects.
60    ///
61    InvalidSubscribeNum,
62    /// The process' task queue is full.
63    ///
64    /// This error can occur when too many tasks (for example, Upcalls) have
65    /// been scheduled for a process, without that process yielding or having a
66    /// chance to resume execution.
67    ///
68    /// No Upcall has been scheduled, the call to
69    /// [`GrantKernelData::schedule_upcall`](crate::grant::GrantKernelData::schedule_upcall)
70    /// had no observable effects.
71    QueueFull,
72    /// A kernel-internal invariant has been violated.
73    ///
74    /// This error should never happen. It can be returned if the process is
75    /// inactive (which should be caught by
76    /// [`Grant::enter`](crate::grant::Grant::enter)) or `process.tasks` was
77    /// taken.
78    ///
79    /// These cases cannot be reasonably handled.
80    KernelError,
81}
82
83/// Type for calling an upcall in a process.
84///
85/// This is essentially a wrapper around a function pointer with associated
86/// process data.
87pub(crate) struct Upcall {
88    /// The [`ProcessId`] of the process this upcall is for.
89    pub(crate) process_id: ProcessId,
90
91    /// A unique identifier of this particular upcall, representing the
92    /// driver_num and subdriver_num used to submit it.
93    pub(crate) upcall_id: UpcallId,
94
95    /// The application data passed by the app when `subscribe()` was called.
96    pub(crate) appdata: MachineRegister,
97
98    /// A pointer to the first instruction of the function in the app that
99    /// corresponds to this upcall.
100    ///
101    /// If this value is `null`, it should not actually be
102    /// scheduled. An `Upcall` can be null when it is first created,
103    /// or after an app unsubscribes from an upcall.
104    pub(crate) fn_ptr: CapabilityPtr,
105}
106
107impl Upcall {
108    pub(crate) fn new(
109        process_id: ProcessId,
110        upcall_id: UpcallId,
111        appdata: MachineRegister,
112        fn_ptr: CapabilityPtr,
113    ) -> Upcall {
114        Upcall {
115            process_id,
116            upcall_id,
117            appdata,
118            fn_ptr,
119        }
120    }
121
122    /// Schedule the upcall.
123    ///
124    /// The arguments (`r0-r2`) are the values passed back to the process and
125    /// are specific to the individual `Driver` interfaces.
126    ///
127    /// # Note on Null upcalls
128    ///
129    /// This function _does_ schedule
130    /// [null upcalls](https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#421-the-null-upcall).
131    /// This is necessary to support the Yield-WaitFor system call which does
132    /// not require an upcall function to be registered.
133    ///
134    /// # Note on dropping upcalls and Yield-WaitFor
135    ///
136    /// If the per-process storage for upcalls is full the schedule operation
137    /// may fail. The exact dynamics of what defines "full" is implementation
138    /// dependent based on the [`Process`](process::Process) implementation. Also, what happens if
139    /// the storage for upcalls is full depends on the [`Process`](process::Process)
140    /// implementation.
141    ///
142    /// It is likely, however, that the per-process storage is a queue, and when
143    /// full, new scheduled upcalls will be dropped. Processes and capsule
144    /// authors should be aware of this specifically in the context of processes
145    /// that use the Yield-WaitFor system call. Once a process is blocking on a
146    /// Yield-WaitFor call, the process only resumes once the matching upcall is
147    /// enqueued. However, if the queue is full, no more upcalls can be
148    /// enqueued, and since the process isn't calling Yield, no queued upcalls
149    /// are being dequeued. The process will not make forward progress.
150    ///
151    /// # Return
152    ///
153    /// On successfully enqueuing the upcall returns `Ok(())`.
154    ///
155    /// On an error, returns `Err()` with these errors:
156    /// - `UpcallError::QueueFull`: The queue for storing upcalls for the
157    ///   process is full.
158    /// - `UpcallError::KernelError`: An internal error occurred. Please
159    ///   submit an issue.
160    // This function takes `process` as a parameter (even though we have
161    // `process_id` in our struct) to avoid a search through the processes
162    // array to schedule the upcall. Currently, it is convenient to pass this
163    // parameter so we take advantage of it. If in the future that is not the
164    // case we could have `process` be an Option and just do the search with
165    // the stored [`ProcessId`].
166    pub(crate) fn schedule(
167        &self,
168        process: &dyn process::Process,
169        r0: usize,
170        r1: usize,
171        r2: usize,
172    ) -> Result<(), UpcallError> {
173        let enqueue_res = self.fn_ptr.map_or_else(
174            || {
175                process.enqueue_task(process::Task::ReturnValue(process::ReturnArguments {
176                    upcall_id: self.upcall_id,
177                    argument0: r0,
178                    argument1: r1,
179                    argument2: r2,
180                }))
181            },
182            |fp| {
183                process.enqueue_task(process::Task::FunctionCall(process::FunctionCall {
184                    source: process::FunctionCallSource::Driver(self.upcall_id),
185                    argument0: r0,
186                    argument1: r1,
187                    argument2: r2,
188                    argument3: self.appdata,
189                    pc: *fp,
190                }))
191            },
192        );
193
194        let res = match enqueue_res {
195            Ok(()) => Ok(()),
196            Err(ErrorCode::NODEVICE) => {
197                // There should be no code path to schedule an Upcall on a
198                // process that is no longer alive. Indicate a kernel-internal
199                // error.
200                Err(UpcallError::KernelError)
201            }
202            Err(ErrorCode::NOMEM) => {
203                // No space left in the process' task queue.
204                Err(UpcallError::QueueFull)
205            }
206            Err(_) => {
207                // All other errors returned by `Process::enqueue_task` must be
208                // treated as kernel-internal errors
209                Err(UpcallError::KernelError)
210            }
211        };
212
213        if config::CONFIG.trace_syscalls {
214            debug!(
215                "[{:?}] schedule[{:#x}:{}] @{:#x}({:#x}, {:#x}, {:#x}, {:#x}) = {:?}",
216                self.process_id,
217                self.upcall_id.driver_num,
218                self.upcall_id.subscribe_num,
219                self.fn_ptr.map_or(core::ptr::null_mut::<()>(), |fp| fp
220                    .as_ptr::<()>()
221                    .cast_mut()) as usize,
222                r0,
223                r1,
224                r2,
225                self.appdata,
226                res
227            );
228        }
229        res
230    }
231
232    /// Create a successful syscall return type suitable for returning to
233    /// userspace.
234    ///
235    /// This function is intended to be called on the "old upcall" that is being
236    /// returned to userspace after a successful subscribe call and upcall swap.
237    ///
238    /// We provide this `.into` function because the return type needs to
239    /// include the function pointer of the upcall.
240    pub(crate) fn into_subscribe_success(self) -> SyscallReturn {
241        self.fn_ptr.map_or(
242            SyscallReturn::SubscribeSuccess(core::ptr::null::<()>(), self.appdata.as_usize()),
243            |fp| SyscallReturn::SubscribeSuccess(fp.as_ptr(), self.appdata.as_usize()),
244        )
245    }
246
247    /// Create a failure case syscall return type suitable for returning to
248    /// userspace.
249    ///
250    /// This is intended to be used when a subscribe call cannot be handled and
251    /// the function pointer passed from userspace must be returned back to
252    /// userspace.
253    ///
254    /// We provide this `.into` function because the return type needs to
255    /// include the function pointer of the upcall.
256    pub(crate) fn into_subscribe_failure(self, err: ErrorCode) -> SyscallReturn {
257        self.fn_ptr.map_or(
258            SyscallReturn::SubscribeFailure(err, core::ptr::null::<()>(), self.appdata.as_usize()),
259            |fp| SyscallReturn::SubscribeFailure(err, fp.as_ptr(), self.appdata.as_usize()),
260        )
261    }
262}