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