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

标签 arm multiprocessing wakeup cpu-cores cortex-a

在过去的 3-4 天里,我一直在思考这个问题,但找不到合适的解释性文档(来自 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 系列程序员指南 v3.0 (在这里找到: link )在 中说明以下内容第 22.5.2 节 (Linux 中的 SMP 启动,页面 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



Linux 如何做到这一点?文档- 小号 不要提供有关“ 它将为辅助内核 提供启动地址”的任何其他详细信息。

我的挫败感与日俱增,我将非常感谢您的回答。
非常感谢您提前!

更多详情

我使用的文档:
  • 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 进行 AND (&) 并将其用作 GIC 基地址,尽管我几乎可以肯定这是不对的
  • 通过将值 0x1 写入 GICD_CTLR 来启用 GIC 分发器(在 GIC 基地址的偏移量 0x1000 处?)
  • 使用以下参数构造一个 SGI:Group = 0, ID = 0, TargetListFilter = "All CPUs except Me"并通过 GICD_SGIR GIC 寄存器 发送(写入)
  • 由于我没有为其他内核传递任何执行起始地址,所以在这之后什么都没有发生

  • ....更新....

    我已经开始查看 Linux 内核和 QEMU 源代码以寻找答案。这是我发现的(如果我错了,请纠正我):
  • 当给电路板加电时,所有内核都从复位向量开始执行
  • 一个软件(固件)组件在辅助核心上执行 WFI 和一些其他代码,当后者想要再次唤醒它们时,这些代码将充当这些辅助核心和主要核心之间的协议(protocol)
  • 例如EnergyCore ECX-1000(Highbank)板上使用的协议(protocol)如下:
  • **(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?
  • 2. 据我了解,这个软件协议(protocol)因板而异。是这样,还是只依赖于底层处理器?
  • 3. 我在哪里可以找到有关此协议(protocol)的信息,用于选择一个 ARM 板? - 我可以在 ARM 官方网站或主板网页上找到它吗?
  • 最佳答案

    好的,我回来了,宝贝。以下是结论:

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

  • 对于我的 ODROID-XU 板,描述此过程的来源是 UBOOT ODROID-v2012.07以及在此处找到的 linux 内核:LINUX ODROIDXU-3.4.y (如果我从分支 odroid-3.12.y 查看内核版本会更好,因为前者不会启动所有 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;
    }
    

    这成功唤醒了 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 是 0x1C CONFIG_PHY_IRAM_NS_BASE 是 0x02073000 ,因此上面的内核是硬编码的 |5067910791079107910816191079100
  • LINUX 内核:通用 - smp.c (查看函数 __cpu_up )、平台特定(odroid-xu):platsmp.c (函数 boot_secondary ,由泛型 __cpu_up 调用;另请查看 platform_smp_prepare_cpus [在底部] => 这是实际设置引导基数和功率基数的函数)|79104|65

  • 关于ARM:启动/唤醒/唤醒其他 CPU 内核/AP 并传递执行起始地址?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20055754/

    相关文章:

    linux - 交叉编译隧道

    c - 如何使用 C 语言迭代 ARM AT91SAM7S256 微 Controller 中的 LED 列表

    android - 在 Android NDK 中使用 -fsigned-char 构建是否安全?

    linux - 设置唤醒警报的 bash 脚本设置了错误的时间

    linux - 关于Uboot中的BOOTCMD

    python - 在 Python 的多进程中硬杀死挂起的子进程

    Python多处理: where should join() be called,如果进程的 child 有孙子?

    python - 如何在多线程中使用python多处理代理对象

    android - 唤醒 Android 手机/平板电脑?

    android - Android Vitals 警告 Google Play 服务地理围栏的过度唤醒