解压内核镜像
步骤 0
uboot 将 zImage 复制到内存之后,跳转到 zImage 处开始执行,首先执行的代码是
arch/arm/boot/compressed/head.S 文件,首先是一些涉及不同体系结构调试相关的汇编宏定义
#ifdef DEBUG
#if defined(CONFIG_DEBUG_ICEDCC)
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_V6K) || defined(CONFIG_CPU_V7)
.macro loadsp, rb, tmp
.endm
.macro writeb, ch, rb
mcr p14, 0, \ch, c0, c5, 0
.endm
... 省略 ...
#endif
#endif
#endif
步骤 1
首先是保存 bootloader 传递过来的机器 ID 和 atags 起始地址
@ 设置段名
.section ".start", #alloc, #execinstr
.align
.arm @ 设置指令为 arm 模式
start:
.type start,#function @ 声明为函数标签
.rept 7
mov r0, r0
.endr
ARM( mov r0, r0 )
ARM( b 1f ) @ 向下跳转
THUMB( adr r12, BSYM(1f) )
THUMB( bx r12 )
.word 0x016f2818 @ Magic numbers to help the loader
.word start @ 程序其实地址,即 zImage 的地址
.word _edata @ zImage 结束地址
THUMB( .thumb )
1: mov r7, r1 @ 保存 bootloader 传递过来的机器 ID
mov r8, r2 @ 保存 bootloader 传递过来的 atags 起始地址
步骤 2
关闭中断、进入 SVC 模式
#ifndef __ARM_ARCH_2__
mrs r2, cpsr @ get current mode
tst r2, #3 @ not user?
bne not_angel
mov r0, #0x17 @ 进入 SVC 模式
ARM( swi 0x123456 ) @ angel_SWI_ARM
THUMB( svc 0xab ) @ angel_SWI_THUMB
not_angel:
mrs r2, cpsr
orr r2, r2, #0xc0 @ 关闭 FIQ 和 IRQ
msr cpsr_c, r2
#else
teqp pc, #0x0c000003 @ turn off interrupts
步骤 3
为了加速解压过程,打开缓存
#ifdef CONFIG_AUTO_ZRELADDR
@ 如果配置了运行时自动计算重定位地址
@ 则根据当前 pc 位置和 TEXT_OFFSET 计算出解压位置
@ TEXT_OFFSET 在 arch/arm/Makefile 中指定,表示的是相对于内存起始地址的偏移
@ 定义为 textofs-y := 0x00008000 TEXT_OFFSET := $(textofs-y)
mov r4, pc
and r4, r4, #0xf8000000 @ 这里假设 zImage 是被放在内存的前 128MB 内的
add r4, r4, #TEXT_OFFSET @ 得到 zImage 解压的物理地址
#else
@ 这里是直接在 arch/arm/mach-s5p4418/Makefile.boot 中定义的
@ 定义为 zreladdr-y := 0x40008000
ldr r4, =zreladdr @ r4 存放解压后内核存放的起始地址
#endif
@ 打开缓存和 MMU
bl cache_on
步骤 4
检查解压后的内核镜像是否与当前镜像发生覆盖
restart: adr r0, LC0
ldmia r0, {r1, r2, r3, r6, r10, r11, r12} @ 将 LC0 数据放入寄存器中
ldr sp, [r0, #28] @ 更新栈指针位置
sub r0, r0, r1 @ 计算当前程序位置和链接地址间的偏移量
add r6, r6, r0 @ 得到 zImage 运行地址的结束位置
add r10, r10, r0 @ 存放未压缩内核大小的运行地址
/*
* 内核编译系统会将未压缩的内核大小追加到压缩后的内核数据之后
* 并以小端格式存储
*/
@ 取出未压缩内核大小放入 r9
ldrb r9, [r10, #0]
ldrb lr, [r10, #1]
orr r9, r9, lr, lsl #8
ldrb lr, [r10, #2]
ldrb r10, [r10, #3]
orr r9, r9, lr, lsl #16
orr r9, r9, r10, lsl #24
#ifndef CONFIG_ZBOOT_ROM
/* 在栈指针之上分配内存空间放到 r10 中 (64k max) */
add sp, sp, r0
add r10, sp, #0x10000
#else
mov r10, r6
#endif
mov r5, #0 @ init dtb size to 0
/*
* 检查解压后的内核是否会覆盖当前代码
* r4 = 最终解压后的内核起始地址
* r9 = 解压后的内核镜像大小
* r10 = 当前镜像结束地址,其中包括所分配的内存区域
* We basically want:
* r4 - 16k 页表 >= r10 -> OK
* r4 + image length <= address of wont_overwrite -> OK
*/
add r10, r10, #16384 @ 内核镜像前的 16KB 页表
cmp r4, r10
bhs wont_overwrite @ 解压后的内核起始地址大于等于 r10
add r10, r4, r9
adr r9, wont_overwrite
cmp r10, r9 @ 或者解压后的内核结束地址在 wont_overwrite 地址之前
bls wont_overwrite
/*
* r6 = _edata
* r10 = end of the decompressed kernel
*/
@ 当前代码段向后移动到的目的地址,不足 256 字节的补足 256 字节,向上取整
add r10, r10, #((reloc_code_end - restart + 256) & ~255)
bic r10, r10, #255
@ 当前代码段起始地址,以 32 字节为单位,向下取整
adr r5, restart
bic r5, r5, #31
sub r9, r6, r5 @ 需要移动镜像的大小
add r9, r9, #31 @ 以 32 字节为单位向上取整
bic r9, r9, #31
add r6, r9, r5 @ 循环结束地址
add r9, r9, r10 @ 目的地址结束位置
@ 循环移动当前镜像
1: ldmdb r6!, {r0 - r3, r10 - r12, lr}
cmp r6, r5
stmdb r9!, {r0 - r3, r10 - r12, lr}
bhi 1b
sub r6, r9, r6 @ 代码重定位的偏移地址
#ifndef CONFIG_ZBOOT_ROM
add sp, sp, r6 @ 对栈指针也进行重定位
#endif
bl cache_clean_flush
@ 跳转到重定位后的镜像 restart 处重新执行,检查是否存在覆盖
adr r0, BSYM(restart)
add r0, r0, r6
mov pc, r0
步骤 5
清理 bss 段、解压内核、关闭缓存,最后启动内核
/* 至此,当前镜像和解压后的内核不会发生覆盖问题 */
wont_overwrite:
/*
* If delta is zero, we are running at the address we were linked at.
* r0 = delta
* r2 = BSS start
* r3 = BSS end
* r4 = kernel execution address
* r5 = appended dtb size (0 if not present)
* r7 = architecture ID
* r8 = atags pointer
* r11 = GOT start
* r12 = GOT end
* sp = stack pointer
*/
orrs r1, r0, r5
beq not_relocated
add r11, r11, r0
add r12, r12, r0
not_relocated: mov r0, #0
1: str r0, [r2], #4 @ 清理 bss 段
str r0, [r2], #4
str r0, [r2], #4
str r0, [r2], #4
cmp r2, r3
blo 1b
/*
* 至此,C 语言运行环境已经初始化完成
* r4 = 解压后的内核起始地址
* r7 = 机器 ID
* r8 = atags 指针
*/
mov r0, r4
mov r1, sp @ 在栈指针之上分配内存空间
add r2, sp, #0x10000 @ 64k max
mov r3, r7
bl decompress_kernel @ 解压内核
bl cache_clean_flush
bl cache_off @ 关闭缓存
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
ARM( mov pc, r4 ) @ 启动内核
THUMB( bx r4 ) @ entry point is always ARM
.align 2
.type LC0, #object
LC0: .word LC0 @ r1:LC0 的链接地址
.word __bss_start @ r2:bss 段的起始地址
.word _end @ r3:bss 段的结束地址
.word _edata @ r6:zImage 的结束地址
.word input_data_end - 4 @ r10:存放未压缩的内核大小的链接地址
.word _got_start @ r11:全局偏移表起始位置
.word _got_end @ ip:全局偏移表结束位置
.word .L_user_stack_end @ sp:栈指针
.size LC0, . - LC0 @ 该 LC0 数据的大小