S5PV210-uboot解析(五)-do_bootm函数分析

来源:互联网 发布:centos7 yum chrome 编辑:程序博客网 时间:2024/06/10 22:56
在main_loop函数中倒计时结束后就执行 bootcmd 命令跳转到 do_bootm函数引导内核启动。

/*
 * Legacy format image header,
 * 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       */
    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;
 
/*
 * Legacy and FIT format headers used by do_bootm() and do_bootm_<os>()
 * routines.
 */
typedef struct bootm_headers {
    /*
     * Legacy os image header, if it is a multi component image
     * then boot_get_ramdisk() and get_fdt() will attempt to get
     * data from second and third component accordingly.
     */
    image_header_t  *legacy_hdr_os;    /* image header pointer */
    image_header_t  legacy_hdr_os_copy;/* header copy */
    ulong       legacy_hdr_valid;
 
#if defined(CONFIG_FIT)
    const char*fit_uname_cfg; /* configuration node unit name */
 
    void       *fit_hdr_os;    /* os FIT image header */
    const char*fit_uname_os;  /* os subimage node unit name */
    int    fit_noffset_os; /* os subimage node offset */
 
    void       *fit_hdr_rd;    /* init ramdisk FIT image header */
    const char*fit_uname_rd;  /* init ramdisk subimage node unit name */
    int    fit_noffset_rd; /* init ramdisk subimage node offset */
 
#if defined(CONFIG_PPC)
    void       *fit_hdr_fdt;   /* FDT blob FIT image header */
    const char*fit_uname_fdt; /* FDT blob subimage node unit name */
    int    fit_noffset_fdt;/* FDT blob subimage node offset */
#endif
#endif
 
    int    verify;     /* getenv("verify")[0] != 'n' */
    struct lmb *lmb;       /* for memory mgmt */
} bootm_headers_t;

这两个结构体都是用来存储镜像的头信息的,image_header 用于 Legacy 方式启动的镜像,而bootm_headers 用于 Legacy 或 设备树(FDT)方式启动的镜像。这里只分析 Legacy 方式启动的镜像,在image_header 中需要注意这几个成员:
  uint32_t    ih_magic;  /* Image Header Magic Number    */
  uint32_t    ih_ep;     /* Entry Point Address       */

ih_magic内存储的是镜像的魔数,用来给uboot判断是什么格式的镜像(zImage、uImage等)

先看九鼎添加的这一段用zImage启动的代码:

#define LINUX_ZIMAGE_MAGIC 0x016f2818
    /* find out kernel image address */
    if (argc < 2) {
        addr = load_addr;
        debug ("*  kernel: default image load address = 0x%08lx\n",
                load_addr);
    else {
        addr = simple_strtoul(argv[1], NULL, 16);
        debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
    }
 
如果 argc<2,也就是没有传参的情况,uboot使用默认的 kernel 加载地址,如果有传参,就会使用传递的地址。load_addr 是在之前用宏定义赋值的一个unsigned long 变量。
 
    if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
        printf("Boot with zImage\n");
        addr = virt_to_phys(addr);
        hdr = (image_header_t *)addr;
        hdr->ih_os = IH_OS_LINUX;
        hdr->ih_ep = ntohl(addr);

kernel的起始地址后的36个字节,也就是第37-40字节中存储的是镜像的魔数,如果等于 LINUX_ZIMAGE_MAGIC 就说明这个镜像是 zImage 的镜像。
之后进行了一个虚拟地址到物理地址的转换,然后将addr 类型转换为 image_header_t,之后赋值,hdr->ih_os代表镜像的系统,hdr->ih_ep 代表镜像的入口(entry point)。ntohl函数是用来转换网络字节序到主机字节序的,与大小端有关,追了几层都是__开头的函数,系统调用的函数,一般不用管。
 
        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
 
        /* save pointer to image header */
        images.legacy_hdr_os = hdr;
 
        images.legacy_hdr_valid = 1;

image_header_t 的信息复制到 bootm_headers_t 中。
 
        goto after_header_check;

头信息校验完毕,跳转到 after_header_check标号执行引导代码
    }
#endif

之后再看下uboot中自带的检查镜像头信息的一部分,和九鼎添加的这段代码差不多,只是封装更好,基本都调用函数来从头信息中获取镜像的信息来完成检查的操作。

/* get kernel image header, start address and length */
os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
        &images, &os_data, &os_len);
if (os_len == 0) {
    puts ("ERROR: can't get kernel image!\n");
    return 1;
}
 
/* get image parameters */
switch (genimg_get_format (os_hdr)) {
case IMAGE_FORMAT_LEGACY:
    type = image_get_type (os_hdr);
    comp = image_get_comp (os_hdr);
    os = image_get_os (os_hdr);
 
    image_end = image_get_image_end (os_hdr);
    load_start = image_get_load (os_hdr);
    break;

---

跳转到 after_header_check 标号,Legacy方式就是一个switch语句,根据镜像的系统来进入到对应的引导内核启动的函数中。

after_header_check:
    os = hdr->ih_os;
#endif
 
    switch (os) {
    default:           /* handled by (original) Linux case */
    case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
        fixup_silent_linux();
#endif
        do_bootm_linux (cmdtp, flag, argc, argv, &images);
        break;

这里进入到 do_bootm_linux函数引导内核启动。

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
             bootm_headers_t *images)
{
    ulong   initrd_start, initrd_end;
    ulong   ep = 0;
    bd_t    *bd = gd->bd;
    char   *s;
    intmachid = bd->bi_arch_number;
    void   (*theKernel)(int zero, int arch, uint params);
    intret;
 
这里定义的几个变量,ep是镜像入口,最后会赋值给theKernel ,theKernel所在的地址就是内核启动的第一句代码,还会接受 uboot 给他传递的几个参数。

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif
 
    /* find kernel entry point */
    if (images->legacy_hdr_valid) {
        ep = image_get_ep (&images->legacy_hdr_os_copy);
#if defined(CONFIG_FIT)
    else if (images->fit_uname_os) {
        ret = fit_image_get_entry (images->fit_hdr_os,
                    images->fit_noffset_os, &ep);
        if (ret) {
            puts ("Can't get entry point property!\n");
            goto error;
        }
#endif
    else {
        puts ("Could not find kernel entry point!\n");
        goto error;
    }

这里用来找到 kernel 的入口,Legacy 方式就直接用image_get_ep 函数获取之前在 do_bootm函数中得到的ep,但是这里有点问题,我用SI找不到image_get_ep 这个函数的定义,只找得到这个函数被调用,很奇怪。后来在uboot/include/image.h 文件中找到了这个函数的定义,这个函数是用宏定义的,和U_BOOT_CMD宏类似。
#define image_get_hdr_l(f) \
    static inline uint32_t image_get_##f(image_header_t *hdr) \
    { \
        return uimage_to_cpu (hdr->ih_##f); \
    }
image_get_hdr_l (ep);
追进 uimage_to_cpu函数后发现就是 ntohl 函数,效果就是将 hdr->ih_ep 进行关于大小端的转换后返回这个值。

    theKernel = (void (*)(intint, uint))ep;

这里就是将 ep赋值给 theKernel,theKernel 将是内核启动的第一句代码的地址。
 
    s = getenv ("machid");
    if (s) {
        machid = simple_strtoul (s, NULL, 16);
        printf ("Using machid 0x%x from environment\n", machid);
    }

获取 machid,之后会作为参数传递给内核进行比对。如果不同就不能启动。
 
    ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM,
            &initrd_start, &initrd_end);
    if (ret)
        goto error;
 
这里和 ramdisk(虚拟内存盘?)有关,在函数定义处发现大部分代码和FIT有关,与Legacy方式关系有限。

    show_boot_progress (15);
 
    debug ("## Transferring control to Linux (at address %08lx) ...\n",
           (ulong) theKernel);
 
#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) || \
    defined (CONFIG_MTDPARTITION)
    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
 
#ifdef CONFIG_MTDPARTITION
    setup_mtdpartition_tag();
#endif
 
    setup_end_tag (bd);
#endif
 
这一段代码和 uboot 向内核传参有关,uboot 向内核的传参方式是 tag 传参,tag 是 linux中定义的一种数据结构,uboot也定义了相同的数据结构以便于向 kernel 传参。
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;
                 
                struct tag_mtdpart      mtdpart_info;
        } u;
};

struct tag_header {
    u32 size;
    u32 tag;
};
这个 tag 数据结构中定义了两个成员,一个是 tag_header 结构体,其中的 tag 成员用来表示有效信
息(比如 tag为 ATAG_CORE 就是起始,ATAG_NONE 就是结束,其他的 ATAG_XX 就是表示下面的
联合体中具体是哪一个结构体)。还有就是这个 tag 结构体没有定义一个具体的变量,在操作时是事
先定义的 tag* 类型的指针 params 来操作的。
这一段代码中的 setup_xxx_tag函数实现方式、作用都非常类似,都是首先给 hdr 赋值 tag参数的名称和大小,随后给联合体中写入之前存放板子参数信息的bd变量中的值。

    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");
 
#ifdef CONFIG_USB_DEVICE
    {
        extern void udc_disconnect (void);
        udc_disconnect ();
    }
#endif
 
    cleanup_before_linux ();

这个是在启动内核之前清 cache。
 
    theKernel (0, machid, bd->bi_boot_params);
    /* does not return */
    return;
 
跳转到 theKernel,附带三个参数,第一个是0,第二个是机器码,第三个是一系列 tag 结构体的首地址(即params = (struct tag *) bd->bi_boot_params;params->hdr.tag = ATAG_CORE 的结构体的地址)。在这里跳转到 theKernel后就正式进入到内核了,所以这里的注释写的 does not return,不会返回了。

error:
    do_reset (cmdtp, flag, argc, argv);
    return;
}

到这里,uboot 已经基本解析完毕,还剩下关于设备树启动方式的分析,这个以后再说吧。
回顾总结整个 uboot 启动过程,从 Makefile 开始配置编译uboot,随后从 start.S 开始启动 跳转到start_armboot 又到main_loop 最后到达 do_bootm 最终引导启动内核。这次对 uboot 代码的分析,一方面是锻炼我阅读代码的能力,另一方面是加强对整个 uboot 启动的理解,还有就是在这个过程中理解 uboot 代码的实现思路,uboot 整个代码的可移植性非常强,几乎考虑到了所有方面,大量的使用条件编译来增强其可移植性。uboot 中命令集的巧妙实现方式、对字符数组的解析、使用函数指针来跳转等技巧也使我受益匪浅。
接下来就准备开始移植 uboot 了。

原创粉丝点击