The Embedded New Testament

The "Holy Bible" for embedded engineers


Project maintained by theEmbeddedGeorge Hosted on GitHub Pages — Theme by mattgraham

Memory Protection in Real-Time Systems

Comprehensive guide to implementing Memory Protection Units (MPU) for task isolation, memory safety, and system security in embedded real-time systems with FreeRTOS examples

🎯 Concept → Why it matters → Minimal example → Try it → Takeaways

Concept

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.

Why it matters

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.

Minimal example

// 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(&region);
}

// 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);
}

Try it

Takeaways

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.


📋 Table of Contents


🎯 Overview

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.

Key Concepts


🛡️ MPU Fundamentals

What is an MPU?

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:

MPU Architecture

Core Components:

Memory Access Flow:

CPU Memory Request → MPU Region Check → Permission Validation → Access Granted/Denied

MPU Benefits in RTOS

1. Task Isolation:

2. Fault Containment:

3. Security Enhancement:


🔒 Task Isolation Strategies

1. Stack Isolation

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);
    }
}

2. Data Structure Isolation

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);
}

3. Code Protection

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);
}

🗺️ Memory Region Configuration

MPU Region Types

1. Privileged Access Regions:

2. User Access Regions:

3. Read-Only Regions:

4. Execute-Only Regions:

Region Size Calculation

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 and Overlap

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);
}

💻 Implementation Examples

Complete MPU Management System

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--;
    }
}

Task-Specific Memory Protection

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");
    }
}

MPU Fault Handler

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;
}

🔐 Security Considerations

Privilege Level Management

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();
}

Secure Data Handling

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);
}

Best Practices

Design Principles

  1. Minimize Region Usage
    • Use regions efficiently
    • Group related memory areas
    • Plan region allocation carefully
  2. Consistent Permission Model
    • Define clear access rules
    • Document permission requirements
    • Apply consistently across system
  3. Fault Handling Strategy
    • Plan response to MPU faults
    • Implement graceful degradation
    • Log and monitor violations

Implementation Guidelines

  1. Region Configuration
    • Start with conservative permissions
    • Test thoroughly before production
    • Monitor performance impact
  2. Task Management
    • Configure protection during task creation
    • Clean up regions on task deletion
    • Handle dynamic memory allocation
  3. Debugging Support
    • Implement comprehensive fault handlers
    • Provide debugging information
    • Support runtime configuration

🔬 Guided Labs

Lab 1: Basic MPU Configuration

Objective: Set up basic MPU regions for task isolation Steps:

  1. Enable MPU and configure basic regions
  2. Create tasks with different memory access permissions
  3. Test memory access violations
  4. Implement MPU fault handlers

Expected Outcome: Tasks are isolated and memory violations are caught

Lab 2: Task Stack Protection

Objective: Protect individual task stacks from corruption Steps:

  1. Configure MPU regions for each task stack
  2. Implement stack overflow detection
  3. Test stack corruption scenarios
  4. Verify fault isolation

Expected Outcome: Stack overflows are contained and don’t affect other tasks

Lab 3: Critical Resource Protection

Objective: Protect system-critical resources and data Steps:

  1. Identify critical system resources
  2. Configure MPU regions with appropriate permissions
  3. Test access control under different conditions
  4. Implement graceful fault handling

Expected Outcome: Critical resources are protected from unauthorized access


Check Yourself

Understanding Check

Practical Skills Check

Advanced Concepts Check


Prerequisites

Next Steps


📋 Quick Reference: Key Facts

Memory Protection Fundamentals

MPU Architecture

Task Isolation Strategies

Implementation Considerations


Interview Questions

Basic Concepts

  1. What is an MPU and how does it differ from an MMU?
    • MPU provides hardware memory protection
    • Works with physical addresses
    • Limited number of regions
    • Simpler than MMU
  2. How do you implement task isolation using MPU?
    • Dedicated memory regions per task
    • Stack and data protection
    • Access permission enforcement
    • Fault handling
  3. What are the main MPU region types?
    • Privileged access regions
    • User access regions
    • Read-only regions
    • Execute-only regions

Advanced Topics

  1. How do you handle MPU faults in real-time systems?
    • Implement fault handlers
    • Log violation information
    • Terminate violating tasks
    • Maintain system stability
  2. Explain MPU region priority and overlap handling.
    • Lower numbers have higher priority
    • Overlapping regions use priority rules
    • Sub-regions for fine control
  3. How do you implement secure data handling with MPU?
    • Protected memory regions
    • Privilege-based access
    • Secure communication buffers
    • Cryptographic key protection

Practical Scenarios

  1. Design an MPU-based task isolation system.
    • Plan memory regions
    • Implement protection logic
    • Handle dynamic allocation
    • Manage fault conditions
  2. How would you protect sensitive data in an embedded system?
    • Identify sensitive data
    • Configure protected regions
    • Implement access control
    • Monitor violations
  3. Explain MPU configuration for a multi-task RTOS.
    • Region allocation strategy
    • Permission management
    • Dynamic configuration
    • Performance optimization

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.