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 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2022.
//! Interfaces for implementing boards in Tock.
use crate::errorcode;
use crate::platform::chip::Chip;
use crate::platform::scheduler_timer;
use crate::platform::watchdog;
use crate::process;
use crate::scheduler::Scheduler;
use crate::syscall;
use crate::syscall_driver::SyscallDriver;
use tock_tbf::types::CommandPermissions;
/// Combination trait that boards provide to the kernel that includes all of
/// the extensible operations the kernel supports.
///
/// This is the primary method for configuring the kernel for a specific board.
pub trait KernelResources<C: Chip> {
/// The implementation of the system call dispatch mechanism the kernel
/// will use.
type SyscallDriverLookup: SyscallDriverLookup;
/// The implementation of the system call filtering mechanism the kernel
/// will use.
type SyscallFilter: SyscallFilter;
/// The implementation of the process fault handling mechanism the kernel
/// will use.
type ProcessFault: ProcessFault;
/// The implementation of the context switch callback handler
/// the kernel will use.
type ContextSwitchCallback: ContextSwitchCallback;
/// The implementation of the scheduling algorithm the kernel will use.
type Scheduler: Scheduler<C>;
/// The implementation of the timer used to create the timeslices provided
/// to applications.
type SchedulerTimer: scheduler_timer::SchedulerTimer;
/// The implementation of the WatchDog timer used to monitor the running
/// of the kernel.
type WatchDog: watchdog::WatchDog;
/// Returns a reference to the implementation of the SyscallDriverLookup this
/// platform will use to route syscalls.
fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup;
/// Returns a reference to the implementation of the SyscallFilter this
/// platform wants the kernel to use.
fn syscall_filter(&self) -> &Self::SyscallFilter;
/// Returns a reference to the implementation of the ProcessFault handler
/// this platform wants the kernel to use.
fn process_fault(&self) -> &Self::ProcessFault;
/// Returns a reference to the implementation of the ContextSwitchCallback
/// for this platform.
fn context_switch_callback(&self) -> &Self::ContextSwitchCallback;
/// Returns a reference to the implementation of the Scheduler this platform
/// wants the kernel to use.
fn scheduler(&self) -> &Self::Scheduler;
/// Returns a reference to the implementation of the SchedulerTimer timer
/// for this platform.
fn scheduler_timer(&self) -> &Self::SchedulerTimer;
/// Returns a reference to the implementation of the WatchDog on this
/// platform.
fn watchdog(&self) -> &Self::WatchDog;
}
/// Configure the system call dispatch mapping.
///
/// Each board should define a struct which implements this trait. This trait is
/// the core for how syscall dispatching is handled, and the implementation is
/// responsible for dispatching to drivers for each system call number.
///
/// ## Example
///
/// ```ignore
/// struct Hail {
/// console: &'static capsules::console::Console<'static>,
/// ipc: kernel::ipc::IPC,
/// dac: &'static capsules::dac::Dac<'static>,
/// }
///
/// impl SyscallDriverLookup for Hail {
/// fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
/// where
/// F: FnOnce(Option<&dyn kernel::SyscallDriver>) -> R,
/// {
/// match driver_num {
/// capsules::console::DRIVER_NUM => f(Some(self.console)),
/// kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
/// capsules::dac::DRIVER_NUM => f(Some(self.dac)),
///
/// _ => f(None),
/// }
/// }
/// }
/// ```
pub trait SyscallDriverLookup {
/// Platform-specific mapping of syscall numbers to objects that implement
/// the Driver methods for that syscall.
///
///
/// An implementation
fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
where
F: FnOnce(Option<&dyn SyscallDriver>) -> R;
}
/// Trait for implementing system call filters that the kernel uses to decide
/// whether to handle a specific system call or not.
pub trait SyscallFilter {
/// Check the platform-provided system call filter for all non-yield system
/// calls. If the system call is allowed for the provided process then
/// return `Ok(())`. Otherwise, return `Err()` with an `ErrorCode` that will
/// be returned to the calling application. The default implementation
/// allows all system calls.
///
/// This API should be considered unstable, and is likely to change in the
/// future.
fn filter_syscall(
&self,
_process: &dyn process::Process,
_syscall: &syscall::Syscall,
) -> Result<(), errorcode::ErrorCode> {
Ok(())
}
}
/// Implement default allow all SyscallFilter trait for unit.
impl SyscallFilter for () {}
/// An allow list system call filter based on the TBF header, with a default
/// allow all fallback.
///
/// This will check if the process has TbfHeaderPermissions specified. If the
/// process has TbfHeaderPermissions they will be used to determine access
/// permissions. For details on this see the TockBinaryFormat documentation. If
/// no permissions are specified the default is to allow the syscall.
pub struct TbfHeaderFilterDefaultAllow {}
/// Implement default SyscallFilter trait for filtering based on the TBF header.
impl SyscallFilter for TbfHeaderFilterDefaultAllow {
fn filter_syscall(
&self,
process: &dyn process::Process,
syscall: &syscall::Syscall,
) -> Result<(), errorcode::ErrorCode> {
match syscall {
// Subscribe is allowed if any commands are
syscall::Syscall::Subscribe {
driver_number,
subdriver_number: _,
upcall_ptr: _,
appdata: _,
} => match process.get_command_permissions(*driver_number, 0) {
CommandPermissions::NoPermsAtAll => Ok(()),
CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
CommandPermissions::Mask(_allowed) => Ok(()),
},
syscall::Syscall::Command {
driver_number,
subdriver_number,
arg0: _,
arg1: _,
} => match process.get_command_permissions(*driver_number, subdriver_number / 64) {
CommandPermissions::NoPermsAtAll => Ok(()),
CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
CommandPermissions::Mask(allowed) => {
if (1 << (subdriver_number % 64)) & allowed > 0 {
Ok(())
} else {
Err(errorcode::ErrorCode::NODEVICE)
}
}
},
// Allow is allowed if any commands are
syscall::Syscall::ReadWriteAllow {
driver_number,
subdriver_number: _,
allow_address: _,
allow_size: _,
} => match process.get_command_permissions(*driver_number, 0) {
CommandPermissions::NoPermsAtAll => Ok(()),
CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
CommandPermissions::Mask(_allowed) => Ok(()),
},
// Allow is allowed if any commands are
syscall::Syscall::UserspaceReadableAllow {
driver_number,
subdriver_number: _,
allow_address: _,
allow_size: _,
} => match process.get_command_permissions(*driver_number, 0) {
CommandPermissions::NoPermsAtAll => Ok(()),
CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
CommandPermissions::Mask(_allowed) => Ok(()),
},
// Allow is allowed if any commands are
syscall::Syscall::ReadOnlyAllow {
driver_number,
subdriver_number: _,
allow_address: _,
allow_size: _,
} => match process.get_command_permissions(*driver_number, 0) {
CommandPermissions::NoPermsAtAll => Ok(()),
CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
CommandPermissions::Mask(_allowed) => Ok(()),
},
// Non-filterable system calls
syscall::Syscall::Yield { .. }
| syscall::Syscall::Memop { .. }
| syscall::Syscall::Exit { .. } => Ok(()),
}
}
}
/// Trait for implementing process fault handlers to run when a process faults.
pub trait ProcessFault {
/// This function is called when an app faults.
///
/// This is an optional function that can be implemented by `Platform`s that
/// allows the chip to handle the app fault and not terminate or restart the
/// app.
///
/// If `Ok(())` is returned by this function then the kernel will not
/// terminate or restart the app, but instead allow it to continue running.
/// NOTE in this case the chip must have fixed the underlying reason for
/// fault otherwise it will re-occur.
///
/// This can not be used for apps to circumvent Tock's protections. If for
/// example this function just ignored the error and allowed the app to
/// continue the fault would continue to occur.
///
/// If `Err(())` is returned then the kernel will set the app as faulted and
/// follow the `FaultResponse` protocol.
///
/// It is unlikely a `Platform` will need to implement this. This should be
/// used for only a handful of use cases. Possible use cases include:
/// - Allowing the kernel to emulate unimplemented instructions This
/// could be used to allow apps to run on hardware that doesn't
/// implement some instructions, for example atomics.
/// - Allow the kernel to handle hardware faults, triggered by the app.
/// This can allow an app to continue running if it triggers certain
/// types of faults. For example if an app triggers a memory parity
/// error the kernel can handle the error and allow the app to continue
/// (or not).
/// - Allow an app to execute from external QSPI. This could be used to
/// allow an app to execute from external QSPI where access faults can
/// be handled by the `Platform` to ensure the QPSI is mapped
/// correctly.
#[allow(unused_variables)]
fn process_fault_hook(&self, process: &dyn process::Process) -> Result<(), ()> {
Err(())
}
}
/// Implement default ProcessFault trait for unit.
impl ProcessFault for () {}
/// Trait for implementing handlers on userspace context switches.
pub trait ContextSwitchCallback {
/// This function is called before the kernel switches to a process.
///
/// `process` is the app that is about to run
fn context_switch_hook(&self, process: &dyn process::Process);
}
/// Implement default ContextSwitchCallback trait for unit.
impl ContextSwitchCallback for () {
fn context_switch_hook(&self, _process: &dyn process::Process) {}
}