cortex-a8 uboot系列:第十二章 uboot源码分析 uboot如何启动内核2
0赞一、 zImage启动细节
do_bootm函数在after_header_check符号前,都是在进行镜像的头部信息校验。校验时就要根据不同的种类的image类型进行不同的校验。而不同的镜像的开头都是有自己的头信息。
所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型,然后按照这种类型的头信息格式去校验。校验通过则进入下一步,准备启动内核,如果检验失败,则认为镜像有问题,不启动内核。
上一节分析过zImage启动,当判断不是从zImage启动时,会检查其他类镜像。如uImage镜像。
Uboot支持多种镜像启动,uboot中定义3种镜像方式。但是只有两种是有效的。
IMAGE_FORMAT_INVALID: 无效的镜像
IMAGE_FORMAT_LEGACY: uImage镜像启动
IMAGE_FORMAT_FIT: 新的设备树启动
宏IMAGE_FORMAT_LEGACY,表示启动镜像是uImage镜像方式。
uImage方式是uboot本身发明的支持linux启动的镜像格式,但是后来这种方式被一种新的方式替代,这个新的方式就是设备树方式启动(在do_bootm中叫FIT)。
1. uImage启动方式
boot_get_kernel函数(common/cmd_bootm.c)寻找镜像的头信息,并校验,得到真正的kernel的起始位置去启动。
获取镜像地址。
genimg_get_image函数(common/image.c)
判断镜像类型,是uImage还是设备树。通过genimg_get_format函数(common/image.c)获取。
判断镜像的魔数。uImage的魔数是0x27051956。
判断完镜像类型后,打印镜像类型的值。启动镜像为uImage,IMAGE_FORMAT_LEGACY的值为1,所以会打印1。
最终执行完这个函数后,会打印如下信息。
获取镜像的大小。
从外部的dataflash中读取kernel到ddr中。
以上的程序都没有执行,因为此时,uboot已经把kernel拷贝到DDR中。在函数的最开始的if判断不成立,所以后面的代码都不执行。
调用image_get_kernel函数(common/cmd_bootm.c)从镜像中获取kernel。
判断魔数和头信息校验是否正确。
image_print_contents函数(common/image.h),打印uImage镜像的信息。
对kernel数据进行校验。
image_check_target_arch函数(common/cmd_bootm.c),判断镜像和soc架构是否匹配。
上面执行完毕后,要获取os数据和长度。通过判断镜像的类型。这里是IH_TYPE_KERNEL,表示数据是一个内核。
调用两个函数获取os数据和os长度
构建images全部变量。
至此boot_get_kernel函数执行完毕。
获取镜像参数。
继续获取到一些信息。
判断镜像的压缩类型,然后根据该类型,uboot中对镜像进行解压缩。但是对于现在的uboot是使用镜像自己提供的解压缩程序,所以comp值为IH_COMP_NONE,uboot对镜像文件不解压。
执行到after_header_check标号,表示bootm第二阶段结束。头信息校验结束。
2. 其他启动方式
uboot本身设计时只支持uImage启动,原来uboot的代码也是如此。后来又了fdt方式之后,就把uImage方式命令为LEGACY方式,fdt方式命令为FIT方式,于是多了很多#if #endif添加的代码。
后来移植的人添加了zImage启动的方式,添加在uImage或fdt启动方式之前。而当zImage进行校验过后,直接跳转到after_header_check标号处,跳过uImage和FIT的校验。
3. 启动linux内核
第二阶段校验镜像头信息结束,进入第三阶段,第三阶段主要任务是启动linux内核,调用do_bootm_linux函数。
判断操作系统类型,然后启动对应的操作系统。对于我们使用的LINUX,调用do_bootm_linux函数(lib_arm/bootm.c)来启动linux。
获取启动参数
获取kernel的入口地址。在boot_get_kernel函数中将images->legacy_hdr_valid置1,说明头有效,然后获取镜像的入口地址。
ep是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像的开头有n个字节的头信息),真正的镜像文件执行时第一句代码相对于镜像起始是有一定偏移量的。这个偏移量是记录在头信息中。
一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由MAGIC_NUM_确定镜像的类型;第二步对镜像进行校验;第三步再次读取头信息,由头信息知道镜像的各种信息(镜像长度、镜像种类、入口地址);第四步跳转到entrypoint处执行镜像。
找到ep后,赋值给函数指针theKernel。theKernel就指向了kernel的真正入口地址(也就是kernel的第一条代码的地址),后面就可以通过theKernel进行执行kernel。
机器码的再次确定
Uboot在启动内核时,机器码要传给内核。Uboot传给内核的机器码是怎么确定?第一顺序备选是环境变量machid,第二顺序备选是gd->bd->bi_arch_num(x210_sd.h中硬编码配置)。
获取机器码,以便后面传递给kernel。
以下这部分代码,就是uboot准备给linux内核准备传递的参数处理。
Uboot最后打印的starting kernel。这句如果能出现,说明uboot整个是成功的,也成功加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行。如果这句输出后,串口没有输出,说明内核没有被成功执行。
原因有:传参问题、内核在DDR中加载地址不对、kernel烧录地址不对。
调用函数指针theKernel去执行linux内核,至此uboot完成了使命。
二、 uboot给linux内核传参
传参在do_bootm_linux函数中。
1. tag方式传参
struct tag,tag是一个数据结构,在uboot和linux kernel中都有定义tag数据结构,而且定义一致。
struct tag定义在include/asm-arm/setup.h中
里面有两个参数。一个是tag_header结构体(include/asm-arm/setup.h中),一个是tag多种类型属性的联合体。
tag_header表征tag的类型编码和大小。kernel得到tag后,先分析这个tag_header得到tag的类型和大小,然后将tag剩余部分当做tag_xxx处理
定义了不同参数的tag值以及不同参数的结构体。
tag_start和tag_end。Kernel接收到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
当需要使用tag传参时,首先传递tag_start,然后传递tag参数,最后传递tag_end。
判断有哪些类型的tag需要传递。当有tag需要传递后,使用setup_start_tag函数开始传递。
首先通过bd->bi_boot_params参数获取传参参数的起始地址。然后在这个地方放入一个ATAG_CORE类型的tag,表示参数传递开始。然后params指向下一个待传输tag的地址。
X210_sd.h中配置传参宏
CONFIG_SETUP_MEMORY_TAGS, tag_mem,传参内容是内存配置信息。两个参数,一个内存大小,一个内存起始地址。
CONFIG_CMDLINE_TAG: tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs。这个参数很重要,uboot要将这个参数传递给linux内核的
去掉命令中开头无效的空格。
CONFIG_INITRD_TAG: tag_initrd,内存磁盘。
CONFIG_MTDPARTITION: tag_mtdpartition,传参内容是iNand/sd卡的分区表。
起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。
参数传递后,内核是怎么拿到这些tag信息了?
就是通过调用的启动内核的函数指针theKernel的3个参数中的其中之一的参数来确定tag信息。
启动内核的函数有3个参数,这3个参数是uboot直接传递给linux内核的,通过寄存器来实现传参的。(第一个参数放在r0中,第二个参数放在r1中,第三个参数放在r2中)。
第一个参数固定为0
第二个参数为机器码
第三个参数为启动传参tag的DDR首地址
三、 移植时需要注意的问题
Uboot移植时一般只需要配置相应的宏即可。
Kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootarg设置是否正确,其次看uboot是否开启了相应宏以支持传参。
四、 Uboot启动内核的总结
启动4步骤:
第一步:将内核搬移到DDR中
第二步:校验DDR中内核镜像、CRC等
第三步:准备传参,设置需要传参的tag
第四步:跳转执行内核,使用函数指针
设计到的主要函数:do_bootm和do_bootm_linux
do_bootm包括两部分:一部分对内核镜像进行校验,另一部分就是执行do_bootm_linux。
do_bootm_linux对传递的参数进行设置,然后去启动linux内核
Uboot能启动的内核格式:zImage uImage fdt格式
跳转与函数指针的方式运行内核,要带3个参数。