Skip to content

Latest commit

 

History

History
711 lines (557 loc) · 23.5 KB

File metadata and controls

711 lines (557 loc) · 23.5 KB

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

  • Experiment: Set up MPU regions for different tasks and try to access protected memory
  • Challenge: Implement a system where critical tasks are completely isolated from non-critical ones
  • Debug: Use MPU fault handlers to detect and log memory access violations

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

  • Memory Protection Unit (MPU) - Hardware memory access control
  • Task Isolation - Preventing tasks from accessing each other's memory
  • Memory Regions - Configurable memory areas with specific permissions
  • Access Control - Read, write, and execute permissions for memory regions
  • Fault Handling - Responding to memory access violations

🛡️ 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: Physical addresses, limited regions (8-16), simple permissions
  • MMU: Virtual addresses, unlimited pages, complex memory management

MPU Architecture

Core Components:

  • Region Registers: Define memory areas and their permissions
  • Permission Logic: Enforce access rights during memory operations
  • Fault Detection: Trigger exceptions on access violations
  • Region Selection: Choose appropriate region for each memory access

Memory Access Flow:

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

MPU Benefits in RTOS

1. Task Isolation:

  • Prevent task A from corrupting task B's memory
  • Isolate critical system components
  • Create secure execution environments

2. Fault Containment:

  • Limit impact of software bugs
  • Prevent stack overflow damage
  • Protect against buffer overflows

3. Security Enhancement:

  • Prevent unauthorized memory access
  • Protect sensitive data structures
  • Create trusted execution zones

🔒 Task Isolation Strategies

1. Stack Isolation

How It Works:

  • Each task gets dedicated stack memory region
  • MPU prevents access to other task stacks
  • Stack overflow triggers MPU fault instead of corruption

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:

  • Critical data structures in protected memory regions
  • Tasks can only access their assigned data regions
  • Shared data in specially configured regions

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:

  • Executable code in read-only memory regions
  • Prevent code modification at runtime
  • Separate code and data regions

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:

  • Only privileged tasks can access
  • Used for system-critical data
  • Kernel and driver memory areas

2. User Access Regions:

  • Both privileged and user tasks can access
  • Used for shared data structures
  • Communication buffers

3. Read-Only Regions:

  • Data cannot be modified
  • Used for constants and configuration
  • Code sections

4. Execute-Only Regions:

  • Code can be executed but not read
  • Used for proprietary algorithms
  • Security-sensitive code

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:

  • Lower region numbers have higher priority
  • Overlapping regions use higher priority region
  • Sub-regions can disable specific areas

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:

  • Kernel runs in privileged mode
  • User tasks run in unprivileged mode
  • MPU enforces privilege-based access

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:

  • Cryptographic keys in protected regions
  • Authentication data in isolated memory
  • Secure communication buffers

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

  • Can you explain why memory protection is important in real-time systems?
  • Do you understand the difference between MPU and MMU?
  • Can you identify what memory regions need protection?
  • Do you know how to handle MPU faults?

Practical Skills Check

  • Can you configure MPU regions for basic task isolation?
  • Do you know how to protect task stacks from corruption?
  • Can you implement MPU fault handlers?
  • Do you understand how to balance protection with performance?

Advanced Concepts Check

  • Can you explain how to implement dynamic memory protection?
  • Do you understand the trade-offs in MPU region configuration?
  • Can you design a comprehensive memory protection strategy?
  • Do you know how to debug MPU-related issues?

🔗 Cross-links

Related Topics

Prerequisites

Next Steps


📋 Quick Reference: Key Facts

Memory Protection Fundamentals

  • Purpose: Hardware-enforced memory isolation and access control
  • Types: MPU (Memory Protection Unit), MMU (Memory Management Unit)
  • Characteristics: Region-based, permission-controlled, fault-detecting
  • Benefits: Task isolation, fault containment, security enhancement

MPU Architecture

  • Region Registers: Define memory areas and their permissions
  • Permission Logic: Enforce access rights during memory operations
  • Fault Detection: Trigger exceptions on access violations
  • Region Selection: Choose appropriate region for each memory access

Task Isolation Strategies

  • Stack Isolation: Each task gets dedicated, protected stack region
  • Data Isolation: Separate memory regions for task-specific data
  • Code Protection: Execute-only regions for critical code
  • Resource Isolation: Protected access to shared resources

Implementation Considerations

  • Region Limits: Most MPUs support 8-16 memory regions
  • Performance Impact: MPU checks add minimal memory access overhead
  • Configuration: Regions must be configured before use
  • Fault Handling: Must implement handlers for access violations

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.