AuxCoreBootSMP多核处理器omap4启动分析 笔记版 转载请注明出处---crosskernel@gmail.com
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(); } |
|