Memory Protection Unit
What is MPU?
In 2004, Arm introduced a new family of CPU cores called Cortex-M (M stands for Microcontroller) based on a Reduced Instruction Set Computer (RISC) architecture. Some Cortex-M implementations are equipped with a Memory Protection Unit (MPU).
An MPU is hardware that limits access to memory and peripheral devices to only the code that needs to access those resources. A memory protection unit (MPU), is a computer hardware unit that provides memory protection. It is usually implemented as part of the central processing unit (CPU). MPU is a trimmed down version of memory management unit (MMU) providing only memory protection support. It is usually implemented in low power processors that require only memory protection and do not need the full fledged feature of a memory management unit like virtual memory management.
The MPU allows the privileged software to define memory regions and assign memory access permission and memory attributes to each of them. MPU monitors transactions, including instruction fetches and data accesses from the processor, which can trigger a fault exception when an access violation is detected.
To keep a system safe and secure, privileged mode code must be reserved for code that has been fully tested and is known to be trusted. RTOSs are generally considered trusted while most application code is not. Application code can be made to run on a Cortex-M in non-privileged mode, thus restricting what the code can do. This is a desirable feature because we don't want untrusted code to give itself privileges and thus change the protection put in place by the system designer.
The main purpose of memory protection is to prevent a process from accessing memory that has not been assigned to it. This prevents a bug or malware within a process from affecting other processes, or the operating system itself. It enhances both the stability and safety of embedded applications and is thus often used in safety-critical applications such as medical devices, avionics, industrial control, and nuclear power plants.
The MPU can be used to make an embedded system more robust and more secure by:
- Prohibiting the user applications from corrupting data used by critical tasks (such as the operating system kernel).
- Defining the SRAM memory region as a non-executable (eXecute Never XN) to prevent code injection attacks.
- Changing the memory access attributes.
Cortex-M MPU architecture
Depending on the implementation of the processor, the number of supported memory regions will vary. The MPU on the Cortex-M allows a process to have access to up to eight (8) or sixteen (16) memory or peripheral regions. The location and size of each region is configurable. The size of each region must be a multiple of a power of two but cannot be smaller than 32 bytes. Also, the base address of a region must be aligned to an integer multiple value of the region size. So, if the region is 8K bytes, then the region must be aligned on an 8K boundary. Moreover, each region, in turn can have eight subregions, if the region is at least 256 bytes. If region size is less than 256 bytes then there are no subregions possible. The subregions are always of equal size, and can be enabled or disabled by a subregion number.
To configure MPU, there are different registers available. There are total five registers namely MPU Type, Control Register, Region Number, Region Base Address, Region Attribute and Size.
Configure MPU on FreeRTOS
FreeRTOS-MPU provides a simple interface to the MPU by hiding the register level MPU configuration from the user. However, writing an application for an environment that does not permit free access to all data can be challenging.
FreeRTOS-MPU can execute tasks in either Privileged mode or User mode. The processor switches automatically to Privileged mode before executing an interrupt service routine. The kernel always switches to Privileged mode whenever a FreeRTOS-MPU API function is called, returning to its previous mode when the API function completes.
FreeRTOS-MPU allows to define MPU Regions for a task. An MPU region is an address range to which access permissions can be applied. A maximum of eight regions can be defined at any one time. Regions zero to four are used by the kernel to pre-configure a usable run time environment, and one region is used for task stack so a total of 3 regions are available for users to configure.
The kernel reconfigures the MPU during each context switch, so the remaining three regions can be defined differently by each task. The task-defined regions use the highest region numbers, so can be used to override the kernel-defined regions.
There are three APIs available in FreeRTOS-MPU to utilize the functionality of the MPU unit. These three APIs are xTaskCreateRestricted(), vTaskAllocateMPURegions() and portSWITCH_TO_USER_MODE() used for task creation, region allocation and to switch mode, respectively.
Figure shows an example of two different tasks having different access permission. In the example, Task A is in user mode and have access to Task A stack and configured memory region only. Task B is in the privileged mode, so can access the entire memory.
In summary, an MPU is a hardware that limits the access to memory and peripheral devices to only the code that needs to access those resources. Tasks are isolated from one another. If a task attempts to access a memory location or a peripheral device outside of its allotted space, then a CPU exception is triggered. The application designer can design a robust and secure application using the simple FreeRTOS-MPU interface. The RTOS is responsible for configuring the MPU on every context switch. However, it's the application developer's responsibility to set up the MPU region table for the application.