kernel/platform/mpu.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//! Interface for configuring the Memory Protection Unit.
6
7use core::fmt::{self, Display};
8
9/// User mode access permissions.
10#[derive(Copy, Clone, Debug)]
11pub enum Permissions {
12 ReadWriteExecute,
13 ReadWriteOnly,
14 ReadExecuteOnly,
15 ReadOnly,
16 ExecuteOnly,
17}
18
19/// MPU region.
20///
21/// This is one contiguous address space protected by the MPU.
22#[derive(Copy, Clone, PartialEq, Eq)]
23pub struct Region {
24 /// The memory address where the region starts.
25 ///
26 /// For maximum compatibility, we use a u8 pointer, however, note that many
27 /// memory protection units have very strict alignment requirements for the
28 /// memory regions protected by the MPU.
29 start_address: *const u8,
30
31 /// The number of bytes of memory in the MPU region.
32 size: usize,
33}
34
35impl Region {
36 /// Create a new MPU region with a given starting point and length in bytes.
37 pub fn new(start_address: *const u8, size: usize) -> Region {
38 Region {
39 start_address,
40 size,
41 }
42 }
43
44 /// Getter: retrieve the address of the start of the MPU region.
45 pub fn start_address(&self) -> *const u8 {
46 self.start_address
47 }
48
49 /// Getter: retrieve the length of the region in bytes.
50 pub fn size(&self) -> usize {
51 self.size
52 }
53}
54
55/// Null type for the default type of the `MpuConfig` type in an implementation
56/// of the `MPU` trait.
57///
58/// This custom type allows us to implement `Display` with an empty
59/// implementation to meet the constraint on `type MpuConfig`.
60pub struct MpuConfigDefault;
61
62impl Display for MpuConfigDefault {
63 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 Ok(())
65 }
66}
67
68/// The generic trait that particular memory protection unit implementations
69/// need to implement.
70///
71/// This trait is a blend of relatively generic MPU functionality that should be
72/// common across different MPU implementations, and more specific requirements
73/// that Tock needs to support protecting applications. While a less
74/// Tock-specific interface may be desirable, due to the sometimes complex
75/// alignment rules and other restrictions imposed by MPU hardware, some of the
76/// Tock details have to be passed into this interface. That allows the MPU
77/// implementation to have more flexibility when satisfying the protection
78/// requirements, and also allows the MPU to specify some addresses used by the
79/// kernel when deciding where to place certain application memory regions so
80/// that the MPU can appropriately provide protection for those memory regions.
81///
82/// # Safety
83///
84/// This is an `unsafe trait`, as it is crucial to uphold Tock's isolation
85/// properties, and thus safety of the Tock kernel. Users of this trait must be
86/// able to rely on its implementations being correct. Specifically, they must
87/// ensure that applications only have access to the configured MPU regions and
88/// no kernel memory.
89pub unsafe trait MPU {
90 /// MPU-specific state that defines a particular configuration for the MPU.
91 /// That is, this should contain all of the required state such that the
92 /// implementation can be passed an object of this type and it should be
93 /// able to correctly and entirely configure the MPU.
94 ///
95 /// This state will be held on a per-process basis as a way to cache all of
96 /// the process settings. When the kernel switches to a new process it will
97 /// use the `MpuConfig` for that process to quickly configure the MPU.
98 ///
99 /// It is `Default` so we can create empty state when the process is
100 /// created, and `Display` so that the `panic!()` output can display the
101 /// current state to help with debugging.
102 type MpuConfig: Display;
103
104 /// Enables the MPU for userspace apps.
105 ///
106 /// This function must enable the permission restrictions on the various
107 /// regions protected by the MPU.
108 fn enable_app_mpu(&self);
109
110 /// Disables the MPU for userspace apps.
111 ///
112 /// This function must disable any memory protection rules that were
113 /// previously setup for an app, if those rules can prevent the kernel from
114 /// accessing this application's memory.
115 ///
116 /// This function is intended to be called when switching back from an
117 /// application to the kernel, as some platforms prevent kernel-mode from
118 /// accessing memory made accessible to less-privileged applications. This
119 /// restriction is intended to increase security by making it harder to
120 /// inject application-controlled data into a buggy kernel (e.g., through an
121 /// incorrect context switch application that provides kernel-mode
122 /// privileges to applications). However, it also prevents the kernel from
123 /// managing application state or accessing application-provided data.
124 ///
125 /// # Safety
126 ///
127 /// This function is marked `unsafe`, as invoking it and subsequently
128 /// switching back to an application could, depending on the underlying MPU
129 /// implementation, provide that application access to kernel memory. Before
130 /// switching back to an application, and after calling this function, the
131 /// kernel must first re-enable memory protection through a call to
132 /// [`MPU::enable_app_mpu`].
133 unsafe fn disable_app_mpu(&self);
134
135 /// Returns the maximum number of regions supported by the MPU.
136 fn number_total_regions(&self) -> usize;
137
138 /// Creates a new empty MPU configuration.
139 ///
140 /// The returned configuration must not have any userspace-accessible
141 /// regions pre-allocated.
142 ///
143 /// The underlying implementation may only be able to allocate a finite
144 /// number of MPU configurations. It may return `None` if this resource is
145 /// exhausted.
146 fn new_config(&self) -> Option<Self::MpuConfig>;
147
148 /// Resets an MPU configuration.
149 ///
150 /// This method resets an MPU configuration to its initial state, as
151 /// returned by [`MPU::new_config`]. After invoking this operation, it must
152 /// not have any userspace-acessible regions pre-allocated.
153 fn reset_config(&self, config: &mut Self::MpuConfig);
154
155 /// Allocates a new MPU region.
156 ///
157 /// An implementation must allocate an MPU region at least `min_region_size`
158 /// bytes in size within the specified stretch of unallocated memory, and
159 /// with the specified user mode permissions, and store it in `config`. The
160 /// allocated region may not overlap any of the regions already stored in
161 /// `config`.
162 ///
163 /// # Arguments
164 ///
165 /// - `unallocated_memory_start`: start of unallocated memory
166 /// - `unallocated_memory_size`: size of unallocated memory
167 /// - `min_region_size`: minimum size of the region
168 /// - `permissions`: permissions for the region
169 /// - `config`: MPU region configuration
170 ///
171 /// # Return Value
172 ///
173 /// Returns the start and size of the allocated MPU region. If it is
174 /// infeasible to allocate the MPU region, returns None.
175 fn allocate_region(
176 &self,
177 unallocated_memory_start: *const u8,
178 unallocated_memory_size: usize,
179 min_region_size: usize,
180 permissions: Permissions,
181 config: &mut Self::MpuConfig,
182 ) -> Option<Region>;
183
184 /// Removes an MPU region within app-owned memory.
185 ///
186 /// An implementation must remove the MPU region that matches the region parameter if it exists.
187 /// If there is not a region that matches exactly, then the implementation may return an Error.
188 /// Implementors should not remove the app_memory_region and should return an Error if that
189 /// region is supplied.
190 ///
191 /// # Arguments
192 ///
193 /// - `region`: a region previously allocated with `allocate_region`
194 /// - `config`: MPU region configuration
195 ///
196 /// # Return Value
197 ///
198 /// Returns an error if the specified region is not exactly mapped to the process as specified
199 fn remove_memory_region(&self, region: Region, config: &mut Self::MpuConfig) -> Result<(), ()>;
200
201 /// Chooses the location for a process's memory, and allocates an MPU region
202 /// covering the app-owned part.
203 ///
204 /// An implementation must choose a contiguous block of memory that is at
205 /// least `min_memory_size` bytes in size and lies completely within the
206 /// specified stretch of unallocated memory.
207 ///
208 /// It must also allocate an MPU region with the following properties:
209 ///
210 /// 1. The region covers at least the first `initial_app_memory_size` bytes
211 /// at the beginning of the memory block.
212 /// 2. The region does not overlap the last `initial_kernel_memory_size`
213 /// bytes.
214 /// 3. The region has the user mode permissions specified by `permissions`.
215 ///
216 /// The end address of app-owned memory will increase in the future, so the
217 /// implementation should choose the location of the process memory block
218 /// such that it is possible for the MPU region to grow along with it. The
219 /// implementation must store the allocated region in `config`. The
220 /// allocated region may not overlap any of the regions already stored in
221 /// `config`.
222 ///
223 /// # Arguments
224 ///
225 /// - `unallocated_memory_start`: start of unallocated memory
226 /// - `unallocated_memory_size`: size of unallocated memory
227 /// - `min_memory_size`: minimum total memory to allocate for process
228 /// - `initial_app_memory_size`: initial size of app-owned memory
229 /// - `initial_kernel_memory_size`: initial size of kernel-owned memory
230 /// - `permissions`: permissions for the MPU region
231 /// - `config`: MPU region configuration
232 ///
233 /// # Return Value
234 ///
235 /// This function returns the start address and the size of the memory block
236 /// chosen for the process. If it is infeasible to find a memory block or
237 /// allocate the MPU region, or if the function has already been called,
238 /// returns None. If None is returned no changes are made.
239 fn allocate_app_memory_region(
240 &self,
241 unallocated_memory_start: *const u8,
242 unallocated_memory_size: usize,
243 min_memory_size: usize,
244 initial_app_memory_size: usize,
245 initial_kernel_memory_size: usize,
246 permissions: Permissions,
247 config: &mut Self::MpuConfig,
248 ) -> Option<(*const u8, usize)>;
249
250 /// Updates the MPU region for app-owned memory.
251 ///
252 /// An implementation must reallocate the MPU region for app-owned memory
253 /// stored in `config` to maintain the 3 conditions described in
254 /// `allocate_app_memory_region`.
255 ///
256 /// # Arguments
257 ///
258 /// - `app_memory_break`: new address for the end of app-owned memory
259 /// - `kernel_memory_break`: new address for the start of kernel-owned memory
260 /// - `permissions`: permissions for the MPU region
261 /// - `config`: MPU region configuration
262 ///
263 /// # Return Value
264 ///
265 /// Returns an error if it is infeasible to update the MPU region, or if it
266 /// was never created. If an error is returned no changes are made to the
267 /// configuration.
268 fn update_app_memory_region(
269 &self,
270 app_memory_break: *const u8,
271 kernel_memory_break: *const u8,
272 permissions: Permissions,
273 config: &mut Self::MpuConfig,
274 ) -> Result<(), ()>;
275
276 /// Configures the MPU with the provided region configuration.
277 ///
278 /// An implementation must ensure that, after the MPU configuration has been
279 /// activated through [`MPU::enable_app_mpu`] and until it has been disabled
280 /// through [`MPU::disable_app_mpu`], all memory locations not covered by an
281 /// allocated region are inaccessible in user mode, and all memory locations
282 /// covered by an allocated region are accessible in user mode.
283 ///
284 /// While the MPU configuration is active, memory locations covered by an
285 /// allocated region may or may not be accessible in kernel mode. When the
286 /// MPU configuration is not active, allocated regions must be accessible in
287 /// kernel mode.
288 ///
289 /// # Arguments
290 ///
291 /// - `config`: MPU region configuration
292 ///
293 /// # Safety
294 ///
295 /// This function is `unsafe` as incorrect use of it can endagner Tock's
296 /// isolation properties, and thus safety of the Tock kernel. Specifically,
297 /// callers of this function must ensure that they are applying an MPU
298 /// configuration that does not permit an application to access any
299 /// kernel-private memory (such as the grant region) or peripherals that can
300 /// transitively write to kernel-private memory (such as MMIO registers of
301 /// DMA-capable peripherals).
302 unsafe fn configure_mpu(&self, config: &Self::MpuConfig);
303}