Omap4 是Ti在移动市场上的绝唱。在没有通信modem的支援下,依赖于omap4,Ti硬是在手机处理器市场赢得最后一站。
Omap4是双核Cortex A9架构的处理器,本文分析其启动表现。
代码执行顺序如黑体:
static int __init kernel_init(void * unused)
{
…
smp_prepare_cpus(setup_max_cpus);
…
}
void __init smp_prepare_cpus(unsigned int max_cpus)
{
…
scu_enable(scu_base);
wakeup_secondary();
}
}
static void __init wakeup_secondary(void)
{
//cpu0 将omap_secondary_startup的物理地址写入omap4的AuxCoreBoot 1寄存器
//这时cpu1执行rom指令WFE处在等待状态
omap_auxcoreboot_addr(virt_to_phys(omap_secondary_startup));
smp_wmb();
dsb();
//cpu0 发出sev指令,sev指令激活cpu1,使之推出的WFE状态,cpu1继续执行rom的指令,其启动顺寻是:1查看AuxCoreBoot 0寄存器的状态是否满足要求 2 若AuxCoreBoot 0寄存器状态满足要求,则取出AuxCoreBoot 1寄存器的地址,跳转之
set_event();
mb();
}
//在omap4的芯片内部存在着一个代码rom,其代码运行在arm架构monitor状态。这段代码只使用master cpu及cpu0里的资源。这样就可以避免寻址的问题,cpu可以不管mmu是否打开关闭,都能访问这个rom。
//cpu0 将物理地址写入omap4的AuxCoreBoot 1寄存器
ENTRY(omap_auxcoreboot_addr)
//这是在cpu0执行的代码,而rom代码也运行在cpu0上,所以要进行寄存器的保存。
stmfd sp!, {r2-r12, lr}
//0x105是rom代码的调用代号,通过r12传递。
ldr
r12, =0x105
Dsb
//smc是进入monitor状态的专用指令,一旦执行这个指令cpu就像发生了一次异常一样,进入monitor状态
smc
#0
//rom代码完成服务工作,cpu0被恢复原来状态,显然rom代码尽量精简,留下了寄存器恢复的工作。这里回复寄存器状态。
ldmfd sp!, {r2-r12, pc}
END(omap_auxcoreboot_addr)
执行到这里,cpu1被激活了,但是他还不能往前跑,因为这个时候内核还没有为其准备好相关管理结构,这时cpu1检查AuxCoreBoot 0寄存器状态,如果还没有满足要求,cpu1执行WFE仍旧进入WFE状态。cpu0要在完成所有准备工作之后才会改写AuxCoreBoot 0寄存器,释放cpu1。
Cpu0在调用void __init smp_prepare_cpus(setup_max_cpus)叫醒cpu1之后,会继续执行一些内核初始化工作,以及smp相关的初始化工作,其中最重要的是通过调用static void __init smp_init(void)->...->int __cpuinit __cpu_up(unsigned int cpu)
int __cpuinit __cpu_up(unsigned int cpu)
{
...
//首先为cpu1 fork出一个idle线程
/*
* Spawn a new process manually, if not already done.
* Grab a pointer to its task struct so we can mess with it
*/
if (!idle) {
idle = fork_idle(cpu);
if (IS_ERR(idle)) {
printk(KERN_ERR "CPU%u: fork() failed\n", cpu);
return PTR_ERR(idle);
}
ci->idle = idle;
} else {
/*
* Since this idle thread is being re-used, call
* init_idle() to reinitialize the thread structure.
*/
init_idle(idle, cpu);
}
/*
* Allocate initial page tables to allow the new CPU to
* enable the MMU safely. This essentially means a set
* of our "standard" page tables, with the addition of
* a 1:1 mapping for the physical address of the kernel.
*/
//为cpu1创建一个struct mm_struct
pgd = pgd_alloc(&init_mm);
pmd = pmd_offset(pgd + pgd_index(PHYS_OFFSET), PHYS_OFFSET);
*pmd = __pmd((PHYS_OFFSET & PGDIR_MASK) |
PMD_TYPE_SECT | PMD_SECT_AP_WRITE);
flush_pmd_entry(pmd);
outer_clean_range(__pa(pmd), __pa(pmd + 1));
/*
* 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;
secondary_data.pgdir = virt_to_phys(pgd);
__cpuc_flush_dcache_area(&secondary_data, sizeof(secondary_data));
outer_clean_range(__pa(&secondary_data), __pa(&secondary_data + 1));
/*
* Now bring the CPU into our world.
*/
//叫醒cpu1,这里cpu1会真正运行起来
ret = boot_secondary(cpu, idle);
//到了这里,完成了cpu1的激活工作,cpu1将执行自己相关到初始化工作,等到cpu1完成自己的工作,cpu1会在cpu_online_mask位图上将自己对应的标志位置位,而cpu0在这里等待那一刻的到来
if (ret == 0) {
unsigned long timeout;
/*
* CPU was successfully started, wait for it
* to come online or time out.
*/
timeout = jiffies + HZ;
while (time_before(jiffies, timeout)) {
//如果对应位有效,说明cpu1完成了初始化工作,真正成为了系统中的一颗对称多处理器
if (cpu_online(cpu))
break;
//代码跑到这里说明,cpu1工作还没完成,再等一会。
udelay(10);
barrier();
}
if (!cpu_online(cpu))
ret = -EIO;
}
......
}
int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle)
{
struct clockdomain *cpu1_clkdm;
static bool booted;
/*
* Set synchronisation state between this boot processor
* and the secondary one
*/
spin_lock(&boot_lock);
/*
* Update the AuxCoreBoot0 with boot state for secondary core.
* omap_secondary_startup() routine will hold the secondary core till
* the AuxCoreBoot1 register is updated with cpu state
* A barrier is added to ensure that write buffer is drained
*/
//这里cpu0改写AuxCoreBoot0的状态,告诉cpu1可以跑了
omap_modify_auxcoreboot0(0x200, 0xfffffdff);
flush_cache_all();
smp_wmb();
/*
* SGI isn't wakeup capable from low power states. This is
* known limitation and can be worked around by using software
* forced wake-up. After the wakeup, the CPU will restore it
* to hw_auto. This code also gets initialised but pm init code
* initialises the CPUx clockdomain to hw-auto mode
*/
if (booted) {
cpu1_clkdm = clkdm_lookup("mpu1_clkdm");
omap2_clkdm_wakeup(cpu1_clkdm);
smp_cross_call(cpumask_of(cpu));
} else {
//第一次启动会跑到这里,因为上次wakeup_secondary函数虽然用SEV指令激活的cpu1,但是cpu1发现AuxCoreBoot0的状态不满足,又进入WFE状态了。所以这里要在捅一次cpu1。
set_event();
booted = true;
}
/*
* Now the secondary core is starting up let it run its
* calibrations, then wait for it to finish
*/
spin_unlock(&boot_lock);
return 0;
}
//omap-headsmp.s
ENTRY(omap_secondary_startup)
//cpu1终于跑出了rom,第一件事是再检查一下AuxCoreBoot0,如果不满足要求就hold下来。在这之前rom里已经检查了AuxCoreBoot0,并且把ENTRY(omap_secondary_startup)地址从AuxCoreBoot0取出来。
hold:
ldr
r12,=0x103
dsb
smc
#0
@ read from AuxCoreBoot0
mov
r0, r0, lsr #9
mrc
p15, 0, r4, c0, c0, 5
and
r4, r4, #0x0f
cmp
r0, r4
bne
hold
/*
* we've been released from the wait loop,secondary_stack
* should now contain the SVC stack for this core
*/
b
secondary_startup
END(omap_secondary_startup)
ENTRY(omap_modify_auxcoreboot0)
stmfd sp!, {r1-r12, lr}
ldr
r12, =0x104
dsb
smc
#0
ldmfd sp!, {r1-r12, pc}
END(omap_modify_auxcoreboot0)
Head.s
#if defined(CONFIG_SMP)
ENTRY(secondary_startup)
/*
* Common entry point for secondary CPUs.
*
* Ensure that we're in SVC mode, and IRQs are disabled. Lookup
* the processor type - there is no need to check the machine type
* as it has already been validated by the primary processor.
*/
setmode
PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9
mrc
p15, 0, r9, c0, c0
@ get processor id
bl
__lookup_processor_type
movs
r10, r5
@ invalid processor?
moveq
r0, #'p'
@ yes, error 'p'
beq
__error
/*
* Use the page tables supplied from __cpu_up.
*/
adr
r4, __secondary_data
ldmia
r4, {r5, r7, r12}
@ address to jump to after
sub
r4, r4, r5
@ mmu has been enabled
ldr
r4, [r7, r4]
@ get secondary_data.pgdir
adr
lr, BSYM(__enable_mmu)
@ return address
mov
r13, r12
@ __secondary_switched address
ARM(
add
pc, r10, #PROCINFO_INITFUNC
) @ initialise processor
@ (return control reg)
THUMB(
add
r12, r10, #PROCINFO_INITFUNC
)
THUMB(
mov
pc, r12
)
ENDPROC(secondary_startup)
ENTRY(__secondary_switched)
ldr
sp, [r7, #4]
@ get secondary_data.stack
mov
fp, #0
b
secondary_start_kernel
ENDPROC(__secondary_switched)
asmlinkage void __cpuinit secondary_start_kernel(void)
{
struct mm_struct *mm = &init_mm;
unsigned int cpu = smp_processor_id();
printk("CPU%u: Booted secondary processor\n", cpu);
/*
* All kernel threads share the same mm context; grab a
* reference and switch to it.
*/
atomic_inc(&mm->mm_users);
atomic_inc(&mm->mm_count);
current->active_mm = mm;
cpumask_set_cpu(cpu, mm_cpumask(mm));
cpu_switch_mm(mm->pgd, mm);
enter_lazy_tlb(mm, current);
local_flush_tlb_all();
cpu_init();
preempt_disable();
/*
* Give the platform a chance to do its own initialisation.
*/
platform_secondary_init(cpu);
/*
* Enable local interrupts.
*/
notify_cpu_starting(cpu);
local_irq_enable();
local_fiq_enable();
/*
* Setup the percpu timer for this CPU.
*/
percpu_timer_setup();
calibrate_delay();
smp_store_cpu_info(cpu);
/*
* OK, now it's safe to let the boot CPU continue
*/
set_cpu_online(cpu, true);
/*
* OK, it's off to the idle thread for us
*/
cpu_idle();
}