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}