u_boot 分析

一、makefile文件分析

u-boot-2014.07-6818中的README可知,u_boot需要先配置后make

(注意:通常都会有一个README文档)

 1、配置命令:make fs6818_config

由顶层Makefile可找到如下命令:

1. 打开u-boot源码顶层目录的Makefile 

vi Makefile

2. 搜索fs6818_config目标

通多部分匹配的方式搜索 _config,

得到以下信息:

467 %_config:: outputmakefile

468 @$(MKCONFIG) -A $(@:_config=)

解析:

%:模式通配符

第一个@:后边的命令不回显到ubuntu终端

@:_config=:将fs6818_config中的_config干掉

如何保留fs6818:

去掉第一个@符,重新执行make fs6818_config

/home/hq/bootloader/u-boot-2014.07-6818/mkconfig -A fs6818

2、mkconfig文件分析

下面是Makefile中关于ARM的部分

vi Makefile

将一下内容:

198 ifeq ($(HOSTARCH),$(ARCH))

199 CROSS_COMPILE ?=

200 endif

修改为:

198 ifeq (arm,arm)(里面的两个相等即可,写arm的原因是我们用的是arm)

199 CROSS_COMPILE ?= arm-none-linux-gnueabi- (后面不要写上空格) 

200 endif

3、这些文件怎么组织成u_boot?

看链接脚本u-boot.lds (此处参考实际文件注释,u-boot.lds:链接脚本(指导程序如何排布的)

其中". = 0x00000000"表示将当前位置设置为内存地址0x00000000,". = ALIGN(4)"表示将当前位置向后移动到下一个4字节对齐的地址。

由u-boot.lds文件可知

第一个运行的文件是start.o,然后开始分析arch/arm/cpu/slsiap/s5p6818/start.S文件,到此makefile分析完毕。

4、结论

cd arch/arm/cpu/slsiap/s5p6818/

第一个文件是:arch/arm/cpu/slsiap/s5p6818/start.S  

  • 二、start.s分析

功能描述

第一阶段

设置为SVC模式、关看门狗、屏蔽中断、初始化SDRAM、设置栈和时钟、代码从flash到RAM、清BSS段、调用start_armboot(C函数)

补充命令:grep "函数名" -nR * :递归查询所有

          Find ./ -name "文件名"

  1. 构建异常向量表

ARM 参考手册中描述的异常向量

  1. 设置为SVC模式、关看门狗

reset函数:

鼠标放到lowlevel_init,进行ctrl+]进行跳转

清BSS段

鼠标放在board_init_r,按ctrl+]进行跳转

跳转到arch/arm/lib/board.c 文件中

void board_init_f(ulong bootflag){}

板子的的结构体gd(global data)的初始化,gb结构体用于存储全局信息的结构体。

回到start.S继续向下执行,直到 ldr pc, =board_init_r 

board_init_r函数完成了大部分的硬件初始化(串口、内存、flash、cache等)

最终进入死循环

for(;;)

{

main_loop;

}

第二阶段

调用board.c\start_armboot开始

首先要知道u_boot的目标:从flash读出内核然后启动

分析代码board.c(在~/bootloader/u-boot-2014.07-6818/arch/arm/lib):

要想读出内核,必须支持flash

561行和588行,分别调用了

flash_init()nand_init()对nor和nand初始化

接下来是617行环境变量函数env_relocate ()

在u_boot里面输入print命令会出现一大堆环境变量,用来设置波特率等

环境变量来源于:1.默认的  2.flash上保存的,启动时先看flash上有没有,如果没有,用默认的。

直到函数中死循环调用main_loop ()函数,转到文件main.c看此函数,

main_loop()函数中。若在bootdelay倒计时为0之前,U-Boot控制台有输入,则进入命令解析-执行的循环;若控制台无输入,U-Boot将启动内核。 

 main_loop()函数调用

autoboot_command()函数讲解

判断键盘是否有按下,也就是是否打断了倒计时,如果键盘按下的话就执行相应的分支。

run_command_list,此函数会执行参数 s 指定的一系列命令,也就是环境变量 bootcmd 的命令,

bootcmd 里面保存着默认的启动命令,因此 linux 内核启动!这个就是 uboot 中倒计时结束以后

自动启动 linux 内核的原理。

如果倒计时结束之前按下了键盘上的按键,那么 run_command_list

函数就不会执行,相当于 autoboot_command 是个空函数。

回到 main_loop 函数中,如果倒计时结束之前按下按键,那么就会执行

cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令。

run_command_list (s, -1,0)执行bootcmd命令启动内核;

所以读出和启动内核取决于s命令,即bootcmd

从环境变量可以看出bootcmd(/uboot/include/configs/fs6818.h)

#define CONFIG_BOOTCOMMAND "ext4load mmc 2:1 0x48000000 uImage;ext4load mmc 2:1 0x49000000 root.img.gz;bootm 0x48000000"

内核格式有两类:zImage和uImage。

并不是所有U-Boot都支持zImage,是否支持就看其配置文件(fs6818.h没定义CONFIG_ZIMAGE_BOOT)中是否定义CONFIG_ZIMAGE_BOOT这个宏。所以有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动

ulmage格式的内核头部信息在/uboot/include/Image.h中定义。

ih_load是加载地址,即内核在DDR中的地址(运行地址);ih_ep是内核入口地址。

复制代码

typedef struct image_header {

uint32_t    ih_magic;    /* Image Header Magic Number    */

uint32_t    ih_hcrc;    /* Image Header CRC Checksum    */

uint32_t    ih_time;    /* Image Creation Timestamp    */

uint32_t    ih_size;    /* Image Data Size    */

uint32_t    ih_load;    /* Data     Load  Address    */

uint32_t    ih_ep;    /* Entry Point Address    */

uint32_t    ih_dcrc;    /* Image Data CRC Checksum    */

uint8_t    ih_os;    /* Operating System    */

uint8_t    ih_arch;    /* CPU architecture    */

uint8_t    ih_type;    /* Image Type    */

uint8_t    ih_comp;    /* Compression Type    */

uint8_t ih_name[IH_NMLEN];    /* Image Name    */

} image_header_t;

总结

1)将内核搬移至DDR中;

2)校验内核格式、CRC;

3)准备传参;

4)跳转执行内核

三、run_command分析

可以想象,命令是一个结构体,{name,fun()},开始分析run_command,在main.c 1280行处为此函数实现。

直接跳到1325行看,1355行是解析命令,比如输入命令是md.w  0,则解析为argv[0]=”md.w”,

argv[1]=”0”。argv[0]放的是命令,argv[1]放的是参数。分析1361行如下代码:

if ((cmdtp = find_cmd(argv[0])) == NULL) {

      printf ("Unknown command '%s' - try 'help'\n", argv[0]);

      rc = -1;  /* give up after bad command */

      continue;}

如何查找命令呢?cmdtp是一个结构体,看具体代码(位于include\command.h第39行)。

find_cmd(argv[0])函数在common\command.c中346行,首先看360行__u_boot_cmd_start和__u_boot_cmd_end,这两个东西搜遍代码是搜不到的,它在链接脚本里,C语言中,链接脚本也可以传入值。下面分析*(.u_boot_cmd)段。在include\command.h中93行:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

输入bootm 后调用哪个函数不知道,在项目里搜bootm,发现它在common\cmd_bootm.c

U_BOOT_CMD(

   bootm,  CFG_MAXARGS,  1,  do_bootm,

   "bootm   - boot application image from memory\n",

   "[addr [arg ...]]\n    - boot application image stored in memory\n"

   "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"

   "\t'arg' can be the address of an initrd image\n"

#ifdef CONFIG_OF_FLAT_TREE

  "\tWhen booting a Linux kernel which requires a flat device-tree\n"

  "\ta third argument is required which is the address of the of the\n"

  "\tdevice-tree blob. To boot that kernel without an initrd image,\n"

  "\tuse a '-' for the second argument. If you do not pass a third\n"

  "\ta bd_info struct will be passed instead\n"

#endif);

去搜索宏U_BOOT_CMD发现在include\command.h 97行:

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

把上面的宏展开得到:

cmd_tbl_t  __u_boot_cmd_bootm 

__attribute__ ((unused,section (".u_boot_cmd")))= {#name, maxargs, rep, cmd, usage, help}

从这里可以看出定义了__u_boot_cmd_bootm这样一个结构体,类型是cmd_tbl_t,

(代码是typedef struct cmd_tbl_s  cmd_tbl_t; include\command.h中93行:)

这个结构体有个属性:__attribute__,强制把段属性section设置为.u_boot_cmd(u-boot.lds中)

里面的内容是:{#name, maxargs, rep, cmd, usage help}

替换得:{bootm, CFG_MAXARGS, 1, do_bootm, "bootm   - boot application image from memory\n",     "bootm   - boot application image from memory\n","[addr [arg ...]]\n    - boot application image stored in memory\n"    "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"  "\t'arg' can be the address of an initrd image\n"

实验内容:增加一个hello命令。

在common目录下新建一个名为cmd_hello.c的文件,里面代码根据cmd_bootm.c来修改,代码如下:

#include <common.h>

#include <watchdog.h>

#include <command.h>

int do_hello(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

int i;

printf(“hello world!\n,参数的个数是%d”,argc);

for(i=0;i<=argc;i++)

printf(“参数是:%s  ”,argv[i]);

return 0;

}

另外里面还需要定义一个宏:

U_BOOT_CMD(

   hello,  CFG_MAXARGS,  1,  do_hello,

   "hello short help.....",

   "hello long help.............................................."\n

);

最后把此文件放到common目录下,修改此目录下的makefile第54行:

加上文件cmd_hello.c,然后重新make一下即可。

四、内核启动分析

根据bootm 命令执行do_bootm_states函数,658行非常重要,通过bootm_os_get_boot_func查找系统启动函数,参数images->os.os为系统类型,函数返回值是查找到的系统启动函数do_bootm_linux。处理615行BOOTM_STATE_OS_PREP状态,调用do_bootm_linux->boot_prep_linux处理环境变量bootargs,bootargs保存着传递给linux kernel的参数。

638行调用boot_selected_os,启动linux内核,第4个参数为linux镜像头,boot_os数组如下:

static boot_os_fn *boot_os[] = {

[IH_OS_U_BOOT] = do_bootm_standalone,

#ifdef CONFIG_BOOTM_LINUX

[IH_OS_LINUX] = do_bootm_linux,

#endif

#ifdef CONFIG_BOOTM_NETBSD

[IH_OS_NETBSD] = do_bootm_netbsd,

#endif

#ifdef CONFIG_LYNXKDI

[IH_OS_LYNXOS] = do_bootm_lynxkdi,

#endif

#ifdef CONFIG_BOOTM_RTEMS

[IH_OS_RTEMS] = do_bootm_rtems,

#endif

#if defined(CONFIG_BOOTM_OSE)

[IH_OS_OSE] = do_bootm_ose,

#endif

#if defined(CONFIG_BOOTM_PLAN9)

[IH_OS_PLAN9] = do_bootm_plan9,

#endif

#if defined(CONFIG_BOOTM_VXWORKS) && \

(defined(CONFIG_PPC) || defined(CONFIG_ARM))

[IH_OS_VXWORKS] = do_bootm_vxworks,

#endif

#if defined(CONFIG_CMD_ELF)

[IH_OS_QNX] = do_bootm_qnxelf,

#endif

#ifdef CONFIG_INTEGRITY

[IH_OS_INTEGRITY] = do_bootm_integrity,

#endif

};

红色函数do_bootm_linux为对应的系统启动函数。

第 295 行,函数 kernel_entry,看名字“内核_进入”,说明此函数是进入 Linux 内核的,也

就是最终的大 boos!!此函数有三个参数:zero,arch,params,第一个参数 zero 同样为 0;第

二个参数为机器 ID;第三个参数 ATAGS 或者设备树(DTB)首地址,ATAGS 是传统的方法,用

于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。

第 299 行,获取 kernel_entry 函数,函数 kernel_entry 并不是 uboot 定义的,而是 Linux 内

核定义的,Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux

内核镜像的起始地址,起始地址保存的正是 Linux 内核第一行代码!

boot_jump_linux,第 315~318 行是设置寄存器 r2 的值?

为什么要设置 r2 的值呢?Linux 内核一开始是汇编代码,因此函数 kernel_entry 就是个汇编函

数。向汇编函数传递参数要使用 r0、r1 和 r2(参数数量不超过 3 个的时候),所以 r2 寄存器就是

函数 kernel_entry 的第三个参数。

如果使用设备树的话,r2 应该是设备树的起始地址,而设备树地址保存在 images

的 ftd_addr 成员变量中。

如果不使用设备树的话,r2 应该是 uboot 传递给 Linux 的参数起始地址,也就

是环境变量 bootargs 的值,调用 kernel_entry 函数进入 Linux 内核,此行将一去不复返,uboot 的使命也就完成了。

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

满山的猴子我的腚最红

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

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

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

打赏作者

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

抵扣说明:

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

余额充值