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