weiqi7777

cortex-a8 uboot系列:第十二章 uboot源码分析 uboot如何启动内核2

0
阅读(2167) 评论(4)

一、            zImage启动细节

do_bootm函数在after_header_check符号前,都是在进行镜像的头部信息校验。校验时就要根据不同的种类的image类型进行不同的校验。而不同的镜像的开头都是有自己的头信息。

所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型,然后按照这种类型的头信息格式去校验。校验通过则进入下一步,准备启动内核,如果检验失败,则认为镜像有问题,不启动内核。

上一节分析过zImage启动,当判断不是从zImage启动时,会检查其他类镜像。如uImage镜像。

 

Uboot支持多种镜像启动,uboot中定义3种镜像方式。但是只有两种是有效的。

clip_image002

IMAGE_FORMAT_INVALID 无效的镜像

IMAGE_FORMAT_LEGACY  uImage镜像启动

IMAGE_FORMAT_FIT      新的设备树启动

clip_image004

IMAGE_FORMAT_LEGACY,表示启动镜像是uImage镜像方式。

uImage方式是uboot本身发明的支持linux启动的镜像格式,但是后来这种方式被一种新的方式替代,这个新的方式就是设备树方式启动(在do_bootm中叫FIT)。

1.    uImage启动方式

boot_get_kernel函数(common/cmd_bootm.c)寻找镜像的头信息,并校验,得到真正的kernel的起始位置去启动。

clip_image006

 

获取镜像地址。

clip_image008

 

genimg_get_image函数(common/image.c

clip_image010

clip_image012

 

clip_image014

判断镜像类型,是uImage还是设备树。通过genimg_get_format函数(common/image.c)获取。

clip_image016

判断镜像的魔数。uImage的魔数是0x27051956

clip_image018

 

判断完镜像类型后,打印镜像类型的值。启动镜像为uImageIMAGE_FORMAT_LEGACY的值为1,所以会打印1

最终执行完这个函数后,会打印如下信息。

clip_image019

 

clip_image021

获取镜像的大小。

 

clip_image023

从外部的dataflash中读取kernelddr中。

 

         以上的程序都没有执行,因为此时,uboot已经把kernel拷贝到DDR中。在函数的最开始的if判断不成立,所以后面的代码都不执行。

clip_image025

 

clip_image027

调用image_get_kernel函数(common/cmd_bootm.c)从镜像中获取kernel

clip_image029

 

clip_image031

判断魔数和头信息校验是否正确。

 

clip_image033

image_print_contents函数(common/image.h,打印uImage镜像的信息。

 

clip_image035

 

clip_image037

         kernel数据进行校验。

 

clip_image039

image_check_target_arch函数(common/cmd_bootm.c),判断镜像和soc架构是否匹配。

clip_image041

 

上面执行完毕后,要获取os数据和长度。通过判断镜像的类型。这里是IH_TYPE_KERNEL,表示数据是一个内核。

调用两个函数获取os数据和os长度

clip_image043

 

 

clip_image045

构建images全部变量。

至此boot_get_kernel函数执行完毕。

 

获取镜像参数。

clip_image047 

继续获取到一些信息。

clip_image049

 

clip_image051

判断镜像的压缩类型,然后根据该类型,uboot中对镜像进行解压缩。但是对于现在的uboot是使用镜像自己提供的解压缩程序,所以comp值为IH_COMP_NONEuboot对镜像文件不解压。

 

执行到after_header_check标号,表示bootm第二阶段结束。头信息校验结束。

clip_image053

2.    其他启动方式

uboot本身设计时只支持uImage启动,原来uboot的代码也是如此。后来又了fdt方式之后,就把uImage方式命令为LEGACY方式,fdt方式命令为FIT方式,于是多了很多#if #endif添加的代码。

后来移植的人添加了zImage启动的方式,添加在uImagefdt启动方式之前。而当zImage进行校验过后,直接跳转到after_header_check标号处,跳过uImageFIT的校验。

3.    启动linux内核

第二阶段校验镜像头信息结束,进入第三阶段,第三阶段主要任务是启动linux内核,调用do_bootm_linux函数。

 

clip_image055

判断操作系统类型,然后启动对应的操作系统。对于我们使用的LINUX,调用do_bootm_linux函数(lib_arm/bootm.c)来启动linux

clip_image057

 

clip_image059

获取启动参数

 

clip_image061

获取kernel的入口地址。在boot_get_kernel函数中将images->legacy_hdr_valid1,说明头有效,然后获取镜像的入口地址。

clip_image063

 

epentrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像的开头有n个字节的头信息),真正的镜像文件执行时第一句代码相对于镜像起始是有一定偏移量的。这个偏移量是记录在头信息中。

一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由MAGIC_NUM_确定镜像的类型;第二步对镜像进行校验;第三步再次读取头信息,由头信息知道镜像的各种信息(镜像长度、镜像种类、入口地址);第四步跳转到entrypoint处执行镜像。

clip_image065

找到ep后,赋值给函数指针theKerneltheKernel就指向了kernel的真正入口地址(也就是kernel的第一条代码的地址),后面就可以通过theKernel进行执行kernel

 

机器码的再次确定

Uboot在启动内核时,机器码要传给内核。Uboot传给内核的机器码是怎么确定?第一顺序备选是环境变量machid,第二顺序备选是gd->bd->bi_arch_numx210_sd.h中硬编码配置)。

 

clip_image067

获取机器码,以便后面传递给kernel

 

 

以下这部分代码,就是uboot准备给linux内核准备传递的参数处理。

clip_image069

 

clip_image071

Uboot最后打印的starting kernel。这句如果能出现,说明uboot整个是成功的,也成功加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行。如果这句输出后,串口没有输出,说明内核没有被成功执行。

原因有:传参问题、内核在DDR中加载地址不对、kernel烧录地址不对。

 

调用函数指针theKernel去执行linux内核,至此uboot完成了使命。

 

二、            ubootlinux内核传参

传参在do_bootm_linux函数中。

1.    tag方式传参

struct tagtag是一个数据结构,在ubootlinux kernel中都有定义tag数据结构,而且定义一致。

clip_image073

struct tag定义在include/asm-arm/setup.h

clip_image075

里面有两个参数。一个是tag_header结构体(include/asm-arm/setup.h中),一个是tag多种类型属性的联合体。

clip_image076

tag_header表征tag的类型编码和大小。kernel得到tag后,先分析这个tag_header得到tag的类型和大小,然后将tag剩余部分当做tag_xxx处理

 

clip_image078

定义了不同参数的tag值以及不同参数的结构体。

 

tag_starttag_endKernel接收到的传参是若干个tag构成的,这些tagtag_start起始,到tag_end结束。

当需要使用tag传参时,首先传递tag_start,然后传递tag参数,最后传递tag_end

 

clip_image080

判断有哪些类型的tag需要传递。当有tag需要传递后,使用setup_start_tag函数开始传递。

clip_image082

首先通过bd->bi_boot_params参数获取传参参数的起始地址。然后在这个地方放入一个ATAG_CORE类型的tag,表示参数传递开始。然后params指向下一个待传输tag的地址。

 

X210_sd.h中配置传参宏

CONFIG_SETUP_MEMORY_TAGS tag_mem,传参内容是内存配置信息。两个参数,一个内存大小,一个内存起始地址。

clip_image084

clip_image086

 

CONFIG_CMDLINE_TAG tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs。这个参数很重要,uboot要将这个参数传递给linux内核的

clip_image088

clip_image090

去掉命令中开头无效的空格。

 

CONFIG_INITRD_TAG tag_initrd,内存磁盘。

clip_image092

 

CONFIG_MTDPARTITION tag_mtdpartition,传参内容是iNand/sd卡的分区表。

clip_image094

clip_image096

 

起始tagATAG_CORE、结束tagATAG_NONE,其他的ATAG_XXX都是有效信息tag

 

参数传递后,内核是怎么拿到这些tag信息了?

就是通过调用的启动内核的函数指针theKernel3个参数中的其中之一的参数来确定tag信息。

 

启动内核的函数有3个参数,这3个参数是uboot直接传递给linux内核的,通过寄存器来实现传参的。(第一个参数放在r0中,第二个参数放在r1中,第三个参数放在r2中)。

第一个参数固定为0

第二个参数为机器码

第三个参数为启动传参tagDDR首地址

三、            移植时需要注意的问题

Uboot移植时一般只需要配置相应的宏即可。

Kernel启动不成功,注意传参是否成功。传参不成功首先看ubootbootarg设置是否正确,其次看uboot是否开启了相应宏以支持传参。

四、            Uboot启动内核的总结

启动4步骤:

第一步:将内核搬移到DDR

第二步:校验DDR中内核镜像、CRC

第三步:准备传参,设置需要传参的tag

第四步:跳转执行内核,使用函数指针

 

设计到的主要函数:do_bootmdo_bootm_linux

do_bootm包括两部分:一部分对内核镜像进行校验,另一部分就是执行do_bootm_linux

do_bootm_linux对传递的参数进行设置,然后去启动linux内核

 

Uboot能启动的内核格式:zImage  uImage  fdt格式

 

跳转与函数指针的方式运行内核,要带3个参数。

  1. 非常的强,如果有事DTB这种的启动方式是什么样的呢?

  2. 太牛了,就服你

  3. 恩,恩,不错,不错

  4. 不错,不错,不错