Chapter 12 of 20

Memory Protection Unit (MPU) — Tiva C

Eslam El Hefny Apr 12, 2025 7 min read
60% done

Memory Protection Unit (MPU) — Tiva C

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

TermMeaning
RegionA defined address range with access rules
AP bitsAccess Permissions — who can read/write
XNeXecute Never — prevents code execution in a region
TEX, C, B, SType Extension, Cacheable, Bufferable, Shareable — memory type bits
MemManage faultException triggered on MPU violation
PRIVDEFENAPrivileged Default Enable — allows privileged access to unmapped regions

Intermediate Level — How It Works

MPU Registers

RegisterAddressDescription
MPU_TYPE0xE000ED90Type info: DREGION (number of regions), IREGION
MPU_CTRL0xE000ED94Control: ENABLE, HFNMIENA, PRIVDEFENA
MPU_RNR0xE000ED98Region Number Register (0–7)
MPU_RBAR0xE000ED9CRegion Base Address (must be aligned to region size)
MPU_RASR0xE000EDA0Region Attribute and Size Register

MPU_RASR field breakdown:

BitsFieldDescription
0ENABLE1 = region enabled
5:1SIZERegion size = 2^(SIZE+1) bytes (minimum SIZE=4 → 32 bytes)
15:8SRDSub-Region Disable (one bit per 1/8 of the region)
16BBufferable
17CCacheable
18SShareable
21:19TEXType Extension
26:24APAccess Permission
28XNExecute Never

Access Permission (AP) values:

APPrivilegedUnprivileged
0b000No accessNo access
0b001Read/WriteNo access
0b010Read/WriteRead only
0b011Read/WriteRead/Write
0b101Read onlyNo access
0b110Read onlyRead 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 valueRegion size
432 bytes
7256 bytes
101 KB
1432 KB (SRAM)
17256 KB (Flash)
28512 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 PointDetails
Regions8 configurable regions (Region 0–7)
Region size2^(SIZE+1), minimum 32 bytes
Base alignmentMust align to region size
AP=000No access (use for guard regions)
AP=011Full read/write access
XN=1Execute Never (block code from RAM)
PriorityHigher region number wins on overlap
MemManageEnable via SHCSR bit 16
PRIVDEFENAAllows privileged code to access unconfigured memory

Next Chapter

Wire Master Module (1-Wire)

Share: