在bootloader将控制权交给linux kernel前,需要完成下面几个动作
-
初始化系统中ram,并将ram信息告知kernel
-
准备好device tree blob, 并将首地址写到x0寄存器
-
解压内核(option)
-
MMU=off, D-cache=off
介绍几个关于内核位置的宏,在启动过程中,经常会用到
-
#define __PHYS_OFFSET (KERNEL_START - TEXT_OFFSET)
: kernel space 首地址 -
KERNEL_START: _text=PAGE_OFFSET + TEXT_OFFSET
: kernel开始运行的虚拟地址(内核正文段开始的虚拟地址) -
TEXT_OFFSET
: 内核正文地址距离kernel space首地址的偏移量-
对于arm32,该空间一般为32kb(0x8000),用于保存内核页表(process id = 0的pgd), 以及bootloader和kernel间参数传递
-
对于arm64, 该空间一般为512kb(0x80000)
-
-
PAGE_OFFSET
: 目前看和__PHYS_OFFSET
一样,均表示kernel space首地址
启动代码分析
常见arm指令
ldr
:ldr r0, [r1]
r0=* r1;将r1指向的内容存入r0ldr r0 0x12345678
: 把0x12345678这个地址中的值存放到r0中, r0=*(0x12345678)ldr r0, =0x12345678
: 此时ldr是个伪指令,功能类似mov,r0=0x12345678
str
:str r0 [r1, #4]
* (r1+4) = r0;将r0中内容存入r1+4中的地址- .macro str_l, src, sym, tmp
- adrp \tmp \sym
- str \src, [\tmp, :lo12:\sym]
-
str
:str r0 [r1] , #4
*r1=r0; r1=r1+4; 将r0中内容存入r1中的地址,并将新地址r1+8存入r1 adr
: 小范围地址读取指令,将基于pc相对偏移的地址值读取到寄存器中(距离pc 1m以内)- 原理:将符号的21位偏移,加上pc,结果写入到寄存器中
adr_l
: 将符号地址转变为运行时地址(通过pc relative offset形式)- .macro ldr_l, dst, sym
- adrp \dst, \sym
- ldr \dst, [\dst, :lo12:\sym]
-
adrp
: 以页为单位的大范围地址读取指令,通过该指令可以获得符号的物理地址,当然是page对齐的 -
mrs
: 处理器模式切换指令,从指定寄存器读取到临时寄存器 -
msr
: 写模式到指定寄存器 -
bic
:bic x0, x0, #0xF000 0000 0000 0000
: 位清除,将x0的高4位清除 -
rsb
:rsb x0, x0, #123
: x0=1280-x0 -
ldp,stp
: load/store pair;ldp x8, x2, [x0, #0x10]
: x0 += 0x10, x8 = *(x0), x2 = *(x0 + 0x8);stp x9, x8, [x4]
: *(x4) = x9, *(x4 + 8) = x9; b,bl,blx,bx
: 分别为跳转指令, 带返回值的跳转指令, 带返回和状态切换的跳转指令, 带状态切换的跳转指令
head.s中入口函数stext
ENTRY(stext)
bl preserve_boot_args //(1)根据arm64规范,保存x0-x3寄存器;在setup_arch函数中会访问boot_args进行校验
bl el2_setup // Drop to EL1, w20=cpu_boot_mode, (2)判断启动模式为EL1或是EL2,EL2表示支持虚拟化,KVM模块可以顺利启动
adrp x24, __PHYS_OFFSET //将__PHYS_OFFSET保存到x24中
bl set_cpu_boot_mode_flag //见下一篇解释
bl __vet_fdt //对bootloader传递给kernel的fdt参数进行校验;1. 是否8字节对齐;2. 是否在kernel space的前512M内
bl __create_page_tables // x25=TTBR0, x26=TTBR1, 见下一篇解释
/*
* The following calls CPU setup code, see arch/arm64/mm/proc.S for
* details.
* On return, the CPU will be ready for the MMU to be turned on and
* the TCR will have been set.
*/
ldr x27, =__mmap_switched // address to jump to after
// MMU has been enabled
adr_l lr, __enable_mmu // return (PIC) address
b __cpu_setup // initialise processor
ENDPROC(stext)
preserve_boot_args
/*
* Preserve the arguments passed by the bootloader in x0 .. x3
* 保存x0-x3寄存器,是为了符合arm64规范:x0必须为dtb物理地址,x1-x3必须为0
* 在setup_arch函数中会访问boot_args并进行校验
*/
preserve_boot_args:
mov x21, x0 // x21=FDT, 将dtb的地址暂存在x21中
adr_l x0, boot_args // record the contents of, 将boot_args首地址保存在x0中
stp x21, x1, [x0] // x0 .. x3 at kernel entry, 将x21,x1分别保存在boot_args[0], boot_args[1]
stp x2, x3, [x0, #16] //将x2,x3分别保存在boot_args[2], boot_args[3]
dmb sy // needed before dc ivac with
// MMU off
add x1, x0, #0x20 // 4 x 8 bytes, 将boot_args非参数首地址赋给x1,即x0是boot_args这段memory的首地址,x1是末尾的地址
b __inval_cache_range // tail call, 将x0, x1地址段对应的cacheline设定为无效
ENDPROC(preserve_boot_args)
el2_setup
/*
* end early head section, begin head code that is also used for
* hotplug and needs to have the same protections as the text region
*/
.section ".text","ax"
/*
* If we're fortunate enough to boot at EL2, ensure that the world is
* sane before dropping to EL1.
*
* Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in x20 if
* booted in EL1 or EL2 respectively.
* 判断cpu模式,只拿部分为例
*/
ENTRY(el2_setup)
mrs x0, CurrentEL
cmp x0, #CurrentEL_EL2 //判断是否处于EL2
b.ne 1f //如果不是,则跳转到1f
mrs x0, sctlr_el2 //从sctlr_el2读取模式到寄存器
CPU_BE( orr x0, x0, #(1 << 25) ) // Set the EE bit for EL2
CPU_LE( bic x0, x0, #(1 << 25) ) // Clear the EE bit for EL2
msr sctlr_el2, x0
b 2f
1: mrs x0, sctlr_el1 //执行到这,说明为EL1模式
CPU_BE( orr x0, x0, #(3 << 24) ) // Set the EE and E0E bits for EL1
CPU_LE( bic x0, x0, #(3 << 24) ) // Clear the EE and E0E bits for EL1
msr sctlr_el1, x0
mov w20, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1
isb
ret
.........
ENDPROC(el2_setup)
set_cpu_boot_mode_flag
/*
* Sets the __boot_cpu_mode flag depending on the CPU boot mode passed
* in x20. See arch/arm64/include/asm/virt.h for more info.
* 执行完el2_setup后,w20保存了exception level
* 分析__boot_cpu_mode的定义,可以有如下结论:
* 1. 如果cpu启动的时候是EL1 mode,会修改变量__boot_cpu_mode A域,将其修改为BOOT_CPU_MODE_EL1
* 2. 如果cpu启动的时候是EL2 mode,会修改变量__boot_cpu_mode B域,将其修改为BOOT_CPU_MODE_EL2
* ENTRY(__boot_cpu_mode)
* .long BOOT_CPU_MODE_EL2--------A
* .long BOOT_CPU_MODE_EL1--------B
*/
ENTRY(set_cpu_boot_mode_flag)
adr_l x1, __boot_cpu_mode
cmp w20, #BOOT_CPU_MODE_EL2
b.ne 1f
add x1, x1, #4 //el1模式走这里,修改x1的地址为B域
1: str w20, [x1] // This CPU has booted in EL1, *x1=w20;
dmb sy
dc ivac, x1 // Invalidate potentially stale cache line
ret
ENDPROC(set_cpu_boot_mode_flag)
__vet_fdt
/*
* Determine validity of the x21 FDT pointer.
* The dtb must be 8-byte aligned and live in the first 512M of memory.
* 见document/boot.txt要求
* 在512m内是为了在保证kernel space的首地址和fdt首地址在一个pud entry中
*/
__vet_fdt:
tst x21, #0x7 //校验x21是否为8字节对齐
b.ne 1f
cmp x21, x24 //是否在小于kernel space的首地址
b.lt 1f
mov x0, #(1 << 29)
add x0, x0, x24
cmp x21, x0
b.ge 1f //是否大于kernel space首地址+512M
ret
1:
mov x21, #0 //fdt地址有误,清零
ret
ENDPROC(__vet_fdt)
参考文档:
- Documentation/arm64/booting.txt
- http://www.wowotech.net/armv8a_arch/arm64_initialize_1.html
- https://www.jianshu.com/p/4b68f45065c6