《uboot源码解析(四)引导内核启动》

1.内核启动bootcmd

  内核启动的两个条件: 1.从Flash中读出内核 2.bootm命令

  在uboot界面,通过prinenv可以看到bootcmd=nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0

  nand read.jffs2 0x30007FC0 kernel就是将内核从Flash读到内存

  bootm 0x30007FC0的作用就是引导启动内核。

 

2.从Flash中将内核读取到内存中

  nand read.jffs2 0x30007FC0 kernel,其中kernel表示分区。可以通过mtd命令看到kernel分区得size和offset。所以命令可以展开为:nand read.jffs2 0x30007FC0 0x60000 0x200000。nand read.jffs2可以自动页对齐,所以大小可以是非页整的;如果使用nand read的大小必须是页对齐的。因此还可以写成nand read 0x30007FC0 0x60000 0x200000。

  此命令会激活(common/cmd_nand.c)中的do_nand函数,从而将nand flash上的kernel分区加载到0x30007fc0位置处。

  还有一种从SPI Flash中读取内核:

  sf read用来读取flash数据到内存

  sf write写内存数据到flash

  sf erase 擦除指定位置,指定长度的flash内容, 擦除后内容全1

  使用范例

  sf probe

  在使用sf read sf write之前,一定要调用sf probe

  sf write 0x82000000 0x0 0x20000

  把内存0x8200 0000处的数据, 写入flash的偏移0x0, 写入数据长度为0x20000(128KB), 操作偏移和长度最小单位是Byte

  sf read 0x82000000 0x10000 0x20000

  把flash偏移0x10000(64KB)处, 长度为0x20000(128KB)的数据, 写入到内存0x82000000, 操作偏移和长度最小单位是Byte

  sf erase 0x0 0x10000擦除偏移0x0处, 到0x10000之间的擦除块, 擦除操作是以erase block为单位的, 要求offset和len参数必须是erase block对齐的

  从sf命令,可以看出几点:

  1. spi flash没有oob数据存在, 也就是不用考虑EDC ECC, 也没有坏块管理概念.

  2. 支持Byte级的读写操作, 支持随机访问.

  如何验证读写效果

  可以结合uboot md命令, sf read, sf write都涉及到内存操作, 可以用md查看内存数据

  md 0x82000000 0x100

  打印0x82000000开始, 长度范围256字节的内存数据

 

 2.启动内核bootm

  根据上一章的内容可知,有一个U_BOOT_CMD的宏来定义bootm。里面主要有两个主要的结构体成员,一个就是指针name,指向字符串“bootm”。另一个是函数指针,指向do_bootm函数。一旦我们使用了bootm命令,就会调用do_bootm函数。

  由于do_bootm函数内容过长,主要挑几个比较重要的讲解:

~/include/image.h
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;

~/common/cmd_bootm.c
image_header_t header;
image_header_t
*hdr = &header;

  header是一个image_header_t结构体类型的变量。其中image_header_t主要是对内核进行描述。主要关心两个结构体成员:ih_load和ih_ep。前者是加载地址,后者是入口地址。(假如ih_load的值是0x30008000,那么使用bootm时候的地址应该是0x30007FC0,因为两个相减等于64,这64k就是这个头的大小,后面就是我们正常使用的内核)。

  那么bootm都做了什么事情呢? 

  1.      根据header将内核移动到合适的地方。

  2.   给内核传递参数。

  3.      启动内核(通过do_bootm_linux函数启动)。

读取uImage得头部

  image_header_t header;  定义一个全局变量header,是读取头部的缓冲区

  addr = simple_strtoul(argv[1], NULL, 16);  定位头部地址,将字符串“0x30007fc0”转化为整型

  printf ("## Booting image at %08lx ...\n", addr); 显示从哪儿启动

  memmove (&header, (char *)addr, sizeof(image_header_t)); 读取头部到header变量中

判断当前的内存区是否是uImage的开始位置

if (ntohl(hdr->ih_magic) != IH_MAGIC) {
      {
  puts ("Bad Magic Number\n");
  SHOW_BOOT_PROGRESS (-1);
  return 1;
     }
 }

#define IH_MAGIC 0x27051956 /* Image Magic Number  */(include/image.h)

校验头部

data = (ulong)&header;
    len  = sizeof(image_header_t);

    checksum = ntohl(hdr->ih_hcrc);
    hdr->ih_hcrc = 0;

    if (crc32 (0, (uchar *)data, len) != checksum) {
        puts ("Bad Header Checksum\n");
        SHOW_BOOT_PROGRESS (-2);
        return 1;
    }

打印头部信息

    /* for multi-file images we need the data part, too */
    print_image_hdr ((image_header_t *)addr);

核查内核数据

data = addr + sizeof(image_header_t);
    len  = ntohl(hdr->ih_size);

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

注意到data已经跳过了uImage的头部,指向了真正的内核首部,也即0x30008000。

 

核查架构、内核类型、压缩类型等信息,其中会涉及到重定位

len_ptr = (ulong *)data;

#if defined(__PPC__)
    if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
    if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
    if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
    if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
    if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
    if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
    if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
    if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
    if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
    if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
    {
        printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
        SHOW_BOOT_PROGRESS (-4);
        return 1;
    }
    SHOW_BOOT_PROGRESS (5);

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

#ifdef CONFIG_AMIGAONEG3SE 
    /*
     * We've possible left the caches enabled during
     * bios emulation, so turn them off again
     */
    icache_disable();  
    invalidate_l1_instruction_cache();
    flush_data_cache();
    dcache_disable();
#endif

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

在这部分代码中,有这么一部分关于压缩类型的:

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;

    可以看到,u-boot会判断当前去除uImage头部内核代码所处的位置(7步骤已经说明地址是data)是否与编译时安排的重定位位置(hdr->ih_load)一致。

  如果一致,就打印一句话。

  如果不一致,则需要调用 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);进行内核的重定位,要知道它有2M多的大小,会花费一些时间。尽量使读取内核的时候,就读取到hdr->ih_load-64的位置上,这样就不必再搬运一次。 

根据操作系统类型,启动对应的操作系统 

switch (hdr->ih_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,
                 addr, len_ptr, verify);
        break;
    case IH_OS_NETBSD:

执行do_bootm_linux,继续启动linux系统

void (*theKernel)(int zero, int arch, uint params);
    image_header_t *hdr = &header;
    bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif

    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

  可见,已经将内核运行的首地址赋给了theKernel函数指针变量,将来可以利用这个变量调用进入内核的函数。

  另外,在进入内核之前,要给内核传递参数。方法是将参数以一定的结构放在内存指定的位置上,将来内核从该地址读取数据即可。

  命令行的启动参数存储在以bootargs命名的对象里,值为

bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

告诉内核,启动后的根文件系统位于mtd的哪个区,初始进程,以及控制台。

 判断是否是一个ramdisk或者multi镜像

/*
     * Check if there is an initrd image
     */
    if (argc >= 3) {
        SHOW_BOOT_PROGRESS (9);

        addr = simple_strtoul (argv[2], NULL, 16);

        printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

        /* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (addr, sizeof (image_header_t),
                    (char *) &header);
        } else
#endif
            memcpy (&header, (char *) addr,
                sizeof (image_header_t));

        if (ntohl (hdr->ih_magic) != IH_MAGIC) {
            printf ("Bad Magic Number\n");
            SHOW_BOOT_PROGRESS (-10);
            do_reset (cmdtp, flag, argc, argv);
        }

        data = (ulong) & header;
        len = sizeof (image_header_t);

        checksum = ntohl (hdr->ih_hcrc);
        hdr->ih_hcrc = 0;

        if (crc32 (0, (unsigned char *) data, len) != checksum) {
            printf ("Bad Header Checksum\n");
            SHOW_BOOT_PROGRESS (-11);
            do_reset (cmdtp, flag, argc, argv);
        }

        SHOW_BOOT_PROGRESS (10);

        print_image_hdr (hdr);

        data = addr + sizeof (image_header_t);
        len = ntohl (hdr->ih_size);

#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
            data = CFG_LOAD_ADDR;
        }
#endif

        if (verify) {
            ulong csum = 0;

            printf ("   Verifying Checksum ... ");
            csum = crc32 (0, (unsigned char *) data, len);
            if (csum != ntohl (hdr->ih_dcrc)) {
                printf ("Bad Data CRC\n");
                SHOW_BOOT_PROGRESS (-12);
                do_reset (cmdtp, flag, argc, argv);
            }
            printf ("OK\n");
        }

        SHOW_BOOT_PROGRESS (11);

        if ((hdr->ih_os != IH_OS_LINUX) ||
            (hdr->ih_arch != IH_CPU_ARM) ||
            (hdr->ih_type != IH_TYPE_RAMDISK)) {
            printf ("No Linux ARM Ramdisk Image\n");
            SHOW_BOOT_PROGRESS (-13);
            do_reset (cmdtp, flag, argc, argv);
        }

#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
        /*
         *we need to copy the ramdisk to SRAM to let Linux boot
         */
        memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
        data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */

        /*
         * Now check if we have a multifile image
         */
    } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
        ulong tail = ntohl (len_ptr[0]) % 4;
        int i;

        SHOW_BOOT_PROGRESS (13);

        /* skip kernel length and terminator */
        data = (ulong) (&len_ptr[2]);
        /* skip any additional image length fields */
        for (i = 1; len_ptr[i]; ++i)
            data += 4;
        /* add kernel length, and align */
        data += ntohl (len_ptr[0]);
        if (tail) {
            data += 4 - tail;
        }

        len = ntohl (len_ptr[1]);

    } else {
        /*
         * no initrd image
         */
        SHOW_BOOT_PROGRESS (14);

        len = data = 0;
    }

#ifdef    DEBUG
    if (!data) {
        printf ("No initrd\n");
    }
#endif

给内核传递参数

#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

  比较重要的函数有:

   setup_start_tag (bd);

  setup_memory_tags (bd);

  setup_commandline_tag (bd, commandline);

  setup_end_tag (bd);

  其中 bd->bi_boot_params(参考uboot全局变量),bi_boot_params=>>0x30000100,启动参数存放的位置。

启动内核

    printf ("\nStarting kernel ...\n\n");
  theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

启动过程展示

1、不需要重定位启动

 

 2、重定位启动

   下例中读取到的位置,不是合适的位置,内核的入口不是0x30008000,所以还要对内核进行重定位,也就是将内核搬移到指定的位置。

 

posted @ 2019-08-30 15:48  一个不知道干嘛的小萌新  阅读(1183)  评论(0编辑  收藏  举报