根据ARM官方文档的说明, 主CPU在作内存,系统资源初始化的过程中, 从CPU可以处于上电或者不上电状态。我是一个好奇心很强的人,想在内核的代码中找到QCOM多核是如何实现的。
经过几天的学习,在LK之后,主CPU上电,其他CPU未上电。在主CPU调用start_kernel-》rest_init-》kernel_init_freeable。(init/main.c)
在kernel_init_freeable中,smp_prepare_cpus(setup_max_cpus); smp_init() 是两个重要SMP设置函数。
arch/arm64/kernel/smp.c
void __init smp_prepare_cpus(unsigned int max_cpus)
{
int err;
unsigned int cpu, ncores = num_possible_cpus();
init_cpu_topology();
smp_store_cpu_info(smp_processor_id());
/*
* are we trying to boot more cores than exist?
*/
if (max_cpus > ncores)
max_cpus = ncores;
/* Don't bother if we're effectively UP */
if (max_cpus <= 1)
return;
/*
* Initialise the present map (which describes the set of CPUs
* actually populated at the present time) and release the
* secondaries from the bootloader.
*
* Make sure we online at most (max_cpus - 1) additional CPUs.
*/
max_cpus--;
for_each_possible_cpu(cpu) {
if (max_cpus == 0)
break;
if (cpu == smp_processor_id())
continue;
if (!cpu_ops[cpu])
continue;
err = cpu_ops[cpu]->cpu_prepare(cpu);
if (err)
continue;
set_cpu_present(cpu, true);
max_cpus--;
}
}
cpu_ops 如何得到。
根据device tree
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x0>;
enable-method = "qcom,8994-arm-cortex-acc";
qcom,acc = <0xa>;
next-level-cache = <0xb>;
linux,phandle = <0x2>;
phandle = <0x2>;
l2-cache {
compatible = "arm,arch-cache";
cache-level = <0x2>;
power-domain = <0xc>;
linux,phandle = <0xb>;
phandle = <0xb>;
l2-tlb {
qcom,dump-size = <0x4000>;
linux,phandle = <0x113>;
phandle = <0x113>;
};
};
};
drivers/soc/qcom/cpu_ops.c
static const struct cpu_operations msm8994_cortex_a_ops = {
.name = "qcom,8994-arm-cortex-acc",
.cpu_init = msm_cpu_init,
.cpu_prepare = msm_cpu_prepare,
.cpu_boot = msm8994_cpu_boot,
.cpu_postboot = msm_cpu_postboot,
#ifdef CONFIG_HOTPLUG_CPU
.cpu_die = msm_wfi_cpu_die,
#endif
.cpu_suspend = msm_pm_collapse,
};
CPU_METHOD_OF_DECLARE(msm8994_cortex_a_ops, &msm8994_cortex_a_ops);
cpu_prepare,将调用msm_cpu_prepare,设置CPU reset的地址
static int __init msm_cpu_prepare(unsigned int cpu)
{
u64 mpidr_el1 = cpu_logical_map(cpu);
if (scm_is_mc_boot_available()) {
if (mpidr_el1 & ~MPIDR_HWID_BITMASK) {
pr_err("CPU%d:Failed to set boot address\n", cpu);
return -ENOSYS;
}
if (scm_set_boot_addr_mc(virt_to_phys(secondary_holding_pen), // cpu reset之后的地址
BIT(MPIDR_AFFINITY_LEVEL(mpidr_el1, 0)),
BIT(MPIDR_AFFINITY_LEVEL(mpidr_el1, 1)),
BIT(MPIDR_AFFINITY_LEVEL(mpidr_el1, 2)),
SCM_FLAG_COLDBOOT_MC)) {
pr_warn("CPU%d:Failed to set boot address\n", cpu);
return -ENOSYS;
}
} else {
if (scm_set_boot_addr(virt_to_phys(secondary_holding_pen),
cold_boot_flags[cpu])) {
pr_warn("Failed to set CPU %u boot address\n", cpu);
return -ENOSYS;
}
}
kernel/smp.c
/* Called by boot processor to activate the rest. */
void __init smp_init(void)
{
unsigned int cpu;
idle_threads_init();
/* FIXME: This should be done in userspace --RR */
for_each_present_cpu(cpu) {
if (num_online_cpus() >= setup_max_cpus)
break;
if (!cpu_online(cpu) && boot_cpu(cpu))
cpu_up(cpu);
}
free_boot_cpu_mask();
/* Any cleanup work */
printk(KERN_INFO "Brought up %ld CPUs\n", (long)num_online_cpus());
smp_cpus_done(setup_max_cpus);
}
kernel/cpu.c
int __cpuinit cpu_up(unsigned int cpu)
{
int err = 0;
cpu_maps_update_begin();
if (cpu_hotplug_disabled) {
err = -EBUSY;
goto out;
}
err = _cpu_up(cpu, 0);
out:
cpu_maps_update_done();
return err;
}
/* Requires cpu_add_remove_lock to be held */
static int __cpuinit _cpu_up(unsigned int cpu, int tasks_frozen)
{
int ret, nr_calls = 0;
void *hcpu = (void *)(long)cpu;
unsigned long mod = tasks_frozen ? CPU_TASKS_FROZEN : 0;
struct task_struct *idle;
cpu_hotplug_begin();
.......
/* Arch-specific enabling code. */
ret = __cpu_up(cpu, idle);
if (ret != 0)
goto out_notify;
BUG_ON(!cpu_online(cpu));
/* Wake the per cpu threads */
smpboot_unpark_threads(cpu);
/* Now call notifier in preparation. */
cpu_notify(CPU_ONLINE | mod, hcpu);
out_notify:
if (ret != 0)
__cpu_notify(CPU_UP_CANCELED | mod, hcpu, nr_calls, NULL);
out:
cpu_hotplug_done();
trace_sched_cpu_hotplug(cpu, ret, 1);
return ret;
}
arch/arm64/kernel/smp.c
int __cpuinit __cpu_up(unsigned int cpu, struct task_struct *idle)
{
int ret;
/*
* We need to tell the secondary core where to find its stack and the
* page tables.
*/
secondary_data.stack = task_stack_page(idle) + THREAD_START_SP;
__flush_dcache_area(&secondary_data, sizeof(secondary_data));
/*
* Now bring the CPU into our world.
*/
ret = boot_secondary(cpu, idle);
if (ret == 0) {
/*
* CPU was successfully started, wait for it to come online or
* time out.
*/
wait_for_completion_timeout(&cpu_running,
msecs_to_jiffies(1000));
if (!cpu_online(cpu)) {
pr_crit("CPU%u: failed to come online\n", cpu);
ret = -EIO;
}
} else {
pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
}
secondary_data.stack = NULL;
return ret;
}
static int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle)
{
if (cpu_ops[cpu]->cpu_boot)
return cpu_ops[cpu]->cpu_boot(cpu);
return -EOPNOTSUPP;
}
根据以上结果,cpu_boot将是msm8994_cpu_boot,
drivers/soc/qcom/cpu_ops.c
static int msm8994_cpu_boot(unsigned int cpu)
{
int ret = 0;
if (per_cpu(cold_boot_done, cpu) == false) {
if (of_board_is_sim()) {
ret = msm_unclamp_secondary_arm_cpu_sim(cpu);
if (ret)
return ret;
} else {
ret = msm8994_unclamp_secondary_arm_cpu(cpu);
if (ret)
return ret;
}
per_cpu(cold_boot_done, cpu) = true;
}
return secondary_pen_release(cpu);
}
int msm8994_unclamp_secondary_arm_cpu(unsigned int cpu)
{
int ret = 0;
struct device_node *cpu_node, *acc_node, *l2_node, *l2ccc_node;
void __iomem *acc_reg, *ldo_bhs_reg;
cpu_node = of_get_cpu_node(cpu, NULL);
if (!cpu_node)
return -ENODEV;
acc_node = of_parse_phandle(cpu_node, "qcom,acc", 0);
if (!acc_node) {
ret = -ENODEV;
goto out_acc;
}
l2_node = of_parse_phandle(cpu_node, "next-level-cache", 0);
if (!l2_node) {
ret = -ENODEV;
goto out_l2;
}
l2ccc_node = of_parse_phandle(l2_node, "power-domain", 0);
if (!l2ccc_node) {
ret = -ENODEV;
goto out_l2;
}
/*
* Ensure L2-cache of the CPU is powered on before
* unclamping cpu power rails.
*/
ret = power_on_l2_cache(l2ccc_node, cpu);
if (ret) {
pr_err("L2 cache power up failed for CPU%d\n", cpu);
goto out_l2ccc;
}
ldo_bhs_reg = of_iomap(acc_node, 0);
if (!ldo_bhs_reg) {
ret = -ENOMEM;
goto out_bhs_reg;
}
acc_reg = of_iomap(acc_node, 1);
if (!acc_reg) {
ret = -ENOMEM;
goto out_acc_reg;
}
/* Assert head switch enable few */
writel_relaxed(0x00000001, acc_reg + CPU_PWR_GATE_CTL);
mb();
udelay(1);
/* Assert head switch enable rest */
writel_relaxed(0x00000003, acc_reg + CPU_PWR_GATE_CTL);
mb();
/* De-assert coremem clamp. This is asserted by default */
writel_relaxed(0x00000079, acc_reg + CPU_PWR_CTL);
mb();
udelay(2);
/* Close coremem array gdhs */
writel_relaxed(0x0000007D, acc_reg + CPU_PWR_CTL);
mb();
udelay(2);
/* De-assert clamp */
writel_relaxed(0x0000003D, acc_reg + CPU_PWR_CTL);
mb();
/* De-assert clamp */
writel_relaxed(0x0000003C, acc_reg + CPU_PWR_CTL);
mb();
udelay(1);
/* De-assert core0 reset */
writel_relaxed(0x0000000C, acc_reg + CPU_PWR_CTL);
mb();
/* Assert PWRDUP */
writel_relaxed(0x0000008C, acc_reg + CPU_PWR_CTL);
mb();
iounmap(acc_reg);
out_acc_reg:
iounmap(ldo_bhs_reg);
out_bhs_reg:
of_node_put(l2ccc_node);
out_l2ccc:
of_node_put(l2_node);
out_l2:
of_node_put(acc_node);
out_acc:
of_node_put(cpu_node);
return ret;
}
其中一些寄存器的操作我不太了解,大致是给CPU上电。 CPU 上电后,将从secondary_holding_pen执行。