ARM Cortex-M23 HardFault During RAM Function Execution in Secure World
The ARM Cortex-M23 is a highly efficient, low-power processor designed for embedded applications, particularly those requiring secure execution environments. However, when attempting to execute functions located in RAM using the __attribute__((section(.ramfunc)))
directive, a HardFault can occur. This issue is particularly prevalent when operating in the secure world, where memory protection and execution permissions are strictly enforced. The HardFault is triggered when the processor attempts to execute code from a RAM address that is not properly configured for execution. This problem is often rooted in the Memory Protection Unit (MPU) configuration, which governs the access permissions for different memory regions.
In the Cortex-M23 architecture, the MPU plays a critical role in defining the memory attributes and access permissions for various regions, including RAM. By default, RAM regions are often marked as non-executable (NX) to enhance security. When a function is placed in RAM using the .ramfunc
section, the MPU must be configured to allow execution from that region. Failure to do so results in a HardFault, as the processor cannot execute code from a non-executable memory region.
The issue is further complicated by the fact that the Cortex-M23 operates in Thumb mode, where branch instructions (e.g., BX
) require the target address to have the least significant bit (LSB) set to 1 to indicate Thumb execution. If the address is not properly aligned or lacks the necessary execution permissions, the processor will generate a HardFault. Additionally, the secure world environment imposes stricter memory access controls, making it essential to ensure that the MPU is correctly configured to allow execution from the designated RAM region.
MPU Configuration and RAM Execution Permissions
The primary cause of the HardFault when executing functions in RAM on the Cortex-M23 is the improper configuration of the MPU. The MPU is responsible for defining the memory attributes and access permissions for different regions, including RAM. By default, RAM regions are often marked as non-executable (NX) to prevent the execution of potentially malicious code. When a function is placed in RAM using the .ramfunc
section, the MPU must be configured to allow execution from that region.
In the Cortex-M23 architecture, the MPU configuration is defined by a set of registers that specify the base address, size, and attributes for each memory region. The attributes include access permissions (e.g., read, write, execute) and memory type (e.g., normal, device, strongly ordered). To allow execution from a RAM region, the MPU must be configured to mark that region as executable (X). This involves setting the appropriate bits in the MPU region attribute and size register (MPU_RASR).
Another potential cause of the HardFault is the omission of the necessary memory barriers or cache management operations. The Cortex-M23 does not have a cache, but memory barriers are still required to ensure that the processor’s memory accesses are properly synchronized. When executing code from RAM, it is essential to ensure that the processor’s instruction pipeline is flushed and that any pending memory operations are completed before branching to the RAM function. This can be achieved using the Data Synchronization Barrier (DSB) and Instruction Synchronization Barrier (ISB) instructions.
The secure world environment adds an additional layer of complexity to the MPU configuration. In the secure world, the MPU is used to enforce stricter memory access controls, ensuring that only trusted code can access sensitive resources. This requires careful configuration of the MPU to ensure that the RAM region containing the .ramfunc
section is marked as executable in the secure world. Failure to do so will result in a HardFault when the processor attempts to execute code from that region.
Configuring the MPU and Ensuring Proper RAM Execution Permissions
To resolve the HardFault issue when executing functions in RAM on the Cortex-M23, the MPU must be properly configured to allow execution from the designated RAM region. This involves the following steps:
-
Enable the MPU: The MPU must be enabled before it can be configured. This is done by setting the MPU enable bit in the MPU Control Register (MPU_CTRL). The MPU_CTRL register is located at address 0xE000ED94 and can be accessed using the following code:
MPU->CTRL = MPU_CTRL_ENABLE_Msk;
-
Define the RAM Region: The next step is to define the RAM region that will be used for executing the
.ramfunc
section. This involves setting the base address, size, and attributes for the region. The base address is specified in the MPU Region Base Address Register (MPU_RBAR), and the size and attributes are specified in the MPU Region Attribute and Size Register (MPU_RASR). The following code demonstrates how to configure a RAM region for execution:MPU->RBAR = (RAM_BASE_ADDRESS & MPU_RBAR_ADDR_Msk) | (REGION_NUMBER << MPU_RBAR_REGION_Pos); MPU->RASR = (MPU_RASR_ENABLE_Msk) | (MPU_RASR_SIZE_Msk & (LOG2_RAM_SIZE << MPU_RASR_SIZE_Pos)) | (MPU_RASR_XN_Msk & 0) | (MPU_RASR_AP_Msk & MPU_RASR_AP_FULL_ACCESS) | (MPU_RASR_TEX_Msk & MPU_RASR_TEX_NORMAL) | (MPU_RASR_S_Msk & MPU_RASR_S_SHAREABLE) | (MPU_RASR_C_Msk & MPU_RASR_C_CACHEABLE) | (MPU_RASR_B_Msk & MPU_RASR_B_BUFFERABLE);
-
Set Execution Permissions: The RAM region must be marked as executable by clearing the Execute Never (XN) bit in the MPU_RASR register. This allows the processor to execute code from the specified RAM region. The XN bit is located at bit 28 of the MPU_RASR register and can be cleared using the following code:
MPU->RASR &= ~MPU_RASR_XN_Msk;
-
Enable the MPU Region: Once the RAM region has been defined and configured, it must be enabled by setting the enable bit in the MPU_RASR register. This is done by setting bit 0 of the MPU_RASR register:
MPU->RASR |= MPU_RASR_ENABLE_Msk;
-
Ensure Proper Memory Synchronization: Before branching to the RAM function, it is essential to ensure that the processor’s instruction pipeline is flushed and that any pending memory operations are completed. This can be achieved using the Data Synchronization Barrier (DSB) and Instruction Synchronization Barrier (ISB) instructions:
__DSB(); __ISB();
-
Branch to the RAM Function: Finally, the processor can branch to the RAM function using the
BX
instruction. The target address must have the least significant bit (LSB) set to 1 to indicate Thumb execution:void (*ram_func)(void) = (void (*)(void))(RAM_FUNCTION_ADDRESS | 0x1); ram_func();
By following these steps, the MPU can be properly configured to allow execution from the designated RAM region, resolving the HardFault issue. It is important to note that the exact configuration may vary depending on the specific requirements of the application and the memory map of the target device. Additionally, the secure world environment may require additional configuration to ensure that the RAM region is accessible and executable in the secure world.
In conclusion, the HardFault issue when executing functions in RAM on the Cortex-M23 is primarily caused by improper MPU configuration. By carefully configuring the MPU to allow execution from the designated RAM region and ensuring proper memory synchronization, the issue can be resolved, allowing the processor to execute code from RAM without generating a HardFault.