armmultiprocessingwakeupcpu-corescortex-a

ARM: Start/Wakeup/Bringup the other CPU cores/APs and pass execution start address?


I've been banging my head with this for the last 3-4 days and I can't find a DECENT explanatory documentation (from ARM or unofficial) to help me. I've got an ODROID-XU board (big.LITTLE 2 x Cortex-A15 + 2 x Cortex-A7) board and I'm trying to understand a bit more about the ARM architecture. In my "experimenting" code I've now arrived at the stage where I want to WAKE UP THE OTHER CORES FROM THEIR WFI (wait-for-interrupt) state.

The missing information I'm still trying to find is:

1. When getting the base address of the memory-mapped GIC I understand that I need to read CBAR; But no piece of documentation explains how the bits in CBAR (the 2 PERIPHBASE values) should be arranged to get to the final GIC base address

2. When sending an SGI through the GICD_SGIR register, what interrupt ID between 0 and 15 should I choose? Does it matter?

3. When sending an SGI through the GICD_SGIR register, how can I tell the other cores WHERE TO START EXECUTION FROM?

4. How does the fact that my code is loaded by the U-BOOT bootloader affect this context?

The Cortex-A Series Programmer's Guide v3.0 (found here: link) states the following in section 22.5.2 (SMP boot in Linux, page 271):

While the primary core is booting, the secondary cores will be held in a standby state, using the WFI instruction. It (the primary core) will provide a startup address to the secondary cores and wake them using an Inter-Processor Interrupt(IPI), meaning an SGI signalled through the GIC

How does Linux do that? The documentation-S don't give any other details regarding "It will provide a startup address to the secondary cores".

My frustration is growing and I'd be very grateful for answers. Thank you very much in advance!

EXTRA DETAILS

Documentation I use:

What I've done by now:

All the above seems to work properly.

What I'm trying to do now:

....UPDATE....

I've started looking at the Linux kernel and QEMU source codes in search for an answer. Here's what I found out (please correct me if I'm wrong):

**(1)** the secondary cores enter WFI and when

**(2)** the primary core sends an SGI to wake them up

**(3)** they check if the value at address (0x40 + 0x10 * coreid) is non-null;

**(4)** if it is non-null, they use it as an address to jump to (execute a BX)

**(5)** otherwise, they re-enter standby state, by re-executing WFI

**(6)** So, if I had an EnergyCore ECX-1000 board, I should write (0x40 + 0x10 * coreid) with the address I want each of the cores to jump to and send an SGI

Questions:


Solution

  • Ok, I'm back baby. Here are the conclusions:

    For my ODROID-XU board the sources describing this process are UBOOT ODROID-v2012.07 and the linux kernel found here: LINUX ODROIDXU-3.4.y (it would have been better if I looked into kernel version from the branch odroid-3.12.y since the former doesn't start all of the 8 processors, just 4 of them but the latter does).

    Anyway, here's the source code I've come up with, I'll post the relevant source files from the above source code trees that helped me writing this code afterwards:

    typedef unsigned int DWORD;
    typedef unsigned char BOOLEAN;
    #define FAILURE (0)
    #define SUCCESS (1)
    #define NR_EXTRA_CPUS (3) // actually 7, but this kernel version can't wake them up all -> check kernel version 3.12 if you need this
    
    // Hardcoded in the kernel and in U-Boot; here I've put the physical addresses for ease
    // In my code (and in the linux kernel) these addresses are actually virtual
    // (thus the 'VA' part in S5P_VA_...); note: mapped with memory type DEVICE
    #define S5P_VA_CHIPID (0x10000000)
    #define S5P_VA_SYSRAM_NS (0x02073000)
    #define S5P_VA_PMU (0x10040000)
    #define EXYNOS_SWRESET ((DWORD) S5P_VA_PMU + 0x0400)
    // Other hardcoded values
    #define EXYNOS5410_REV_1_0 (0x10)
    #define EXYNOS_CORE_LOCAL_PWR_EN (0x3)
    
    BOOLEAN BootAllSecondaryCPUs(void* CPUExecutionAddress){
    
    // 1. Get bootBase (the address where we need to write the address where the woken CPUs will jump to)
    //    and powerBase (we also need to power up the cpus before waking them up (?))
    DWORD bootBase, powerBase, powerOffset, clusterID;
    
    asm volatile ("mrc p15, 0, %0, c0, c0, 5" : "=r" (clusterID));
    clusterID = (clusterID >> 8);
    powerOffset = 0;
    if( (*(DWORD*)S5P_VA_CHIPID & 0xFF) < EXYNOS5410_REV_1_0 )
    {
        if( (clusterID & 0x1) == 0 ) powerOffset = 4;
    }
    else if( (clusterID & 0x1) != 0 ) powerOffset = 4;
    
    bootBase = S5P_VA_SYSRAM_NS + 0x1C;
    powerBase = (S5P_VA_PMU + 0x2000) + (powerOffset * 0x80);
    
    // 2. Power up each CPU, write bootBase and send a SEV (they are in WFE [wait-for-event] standby state)
    for (i = 1; i <= NR_EXTRA_CPUS; i++)
    {
        // 2.1 Power up this CPU
        powerBase += 0x80;
        DWORD powerStatus = *(DWORD*)( (DWORD) powerBase + 0x4);
    
        if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) == 0)
        {
            *(DWORD*) powerBase = EXYNOS_CORE_LOCAL_PWR_EN;
            for (i = 0; i < 10; i++) // 10 millis timeout
            {
                powerStatus = *(DWORD*)((DWORD) powerBase + 0x4);
                if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) == EXYNOS_CORE_LOCAL_PWR_EN)
                    break;
                DelayMilliseconds(1); // not implemented here, if you need this, post a comment request 
            }
            if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) != EXYNOS_CORE_LOCAL_PWR_EN)
                return FAILURE;
        }
        if ( (clusterID & 0x0F) != 0 )
        {
            if ( *(DWORD*)(S5P_VA_PMU + 0x0908) == 0 )
            do { DelayMicroseconds(10); } // not implemented here, if you need this, post a comment request
            while (*(DWORD*)(S5P_VA_PMU + 0x0908) == 0);
            *(DWORD*) EXYNOS_SWRESET = (DWORD)(((1 << 20) | (1 << 8)) << i);
        }
    
        // 2.2 Write bootBase and execute a SEV to finally wake up the CPUs
        asm volatile ("dmb" : : : "memory");
        *(DWORD*) bootBase = (DWORD) CPUExecutionAddress;
        asm volatile ("isb");
        asm volatile ("\n   dsb\n   sev\n   nop\n");
    }
    return SUCCESS;
    }
    

    This successfully wakes 3 of 7 of the secondary CPUs.

    And now for that short list of relevant source files in u-boot and the linux kernel: