【问题标题】:ARM: Start/Wakeup/Bringup the other CPU cores/APs and pass execution start address?ARM:启动/唤醒/启动其他 CPU 内核/AP 并传递执行起始地址?
【发布时间】:2013-12-02 01:45:53
【问题描述】:

在过去的 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):

在主核心启动时,辅助核心将保持在备用状态,使用 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 = "All CPUs except Me" 并通过 GICD_SGIR GIC 寄存器发送(写入)
  • 由于我没有传递其他内核的任何执行起始地址,所以在这一切之后什么都没有发生

....更新....

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

  • 为开发板加电时,所有内核都从复位向量开始执行
  • 软件(固件) 组件在辅助核心上执行 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?
  • 2。据我了解,此软件协议因板而异。是这样,还是只取决于底层处理器?
  • 3。我在哪里可以找到有关 Pick-one ARM 板的此协议的信息? - 我可以在 ARM 官方网站或开发板网页上找到它吗?

【问题讨论】:

  • 我相信这种初始化序列(即从待机中唤醒次级内核)是由芯片制造商(即TI,三星,... )。例如,这是在 TI OMAP 中完成的。 omappedia.org/wiki/Bootloader_Project
  • 我相信(显然)它必须是以下之一:ROM 代码、BL1 代码 => 两者都无法访问(BL1 已加密 [签名]),或者引导加载程序...我已经搜索了一下引导加载程序源,但我没有找到与此 SMP 协议的任何关系;另外,Linux 似乎并不依赖引导加载程序的参数来发现协议(可能通过 ATAG),这让我相信我正在寻找的内容要么在 ROM 代码中,要么在 BL1 代码中。 .问题是哪个,因为它们无法直接访问,我在哪里可以找到关于它们为我的董事会做了什么的详细信息(ODROID-XU)......
  • 我想你的意思是“即让辅助核心处于待机状态”
  • 这种细节可以由某种制造商的 SoC 技术数据表提供。这就是为什么我会首先尝试查看三星资源,特别是 Exynos 5。我无法足够快地找到合适的表,所以我提供了一个类似 OMAP 芯片的链接,只是为了让您了解 TI 如何处理这些事情。
  • 哦,相信我,我也试过了;我在那里打印的文档列表不完整:我也有 Exynos Dual 5 用户手册,搜索了其他相关的三星文档……但不幸的是,我在他们的文档中也找不到所需的信息;一切都是一团糟……所以我希望其他已经做过这件事的人能帮助我,也许有人知道 ARM 架构上 linux 内核的内在特性……不幸的是,这就是互联网告诉我的 @987654323 @

标签: arm multiprocessing wakeup cpu-cores cortex-a


【解决方案1】:

转到www.arm.com 并在那里下载 DS-5 开发套件的评估副本。安装后,在示例下将有一个startup_Cortex-A15MPCore directory。看startup.s

【讨论】:

  • 如果再详细一点,这可能会更有帮助。 startup.s 说明了什么?
  • 嗨,很抱歉没有早点发布,我已经成功解决了这个问题;我明天会发布我的结论和解决方案,现在我真的没有时间
【解决方案2】:

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

  • 使 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 处的值 strong> 不归零并跳转到它; _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,因此上述 linux 内核中的硬编码

  • LINUX 内核:通用 - smp.c(查看函数 __cpu_up),平台特定(odroid-xu):platsmp.c (函数 boot_secondary,由通用 __cpu_up 调用;另请查看 platform_smp_prepare_cpus [在底部] => 这是实际设置引导基础和电源基础值的函数)

【讨论】:

    【解决方案3】:

    为了清楚起见和将来参考,由于缺少有关 Exynos 启动协议的适当文档,此处缺少一条微妙的信息(nb 这个问题确实应该标记为“Exynos 5”而不是“Cortex-A15” - 它是特定于 SoC 的东西,而 ARM 所说的只是一般建议)。 从冷启动开始,辅助核心不在 WFI 中,它们仍然处于关闭状态

    我在编写引导 shim 以在 XU 上运行虚拟机管理程序的过程中制定了更简单的最小解决方案(基于 Linux 的 hotplug 所做的),它需要两个步骤:

    1. 先将入口点地址写入Exynos持笔(0x02073000 + 0x1c)
    2. 然后戳电源控制器以打开相关内核:这样,它们会退出安全启动路径,进入握持笔以找到等待它们的入口点,从而跳过 WFI 循环并且根本不需要接触 GIC。

    除非您正在计划一个完整的 CPU 热插拔实施,否则您可以跳过检查集群 ID - 如果我们正在启动,我们在集群 0 上并且没有其他地方(检查具有向后集群寄存器的预生产芯片在 Odroid 上也应该是不必要的 - 当然是对我而言)。

    根据我的调查,启动 A7 的难度更大。从the Exynos big.LITTLE switcher driver 来看,您似乎需要先插入一组单独的电源控制器寄存器才能启用集群 1(您可能也需要弄乱 CCI,尤其是要打开 MMU 和缓存)-我没有没有更进一步,因为到那时它更像是“玩得开心”而不是“做真正的工作”......

    顺便说一句,Samsung's mainline patch for CPU hotplug on the 5410 使核心电源控制内容比其下游代码 IMO 中的混乱更加清晰。

    【讨论】:

    • 感谢您的澄清,我只是想说我从标题中删除了“Cortex-A15”但没有添加“Exynos”部分,因为讨论可以帮助一般的ARM程序员,不管是哪个SoC,即唤醒其他内核的过程对于其他特定的SoC都可以类似地找到;因此,谷歌搜索也可以将它们指向这里
    【解决方案4】:

    QEMU 使用 PSCI

    ARM 电源状态协调接口 (PSCI) 记录在:https://developer.arm.com/docs/den0022/latest/arm-power-state-coordination-interface-platform-design-document 并控制诸如打开和关闭内核之类的事情。

    TL;DR 这是在 QEMU v3.0.0 ARMv8 aarch64 上唤醒 CPU 1 的 aarch64 sn-p:

    /* PSCI function identifier: CPU_ON. */
    ldr w0, =0xc4000003
    /* Argument 1: target_cpu */
    mov x1, 1
    /* Argument 2: entry_point_address */
    ldr x2, =cpu1_entry_address
    /* Argument 3: context_id */
    mov x3, 0
    /* Unused hvc args: the Linux kernel zeroes them,
     * but I don't think it is required.
     */
    hvc 0
    

    对于 ARMv7:

    ldr r0, =0x84000003
    mov r1, #1
    ldr r2, =cpu1_entry_address
    mov r3, #0
    hvc 0
    

    此答案的 ARM 部分提供了带有自旋锁的完整可运行示例:What does multicore assembly language look like?

    hvc 指令随后由 EL2 处理程序处理,另请参阅:What are Ring 0 and Ring 3 in the context of operating systems? 的 ARM 部分

    Linux 内核

    在 Linux v4.19 中,该地址通过设备树通知 Linux 内核,例如 QEMU 会自动生成以下形式的条目:

        psci {
                method = "hvc";
                compatible = "arm,psci-0.2", "arm,psci";
                cpu_on = <0xc4000003>;
                migrate = <0xc4000005>;
                cpu_suspend = <0xc4000001>;
                cpu_off = <0x84000002>;
        };
    

    hvc 指令调用自:https://github.com/torvalds/linux/blob/v4.19/drivers/firmware/psci.c#L178

    static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)
    

    最终到达:https://github.com/torvalds/linux/blob/v4.19/arch/arm64/kernel/smccc-call.S#L51

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-24
      • 2012-08-25
      相关资源
      最近更新 更多