分析uboot启动内核命令
从uboot的终端执行print命令可以看到以下命令
bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
这就是读出内核,启动内核的命令
nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
从哪里读: 从nandflash 的kernel分区读出内核, kernel分区其实也只是一个地址而已
读到哪里: 读到0x30007FC0
分区:
对应嵌入式Linux来说Flash没有分区表, 既然没有分区表, 所谓的boot,parms,kernel,boot分区只能在Flash里面写死分区表
jffs2:
read.jffs2 使用jffs2的读地址不需要页对齐
uboot终端执行help nand查看nand的用法
help nand
...
nand read[.jffs2] - addr off|partition size
...
分析了命令xxx的执行有一个do_xxx的执行函数,这里nand应该也有一个do_nand函数去执行nand的读写操作
查看cmd_nand.c
int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
...
//
if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
int read;
if (argc < 4)
goto usage;
addr = (ulong)simple_strtoul(argv[2], NULL, 16);
read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
printf("\nNAND %s: ", read ? "read" : "write");
if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
return 1;
s = strchr(cmd, '.');
if (s != NULL &&
(!strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i"))) {
if (read) {
/* read */
nand_read_options_t opts;
memset(&opts, 0, sizeof(opts));
opts.buffer = (u_char*) addr;
opts.length = size;
opts.offset = off;
opts.quiet = quiet;
ret = nand_read_opts(nand, &opts); // 最终调到这里
}
...
}
....
printf(" %d bytes %s: %s\n", size,
read ? "read" : "written", ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
}
下面分析启动内核:
bootm 0x30007FC0
Flash上存的内核: uImage
uImage: 头部 + 真正格式
/*
* all data in network byte order (aka natural aka bigendian)
*/
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 入口地址, 要运行内核直接跳到ep就可以 */
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;
uImage里的头部包含记载地址和入口地址等信息, bootm 0x30007FC0会从0x30007FC0地址先读出头部,
得到内核加载地址和入口地址, 如果发现真正的内核不在加载地址上的话, 先将内核移动到加载地址,
然后跳到入口地址执行
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
image_header_t *hdr = &header;
...
memmove (&header, (char *)addr, sizeof(image_header_t)); // 首先读出uImage的头部, 拿到image_header_t结构
...
data = addr + sizeof(image_header_t);
...
if(ntohl(hdr->ih_load) == data) { // 判断加载地址是不是真正内核所在地址
printf (" XIP %s ... ", name);
} else {
...
printf (" Loading %s ... ", name);
...
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); // 移动内核到加载地址
}
...
do_bootm_linux (cmdtp, flag, argc, argv, addr, len_ptr, verify); // 启动内核
}
可以看出bootm做了以下事情:
1. 读取uImage头部, 判断头部结构image_header_t中的的加载地址ih_load是不是真正内核所在地址, 如果真正的内核不在加载地址上的话, 将内核memmove移动到加载地址,
2. 调用do_bootm_linux跳到入口地址执行
在启动kernel之前uboot需要告诉内核一些启动参数才能启动, 通过do_bootm_linux传递参数给内核, 但是当跳到内核运行时uboot已经不起作用了怎么传参数,
uboot是按照某种约定数据格式(称为TAG)把要传给kernel的参数写在某个约定地址, 当kernel启动后就去约定的地址读取参数.
下面分析do_bootm_linux:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
...
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
bd_t *bd = gd->bd;
...
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); // theKernel指向kernel入口地址
...
// 设置参数到某个约定的地址
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
...
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); // 跳到kernel入口地址执行, 一去不复返, 控制权交给kernel
// 第一个固定是0,;第二个是机器码;第三个就是tag的存放的起始地址
}
/***********************************************************************************/
一些关于uboot传参给kernel的概念:
uboot向kernel传参的实现就是uboot和kernel规定一种传参的数据格式(tag),
然后uboot将需要传递给kernel的参数存放到SDRAM的某段内存中,
然后将这段内存的起始地址传递给kernel,
然后kernel通过这个地址来读取传递的参数。
什么是tag???
1. tag是一种数据结构,用于uboot向内核传递参数,一个tag代表了一个参数,
uboot在向kernel传参时,就是通过一个个tag来实现的。
2. 每个tag都有一个tag_header(一个结构体),包含了tag的类型和大小,
然后tag中剩余的部分tag_xxx来当作传参的tag
struct tag_header {
u32 size;
u32 tag;
};
kernel如何知道tag的起始地址???
uboot启动内核的最后通过
theKernel (0, machid, bd->bi_boot_params);来启动内核,
这三个参数,第一个固定是0,;第二个是机器码;第三个就是tag的存放的起始地址,
也就是setup_start_tag。这三个参数分别存放在寄存器r0 r1 r2中。
kernel如何知道tag的结束地址???
在tag传参的过程中,有一个setup_start_tag(ATAG_CORE类型)和setup_end_tag(AATAG_NONE类型),
setup_start_tag表示tag开始传参,
setup_end_tag 表示传参的结束,
这两个tag之间的tag就是实际的向内核传递的tag。
设置参数到某个约定的地址, 这个约定的地址就是启动参数的起始地址,
在board/100ask24x0/100ask24x0.c文件的board_init函数中设置
int board_init (void)
{
...
/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100; // 这个就是TAG的起始地址, 也就是启动参数的起始地址
}
/***********************************************************************************/
以下是一些关于tag的结构:
struct tag_header {
u32 size; // tag参数个数
u32 tag; // tag地址
};
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM 0x54410002
struct tag_mem32 {
u32 size;
u32 start; /* physical start address */
};
#define ATAG_CMDLINE 0x54410009
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
#define ATAG_NONE 0x00000000
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
/***********************************************************************************/
下面分析do_bootm_linux函数时如何设置内核启动参数的:
setup_start_tag (bd);
-> params = (struct tag *) bd->bi_boot_params; // params = 0x30000100
-> params->hdr.tag = ATAG_CORE; // params->hdr.tag = 0x54410001
-> params->hdr.size = tag_size (tag_core); // params->hdr.size = (8 + 12) >> 2 = 5 //tag参数个数
-> params->u.core.flags = 0;
-> params->u.core.pagesize = 0;
-> params->u.core.rootdev = 0;
-> params = tag_next (params); // params指向下一个tag起始地址
...
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
-> params->hdr.tag = ATAG_MEM; // params->hdr.tag = 0x54410002
-> params->hdr.size = tag_size (tag_mem32); // params->hdr.size = (8 + 8) >> 2 = 4 //tag参数个数
-> params->u.mem.start = bd->bi_dram[i].start; // sdram起始地址, params->u.mem.start = 0x30000000;
-> params->u.mem.size = bd->bi_dram[i].size; // sdarm大小64M, params->u.mem.size = 64*1024*1024;
-> params = tag_next (params); // params指向下一个tag起始地址
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline); //
-> pparams->hdr.tag = ATAG_CMDLINE; // params->hdr.tag = 0x54410009
-> pparams->hdr.size = (sizeof (struct tag_header) + strlen (commandline) + 1 + 4) >> 2; // params->hdr.size = (8+X+1+4)/4
-> strlen (commandline)得到命令命令行的长度
-> strcpy (params->u.cmdline.cmdline, commandline); //将命令行拷贝进结构体存储
#endif -> params = tag_next (params); // params指向下一个tag起始地址
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
-> params->hdr.tag = ATAG_NONE; // params->hdr.tag = 0x00000000 tag结束标记
-> params->hdr.size = 0;
#endif
可以得到启动参数存储结构:
-------------------------
| 内核 |
| |
-------------------------0x300080000
| . |
| . |
| . |
| . |
-------------------------
hdr.tag | 0x00000000 |
-------------------------
hdr.size | 0 |
------------------------- params = tag_next (params); // end_tag
cmdline | 命令行数据 |
-------------------------
hdr.tag | 0x54410009 |
-------------------------
hdr.size | (8+strlen+1+4)/4 | 命令行长度
------------------------- params = tag_next (params); // commandline_tag
| 其他memory_tag |
-------------------------
u.mem.start | 0x30000000 |
-------------------------
u.mem.size | 64*1024*1024 |
-------------------------
hdr.tag | 0x54410002 |
-------------------------
hdr.size | 4 |
------------------------- params = tag_next (params); // memory_tags
| 0 |
-------------------------
| 0 |
-------------------------
| 0 |
-------------------------
hdr.tag | 0x54410001 | tag
-------------------------
hdr.size | 5 | tag参数个数
------------------------- 0x30000100 tag起始地址 start_tag
| . |
| . |
------------------------- 0x30000000 sdram起始地址
uboot: 最终目的启动内核
1. 从Flash读出内核
2. 启动
而启动之前需要设置参数给kernel然后跳到kernel入口地址执行
06-uboot分析之uboot启动内核
最新推荐文章于 2023-11-09 18:31:38 发布