kernel/platform/platform.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//! Interfaces for implementing boards in Tock.
6
7use crate::errorcode;
8use crate::platform::chip::Chip;
9use crate::platform::scheduler_timer;
10use crate::platform::watchdog;
11use crate::process;
12use crate::scheduler::Scheduler;
13use crate::syscall;
14use crate::syscall_driver::SyscallDriver;
15use tock_tbf::types::CommandPermissions;
16
17/// Combination trait that boards provide to the kernel that includes all of
18/// the extensible operations the kernel supports.
19///
20/// This is the primary method for configuring the kernel for a specific board.
21pub trait KernelResources<C: Chip> {
22 /// The implementation of the system call dispatch mechanism the kernel
23 /// will use.
24 type SyscallDriverLookup: SyscallDriverLookup;
25
26 /// The implementation of the system call filtering mechanism the kernel
27 /// will use.
28 type SyscallFilter: SyscallFilter;
29
30 /// The implementation of the process fault handling mechanism the kernel
31 /// will use.
32 type ProcessFault: ProcessFault;
33
34 /// The implementation of the context switch callback handler
35 /// the kernel will use.
36 type ContextSwitchCallback: ContextSwitchCallback;
37
38 /// The implementation of the scheduling algorithm the kernel will use.
39 type Scheduler: Scheduler<C>;
40
41 /// The implementation of the timer used to create the timeslices provided
42 /// to applications.
43 type SchedulerTimer: scheduler_timer::SchedulerTimer;
44
45 /// The implementation of the WatchDog timer used to monitor the running
46 /// of the kernel.
47 type WatchDog: watchdog::WatchDog;
48
49 /// Returns a reference to the implementation of the SyscallDriverLookup this
50 /// platform will use to route syscalls.
51 fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup;
52
53 /// Returns a reference to the implementation of the SyscallFilter this
54 /// platform wants the kernel to use.
55 fn syscall_filter(&self) -> &Self::SyscallFilter;
56
57 /// Returns a reference to the implementation of the ProcessFault handler
58 /// this platform wants the kernel to use.
59 fn process_fault(&self) -> &Self::ProcessFault;
60
61 /// Returns a reference to the implementation of the ContextSwitchCallback
62 /// for this platform.
63 fn context_switch_callback(&self) -> &Self::ContextSwitchCallback;
64
65 /// Returns a reference to the implementation of the Scheduler this platform
66 /// wants the kernel to use.
67 fn scheduler(&self) -> &Self::Scheduler;
68
69 /// Returns a reference to the implementation of the SchedulerTimer timer
70 /// for this platform.
71 fn scheduler_timer(&self) -> &Self::SchedulerTimer;
72
73 /// Returns a reference to the implementation of the WatchDog on this
74 /// platform.
75 fn watchdog(&self) -> &Self::WatchDog;
76}
77
78/// Configure the system call dispatch mapping.
79///
80/// Each board should define a struct which implements this trait. This trait is
81/// the core for how syscall dispatching is handled, and the implementation is
82/// responsible for dispatching to drivers for each system call number.
83///
84/// ## Example
85///
86/// ```ignore
87/// struct Hail {
88/// console: &'static capsules::console::Console<'static>,
89/// ipc: kernel::ipc::IPC,
90/// dac: &'static capsules::dac::Dac<'static>,
91/// }
92///
93/// impl SyscallDriverLookup for Hail {
94/// fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
95/// where
96/// F: FnOnce(Option<&dyn kernel::SyscallDriver>) -> R,
97/// {
98/// match driver_num {
99/// capsules::console::DRIVER_NUM => f(Some(self.console)),
100/// kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
101/// capsules::dac::DRIVER_NUM => f(Some(self.dac)),
102///
103/// _ => f(None),
104/// }
105/// }
106/// }
107/// ```
108pub trait SyscallDriverLookup {
109 /// Platform-specific mapping of syscall numbers to objects that implement
110 /// the Driver methods for that syscall.
111 ///
112 ///
113 /// An implementation
114 fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
115 where
116 F: FnOnce(Option<&dyn SyscallDriver>) -> R;
117}
118
119/// Trait for implementing system call filters that the kernel uses to decide
120/// whether to handle a specific system call or not.
121pub trait SyscallFilter {
122 /// Check the platform-provided system call filter for all non-yield system
123 /// calls. If the system call is allowed for the provided process then
124 /// return `Ok(())`. Otherwise, return `Err()` with an `ErrorCode` that will
125 /// be returned to the calling application. The default implementation
126 /// allows all system calls.
127 ///
128 /// This API should be considered unstable, and is likely to change in the
129 /// future.
130 fn filter_syscall(
131 &self,
132 _process: &dyn process::Process,
133 _syscall: &syscall::Syscall,
134 ) -> Result<(), errorcode::ErrorCode> {
135 Ok(())
136 }
137}
138
139/// Implement default allow all SyscallFilter trait for unit.
140impl SyscallFilter for () {}
141
142/// An allow list system call filter based on the TBF header, with a default
143/// allow all fallback.
144///
145/// This will check if the process has TbfHeaderPermissions specified. If the
146/// process has TbfHeaderPermissions they will be used to determine access
147/// permissions. For details on this see the TockBinaryFormat documentation. If
148/// no permissions are specified the default is to allow the syscall.
149pub struct TbfHeaderFilterDefaultAllow {}
150
151/// Implement default SyscallFilter trait for filtering based on the TBF header.
152impl SyscallFilter for TbfHeaderFilterDefaultAllow {
153 fn filter_syscall(
154 &self,
155 process: &dyn process::Process,
156 syscall: &syscall::Syscall,
157 ) -> Result<(), errorcode::ErrorCode> {
158 match syscall {
159 // Subscribe is allowed if any commands are
160 syscall::Syscall::Subscribe {
161 driver_number,
162 subdriver_number: _,
163 upcall_ptr: _,
164 appdata: _,
165 } => match process.get_command_permissions(*driver_number, 0) {
166 CommandPermissions::NoPermsAtAll => Ok(()),
167 CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
168 CommandPermissions::Mask(_allowed) => Ok(()),
169 },
170
171 syscall::Syscall::Command {
172 driver_number,
173 subdriver_number,
174 arg0: _,
175 arg1: _,
176 } => match process.get_command_permissions(*driver_number, subdriver_number / 64) {
177 CommandPermissions::NoPermsAtAll => Ok(()),
178 CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
179 CommandPermissions::Mask(allowed) => {
180 if (1 << (subdriver_number % 64)) & allowed > 0 {
181 Ok(())
182 } else {
183 Err(errorcode::ErrorCode::NODEVICE)
184 }
185 }
186 },
187
188 // Allow is allowed if any commands are
189 syscall::Syscall::ReadWriteAllow {
190 driver_number,
191 subdriver_number: _,
192 allow_address: _,
193 allow_size: _,
194 } => match process.get_command_permissions(*driver_number, 0) {
195 CommandPermissions::NoPermsAtAll => Ok(()),
196 CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
197 CommandPermissions::Mask(_allowed) => Ok(()),
198 },
199
200 // Allow is allowed if any commands are
201 syscall::Syscall::UserspaceReadableAllow {
202 driver_number,
203 subdriver_number: _,
204 allow_address: _,
205 allow_size: _,
206 } => match process.get_command_permissions(*driver_number, 0) {
207 CommandPermissions::NoPermsAtAll => Ok(()),
208 CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
209 CommandPermissions::Mask(_allowed) => Ok(()),
210 },
211
212 // Allow is allowed if any commands are
213 syscall::Syscall::ReadOnlyAllow {
214 driver_number,
215 subdriver_number: _,
216 allow_address: _,
217 allow_size: _,
218 } => match process.get_command_permissions(*driver_number, 0) {
219 CommandPermissions::NoPermsAtAll => Ok(()),
220 CommandPermissions::NoPermsThisDriver => Err(errorcode::ErrorCode::NODEVICE),
221 CommandPermissions::Mask(_allowed) => Ok(()),
222 },
223
224 // Non-filterable system calls
225 syscall::Syscall::Yield { .. }
226 | syscall::Syscall::Memop { .. }
227 | syscall::Syscall::Exit { .. } => Ok(()),
228 }
229 }
230}
231
232/// Trait for implementing process fault handlers to run when a process faults.
233pub trait ProcessFault {
234 /// This function is called when an app faults.
235 ///
236 /// This is an optional function that can be implemented by `Platform`s that
237 /// allows the chip to handle the app fault and not terminate or restart the
238 /// app.
239 ///
240 /// If `Ok(())` is returned by this function then the kernel will not
241 /// terminate or restart the app, but instead allow it to continue running.
242 /// NOTE in this case the chip must have fixed the underlying reason for
243 /// fault otherwise it will re-occur.
244 ///
245 /// This can not be used for apps to circumvent Tock's protections. If for
246 /// example this function just ignored the error and allowed the app to
247 /// continue the fault would continue to occur.
248 ///
249 /// If `Err(())` is returned then the kernel will set the app as faulted and
250 /// follow the `FaultResponse` protocol.
251 ///
252 /// It is unlikely a `Platform` will need to implement this. This should be
253 /// used for only a handful of use cases. Possible use cases include:
254 /// - Allowing the kernel to emulate unimplemented instructions This
255 /// could be used to allow apps to run on hardware that doesn't
256 /// implement some instructions, for example atomics.
257 /// - Allow the kernel to handle hardware faults, triggered by the app.
258 /// This can allow an app to continue running if it triggers certain
259 /// types of faults. For example if an app triggers a memory parity
260 /// error the kernel can handle the error and allow the app to continue
261 /// (or not).
262 /// - Allow an app to execute from external QSPI. This could be used to
263 /// allow an app to execute from external QSPI where access faults can
264 /// be handled by the `Platform` to ensure the QPSI is mapped
265 /// correctly.
266 #[allow(unused_variables)]
267 fn process_fault_hook(&self, process: &dyn process::Process) -> Result<(), ()> {
268 Err(())
269 }
270}
271
272/// Implement default ProcessFault trait for unit.
273impl ProcessFault for () {}
274
275/// Trait for implementing handlers on userspace context switches.
276pub trait ContextSwitchCallback {
277 /// This function is called before the kernel switches to a process.
278 ///
279 /// `process` is the app that is about to run
280 fn context_switch_hook(&self, process: &dyn process::Process);
281}
282
283/// Implement default ContextSwitchCallback trait for unit.
284impl ContextSwitchCallback for () {
285 fn context_switch_hook(&self, _process: &dyn process::Process) {}
286}