1.MIPS CPU Address
MIPS CPU运行时有三种状态:用户模式(User Mode);核心模式(Kernel Mode);管理模式(Supervisor Mode)。其中管理模式不常用。用户模式下,CPU只能访问KUSeg;当需要访问KSeg0、Kseg1和Kseg2时,必须使用核心模式或管理模 式。
Kuseg:0×00000000~0×7FFFFFFF(2G)。这些地址是用户态可用的地址。 在有MMU的机器里,这些地址将一概被转换。除非MMU已经设置好,否则不应该使用这些地址;对于没有MMU的处理器,这些地址的行为与具体处理器有关。 如果想要你的代码能够移植到无MMU的处理器上,或者能够在无MMU的处理器间移植,应避免使用这块区域。
Kseg0:0×80000000~0×9FFFFFFF(512M)。只要把最高位清零,这些地址就会转换成物理地址,映射到连续的 低端512M的物理空间上。这段区域的地址几乎总是要通过高速缓存来存取(write-back,或write-through模式),所以在高速缓存初 始化之前,不能使用。因为这种转换极为简单,且通过Cache访问,因而常称为这段地址为Unmapped Cached。这个区域在无MMU的系统中用来存放大多数程序和数据;在有MMU的系统中用来存放操作系统核心。
KSeg1:0xA0000000~0xBFFFFFFF(512M)。这些地址通过把最高三位清零,映射到连续的第算512M的物理地址,即 Kseg1和Kseg0对应同一片物理内存。但是与通过Kseg0的访问不同,通过Kseg1访问的话,不经过高速缓存。 Kseg1是唯一的在系统加电/复位时,能正常访问的地址空间,这也是为什么复位入口点(0xBFC00000)放在这个区域的原因,即对应复位物理地址 为0×1FC00000。因而,一般情况下利用该区域存储Bootlooder;大多数人也把该区域用作IO寄存器(这样可以保证访问时,是直接访问 IO,而不是访问cache中内容),因而建议IO相关内容也要放在物理地址的512M空间内。
KSeg2:0xC0000000~0xFFFFFFFF(1G)。这块区域只能在核心态下使用,并且要经过MMU的转换,因而在MMU设置好之前,不要存取该区域。除非你在写一个真正的操作系统,否则没有理由使用Kseg2。
2.内核启动
(1)主核core0 启动linux的入口是 kernel_entry(在arch/mips/kerenl/head.S中定义),跳转到kernel_entry_setup(在arch\mips\include\asm\mach-cavium-octeon\kernel-entry-init.h定义),因为BootLoader将主核core0的寄存器a2设置为1,所以跳转到octeon_main_processor,继而跳转到start_kernel继续启动linux;
(2)从核启动linux的入口也是kernel_entry,因为BootLoader将从核的寄存器a2设置为0,于是进入octeon_spin_wait_boot等待主核core0唤醒;
(3)主核core0执行start_kernel,调用boot_cpu_init,设置core0点cpu状态为online(可被 调度 ),active(可被 迁移 ), present( 被内核接管 ) , possible(系统存在,但没有被内核接管);
(4)主核 core0调用setup_arch进行mips体系结构相关初始化,与SMP相关的有:prom_init调用octeon_setup_smp注册octeon_smp_ops结构体,赋值给全局变量struct plat_smp_ops *mp_ops,是多核启动的操作函数集;plat_smp_setup调用 plat_smp_ops函数集octeon_smp_ops 的octeon_smp_setup根据coremask参数设置可以被启动点从核的各cpu状态为 present( 被内核接管 )和 possible(系统存在,但没有被内核接管),设置各从核cpu点logic map。
(5)主核 core0在kernel_init线程执行smp_prepare_cpus,调用octeon_smp_ops 的octeon_prepare_cpus初始化mailbox并申请mailbox中断;
(6)主核 core0执行smp_init,调用idle_threads_init初始化idle线程,然后调用cpu,最终调用octeon_smp_ops的octeon_boot_secondary,将 octeon_processor_sp和octeon_processor_gp指向idle线程的stack,将octeon_processor_sp赋值为待启动从核点逻辑core id;
(7)从核 octeon_spin_wait_boot检测到 octeon_processor_sp和自己的core id匹配了,就跳出 octeon_spin_wait_boot,执行smp_bootstrap,调用start_secondary,进而调用 octeon_smp_ops 的octeon_init_secondary和octeon_smp_finish;
(8)从核执行cpu_startup_entry进入idle loop。
2.1.启动流程分析
系统加电启动后,任何MIPS Core都是从系统的虚拟地址0xbfc00000(BIOS入口地址)启动的,其对应的物理地址为0x1FC00000。因为上述地址处于kseg1中,所以此时系统不需要TLB映射就能够运行(这段空间通过去掉最高的三位来获得物理地址)。CPU从物理地址0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH(BIOS)的位置,BIOS将Linux内核镜像文件拷贝到RAM中某个空闲地址(LOAD地址)处,然后一般有个内存移动的操作(Entry point(EP)的地址),最后BIOS跳转到EP指定的地址运行,此时开始运行Linux kernel。
CPU根据型号不同,一个CPU里面可能集成了多个Core。这些Core里面,在上电时,只有core 0(一般称之为主核,其它核称为从核)会从reset状态跳转到物理地址0x1FC00000(我们的flash起始地址会映射成这个值),开始执行相关的一系列初始化代码,如内存、外设等等。而另外一些核则仍处于reset状态,只有core 0主动去唤醒它们时,从核才有可能开始正常运转。
在编译完内核时,一般情况下生成两个版本的内核vmlinux与vmlinuz。其中vmlinux为非压缩版内核,vmlinuz为压缩版内核(包含内核自解压程序)。使用readelf -l vmlinux 命令可以读到LOAD地址,这个地址是由arch/mips/kernel/vmlinux.lds决定的:
1 OUTPUT_ARCH(mips)
2 ENTRY(kernel_entry)
3 PHDRS {
4 text PT_LOAD FLAGS(7);
5 note PT_NOTE FLAGS(4);
6 }
7 jiffies = jiffies_64;
8 SECTIONS
9 {
10 . = 0xffffffff80200000;
11 _text = .;
12 .text : {
13 . = ALIGN(8); *(.text.hot .text .text.fixup .text.unlikely) *(.text..refcount) *(.ref.text) *(.meminit.text*) *(.memexit.text*)
14 . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
15 . = ALIGN(8); __cpuidle_text_start = .; *(.cpuidle.text) __cpuidle_text_end = .;
16 . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
17 . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
18 . = ALIGN(8); __irqentry_text_start = .; *(.irqentry.text) __irqentry_text_end = .;
19 . = ALIGN(8); __softirqentry_text_start = .; *(.softirqentry.text) __softirqentry_text_end = .;
20 *(.text.*)
21 *(.fixup)
22 *(.gnu.warning)
23 } :text = 0
24 _etext = .;
25 . = ALIGN(16); __ex_table : AT(ADDR(__ex_table) - 0) { __start___ex_table = .; KEEP(*(__ex_table)) __stop___ex_table = .; }
26 __dbe_table : {
27 __start___dbe_table = .;
28 *(__dbe_table)
29 __stop___dbe_table = .;
30 }
关于Entry point(EP)的一些说明:
EP(ELF可以读到)地址是BIOS移动完内核后,直接跳转的地址(控制权由BIOS转移到KERNEL)。这个地址由ld写入ELF的头中,会依次用下面的方法尝试设置入口地址,当遇见成功时则停止:
a.命令行选项 -e entry;
b.脚本(vmlinux.lds)中的ENTRY(xxx);
c.如果有定义start符号,则使用start符号(xxx);
d.如果存在.text节,则使用第一个字节的地址;
e.地址0。
由于上述ld 脚本(vmlinux.lds)中,用ENTRY宏设置了内核的EP是kernel_entry (KE)函数的地址,所以内核取得控制权(BIOS跳转之后)后执行的第一条指令就是 KE函数。
注意:这种情况只是vmlinux(非压缩版的内核),对于vmlinuz(压缩版的内核),EP会被设置成内核自解压缩的程序代码的地址,这样固件就会跳转到内核自解压代码(此时的EP为解压程序的代码地址),最后还是会到KE函数去执行。
vmlinux : 执行入口是arch/mips/boot//head.S中的kernel_entry;
vmlinuz : 解压前真正执行入口是arch/mips/boot/compressed/head.S中的start标号;
由以上分析可知无论是压缩版还是非压缩版的Linux内核,内核第一个执行的函数是KE。kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零,最后跳转到 /init/main.c 中的 start_kernel()初始化硬件平台相关的代码。源代码如下:
NESTED(kernel_entry, 16, sp) # KE函数定义,函数栈的大小为16字节
kernel_entry_setup # 对CPU的配置,详情见kernel_entry_setup函数分析NOTE1
setup_c0_status_pri #设置mips协处理器(cp0)中的寄存器,详情见NOTE2
PTR_LA t0, 0f
jr t0
0:
#ifdef CONFIG_MIPS_MT_SMTC #硬件多线程
mtc0 zero, CP0_TCCONTEXT
mfc0 t0, CP0_STATUS
ori t0, t0, 0xff1f
xori t0, t0, 0x001e
mtc0 t0, CP0_STATUS
#endif /* CONFIG_MIPS_MT_SMTC */
PTR_LA t0, __bss_start # 清除BSS段,详情见NOTE3
LONG_S zero, (t0)
PTR_LA t1, __bss_stop - LONGSIZE
1:
PTR_ADDIU t0, LONGSIZE
LONG_S zero, (t0)
bne t0, t1, 1b
LONG_S a0, fw_arg0 # BIOS传参数,详情见NOTE4
LONG_S a1, fw_arg1
LONG_S a2, fw_arg2
LONG_S a3, fw_arg3
MTC0 zero, CP0_CONTEXT # NOTE5
PTR_LA $28, init_thread_union #为0号进程准备内核栈,详情见NOTE6
PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE
PTR_ADDU sp, $28
back_to_back_c0_hazard #NOTE7
set_saved_sp sp, t0, t1 #NOTE6
PTR_SUBU sp, 4 * SZREG #NOTE8
j start_kernel #NOTE9
END(kernel_entry)
__CPUINIT
1).NOTE1(kernel_entry_setup函数分析):
KE第一个调用函数则是kernel_entry_setup(arch/mips/include/asm/mach-loongson/kernel-entry-init.h)
源代码:
#ifndef __ASM_MACH_LOONGSON_KERNEL_ENTRY_H
#define __ASM_MACH_LOONGSON_KERNEL_ENTRY_H
.macro kernel_entry_setup
#ifdef CONFIG_CPU_LOONGSON3
.set push
.set mips64
/* Set LPA on LOONGSON3 config3 */
mfc0 t0, $16, 3
or t0, (0x1 << 7)
mtc0 t0, $16, 3
/* Set ELPA on LOONGSON3 pagegrain */
mfc0 t0, $5, 1
or t0, (0x1 << 29)
mtc0 t0, $5, 1
#ifdef CONFIG_LOONGSON3_ENHANCEMENT
/* Enable STFill Buffer */
mfc0 t0, $16, 6
or t0, 0x100
mtc0 t0, $16, 6
#endif
_ehb
.set pop
#endif
.endm
通常情况下这个函数的实现与具体的CPU有关,阅读这段代码得结合LOONGSON CPU手册。 这个函数的作用是设置CPU,由于LOONGSON是基于MIPS架构架构的,所以对CPU的设置是对CPU协处理器(CP0)的寄存器进行设置来设置CPU。kernel_entry_setup函数主要做了俩件事情:
2).NOTE2(setup_c0_status_pri函数分析):
mfc0 t0, CP0_STATUS
or t0, ST0_CU0|\set|0x1f|\clr
xor t0, 0x1f|\clr
mtc0 t0, CP0_STATUS
.set noreorder
sll zero,3 # ehb
.set pop
.endm
.macro setup_c0_status_pri
#ifdef CONFIG_64BIT
#ifdef CONFIG_CPU_LOONGSON3
setup_c0_status ST0_KX|ST0_MM 0 #(1)
#else
setup_c0_status ST0_KX 0
#endif
#else
#ifdef CONFIG_CPU_LOONGSON3
setup_c0_status ST0_MM 0
#else
setup_c0_status 0 0
#endif
#endif
.endm
setup_c0_status_pri函数与具体的CPU有关的汇编实现的。根据芯片手册及代码可以看出这个函数主要做了一下几个事情:
3).NOTE3(清除BBS段):
PTR_LA t0, __bss_start
LONG_S zero, (t0)
PTR_LA t1, __bss_stop - LONGSIZE
1:
PTR_ADDIU t0, LONGSIZE
LONG_S zero, (t0)
bne t0, t1, 1b
以bss_start为起始地址,步调为LONGSIZE(LOONGSON 是64位处理器,所以LOONGSIZE为8),终点地址为 bss_stop-LONGSIZE做循环清零的事情。
4).NOTE4(BIOS传参):
LONG_S a0, fw_arg0
LONG_S a1, fw_arg1
LONG_S a2, fw_arg2
LONG_S a3, fw_arg3
固件将要传递的参数的地址放在了a0,a1,a2,a3寄存器中,通过这段代码将地址赋予fw_arg*等变量。这段代码通过传递地址间接做参数传递。
5).NOTE5:
MTC0 zero, CP0_CONTEXT
清除CP0的Context寄存器,这个寄存器用来保存页表的起始地址
6).NOTE6(为0号进程准备内核栈):
PTR_LA $28, init_thread_union
PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE
PTR_ADDU sp, $28
set_saved_sp sp, t0, t1
源文件:arch/mips/include/asm/stackframe.h
.macro set_saved_sp stackp temp temp2
ASM_CPUID_MFC0 \temp, ASM_SMP_CPUID_REG
LONG_SRL \temp, SMP_CPUID_PTRSHIFT
LONG_S \stackp, kernelsp(\temp)
.endm
如图所示:
代码片段2将SP保存到kernelsp数组中去。
其中kernelsp数组定义在arch/mips/kernel/setup.c中。
unsigned long kernelsp[NR_CPUS]; #NR_CPUS CPU核的个数
注意:代码片段2将SP最终保存到kernelsp数组中,它是以CPUID号作为数组的偏移,而CPUID是存在CP0 Context寄存器中的,虽然前面已经清零,但是在这一刻,CPU将ID存到了这个寄存器中。
由上图引发的一些问题:
arch/mips/Makefile :
entry-y = $(shell $(objtree)/arch/mips/tools/elf-entry vmlinux)
~/work/code/linux/loongson/loongson-kernel/arch/mips/tools$ ./elf-entry ../../../vmlinux
0xffffffff81b62b80
refer to