Memory Protection Unit (MPU) — Tiva C
- Eslam El Hefny
- Tutorials, Tiva c
- April 12, 2025
Overview
The Memory Protection Unit (MPU) is an optional Cortex-M4 feature present on the TM4C123GH6PM. It enforces access rules on up to 8 configurable memory regions, preventing tasks from corrupting each other’s data, stopping code execution from RAM, and catching stack overflow before it corrupts critical variables.
Beginner Level — What & Why
What is the MPU?
The MPU is a hardware “access control list” for memory. You define regions (address + size + rules), and any violation — a task writing to Flash, code running from the stack, or a pointer escaping its buffer — immediately triggers a MemManage fault exception instead of silently corrupting memory.
Real-World Analogy
Imagine a building with different security zones. The MPU is the access-card system: area A (Flash) is read-only for everyone, area B (stack) is read/write only for the owning department, and area C (peripherals) requires supervisor badge. If someone tries to enter an area without the right access, an alarm sounds (MemManage fault).
What Problem Does It Solve?
- Protects the RTOS kernel from buggy application tasks
- Prevents stack overflow from silently overwriting heap or global data
- Blocks code execution from RAM (XN bit) — a security and safety requirement
- Provides the foundation for memory isolation in safety-critical systems (ISO 26262, IEC 61508)
Key Terms
| Term | Meaning |
|---|---|
| Region | A defined address range with access rules |
| AP bits | Access Permissions — who can read/write |
| XN | eXecute Never — prevents code execution in a region |
| TEX, C, B, S | Type Extension, Cacheable, Bufferable, Shareable — memory type bits |
| MemManage fault | Exception triggered on MPU violation |
| PRIVDEFENA | Privileged Default Enable — allows privileged access to unmapped regions |
Intermediate Level — How It Works
MPU Registers
| Register | Address | Description |
|---|---|---|
| MPU_TYPE | 0xE000ED90 |
Type info: DREGION (number of regions), IREGION |
| MPU_CTRL | 0xE000ED94 |
Control: ENABLE, HFNMIENA, PRIVDEFENA |
| MPU_RNR | 0xE000ED98 |
Region Number Register (0–7) |
| MPU_RBAR | 0xE000ED9C |
Region Base Address (must be aligned to region size) |
| MPU_RASR | 0xE000EDA0 |
Region Attribute and Size Register |
MPU_RASR field breakdown:
| Bits | Field | Description |
|---|---|---|
| 0 | ENABLE | 1 = region enabled |
| 5:1 | SIZE | Region size = 2^(SIZE+1) bytes (minimum SIZE=4 → 32 bytes) |
| 15:8 | SRD | Sub-Region Disable (one bit per 1/8 of the region) |
| 16 | B | Bufferable |
| 17 | C | Cacheable |
| 18 | S | Shareable |
| 21:19 | TEX | Type Extension |
| 26:24 | AP | Access Permission |
| 28 | XN | Execute Never |
Access Permission (AP) values:
| AP | Privileged | Unprivileged |
|---|---|---|
| 0b000 | No access | No access |
| 0b001 | Read/Write | No access |
| 0b010 | Read/Write | Read only |
| 0b011 | Read/Write | Read/Write |
| 0b101 | Read only | No access |
| 0b110 | Read only | Read only |
TivaWare DriverLib API
#include "driverlib/mpu.h"
/* Check number of regions supported */
uint32_t type = MPURegionCountGet(); // should be 8 on TM4C123
/* Configure Region 0: Flash (read-only, executable, privileged only) */
MPURegionSet(0, // region number 0
0x00000000, // base address (Flash)
MPU_RGN_SIZE_256K | // 256 KB (size field = 17 → 2^18)
MPU_RGN_PERM_PRV_RO_USR_RO | // priv=RO, user=RO
MPU_RGN_ENABLE); // enable this region
/* Configure Region 1: SRAM (read/write, XN, privileged only) */
MPURegionSet(1,
0x20000000, // base address (SRAM)
MPU_RGN_SIZE_32K | // 32 KB
MPU_RGN_PERM_PRV_RW_USR_NO | // priv=RW, user=no access
MPU_RGN_PERM_NOEXEC | // XN = execute never
MPU_RGN_ENABLE);
/* Enable the MPU with default background region (privileged full access) */
MPUEnable(MPU_CONFIG_PRIV_DEFAULT); // PRIVDEFENA = 1
Advanced Level — Deep Dive
Bare-Metal MPU Configuration
#include "inc/hw_types.h"
#include "inc/hw_nvic.h" // MPU register definitions
void MPU_Init(void)
{
/* Disable MPU before configuration */
HWREG(NVIC_MPU_CTRL) = 0;
/* --- Region 0: Flash 0x00000000–0x0003FFFF (256 KB) ---
* AP=001 (priv RW, user No), XN=0 (executable), SIZE=17 (2^18=256KB) */
HWREG(NVIC_MPU_NUMBER) = 0;
HWREG(NVIC_MPU_BASE) = 0x00000000;
HWREG(NVIC_MPU_ATTR) =
(0x11 << 1) | // SIZE = 17 → 2^18 = 256 KB
(0x03 << 24) | // AP = 0b011 (full access)
(0x01 << 28) | // XN = 0 (executable — flash is OK)
(1U << 0); // ENABLE
/* --- Region 1: SRAM 0x20000000–0x20007FFF (32 KB) ---
* AP=011 (full RW), XN=1 (no execute from RAM), SIZE=14 (2^15=32KB) */
HWREG(NVIC_MPU_NUMBER) = 1;
HWREG(NVIC_MPU_BASE) = 0x20000000;
HWREG(NVIC_MPU_ATTR) =
(0x0E << 1) | // SIZE = 14 → 2^15 = 32 KB
(0x03 << 24) | // AP = full access
(0x01 << 28) | // XN = 1 (no execute from SRAM)
(1U << 0); // ENABLE
/* --- Region 2: Peripheral 0x40000000–0x5FFFFFFF (512 MB) ---
* AP=001 (priv RW only), XN=1, SIZE=28 (2^29=512MB) */
HWREG(NVIC_MPU_NUMBER) = 2;
HWREG(NVIC_MPU_BASE) = 0x40000000;
HWREG(NVIC_MPU_ATTR) =
(0x1C << 1) | // SIZE = 28 → 2^29 = 512 MB
(0x01 << 24) | // AP = priv RW, user no access
(0x01 << 28) | // XN = 1
(1U << 0); // ENABLE
/* Enable MPU: ENABLE=1, PRIVDEFENA=1 (privileged can access all) */
HWREG(NVIC_MPU_CTRL) = NVIC_MPU_CTRL_ENABLE | NVIC_MPU_CTRL_PRIVDEF;
}
Stack Overflow Protection with Sub-Regions
Use a “guard region” at the bottom of the stack that generates a MemManage fault when overflowed:
extern uint32_t __stack_start; // linker symbol for stack bottom
void MPU_ProtectStack(void)
{
/* Place a 32-byte no-access guard at the bottom of the stack */
HWREG(NVIC_MPU_NUMBER) = 3;
/* Region must be aligned to its size (32 bytes → align to 32 bytes) */
HWREG(NVIC_MPU_BASE) = (uint32_t)&__stack_start;
HWREG(NVIC_MPU_ATTR) =
(0x04 << 1) | // SIZE = 4 → 2^5 = 32 bytes
(0x00 << 24) | // AP = no access (000)
(1U << 0); // ENABLE
}
MemManage Fault Handler
void MemManage_Handler(void)
{
/* Read fault address from MMFAR if MMARVALID is set */
uint32_t ui32CFSR = HWREG(0xE000ED28); // CFSR
uint32_t ui32MMFAR = HWREG(0xE000ED34); // MMFAR
if (ui32CFSR & 0x80) // MMARVALID bit
{
/* ui32MMFAR contains the faulting address */
(void)ui32MMFAR;
}
/* In production: log the fault, then reset */
while (1); // halt for debugging
}
Size Field Table
| SIZE value | Region size |
|---|---|
| 4 | 32 bytes |
| 7 | 256 bytes |
| 10 | 1 KB |
| 14 | 32 KB (SRAM) |
| 17 | 256 KB (Flash) |
| 28 | 512 MB (peripheral space) |
Formula: region size = 2^(SIZE+1)
Gotchas
- Region base address must be aligned to the region size. A 32 KB region at 0x20000000 is fine; at 0x20001000 is invalid if 0x1000 < 32 KB.
- Overlapping regions: the highest-numbered region takes precedence. Use this to grant sub-region exceptions.
- MemManage fault must be enabled — set bit 16 of SCB SHCSR:
HWREG(NVIC_SYS_HND_CTRL) |= (1 << 16). - Without
PRIVDEFENA, the kernel in privileged mode cannot access unconfigured memory — this will cause immediate faults. - The MPU does not apply in NMI and HardFault handlers — these always execute with full access.
Step-by-Step Example
/*
* mpu_stack_guard.c
* Demonstrates MPU stack overflow detection with MemManage fault
* Board : TM4C123GXL EK LaunchPad
* SDK : TivaWare_C_Series-2.2.x
*/
#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_nvic.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/mpu.h"
#include "driverlib/interrupt.h"
/* Linker-provided stack bottom symbol (defined in TM4C123.ld) */
extern uint32_t _stack; // bottom of stack in linker script
void MemManage_Handler(void)
{
/* Stack overflow detected — blink all LEDs rapidly */
GPIOPinWrite(GPIO_PORTF_BASE,
GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3,
GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3); // all LEDs on
while (1);
}
/* Recursive function to force stack overflow (for demonstration only) */
static void overflow_stack(uint32_t depth)
{
volatile uint32_t buf[32]; // 128 bytes per frame
buf[0] = depth;
overflow_stack(depth + 1); // infinite recursion → overflow
}
int main(void)
{
SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,
GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
/* Configure Flash region: full access, executable */
MPURegionSet(0, 0x00000000,
MPU_RGN_SIZE_256K |
MPU_RGN_PERM_PRV_RW_USR_RW |
MPU_RGN_ENABLE);
/* Configure SRAM region: full RW, execute-never */
MPURegionSet(1, 0x20000000,
MPU_RGN_SIZE_32K |
MPU_RGN_PERM_PRV_RW_USR_RW |
MPU_RGN_PERM_NOEXEC |
MPU_RGN_ENABLE);
/* Guard region: 256 bytes at bottom of stack, no access */
/* Stack bottom is typically 0x20000000 for TM4C123 */
MPURegionSet(2, 0x20000000,
MPU_RGN_SIZE_256B |
MPU_RGN_PERM_PRV_NO_USR_NO | // no access at all
MPU_RGN_ENABLE);
/* Enable MemManage fault */
HWREG(NVIC_SYS_HND_CTRL) |= (1U << 16);
/* Enable MPU with privileged default region */
MPUEnable(MPU_CONFIG_PRIV_DEFAULT);
IntMasterEnable();
/* Green LED on = MPU configured successfully */
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, GPIO_PIN_3);
SysCtlDelay(SysCtlClockGet() / 3);
/* Trigger a stack overflow — MemManage_Handler will fire */
overflow_stack(0);
/* Should never reach here */
while (1);
}
Summary
| Key Point | Details |
|---|---|
| Regions | 8 configurable regions (Region 0–7) |
| Region size | 2^(SIZE+1), minimum 32 bytes |
| Base alignment | Must align to region size |
| AP=000 | No access (use for guard regions) |
| AP=011 | Full read/write access |
| XN=1 | Execute Never (block code from RAM) |
| Priority | Higher region number wins on overlap |
| MemManage | Enable via SHCSR bit 16 |
| PRIVDEFENA | Allows privileged code to access unconfigured memory |