1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
//! Interface for configuring the Memory Protection Unit.

use crate::callback::AppId;
use core::cmp;
use core::fmt::{self, Display};

/// User mode access permissions.
#[derive(Copy, Clone)]
pub enum Permissions {
    ReadWriteExecute,
    ReadWriteOnly,
    ReadExecuteOnly,
    ReadOnly,
    ExecuteOnly,
}

/// MPU region.
///
/// This is one contiguous address space protected by the MPU.
#[derive(Copy, Clone)]
pub struct Region {
    /// The memory address where the region starts.
    ///
    /// For maximum compatibility, we use a u8 pointer, however, note that many
    /// memory protection units have very strict alignment requirements for the
    /// memory regions protected by the MPU.
    start_address: *const u8,

    /// The number of bytes of memory in the MPU region.
    size: usize,
}

impl Region {
    /// Create a new MPU region with a given starting point and length in bytes.
    pub fn new(start_address: *const u8, size: usize) -> Region {
        Region {
            start_address: start_address,
            size: size,
        }
    }

    /// Getter: retrieve the address of the start of the MPU region.
    pub fn start_address(&self) -> *const u8 {
        self.start_address
    }

    /// Getter: retrieve the length of the region in bytes.
    pub fn size(&self) -> usize {
        self.size
    }
}

/// Null type for the default type of the `MpuConfig` type in an implementation
/// of the `MPU` trait. We need this to workaround a bug in the Rust compiler.
///
/// Depending how <https://github.com/rust-lang/rust/issues/65774> is resolved we
/// may be able to remove this type, but only if a default `Display` is
/// provided for the `()` type.
#[derive(Default)]
pub struct MpuConfigDefault {}

impl Display for MpuConfigDefault {
    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Ok(())
    }
}

/// The generic trait that particular memory protection unit implementations
/// need to implement.
///
/// This trait is a blend of relatively generic MPU functionality that should be
/// common across different MPU implementations, and more specific requirements
/// that Tock needs to support protecting applications. While a less
/// Tock-specific interface may be desirable, due to the sometimes complex
/// alignment rules and other restrictions imposed by MPU hardware, some of the
/// Tock details have to be passed into this interface. That allows the MPU
/// implementation to have more flexibility when satisfying the protection
/// requirements, and also allows the MPU to specify some addresses used by the
/// kernel when deciding where to place certain application memory regions so
/// that the MPU can appropriately provide protection for those memory regions.
pub trait MPU {
    /// MPU-specific state that defines a particular configuration for the MPU.
    /// That is, this should contain all of the required state such that the
    /// implementation can be passed an object of this type and it should be
    /// able to correctly and entirely configure the MPU.
    ///
    /// This state will be held on a per-process basis as a way to cache all of
    /// the process settings. When the kernel switches to a new process it will
    /// use the `MpuConfig` for that process to quickly configure the MPU.
    ///
    /// It is `Default` so we can create empty state when the process is
    /// created, and `Display` so that the `panic!()` output can display the
    /// current state to help with debugging.
    type MpuConfig: Default + Display;

    /// Clears the MPU.
    ///
    /// This function will clear any access control enforced by the
    /// MPU where possible.
    /// On some hardware it is impossible to reset the MPU after it has
    /// been locked, in this case this function wont change those regions.
    fn clear_mpu(&self) {}

    /// Enables the MPU for userspace apps.
    ///
    /// This function must enable the permission restrictions on the various
    /// regions protected by the MPU.
    fn enable_app_mpu(&self) {}

    /// Disables the MPU for userspace apps.
    ///
    /// This function must disable any access control that was previously setup
    /// for an app if it will interfere with the kernel.
    /// This will be called before the kernel starts to execute as on some
    /// platforms the MPU rules apply to privileged code as well, and therefore
    /// some of the MPU configuration must be disabled for the kernel to effectively
    /// manage processes.
    fn disable_app_mpu(&self) {}

    /// Returns the maximum number of regions supported by the MPU.
    fn number_total_regions(&self) -> usize {
        0
    }

    /// Allocates a new MPU region.
    ///
    /// An implementation must allocate an MPU region at least `min_region_size`
    /// bytes in size within the specified stretch of unallocated memory, and
    /// with the specified user mode permissions, and store it in `config`. The
    /// allocated region may not overlap any of the regions already stored in
    /// `config`.
    ///
    /// # Arguments
    ///
    /// - `unallocated_memory_start`: start of unallocated memory
    /// - `unallocated_memory_size`:  size of unallocated memory
    /// - `min_region_size`:          minimum size of the region
    /// - `permissions`:              permissions for the region
    /// - `config`:                   MPU region configuration
    ///
    /// # Return Value
    ///
    /// Returns the start and size of the allocated MPU region. If it is
    /// infeasible to allocate the MPU region, returns None.
    #[allow(unused_variables)]
    fn allocate_region(
        &self,
        unallocated_memory_start: *const u8,
        unallocated_memory_size: usize,
        min_region_size: usize,
        permissions: Permissions,
        config: &mut Self::MpuConfig,
    ) -> Option<Region> {
        if min_region_size > unallocated_memory_size {
            None
        } else {
            Some(Region::new(unallocated_memory_start, min_region_size))
        }
    }

    /// Chooses the location for a process's memory, and allocates an MPU region
    /// covering the app-owned part.
    ///
    /// An implementation must choose a contiguous block of memory that is at
    /// least `min_memory_size` bytes in size and lies completely within the
    /// specified stretch of unallocated memory.
    ///
    /// It must also allocate an MPU region with the following properties:
    ///
    /// 1. The region covers at least the first `initial_app_memory_size` bytes
    ///    at the beginning of the memory block.
    /// 2. The region does not overlap the last `initial_kernel_memory_size`
    ///    bytes.
    /// 3. The region has the user mode permissions specified by `permissions`.
    ///
    /// The end address of app-owned memory will increase in the future, so the
    /// implementation should choose the location of the process memory block
    /// such that it is possible for the MPU region to grow along with it. The
    /// implementation must store the allocated region in `config`. The
    /// allocated region may not overlap any of the regions already stored in
    /// `config`.
    ///
    /// # Arguments
    ///
    /// - `unallocated_memory_start`:   start of unallocated memory
    /// - `unallocated_memory_size`:    size of unallocated memory
    /// - `min_memory_size`:            minimum total memory to allocate for process
    /// - `initial_app_memory_size`:    initial size of app-owned memory
    /// - `initial_kernel_memory_size`: initial size of kernel-owned memory
    /// - `permissions`:                permissions for the MPU region
    /// - `config`:                     MPU region configuration
    ///
    /// # Return Value
    ///
    /// This function returns the start address and the size of the memory block
    /// chosen for the process. If it is infeasible to find a memory block or
    /// allocate the MPU region, or if the function has already been called,
    /// returns None. If None is returned no changes are made.
    #[allow(unused_variables)]
    fn allocate_app_memory_region(
        &self,
        unallocated_memory_start: *const u8,
        unallocated_memory_size: usize,
        min_memory_size: usize,
        initial_app_memory_size: usize,
        initial_kernel_memory_size: usize,
        permissions: Permissions,
        config: &mut Self::MpuConfig,
    ) -> Option<(*const u8, usize)> {
        let memory_size = cmp::max(
            min_memory_size,
            initial_app_memory_size + initial_kernel_memory_size,
        );
        if memory_size > unallocated_memory_size {
            None
        } else {
            Some((unallocated_memory_start, memory_size))
        }
    }

    /// Updates the MPU region for app-owned memory.
    ///
    /// An implementation must reallocate the MPU region for app-owned memory
    /// stored in `config` to maintain the 3 conditions described in
    /// `allocate_app_memory_region`.
    ///
    /// # Arguments
    ///
    /// - `app_memory_break`:    new address for the end of app-owned memory
    /// - `kernel_memory_break`: new address for the start of kernel-owned memory
    /// - `permissions`:         permissions for the MPU region
    /// - `config`:              MPU region configuration
    ///
    /// # Return Value
    ///
    /// Returns an error if it is infeasible to update the MPU region, or if it
    /// was never created. If an error is returned no changes are made to the
    /// configuration.
    #[allow(unused_variables)]
    fn update_app_memory_region(
        &self,
        app_memory_break: *const u8,
        kernel_memory_break: *const u8,
        permissions: Permissions,
        config: &mut Self::MpuConfig,
    ) -> Result<(), ()> {
        if (app_memory_break as usize) > (kernel_memory_break as usize) {
            Err(())
        } else {
            Ok(())
        }
    }

    /// Configures the MPU with the provided region configuration.
    ///
    /// An implementation must ensure that all memory locations not covered by
    /// an allocated region are inaccessible in user mode and accessible in
    /// supervisor mode.
    ///
    /// # Arguments
    ///
    /// - `config`: MPU region configuration
    /// - `app_id`: AppId of the process that the MPU is configured for
    #[allow(unused_variables)]
    fn configure_mpu(&self, config: &Self::MpuConfig, app_id: &AppId) {}
}

/// Implement default MPU trait for unit.
impl MPU for () {
    type MpuConfig = MpuConfigDefault;
}

/// The generic trait that particular kernel level memory protection unit
/// implementations need to implement.
///
/// This trait provides generic functionality to extend the MPU trait above
/// to also allow the kernel to protect itself. It is expected that only a
/// limited number of SoCs can support this, which is why it is a seperate
/// implementation.
pub trait KernelMPU {
    /// MPU-specific state that defines a particular configuration for the kernel
    /// MPU.
    /// That is, this should contain all of the required state such that the
    /// implementation can be passed an object of this type and it should be
    /// able to correctly and entirely configure the MPU.
    ///
    /// It is `Default` so we can create empty state when the kernel is
    /// created, and `Display` so that the `panic!()` output can display the
    /// current state to help with debugging.
    type KernelMpuConfig: Default + Display;

    /// Mark a region of memory that the Tock kernel owns.
    ///
    /// This function will optionally set the MPU to enforce the specified
    /// constraints for all accessess (even from the kernel).
    /// This should be used to mark read/write/execute areas of the Tock
    /// kernel to have the hardware enforce those permissions.
    ///
    /// If the KernelMPU trait is supported a board should use this function
    /// to set permissions for all areas of memory the kernel will use.
    /// Once all regions of memory have been allocated, the board must call
    /// enable_kernel_mpu(). After enable_kernel_mpu() is called no changes
    /// to kernel level code permissions can be made.
    ///
    /// Note that kernel level permissions also apply to apps, although apps
    /// will have more constraints applied on top of the kernel ones as
    /// specified by the `MPU` trait.
    ///
    /// Not all architectures support this, so don't assume this will be
    /// implemented.
    ///
    /// # Arguments
    ///
    /// - `memory_start`:             start of memory region
    /// - `memory_size`:              size of unallocated memory
    /// - `permissions`:              permissions for the region
    /// - `config`:                   MPU region configuration
    ///
    /// # Return Value
    ///
    /// Returns the start and size of the requested memory region. If it is
    /// infeasible to allocate the MPU region, returns None. If None is
    /// returned no changes are made.
    #[allow(unused_variables)]
    fn allocate_kernel_region(
        &self,
        memory_start: *const u8,
        memory_size: usize,
        permissions: Permissions,
        config: &mut Self::KernelMpuConfig,
    ) -> Option<Region>;

    /// Enables the MPU for the kernel.
    ///
    /// This function must enable the permission restrictions on the various
    /// kernel regions specified by `allocate_kernel_region()` protected by
    /// the MPU.
    ///
    /// It is expected that this function is called in `reset_handler()`.
    ///
    /// Once enabled this cannot be disabled. It is expected there won't be any
    /// changes to the kernel regions after this is enabled.
    #[allow(unused_variables)]
    fn enable_kernel_mpu(&self, config: &mut Self::KernelMpuConfig);
}