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参数和内核本身参数传递给内核。

相关内容