kernel/kernel.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//! Tock's main kernel loop, scheduler loop, and Scheduler trait.
6//!
7//! This module also includes utility functions that are commonly used by
8//! scheduler policy implementations. Scheduling policy (round robin, priority,
9//! etc.) is defined in the `scheduler` subcrate and selected by a board.
10
11use core::cell::Cell;
12use core::num::NonZeroU32;
13
14use crate::capabilities;
15use crate::config;
16use crate::debug;
17use crate::deferred_call::DeferredCall;
18use crate::errorcode::ErrorCode;
19use crate::grant::{AllowRoSize, AllowRwSize, Grant, UpcallSize};
20use crate::ipc;
21use crate::memop;
22use crate::platform::chip::Chip;
23use crate::platform::mpu::MPU;
24use crate::platform::platform::ContextSwitchCallback;
25use crate::platform::platform::KernelResources;
26use crate::platform::platform::{ProcessFault, SyscallDriverLookup, SyscallFilter};
27use crate::platform::scheduler_timer::SchedulerTimer;
28use crate::platform::watchdog::WatchDog;
29use crate::process::ProcessSlot;
30use crate::process::{self, ProcessId, Task};
31use crate::scheduler::{Scheduler, SchedulingDecision};
32use crate::syscall::SyscallDriver;
33use crate::syscall::{ContextSwitchReason, SyscallReturn};
34use crate::syscall::{Syscall, YieldCall};
35use crate::syscall_driver::CommandReturn;
36use crate::upcall::{Upcall, UpcallId};
37use crate::utilities::cells::NumericCellExt;
38
39/// Threshold in microseconds to consider a process's timeslice to be exhausted.
40/// That is, Tock will skip re-scheduling a process if its remaining timeslice
41/// is less than this threshold.
42pub(crate) const MIN_QUANTA_THRESHOLD_US: u32 = 500;
43
44/// Main object for the kernel. Each board will need to create one.
45pub struct Kernel {
46 /// This holds a pointer to the static array of Process pointers.
47 processes: &'static [ProcessSlot],
48
49 /// A counter which keeps track of how many process identifiers have been
50 /// created. This is used to create new unique identifiers for processes.
51 process_identifier_max: Cell<usize>,
52
53 /// How many grant regions have been setup. This is incremented on every
54 /// call to `create_grant()`. We need to explicitly track this so that when
55 /// processes are created they can be allocated pointers for each grant.
56 grant_counter: Cell<usize>,
57
58 /// Flag to mark that grants have been finalized. This means that the kernel
59 /// cannot support creating new grants because processes have already been
60 /// created and the data structures for grants have already been
61 /// established.
62 grants_finalized: Cell<bool>,
63}
64
65/// Represents the different outcomes when trying to allocate a grant region
66enum AllocResult {
67 NoAllocation,
68 NewAllocation,
69 SameAllocation,
70}
71
72/// Tries to allocate the grant region for specified driver and process.
73/// Returns if a new grant was allocated or not
74fn try_allocate_grant(driver: &dyn SyscallDriver, process: &dyn process::Process) -> AllocResult {
75 let before_count = process.grant_allocated_count().unwrap_or(0);
76 match driver.allocate_grant(process.processid()).is_ok() {
77 true if before_count == process.grant_allocated_count().unwrap_or(0) => {
78 AllocResult::SameAllocation
79 }
80 true => AllocResult::NewAllocation,
81 false => AllocResult::NoAllocation,
82 }
83}
84
85impl Kernel {
86 /// Create the kernel object that knows about the list of processes.
87 ///
88 /// Crucially, the processes included in the `processes` array MUST be valid
89 /// to execute. Any credential checks or validation MUST happen before the
90 /// `Process` object is included in this array.
91 pub const fn new(processes: &'static [ProcessSlot]) -> Kernel {
92 Kernel {
93 processes,
94 process_identifier_max: Cell::new(0),
95 grant_counter: Cell::new(0),
96 grants_finalized: Cell::new(false),
97 }
98 }
99
100 /// Helper function that moves all non-generic portions of process_map_or
101 /// into a non-generic function to reduce code bloat from monomorphization.
102 pub(crate) fn get_process(&self, processid: ProcessId) -> Option<&dyn process::Process> {
103 // We use the index in the [`ProcessId`] so we can do a direct lookup.
104 // However, we are not guaranteed that the app still exists at that
105 // index in the processes array. To avoid additional overhead, we do the
106 // lookup and check here, rather than calling `.index()`.
107 self.processes
108 .get(processid.index)
109 .and_then(|pslot| pslot.get())
110 // Check that the process stored here matches the
111 // identifier in the `processid`.
112 .filter(|process| process.processid() == processid)
113 }
114
115 /// Run a closure on a specific process if it exists. If the process with a
116 /// matching [`ProcessId`] does not exist at the index specified within the
117 /// [`ProcessId`], then `default` will be returned.
118 ///
119 /// A match will not be found if the process was removed (and there is a
120 /// `None` in the process array), if the process changed its identifier
121 /// (likely after being restarted), or if the process was moved to a
122 /// different index in the processes array. Note that a match _will_ be
123 /// found if the process still exists in the correct location in the array
124 /// but is in any "stopped" state.
125 pub(crate) fn process_map_or<F, R>(&self, default: R, processid: ProcessId, closure: F) -> R
126 where
127 F: FnOnce(&dyn process::Process) -> R,
128 {
129 match self.get_process(processid) {
130 Some(process) => closure(process),
131 None => default,
132 }
133 }
134
135 /// Run a closure on a specific process if it exists. If the process with a
136 /// matching `ProcessId` does not exist at the index specified within the
137 /// `ProcessId`, then `default` will be returned.
138 ///
139 /// A match will not be found if the process was removed (and there is a
140 /// `None` in the process array), if the process changed its identifier
141 /// (likely after being restarted), or if the process was moved to a
142 /// different index in the processes array. Note that a match _will_ be
143 /// found if the process still exists in the correct location in the array
144 /// but is in any "stopped" state.
145 ///
146 /// This is functionally the same as `process_map_or()`, but this method is
147 /// available outside the kernel crate and requires a
148 /// `ProcessManagementCapability` to use.
149 pub fn process_map_or_external<F, R>(
150 &self,
151 default: R,
152 processid: ProcessId,
153 closure: F,
154 _capability: &dyn capabilities::ProcessManagementCapability,
155 ) -> R
156 where
157 F: FnOnce(&dyn process::Process) -> R,
158 {
159 match self.get_process(processid) {
160 Some(process) => closure(process),
161 None => default,
162 }
163 }
164
165 /// Run a closure on every valid process. This will iterate the array of
166 /// processes and call the closure on every process that exists.
167 pub(crate) fn process_each<F>(&self, closure: F)
168 where
169 F: FnMut(&dyn process::Process),
170 {
171 self.get_process_iter().for_each(closure);
172 }
173
174 pub fn process_iter_capability(
175 &self,
176 _capability: &dyn capabilities::ProcessManagementCapability,
177 ) -> impl Iterator<Item = &dyn process::Process> {
178 self.get_process_iter()
179 }
180
181 /// Returns an iterator over all processes loaded by the kernel.
182 pub(crate) fn get_process_iter(
183 &self,
184 ) -> core::iter::FilterMap<
185 core::slice::Iter<ProcessSlot>,
186 fn(&ProcessSlot) -> Option<&'static dyn process::Process>,
187 > {
188 self.processes.iter().filter_map(ProcessSlot::get)
189 }
190
191 /// Run a closure on every valid process. This will iterate the array of
192 /// processes and call the closure on every process that exists.
193 ///
194 /// This is functionally the same as `process_each()`, but this method is
195 /// available outside the kernel crate and requires a
196 /// `ProcessManagementCapability` to use.
197 pub fn process_each_capability<F>(
198 &'static self,
199 _capability: &dyn capabilities::ProcessManagementCapability,
200 closure: F,
201 ) where
202 F: FnMut(&dyn process::Process),
203 {
204 self.process_each(closure);
205 }
206
207 /// Run a closure on every process, but only continue if the closure returns
208 /// `None`. That is, if the closure returns any non-`None` value, iteration
209 /// stops and the value is returned from this function to the called.
210 pub(crate) fn process_until<T, F>(&self, closure: F) -> Option<T>
211 where
212 F: Fn(&dyn process::Process) -> Option<T>,
213 {
214 for process in self.get_process_iter() {
215 let ret = closure(process);
216 if ret.is_some() {
217 return ret;
218 }
219 }
220 None
221 }
222
223 /// Checks if the provided [`ProcessId`] is still valid given the processes
224 /// stored in the processes array. Returns `true` if the ProcessId still
225 /// refers to a valid process, and `false` if not.
226 ///
227 /// This is needed for `ProcessId` itself to implement the `.index()`
228 /// command to verify that the referenced app is still at the correct index.
229 pub(crate) fn processid_is_valid(&self, processid: &ProcessId) -> bool {
230 self.processes
231 .get(processid.index)
232 .is_some_and(|p| p.contains_process_with_id(processid.id()))
233 }
234
235 /// Create a new grant. This is used in board initialization to setup grants
236 /// that capsules use to interact with processes.
237 ///
238 /// Grants **must** only be created _before_ processes are initialized.
239 /// Processes use the number of grants that have been allocated to correctly
240 /// initialize the process's memory with a pointer for each grant. If a
241 /// grant is created after processes are initialized this will panic.
242 ///
243 /// Calling this function is restricted to only certain users, and to
244 /// enforce this calling this function requires the
245 /// `MemoryAllocationCapability` capability.
246 pub fn create_grant<
247 T: Default,
248 Upcalls: UpcallSize,
249 AllowROs: AllowRoSize,
250 AllowRWs: AllowRwSize,
251 >(
252 &'static self,
253 driver_num: usize,
254 _capability: &dyn capabilities::MemoryAllocationCapability,
255 ) -> Grant<T, Upcalls, AllowROs, AllowRWs> {
256 if self.grants_finalized.get() {
257 panic!("Grants finalized. Cannot create a new grant.");
258 }
259
260 // Create and return a new grant.
261 let grant_index = self.grant_counter.get();
262 self.grant_counter.increment();
263 Grant::new(self, driver_num, grant_index)
264 }
265
266 /// Returns the number of grants that have been setup in the system and
267 /// marks the grants as "finalized". This means that no more grants can
268 /// be created because data structures have been setup based on the number
269 /// of grants when this function is called.
270 ///
271 /// In practice, this is called when processes are created, and the process
272 /// memory is setup based on the number of current grants.
273 pub(crate) fn get_grant_count_and_finalize(&self) -> usize {
274 self.grants_finalized.set(true);
275 self.grant_counter.get()
276 }
277
278 /// Returns the number of grants that have been setup in the system and
279 /// marks the grants as "finalized". This means that no more grants can
280 /// be created because data structures have been setup based on the number
281 /// of grants when this function is called.
282 ///
283 /// In practice, this is called when processes are created, and the process
284 /// memory is setup based on the number of current grants.
285 ///
286 /// This is exposed publicly, but restricted with a capability. The intent
287 /// is that external implementations of `Process` need to be able to
288 /// retrieve the final number of grants.
289 pub fn get_grant_count_and_finalize_external(
290 &self,
291 _capability: &dyn capabilities::ExternalProcessCapability,
292 ) -> usize {
293 self.get_grant_count_and_finalize()
294 }
295
296 /// Create a new unique identifier for a process and return the identifier.
297 ///
298 /// Typically we just choose a larger number than we have used for any
299 /// process before which ensures that the identifier is unique.
300 pub(crate) fn create_process_identifier(&self) -> usize {
301 self.process_identifier_max.get_and_increment()
302 }
303
304 /// Find the next slot that is available for storing a new [`&Process`]
305 /// (Process).
306 ///
307 /// Returns `Err(())` if there are no available slots.
308 pub(crate) fn next_available_process_slot(&self) -> Result<(usize, &ProcessSlot), ()> {
309 for (index, slot) in self.processes.iter().enumerate() {
310 if slot.proc.get().is_none() {
311 return Ok((index, slot));
312 }
313 }
314 Err(())
315 }
316
317 /// Cause all apps to fault.
318 ///
319 /// This will call `set_fault_state()` on each app, causing the app to enter
320 /// the state as if it had crashed (for example with an MPU violation). If
321 /// the process is configured to be restarted it will be.
322 ///
323 /// Only callers with the `ProcessManagementCapability` can call this
324 /// function. This restricts general capsules from being able to call this
325 /// function, since capsules should not be able to arbitrarily restart all
326 /// apps.
327 pub fn hardfault_all_apps<C: capabilities::ProcessManagementCapability>(&self, _c: &C) {
328 for process in self.get_process_iter() {
329 process.set_fault_state();
330 }
331 }
332
333 /// Perform one iteration of the core Tock kernel loop.
334 ///
335 /// This function is responsible for three main operations:
336 ///
337 /// 1. Check if the kernel itself has any work to be done and if the
338 /// scheduler wants to complete that work now. If so, it allows the
339 /// kernel to run.
340 /// 2. Check if any processes have any work to be done, and if so if the
341 /// scheduler wants to allow any processes to run now, and if so which
342 /// one.
343 /// 3. After ensuring the scheduler does not want to complete any kernel or
344 /// process work (or there is no work to be done), are there are no
345 /// outstanding interrupts to handle, put the chip to sleep.
346 ///
347 /// This function has one configuration option: `no_sleep`. If that argument
348 /// is set to true, the kernel will never attempt to put the chip to sleep,
349 /// and this function can be called again immediately.
350 pub fn kernel_loop_operation<KR: KernelResources<C>, C: Chip, const NUM_PROCS: u8>(
351 &self,
352 resources: &KR,
353 chip: &C,
354 ipc: Option<&ipc::IPC<NUM_PROCS>>,
355 no_sleep: bool,
356 _capability: &dyn capabilities::MainLoopCapability,
357 ) {
358 let scheduler = resources.scheduler();
359
360 resources.watchdog().tickle();
361 unsafe {
362 // Ask the scheduler if we should do tasks inside of the kernel,
363 // such as handle interrupts. A scheduler may want to prioritize
364 // processes instead, or there may be no kernel work to do.
365 match scheduler.do_kernel_work_now(chip) {
366 true => {
367 // Execute kernel work. This includes handling
368 // interrupts and is how code in the chips/ and capsules
369 // crates is able to execute.
370 scheduler.execute_kernel_work(chip);
371 }
372 false => {
373 // No kernel work ready, so ask scheduler for a process.
374 match scheduler.next() {
375 SchedulingDecision::RunProcess((processid, timeslice_us)) => {
376 self.process_map_or((), processid, |process| {
377 let (reason, time_executed) =
378 self.do_process(resources, chip, process, ipc, timeslice_us);
379 scheduler.result(reason, time_executed);
380 });
381 }
382 SchedulingDecision::TrySleep => {
383 // For testing, it may be helpful to
384 // disable sleeping the chip in case
385 // the running test does not generate
386 // any interrupts.
387 if !no_sleep {
388 chip.atomic(|| {
389 // Cannot sleep if interrupts are pending,
390 // as on most platforms unhandled interrupts
391 // will wake the device. Also, if the only
392 // pending interrupt occurred after the
393 // scheduler decided to put the chip to
394 // sleep, but before this atomic section
395 // starts, the interrupt will not be
396 // serviced and the chip will never wake
397 // from sleep.
398 if !chip.has_pending_interrupts() && !DeferredCall::has_tasks()
399 {
400 resources.watchdog().suspend();
401 chip.sleep();
402 resources.watchdog().resume();
403 }
404 });
405 }
406 }
407 }
408 }
409 }
410 }
411 }
412
413 /// Main loop of the OS.
414 ///
415 /// Most of the behavior of this loop is controlled by the [`Scheduler`]
416 /// implementation in use.
417 pub fn kernel_loop<KR: KernelResources<C>, C: Chip, const NUM_PROCS: u8>(
418 &self,
419 resources: &KR,
420 chip: &C,
421 ipc: Option<&ipc::IPC<NUM_PROCS>>,
422 capability: &dyn capabilities::MainLoopCapability,
423 ) -> ! {
424 resources.watchdog().setup();
425 // Before we begin, verify that deferred calls were soundly setup.
426 DeferredCall::verify_setup();
427 loop {
428 self.kernel_loop_operation(resources, chip, ipc, false, capability);
429 }
430 }
431
432 /// Transfer control from the kernel to a userspace process.
433 ///
434 /// This function is called by the main kernel loop to run userspace code.
435 /// Notably, system calls from processes are handled in the kernel, *by the
436 /// kernel thread* in this function, and the syscall return value is set for
437 /// the process immediately. Normally, a process is allowed to continue
438 /// running after calling a syscall. However, the scheduler is given an out,
439 /// as `do_process()` will check with the scheduler before re-executing the
440 /// process to allow it to return from the syscall. If a process yields with
441 /// no upcalls pending, exits, exceeds its timeslice, or is interrupted,
442 /// then `do_process()` will return.
443 ///
444 /// Depending on the particular scheduler in use, this function may act in a
445 /// few different ways. `scheduler.continue_process()` allows the scheduler
446 /// to tell the Kernel whether to continue executing the process, or to
447 /// return control to the scheduler as soon as a kernel task becomes ready
448 /// (either a bottom half interrupt handler or dynamic deferred call), or to
449 /// continue executing the userspace process until it reaches one of the
450 /// aforementioned stopping conditions. Some schedulers may not require a
451 /// scheduler timer; passing `None` for the timeslice will use a null
452 /// scheduler timer even if the chip provides a real scheduler timer.
453 /// Schedulers can pass a timeslice (in us) of their choice, though if the
454 /// passed timeslice is smaller than `MIN_QUANTA_THRESHOLD_US` the process
455 /// will not execute, and this function will return immediately.
456 ///
457 /// This function returns a tuple indicating the reason the reason this
458 /// function has returned to the scheduler, and the amount of time the
459 /// process spent executing (or `None` if the process was run
460 /// cooperatively). Notably, time spent in this function by the kernel,
461 /// executing system calls or merely setting up the switch to/from
462 /// userspace, is charged to the process.
463 fn do_process<KR: KernelResources<C>, C: Chip, const NUM_PROCS: u8>(
464 &self,
465 resources: &KR,
466 chip: &C,
467 process: &dyn process::Process,
468 ipc: Option<&crate::ipc::IPC<NUM_PROCS>>,
469 timeslice_us: Option<NonZeroU32>,
470 ) -> (process::StoppedExecutingReason, Option<u32>) {
471 // We must use a dummy scheduler timer if the process should be executed
472 // without any timeslice restrictions. Note, a chip may not provide a
473 // real scheduler timer implementation even if a timeslice is requested.
474 let scheduler_timer: &dyn SchedulerTimer = if timeslice_us.is_none() {
475 &() // dummy timer, no preemption
476 } else {
477 resources.scheduler_timer()
478 };
479
480 // Clear the scheduler timer and then start the counter. This starts the
481 // process's timeslice. Since the kernel is still executing at this
482 // point, the scheduler timer need not have an interrupt enabled after
483 // `start()`.
484 scheduler_timer.reset();
485 if let Some(timeslice) = timeslice_us {
486 scheduler_timer.start(timeslice)
487 }
488
489 // Need to track why the process is no longer executing so that we can
490 // inform the scheduler.
491 let mut return_reason = process::StoppedExecutingReason::NoWorkLeft;
492
493 // Since the timeslice counts both the process's execution time and the
494 // time spent in the kernel on behalf of the process (setting it up and
495 // handling its syscalls), we intend to keep running the process until
496 // it has no more work to do. We break out of this loop if the scheduler
497 // no longer wants to execute this process or if it exceeds its
498 // timeslice.
499 loop {
500 let stop_running = match scheduler_timer.get_remaining_us() {
501 Some(us) => us.get() <= MIN_QUANTA_THRESHOLD_US,
502 None => true,
503 };
504 if stop_running {
505 // Process ran out of time while the kernel was executing.
506 process.debug_timeslice_expired();
507 return_reason = process::StoppedExecutingReason::TimesliceExpired;
508 break;
509 }
510
511 // Check if the scheduler wishes to continue running this process.
512 let continue_process = unsafe {
513 resources
514 .scheduler()
515 .continue_process(process.processid(), chip)
516 };
517 if !continue_process {
518 return_reason = process::StoppedExecutingReason::KernelPreemption;
519 break;
520 }
521
522 // Check if this process is actually ready to run. If not, we don't
523 // try to run it. This case can happen if a process faults and is
524 // stopped, for example.
525 if !process.ready() {
526 return_reason = process::StoppedExecutingReason::NoWorkLeft;
527 break;
528 }
529
530 match process.get_state() {
531 process::State::Running => {
532 // Running means that this process expects to be running, so
533 // go ahead and set things up and switch to executing the
534 // process. Arming the scheduler timer instructs it to
535 // generate an interrupt when the timeslice has expired. The
536 // underlying timer is not affected.
537 resources
538 .context_switch_callback()
539 .context_switch_hook(process);
540 process.setup_mpu();
541 chip.mpu().enable_app_mpu();
542 scheduler_timer.arm();
543 let context_switch_reason = process.switch_to();
544 scheduler_timer.disarm();
545 chip.mpu().disable_app_mpu();
546
547 // Now the process has returned back to the kernel. Check
548 // why and handle the process as appropriate.
549 match context_switch_reason {
550 Some(ContextSwitchReason::Fault) => {
551 // The app faulted, check if the chip wants to
552 // handle the fault.
553 if resources
554 .process_fault()
555 .process_fault_hook(process)
556 .is_err()
557 {
558 // Let process deal with it as appropriate.
559 process.set_fault_state();
560 }
561 }
562 Some(ContextSwitchReason::SyscallFired { syscall }) => {
563 self.handle_syscall(resources, process, syscall);
564 }
565 Some(ContextSwitchReason::Interrupted) => {
566 if scheduler_timer.get_remaining_us().is_none() {
567 // This interrupt was a timeslice expiration.
568 process.debug_timeslice_expired();
569 return_reason = process::StoppedExecutingReason::TimesliceExpired;
570 break;
571 }
572 // Go to the beginning of loop to determine whether
573 // to break to handle the interrupt, continue
574 // executing this process, or switch to another
575 // process.
576 continue;
577 }
578 None => {
579 // Something went wrong when switching to this
580 // process. Indicate this by putting it in a fault
581 // state.
582 process.set_fault_state();
583 }
584 }
585 }
586 process::State::Yielded => {
587 // If the process is yielded or hasn't been started it is
588 // waiting for a upcall. If there is a task scheduled for
589 // this process go ahead and set the process to execute it.
590 match process.dequeue_task() {
591 None => break,
592 Some(cb) => match cb {
593 Task::ReturnValue(_) => {
594 // Per TRD104, Yield-Wait does not wake the
595 // process for events that generate Null
596 // Upcalls.
597 break;
598 }
599 Task::FunctionCall(ccb) => {
600 if config::CONFIG.trace_syscalls {
601 debug!(
602 "[{:?}] function_call @{:#x}({:#x}, {:#x}, {:#x}, {:#x})",
603 process.processid(),
604 ccb.pc,
605 ccb.argument0,
606 ccb.argument1,
607 ccb.argument2,
608 ccb.argument3,
609 );
610 }
611 process.set_process_function(ccb);
612 }
613 Task::IPC((otherapp, ipc_type)) => {
614 ipc.map_or_else(
615 || {
616 panic!("Kernel consistency error: IPC Task with no IPC");
617 },
618 |ipc| {
619 // TODO(alevy): this could error for a variety of reasons.
620 // Should we communicate the error somehow?
621 // https://github.com/tock/tock/issues/1993
622 unsafe {
623 let _ = ipc.schedule_upcall(
624 process.processid(),
625 otherapp,
626 ipc_type,
627 );
628 }
629 },
630 );
631 }
632 },
633 }
634 }
635 process::State::YieldedFor(upcall_id) => {
636 // If this process is waiting for a specific upcall, see if
637 // it is ready. If so, dequeue it and return its values to
638 // the process without scheduling the callback.
639 match process.remove_upcall(upcall_id) {
640 None => break,
641 Some(task) => {
642 let (a0, a1, a2) = match task {
643 // There is no callback function registered, we
644 // just return the values provided by the driver
645 Task::ReturnValue(rv) => {
646 if config::CONFIG.trace_syscalls {
647 debug!(
648 "[{:?}] Yield-WaitFor: [NU] ({:#x}, {:#x}, {:#x})",
649 process.processid(),
650 rv.argument0,
651 rv.argument1,
652 rv.argument2,
653 );
654 }
655 (rv.argument0, rv.argument1, rv.argument2)
656 }
657 // There is a registered callback function, but
658 // since the process used `Yield-WaitFor`, we do
659 // not execute it, we just return its arguments
660 // values to the application.
661 Task::FunctionCall(ccb) => {
662 if config::CONFIG.trace_syscalls {
663 debug!(
664 "[{:?}] Yield-WaitFor [Suppressed function_call @{:#x}] ({:#x}, {:#x}, {:#x}, {:#x})",
665 process.processid(),
666 ccb.pc,
667 ccb.argument0,
668 ccb.argument1,
669 ccb.argument2,
670 ccb.argument3,
671 );
672 }
673 (ccb.argument0, ccb.argument1, ccb.argument2)
674 }
675 Task::IPC(_) => todo!(),
676 };
677 process
678 .set_syscall_return_value(SyscallReturn::YieldWaitFor(a0, a1, a2));
679 }
680 }
681 }
682 process::State::Faulted | process::State::Terminated => {
683 // We should never be scheduling an unrunnable process.
684 // This is a potential security flaw: panic.
685 panic!("Attempted to schedule an unrunnable process");
686 }
687 process::State::Stopped(_) => {
688 return_reason = process::StoppedExecutingReason::Stopped;
689 break;
690 }
691 }
692 }
693
694 // Check how much time the process used while it was executing, and
695 // return the value so we can provide it to the scheduler.
696 let time_executed_us = timeslice_us.map(|timeslice| {
697 // Note, we cannot call `.get_remaining_us()` again if it has
698 // previously returned `None`, so we _must_ check the return reason
699 // first.
700 if return_reason == process::StoppedExecutingReason::TimesliceExpired {
701 // used the whole timeslice
702 timeslice.get()
703 } else {
704 match scheduler_timer.get_remaining_us() {
705 Some(remaining) => timeslice.get() - remaining.get(),
706 None => timeslice.get(), // used whole timeslice
707 }
708 }
709 });
710
711 // Reset the scheduler timer in case it unconditionally triggers
712 // interrupts upon expiration. We do not want it to expire while the
713 // chip is sleeping, for example.
714 scheduler_timer.reset();
715
716 (return_reason, time_executed_us)
717 }
718
719 /// Method to invoke a system call on a particular process. Applies the
720 /// kernel system call filtering policy (if any). Handles `Yield` and
721 /// `Exit`, dispatches `Memop` to `memop::memop`, and dispatches peripheral
722 /// driver system calls to peripheral driver capsules through the platforms
723 /// `with_driver` method.
724 #[inline]
725 fn handle_syscall<KR: KernelResources<C>, C: Chip>(
726 &self,
727 resources: &KR,
728 process: &dyn process::Process,
729 syscall: Syscall,
730 ) {
731 // Hook for process debugging.
732 process.debug_syscall_called(syscall);
733
734 // Enforce platform-specific syscall filtering here.
735 //
736 // Before continuing to handle non-yield syscalls the kernel first
737 // checks if the platform wants to block that syscall for the process,
738 // and if it does, sets a return value which is returned to the calling
739 // process.
740 //
741 // Filtering a syscall (i.e. blocking the syscall from running) does not
742 // cause the process to lose its timeslice. The error will be returned
743 // immediately (assuming the process has not already exhausted its
744 // timeslice) allowing the process to decide how to handle the error.
745 match syscall {
746 Syscall::Yield {
747 which: _,
748 param_a: _,
749 param_b: _,
750 } => {} // Yield is not filterable.
751 Syscall::Exit {
752 which: _,
753 completion_code: _,
754 } => {} // Exit is not filterable.
755 Syscall::Memop {
756 operand: _,
757 arg0: _,
758 } => {} // Memop is not filterable.
759 _ => {
760 // Check all other syscalls for filtering.
761 if let Err(response) = resources.syscall_filter().filter_syscall(process, &syscall)
762 {
763 process.set_syscall_return_value(SyscallReturn::Failure(response));
764
765 if config::CONFIG.trace_syscalls {
766 debug!(
767 "[{:?}] Filtered: {:?} was rejected with {:?}",
768 process.processid(),
769 syscall,
770 response
771 );
772 }
773
774 return;
775 }
776 }
777 }
778
779 // Handle each of the syscalls.
780 match syscall {
781 Syscall::Memop { operand, arg0 } => {
782 let rval = memop::memop(process, operand, arg0);
783 if config::CONFIG.trace_syscalls {
784 debug!(
785 "[{:?}] memop({}, {:#x}) = {:?}",
786 process.processid(),
787 operand,
788 arg0,
789 rval
790 );
791 }
792 process.set_syscall_return_value(rval);
793 }
794 Syscall::Yield {
795 which,
796 param_a,
797 param_b,
798 } => {
799 if config::CONFIG.trace_syscalls {
800 debug!("[{:?}] yield. which: {}", process.processid(), which);
801 }
802 match which.try_into() {
803 Ok(YieldCall::NoWait) => {
804 // If this is a `Yield-WaitFor` AND there are no pending
805 // tasks, then return immediately. Otherwise, go into
806 // the yielded state and execute tasks now or when they
807 // arrive.
808 let has_tasks = process.has_tasks();
809
810 // Set the "did I trigger upcalls" flag.
811 // If address is invalid does nothing.
812 //
813 // # Safety
814 //
815 // This is fine as long as no references to the
816 // process's memory exist. We do not have a reference,
817 // so we can safely call `set_byte()`.
818 unsafe {
819 let address = param_a as *mut u8;
820 process.set_byte(address, has_tasks as u8);
821 }
822
823 if has_tasks {
824 process.set_yielded_state();
825 }
826 }
827
828 Ok(YieldCall::Wait) => {
829 process.set_yielded_state();
830 }
831
832 Ok(YieldCall::WaitFor) => {
833 let upcall_id = UpcallId {
834 driver_num: param_a,
835 subscribe_num: param_b,
836 };
837 process.set_yielded_for_state(upcall_id);
838 }
839
840 _ => {
841 // Only 0, 1, and 2 are valid, so this is not a valid
842 // yield system call, Yield does not have a return value
843 // because it can push a function call onto the stack;
844 // just return control to the process.
845 }
846 }
847 }
848 Syscall::Subscribe { driver_number, .. }
849 | Syscall::Command { driver_number, .. }
850 | Syscall::ReadWriteAllow { driver_number, .. }
851 | Syscall::UserspaceReadableAllow { driver_number, .. }
852 | Syscall::ReadOnlyAllow { driver_number, .. } => {
853 resources
854 .syscall_driver_lookup()
855 .with_driver(driver_number, |driver| match syscall {
856 Syscall::Subscribe {
857 driver_number,
858 subdriver_number,
859 upcall_ptr,
860 appdata,
861 } => {
862 // A upcall is identified as a tuple of the driver
863 // number and the subdriver number.
864 let upcall_id = UpcallId {
865 driver_num: driver_number,
866 subscribe_num: subdriver_number,
867 };
868
869 // TODO: when the compiler supports capability types
870 // bring this back as a NonNull
871 // type. https://github.com/tock/tock/issues/4134.
872 //
873 // Previously, we had a NonNull type (that had a niche)
874 // here, and could wrap that in Option to fill the niche
875 // and handle the Null case. CapabilityPtr is filling
876 // the gap left by * const(), which does not have the
877 // niche and allows NULL internally. Having a CHERI
878 // capability type with a niche is (maybe?) predicated
879 // on having better compiler support.
880 // Option<NonNull<()>> is preferable here, and it should
881 // go back to it just as soon as we can express "non
882 // null capability". For now, checking for the null case
883 // is handled internally in each `map_or` call.
884 //
885 //First check if `upcall_ptr` is null. A null
886 //`upcall_ptr` will result in `None` here and
887 //represents the special "unsubscribe" operation.
888 //let ptr = NonNull::new(upcall_ptr);
889
890 // For convenience create an `Upcall` type now. This is
891 // just a data structure and doesn't do any checking or
892 // conversion.
893 let upcall = Upcall::new(process.processid(), upcall_id, appdata, upcall_ptr);
894
895 // If `ptr` is not null, we must first verify that the
896 // upcall function pointer is within process accessible
897 // memory. Per TRD104:
898 //
899 // > If the passed upcall is not valid (is outside
900 // > process executable memory...), the kernel...MUST
901 // > immediately return a failure with a error code of
902 // > `INVALID`.
903 let rval1 = upcall_ptr.map_or(None, |upcall_ptr_nonnull| {
904 if !process.is_valid_upcall_function_pointer(upcall_ptr_nonnull.as_ptr()) {
905 Some(ErrorCode::INVAL)
906 } else {
907 None
908 }
909 });
910
911 // If the upcall is either null or valid, then we
912 // continue handling the upcall.
913 let rval = match rval1 {
914 Some(err) => upcall.into_subscribe_failure(err),
915 None => {
916 match driver {
917 Some(driver) => {
918 // At this point we must save the new
919 // upcall and return the old. The
920 // upcalls are stored by the core kernel
921 // in the grant region so we can
922 // guarantee a correct upcall swap.
923 // However, we do need help with
924 // initially allocating the grant if
925 // this driver has never been used
926 // before.
927 //
928 // To avoid the overhead with checking
929 // for process liveness and grant
930 // allocation, we assume the grant is
931 // initially allocated. If it turns out
932 // it isn't we ask the capsule to
933 // allocate the grant.
934 match crate::grant::subscribe(process, upcall) {
935 Ok(upcall) => upcall.into_subscribe_success(),
936 Err((upcall, err @ ErrorCode::NOMEM)) => {
937 // If we get a memory error, we
938 // always try to allocate the
939 // grant since this could be the
940 // first time the grant is
941 // getting accessed.
942 match try_allocate_grant(driver, process) {
943 AllocResult::NewAllocation => {
944 // Now we try again. It
945 // is possible that the
946 // capsule did not
947 // actually allocate the
948 // grant, at which point
949 // this will fail again
950 // and we return an
951 // error to userspace.
952 match crate::grant::subscribe(
953 process, upcall,
954 ) {
955 // An Ok() returns
956 // the previous
957 // upcall, while
958 // Err() returns the
959 // one that was just
960 // passed.
961 Ok(upcall) => {
962 upcall.into_subscribe_success()
963 }
964 Err((upcall, err)) => {
965 upcall.into_subscribe_failure(err)
966 }
967 }
968 }
969 alloc_failure => {
970 // We didn't actually
971 // create a new alloc,
972 // so just error.
973 match (
974 config::CONFIG.trace_syscalls,
975 alloc_failure,
976 ) {
977 (true, AllocResult::NoAllocation) => {
978 debug!("[{:?}] WARN driver #{:x} did not allocate grant",
979 process.processid(), driver_number);
980 }
981 (true, AllocResult::SameAllocation) => {
982 debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts",
983 process.processid(), driver_number);
984 }
985 _ => {}
986 }
987 upcall.into_subscribe_failure(err)
988 }
989 }
990 }
991 Err((upcall, err)) => {
992 upcall.into_subscribe_failure(err)
993 }
994 }
995 }
996 None => upcall.into_subscribe_failure(ErrorCode::NODEVICE),
997 }
998 }
999 };
1000
1001 // Per TRD104, we only clear upcalls if the subscribe
1002 // will return success. At this point we know the result
1003 // and clear if necessary.
1004 if rval.is_success() {
1005 // Only one upcall should exist per tuple. To ensure
1006 // that there are no pending upcalls with the same
1007 // identifier but with the old function pointer, we
1008 // clear them now.
1009 let _ =process.remove_pending_upcalls(upcall_id);
1010 }
1011
1012 if config::CONFIG.trace_syscalls {
1013 debug!(
1014 "[{:?}] subscribe({:#x}, {}, @{:#x}, {:#x}) = {:?}",
1015 process.processid(),
1016 driver_number,
1017 subdriver_number,
1018 upcall_ptr,
1019 appdata,
1020 rval
1021 );
1022 }
1023
1024 process.set_syscall_return_value(rval);
1025 }
1026 Syscall::Command {
1027 driver_number,
1028 subdriver_number,
1029 arg0,
1030 arg1,
1031 } => {
1032 let cres = match driver {
1033 Some(d) => d.command(subdriver_number, arg0, arg1, process.processid()),
1034 None => CommandReturn::failure(ErrorCode::NODEVICE),
1035 };
1036
1037 let res = SyscallReturn::from_command_return(cres);
1038
1039 if config::CONFIG.trace_syscalls {
1040 debug!(
1041 "[{:?}] cmd({:#x}, {}, {:#x}, {:#x}) = {:?}",
1042 process.processid(),
1043 driver_number,
1044 subdriver_number,
1045 arg0,
1046 arg1,
1047 res,
1048 );
1049 }
1050 process.set_syscall_return_value(res);
1051 }
1052 Syscall::ReadWriteAllow {
1053 driver_number,
1054 subdriver_number,
1055 allow_address,
1056 allow_size,
1057 } => {
1058 let res = match driver {
1059 Some(driver) => {
1060 // Try to create an appropriate
1061 // [`ReadWriteProcessBuffer`]. This method will
1062 // ensure that the memory in question is located
1063 // in the process-accessible memory space.
1064 match process
1065 .build_readwrite_process_buffer(allow_address, allow_size)
1066 {
1067 Ok(rw_pbuf) => {
1068 // Creating the
1069 // [`ReadWriteProcessBuffer`] worked,
1070 // try to set in grant.
1071 match crate::grant::allow_rw(
1072 process,
1073 driver_number,
1074 subdriver_number,
1075 rw_pbuf,
1076 ) {
1077 Ok(rw_pbuf) => {
1078 let (ptr, len) = rw_pbuf.consume();
1079 SyscallReturn::AllowReadWriteSuccess(ptr, len)
1080 }
1081 Err((rw_pbuf, err @ ErrorCode::NOMEM)) => {
1082 // If we get a memory error, we
1083 // always try to allocate the
1084 // grant since this could be the
1085 // first time the grant is
1086 // getting accessed.
1087 match try_allocate_grant(driver, process) {
1088 AllocResult::NewAllocation => {
1089 // If we actually
1090 // allocated a new
1091 // grant, try again and
1092 // honor the result.
1093 match crate::grant::allow_rw(
1094 process,
1095 driver_number,
1096 subdriver_number,
1097 rw_pbuf,
1098 ) {
1099 Ok(rw_pbuf) => {
1100 let (ptr, len) = rw_pbuf.consume();
1101 SyscallReturn::AllowReadWriteSuccess(
1102 ptr, len,
1103 )
1104 }
1105 Err((rw_pbuf, err)) => {
1106 let (ptr, len) = rw_pbuf.consume();
1107 SyscallReturn::AllowReadWriteFailure(
1108 err, ptr, len,
1109 )
1110 }
1111 }
1112 }
1113 alloc_failure => {
1114 // We didn't actually
1115 // create a new alloc,
1116 // so just error.
1117 match (
1118 config::CONFIG.trace_syscalls,
1119 alloc_failure,
1120 ) {
1121 (true, AllocResult::NoAllocation) => {
1122 debug!("[{:?}] WARN driver #{:x} did not allocate grant",
1123 process.processid(), driver_number);
1124 }
1125 (true, AllocResult::SameAllocation) => {
1126 debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts",
1127 process.processid(), driver_number);
1128 }
1129 _ => {}
1130 }
1131 let (ptr, len) = rw_pbuf.consume();
1132 SyscallReturn::AllowReadWriteFailure(
1133 err, ptr, len,
1134 )
1135 }
1136 }
1137 }
1138 Err((rw_pbuf, err)) => {
1139 let (ptr, len) = rw_pbuf.consume();
1140 SyscallReturn::AllowReadWriteFailure(err, ptr, len)
1141 }
1142 }
1143 }
1144 Err(allow_error) => {
1145 // There was an error creating the
1146 // [`ReadWriteProcessBuffer`]. Report
1147 // back to the process with the original
1148 // parameters.
1149 SyscallReturn::AllowReadWriteFailure(
1150 allow_error,
1151 allow_address,
1152 allow_size,
1153 )
1154 }
1155 }
1156 }
1157 None => SyscallReturn::AllowReadWriteFailure(
1158 ErrorCode::NODEVICE,
1159 allow_address,
1160 allow_size,
1161 ),
1162 };
1163
1164 if config::CONFIG.trace_syscalls {
1165 debug!(
1166 "[{:?}] read-write allow({:#x}, {}, @{:#x}, {}) = {:?}",
1167 process.processid(),
1168 driver_number,
1169 subdriver_number,
1170 allow_address as usize,
1171 allow_size,
1172 res
1173 );
1174 }
1175 process.set_syscall_return_value(res);
1176 }
1177 Syscall::UserspaceReadableAllow {
1178 driver_number,
1179 subdriver_number,
1180 allow_address,
1181 allow_size,
1182 } => {
1183 let res = match driver {
1184 Some(d) => {
1185 // Try to create an appropriate
1186 // [`UserspaceReadableProcessBuffer`]. This
1187 // method will ensure that the memory in
1188 // question is located in the process-accessible
1189 // memory space.
1190 match process
1191 .build_readwrite_process_buffer(allow_address, allow_size)
1192 {
1193 Ok(rw_pbuf) => {
1194 // Creating the
1195 // [`UserspaceReadableProcessBuffer`]
1196 // worked, provide it to the capsule.
1197 match d.allow_userspace_readable(
1198 process.processid(),
1199 subdriver_number,
1200 rw_pbuf,
1201 ) {
1202 Ok(returned_pbuf) => {
1203 // The capsule has accepted the
1204 // allow operation. Pass the
1205 // previous buffer information
1206 // back to the process.
1207 let (ptr, len) = returned_pbuf.consume();
1208 SyscallReturn::UserspaceReadableAllowSuccess(
1209 ptr, len,
1210 )
1211 }
1212 Err((rejected_pbuf, err)) => {
1213 // The capsule has rejected the
1214 // allow operation. Pass the new
1215 // buffer information back to
1216 // the process.
1217 let (ptr, len) = rejected_pbuf.consume();
1218 SyscallReturn::UserspaceReadableAllowFailure(
1219 err, ptr, len,
1220 )
1221 }
1222 }
1223 }
1224 Err(allow_error) => {
1225 // There was an error creating the
1226 // [`UserspaceReadableProcessBuffer`].
1227 // Report back to the process.
1228 SyscallReturn::UserspaceReadableAllowFailure(
1229 allow_error,
1230 allow_address,
1231 allow_size,
1232 )
1233 }
1234 }
1235 }
1236
1237 None => SyscallReturn::UserspaceReadableAllowFailure(
1238 ErrorCode::NODEVICE,
1239 allow_address,
1240 allow_size,
1241 ),
1242 };
1243
1244 if config::CONFIG.trace_syscalls {
1245 debug!(
1246 "[{:?}] userspace readable allow({:#x}, {}, @{:#x}, {}) = {:?}",
1247 process.processid(),
1248 driver_number,
1249 subdriver_number,
1250 allow_address as usize,
1251 allow_size,
1252 res
1253 );
1254 }
1255 process.set_syscall_return_value(res);
1256 }
1257 Syscall::ReadOnlyAllow {
1258 driver_number,
1259 subdriver_number,
1260 allow_address,
1261 allow_size,
1262 } => {
1263 let res = match driver {
1264 Some(driver) => {
1265 // Try to create an appropriate
1266 // [`ReadOnlyProcessBuffer`]. This method will
1267 // ensure that the memory in question is located
1268 // in the process-accessible memory space.
1269 match process
1270 .build_readonly_process_buffer(allow_address, allow_size)
1271 {
1272 Ok(ro_pbuf) => {
1273 // Creating the
1274 // [`ReadOnlyProcessBuffer`] worked, try
1275 // to set in grant.
1276 match crate::grant::allow_ro(
1277 process,
1278 driver_number,
1279 subdriver_number,
1280 ro_pbuf,
1281 ) {
1282 Ok(ro_pbuf) => {
1283 let (ptr, len) = ro_pbuf.consume();
1284 SyscallReturn::AllowReadOnlySuccess(ptr, len)
1285 }
1286 Err((ro_pbuf, err @ ErrorCode::NOMEM)) => {
1287 // If we get a memory error, we
1288 // always try to allocate the
1289 // grant since this could be the
1290 // first time the grant is
1291 // getting accessed.
1292 match try_allocate_grant(driver, process) {
1293 AllocResult::NewAllocation => {
1294 // If we actually
1295 // allocated a new
1296 // grant, try again and
1297 // honor the result.
1298 match crate::grant::allow_ro(
1299 process,
1300 driver_number,
1301 subdriver_number,
1302 ro_pbuf,
1303 ) {
1304 Ok(ro_pbuf) => {
1305 let (ptr, len) = ro_pbuf.consume();
1306 SyscallReturn::AllowReadOnlySuccess(
1307 ptr, len,
1308 )
1309 }
1310 Err((ro_pbuf, err)) => {
1311 let (ptr, len) = ro_pbuf.consume();
1312 SyscallReturn::AllowReadOnlyFailure(
1313 err, ptr, len,
1314 )
1315 }
1316 }
1317 }
1318 alloc_failure => {
1319 // We didn't actually
1320 // create a new alloc,
1321 // so just error.
1322 match (
1323 config::CONFIG.trace_syscalls,
1324 alloc_failure,
1325 ) {
1326 (true, AllocResult::NoAllocation) => {
1327 debug!("[{:?}] WARN driver #{:x} did not allocate grant",
1328 process.processid(), driver_number);
1329 }
1330 (true, AllocResult::SameAllocation) => {
1331 debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts",
1332 process.processid(), driver_number);
1333 }
1334 _ => {}
1335 }
1336 let (ptr, len) = ro_pbuf.consume();
1337 SyscallReturn::AllowReadOnlyFailure(
1338 err, ptr, len,
1339 )
1340 }
1341 }
1342 }
1343 Err((ro_pbuf, err)) => {
1344 let (ptr, len) = ro_pbuf.consume();
1345 SyscallReturn::AllowReadOnlyFailure(err, ptr, len)
1346 }
1347 }
1348 }
1349 Err(allow_error) => {
1350 // There was an error creating the
1351 // [`ReadOnlyProcessBuffer`]. Report
1352 // back to the process with the original
1353 // parameters.
1354 SyscallReturn::AllowReadOnlyFailure(
1355 allow_error,
1356 allow_address,
1357 allow_size,
1358 )
1359 }
1360 }
1361 }
1362 None => SyscallReturn::AllowReadOnlyFailure(
1363 ErrorCode::NODEVICE,
1364 allow_address,
1365 allow_size,
1366 ),
1367 };
1368
1369 if config::CONFIG.trace_syscalls {
1370 debug!(
1371 "[{:?}] read-only allow({:#x}, {}, @{:#x}, {}) = {:?}",
1372 process.processid(),
1373 driver_number,
1374 subdriver_number,
1375 allow_address as usize,
1376 allow_size,
1377 res
1378 );
1379 }
1380
1381 process.set_syscall_return_value(res);
1382 }
1383 Syscall::Yield { .. }
1384 | Syscall::Exit { .. }
1385 | Syscall::Memop { .. } => {
1386 // These variants must not be reachable due to the outer
1387 // match statement:
1388 debug_assert!(false, "Kernel system call handling invariant violated!");
1389 },
1390 })
1391 }
1392 Syscall::Exit {
1393 which,
1394 completion_code,
1395 } => {
1396 // exit try restart modifies the ID of the process.
1397 let old_process_id = process.processid();
1398 let optional_return_value = match which {
1399 // The process called the `exit-terminate` system call.
1400 0 => {
1401 process.terminate(Some(completion_code as u32));
1402 None
1403 }
1404 // The process called the `exit-restart` system call.
1405 1 => {
1406 process.try_restart(Some(completion_code as u32));
1407 None
1408 }
1409 // The process called an invalid variant of the Exit
1410 // system call class.
1411 _ => {
1412 let return_value = SyscallReturn::Failure(ErrorCode::NOSUPPORT);
1413 process.set_syscall_return_value(return_value);
1414 Some(return_value)
1415 }
1416 };
1417 if config::CONFIG.trace_syscalls {
1418 debug!(
1419 "[{:?}] exit(which: {}, completion_code: {}) = {:?}",
1420 old_process_id, which, completion_code, optional_return_value,
1421 );
1422 }
1423 }
1424 }
1425 }
1426}