HomeAbout UsContact Us

Cortex-M MPU Configuration for Memory Protection

By Jithin Tom
Published in Embedded Concepts
August 11, 2026
2 min read
Cortex-M MPU Configuration for Memory Protection

Table Of Contents

01
MPU Registers Overview
02
Region Configuration Fundamentals
03
Typical Region Layout for an RTOS Application
04
Access Permission Encoding (ARMv7-M RASR)
05
Memory Attributes (TEX, S, C, B)
06
Subregion Disable — Creating "Holes"
07
ARMv8-M Enhancements (Cortex-M33/M55/M85)
08
Enabling the MPU
09
Stack Overflow Detection with MPU
10
Execute-Never (XN) Enforcement
11
Common Pitfalls
12
MemManage Fault Handler
13
Testing MPU Configuration
14
Summary
15
Related Reading
16
References
17
Frequently Asked Questions

The ARM Cortex-M Memory Protection Unit (MPU) is the primary hardware mechanism for enforcing memory access policies in embedded systems. Unlike an MMU, it does not perform virtual-to-physical address translation — it simply checks every memory access against a set of programmer-defined regions and faults on violations. Proper MPU configuration is essential for privilege separation, stack overflow detection, execute-never enforcement, and safety-critical certification (IEC 61508, ISO 26262).

MPU Registers Overview

The MPU is programmed through a set of core registers accessed via the System Control Block (SCB):

RegisterPurpose
MPU_TYPERead-only: number of instruction/data regions (DREGION, IREGION)
MPU_CTRLGlobal enable, HFNMIENA (enable during HardFault/NMI), PRIVDEFENA (default memory map for privileged)
MPU_RNRRegion Number Register — selects which region to program
MPU_RBARRegion Base Address Register — base address + region number (ARMv8-M)
MPU_RASRRegion Attribute and Size Register — size, permissions, subregions, enable
MPU_RLARRegion Limit Address Register (ARMv8-M only) — upper bound + attributes

Region Configuration Fundamentals

Each region defines:

  • Base address (size-aligned)
  • Size (power of two, minimum 32B/256B)
  • Access permissions (read/write/execute for privileged/unprivileged)
  • Memory attributes (shareability, cacheability, execute-never)
  • Subregion disable mask (8 bits, for ARMv7-M)
// ARMv7-M style (Cortex-M3/M4/M7)
#define MPU_REGION_SIZE_32KB (14 << 1) // 2^(14+1) = 32KB
#define MPU_REGION_ENABLE (1 << 0)
void mpu_configure_region(uint8_t region, uint32_t base_addr,
uint32_t size_attr, uint32_t access_attr) {
SCB->MPU_RNR = region;
SCB->MPU_RBAR = base_addr;
SCB->MPU_RASR = size_attr | access_attr | MPU_REGION_ENABLE;
}
// ARMv8-M style (Cortex-M33/M55/M85) - uses RLAR for limit address
void mpu_configure_region_v8(uint8_t region, uint32_t base_addr,
uint32_t limit_addr, uint32_t attr) {
SCB->MPU_RNR = region;
SCB->MPU_RBAR = base_addr | region;
SCB->MPU_RLAR = limit_addr | attr;
}

Typical Region Layout for an RTOS Application

A production MPU configuration usually defines regions in priority order (higher region number = higher priority on overlap):

+--------------------------------------------------------------+
| Region 7 (highest): PERIPHERALS - RW, XN, Device/nGnRnE |
| Region 6: FLASH (code) - RX, Privileged only |
| Region 5: FLASH (data/const) - R, Privileged |
| Region 4: KERNEL RAM - RW, XN, Privileged |
| Region 3: TASK A STACK - RW, XN, Unprivileged |
| Region 2: TASK B STACK - RW, XN, Unprivileged |
| Region 1: SHARED/IPC BUFFER - RW, XN, Both |
| Region 0 (lowest): BACKGROUND - Default map (PRIVDEFENA) |
+--------------------------------------------------------------+

Access Permission Encoding (ARMv7-M RASR)

AP[2:0]PrivilegedUnprivilegedDescription
000No accessNo accessDisable region
001RWNo accessPrivileged only
010RWRPrivileged RW, Unprivileged R
011RWRWFull access
100Reserved
101RNo accessPrivileged R only
110RRRead-only (both)
111RRRead-only (both, alias)

XN (Execute Never): Bit 28 in RASR. Always set XN=1 for RAM and peripheral regions.

Memory Attributes (TEX, S, C, B)

TEX[2:0]SCBMemory TypeTypical Use
000000Strongly OrderedPeripherals, shared memory
000001Device (shared)Memory-mapped peripherals
000100Normal, Outer/Inner Non-cacheableUncached RAM
000101Normal, Outer/Inner Write-BackCached RAM (with caution)
001000Device (non-shared)Non-shared peripherals

For most embedded code: Device/nGnRnE for peripherals, Normal Non-cacheable for RAM, Normal WT/WB for cacheable Flash.

Subregion Disable — Creating “Holes”

Each region splits into 8 equal subregions. Setting a bit in SRD[7:0] disables that subregion:

// 64KB region at 0x2000_0000, disable subregions 2 and 3 (middle 16KB)
// to carve out a hole for a peripheral mapped inside RAM space
uint32_t srd = (1 << 2) | (1 << 3); // Disable subregions 2,3
SCB->MPU_RASR |= (srd << 8);

This is the only way to overlay regions of different permissions in ARMv7-M.

ARMv8-M Enhancements (Cortex-M33/M55/M85)

ARMv8-M (v8.1-M) adds significant improvements:

  • RLAR (Region Limit Address Register): Upper bound instead of size encoding — more flexible, no power-of-two size constraint
  • PA (Privileged Access) and U (Unprivileged Access) bits in RLAR replace AP encoding
  • PXN (Privileged Execute Never) and UXN (Unprivileged Execute Never): Separate execute control
  • MAIR (Memory Attribute Indirection Register): 8 programmable memory attribute profiles referenced by index
// ARMv8-M RLAR encoding
#define MPU_RLAR_LIMIT_MASK (0xFFFFF000) // Limit address[31:12]
#define MPU_RLAR_PXN (1 << 1) // Privileged Execute Never
#define MPU_RLAR_UXN (1 << 2) // Unprivileged Execute Never
#define MPU_RLAR_ATTR_IDX(n) ((n) << 1) // MAIR index (0-7)
#define MPU_RLAR_EN (1 << 0) // Region enable
// MAIR programming (per attribute index)
SCB->MAIR0 = (ATTR_DEVICE_nGnRnE << 0) | // Attr0: Device
(ATTR_NORMAL_NC << 8) | // Attr1: Normal Non-cacheable
(ATTR_NORMAL_WB << 16) | // Attr2: Normal Write-Back
(ATTR_NORMAL_WT << 24); // Attr3: Normal Write-Through

Enabling the MPU

void mpu_enable(void) {
// 1. Disable MPU before configuration
SCB->MPU_CTRL = 0;
// 2. Configure all regions (as shown above)
mpu_setup_regions();
// 3. Enable MPU with:
// - MPU_CTRL.ENABLE = 1
// - MPU_CTRL.HFNMIENA = 1 (MPU active during HardFault/NMI)
// - MPU_CTRL.PRIVDEFENA = 1 (Default memory map for privileged)
SCB->MPU_CTRL = (1 << 0) | (1 << 1) | (1 << 2);
// 4. DSB + ISB to ensure MPU takes effect before next instruction
__DSB();
__ISB();
}

PRIVDEFENA=1: When MPU is enabled but no region matches, privileged accesses use the default memory map. Unprivileged accesses fault. This is the safe default.

Stack Overflow Detection with MPU

Place a guard region immediately below each task stack:

// Task stack: 0x2000_8000 - 0x2000_9FFF (8KB)
// Guard region: 0x2000_7000 - 0x2000_7FFF (4KB, No Access)
#define STACK_TOP 0x2000A000
#define STACK_SIZE 0x2000 // 8KB
#define GUARD_SIZE 0x1000 // 4KB
#define GUARD_BASE (STACK_TOP - STACK_SIZE - GUARD_SIZE)
mpu_configure_region(REGION_STACK_GUARD,
GUARD_BASE,
MPU_REGION_SIZE_4KB | MPU_AP_PRIV_NO_UNPRIV_NO | MPU_XN,
0); // No access for anyone

Any stack overflow into the guard region triggers a MemManage fault.

Execute-Never (XN) Enforcement

Every RAM and peripheral region must have XN=1. This prevents code injection attacks and catches accidental execution from data buffers.

// RAM region - RW, XN
#define RASR_RAM (MPU_REGION_SIZE_64KB | MPU_AP_FULL | MPU_XN | MPU_TEX_SCB_NORMAL_NC)
// Peripheral region - RW, XN, Device
#define RASR_PERIPH (MPU_REGION_SIZE_512MB | MPU_AP_PRIV_ONLY | MPU_XN | MPU_TEX_SCB_DEVICE)

Common Pitfalls

PitfallConsequenceFix
Region base not size-alignedUnpredictable behavior / fault on configAlign base to region size (use __ALIGNED() or linker script)
Overlapping regions with wrong priorityLower-priority region wins unexpectedlyHigher region number = higher priority. Plan region numbers carefully.
Forgetting XN on RAMCode execution from data, security holesAlways set XN=1 for non-code regions
Subregion disable on ARMv8-MSRD not supported in RLAR modelUse separate regions or MAIR attributes instead
MPU enabled before all regions configuredDefault map gaps cause faultsConfigure all regions, then enable in one atomic sequence
Not using HFNMIENAMPU disabled during HardFault/NMI — debugger can’t inspectSet HFNMIENA=1 for production; clear only for debug if needed

MemManage Fault Handler

void MemManage_Handler(void) {
uint32_t mmfsr = SCB->CFSR & 0xFF; // MemManage Fault Status Register
uint32_t mmfar = SCB->MMFAR; // Faulting address (valid if MMARVALID=1)
// MMFSR bits:
// 0: IACCVIOL - Instruction access violation
// 1: DACCVIOL - Data access violation
// 3: MUNSTKERR - Unstacking error
// 4: MSTKERR - Stacking error
// 5: MLSPERR - Lazy FPU stacking error
// 7: MMARVALID - MMFAR valid
if (mmfsr & (1 << 7)) {
log_error("MemManage fault at 0x%08X: %s%s%s",
mmfar,
(mmfsr & 1) ? "IACCVIOL " : "",
(mmfsr & 2) ? "DACCVIOL " : "",
(mmfsr & 8) ? "MUNSTKERR " : "");
}
// For RTOS: identify faulting task, dump context, trigger watchdog reset
// Do NOT return from this handler if fault is unrecoverable
while (1) { __WFI(); }
}

Testing MPU Configuration

  1. Unit test each region: Write/read/execute at base, limit-1, limit, limit+1
  2. Verify subregion holes: Access disabled subregions — expect fault
  3. Stack overflow test: Recursively call until guard region hit
  4. Privilege escalation test: Run unprivileged, attempt privileged-only access
  5. Measure fault latency: Time from violation to handler entry (critical for safety)

Summary

AspectARMv7-M (M3/M4/M7)ARMv8-M (M33/M55/M85)
Max regions816
Size encodingPower-of-2 (SIZE field)Limit address (RLAR)
PermissionsAP[2:0] + XNPA/U + PXN/UXN
Memory attrsTEX/S/C/BMAIR[7:0] indices
Subregions8 per region (SRD)Not supported (use regions)

The MPU is not optional for robust embedded firmware. A well-designed MPU configuration catches stack overflows, prevents privilege escalation, enforces execute-never, and provides the hardware foundation for safety certification. Invest the time to map your memory, define regions carefully, and test every boundary.

References

  1. ARM, Cortex-M4 Devices Generic User Guide, Section 4.5 “Memory Protection Unit”, Document ARM DUI 0553A
  2. ARM, ARMv7-M Architecture Reference Manual, Section B3.5 “Memory Protection Unit”, ARM DDI 0403E
  3. ARM, ARMv8-M Architecture Reference Manual, Section D10.2 “Protected Memory System Architecture”, ARM DDI 0553B
  4. FreeRTOS, MPU Support in FreeRTOS, https://freertos.org/Documentation/02-Kernel/04-API-references/13-FreeRTOS-MPU-specific/00-FreeRTOS-MPU-specific
  5. STMicroelectronics, STM32F4 Series Reference Manual (RM0090), Section 4.3 “MPU”, https://www.st.com/en/microcontrollers-microprocessors/stm32f4-series/documentation.html
  6. J. Yiu, The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors, 3rd Ed., Chapter 10 “Memory Protection Unit”

Frequently Asked Questions

What is the Cortex-M Memory Protection Unit (MPU)?

The MPU is a programmable hardware unit that divides the memory map into a small number of regions (typically 8-16), each with configurable base address, size, access permissions (read/write/execute), and shareability attributes. It enforces these permissions at runtime, faulting on violations.

How many MPU regions do typical Cortex-M parts have?

Cortex-M3/M4/M7 typically have 8 regions. Cortex-M33/M55/M85 with ARMv8-M can have up to 16 regions. The exact number is in MPU_TYPE.DREGION.

What is the minimum region size and alignment requirement?

Minimum region size is 32 bytes for ARMv7-M, 256 bytes for ARMv8-M. Region base address must be aligned to the region size (size-aligned).

How do subregions work in the MPU?

Each region can be divided into 8 equal subregions (SUBREGION_DISABLE in MPU_RASR). This allows creating 'holes' in a region — useful for excluding a peripheral block inside a larger RAM region, or for overlaying a smaller execute-never region inside a larger executable region.

What happens on an MPU violation?

The processor triggers a MemManage fault (HardFault on Cortex-M3/M4 if MemManage is not enabled). The fault handler can read MMFAR (MemManage Fault Address Register) and MMFSR to determine the faulting address and access type.

Tags

cortex-mmpumemory-protectionarmrtossecurity

Share


Previous Article
Zephyr BLE Power Optimization for Coin Cell Devices
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Cortex-M FPU Context Switching: Lazy Stacking vs Eager State Save
Cortex-M FPU Context Switching: Lazy Stacking vs Eager State Save
August 10, 2026
3 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media