MPU Configuration and Memory Protection Failure
The issue at hand revolves around the Memory Protection Unit (MPU) on an ARM Cortex-M processor failing to trigger a MemManage fault or HardFault when a protected memory region is accessed. The MPU is a critical component in embedded systems for enforcing memory access rules, ensuring that unauthorized access to specific memory regions is prevented. In this case, the MPU is configured to protect a 64-byte memory region starting at address 0x20000000
. Despite the configuration, writing to this protected region does not result in the expected fault, raising concerns about the correctness of the MPU setup or the underlying hardware-software interaction.
The provided code snippet demonstrates the problem. The MPU is configured using the following registers:
MPU_RNR
(MPU Region Number Register) to select the region.MPU_RBAR
(MPU Region Base Address Register) to set the base address of the protected region.MPU_RASR
(MPU Region Attribute and Size Register) to define the size, access permissions, and other attributes of the region.MPU_CTRL
(MPU Control Register) to enable the MPU.SCB_SHCSR
(System Handler Control and State Register) to enable the MemManage fault handler.
The code attempts to write to the protected memory region using inline assembly in the main
function. However, the write operation succeeds without triggering any fault, indicating that the MPU is not enforcing the configured memory protection rules.
Incorrect Register Configuration and Assembly Code Typo
The failure of the MPU to trigger a fault can be attributed to two primary issues: an incorrect configuration of the MPU registers and a typo in the assembly code used to perform the memory write operation.
MPU Register Configuration Issues
The MPU configuration in the provided code is incomplete. Specifically, the MPU_RBAR
register is missing the VALID
bit, which is crucial for enabling the region. The VALID
bit (bit 4 of MPU_RBAR
) must be set to indicate that the base address and region number are valid. Without this bit set, the MPU region is effectively disabled, and the memory protection rules are not enforced.
The MPU_RASR
register is configured with the value 0x1608FF0B
, which corresponds to the following attributes:
- Size: 64 bytes (encoded as
0x08
in the size field). - Access permissions: Read-only for both privileged and unprivileged access (encoded as
0x03
in the AP field). - Execute Never (XN) bit: Set to 0, allowing execution from this region.
- Sub-region disable bits: All sub-regions are enabled (
0xFF
). - Memory attributes:
b=s=c=0
, indicating that the region is not cacheable, not shareable, and not bufferable.
While the MPU_RASR
configuration appears correct, the absence of the VALID
bit in MPU_RBAR
renders the entire MPU configuration ineffective.
Assembly Code Typo
The assembly code in the main
function contains a critical typo that prevents the intended memory access from being properly tested. The code is as follows:
LDR R0, =0x20000000
MOV R1, 0x77777777
STR R1, [R5,#0]
The issue lies in the last instruction, STR R1, [R5,#0]
. The intention is to store the value in R1
(which is 0x77777777
) into the memory location pointed to by R0
(which is 0x20000000
). However, the code mistakenly uses R5
instead of R0
as the base register for the store operation. Since R5
is not initialized in this context, its value is undefined, leading to an unpredictable memory access. This typo not only prevents the intended test of the MPU protection but also introduces undefined behavior that could result in a HardFault for reasons unrelated to the MPU configuration.
Correcting MPU Configuration and Assembly Code
To resolve the issue and ensure that the MPU correctly triggers a MemManage fault when the protected memory region is accessed, the following steps must be taken:
Setting the VALID Bit in MPU_RBAR
The MPU_RBAR
register must be configured with the VALID
bit set to indicate that the region is active. The correct configuration for MPU_RBAR
is as follows:
MPU_RBAR = 0x20000000 | (0x00 << 4) | 0x10;
Here, 0x20000000
is the base address, 0x00
is the region number (region 0), and 0x10
sets the VALID
bit. This ensures that the MPU region is properly enabled and that the base address and region number are recognized as valid.
Fixing the Assembly Code Typo
The assembly code in the main
function must be corrected to use R0
as the base register for the store operation. The corrected code is as follows:
LDR R0, =0x20000000
MOV R1, 0x77777777
STR R1, [R0,#0]
This ensures that the value in R1
is stored at the memory location 0x20000000
, which is the intended protected region. With this correction, the MPU should trigger a MemManage fault when the store operation is attempted.
Verifying the MPU Configuration
After making the above corrections, it is essential to verify that the MPU is correctly configured and that the fault handlers are properly set up. The following steps should be taken:
-
Enable the MemManage Fault Handler: Ensure that the MemManage fault handler is enabled in the
SCB_SHCSR
register. This is already done in the provided code with the lineSCB_SHCSR |= 0x00010000;
. -
Enable the MPU: Ensure that the MPU is enabled by setting the
ENABLE
bit in theMPU_CTRL
register. This is also correctly done in the provided code with the lineMPU_CTRL = 0x00000005;
. -
Check the Fault Status Registers: If a fault occurs, the fault status registers (
SCB_CFSR
,SCB_HFSR
, etc.) should be checked to determine the cause of the fault. This can be done within theMemManage_Handler
orHardFault_Handler
functions. -
Debugging with Breakpoints: Set breakpoints in the
MemManage_Handler
andHardFault_Handler
functions to confirm that the fault is being triggered as expected. This can help verify that the MPU is correctly enforcing the memory protection rules.
Example of Corrected Code
The following is the corrected version of the provided code, incorporating the fixes discussed above:
#define MPU_CTRL (*((volatile unsigned long*) 0xE000ED94))
#define MPU_RNR (*((volatile unsigned long*) 0xE000ED98))
#define MPU_RBAR (*((volatile unsigned long*) 0xE000ED9C))
#define MPU_RASR (*((volatile unsigned long*) 0xE000EDA0))
#define SCB_SHCSR (*((volatile unsigned long*) 0xE000ED24)) // System handler control and state register
void Registers_Init(void) {
// MPU Configuring
MPU_RNR = 0x00000000; // region 0
MPU_RBAR = 0x20000000 | (0x00 << 4) | 0x10; // base address is 0x20000000, region 0, VALID bit set
MPU_RASR = 0x1608FF0B; // 64 bytes, ro/ro, b=s=c=0
SCB_SHCSR |= 0x00010000; // enable MemManage Fault
MPU_CTRL = 0x00000005; // enable memory protection unit, guaranteeing default privileged access
}
void MemManage_Handler(void) {
__asm(
"MOV R4, 0x77777777\n\t"
"MOV R5, 0x77777777\n\t"
);
}
void HardFault_Handler(void) {
__asm(
"MOV R6, 0x77777777\n\t"
"MOV R7, 0x77777777\n\t"
);
}
int main(void) {
Registers_Init();
while(1) {
__asm(
"LDR R0, =0x20000000\n\t"
"MOV R1, 0x77777777\n\t"
"STR R1, [R0,#0]\n\t"
);
}
return (1);
}
void SystemInit(void) {}
Conclusion
The failure of the MPU to trigger a MemManage fault when accessing a protected memory region is primarily due to two issues: the absence of the VALID
bit in the MPU_RBAR
register and a typo in the assembly code used to perform the memory write operation. By setting the VALID
bit and correcting the assembly code, the MPU should now correctly enforce the memory protection rules and trigger the appropriate fault when an unauthorized access is attempted. Additionally, verifying the MPU configuration and fault handlers ensures that the system behaves as expected and provides the necessary debugging information to diagnose any further issues.