bool "big.LITTLE switcher support (experimental)"
depends on CPU_V7 && EXPERIMENTAL
select CPU_PM
select ARM_CPU_SUSPEND
help
The big.LITTLE "switcher" provides the core functionality to
transparently handle transition between a cluster of A15's
and a cluster of A7's in a big.LITTLE system.
bool "Simple big.LITTLE switcher user interface"
depends on BL_SWITCHER
help
This is a simple and dummy char dev interface to control
the big.LITTLE switcher core code. It is meant for
debugging purposes only.
./arch/arm/configs/arndale_octa_android_defconfig:37:CONFIG_BL_SWITCHER_DUMMY_IF=y
./drivers/cpufreq/exynos-ikcs-cpufreq.c:388:#ifdef CONFIG_BL_SWITCHER
./drivers/cpufreq/exynos-ikcs-cpufreq.c:459:#ifdef CONFIG_BL_SWITCHER
./drivers/cpufreq/exynos-ikcs-cpufreq.c:550:#ifdef CONFIG_BL_SWITCHER
...
./arch/arm/common/Makefile:18:obj-$(CONFIG_BL_SWITCHER) += bL_head.o bL_entry.o
./arch/arm/common/Makefile:19:obj-$(CONFIG_BL_SWITCHER) += bL_switcher.o
./arch/arm/common/Makefile:20:obj-$(CONFIG_BL_SWITCHER) += bL_vlock.o
./arch/arm/common/gic.c:97:#if defined(CONFIG_BL_SWITCHER)
./arch/arm/common/gic.c:432:#if defined(CONFIG_BL_SWITCHER)
./arch/arm/common/gic.c:542:#if defined(CONFIG_BL_SWITCHER)
./arch/arm/common/gic.c:580:#if defined(CONFIG_BL_SWITCHER)
./arch/arm/common/gic.c:814:#ifdef CONFIG_BL_SWITCHER
...
./arch/arm/mach-exynos/Makefile:24:obj-$(CONFIG_BL_SWITCHER) += sys-if-switcher.o
./arch/arm/mach-exynos/Makefile:185:obj-$(CONFIG_BL_SWITCHER) += bL_control.o bL_setup.o
./arch/arm/mach-exynos/mach-arndale_octa.c:160:#ifdef CONFIG_BL_SWITCHER
./arch/arm/mach-exynos/mach-arndale_octa.c:244:#ifdef CONFIG_BL_SWITCHER
...
./arch/arm/kernel/setup.c:271:#ifdef CONFIG_BL_SWITCHER
./arch/arm/mm/proc-v7.S:130:#ifdef CONFIG_BL_SWITCHER
./kernel/panic.c:76:#ifdef CONFIG_BL_SWITCHER
./kernel/panic.c:117:#ifdef CONFIG_BL_SWITCHER
./kernel/printk.c:47:#ifdef CONFIG_BL_SWITCHER
./kernel/printk.c:1032:#ifdef CONFIG_BL_SWITCHER

一、首先,kernel在每一个cpu上(共4个)各启动一个work thread,名字叫做bL_switch_thread, 这个thread在被请求切换cpu时会被激活。
1)启动task
late_initcall(bL_control_init);
bL_control_init (arch/arm/mach-exynos/bL_control.c) -> __raw_writel(virt_to_phys(bl_entry_point), REG_SWITCHING_ADDR); //对于REG_SWITCHING_ADDR这个寄存器有疑问,看起来是switch时inbound cpu会跳入这里设置的entry,待查spac
bL_control_init (arch/arm/mach-exynos/bL_control.c) -> bL_switcher_init (arch/arm/common/bL_switcher.c) -> schedule_on_each_cpu(switcher_thread_on_each_cpu) ->
-> switcher_thread_on_each_cpu -> bL_switcher_thread_create -> kthread_create_on_node(bL_switcher_thread ...) 然后 wake_up_process(t->task)
2)bL_switch_thread同步策略
do{
...
wait_event_interruptible(t->wq, t->wanted_cluster != -1)
...
bL_switch_to(cluster)
...
}while (!kthread_should_stop())
switch thread正在等待wanted_cluster状态更改请求。wanted_cluster是core所在的组,如A15组或A7组。
二、然后,在需要切换cpu时,调用bL_switch_request/bL_cluster_switch_request 这个接口。
这类接口主要做2件事儿:
1)bL_enter_migration
向各模块发送SWITCH_ENTER事件,这样,各个模块就能进行各自的migration处理。目前代码中关心此事件的模块有MobiCoreDriver和sys-if-switcher(之前提到的耗时统计)。
2)唤醒switch thread
t->wanted_cluster = new_cluster_id; //更改状态
wake_up(&t->wq);
三、最后,bL_switch_thread被唤醒,执行bL_switch_to(cluster)
这里的cluster就是new_cluster_id.
bL_switch_to中有几个地方需要分析:
这里将outbound和inbound cpu对应的bL_entry_vectors清除。这样,进入bl_entry_point后发现该vector为空,就会循环等待。
/* Close the gate for our entry vectors */
bL_set_entry_vector(cpuid, ob_cluster, NULL);
bL_set_entry_vector(cpuid, ib_cluster, NULL);
...
//绿色待分析
bL_platform_ops->power_up(cpuid, ib_cluster);
gic_migrate_target(cpuid + ib_cluster*4); 将中断target切换到另一组pair的cpu上。
arm_send_ping_ipi(smp_processor_id()); // 此时,inbound cpu被ipi中断唤醒,进入bl_entry_point
cpu_suspend((unsigned long)NULL, bL_switchpoint);
1)cpu_suspend
cpu_suspend((unsigned long)NULL, bL_switchpoint) (arch/arm/common/bL_switcher.c) -> __cpu_suspend (arch/arm/kernel/sleep.S) -> __cpu_suspend_save (arch/arm/kernel/suspend.c) -> cpu_do_suspend -> cpu_v7_do_suspend (#define ____glue(name,fn) name##fn, arch/arm/mm/proc-v7.S)
其中__cpu_suspend
stmfd sp!, {r4 - r11, lr} @将r4~r11和返回地址入栈
...
mov r5, sp
...
mov r2, r5 @栈地址作为参数,准备存储
...
stmfd sp!, {r0, r1} @ save suspend func arg and pointer
...
bl __cpu_suspend_save
/* This must correspond to the LDM in cpu_resume() assembly */
@*ptr++ = virt_to_phys(idmap_pgd);
@ *ptr++ = sp; 将sp存入sleep_save_sp,顺带说下,这里stack内存储的是调用__cpu_suspend之后的{r4 - r11, lr}。
@ *ptr++ = virt_to_phys(cpu_do_resume); 在ptr++的位置存储cpu_do_resume入口地址。之后resume时会使用。
...
ldmfd sp!, {r0, pc} @ call suspend fn,此时,save工作已经结束。
...
即最后调用bL_switchpoint -> call_with_stack(bL_do_switch, NULL, stack) -> bL_do_switch
附:sleep_save_sp存储的内容
sleep_save_sp (cpu n) | ||
idmap_pgd | outbound cpu 进入__cpu_suspend时的记录 | |
sp | -------> | r4 |
cpu_do_resume | r5 | |
cp15的内容 | FCSE/PID | r6 |
User r/o thread ID | r7 | |
Domain ID | r8 | |
TTB 1 | r9 | |
Control register | r10 | |
Auxiliary control register | r11 | |
Co-processor access control | lr | |
TTB control register |
2)bL_do_switch
bL_do_switch -> bL_set_entry_vector(cpuid, ib_cluster, cpu_resume)
在bL_set_entry_vector函数中,将inbound cpu的bL_entry_vectors设置为cpu_resume。
此时inbound cpu已经开始平行运行。执行cpu_resume相关操作,后面会分析。
outbound cpu运行arch/arm/mach-exynos/bL_control.c内注册的power_down函数。
bL_platform_ops->power_down(cpuid, ob_cluster);
phys_reset = (phys_reset_t)(unsigned long)virt_to_phys(cpu_reset);
phys_reset(virt_to_phys(bl_entry_point));
其中cpu_reset -> cpu_v7_reset (arch/arm/mm/proc-v7.S) 有如下代码:
...
disable mmu,
mov pc, r0
所以最后调用bl_entry_point //这里有疑问,为什么outbound cpu也要reset,并且进入bl_entry_point。
3)bl_entry_point (arch/arm/common/bL_head.S)
...
add r5, r5, r6 @ r5 = bL_entry_vectors
bL_entry_gated:
ldr r6, [r5, r4, lsl #2]
cmp r6, #0
/* wfeeq */
beq bL_entry_gated
...
bx r6
在bL_entry_vectors被填写之后,最后会跳入相应的vectors,也就是cpu_resume
4) cpu_resume
cpu_resume ( arch/arm/kernel/sleep.S ) -> cpu_v7_do_resume (arch/arm/mm/proc-v7.S) ->cpu_resume_mmu ( arch/arm/kernel/sleep.S ) -> cpu_resume_after_mmu
cpu_resume调用
adr r0, sleep_save_sp @将之前存储的sp和pc等取出,
ARM( ldmia r0!, {r1, sp, pc} @ 这时的pc = cpu_v7_do_resume
cpu_v7_do_resume
会进行cp15的恢复,且不会对sp进行任何修改和操作。
最后调用b cpu_resume_mmu
cpu_resume_after_mmu
会将r0设置为0 (成功返回)
最后调用ldmfd sp!, {r4 - r11, pc} ,这样,inbound在resume完,就回到oubound cpu之前suspend结束的点,继续运行。