ARM Cortex-M7 MPU Configuration and Privilege Mode Transition Fault
The issue at hand involves an unexpected MemManage Fault on an ARM Cortex-M7 processor (specifically the STM32H7 series) when transitioning from privileged thread mode to unprivileged thread mode. The fault is triggered immediately after writing to the CONTROL register to switch to unprivileged mode, with the fault status register indicating both stacking and instruction access violations. This suggests that the Memory Protection Unit (MPU) configuration or the transition process itself is misconfigured, leading to access violations when the processor attempts to execute instructions or manage the stack in unprivileged mode.
The Cortex-M7 MPU is designed to enforce memory access rules based on the configured regions and the current privilege level of the executing code. When transitioning to unprivileged mode, the processor must ensure that all memory regions required for execution, stack operations, and peripheral access are accessible in unprivileged mode. The fault indicates that this is not the case, and the root cause likely lies in one or more of the following areas: incorrect MPU region settings, improper handling of the privilege transition, or insufficient memory access permissions for unprivileged mode.
The provided code configures three MPU regions: one for Flash memory (region 0), one for SRAM (region 1), and one for peripherals (region 2). Each region is configured with specific attributes, including access permissions, cache policies, and region sizes. However, the fault suggests that either the regions are not covering all necessary memory areas or the access permissions are insufficient for unprivileged mode.
Misconfigured MPU Regions and Access Permissions
The primary cause of the MemManage Fault lies in the MPU region configuration and the access permissions set for unprivileged mode. The Cortex-M7 MPU requires precise configuration to ensure that all memory areas required for execution and stack operations are accessible in both privileged and unprivileged modes. The fault occurs because the current MPU configuration does not adequately cover the memory areas needed during the transition to unprivileged mode.
The Flash memory region (region 0) is configured with read-only access for both privileged and unprivileged modes. While this is generally correct for code execution, it does not account for potential read-write operations that might occur during exception handling or stack operations. The SRAM region (region 1) is configured with full access for both privileged and unprivileged modes, which is appropriate for stack and global variable access. However, the region size of 256MB is excessive and could lead to unintended overlaps or gaps in memory coverage.
The peripherals region (region 2) is configured with full access for both privileged and unprivileged modes, which is acceptable for most use cases. However, the region size of 256MB is again excessive and could lead to unintended overlaps or gaps in memory coverage. Additionally, the use of device memory attributes for peripherals is correct, but the sharable attribute might not be necessary for all peripherals.
The fault is triggered when the processor attempts to execute the ISB instruction after writing to the CONTROL register. This indicates that the processor is unable to access the necessary memory areas for instruction fetching or stack operations in unprivileged mode. The stacking violation suggests that the stack pointer is pointing to a memory area that is not accessible in unprivileged mode, while the instruction access violation suggests that the code being executed is not accessible in unprivileged mode.
Correcting MPU Configuration and Ensuring Safe Privilege Transitions
To resolve the MemManage Fault, the MPU configuration must be revised to ensure that all necessary memory areas are accessible in unprivileged mode. This involves adjusting the region sizes, access permissions, and memory attributes to match the actual memory layout and usage requirements of the application.
First, the Flash memory region (region 0) should be adjusted to cover only the necessary code area, typically the size of the Flash memory on the STM32H7. The access permissions should remain read-only for both privileged and unprivileged modes, but the region size should be reduced to avoid unintended overlaps. For example, if the Flash memory size is 2MB, the region size should be set to 2MB instead of 256MB.
Second, the SRAM region (region 1) should be adjusted to cover only the necessary SRAM area, typically the size of the SRAM on the STM32H7. The access permissions should remain full access for both privileged and unprivileged modes, but the region size should be reduced to avoid unintended overlaps. For example, if the SRAM size is 1MB, the region size should be set to 1MB instead of 256MB.
Third, the peripherals region (region 2) should be adjusted to cover only the necessary peripheral area, typically the size of the peripheral memory space on the STM32H7. The access permissions should remain full access for both privileged and unprivileged modes, but the region size should be reduced to avoid unintended overlaps. For example, if the peripheral memory space is 1MB, the region size should be set to 1MB instead of 256MB.
Additionally, the privilege transition process should be carefully reviewed to ensure that all necessary memory areas are accessible in unprivileged mode before the transition occurs. This includes ensuring that the stack pointer is pointing to a valid memory area in the SRAM region and that the code being executed is accessible in unprivileged mode.
The revised MPU configuration code should look like this:
void mpu_config_test(void)
{
ARM_MPU_Disable();
/*
* Set region 0 : FLASH : code
* Normal memory, non-sharable write through, no write allocate
* C = 1, B = 0, TEX = 0, S = 0
* SRD = 0, XN = 0
* AP = Privileged : Read-only / Unprivileged : Read-only
*/
MPU->RBAR = ARM_MPU_RBAR(0U, 0x00000000);
MPU->RASR = ARM_MPU_RASR_EX(
0U, /* DisableExec: 1 = disable instruction fetches */
ARM_MPU_AP_FULL, /* AccessPermission */
ARM_MPU_ACCESS_NORMAL( /* Memory access attribution */
ARM_MPU_CACHEP_WT_NWA, /* Outer cache policy */
ARM_MPU_CACHEP_WT_NWA, /* Inner cache policy */
0 /* Sharable */
),
0x00UL, /* SubRegionDisable */
ARM_MPU_REGION_SIZE_2MB /* Size */
);
/*
* Set region 1 : SRAM : stack + global
* Normal memory, sharable, write through, no write allocate.
* C = 1, B = 0, TEX = 0, S = 1
* SRD = 0, XN = 1
* AP = Privileged : full-access / Unprivileged : full-access
*/
MPU->RBAR = ARM_MPU_RBAR(1U, 0x20000000);
MPU->RASR = ARM_MPU_RASR_EX(
0U, /* DisableExec: 1 = disable instruction fetches */
ARM_MPU_AP_FULL, /* AccessPermission */
ARM_MPU_ACCESS_NORMAL( /* Memory access attribution */
ARM_MPU_CACHEP_WT_NWA, /* Outer cache policy */
ARM_MPU_CACHEP_WT_NWA, /* Inner cache policy */
1 /* Sharable */
),
0x00UL, /* SubRegionDisable */
ARM_MPU_REGION_SIZE_1MB /* Size */
);
/* Set region 2 : peripherals
* Device memory, sharable
* C = 1, B = 0, TEX = 0, S = 1
* SRD = 0, XN = 1
* AP = Privileged : full-access / Unprivileged : full-access
*/
MPU->RBAR = ARM_MPU_RBAR(2U, 0x40000000);
MPU->RASR = ARM_MPU_RASR_EX(
0U, /* DisableExec: 1 = disable instruction fetches */
ARM_MPU_AP_FULL, /* AccessPermission */
ARM_MPU_ACCESS_DEVICE( /* Memory access attribution */
1 /* Sharable */
),
0x00UL, /* SubRegionDisable */
ARM_MPU_REGION_SIZE_1MB /* Size */
);
/* Enable default memory map as a background region for privileged access */
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);
}
After revising the MPU configuration, the privilege transition code should be reviewed to ensure that all necessary memory areas are accessible in unprivileged mode before the transition occurs. This includes ensuring that the stack pointer is pointing to a valid memory area in the SRAM region and that the code being executed is accessible in unprivileged mode.
The revised privilege transition code should look like this:
int main()
{
/* Initialize the system */
/* .... */
/* Config the MPU */
mpu_config_test();
/* Some other code that runs correctly */
/* Drop down to unprivileged thread mode */
asm volatile ( "MRS R0, CONTROL\n\t"
"ORR R0, R0, #0x1\n\t"
"MSR CONTROL, R0\n\t" /* <-- this instruction triggers the fault */
"ISB \n\t"
);
}
By carefully revising the MPU configuration and ensuring that all necessary memory areas are accessible in unprivileged mode, the MemManage Fault can be resolved, and the privilege transition can be performed safely.