06-uboot分析之uboot启动内核

分析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 (&params);
#endif
#ifdef CONFIG_REVISION_TAG
    setup_revision_tag (&params);
#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入口地址执行
    
   

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值