The "Holy Bible" for embedded engineers
Comprehensive guide to implementing Memory Protection Units (MPU) for task isolation, memory safety, and system security in embedded real-time systems with FreeRTOS examples
Memory protection is like having security guards at different doors in a building. Instead of letting anyone access any room, each person (task) only gets access to their assigned areas. If someone tries to break into a restricted area, the security system (MPU) immediately stops them and alerts the authorities.
In embedded systems, a single bug in one task can corrupt memory used by other tasks, causing the entire system to fail. Memory protection creates “firewalls” between tasks, so if one task crashes or has a bug, it can’t take down the whole system. This is especially critical for safety-critical applications where system failure could be dangerous.
// Configure MPU region for task stack protection
void configure_task_protection(TaskHandle_t task, uint8_t region_num) {
// Get task stack boundaries
uint32_t *stack_start = pxTaskGetStackStart(task);
uint32_t stack_size = uxTaskGetStackHighWaterMark(task) * sizeof(StackType_t);
// Configure MPU region
MPU_Region_InitTypeDef region;
region.Number = region_num;
region.BaseAddress = (uint32_t)stack_start;
region.Size = MPU_REGION_SIZE_1KB; // Adjust based on actual size
region.AccessPermission = MPU_REGION_PRIV_RO; // Read-only for other tasks
region.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
region.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
// Enable the region
HAL_MPU_ConfigRegion(®ion);
}
// Task creation with protection
void create_protected_task(void) {
TaskHandle_t task_handle;
xTaskCreate(task_function, "Protected", 128, NULL, 1, &task_handle);
// Configure memory protection for this task
configure_task_protection(task_handle, 1);
}
Memory protection transforms your system from a “free-for-all” where any bug can crash everything into a robust, fault-tolerant system where problems are contained and isolated.
Memory Protection Units (MPUs) provide hardware-enforced memory isolation between tasks, preventing unauthorized memory access and ensuring system reliability. In real-time systems, MPUs are crucial for creating secure, fault-tolerant applications where task failures don’t compromise system integrity.
A Memory Protection Unit is a hardware component that enforces memory access permissions at runtime. Unlike Memory Management Units (MMUs) that provide virtual memory, MPUs work with physical addresses and provide simpler but effective memory protection.
MPU vs MMU:
Core Components:
Memory Access Flow:
CPU Memory Request → MPU Region Check → Permission Validation → Access Granted/Denied
1. Task Isolation:
2. Fault Containment:
3. Security Enhancement:
How It Works:
Implementation Example:
typedef struct {
uint32_t stack_start;
uint32_t stack_size;
uint8_t region_number;
MPU_Region_InitTypeDef mpu_region;
} task_stack_protection_t;
void vConfigureTaskStackProtection(TaskHandle_t task_handle, uint8_t region_num) {
task_stack_protection_t *protection = pvPortMalloc(sizeof(task_stack_protection_t));
if (protection != NULL) {
// Get task stack information
UBaseType_t stack_size = uxTaskGetStackHighWaterMark(task_handle);
uint32_t *stack_ptr = (uint32_t*)pxTaskGetStackStart(task_handle);
protection->stack_start = (uint32_t)stack_ptr;
protection->stack_size = stack_size * sizeof(StackType_t);
protection->region_number = region_num;
// Configure MPU region for stack protection
protection->mpu_region.Number = region_num;
protection->mpu_region.BaseAddress = protection->stack_start;
protection->mpu_region.Size = vCalculateMPUSize(protection->stack_size);
protection->mpu_region.AccessPermission = MPU_REGION_FULL_ACCESS;
protection->mpu_region.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
protection->mpu_region.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
protection->mpu_region.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
protection->mpu_region.Number = MPU_REGION_NUMBER0;
protection->mpu_region.TypeExtField = MPU_TEX_LEVEL0;
protection->mpu_region.SubRegionDisable = 0x00;
protection->mpu_region.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
// Apply MPU configuration
HAL_MPU_ConfigRegion(&protection->mpu_region);
}
}
How It Works:
Implementation Example:
typedef struct {
uint32_t data_start;
uint32_t data_size;
uint8_t access_permissions;
bool is_shared;
} data_protection_t;
void vConfigureDataProtection(uint8_t region_num, uint32_t start_addr,
uint32_t size, uint8_t permissions, bool shared) {
MPU_Region_InitTypeDef mpu_region;
mpu_region.Number = region_num;
mpu_region.BaseAddress = start_addr;
mpu_region.Size = vCalculateMPUSize(size);
if (shared) {
mpu_region.AccessPermission = MPU_REGION_FULL_ACCESS;
mpu_region.IsShareable = MPU_ACCESS_SHAREABLE;
} else {
mpu_region.AccessPermission = permissions;
mpu_region.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
}
mpu_region.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
mpu_region.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
mpu_region.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
mpu_region.TypeExtField = MPU_TEX_LEVEL0;
mpu_region.SubRegionDisable = 0x00;
HAL_MPU_ConfigRegion(&mpu_region);
}
How It Works:
Implementation Example:
void vConfigureCodeProtection(uint8_t region_num, uint32_t code_start, uint32_t code_size) {
MPU_Region_InitTypeDef mpu_region;
mpu_region.Number = region_num;
mpu_region.BaseAddress = code_start;
mpu_region.Size = vCalculateMPUSize(code_size);
mpu_region.AccessPermission = MPU_REGION_PRIVILEGED_READ_ONLY;
mpu_region.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
mpu_region.IsCacheable = MPU_ACCESS_CACHEABLE;
mpu_region.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
mpu_region.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
mpu_region.TypeExtField = MPU_TEX_LEVEL0;
mpu_region.SubRegionDisable = 0x00;
HAL_MPU_ConfigRegion(&mpu_region);
}
1. Privileged Access Regions:
2. User Access Regions:
3. Read-Only Regions:
4. Execute-Only Regions:
MPU Size Values:
uint32_t vCalculateMPUSize(uint32_t size_bytes) {
if (size_bytes <= 32) return MPU_REGION_SIZE_32B;
if (size_bytes <= 64) return MPU_REGION_SIZE_64B;
if (size_bytes <= 128) return MPU_REGION_SIZE_128B;
if (size_bytes <= 256) return MPU_REGION_SIZE_256B;
if (size_bytes <= 512) return MPU_REGION_SIZE_512B;
if (size_bytes <= 1*1024) return MPU_REGION_SIZE_1KB;
if (size_bytes <= 2*1024) return MPU_REGION_SIZE_2KB;
if (size_bytes <= 4*1024) return MPU_REGION_SIZE_4KB;
if (size_bytes <= 8*1024) return MPU_REGION_SIZE_8KB;
if (size_bytes <= 16*1024) return MPU_REGION_SIZE_16KB;
if (size_bytes <= 32*1024) return MPU_REGION_SIZE_32KB;
if (size_bytes <= 64*1024) return MPU_REGION_SIZE_64KB;
if (size_bytes <= 128*1024) return MPU_REGION_SIZE_128KB;
if (size_bytes <= 256*1024) return MPU_REGION_SIZE_256KB;
if (size_bytes <= 512*1024) return MPU_REGION_SIZE_512KB;
if (size_bytes <= 1*1024*1024) return MPU_REGION_SIZE_1MB;
if (size_bytes <= 2*1024*1024) return MPU_REGION_SIZE_2MB;
if (size_bytes <= 4*1024*1024) return MPU_REGION_SIZE_4MB;
if (size_bytes <= 8*1024*1024) return MPU_REGION_SIZE_8MB;
if (size_bytes <= 16*1024*1024) return MPU_REGION_SIZE_16MB;
if (size_bytes <= 32*1024*1024) return MPU_REGION_SIZE_32MB;
if (size_bytes <= 64*1024*1024) return MPU_REGION_SIZE_64MB;
if (size_bytes <= 128*1024*1024) return MPU_REGION_SIZE_128MB;
if (size_bytes <= 256*1024*1024) return MPU_REGION_SIZE_256MB;
if (size_bytes <= 512*1024*1024) return MPU_REGION_SIZE_512MB;
if (size_bytes <= 1*1024*1024*1024) return MPU_REGION_SIZE_1GB;
if (size_bytes <= 2*1024*1024*1024) return MPU_REGION_SIZE_2GB;
if (size_bytes <= 4*1024*1024*1024) return MPU_REGION_SIZE_4GB;
return MPU_REGION_SIZE_4GB; // Maximum size
}
Region Priority Rules:
Sub-region Configuration:
void vConfigureSubRegions(uint8_t region_num, uint32_t sub_region_mask) {
MPU_Region_InitTypeDef mpu_region;
// Get current region configuration
HAL_MPU_GetRegionConfig(&mpu_region, region_num);
// Configure sub-regions
mpu_region.SubRegionDisable = sub_region_mask;
// Reapply configuration
HAL_MPU_ConfigRegion(&mpu_region);
}
typedef struct {
uint8_t region_count;
MPU_Region_InitTypeDef regions[16];
bool regions_enabled[16];
} mpu_manager_t;
mpu_manager_t g_mpu_manager = {0};
void vInitializeMPUManager(void) {
// Enable MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
// Initialize region tracking
memset(&g_mpu_manager, 0, sizeof(mpu_manager_t));
printf("MPU Manager initialized\n");
}
uint8_t vAllocateMPURegion(void) {
for (uint8_t i = 0; i < 16; i++) {
if (!g_mpu_manager.regions_enabled[i]) {
g_mpu_manager.regions_enabled[i] = true;
g_mpu_manager.region_count++;
return i;
}
}
return 0xFF; // No free regions
}
void vFreeMPURegion(uint8_t region_num) {
if (region_num < 16 && g_mpu_manager.regions_enabled[region_num]) {
// Disable region
HAL_MPU_DisableRegion(region_num);
g_mpu_manager.regions_enabled[region_num] = false;
g_mpu_manager.region_count--;
}
}
typedef struct {
TaskHandle_t task_handle;
uint8_t stack_region;
uint8_t data_region;
uint8_t code_region;
bool protection_enabled;
} task_memory_protection_t;
task_memory_protection_t task_protection[10];
void vEnableTaskMemoryProtection(TaskHandle_t task_handle) {
// Find free slot
int slot = -1;
for (int i = 0; i < 10; i++) {
if (task_protection[i].task_handle == NULL) {
slot = i;
break;
}
}
if (slot >= 0) {
task_protection[slot].task_handle = task_handle;
// Allocate MPU regions
task_protection[slot].stack_region = vAllocateMPURegion();
task_protection[slot].data_region = vAllocateMPURegion();
task_protection[slot].code_region = vAllocateMPURegion();
// Configure protection
vConfigureTaskStackProtection(task_handle, task_protection[slot].stack_region);
task_protection[slot].protection_enabled = true;
printf("Memory protection enabled for task\n");
}
}
void MemManage_Handler(void) {
// Get fault information
uint32_t mmfar = SCB->MMFAR;
uint32_t cfsr = SCB->CFSR;
uint32_t mmfault = (cfsr >> 7) & 0x1;
uint32_t daccviol = (cfsr >> 1) & 0x1;
uint32_t mmarvalid = (cfsr >> 7) & 0x1;
printf("MPU Fault Detected!\n");
printf("MMAR: 0x%08lx\n", mmfar);
printf("MMFault: %lu\n", mmfault);
printf("DACCViol: %lu\n", daccviol);
printf("MMARValid: %lu\n", mmarvalid);
// Get current task information
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
if (current_task != NULL) {
printf("Fault in task: %s\n", pcTaskGetName(current_task));
}
// Handle fault based on type
if (daccviol) {
// Data access violation - terminate task
printf("Data access violation - terminating task\n");
vTaskDelete(current_task);
} else if (mmfault) {
// Memory management fault - log and continue
printf("Memory management fault - logging\n");
}
// Clear fault flags
SCB->CFSR = cfsr;
}
Privilege Separation:
Implementation:
void vSwitchToUserMode(void) {
// Configure MPU for user mode
__set_CONTROL(0x02); // User mode, no FPU
// ISB to ensure context switch
__ISB();
}
void vSwitchToPrivilegedMode(void) {
// Configure MPU for privileged mode
__set_CONTROL(0x00); // Privileged mode, no FPU
// ISB to ensure context switch
__ISB();
}
Sensitive Data Protection:
Implementation:
void vProtectSensitiveData(uint32_t data_start, uint32_t data_size) {
uint8_t region_num = vAllocateMPURegion();
MPU_Region_InitTypeDef mpu_region;
mpu_region.Number = region_num;
mpu_region.BaseAddress = data_start;
mpu_region.Size = vCalculateMPUSize(data_size);
mpu_region.AccessPermission = MPU_REGION_PRIVILEGED_READ_WRITE;
mpu_region.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
mpu_region.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
mpu_region.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
mpu_region.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&mpu_region);
}
Objective: Set up basic MPU regions for task isolation Steps:
Expected Outcome: Tasks are isolated and memory violations are caught
Objective: Protect individual task stacks from corruption Steps:
Expected Outcome: Stack overflows are contained and don’t affect other tasks
Objective: Protect system-critical resources and data Steps:
Expected Outcome: Critical resources are protected from unauthorized access
This comprehensive Memory Protection document provides embedded engineers with the theoretical foundation, practical implementation examples, and best practices needed to implement robust memory protection systems using MPUs in real-time environments.