ARM Cortex-A9 MPCore CP15 Affinity Register Misread During Dual-Core Initialization
The ARM Cortex-A9 MPCore processor is a widely used multicore architecture in embedded systems, known for its performance and flexibility. However, during the initialization of a dual-core system, a critical issue arises where the CP15 affinity register (MPIDR) is misread, causing both cores to incorrectly identify themselves as Core 0. This misidentification leads to redundant and potentially conflicting initialization steps, such as reinitializing the MMU, caches, and other system resources that should only be handled by the primary core. The issue manifests when both cores execute the same startup code, despite being located at different memory addresses (Core 0 at 0x10000000 and Core 1 at 0x10300000). The MPIDR register, which should uniquely identify each core, consistently returns 0x80000000 for both cores, indicating a fundamental problem in the core identification process.
The MPIDR register is a critical component in multicore systems, as it provides the affinity information necessary for the operating system and runtime environment to distinguish between cores. In the Cortex-A9, the MPIDR register contains fields for the CPU ID, cluster ID, and other topology information. The CPU ID field (bits [1:0]) is particularly important, as it uniquely identifies each core within a cluster. When this field is misread, the system cannot properly distribute tasks or manage resources, leading to inefficiencies and potential runtime errors.
The issue is further complicated by the use of an RTOS that relies on the MPIDR register to determine which core it is running on. If both cores believe they are Core 0, the RTOS may attempt to initialize system resources multiple times, leading to undefined behavior. Additionally, the debugger confirms that the program counters (PCs) for Core 0 and Core 1 are correctly located in their respective memory regions, indicating that the cores are executing the correct code but misidentifying themselves.
Misconfigured MPIDR Register Access and Secondary Core Startup Sequence
The root cause of the MPIDR misread issue lies in a combination of factors, including potential misconfiguration of the CP15 coprocessor, incorrect handling of the secondary core startup sequence, and possible hardware or toolchain limitations. The MPIDR register is accessed using the MRC instruction, which reads a coprocessor register into an ARM register. In this case, the instruction mrc p15, 0, r2, c0, c0, 5
is used to read the MPIDR register into register r2. However, the value returned is consistently 0x80000000, which suggests that the CPU ID field (bits [1:0]) is not being correctly populated.
One possible cause is that the secondary core is not being properly initialized before it starts executing code. In the Cortex-A9, secondary cores typically start in a "wait for interrupt" (WFI) state and must be woken up using a software-generated interrupt (SGI). If the secondary core is not correctly woken up or if the SGI is not properly configured, the core may not fully initialize its internal state, including the MPIDR register. This could result in the register returning an incorrect value.
Another potential cause is the use of an incorrect or incomplete startup sequence for the secondary core. The NXP iMX6DQ SDK code writes the starting address of the secondary core’s code into the SRC_GPR3 register and sets the core 1 enable bit in the SRC_SCR register. However, if this sequence is not followed precisely or if additional steps are required, the secondary core may not initialize correctly. For example, the MMU and caches may need to be explicitly disabled or invalidated before the secondary core starts executing code.
Finally, the issue could be related to the toolchain or debugger being used. The IAR toolset and J-Link probe are commonly used for ARM development, but they may have limitations or bugs that affect the reading of the MPIDR register. For example, the debugger may not correctly display the value of the MPIDR register or may interfere with the core initialization process.
Correcting MPIDR Register Access and Secondary Core Initialization
To resolve the MPIDR misread issue, a systematic approach is required to ensure that the CP15 coprocessor is correctly configured, the secondary core startup sequence is properly executed, and the toolchain and debugger are not interfering with the process. The following steps outline the necessary actions to correct the issue:
-
Verify CP15 Configuration: Ensure that the CP15 coprocessor is correctly configured to allow access to the MPIDR register. This includes checking that the coprocessor access control register (CPACR) is set to allow full access to CP15. The CPACR is located at CP15, c1, c0, 2, and should be set to 0xF00000 to enable full access to CP15.
-
Check MPIDR Register Access: Verify that the MRC instruction used to read the MPIDR register is correctly formatted and executed. The instruction
mrc p15, 0, r2, c0, c0, 5
should read the MPIDR register into register r2. Ensure that the instruction is being executed in the correct mode (e.g., supervisor mode) and that there are no intervening instructions that could affect the result. -
Validate Secondary Core Startup Sequence: Review the secondary core startup sequence to ensure that all necessary steps are being followed. This includes writing the starting address of the secondary core’s code into the SRC_GPR3 register, setting the core 1 enable bit in the SRC_SCR register, and sending an SGI to wake up the secondary core. Additionally, ensure that the MMU and caches are properly configured before the secondary core starts executing code.
-
Disable MMU and Caches: Before the secondary core starts executing code, disable the MMU and caches to ensure that the core starts in a clean state. This can be done by writing to the SCTLR register (CP15, c1, c0, 0) to disable the MMU and caches. For example, the following code can be used to disable the MMU and caches:
mrc p15, 0, r0, c1, c0, 0 // Read SCTLR into r0 bic r0, r0, #0x1 // Clear bit 0 to disable MMU bic r0, r0, #0x1000 // Clear bit 12 to disable I-cache bic r0, r0, #0x4 // Clear bit 2 to disable D-cache mcr p15, 0, r0, c1, c0, 0 // Write modified SCTLR back
-
Use Data Synchronization Barriers: Ensure that data synchronization barriers (DSB) and instruction synchronization barriers (ISB) are used where necessary to ensure that the core’s state is consistent. For example, after disabling the MMU and caches, use a DSB to ensure that all previous memory accesses are complete, and an ISB to ensure that the core’s instruction pipeline is flushed.
-
Verify Toolchain and Debugger Configuration: Ensure that the IAR toolset and J-Link probe are correctly configured and are not interfering with the core initialization process. Check for any known issues or bugs related to the MPIDR register and apply any necessary updates or patches.
-
Test with Minimal Code: Create a minimal test case that isolates the MPIDR register access and secondary core startup sequence. This will help to identify any issues in the startup code and ensure that the MPIDR register is being correctly read. For example, the following code can be used to test the MPIDR register access:
__boot // Setup temporary vector table b reset b . // undefined b . // svc b . // prefetch b . // abort b . // reserved vector b . // irq b . // fiq reset // Write the address of the vector table into the VBAR register ldr r0, =__boot mcr p15, 0, r0, c12, c0, 0 // Write VBAR register // Get core ID and save it in r5 mrc p15, 0, r2, c0, c0, 5 // Read multiprocessor affinity register and r2, r2, #3 // Mask off, leaving CPU ID field mov r5, r2 // Save core ID for later
-
Debugging and Verification: Use the debugger to step through the startup code and verify that the MPIDR register is being correctly read. Check the value of r2 after the MRC instruction is executed and ensure that it matches the expected CPU ID. If the value is incorrect, review the CP15 configuration and startup sequence to identify any issues.
By following these steps, the MPIDR misread issue can be resolved, ensuring that each core correctly identifies itself and initializes the system resources as intended. This will prevent redundant initialization steps and ensure that the system operates efficiently and reliably.