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}