由u-boot-2014.07-6818中的README可知,u_boot需要先配置后make
(注意:通常都会有一个README文档)
由顶层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
下面是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
看链接脚本u-boot.lds (此处参考实际文件注释,u-boot.lds:链接脚本(指导程序如何排布的))
其中". = 0x00000000"表示将当前位置设置为内存地址0x00000000,". = ALIGN(4)"表示将当前位置向后移动到下一个4字节对齐的地址。
由u-boot.lds文件可知
第一个运行的文件是start.o,然后开始分析arch/arm/cpu/slsiap/s5p6818/start.S文件,到此makefile分析完毕。
cd arch/arm/cpu/slsiap/s5p6818/
第一个文件是:arch/arm/cpu/slsiap/s5p6818/start.S
设置为SVC模式、关看门狗、屏蔽中断、初始化SDRAM、设置栈和时钟、代码从flash到RAM、清BSS段、调用start_armboot(C函数)
补充命令:grep "函数名" -nR * :递归查询所有
Find ./ -name "文件名"
ARM 参考手册中描述的异常向量
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)跳转执行内核
可以想象,命令是一个结构体,{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
#include
#include
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 的使命也就完成了。