2021-09-14 uboot移植开发

引言:最近要改动uboot,实现像微软PC上,u盘一键刷机或手机上安全模式下刷机的操作
专门去好好研究了点uboot的启动过程;以下为总结:

嵌入式系统                                                        微软-PC
————————————————————————

bootloader   -引导.启动内核                              Bios

内核                                                                   引导操作系统Windows

挂载根文件                                                        识别系统盘(C/D盘)

应用程序                                                            应用程序....
————————————————————————
bootloader之uboot的作用:引导、启动内核

从flash中读内核到SDRAM,在这之前,要求先先关开门口内初始化时钟和初始化SDRAM(通常称为硬件相关初始化)

uboot三个步奏:
1)从flash读出内核,

2)将其放到sdram中运行(要求可:写读flash->初始化sdram(初始化时钟、初始化串口)

3)然后启动内核

4)为开发方便,额外加入其他功能--烧写flash(比如从网络,usb或串口传进来的内核)!

分析Makefile,达到分析文件结构;分析配置过程:

1)配置:比如make Linux-menuconfig

2)编译:makez

二.读出内核并启动内核

1.了解系统的上电过程:
board_init_r --> init_sequence_r --> run_main_loop --> main_loop --> bootdelay_process-->autoboot_command 主要就是这么个流程(下图从右向左)



所以:具体怎么决定是进命令行还是启动kernel是在autoboot_command决定的!!!
正常自动启动如下:
左边为终端打印:右方tstc()即为获取键值;此处可以设置特定键值为启动内核开关!!

如果看到:

if (bootdelay >= 0) 
{ //此处:键盘按Ctrl+C、enter、或者Space将abort置1,将不再启动内核
	if (tstc()) 
	{    /* we got a key press    */             
		key = getc(); /* consume input */             
		puts("\b\b\b 0");             
	switch (key) 
	{                 
		case 0x03:      /* ^C - Ctrl+C */                 
		case 0x0d:      /* Enter */                 
		case 0x20:      /* Space */ //only "enter" key can triger abort                 
		abort = 1;    /* don't auto boot    */             
	}         
}

如果abortboot_single_key / abortboot_normal返回1,则autoboot_command执行完成,回到main_loop继续跑到cli_loop-->cli_simple_loop进入,并停在命令行模式。
2.上面是命令行模式,如果abortboot_single_key/abortboot_normal返回0.则abortboot返回0,那么autoboot_command里调用run_command_list,run_command_list中执行命令bootcmd启动kernel!
接下来说,上电从flash读取内核存入系统内存并启动kernel的过程:
如上说到run_command_list函数,该函数会调取board_run_command(buff);
#ifdef CONFIG_CMDLINE
    rcode = cli_simple_run_command_list(buff, flag);
#else
    rcode = board_run_command(buff);
#endif


在调取之前,需要读取环境变量的数据参数;根据参数去读取指定位置flash的内核!
如下,是环境参数:

--------下面涉及到一般执行一个内核镜像的四个步骤-------
第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定种类,第二步对镜像进行校验,第三步再次读取头信息,由头信息的特定地址知道这个镜像的各种信息,包括长度,种类,入口地址等等,第四步就是去entrypoint处开始执行镜像,

 第一步:如果直接在kernel底下去make uImage会提示mkimage没找到,解决方案是在 /uboot/tool下去复制到 /usr/local/bin.下面去,复制它到系统目录下,再去执行make uImage就可以了;

do_bootm函数一直到397行之前都是进行zImage镜像的头部信息校验。校验时就要根据不同种类的image类型进行不同的校验。所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型,然后按照这种类型的头信息格式去校验。校验通过则进入下一步准备启动内核;若果校验失败则认为镜像有问题,所以不能启动


do_bootm调用内核获取和校验


image_header_t结构体
(1)这个数据结构是我们uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造,
if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
        printf("Boot with zImage\n");
        addr = virt_to_phys(addr);
        hdr = (image_header_t *)addr;
        hdr->ih_os = IH_OS_LINUX;
        hdr->ih_ep = ntohl(addr);
        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));

        /* save pointer to image header */
        images.legacy_hdr_os = hdr;

        images.legacy_hdr_valid = 1;

这里就是在进行改造。
(2)image全局变量是在bootm函数中使用,目的是用来指向 os/initrd/fdt images,也就是用来完成启动的,zImage校验过程先确定是不是zImage,然后再修改zImage头信息,到合适,也就是上面的改造,最后再用这个头信息去初始化image,然后完成了校验。
zImage启动方式是后来添加的,而且用了goto的方式跳转了一部分代码,本身对uboot的结构上添加的。
uImage启动
(1)在uboot启动的do_boot中有一个legacy的方法,指的就是uImage这样的方式,为什么是legacy(遗留的),是因为uImage本身是uboot发明的一种启动的方式,后来这种方式是不好的,被废弃,于是被一种新的方式给替代了,新的方式就是设备树的方式,在这里被叫做fit,这个就是设备树的方式。
(2)uImage启动校验函数是在boot_get_kernel这个函数里,主要任务就是校验我们的uImage的头信息,并且得到真正的kernel的起始位置去启动。
总结:uboot本身也只支持uImage方式启动的,后来有了设备树之后,就把uImage方式命名为legacy方式,fdt方式就命名为fit方式,于是乎多了#if  #endif添加代码。后来移植的人又为了省事添加了zImage的方式,又为了省事,添加了#if  #endif .
第二阶段校验头信息结束!

 第三阶段,启动Linux内核,调用do_bootm_linux这个函数

do_bootm_linux:找到do_bootm_linux,这个函数在lib_arm/bootm.c中() ,给内核传参-image结构体

 
第四步:镜像的entrypoint:
ep就是程序入口,一个镜像的起始部分不是在镜像的开头(镜像的开头有很多字节的头信息),真正开始执行的代码在中间的某个部分,相对于头有一定偏移量的,这个偏移量放在头信息中的。
 一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定种类,第二步对镜像进行校验,第三步再次读取头信息,由头信息的特定地址知道这个镜像的各种信息,包括长度,种类,入口地址等等,第四步就是去entrypoint处开始执行镜像(此处第四步)
theKernel = (void (*)(int, int, uint))ep;将真正的入口地址赋值给thekernel,就是操作系统的第一句代码
Starting kernel ...这里打印是uboot的最后打印信息,如果这句后,串口没输出信息了,说明内核没有成功被执行,原因是传参错误(80%),内核在DDR中的加载地址。。。
如果传参正确:"Starting kernel ..."是内核启动的第0条

接下来查看给内核传参并启动内核的操作

tag方式传参
(1)struct tag,tag是一个数据结构,在uboot和linux kernel中都有定义tag数据机构,而且定义是一样的。
(2)tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。
(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
(4)tag传参的方式是由linux kernel发明的,kernel定义了这种向我传参的方式,uboot只是实现了这种传参方式从而可以支持给kernel传参。

配置传参宏
(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。
(2)CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.
(3)CONFIG_INITRD_TAG
(4)CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
(5)起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。
思考:内核如何拿到这些tag?
uboot最终是调用theKernel函数来执行linux内核的,uboot调用这个函数(其实就是linux内核)时传递了3个参数。这3个参数就是uboot直接传递给linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0中,第二个参数放在r1中,第3个参数放在r2中)第1个参数固定为0,第2个参数是机器码,第3个参数传递的就是大片传参tag的首地址。
移植时注意事项
(1)uboot移植时一般只需要配置相应的宏即可
(2)kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。

附:uboot命令的实现-命令为输入的字符串+参数[比较简单,详细可以去看韦东山的uboot视频]

输入字符串---即为命令name--->存在针对于命令的结构体

结构体

{
。name
。fun =
。。。。
}

出现对应的动作--->函数fune-
run_common()的实现先比较,若name比较正确,执行对应的fun


 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoxilang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值