ppc-booting-sequence
ppc-booting-sequence
Powerpc启动顺序分析。
摘要:本文致力于研究Powerpc的引导技术,其中包括U-boot启动代码分析,kernel for Powerpc 启动代码分析,以及U-boot加载kernel代码分析。以上三个部分属于体系结构相关的内容。
由于时间有限,只是对代码进行粗读。
一、Kernel 启动代码分析
如果由u-boot解压缩内核,则内核的入口点是arch/ppc/head.s,注意,如果使用新的bsp,则arch使用powerpc而不是ppc。Ppc主要针对32位系统,powerpc把32位系统与64位系统整合在一起。
Powerpc有四种子体系:PMAC——PowerMacintosh主要是对mac的支持;PReP主要支持IBM,motorola,我们的ppc 440,460,mpc8548应该属于这个体系;CHRP支持IBM RS/6000,Genesi;APUS Amiga Power-UP Systems (APUS)。每种架构的启动是不一样的。
head.s
1、找到_start,首先根据ABI规范保存r3~r7(保存到r27~r31)。主要是一些内核参数。此时mmu是打开的,当然物理内核和虚拟内存相同。然后跳到early_init中把bss清零,然后判断CPU类型,根据类型判断物理地址(在关掉mmu时需要定位代码所在的物理地址),最后通过r3返回物理地址。
2、Bl mmu_off 关mmu,默认启动mmu是开启的,使用cpu内部的tlb进行内存映射。
3、Bl clear_bats 清除block address table
4、BL Flush_tlbs 应该是刷新tlb//其实都是写寄存器的操作。
5、Bl initial_bats 将前16M内存映射给内核使用。
6、Bl reloc_offset 计算连接地址与实际地址的偏移
7、Call_setup_cpu 设置cpu的ctl寄存器
8、Reloc_kernel 如果有必要则reloc
9、B turn_on_mmu 这个函数通过rfi返回,将SPRN_SRR0设置成start_here地址,rfi中断返回时将跳到这个地址执行,并切换上下文。
10、 Start_here 真正的打开mmu,通过rfi跳转到start_kernel运行。
11、 Start_kernel在main.c中,后面的内容和所有架构下无异。
二、 U-boot启动代码分析
U-Boot启动代码(for ppc)主要关注如下几个文件:1、u-boot.lds,2、start.s,3、board.c。u-boot.lds是链接文件,一般在对应开发板目录下;
Start.s 是启动代码文件,一般在对应得cpu目录下;
Board.c 是板子初始化文件,一般在对应体系结构的lib下;
首先来看u-boot.lds,从这个文件可以找到整个程序的入口位置。开头代码如下:
OUTPUT_ARCH(powerpc) /*代码是for ppc的*/
/* Do we need any of these for elf?
__DYNAMIC = 0; */
SECTIONS
{
.resetvec 0xFFFFFFFC : /*定义resetvec段在0xFFFFFFFC位置,这个位置是ppc上电后IP指向的位置*/
{
*(.resetvec) /*段内容*/
} = 0xffff /*用0xffff填充空余部分*/
.bootpg 0xFFFFF000 : /*定义.bootpg段在0xFFFFF000位置*/
{
cpu/ppc4xx/start.o (.bootpg)
} = 0xffff /*用0xffff填充空余部分*/
…………
现在找到resetvec.s 文件。
#include <config.h>
.section .resetvec,"ax"
#if defined(CONFIG_440)
b _start_440 /*CPU上电后执行的第一条命令:b _start_440*/
#else
#if defined(CONFIG_BOOT_PCI) && defined(CONFIG_MIP405)
b _start_pci
#else
b _start
#endif
#endif
Start.S 代码分析。_start_440在start.s中定义。Ppc440启动时,只有最高位的4k地址被TLB映射,此时控制器将最高4k的内容拷贝到cache中运行(我猜想在启动时440将自己的cache作为ram来使用,这样在不初始化外部ram的时候也能跑代码)。Start.s正是位于这4k当中,因此start.s担负着初始化整个正常环境的重任。接下来:
1) 设置中断控制器寄存器
2) 设置调试寄存器
3) 设置cpu控制器寄存器
4) 安装中断向量
5) 配置cache
6) 初始化mmu控制器,在启动时使用I/D cache控制器中的tlb实现地址映射。
7) 删除所有的tlb entries
8) 建立新的tlb。包括16M启动flash,内存,PCI存储空间,BCSR空间等等。
9) B _start 跳转到_start。
10) 此时需要建立新的工作环境,因此首先需要disable异常和中断。
11) 设置intenal ram
12) 设置电源管理
13) 设置堆栈 (到这里为止代码应该是在cache中)
14) bl cpu_init_f(board.c) 跳到Flash中执行cpu初始化。此时内存并没有完全初始化,堆栈空间很小,BSS也没有初始化。这段代码主要是打开一个console以便于调试,以及初始化RAM。以便把代码全部搬到RAM中运行。还有一个重要工作就是获取全局数据——gd。
Cpu_init_f:
(1) gd = (gd_t *) (CFG_INIT_RAM_ADDR + CFG_GBL_DATA_OFFSET);全局gd指针。__asm__ __volatile__("": : :"memory"); 内存壁垒,防止编译器优化。清空gd。
(2) for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr) () != 0) {
hang ();
}
}/*cpu/board /console… 初始化*/
(3) 设置gd
(4) 初始化sram
(5) 计算需要保护的内存区域(board infor,kernel log buf ,monitor code等等),addr即为除去保护内存后用于存放relocate code的地址,addr_sp为堆栈可以使用的基地址
(6) 调用relocate_code (start.s文件中) ,r3 = addr_sp , r4 = gd, r5= addr
Relocate_code:
(1) 使能I/D cache
(2) 使用新的stack:r1=addr_sp。将gd(global data)保存在 r9中,将relocate code地址保存在r10中
mr r1, r3 /* Set new stack pointer */
mr r9, r4 /* Save copy of Init Data pointer */
mr r10, r5 /* Save copy of Destination Address */
(3) 重定位GOT
(4) Relocate code
(5) Flush D cache
(6) 跳转到in_ram代码中,r10中保存着RAM中代码起始位置,加上in_ram的相对位置即为in_ram在ram中的位置:
addi r0, r10, in_ram - _start + _START_OFFSET
mtlr r0
blr /* NEVER RETURNS! */
in_ram:
(1) 重定位GOT2
(2) Clear bss
(3) 将gd以及代码在内存中的起始位置作为参数,调用board_init_r
mr r3, r9 /* Init Data pointer */
mr r4, r10 /* Destination Address */
bl board_init_r
下面再次进入board.c 文件
board_init_r:
这部分代码初始化一些列外设,这里只提几个感兴趣的地方。
(1) 建立Command table。U-boot支持N多命令,通过一个cmdtbl来管理,这个cmdtbl是在运行时被负值的
for (cmdtp = &__u_boot_cmd_start; cmdtp != &__u_boot_cmd_end; cmdtp++) {……}
u-boot的命令都被定义在.u_boot.cmd段,并在链接时确定期位置:在__u_boot_cmd_start和__u_boot_cmd_end之间。
(2) Trap的安装是通过调用start.s中的代码完成的。其实就是将原有的trap重定位到dest_addr,也就是RAM中的起始位置。
(3) 茫茫多设备的初始化 …………
(4) 进入main_loop()等待用户输入命令,如bootm命令。
三、 Bootm代码分析
目前大多数kernel的booting都由bootm来完成,可以说这个函数是连接U-boot和kernel的桥梁,bootm命令主要通过do_bootm()(/common/cmd_bootm.c)函数和do_bootm_linux()(/lib_ppc/bootm.c)函数来实现。后者是与架构相关的。Powerpc kernel booting有两种方式:1、使用of——open firmware;2、使用dt——device tree。现在高版本的u-boot基本都支持dt启动方式。在制作uImage时会在Image head中加入一段信息,告诉u-boot使用的启动方式。这里只讨论dt启动方式。
其实整个启动过程很简单,do_bootm命令接收命令行参数,根据命令行参数对内核的资源进行有效性校验,然后将所有信息存入image结构。并把这个结构传递给bootm.c。bootm.c 从image中找到内核、ramdisk、cmdline以及FDT,将cmdline保存在固定的位置,然后跳转到kernel所在的地址,并把FDT和kernel本身的地址作为参数传递过去,kernel会根据cmdline中的信息选择启动方式。
1、当用户敲入bootm kernel_addr – fdt_addr时,u-boot调用do_bootm函数。
2、Local memory block initiate。初始化本地存储块(可能是用户kernel内存管理)
3、boot_get_kernel(),这个函数找到kernel image,验证其完整性,并定位内核数据。将信息存放在images中。
fit_parse_conf;取出load_addr,这个地址在uImage编译时确定。
genimg_get_image;如果image在flash上,则拷贝到load_addr内存中。
image_get_type;判断image head的类型,显然kernel应该是IH_TYPE_KERNEL类型的。
image_get_kernel;根据image head中的信息对image进行校验。
image_get_data;取得kernel数据指针。
image_get_data_size;取得kernel大小。
返回image地址
4、判断压缩格式,并解压缩内核
5、调用do_bootm_linux()函数启动内核。
下面分析do_bootm_linux()函数。这个函数比较重要,我们将详细分析之。
(1) 首先为内核命令行参数分配空间,这个空间必须在内核可以访问的高地址,并且不会被其他数据干扰。取出sp值:int sp; asm( "mr %0,1": "=r"(sp) : );// sp=r1。即栈。
(2) sp -= 1024; 留出1k空间,保证访问安全
(3) lmb_reserve(lmb, sp, (CFG_SDRAM_BASE + get_effective_memsize() - sp)); //把这sp以上的空间加入到lmb保护起来。
(4) boot_get_fdt :获取fdt,这段代码较长,我没有细看。主要就是从image中获取tdt的地址和大小。
(5) boot_get_cmdline:获取命令行参数,在lmb中申请一段空间,然后把uboot中bootargs的内容复制到里面。
(6) boot_get_kbd:获取board info,也就是从gd(global data)中将bd复制到lmb中。
(7) 找到内核起始位置
(8) 找ramdisk。如果有,首先判断是否有效,是否是initrd,然后拷贝到RAM中的rd_load地址,这个地址在image 结构中有定义。
(9) boot_relocate_fdt :将fdt定位到lmb中
(10) 启动内核,将fdt参数和内核本身参数传递给内核。
评论暂时关闭