ARM:启动/唤醒/启动其他CPU内核/ AP并传递执行起始地址?

Cor*_*uzu 16 arm multiprocessing cpu-cores wakeup cortex-a

在过去的3-4天里,我一直在用这个来敲打我的脑袋,我找不到DECENT解释性文件(来自ARM或非官方的)来帮助我.我有一块ODROID-XU板(big.LITTLE 2 x Cortex-A15 + 2 x Cortex-A7)板,我正在尝试更多地了解ARM架构.在我的"试验"代码中,我现在已经到了我想从其WFI(等待中断)状态唤醒其他核心的阶段.

我仍然试图找到的缺失信息是:

1.获取内存映射GIC的基地址时,我明白我需要读取CBAR; 但没有任何文档解释如何安排CBAR中的位(2个PERIPHBASE值)来获取最终的GIC基址

2.通过GICD_SGIR寄存器发送SGI时,我应该选择0到15之间的中断ID?有关系吗?

3.通过GICD_SGIR寄存器发送SGI时,如何告知其他内核何处开始执行

4. U-BOOT引导加载程序加载我的代码的事实如何影响这个上下文?

的Cortex-A系列程序员指南3.0版(这里找到:链接)规定在以下部分22.5.2(在Linux中SMP开机,页271):

当主核心启动时,辅助核心将使用WFI指令保持待机状态.它(主核心)将为辅助核心提供启动地址,并使用处理器间中断(IPI)唤醒它们,这意味着通过GIC发信号通知SGI

Linux如何做到这一点?文档-S没有提供有关" 它将为辅助核心提供启动地址 "的任何其他详细信息.

我的挫折感越来越大,我会非常感谢你的答案.非常感谢你提前!

额外细节

我使用的文档:

  • ARMv7-A&R架构参考手册
  • Cortex-A15 TRM(技术参考手册)
  • Cortex-A15 MPCore TRM
  • Cortex-A系列程序员指南v3.0
  • GICv2架构规范

我现在做了什么:

  • UBOOT将我加载到0x40008000; 我已经设置了翻译表(TTB),相应地写了TTBR0和TTBCR并将0x40008000映射到0x8000_0000(2GB),所以我也启用了MMU
  • 设置我自己的异常处理程序
  • 我在串口上有Printf功能(ODROID-XU上的UART2)

以上所有似乎都能正常工作.

我现在要做的是:

  • 获取GIC基地址=>在我读CBAR的那一刻,我简单地用(和)它的值与0xFFFF8000并使用它作为GIC基地址,虽然我几乎可以肯定这是不对的
  • 通过写入值为0x1的GICD_CTLR,启用GIC分配器(偏离GIC基地址偏移0x1000?)
  • 使用以下参数构造SGI:Group = 0,ID = 0,TargetListFilter ="除了我以外的所有CPU"并通过GICD_SGIR GIC寄存器发送(写入)
  • 由于我没有传递其他内核的任何执行起始地址,所以这一切都没有发生

.... UPDATE ....

我已经开始查看Linux内核和QEMU源代码以寻找答案.这是我发现的(如果我错了,请纠正我):

  • 当给电路板加电时,ALL THE CORES从复位向量开始执行
  • 软件(固件)组件上的次级铁心和一些其他的代码,将作为这些二次铁芯和初级芯之间的协议中,当后者希望再次唤醒他们执行WFI
  • 例如,EnergyCore ECX-1000(Highbank)板上使用的协议如下:

**(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

问题:

  • 1.执行此操作的软件组件是什么?是我在SD卡上写的BL1二进制文件,还是U-BOOT?
  • 据我所知,这个软件协议因板而异.是这样,还是只依赖于底层处理器?
  • 3.哪里可以找到有关此协议的选择ARM板的信息? - 我可以在ARM官方网站或董事会网页上找到它吗?

Cor*_*uzu 9

好的,我回来了.以下是结论:

  • 使CPU进入休眠状态的软件组件是引导加载程序(在我的例子中是U-Boot)
  • Linux以某种方式知道引导程序如何执行此操作(在每个板的Linux内核中进行硬编码)并知道如何再次唤醒它们

对于我的ODROID-XU板,描述这个过程的源代码是UBOOT ODROID-v2012.07和linux内核:LINUX ODROIDXU-3.4.y(如果我从分支odroid-3.12查看内核版本会更好. Ÿ因为前者不启动所有8个处理器的,只是其中的4,但后者则).

无论如何,这是我提出的源代码,我将发布上述源代码树中的相关源文件,这些源代码树帮助我之后编写了这段代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

这成功唤醒了7个辅助CPU中的3个.

现在,对于u-boot和linux内核中相关源文件的简短列表:

  • UBOOT:lowlevel_init.S - 通知行363-369,辅助CPU如何在WFE中等待_hotplug_addr的值为非归零并跳转到它; _hotplug_addr实际上是上面代码中的bootBase; 还有第282-285行告诉我们_hotplug_addr将被重新定位在CONFIG_PHY_IRAM_NS_BASE + _hotplug_addr - nscode_base(_hotplug_addr - nscode_base是0x1CCONFIG_PHY_IRAM_NS_BASE是0x02073000,因此上面的linux内核中的硬编码)

  • LINUX KERNEL:泛型 - smp.c(查看函数__cpu_up),特定平台(odroid-xu):platsmp.c(函数boot_secondary,通用__cpu_up调用;另见platform_smp_prepare_cpus [在底部] =>这是函数实际设置启动基数和功率基值)