学习 uboot 之四主流程main_loop分析

接着上一篇内容,本篇我们来分析main_loop的主要内容:
main_loop主要功能有两点:

1.没有进入调试模式的话,初始化一些参数,直接跳转到内核入口处执行
2.进入调试模式,执行串口输入的命令

   s = getenv ("bootcmd");
    if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
    {
            printf("Booting Linux ...\n");            
            run_command (s, 0);
     }

    }

上述代码从环境变量中获取bootcmd的命令,如下

    #define CONFIG_BOOTCOMMAND  "nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0"

这里我们分析下bootm命令,从cmd_bootm.c中我们能够找到bootm命令对应的函数为do_bootm。
下面重点分析下这个函数。

1.do_bootm分析

1.获取内核版本在RAM中的位置,并取出前64个字节的uImage版本头

    if (argc < 2) { 
        addr = load_addr;
    } else {
        /*获取uImage版本在内存中的地址*/
        addr = simple_strtoul(argv[1], NULL, 16);/*bootm 0x30007FC0,argv[1]代表kernel开始的地址*/
    }

    SHOW_BOOT_PROGRESS (1);
    printf ("## Booting image at %08lx ...\n", addr);

    /* Copy header so we can blank CRC field for re-calculation */
    memmove (&header, (char *)addr, sizeof(image_header_t));/*直接得到头部*/

2.根据得到的版本头信息做一些校验,主要包括版本魔数,CRC等

if (ntohl(hdr->ih_magic) != IH_MAGIC) { /*版本魔数不对*/
    {
    puts ("Bad Magic Number\n");
    SHOW_BOOT_PROGRESS (-1);
    return 1;
    }
}


data = (ulong)&header;/*内核地址*/
len  = sizeof(image_header_t);/*版本头部*/

checksum = ntohl(hdr->ih_hcrc);/*网络序到主机序--计算版本头部的CRC*/
hdr->ih_hcrc = 0;

/*计算CRC校验码并判断*/
if (crc32 (0, (uchar *)data, len) != checksum) {
    puts ("Bad Header Checksum\n");
    SHOW_BOOT_PROGRESS (-2);
    return 1;
}



data = addr + sizeof(image_header_t);/*计算正式版本的地址*/
len  = ntohl(hdr->ih_size);          /*得到版本的大小*/

/*计算版本的CRC*/
if (verify) {
    puts ("   Verifying Checksum ... ");
    if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
        printf ("Bad Data CRC\n");
        SHOW_BOOT_PROGRESS (-3);
        return 1;
    }
    puts ("OK\n");
}

3.根据生成内核时采用的压缩方式解压内核


    switch (hdr->ih_comp) {
    .
    ......
    .
    case IH_COMP_GZIP:/*如果是压缩方式GZIP,解压版本*/
        printf ("   Uncompressing %s ... ", name);
        if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
                (uchar *)data, &len) != 0) {
            puts ("GUNZIP ERROR - must RESET board to recover\n");
            SHOW_BOOT_PROGRESS (-6);
            do_reset (cmdtp, flag, argc, argv);
        }
        break;

4.下面是跳转到内核入口的处理函数,根据os类型,本篇使用的linux,do_bootm_linux定义在lib_arm目录下的armlinux.c

switch (hdr->ih_os) {
    default:            /* handled by (original) Linux case */
    case IH_OS_LINUX:
        do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);/*addr 代表LOAD地址,len_ptr 版本数据正式开始的地方*/

2.do_bootm_linux分析

1.启动参数获取,内核入口函数定义


    void (*theKernel)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif

    /*得到内核的入口地址*/
    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

上面获取bootargs环境变量,定义内核入口函数theKernel,函数的地址在版本头ih_ep中指明,值得注意的是参数为int,int,uint最后一个参数并不是指针类型

2.设置标记列表,内核传递参数


#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD)

    setup_start_tag (bd);

    /*设置标记列表中的参数*/
#ifdef CONFIG_SETUP_MEMORY_TAGS
    setup_memory_tags (bd);
#endif

#ifdef CONFIG_CMDLINE_TAG
    setup_commandline_tag (bd, commandline);
#endif

    setup_end_tag (bd);
#endif

目前boot向内核传递参数的常用方法便是标记列表法:
每个标记列表的结构体如下:

struct tag {
    struct tag_header hdr;
    union {
    /*这里定义联合体,分别可以是tag_mem32,cmd_line*/
        struct tag_core     core;
        struct tag_mem32    mem;
        ......
        struct tag_cmdline  cmdline;
        ......
    } u;
};

struct tag_header {
    u32 size; /*整个tag的大小*/
    u32 tag;  /*tag的类型*/
};

整个标记列表以ATAG_CORE开始,以ATAG_NONE结束,中间放入我们需要传递的参数


    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);


    /**/
    params->hdr.tag = ATAG_NONE;
    params->hdr.size = 0;

这里分析下setup_memory_tags参数,这个参数用于向内核传递RAM的起始地址和大小

    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);
    }

CONFIG_NR_DRAM_BANKS为DRAM数目,每一个DRAM要单独定义一个TAG

紧接着调用内核入口函数

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

其中bd->bi_arch_number为ARCH_ID 在board_init中说明。至此uboot完成其任务,将控制权交给内核,退出了“”舞台”。后面几篇我会再补充一些uboot钟用到的关键知识点,完善uboot的体系

你可能感兴趣的:(uboot,C语言)