ARM Cortex-A9 L2 Cache Parity Error Injection Failure

The core issue revolves around the inability to trigger a Prefetch Abort by injecting parity errors into the L2 cache of an ARM Cortex-A9 dual-core processor. The goal was to corrupt instructions stored in the L2 cache, which is shared between the instruction and data pipelines, by writing a fixed pattern (e.g., 0x5A5A5A5A) into the cache lines of a specific function. The expectation was that this corruption would cause a parity error during instruction fetch, leading to a Prefetch Abort. However, the Prefetch Abort was not triggered, and the function continued to execute normally. This suggests that the instructions in the L2 cache were not actually corrupted, despite the explicit write operation.

The problem was further complicated by the discovery that the function intended for corruption was not located in the expected memory region. The linker script was configured to allocate a specific section (injection_section) at a fixed address (0x100000), but the function (test_function) was placed outside this region (> 0x200000). This misplacement raised questions about the linker script configuration and the memory layout of the system.

Memory Layout Misconfiguration and Cache Coherency Challenges

The failure to trigger a Prefetch Abort can be attributed to two primary factors: memory layout misconfiguration and cache coherency issues.

Memory Layout Misconfiguration

The linker script was designed to allocate a specific memory region (injection_section) starting at address 0x100000. However, the function (test_function) intended for corruption was not placed within this region. Instead, it was located at an address beyond 0x200000. This misalignment suggests that the linker script did not enforce the placement of the function in the designated section. The linker script configuration likely lacked the necessary directives to ensure that the function was placed in the correct memory region. Additionally, the memory attributes (e.g., cacheability) of the region may not have been correctly configured in the MMU translation tables, further complicating the issue.

Cache Coherency Challenges

The ARM Cortex-A9 employs a unified L2 cache, which serves as the Point of Unification (PoU) for the instruction and data pipelines. The PoU ensures that both pipelines see a consistent view of memory. However, the Cortex-A9’s cache coherency mechanisms may have prevented the intended corruption of instructions in the L2 cache. Specifically, the data pipeline’s write operation to the L2 cache may not have been synchronized with the instruction pipeline, resulting in the instruction pipeline fetching uncorrupted instructions. This lack of synchronization could be due to the absence of cache maintenance operations (e.g., cache invalidation or cleaning) or incorrect configuration of the cache attributes in the MMU.

The Cache Level ID Register (CLIDR) indicates that the Level of Unification Uniprocessor (LoUU) is the L1 cache, meaning the PoU is the L2 cache. However, the maintenance broadcast bit in the ID_MMFR3 register suggests that cache maintenance operations are only related to their own definition. This implies that invalidating the L1 instruction cache to the PoU would not necessarily invalidate the L2 cache, potentially leaving corrupted instructions in the L2 cache undetected by the instruction pipeline.

Resolving Linker Script Misconfiguration and Ensuring Cache Coherency

Correcting Linker Script Misconfiguration

To ensure that the function (test_function) is placed in the designated memory region (injection_section), the linker script must be modified to explicitly enforce the placement of the function. The following steps outline the necessary changes:

  1. Define the Section in the Linker Script: The linker script should define the injection_section with a fixed starting address and size. For example:

    .injection_section 0x100000 :
    {
        *(.injection_section)
    }
    

    This ensures that any code or data assigned to the injection_section is placed at the specified address.

  2. Assign the Function to the Section: The function (test_function) must be explicitly assigned to the injection_section using the __attribute__ directive. For example:

    void __attribute__((section(".injection_section"))) test_function(void) {
        // Function implementation
    }
    

    This ensures that the function is placed in the correct memory region.

  3. Verify the Memory Layout: After compiling and linking the code, the memory layout should be verified using a map file or a debugger to confirm that the function is located at the expected address (0x100000).

Ensuring Cache Coherency

To ensure that the instruction pipeline fetches the corrupted instructions from the L2 cache, the following cache maintenance operations must be performed:

  1. Invalidate the L1 Instruction Cache: Before executing the function, the L1 instruction cache should be invalidated to ensure that the instruction pipeline fetches the instructions from the L2 cache. This can be done using the ISB (Instruction Synchronization Barrier) and ICIALLU (Invalidate All Instruction Caches to PoU) instructions. For example:

    __asm volatile ("ISB");
    __asm volatile ("ICIALLU");
    __asm volatile ("ISB");
    

    This ensures that the instruction pipeline does not use stale instructions from the L1 cache.

  2. Write to the L2 Cache: After disabling L2 cache parity checking, the cache lines corresponding to the function’s memory region should be written with the fixed pattern (e.g., 0x5A5A5A5A). This can be done using a loop to write to each cache line. For example:

    for (uint32_t *addr = (uint32_t *)0x100000; addr < (uint32_t *)0x200000; addr++) {
        *addr = 0x5A5A5A5A;
    }
    

    This ensures that the instructions in the L2 cache are corrupted.

  3. Enable L2 Cache Parity Checking: After writing to the L2 cache, parity checking should be re-enabled to detect the corrupted instructions. This can be done using the appropriate register writes.

  4. Execute the Function: Finally, the function should be executed to trigger the Prefetch Abort. If the instructions in the L2 cache are corrupted, the instruction pipeline should detect the parity error and raise a Prefetch Abort.

Additional Considerations

  • MMU Configuration: Ensure that the MMU translation tables are correctly configured to mark the injection_section as non-cacheable. This prevents the cache from being bypassed during the write operation.
  • Cache Maintenance Broadcast: Verify that the maintenance broadcast bit in the ID_MMFR3 register is correctly configured to ensure that cache maintenance operations are propagated to the L2 cache.
  • Debugging Tools: Use debugging tools (e.g., JTAG, GDB) to monitor the cache state and verify that the instructions in the L2 cache are corrupted.

By addressing the linker script misconfiguration and ensuring proper cache coherency, the Prefetch Abort should be successfully triggered when the corrupted instructions are fetched from the L2 cache. This approach provides a robust method for testing error handling mechanisms in ARM Cortex-A9-based systems.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *