Skip to main content

kernel/platform/
chip.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 microcontrollers in Tock.
6
7use crate::platform::mpu;
8use crate::syscall;
9use crate::utilities::io_write::IoWrite;
10use core::fmt::Write;
11use core::panic::PanicInfo;
12
13/// Interface for individual MCUs.
14///
15/// The trait defines chip-specific properties of Tock's operation. These
16/// include whether and which memory protection mechanism and scheduler_timer to
17/// use, how to switch between the kernel and userland applications, and how to
18/// handle hardware events.
19///
20/// Each microcontroller should define a struct and implement this trait.
21pub trait Chip {
22    /// The particular Memory Protection Unit (MPU) for this chip.
23    type MPU: mpu::MPU;
24
25    /// Provider to query the currently running thread ID.
26    type ThreadIdProvider: ThreadIdProvider;
27
28    /// The implementation of the interface between userspace and the kernel for
29    /// this specific chip. Likely this is architecture specific, but individual
30    /// chips may have various custom requirements.
31    type UserspaceKernelBoundary: syscall::UserspaceKernelBoundary;
32
33    /// Run any necessary initialization for this chip.
34    ///
35    /// This should be called first by the board in its `main()` function.
36    ///
37    /// This should contain any necessary initialization steps, including:
38    ///
39    /// - Any architecture-specific setup
40    /// - Any errata fixes
41    /// - Any configuration needed for the chip to continue booting the kernel
42    ///
43    /// `init()` does not use `&self` so it can be called before the actual
44    /// `Chip` is instantiated. These initializations may be needed very soon
45    /// after the chip boots, and potentially much before the actual `Chip` can
46    /// be created.
47    ///
48    /// `init()` is marked safe as the implementation must handle any safety
49    /// requirements of the underlying initialization steps. As this will be
50    /// called very early when the chip boots, it is not useful or reasonable
51    /// for the kernel's main function to address any safety requirements.
52    fn init();
53
54    /// The kernel calls this function to tell the chip to check for all pending
55    /// interrupts and to correctly dispatch them to the peripheral drivers for
56    /// the chip.
57    ///
58    /// This function should loop internally until all interrupts have been
59    /// handled. It is ok, however, if an interrupt occurs after the last check
60    /// but before this function returns. The kernel will handle this edge case.
61    fn service_pending_interrupts(&self);
62
63    /// Ask the chip to check if there are any pending interrupts.
64    fn has_pending_interrupts(&self) -> bool;
65
66    /// Returns a reference to the implementation for the MPU on this chip.
67    fn mpu(&self) -> &Self::MPU;
68
69    /// Returns a reference to the implementation for the interface between
70    /// userspace and kernelspace.
71    fn userspace_kernel_boundary(&self) -> &Self::UserspaceKernelBoundary;
72
73    /// Called when there is nothing left for the chip to do and it should enter
74    /// a low power sleep state. This low power sleep state should allow
75    /// interrupts to still be active so that the next interrupt event wakes the
76    /// chip and resumes the scheduler.
77    fn sleep(&self);
78
79    /// Run a function in an atomic state w.r.t. to the current core. This
80    /// means that interrupts are disabled so that an interrupt will not fire
81    /// during the passed in function's execution, but *does not* make any
82    /// guarantees about memory consistency on a multi-core system.
83    fn with_interrupts_disabled<F, R>(&self, f: F) -> R
84    where
85        F: FnOnce() -> R;
86
87    /// Print out debug information about the current chip state (system
88    /// registers, MPU configuration, etc.) to a supplied writer.
89    ///
90    /// This function may be called across thread boundaries (such as from a
91    /// panic handler). As implementors of `Chip` do not have to be `Send` or
92    /// `Sync`, `&self` may not be available in these contexts. Therefore, this
93    /// function instead accepts an `Option<&Self>` parameter named `this`. In
94    /// contexts where `&self` is available, callers should invoke this function
95    /// by passing `Some(&self)` to `this`. Otherwise, `this` will be set to
96    /// `None`. The implementation of `print_state` may not print certain
97    /// information if it depends on runtime-accessible state in `Self`, but
98    /// that reference is not provided.
99    ///
100    /// # Safety
101    ///
102    /// Implementations may need to access low-level chip-specific hardware that
103    /// isn't safe to access during the normal operation of the chip. This
104    /// function may only be called from a panic context.
105    unsafe fn print_state(this: Option<&Self>, writer: &mut dyn Write);
106}
107
108/// Interface for retrieving the currently executing thread.
109///
110/// This is used to enforce correctness with shared state that has access
111/// restrictions (e.g., only a single thread can access a specific value).
112///
113/// Many embedded platforms are single-core and only permit a single execution
114/// thread at a time. However, interrupts can typically occur at any time, and
115/// the execution of an interrupt service routine (ISR) constitutes a second
116/// thread. Implementations of this trait must be able to differentiate between
117/// at minimum the main thread of execution and an ISR execution, but may also
118/// consider multiple execution threads if available on a particular device.
119///
120/// # Safety
121///
122/// This thread is marked as `unsafe` as implementation must guarantee its
123/// correctness. Users of this trait are allowed to make soundness guarantees
124/// based on the implementation being correct. Failing to provide a correct
125/// implementation can lead to unsound behavior. By implementing this trait,
126/// providers are guaranteeing the implementations are always correct for the
127/// given hardware platform.
128pub unsafe trait ThreadIdProvider {
129    /// Return a unique ID for the currently executing thread.
130    ///
131    /// The unique ID must fit in a `usize` and must be unique and consistent
132    /// for the currently running thread. The actual value is opaque and there
133    /// is no assumption about the meaning of the assigned IDs. Implementations
134    /// are allowed to arbitrarily assign IDs to threads as long as the IDs are
135    /// unique and consistent.
136    fn running_thread_id() -> usize;
137}
138
139/// Interface for handling interrupts on a hardware chip.
140///
141/// Each board must construct an implementation of this trait to handle specific
142/// interrupts. When an interrupt (identified by number) has triggered and
143/// should be handled, the implementation of this trait will be called with the
144/// interrupt number. The implementation can then handle the interrupt, or
145/// return `false` to signify that it does not know how to handle the interrupt.
146///
147/// This functionality is given this `InterruptService` interface so that
148/// multiple objects can be chained together to handle interrupts for a chip.
149/// This is useful for code organization and removing the need for duplication
150/// when multiple variations of a specific microcontroller exist. Then a shared,
151/// base object can handle most interrupts, and variation-specific objects can
152/// handle the variation-specific interrupts.
153///
154/// To simplify structuring the Rust code when using `InterruptService`, the
155/// interrupt number should be passed "top-down". That is, an interrupt to be
156/// handled will first be passed to the `InterruptService` object that is most
157/// specific. If that object cannot handle the interrupt, then it should
158/// maintain a reference to the second most specific object, and return by
159/// calling to that object to handle the interrupt. This continues until the
160/// base object handles the interrupt or decides that the chip does not know how
161/// to handle the interrupt. For example, consider a `nRF52840` chip that
162/// depends on the `nRF52` crate. If both have specific interrupts they know how
163/// to handle, the flow would look like:
164///
165/// ```ignore
166///           +---->nrf52840_peripherals
167///           |        |
168///           |        |
169///           |        v
170/// kernel-->nrf52     nrf52_peripherals
171/// ```
172/// where the kernel instructs the `nrf52` crate to handle interrupts, and if
173/// there is an interrupt ready then that interrupt is passed through the
174/// InterruptService objects until something can service it.
175pub trait InterruptService {
176    /// Service an interrupt, if supported by this chip. If this interrupt
177    /// number is not supported, return false.
178    fn service_interrupt(&self, interrupt: u32) -> bool;
179}
180
181/// A default implementation of `InterruptService` that handles nothing and returns `false`.
182impl InterruptService for () {
183    fn service_interrupt(&self, _interrupt: u32) -> bool {
184        false
185    }
186}
187
188/// Generic operations that clock-like things are expected to support.
189pub trait ClockInterface {
190    fn is_enabled(&self) -> bool;
191    fn enable(&self);
192    fn disable(&self);
193}
194
195/// Helper struct for interfaces that expect clocks, but have no clock control.
196pub struct NoClockControl {}
197impl ClockInterface for NoClockControl {
198    fn is_enabled(&self) -> bool {
199        true
200    }
201    fn enable(&self) {}
202    fn disable(&self) {}
203}
204
205/// Instance of NoClockControl for things that need references to
206/// `ClockInterface` objects.
207pub const NO_CLOCK_CONTROL: NoClockControl = NoClockControl {};
208
209/// Interface for chips to create a synchronous writer for panics.
210///
211/// Any mechanism that can output a panic message during a panic must implement
212/// [`PanicWriter`] to enable the `panic()` functions to write the output. This
213/// requires the mechanism to provide a new constructor for the writer that
214/// creates a synchronous writer that implements [`IoWrite`].
215///
216/// This is a dedicated trait because synchronous I/O is only used for panic
217/// handling. This allows chips to clearly separate synchronous implementations
218/// that are a special case only for panics.
219pub trait PanicWriter {
220    /// The configuration data the mechanism needs to configure the writer for
221    /// panic output.
222    type Config;
223
224    /// Create a new synchronous writer capable of sending panic messages.
225    ///
226    /// The writer must implement [`IoWrite`] (which is just `std:io::Write`
227    /// implemented for no_std).
228    ///
229    /// This function requires a [`PanicInfo`] reference, but the implementation
230    /// should not use it. This only enforces that this function is only called
231    /// from a panic context and not during normal operation.
232    fn create_panic_writer(
233        config: Self::Config,
234        _panic: &PanicInfo,
235    ) -> impl IoWrite + core::fmt::Write;
236}