U-Boot之LINUX内核引导

sunln00 2019-12-13

基于samsung的Exynos 4412,U-Boot版本为2010.03

前面我们介绍了u-boot的第一阶段和第二阶段,今天我们来介绍u-boot引导内核,这也是u-boot的最后一个阶段,也就是说,这个阶段过后,u-boot的任务就彻底结束了,Linux内核将接管一切软硬件资源。下面开始我们的分析。

u-boot引导内核分为两个阶段

  • 将内核镜像从FLASH加载到内存
  • 启动内核

将内核镜像从FLASH加载到内存,这个目标一般是通过一个叫做do_xxx的函数实现的,xxx是存储介质的名称,比如do_emmc,do_nand等,我们这里使用的是emmc,所以按道理说应该是do_emmc,但是我看了源码,里面并没有按照这种方式来定义,而是用了do_emmcops的名称来替代,do_emmc有别的功能,至于为什么这样,管他呢。do_emmcops这个函数在cmd_mmc.c这个文件中,下面是与有关的代码

else if (strcmp(argv[1], "read") == 0) {
            int dev = simple_strtoul(argv[2], NULL, 10);
            void *addr = (void *)simple_strtoul(argv[3], NULL, 16);
            u32 cnt = simple_strtoul(argv[5], NULL, 16);
            u32 blk = simple_strtoul(argv[4], NULL, 16);
            u32 n;

            struct mmc *mmc = find_mmc_device(dev);
        
            if (!mmc)
                return 1;

            printf("\nMMC read: dev # %d, block # %d, count %d ...",
                dev, blk, cnt);

            n = mmc->block_dev.block_read(dev, blk, cnt, addr);

            /* flush cache after read */
            flush_cache((ulong)addr, cnt * 512); /* FIXME */

            printf("%d blocks read: %s\n",
                n, (n==cnt) ? "OK" : "ERROR");
            return (n == cnt) ? 0 : 1;

addr是由我们传递的argv[3]解析得到的,mmc->block_dev.block_read把对应的内核镜像从emmc读到addr处,

U_BOOT_CMD(
    mmc, 6, 1, do_mmcops,
    "MMC sub system",
    "read <device num> addr blk# cnt\n"
    "mmc write <device num> addr blk# cnt\n"
    "mmc rescan <device num>\n"
    "mmc erase <boot | user> <device num> <start block> <block count>\n"
    "mmc list - lists available devices");

除此之外,这个函数还支持其它功能,如写入、擦除、扫描等,通过传入的参数来进行功能选择

启动内核,在上一步中, 内核镜像已经被加载到了内存中,但这个内核镜像还不一定能用,因为它还没被解压缩,也有可能不在正确的内存地址上,所以,do_bootm的首要工作是将内核解压缩并加载到正确的内存地址上,这些功能主要在bootm_load_os这个函数中完成

ret = bootm_load_os(images.os, &load_end, 1);

当内核准备好之后,我们就可以启动内核了,这个目标主要是通过do_bootm_linux这个函数来完成

/* boot the linux kernel */
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t * images)
{
    char *bootargs;
    ulong initrd_start, initrd_end;
    ulong rd_len;
    unsigned int data, len, checksum;
    unsigned int initrd_addr, kernend;
    void (*kernel) (struct linux_romvec *, void *);
    struct lmb *lmb = &images->lmb;
    int ret;

    if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
        return 1;

    /* Get virtual address of kernel start */
    linux_hdr = (void *)images->os.load;

    /* */
    kernel = (void (*)(struct linux_romvec *, void *))images->ep;

    /* check for a SPARC kernel */
    if ((linux_hdr->hdr[0] != 'H') ||
        (linux_hdr->hdr[1] != 'd') ||
        (linux_hdr->hdr[2] != 'r') || (linux_hdr->hdr[3] != 'S')) {
        puts("Error reading header of SPARC Linux kernel, aborting\n");
        goto error;
    }
#ifdef PRINT_KERNEL_HEADER
    printf("## Found SPARC Linux kernel %d.%d.%d ...\n",
           linux_hdr->linuxver_major,
           linux_hdr->linuxver_minor, linux_hdr->linuxver_revision);
#endif

#ifdef CONFIG_USB_UHCI
    usb_lowlevel_stop();
#endif

    /* set basic boot params in kernel header now that it has been
     * extracted and is writeable.
     */

    /* Calc length of RAM disk, if zero no ramdisk available */
    rd_len = images->rd_end - images->rd_start;

    if (rd_len) {
        ret = boot_ramdisk_high(lmb, images->rd_start, rd_len,
                    &initrd_start, &initrd_end);
        if (ret) {
            puts("### Failed to relocate RAM disk\n");
            goto error;
        }

        /* Update SPARC kernel header so that Linux knows
         * what is going on and where to find RAM disk.
         *
         * Set INITRD Image address relative to RAM Start
         */
        linux_hdr->hdr_input.ver_0203.sparc_ramdisk_image =
            initrd_start - CONFIG_SYS_RAM_BASE;
        linux_hdr->hdr_input.ver_0203.sparc_ramdisk_size = rd_len;
        /* Clear READ ONLY flag if set to non-zero */
        linux_hdr->hdr_input.ver_0203.root_flags = 1;
        /* Set root device to: Root_RAM0 */
        linux_hdr->hdr_input.ver_0203.root_dev = 0x100;
        linux_hdr->hdr_input.ver_0203.ram_flags = 0;
    } else {
        /* NOT using RAMDISK image, overwriting kernel defaults */
        linux_hdr->hdr_input.ver_0203.sparc_ramdisk_image = 0;
        linux_hdr->hdr_input.ver_0203.sparc_ramdisk_size = 0;
        /* Leave to kernel defaults
           linux_hdr->hdr_input.ver_0203.root_flags = 1;
           linux_hdr->hdr_input.ver_0203.root_dev = 0;
           linux_hdr->hdr_input.ver_0203.ram_flags = 0;
         */
    }

    /* Copy bootargs from bootargs variable to kernel readable area */
    bootargs = getenv("bootargs");
    prepare_bootargs(bootargs);

    /* turn on mmu & setup context table & page table for process 0 (kernel) */
    srmmu_init_cpu((unsigned int)kernel);

    /* Enter SPARC Linux kernel
     * From now on the only code in u-boot that will be
     * executed is the PROM code.
     */
    kernel(kernel_arg_promvec, (void *)images->ep);

    /* It will never come to this... */
    while (1) ;

      error:
    return 1;
}

这个函数主要完成了两个功能:传递参数给Linux内核跳转到linux入口地址

到这里,u-boot的任务就彻底完成了,下面就该Linux上场了

注:这里有很多细节没有介绍,但我对于这种工具性知识的学习一向讲求先整体、后细节。

如果有什么不对的地方,请指正,万分感谢。

相关推荐