U-BOOT启动kernel的过程

来源:互联网 发布:java上传图片到ftp 编辑:程序博客网 时间:2024/06/09 04:25

u-boot是一种bootloader,它其实就是一段单机程序,在系统上电时自动执行,初始化硬件设备,准备好软件环境,就是为了达到其终极目的——启动内核。
本文记录了以u-boot启动运行在ARM上的Linux为例,拷贝内核镜像文件到SDRAM后,调用do_bootm的过程。话不多说,先上软件流程图:
这里写图片描述

一、内核镜像文件的检查

内核镜像文件拷贝到SDRAM上之后,需要对镜像文件进行检查,包括Image Magic Number,镜像文件头CRC,内核内容CRC,是否支持当前的CPU,是否需要解压,将内核内容拷贝到内核启动地址。这一些列的操作都是通过common/cmd_bootm.c中的do_bootm()函数来实现的。
@cmd_tbl_t *cmdtp: do_bootm的命令结构体指针
@argc: 参数个数,以”bootm 0x30007FC0”为例,argc = 2
@argv:存放参数,argv[0] = “bootm”, argv[1] = “0x30007FC0”

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){    ulong   iflag;    ulong   addr;    ulong   data, len, checksum;    ulong  *len_ptr;    uint    unc_len = CFG_BOOTM_LEN;    int i, verify;    char    *name, *s;    int (*appl)(int, char *[]);    image_header_t *hdr = &header;    // 检索环境变量"verify",若检索成功则verify = 0,否则为1    s = getenv ("verify");    verify = (s && (*s == 'n')) ? 0 : 1;    // 若参数个数小于2,表明没有传入内核镜像加载地址,则使用默认的加载地址load_addr    if (argc < 2) {        addr = load_addr;    } else {        addr = simple_strtoul(argv[1], NULL, 16);    }    SHOW_BOOT_PROGRESS (1);    printf ("## Booting image at %08lx ...\n", addr);    // 拷贝镜像文件的文件头到header。    memmove (&header, (char *)addr, sizeof(image_header_t));    // check the Image Magic Number    if (ntohl(hdr->ih_magic) != IH_MAGIC) {        {        puts ("Bad Magic Number\n");        SHOW_BOOT_PROGRESS (-1);        return 1;        }    }    SHOW_BOOT_PROGRESS (2);    // 将header的地址值赋给data,长度赋给len    data = (ulong)&header;    len  = sizeof(image_header_t);    // 读取镜像文件头hcrc的值    checksum = ntohl(hdr->ih_hcrc);    hdr->ih_hcrc = 0;    // 对镜像文件头做CRC校验。    if (crc32 (0, (uchar *)data, len) != checksum) {        puts ("Bad Header Checksum\n");        SHOW_BOOT_PROGRESS (-2);        return 1;    }    SHOW_BOOT_PROGRESS (3);    /* for multi-file images we need the data part, too */    // 显示镜像文件头信息。    print_image_hdr ((image_header_t *)addr);    // 计算内核的入口地址值,赋给data,内核的大小赋给len    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");    }    SHOW_BOOT_PROGRESS (4);    // len_ptr指向内核的入口地址。    len_ptr = (ulong *)data;    if (hdr->ih_arch != IH_CPU_ARM)    {        printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);        SHOW_BOOT_PROGRESS (-4);        return 1;    }    SHOW_BOOT_PROGRESS (5);    // 获取镜像文件的类型,若是内核则 name = "Kernel Image";    switch (hdr->ih_type) {    case IH_TYPE_STANDALONE:        name = "Standalone Application";        /* A second argument overwrites the load address */        if (argc > 2) {            hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));        }        break;    case IH_TYPE_KERNEL:        name = "Kernel Image";        break;    case IH_TYPE_MULTI:        name = "Multi-File Image";        len  = ntohl(len_ptr[0]);        /* OS kernel is always the first image */        data += 8; /* kernel_len + terminator */        for (i=1; len_ptr[i]; ++i)            data += 4;        break;    default: printf ("Wrong Image Type for %s command\n", cmdtp->name);        SHOW_BOOT_PROGRESS (-5);        return 1;    }    SHOW_BOOT_PROGRESS (6);    /*     * We have reached the point of no return: we are going to     * overwrite all exception vector code, so we cannot easily     * recover from any failures any more...     */    iflag = disable_interrupts();    // 判断镜像文件是否为压缩文件,若为压缩文件,解压缩。    switch (hdr->ih_comp) {    case IH_COMP_NONE:        if(ntohl(hdr->ih_load) == data) {            printf ("   XIP %s ... ", name);        } else {#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)            size_t l = len;            void *to = (void *)ntohl(hdr->ih_load);            void *from = (void *)data;            printf ("   Loading %s ... ", name);            while (l > 0) {                size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;                WATCHDOG_RESET();                memmove (to, from, tail);                to += tail;                from += tail;                l -= tail;            }#else   /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */            memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);#endif  /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */        }        break;    case IH_COMP_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;#ifdef CONFIG_BZIP2    case IH_COMP_BZIP2:        printf ("   Uncompressing %s ... ", name);        /*         * If we've got less than 4 MB of malloc() space,         * use slower decompression algorithm which requires         * at most 2300 KB of memory.         */        i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),                        &unc_len, (char *)data, len,                        CFG_MALLOC_LEN < (4096 * 1024), 0);        if (i != BZ_OK) {            printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);            SHOW_BOOT_PROGRESS (-6);            udelay(100000);            do_reset (cmdtp, flag, argc, argv);        }        break;#endif /* CONFIG_BZIP2 */    default:        if (iflag)            enable_interrupts();        printf ("Unimplemented compression type %d\n", hdr->ih_comp);        SHOW_BOOT_PROGRESS (-7);        return 1;    }    puts ("OK\n");    SHOW_BOOT_PROGRESS (7);    // 对于内核镜像文件,在这里直接跳到下一步操作。    switch (hdr->ih_type) {    case IH_TYPE_STANDALONE:        if (iflag)            enable_interrupts();        /* load (and uncompress), but don't start if "autostart"         * is set to "no"         */        if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {            char buf[32];            sprintf(buf, "%lX", len);            setenv("filesize", buf);            return 0;        }        appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);        (*appl)(argc-1, &argv[1]);        return 0;    case IH_TYPE_KERNEL:    case IH_TYPE_MULTI:        /* handled below */        break;    default:        if (iflag)            enable_interrupts();        printf ("Can't boot image type %d\n", hdr->ih_type);        SHOW_BOOT_PROGRESS (-8);        return 1;    }    SHOW_BOOT_PROGRESS (8);    // 到了这里说明镜像文件是内核文件,判断是什么类型的内核,然后调用相应的启动函数,这里是Linux OS,所以调用do_bootm_linux    switch (hdr->ih_os) {    default:            /* handled by (original) Linux case */    case IH_OS_LINUX:#ifdef CONFIG_SILENT_CONSOLE        fixup_silent_linux();#endif        // 若bootm传入的命令参数为"bootm 0x30007FC0",则        // cmdtp = store the address where the cmd_bootm struct table.        // flag = 0        // argc = 2        // argv[0] = "bootm"        // argv[1] = "0x30007FC0"        // addr = 0x30007FC0        // len_ptr = 0x30008000        // verify = 1        do_bootm_linux  (cmdtp, flag, argc, argv,                 addr, len_ptr, verify);        break;    case IH_OS_NETBSD:        do_bootm_netbsd (cmdtp, flag, argc, argv,                 addr, len_ptr, verify);        break;#ifdef CONFIG_LYNXKDI    case IH_OS_LYNXOS:        do_bootm_lynxkdi (cmdtp, flag, argc, argv,                 addr, len_ptr, verify);        break;#endif    case IH_OS_RTEMS:        do_bootm_rtems (cmdtp, flag, argc, argv,                 addr, len_ptr, verify);        break;#if (CONFIG_COMMANDS & CFG_CMD_ELF)    case IH_OS_VXWORKS:        do_bootm_vxworks (cmdtp, flag, argc, argv,                  addr, len_ptr, verify);        break;    case IH_OS_QNX:        do_bootm_qnxelf (cmdtp, flag, argc, argv,                  addr, len_ptr, verify);        break;#endif /* CFG_CMD_ELF */#ifdef CONFIG_ARTOS    case IH_OS_ARTOS:        do_bootm_artos  (cmdtp, flag, argc, argv,                 addr, len_ptr, verify);        break;#endif    }    SHOW_BOOT_PROGRESS (-9);#ifdef DEBUG    puts ("\n## Control returned to monitor - resetting...\n");    do_reset (cmdtp, flag, argc, argv);#endif    return 1;}

二、达成终极目标——启动内核

在确认镜像文件无误,拷贝内核到调用入口地址处(如果需要的话)后,调用do_bootm_linux(),设置u-boot传给内核的参数并为启动内核做一些初始化,包括关闭中断,关闭MMU,关闭数据cache,设置CPU为SVC模式,设置R0~R2寄存器的值,最终跳到内核入口地址调用内核。
由于u-boot和内核的交互是单向的,传递参数的办法只有一个:u-boot将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。

// 若bootm传入的命令参数为"bootm 0x30007FC0",则// cmdtp = store the address where the cmd_bootm struct table.// flag = 0// argc = 2// argv[0] = "bootm"// argv[1] = "0x30007FC0"// addr = 0x30007FC0// len_ptr = 0x30008000// verify = 1void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],ulong addr, ulong *len_ptr, int verify){    ulong len = 0, checksum;    ulong initrd_start, initrd_end;    ulong data;    void (*theKernel)(int zero, int arch, uint params);    image_header_t *hdr = &header;    bd_t *bd = gd->bd;#ifdef CONFIG_CMDLINE_TAG    // 获取OS的启动参数: 若 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0    // 则commandline = "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"    char *commandline = getenv ("bootargs");#endif    // theKernel指向内核的入口地址    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);    /*    * no initrd image    */    SHOW_BOOT_PROGRESS (14);    len = data = 0;#ifdef  DEBUG    if (!data) {        printf ("No initrd\n");    }#endif    if (data) {        initrd_start = data;        initrd_end = initrd_start + len;    } else {        initrd_start = 0;        initrd_end = 0;    }    SHOW_BOOT_PROGRESS (15);    debug ("## Transferring control to Linux (at address %08lx) ...\n",           (ulong) theKernel);    // 设置传递给内核的参数 TLV格式#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_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");#ifdef CONFIG_USB_DEVICE    {        extern void udc_disconnect (void);                //udc_disconnect (); // cancled by www.100ask.net    }#endif    // 在调用内核之前,做一些必要的初始化。    cleanup_before_linux ();    // 通过入参,设置CPU寄存器    // R0 = 0    // R1 = 机器类型ID    // R2 = 启动参数标记列表在RAM中起始基地址    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);}
// 设置起始TAG参数,参数列表的其实地址为 bd->bi_boot_paramsstatic void setup_start_tag (bd_t *bd){    params = (struct tag *) bd->bi_boot_params;    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);}
// 关中断,关流水线,清cacheint cleanup_before_linux (void){    /*     * this function is called just before we call linux     * it prepares the processor for linux     *     * we turn off caches etc ...     */    unsigned long i;    disable_interrupts ();    /* turn off I/D-cache */    asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));    i &= ~(C1_DC | C1_IC);    asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));    /* flush I/D-cache */    i = 0;    asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));    return (0);}
原创粉丝点击