Bootstrap

8.uboot 启动内核代码分析

0、uboot和内核区别

uboot的本质就是一个复杂点的裸机程序。内核本身也是一个"裸机程序“,和uboot、和其他裸机程序并没有本质区别。
区别就是操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,在内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址)。
直观来看:uboot的镜像是u-boot.bin,linux系统的镜像是zImage,这两个东西其实都是两个裸机程序镜像。从系统的启动角度来讲,内核其实就是一个大的复杂点裸机程序。

1、嵌入式系统部署在SD卡中特定分区内

(1)一个完整的嵌入式系统,静止时(未上电时)bootloader、kernel、rootfs等软件都以镜像的形式存储在启动介质中(X210中是iNand/SD卡);运行时都是在DDR内存中运行的,与存储介质无关。从静止态到运行态的过程,就是uboot启动内核的过程。

(2)启动过程就是一个将内核镜像从SD卡搬移到DDR内存去运行,最终达到稳定状态的过程。

(3)静止时u-boot.bin zImage rootfs都在SD卡中,他们不可以在SD卡中胡乱存放,而是存放在各自的分区中,这样在启动过程中uboot、内核等就知道到哪里去找。在uboot和kernel中都设置了一张分区表,这张分区表必须保持一致,并且要和实际烧写在SD卡中的位置一致。
譬如uboot的BL1从SD卡49扇区开始重定位整个uboot,那么事先就应该将uboot.bin烧写在49扇区。

2、内核运行必须加载到DDR中的链接地址处

(1)uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。(0xc3e00000是个虚拟地址,真实的物理地址是0x33e00000)

(2)内核也有类似要求,uboot启动内核时将内核从SD卡读取放到DDR中(其实就是个重定位的过程),不能随意放置,必须放在内核的链接地址处才能运行,否则内核启动不起来。我们使用的内核链接地址是0x30008000。

3、内核启动需要uboot传递必要的启动参数

(1)uboot是无条件自启动的。

(2)内核是不能开机自动启动的,uboot不仅要重定位内核到DDR内存,还要给内核提供必要启动参数才能运行(譬如uboot对内存的配置信息(维护在gd->bd的数据结构中)以及 uboot的环境变量bootargs 就是将来要传递给内核去使用的)。

4、启动内核第一步:加载内核到DDR中

(1)uboot要启动内核,首先就是要将内核加载到DDR中。uboot实现了重定位自己,但内核没有,内核代码从没考虑重定位自己,因为内核知道会有uboot之类的bootloader将自己加载到DDR中的链接地址处,所以内核直接就是从链接地址处开始运行的。

5、内核镜像在哪里?

(1)SD卡/iNand/Nand/NorFlash等启动介质:raw分区
启动时各种镜像都在SD卡中,内核镜像在SD卡的kernel分区,uboot使用movi命令将内核镜像从这个分区读到DDR中(譬如X210的iNand版本是movi命令,X210的Nand版本就是Nand命令)
使用命令:movi read kernel 30008000 将内核从iNand读取到DDR。其中kernel指的是uboot中的kernel分区(uboot在SD卡中划分了一个区域范围,这个区域范围专门用来存放kernel镜像)

(2)tftp、nfs等网络下载方式从远端服务器获取镜像
uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后uboot通过网络从服务器中下载镜像到开发板的DDR中。
分析总结:
最终目的是要将内核镜像放到DDR中的特定地址,不管内核镜像是怎么到DDR中的。
以上2种方式各有优劣。
产品出厂时会设置为从SD卡中启动内核(客户不会还要搭建tftp服务器才能使用···);
tftp下载远程启动这种方式一般用来开发。

6、镜像要放在DDR的什么地址?

(1)内核一定要放在链接地址处,链接地址去内核源代码的链接脚本或者Makefile中去查找。X210中是0x30008000。

7、zImage和uImage的区别联系

7.1、bootm命令对应do_bootm函数

(1)命令名前加do_即可构成这个命令对应的函数,因此当我们bootm命令执行时,uboot实际执行的函数叫do_bootm函数,在cmd_bootm.c。

(2)do_bootm函数刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的一些代码(主要进行签名认证),x210的uboot中并没有使用安全启动,先不管他;然后进行了一些数据结构的赋值操作,也不管他。然后到了CONFIG_ZIMAGE_BOOT,用这个宏来条件编译一段代码,这段代码是用来支持zImage格式的内核启动的。

7.2、vmlinuz和zImage和uImage

在这里插入图片描述
(1)uboot经过编译生成了elf格式的可执行程序u-boot,这个程序类似于windows下的exe格式,在Ubutun下是可以直接运行的。但是这种格式不能用来烧录下载。我们用来烧录下载使用的是u-boot.bin,它是对elf格式的u-boot使用arm-linux-objcopy工具进行加工处理(主要目的是去掉一些无用的)得到的。这个u-boot.bin就是镜像(image),镜像就是用来烧录到iNand中执行的。
在这里插入图片描述
(2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的内核文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),内核经过objcopy工具加工得到的烧录镜像的文件就叫Image(把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。

(3)原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。实际上linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在image压缩后的文件的前端附加了一部分解压缩代码,这段代码是未经压缩的。两者一起构成的镜像就叫zImage。(因为当年Image大小刚好比一张软盘(软盘有2种,1.2M的和1.44MB两种)大,为了节省1张软盘的钱于是乎设计了这种压缩Image成zImage的技术)。
zImage = zImage信息头 + 解压缩代码 + image压缩后的文件

(4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具mkimage,可以将zImage加工生成uImage。
注意:uImage不关linux内核的事,linux内核只管生成zImage即可,所以在linux内核源码目录中是没有这个mkimage工具的,因此需要将这个工具从uboot目录中复制给linux内核去使用,然后使用mkimage工具将zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息。
uImage = uImage信息头 + zImage信息头 + 解压缩代码 + image压缩后的文件
在这里插入图片描述
(5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了CONFIG_ZIMAGE_BOOT这个宏。
因此,有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。

7.3、将内核编译成uImage格式去启动

(1)如果直接在kernel底下去make uImage会提供mkimage command not found错误。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统环境变量包含的目录下。再去make uImage即可。

8、zImage启动细节

(1)do_bootm函数中一直到397行的after_header_check这个符号处,都是在进行镜像的头部信息校验。校验时就要根据不同种类的image类型进行不同的校验。所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型,然后按照这种类型的头信息格式去校验。校验通过则进入下一步准备启动内核;如果校验失败则认为镜像有问题,所以不能启动。
zImage = zImage头信息+解压缩代码+压缩后的image
uImage = uImage头信息+zImage头信息+解压缩代码+压缩后的image

8.1、LINUX_ZIMAGE_MAGIC

//cmd_bootm.c下的do_bootm函数第196~225行
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC    0x016f2818
    /* find out kernel image address */
    if (argc < 2) {
        addr = load_addr;                            //最终值为0x30000000
        debug ("*  kernel: default image load address = 0x%08lx\n",
                load_addr);
    } else {
        addr = simple_strtoul(argv[1], NULL, 16);    //argv[1] = 0x30008000
        debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
    }
//以上这段代码告诉我们镜像放在内存的什么位置

    if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {    //zImage头部开始的第37-40字节处存放着zImage的标志位,这里是判断这个镜像是不是zImage
        printf("Boot with zImage\n");    
        addr = virt_to_phys(addr);              //将虚拟地址转化为物理地址 c3e00000或33e00000 -> 33e00000
        hdr = (image_header_t *)addr;           
  /*image_header_t是一个结构体,zImage头信息也是image_header_t这种类型的数据结构,
   *所以这里是要将镜像文件addr中的zImage头信息单独取出,用hdr这个指针指向zImage头
。    
   */
          hdr->ih_os = IH_OS_LINUX;
               //改造zImage头信息中的ih_os和ih_ep
        hdr->ih_ep = ntohl(addr);

        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
                                                        //用zImage头信息构建images                                                          

        /* save pointer to image header */
        images.legacy_hdr_os = hdr;

        images.legacy_hdr_valid = 1;

        goto after_header_check;
    }
#endif

(1)LINUX_ZIMAGE_MAGIC这是一个定义的魔数,这个数等于0x016f2818,代表这个镜像是一个zImage。在zImage的头信息格式中某个位置定义了一个标志,这个标志用来表示镜像类型,如果这个标志等于0x016f2818,则说明这个镜像是zImage格式的。这里定义这个宏是为了之后和zImage的头信息中的标志进行比对。

(2)uboot命令行下使用命令 bootm 0x30008000 启动内核,所以do_bootm函数的argc=2,argv[0]=bootm argv[1]=0x30008000。但是实际bootm命令还可以不带参数执行。直接bootm,则会从load_addr这个默认地址去执行,load_addr = CFG_LOAD_ADDR = MEMORY_BASE_ADDRESS = 0x30000000(定义在x210_sd.h中)。

(3)zImage头部开始的第37-40字节处存放着标志类型的魔数,从这个位置取出后对比LINUX_ZIMAGE_MAGIC,相等则说明这个镜像文件是zImage格式,不相等则为其它格式。可以用二进制阅读软件来打开zImage查看,就可以证明,如winhex、UltraEditor。
在这里插入图片描述

8.2、image_header_t 212行

在这里插入图片描述

//do_bootm函数(209~224行)
 if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {    //zImage头部开始的第37-40字节处存放着zImage的标志位,这里是判断这个镜像是不是zImage
        printf("Boot with zImage\n");    
        addr = virt_to_phys(addr);              //将虚拟地址转化为物理地址 c3e00000或33e00000 -> 33e00000
        hdr = (image_header_t *)addr;           
  /*image_header_t是一个结构体,zImage头信息也是image_header_t这种类型的数据结构,
   *所以这里是要将镜像文件addr中的zImage头信息单独取出,用hdr这个指针指向zImage头
。    
   */
          hdr->ih_os = IH_OS_LINUX;
               //改造zImage头信息中的ih_os和ih_ep
        hdr->ih_ep = ntohl(addr);

        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
                                                        //用zImage头信息构建images                                                          

        /* save pointer to image header */
        images.legacy_hdr_os = hdr;

        images.legacy_hdr_valid = 1;

        goto after_header_check;
    }

(1)image_header_t是一个结构体,zImage头信息也是image_header_t这种类型的数据结构,所以这里是要将镜像文件addr中的zImage头信息单独取出,用hdr这个指针指向zImage头

后续还进行了一些改造。hdr->ih_os = IH_OS_LINUX; hdr->ih_ep = ntohl(addr);这两句就是在进行改造zImage头信息。
然后再将Image头信息构建成images这个变量,后续会用到。
在这里插入图片描述
(2)images全局变量仅仅只是在do_bootm函数中使用。zImage的校验过程其实就是先确认是不是zImage格式的镜像文件,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量,然后就完成了校验,images这个变量再后面会被使用。

9、uImage启动细节

9.1、uImage启动

//cmd_bootm.c下的do_bootm函数第227~244行
 /* 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;

(1)do_bootm函数237行中的IMAGE_FORMAT_LEGACY宏,LEGACY(表示遗留的),在do_bootm函数中,这种方式指的就是uImage的方式。uImage方式是uboot本身最早使用的支持linux启动的镜像格式,但是后来这种方式由于一些缺陷被另一种新的方式替代,这个新的方式就是设备树方式(在do_bootm函数中叫FIT,从do_bootm函数245行开始)
(2)uImage的启动校验主要在boot_get_kernel函数中,主要任务就是校验uImage的头信息,以下为boot_get_kernel函数内容:

//在cmd_bootm.c下的do_bootm函数第228行调用,定义在该文件下的562~715行。
/* find out kernel image address */
if (argc < 2) {        //和zImage处相同
    img_addr = load_addr;
        debug ("*  kernel: default image load address = 0x%08lx\n",
    load_addr);
#if defined(CONFIG_FIT)     //这个是设备树传参方式才有的
} else if (fit_parse_conf (argv[1], load_addr, &img_addr,
&fit_uname_config)) {
        debug ("*  kernel: config '%s' from image at 0x%08lx\n",
    fit_uname_config, img_addr);
} else if (fit_parse_subimage (argv[1], load_addr, &img_addr,
&fit_uname_kernel)) {
        debug ("*  kernel: subimage '%s' from image at 0x%08lx\n",
    fit_uname_kernel, img_addr);
#endif
} else {        //这个是uImage启动 镜像放在内存中的位置,可以看到和zImage启动相同,都是0x30008000
    img_addr = simple_strtoul(argv[1], NULL, 16);
        debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
}
show_boot_progress (1);    //打印一个调试信息,告诉我们启动了百分之多少

/* copy from dataflash if needed */  //实际没有使用,这个是对镜像的重定位,把镜像在30008000复制到30000000
img_addr = genimg_get_image (img_addr);


/* check image type, for FIT images get FIT kernel node */    //检验镜像的类型
*os_data = *os_len = 0;
switch (genimg_get_format ((void *)img_addr)) {
    case IMAGE_FORMAT_LEGACY:
        printf ("## Booting kernel from Legacy Image at %08lx ...\n",
img_addr);
    hdr = image_get_kernel (img_addr, images->verify);//校验镜像的一系列标志位,并且打印image的相关信息(最终是可以看到的)
        if (!hdr)            //hdr = img_addr = 0x30008000
        return NULL;
        show_boot_progress (5);

        
         /* get os_data and os_len */
            switch (image_get_type (hdr)) {
        case IH_TYPE_KERNEL:            //说明这是一个内核镜像
            *os_data = image_get_data (hdr);
            *os_len = image_get_data_size (hdr);
            break;
        case IH_TYPE_MULTI:
            image_multi_getimg (hdr, 0, os_data, os_len);
            break;
        default:
            printf ("Wrong Image Type for %s command\n", cmdtp->name);
            show_boot_progress (-5);
            return NULL;
        }
}
    /*
  * copy image header to allow for image overwrites during kernel
  * decompression.
  */
    
    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;    //跟zImage的处理一样
    show_boot_progress (6);
    break;
}
return (void *)img_addr;

以下为genimg_get_image函数内容,这个函数实际并没有使用,这个是对镜像的重定位,把镜像在30008000复制到30000000:

ulong genimg_get_image (ulong img_addr)
{
    ulong ram_addr = img_addr;               //0x30008000
#ifdef CONFIG_HAS_DATAFLASH           //实际不使用
    ulong h_size, d_size;

    if (addr_dataflash (img_addr)){
        /* ger RAM address */
        ram_addr = CFG_LOAD_ADDR;           //ram_addr = 0x30000000

        /* get header size */
        h_size = image_get_header_size ();  //uImage方式的校验头大小为sizeof(fdt_header)=64B                
#if defined(CONFIG_FIT)                //设备树方式使用
    if (sizeof(struct fdt_header) > h_size)
        h_size = sizeof(struct fdt_header);    //设备树方式的校验头大小为sizeof(fdt_header)=40B            
#endif

    /* read in header */
    debug ("   Reading image header from dataflash address "
    "%08lx to RAM address %08lx\n", img_addr, ram_addr);

    read_dataflash (img_addr, h_size, (char *)ram_addr);//复制uImage的校验头到ram_addr缓存

    /* get data size */
    switch (genimg_get_format ((void *)ram_addr)) {    //校验是uImage方式还是设备树传参方式
        case IMAGE_FORMAT_LEGACY:                                //uImage方式
        d_size = image_get_data_size ((image_header_t *)ram_addr);    //获取镜像的大小
            debug ("   Legacy format image found at 0x%08lx, size 0x%08lx\n",
            ram_addr, d_size);
            break;
#if defined(CONFIG_FIT)                                        //设备树传参方式
            case IMAGE_FORMAT_FIT:                                    //设备树传参方式
        d_size = fit_get_size ((const void *)ram_addr) - h_size;
            debug ("   FIT/FDT format image found at 0x%08lx, size 0x%08lx\n",
            ram_addr, d_size);
            break;
#endif
        default:
        printf ("   No valid image found at 0x%08lx\n", img_addr);
        return ram_addr;
    }

    /* read in image data */
    debug ("   Reading image remaining data from dataflash address "
    "%08lx to RAM address %08lx\n", img_addr + h_size,
    ram_addr + h_size);

    read_dataflash (img_addr + h_size, d_size,
(char *)(ram_addr + h_size));
    }
#endif /* CONFIG_HAS_DATAFLASH */

    return ram_addr;
}

总结1:uboot本身设计时只支持uImage启动,原来uboot的代码也是这样写的。后来有了fdt(设备树)方式之后,就把uImage方式命令为LEGACY方式,fdt方式命令为FIT方式,于是乎多了些#if #endif添加的代码。后来移植的人又为了省事添加了zImage启动的方式,又为了省事把zImage启动方式直接写在了uImage和fdt启动方式之前,于是乎又有了一对#if #endif。于是乎整个的代码看起来很恶心。
总结2:第二阶段校验头信息结束,下面进入第三阶段,第三阶段主要任务是启动linux内核,调用do_bootm_linux函数来完成。

10、do_bootm_linux函数(do_bootm函数407处调用)

(1)函数定义在uboot/lib_arm/bootm.c中(61~165行)。

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
             bootm_headers_t *images)
{
    
    int    machid = bd->bi_arch_number;           //机器码
#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");   //获取环境变量bootargs的值,后面会作为参数传给内核
#endif

    /* find kernel entry point */
    if (images->legacy_hdr_valid) {          //第二步时就已经赋值为1,说明当前我们的镜像是有效的
        ep = image_get_ep (&images->legacy_hdr_os_copy);//ep为操作系统程序入口,从头信息中获取程序入口 

    

    } 
    theKernel = (void (*)(int, int, uint))ep; //ep本身只是一个数字,强制类型转换成一个函数去访问

    s = getenv ("machid");       //获取环境变量中的机器码
    if (s) {                //如果环境变量中有机器码
        machid = simple_strtoul (s, NULL, 16);    
                //替代第一次bd->bi_arch_number的赋值,可以看出环境变量的优先级更高
        printf ("Using machid 0x%x from environment\n", machid);
    }
//以下开始传参,uboot传递参数以tag格式的数据结构,一tag一tag的发送到指定地址处(设定为0x30001000)
//那内核从指定地址处取参,从哪里开始读取参数,从哪里结束取参?以start_tag开始,以end_tag结束的参数空间
#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)     //以上这下宏,哪个定义了,哪个将来就会以xxx_tag的参数形式发送给内核
    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

    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");



    theKernel (0, machid, bd->bi_boot_params);
 //启动内核,不再返回,uboot生命周期结束
 //参数 0, machid, bd->bi_boot_params  意思分别是:0, 机器码, 参数在内存中存放的首地址(0x30001000)
 //那么寄存器r0 = 0 , r1 = machid , r2 = bd->bi_boot_params  
    /* does not return */



    return;
}

(2)SI找不到(是黑色的)不代表就没有,要搜索一下才能确定;搜索不到也不能代表就没有,因为我们在向SI工程中添加文件时,SI只会添加它能识别的文件格式的文件,有一些像Makefile、xx.conf等Makefile不识别的文件是没有被添加的。所以如果要搜索的关键字在makefile中或者脚本中,可能就是搜索不到的。(譬如TEXT_BASE)

10.1、镜像的entrypoint

(1)ep就是entrypoint的缩写,就是程序入口。这个程序入口地址由(内核镜像地址 + 偏移量)得到。
内核镜像地址 = 0x30008000,而偏移量则记录在头信息中。

(2)一般执行一个内核镜像都是:
第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类;
第二步对镜像进行校验,判断内核是不是一个完整有效的内核。
第三步再次读取头信息,在特定地址处知道这个镜像的各种信息(镜像种类、镜像长度、程序入口地址);
第四步就去程序入口entrypoint处开始执行镜像。
至此,OS内核启动。

(3)theKernel = (void (*)(int, int, uint))ep;
将ep强制转化成函数地址并赋值给theKernel,则theKernel这个函数指针就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。

10.2、机器码的再次确定

 s = getenv ("machid");       //获取环境变量中的机器码
    if (s) {                //如果环境变量中有机器码
        machid = simple_strtoul (s, NULL, 16);    
                //替代第一次bd->bi_arch_number的赋值,可以看出环境变量的优先级更高
        printf ("Using machid 0x%x from environment\n", machid);
    }

(1)uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码是怎么确定的?第一备选是环境变量machid,第二备选是uboot的一个数据结构gd->bd->bi_arch_num(x210_sd.h中硬编码配置的)

10.3、传参并启动概述
(1)从110行到144行就是uboot将要传递给内核的参数放到“以某个地址为开始的内存区域”(uboot代码中设置为0x30001000)。之后再将首地址告诉内核,内核就会从这里取走参数。

(2)Starting kernel … 这个是uboot中最后一句打印出来的东西。这句如果能出现,说明uboot整个是成功的,也成功的加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行了。如果这句后串口就没输出了,说明内核并没有被成功执行。原因一般是:传参(80%)、内核在DDR中的加载地址·······

11、传参详解

11.1、tag方式传参

struct tag_header {
    u32 size;       //指定tag的大小 
    u32 tag;        //指定tag的类型, core、mem、videotext......之类的
};
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;
};

(1)struct tag,tag是一个数据结构,在uboot和linux kernel中都有定义tag数据机构,而且定义是一样的。

(2)tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理,tar_xxx取决于hdrr->tag的类型。

(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag以tag_start起始,到tag_end结束。

(4)tag传参的方式是由linux kernel发明的,kernel定义了bootloder向我传参的方式,uboot只是实现了这种传参方式给内核传参而已。

11.2、x210_sd.h中配置传参宏

(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。uboot将内存的配置信息维护在gd->bd这个数据结构中,tag_men就是传递的这个
(2)CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.
(3)CONFIG_INITRD_TAG
(4)CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
(5)起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。
思考:内核如何拿到这些tag?
在这里插入图片描述
uboot最终是调用theKernel函数来执行linux内核的,uboot调用这个函数(其实就是linux内核)时传递了3个参数。这3个参数就是uboot直接传递给linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0中,第二个参数放在r1中,第3个参数放在r2中)第1个参数固定为0,第2个参数是机器码,第3个参数传递的就是大片传参tag的首地址。

11.3、移植时注意事项

(1)uboot移植时一般只需要配置相应的宏即可
(2)kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。

12、uboot启动内核的总结

12.1、启动4步骤

第一步:将内核搬移到DDR中
第二步:校验内核格式、CRC(判断内核是否完整)等
第三步:准备参数
第四步:跳转执行内核

12.2、涉及到的主要函数是:do_boom和do_bootm_linux

12.3、uboot能启动的内核格式:zImage uImage fdt方式

12.4、跳转与函数指针的方式运行内核

;