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}