前言:
本帖是学习韦东山老师uboot教程,粗略总结的课堂笔记及自己感悟。如想深入了解uboot 启动过程,请绕路。
相关帖子:
概述
uboot第一阶段在汇编代码中进行了硬件初始化(看门狗、中断、堆栈)
第二阶段主要是C代码进行硬件初始化、设置内核启动参数、将内核加载到内存(SDRAM)并启动内核。
注意:uboot的目的就是启动内核。其他的初始化,参数设置都是辅助启动内核的。这就是唯物辩证法的认识现象和本质。
uboot启动第一阶段 中最后会调用start_armboot()接口进行第二阶段工作。
1- start_armboot()
start_armboot主要将内核从flash读取到内存(SDRAM, 64M)中,并启动内核。
这里至少涉及flash(NOR/NAND FLASH)初始化,读取等操作,当然也包括一些参数获取。
之后进入死循环接口main_loop ()。
/*NOR FLASH初始化*/
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
#endif /* CFG_NO_FLASH */
/*Nand FLASH初始化*/
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
/*死循环*/
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
2- main_loop()
进行一些命令行的检测,或者直接进行启动内核。
场景1: 没有按下空格键,直接启动内核
对单板上电后,如果在倒数前没有按下空格键,则直接启动内核run_command (s, 0);
/*获取bootcmd参数 启动内核 */
s = getenv ("bootcmd");
if (bootdelay >= 0 && s && !abortboot (bootdelay)
{
printf("Booting Linux ...\n");
run_command (s, 0);
}
abortboot()接口判断有无按键按下,并打印倒数计时
Hit any key to stop autoboot: 0
场景2: 倒数计时前按下空格键,运行厂商的菜单栏
如果我们在倒数计结束前前按下空格键,运行下面语句,进入厂商编写的菜单应用程序。
run_command("menu", 0);
菜单程序的入口为:
void menu_shell(void):
...
case 'b':
{
printf("Booting Linux ...\n");
strcpy(cmd_buf, "nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0");
run_command(cmd_buf, 0);
break;
}
case 'k':
{
strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase kernel; nand write.jffs2 0x30000000 kernel $(filesize)");
run_command(cmd_buf, 0);
break;
}
...
菜单程序主要是为简化初学者的操作,将一系列命令集合在一个命令中。比如我们输入k,低层的uboot命令为:
usbslave 1 0x30000000; nand erase kernel; nand write.jffs2 0x30000000 kernel $(filesize)
场景3:uboot菜单模式中输入q,进入到uboot命令行模式
在uboot命令行中,输入help可以查看uboot支持的命令:
OpenJTAG> help
? - alias for 'help'
autoscr - run script from memory
base - print or set address offset
bdinfo - print Board Info structure
boot - boot default, i.e., run 'bootcmd'
...
假设我们在nand flash上已经下载了内核,我们可以在命令行中输入命令,手动将内核加载到SDRAM中。
nand read.jffs2 0x30007FC0 kernel
bootm 0x30007FC0
或者,先查看nand flash的分区情况,使用 nand read.jffs2 SDRAM存放地址 KERNEL分区偏移地址 KERNEL分区大小方式,加载内核。
#查看分区情况。 可以看到kernel分区偏移地址为0x00060000,分区大小为0x00200000
#mtd
device nand0 <nandflash0>, # parts = 4
#: name size offset mask_flags
0: bootloader 0x00040000 0x00000000 0
1: params 0x00020000 0x00040000 0
2: kernel 0x00200000 0x00060000 0
3: root 0x0fda0000 0x00260000 0
#将kernel分区内容写道内存0x30007FC0
#nand read.jffs2 0x30007FC0 0x00060000 0x00200000
#加载0x30007FC0地址数据
bootm 0x30007FC0
正常情况下,我们在倒数计时前没有按空格键,则读取bootcmd信息。
在uboot 命令行模式下输入print,可以看到bootcmd信息:
OpenJTAG> print
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200
bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
bootdelay=2
baudrate=115200
ethaddr=08:00:3e:26:0a:5b
ipaddr=192.168.1.17
serverip=192.168.1.11
netmask=255.255.255.0
stdin=serial
stdout=serial
stderr=serial
mtdids=nand0=nandflash0
mtdparts=mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root)
partition=nand0,0
mtddevnum=0
mtddevname=bootloader
Environment size: 450/131068 bytes
#从flash 将kernel分区内容加载到SDRAM 0x30007FC0位置
bootcmd=nand read.jffs2 0x30007FC0 kernel;
#从0x30007FC0地址启动内核
bootm 0x30007FC0
3- do_bootm()
bootm命令对应的接口为:do_bootm().
我们执行bootm 0x30007FC0,给do_bootm 传入内核在SDRAM上的加载地址
我们使用的内核为UImage=头部+真正内核。
3.1- 解析头部信息
头部参数结构体为,大小为64M:
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;
我们在do_bootm中解析UImage头部信息,根据头部信息进行不同处理。
我们在bootm传入的地址为0x30007FC0,头部大小为64byte,所以计算出内核的加载地址为:
image_header->ih_load = 0x30007FC0 + 64 = 0x3000 8000
解析到我们的image类型为linux,执行do_bootm_linux()
/*获取头部信息 */
image_header_t *hdr = &header;
memmove (&header, (char *)addr, sizeof(image_header_t));
/*解析头部信息*/
if (ntohl(hdr->ih_magic) != IH_MAGIC)
...
/*压缩方式*/
switch (hdr->ih_comp)
{
case IH_COMP_NONE:
/* 如果头部大小 + bootm传入地址 = hdr->ih_load,则将内核移动到hdr->ih_load地址处*/
if(ntohl(hdr->ih_load) == data) {
/*不移动内核*/
} else {
/*移动内核到hdr->ih_load*/
}
}
...
/*image类型*/
switch (hdr->ih_type)
{
case IH_OS_LINUX:
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
}
3.2- do_bootm_linux()
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);用于启动内核,启动内核时给内核传入了3个参数,第二个参数为arch_number,第三个参数为boot参数,参数设置在3.3中介绍。
/* 内核入口 */
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
/* 内核传递参数 */
setup_start_tag (bd);
setup_revision_tag (¶ms);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_end_tag (bd);
/*启动内核*/
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
3.3- uboot给内核传参
uboot将内核加载到SDRAM后,在启动内核前,需要给内核传递一些参数。我们将参数按照结构体struct tag形式保存在SDRAM指定地址处,等开始加载内核后从SDRAM指定地址按照struct tag解析处参数。
参数存放地址:
/* \board\100ask24x0\100ask24x0.c */
gd->bd->bi_boot_params = 0x30000100;
/* \lib_arm\armlinux.c */
params = (struct tag *) bd->bi_boot_params;
参数存放结构体:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
设置的参数有:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
static void setup_memory_tags (bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
}
}
static void setup_commandline_tag (bd_t *bd, char *commandline)
{
char *p;
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
结合初识uboot Makefile、uboot启动第一阶段 中对SDRAM的地址规划,我们可以画出SDRAM的存放内容如下:
书里画的如下: