ARM Cortex-M33/M55 Vector Table Relocation and INVSTATE Fault Overview
The ARM Cortex-M33 and Cortex-M55 processors, based on the ARMv8-M architecture, provide advanced features such as TrustZone security, enhanced DSP capabilities, and improved performance. One of the critical aspects of these processors is the Vector Table, which holds the addresses of exception handlers and is essential for proper exception handling. Relocating the Vector Table dynamically is a common requirement in embedded systems, especially in scenarios where multiple firmware images or runtime configurations are involved. However, improper handling of Vector Table relocation can lead to subtle and hard-to-debug issues, such as the INVSTATE fault, which occurs when the processor attempts to execute an instruction in an invalid state.
In the context of the Cortex-M33/M55, the INVSTATE fault is often triggered when the Thumb bit (EPSR.T) is cleared, indicating that the processor is attempting to execute an ARM instruction instead of a Thumb instruction. This fault can occur during Vector Table relocation if the cache coherency between the Data Cache (D-Cache) and Instruction Cache (I-Cache) is not maintained. The ARMv8-M architecture explicitly states that if the Vector Table is located in a cacheable memory region, any modification to the Vector Table must be treated as self-modifying code, and appropriate cache maintenance operations must be performed to ensure coherency.
The core issue arises when the Vector Table is relocated to a cacheable memory region, such as SRAM, and the D-Cache is not flushed after updating the Vector Table entries. This results in the I-Cache fetching stale data, leading to the INVSTATE fault when the processor attempts to execute an invalid instruction. The problem is exacerbated by the fact that the Cortex-M33/M55 processors have separate D-Cache and I-Cache, and the hardware does not automatically maintain coherency between them.
Cache Coherency and Memory Barrier Omission in Vector Table Relocation
The root cause of the INVSTATE fault in the context of Vector Table relocation on the Cortex-M33/M55 processors is the lack of cache coherency between the D-Cache and I-Cache. When the Vector Table is relocated to a cacheable memory region, such as SRAM, the following sequence of events typically occurs:
- The Vector Table is updated in SRAM, and the new address is written to the Vector Table Offset Register (VTOR).
- The D-Cache is updated with the new Vector Table entries, but the I-Cache still holds the old or stale data.
- When an exception, such as an SVC, is triggered, the processor fetches the exception handler address from the Vector Table.
- If the I-Cache contains stale data, the processor may fetch an invalid address or an incorrect Thumb bit setting, leading to the INVSTATE fault.
The ARMv8-M architecture mandates that any modification to the Vector Table in a cacheable memory region must be treated as self-modifying code. This means that the following cache maintenance operations must be performed:
- Data Cache Clean and Invalidate by Modified Virtual Address (DCCIMVAC): This operation ensures that the updated Vector Table entries are written back to the main memory and invalidates the corresponding cache lines in the D-Cache.
- Instruction Cache Invalidate All (ICCIALLU): This operation invalidates the entire I-Cache, ensuring that the processor fetches the updated Vector Table entries from the main memory.
Failure to perform these cache maintenance operations results in the I-Cache holding stale data, which can lead to the INVSTATE fault. Additionally, the timing of these operations is critical. The cache maintenance operations must be performed immediately after updating the Vector Table and before any exception is triggered.
Implementing Cache Maintenance Operations for Vector Table Relocation
To resolve the INVSTATE fault caused by cache coherency issues during Vector Table relocation on the Cortex-M33/M55 processors, the following steps must be taken:
-
Ensure Proper Alignment and Initialization of the Vector Table: The Vector Table must be aligned to a 1024-byte boundary, as required by the ARMv8-M architecture. Additionally, all exception handlers in the Vector Table must be properly initialized, with the Thumb bit (bit[0]) set to 1 for each handler.
-
Perform Cache Maintenance Operations After Updating the Vector Table: After updating the Vector Table entries in SRAM, the following cache maintenance operations must be performed:
- Clean and invalidate the specific cache line corresponding to the updated Vector Table entry using the DCCIMVAC operation.
- Invalidate the entire I-Cache using the ICCIALLU operation to ensure that the processor fetches the updated Vector Table entries from the main memory.
-
Update the Vector Table Offset Register (VTOR): After performing the cache maintenance operations, the new address of the Vector Table must be written to the VTOR register. This ensures that the processor uses the updated Vector Table for exception handling.
-
Trigger the Exception: After ensuring that the cache maintenance operations have been performed and the VTOR has been updated, the exception (e.g., SVC) can be triggered. The processor will now fetch the correct exception handler address from the updated Vector Table, avoiding the INVSTATE fault.
The following code snippet demonstrates the correct implementation of Vector Table relocation with cache maintenance operations:
extern void svc_handler(void);
// Define the Vector Table structure with proper alignment
typedef struct {
uint32_t *SPMain;
void (*Reset)(void);
void (*NMI)(void);
void (*HardFault)(void);
void (*MemManage)(void);
void (*BusFault)(void);
void (*UsageFault)(void);
void (*SecureFault)(void);
uint32_t Res[3];
void (*SVC)(void);
void (*DebugMonitor)(void);
uint32_t Res4;
void (*PendSV)(void);
void (*SysTick)(void);
void (*IRQ[NUMIRQ])(void);
} stl_vect_t __attribute__((aligned(1024)));
// Global Vector Table allocated in SRAM
stl_vect_t vect;
// Define pointers to the VTOR, DCCIMVAC, and ICCIALLU registers
volatile uint32_t *VTOR = (uint32_t*)0xE000ED08;
volatile uint32_t *DCCIMVAC = (uint32_t*)0xE000EF70;
volatile uint32_t *ICCIALLU = (uint32_t*)0xE000EF50;
// Function to relocate the Vector Table
void relocate_vector_table(void) {
// Initialize the SVC handler in the Vector Table
vect.SVC = svc_handler;
// Clean and invalidate the D-Cache for the updated Vector Table entry
*DCCIMVAC = (uint32_t)&vect.SVC;
// Invalidate the entire I-Cache
*ICCIALLU = 0x0;
// Update the VTOR with the new Vector Table address
*VTOR = (uint32_t)&vect;
}
// Trigger the SVC exception
void trigger_svc(void) {
__asm volatile ("svc #0");
}
int main(void) {
// Relocate the Vector Table
relocate_vector_table();
// Trigger the SVC exception
trigger_svc();
return 0;
}
In this implementation, the relocate_vector_table
function performs the necessary cache maintenance operations after updating the Vector Table entries. The DCCIMVAC
operation ensures that the updated Vector Table entry is written back to the main memory and invalidates the corresponding cache line in the D-Cache. The ICCIALLU
operation invalidates the entire I-Cache, ensuring that the processor fetches the updated Vector Table entries from the main memory. Finally, the VTOR is updated with the new Vector Table address, and the SVC exception is triggered.
By following these steps, the INVSTATE fault caused by cache coherency issues during Vector Table relocation on the Cortex-M33/M55 processors can be effectively resolved. This approach ensures that the processor always fetches the correct exception handler addresses from the updated Vector Table, avoiding any invalid state exceptions.