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