The boot flow diagram
In ARM v8 64-bit systems this property is required and matches the MPIDR_EL1 register affinity bits.
* If cpus node's #address-cells property is set to 2
The first reg cell bits [7:0] must be set to bits [39:32] of MPIDR_EL1.
The second reg cell bits [23:0] must be set to bits [23:0] of MPIDR_EL1.
* If cpus node's #address-cells property is set to 1
The reg cell bits [23:0] must be set to bits [23:0] of MPIDR_EL1.
All other bits in the reg cells must be set to 0.
compatible:
enum:
- arm,cortex-a78
enable-method:
# On ARM v8 64-bit this property is required
- enum:
- psci
- spin-table
cpu-release-addr:
description:
The DT specification defines this as 64-bit always, but some 32-bit Arm
systems have used a 32-bit value which must be supported.
Required for systems that have an "enable-method"
property value of "spin-table".
cpu-idle-states:
items:
maxItems: 1
description: |
List of phandles to idle state nodes supported by this cpu.
capacity-dmips-mhz:
description:
u32 value representing CPU capacity in DMIPS/MHz, relative to highest capacity-dmips-mhz in the system.
cci-control-port: true
dynamic-power-coefficient:
description:
A u32 value that represents the running time dynamic power coefficient in units of uW/MHz/V^2. The coefficient can either be calculated from power measurements or derived by analysis.
The dynamic power consumption of the CPU is proportional to the square of the Voltage (V) and the clock frequency (f). The coefficient is used to calculate the dynamic power as below -
Pdyn = dynamic-power-coefficient * V^2 * f
where voltage is in V, frequency is in MHz.
performance-domains:
maxItems: 1
description:
List of phandles and performance domain specifiers, as defined by
bindings of the performance domain provider. See also
dvfs/performance-domain.yaml.
power-domains:
description:
List of phandles and PM domain specifiers, as defined by bindings of the
PM domain provider (see also ../power_domain.txt).
power-domain-names:
description:
A list of power domain name strings sorted in the same order as the
power-domains property.
For PSCI based platforms, the name corresponding to the index of the PSCI
PM domain provider, must be "psci".
qcom,saw:
description:
Specifies the SAW* node associated with this CPU.
Required for systems that have an "enable-method" property
value of "qcom,kpss-acc-v1" or "qcom,kpss-acc-v2"
* arm/msm/qcom,saw2.txt
qcom,acc:
description:
Specifies the ACC* node associated with this CPU.
Required for systems that have an "enable-method" property
value of "qcom,kpss-acc-v1", "qcom,kpss-acc-v2", "qcom,msm8226-smp" or
"qcom,msm8916-smp".
secondary-boot-reg:
description:
Required for systems that have an "enable-method" property value of
"brcm,bcm11351-cpu-method", "brcm,bcm23550" or "brcm,bcm-nsp-smp".
This binding defines the enable method used for starting secondary CPUs in the following Broadcom SoCs
This includes the following SoCs: |
BCM11130, BCM11140, BCM11351, BCM28145, BCM28155, BCM21664, BCM23550
BCM58522, BCM58525, BCM58535, BCM58622, BCM58623, BCM58625, BCM88312
The secondary-boot-reg property is a u32 value that specifies the
physical address of the register used to request the ROM holding pen
code release a secondary CPU. The value written to the register is
formed by encoding the target CPU id into the low bits of the
physical start address it should jump to.
Example:
cpu@1 {
device_type = "cpu";
compatible = "arm,cortex-a78";
reg = <0x0 0x1>;
enable-method = "spin-table";
cpu-release-addr = <0 0x20000000>;
};
1. Bootloader starts first in the upper part of the picture;
2. The Kernel is in the lower part of the picture, which is guided by the bootloader;
3. The execution flow of CPU0 is in the left half of the picture, and the bootloader code will judge and start CPU0 first;
4. Secondary CPUs are in the right half of the picture and are awakened by the CPU
The specific startup process is as follows:
1. When bootloader/ABL starts, it will judge whether the executing code is CPU0, if not, execute wfe and wait for CPU0 to issue sev instruction to wake up. If it is CPU0, continue the initialization work.
mrs x4,mpidr_el1
tst
x4,#15
//testwether the current cpu is CPU0, ie. mpidr_el1=15
b.eq 2f
/*
* Secondary CPUs
*/
1: wfe
ldr x4, mbox
cbz x4, 1b //if x4==0(ie. The value in address of mbox is 0) dead loop,or jump to x4
br x4//branch to thegiven address
2:……//UART initialisation (38400 8N1)
The modification process of the secondary_holding_pen_release variable is carried out by CPU0 calling the smp_init() function. This function first binds an idle thread to the corresponding CPU, then modifies the value of secondary_holding_pen_release (its value is the ID of the CPU to be awakened by CPU0), and finally sends the sev instruction to wake up the corresponding CPU to execute the idle thread.
NTRY(secondary_holding_pen)
bl el2_setup // Drop to EL1, w0=cpu_boot_mode
bl set_cpu_boot_mode_flag
mrs x0, mpidr_el1
mov_q x1, MPIDR_HWID_BITMASK
and x0, x0, x1
adr_l x3, secondary_holding_pen_release
pen: ldr x4, [x3]
cmp x4, x0
b.eq secondary_startup
wfe
b pen
ENDPROC(secondary_holding_pen)
start_kernel
, it will spawn a kernel thread in rest_init
, which will eventually run kernel_init
–> kernel_init_freeable
–> smp_init
.- smp_init will call
idle_threads_init
to fork a swapper for each cpu, share the same PID but with different thread_info and task_struct, as shown below.
void __init smp_init(void)
{
unsigned int cpu;
idle_threads_init();
cpuhp_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))
cpu_up(cpu);
}
/* Any cleanup work */
smp_announce();
smp_cpus_done(setup_max_cpus);
}
void __init idle_threads_init(void)
{
unsigned int cpu, boot_cpu;
boot_cpu = smp_processor_id();
for_each_possible_cpu(cpu) {
if (cpu != boot_cpu)
idle_init(cpu);
}
}
static inline void idle_init(unsigned int cpu)
{
struct task_struct *tsk = per_cpu(idle_threads, cpu);
if (!tsk) {
tsk = fork_idle(cpu);
if (IS_ERR(tsk))
pr_err("SMP: fork_idle() failed for CPU %u\n", cpu);
else
per_cpu(idle_threads, cpu) = tsk;
}
}
static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
{
......
target = min((int)target, CPUHP_BRINGUP_CPU);
ret = cpuhp_up_callbacks(cpu, st, target);
out:
cpu_hotplug_done();
return ret;
}
static int cpuhp_up_callbacks(unsigned int cpu, struct cpuhp_cpu_state *st,
enum cpuhp_state target)
{
enum cpuhp_state prev_state = st->state;
int ret = 0;
while (st->state < target) {
st->state++;
ret = cpuhp_invoke_callback(cpu, st->state, true, NULL);
if (ret) {
st->target = prev_state;
undo_cpu_up(cpu, st);
break;
}
}
return ret;
}
static struct cpuhp_step cpuhp_bp_states[] = {
[CPUHP_BRINGUP_CPU] = {
.name = "cpu:bringup",
.startup.single = bringup_cpu,
.teardown.single = NULL,
.cant_stop = true,
},
};
static int cpuhp_invoke_callback(unsigned int cpu, enum cpuhp_state state,
bool bringup, struct hlist_node *node)
{
......
if (!step->multi_instance) {
cb = bringup ? step->startup.single : step->teardown.single;
ret = cb(cpu);
}
......
}
_cpu_up
-> cpuhp_up_callbacks -> cpuhp_invoke_callback, which will call cpuhp_bp_states->startup.single, which points to bringup_cpu
. bringup_cpu -> __cpu_up
-> boot_secondary
–> cpu_ops[cpu]->cpu_boot, which is smp_spin_table_cpu_boot
–> write_pen_release
.
static void write_pen_release(u64 val)
{
void *start = (void *)&secondary_holding_pen_release;
unsigned long size = sizeof(secondary_holding_pen_release);
secondary_holding_pen_release = val;
__flush_dcache_area(start, size);
}
In write_pen_release
, the variable secondary_holding_pen_release
will be updated, which allows the secondary core breaks the loop of secondary_holding_pen, the boot process can finally move on.
Modern age: jump to C world
secondary_startup:
/*
* Common entry point for secondary CPUs.
*/
bl __cpu_setup // initialise processor
bl __enable_mmu
ldr x8, =__secondary_switched
br x8
ENDPROC(secondary_startup)
__secondary_switched:
adr_l x5, vectors
msr vbar_el1, x5
isb
adr_l x0, secondary_data
ldr x0, [x0, #CPU_BOOT_STACK] // get secondary_data.stack
mov sp, x0
and x0, x0, #~(THREAD_SIZE - 1)
msr sp_el0, x0 // save thread_info
mov x29, #0
b secondary_start_kernel
ENDPROC(__secondary_switched)
// in arch/arm64/kernel/smp.c
asmlinkage void secondary_start_kernel(void) {}
secondary_holding_pen
-> secondary_startup
-> __secondary_switched
-> secondary_start_kernel
, which is in arch/arm64/kernel/smp.c. The secondary bootup is done. To summarize, a figure from wowotech explains the overall secondary core bootup steps.
No comments:
Post a Comment