SMP多核处理器omap4启动分析 笔记版 转载请注明出处[email protected]


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();
}

你可能感兴趣的:(cpu,kernel,ARM,arm-linux,omap4)