在过去的 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 如何做到这一点?文档- 小号 不要提供有关“ 它将为辅助内核 提供启动地址”的任何其他详细信息。
我的挫败感与日俱增,我将非常感谢您的回答。
非常感谢您提前!
更多详情
我使用的文档:
我现在所做的:
以上所有似乎都可以正常工作。
我现在想做的是:
....更新....
我已经开始查看 Linux 内核和 QEMU 源代码以寻找答案。这是我发现的(如果我错了,请纠正我):
**(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
问题:
最佳答案
好的,我回来了,宝贝。以下是结论:
对于我的 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 内核中相关源文件的简短列表:
关于ARM:启动/唤醒/唤醒其他 CPU 内核/AP 并传递执行起始地址?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20055754/