Uboot34之bootm函数

1.uboot是一个裸机程序

1)uboot的本质就是一个复杂点的裸机程序。和我们在ARM裸机全集中学习的每一个裸机程序并没有本质区别。

2)ARM裸机第十六部分写了个简单的shell,这东西其实就是个mini型的uboot。

2.内核本身也是一个"裸机程序"

1)操作系统内核本身就是一个裸机程序,和uboot、和其他裸机程序并没有本质区别。

2)区别就是操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址)。

直观来看:uboot的镜像是u-boot.bin,linux系统的镜像是zImage,这两个东西其实都是两个裸机程序镜像。从系统的启动角度来讲,内核其实就是一个大的复杂点裸机程序。

3.部署在SD卡中特定分区内

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

2)动态启动过程就是一个从SD卡逐步搬移到DDR内存,并且运行启动代码进行相关的硬件初始化和软件架构的建立,最终达到运行时稳定状态。

3)静止时u-boot.bin zImage rootfs都在SD卡中,他们不可能随意存在SD卡的任意位置,因此需要对SD卡进行一个分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中uboot、内核等就知道到哪里去找谁。(uboot和kernel中的分区表必须一致,同时和SD卡的实际使用的分区要一致)

4.运行时必须先加载到DDR中链接地址处

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

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

5.内核启动需要必要的启动参数

1)uboot是无条件启动的,从零开始启动的。

2)内核是不能开机自动完全从零开始启动的,内核启动要别人帮忙。uboot要帮助内核实现重定位(从SD卡到DDR),uboot还要给内核提供启动参数。

3)uboot要启动内核,分为2个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码根本就没考虑重定位,因为内核知道会有uboot之类的把自己加载到DDR中链接地址处的,所以内核直接就是从链接地址处开始运行的)

6.静态内核镜像在哪里?

1)SD卡/iNand/Nand/NorFlash等:raw分区

常规启动时各种镜像都在SD卡中,因此uboot只需要从SD卡的kernel分区去读取内核镜像到DDR中即可。读取要使用uboot的命令来读取(譬如X210的iNand版本是movi命令,X210的Nand版本就是Nand命令)

2)这种启动方式来加载ddr,使用命令:movi read kernel 30008000。其中kernel指的是uboot中的kernel分区(就是uboot中规定的SD卡中的一个区域范围,这个区域范围被设计来存放kernel镜像,就是所谓的kernel分区)

3)tftp、nfs等网络下载方式从远端服务器获取镜像

uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。

分析总结:最终结果要的是内核镜像到DDR中特定地址即可,不管内核镜像是怎么到DDR中的。以上2种方式各有优劣。产品出厂时会设置为从SD卡中启动(客户不会还要搭建tftp服务器才能用•••);tftp下载远程启动这种方式一般用来开发。

7.镜像要放在DDR的什么地址?

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

/*******************************************************************/

/* bootm - boot application image from image in memory */

/*******************************************************************/

8.bootm命令对应do_bootm函数

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

2)do_bootm刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的一些代码(主要进行签名认证),先不管他;然后进行了一些一些细节部分操作,也不管他。然后到了CONFIG_ZIMAGE_BOOT,用这个宏来控制进行条件编译一段代码,这段代码是用来支持zImage格式的内核启动的。

00135: int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
00136: {
00137:     image_header_t *hdr;
00138:         ulong addr;
00139:         ulong iflag;
00140:     const char *type_name;
00141:     uint unc_len = CFG_BOOTM_LEN;
00142:     uint8_t comp, type, os;
00143:
00144:     void *os_hdr;
00145:     ulong os_data, os_len;
00146:     ulong image_start, image_end;
00147:     ulong load_start, load_end;
00148:     ulong mem_start;
00149:     phys_size_t mem_size;
00150:
00151:     struct lmb lmb;
00182:
00183:     memset ((void *)&images, 0, sizeof (images));
00184:     images.verify = getenv_yesno ("verify");

------------------------------------------------------------------------------------------------------------------------------------------------------------

00123:static bootm_headers_t images;

00194: typedef struct bootm_headers {
00195:
/*
00196: * Legacy os image header, if it is a multi component imagethen boot_get_ramdisk() and get_fdt() will attempt to get
00198: * data from second and third component accordingly.*/
00200:         image_header_t *legacy_hdr_os; /* 指向镜像头的指针 */  
00200:     
/* image header pointer */
00201:     image_header_t legacy_hdr_os_copy; /* header copy *//* 镜像头的备份 */ 
00202:     ulong legacy_hdr_valid;  /* 镜像头存在标记 */ 
00221:
00222:     int
verify; /* getenv("verify")[0] != 'n' */
00223:     struct lmb *lmb; /* for memory mgmt *//* 逻辑内存块 */  
00224: } ? end bootm_headers ? bootm_headers_t;

------------------------------------------------------------------------------------------------------------------------------------------------------------

/*为是否对镜像头做校验做准备,读取uboot的环境变量verify如果环境变量verify等于'n',则局部变量verify赋值成为0;如果环境变量verify为空(即没有定义环境变量verify)或者环境变量verify不等于'n',则局部变量verify赋值成为1
00185:     images.lmb = &lmb;
00186:
00187:         lmb_init(&lmb);
00188:
00189:     mem_start = getenv_bootm_low();
00190:     mem_size = getenv_bootm_size();
00191:
00192:     lmb_add(&lmb, (phys_addr_t)mem_start, mem_size);
00193:
00194:     board_lmb_reserve(&lmb);
00195:
00196:     #ifdef CONFIG_ZIMAGE_BOOT

9.vmlinuz和zImage和uImage

1)uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序类似于windows下的exe格式,在操作系统下是可以直接执行的。但是这种格式不能用来烧录下载。我们用来烧录下载的是u-boot.bin,这个东西是由u-boot使用arm-linux-objcopy工具进行加工(主要目的是去掉一些无用的)得到的。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到iNand中执行的。

2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的原版内核elf文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),经过制作加工成烧录镜像的文件就叫Image(制作把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。

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

4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。

5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。

10.编译内核得到uImage去启动

如果直接在kernel底下去make uImage会提供mkimage command not found。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。

11. zImage启动细节

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

00197:     #define LINUX_ZIMAGE_MAGIC 0x016f2818

12.LINUX_ZIMAGE_MAGIC

1)这个是一个定义的魔数,这个数等于0x016f2818,表示这个镜像是一个zImage。也就是说zImage格式的镜像中在头部的一个固定位置存放了这个数作为格式标记。如果我们拿到了一个image,去他的那个位置去取4字节判断它是否等于LINUX_ZIMAGE_MAGIC,则可以知道这个镜像是不是一个zImage

3)zImage头部开始的第37-40字节处存放着zImage标志魔数,从这个位置取出然后对比LINUX_ZIMAGE_MAGIC。可以用二进制阅读软件来打开zImage查看,就可以证明。很多软件都可以打开二进制文件,如winhexUltraEditor


00198:     
/* find out kernel image address */
00199:     if (argc < 2) {
00200:         addr = load_addr;

2)命令 bootm 0x30008000,所以do_boomargc=2argv[0]=bootm argv[1]=0x30008000。但是实际bootm命令还可以不带参数执行。如果不带参数直接bootm,则会从CFG_LOAD_ADDR地址去执行(定义在x210_sd.h中)。
00201:         debug ("* kernel: default image load address = 0x%08lx\n",load_addr);
00203:     } else {
00204:         addr = simple_strtoul(argv[1], NULL, 16);
00205:         debug ("* kernel: cmdline image address = 0x%08lx\n",img_addr);
00206:     }
00207:
00208:
00209:     if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC)
00209:     {
00210:         printf("Boot with zImage\n");
00211:         addr = virt_to_phys(addr);
00212:         hdr = (image_header_t *)addr;

1)这个数据结构是我们uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造。hdr->ih_os = IH_OS_LINUX;    hdr->ih_ep = ntohl(addr);这两句就是在进行改造。
00213:         hdr->ih_os = IH_OS_LINUX;
00214:         hdr->ih_ep = ntohl(addr);
00215:
00216:         memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
00217:
00218:         
/* save pointer to image header */
00219:         images.legacy_hdr_os = hdr;
00220:
00221:         images.legacy_hdr_valid = 1;
2)images
全局变量是
do_bootm
函数中使用,用来完成启动过程的。zImage的校验过程其实就是先确认是不是zImage,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量,然后就完成了校验。
00223:         goto
¯after_header_check;
00224:     }
00225:     #endif
00226:
00227:
/* get kernel image header, start address and length */
00228:     os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,&images, &os_data, &os_len);

-------------------------------------

00562: static void *boot_get_kernel (cmd_tbl_t *cmdtp,int flag, int argc, char *argv[],bootm_headers_t *images, ulong *os_data, ulong*os_len)
00564: {
00565:     image_header_t *hdr;
00566:     ulong img_addr;
00577:     
/* find out kernel image address */
00578:     if (argc < 2) {
00579:     img_addr = load_addr;
00580:     debug ("* kernel: default image load address = 0x%08lx\n",load_addr);

//argc < 2即直接输入bootm命令,此时映像存储地址为load_addr; 定义处有ulong load_addr = CFG_LOAD_ADDR; 即初始的映像存储地址,但在u-boot运行期间,load_addr的值会被环境变量所更改。若bootm后面还带了一参数,如bootm 30000,则映像的存储地址为0x30000接下来要从nand中读出kernel image时,就从地址0x3000开始读
00592:     } else {
00593:             img_addr = simple_strtoul(argv[1], NULL, 16);
00594:         debug ("* kernel: cmdline image address = 0x%08lx\n",img_addr);
00595:     }
00596:         show_boot_progress (1);
00598:
00599:     
/* copy from dataflash if needed */
00600:     img_addr = genimg_get_image (img_addr);
//
从存储介质中将
image
读出,img_addr是前面获得的映像存储地址。这个函数体内被CONFIG_HAS_DATAFLASH 包围,是指kernel存于atmel的数据flash.由于我这里只有nand,所以这个函数相当于空。在运行bootm命令之前,一般使用tftp or nand read.jffs2 kernel image 先读到sdram中,所以此时kernel image 已经在sdram中了

00602: /* check image type, for FIT images get FIT kernel node */
00603:     *os_data = *os_len = 0;
00604:         switch (genimg_get_format ((void *)img_addr)) {

//判断imgae的格式,通过判断头信息中的幻数来确断格式,格式有三种
// IMAGE_FORMAT_LEGACY
// IMAGE_FORMAT_FIT  
这里不支持,在预定义里就没有定义 CONFIG_FIT这项
// IMAGE_FORMAT_INVALID
无效的格式

00605:         case IMAGE_FORMAT_LEGACY:
00606:         printf ("## Booting kernel from Legacy Image at %08lx ...\n",img_addr);
00608:         hdr = image_get_kernel (img_addr, images->verify);

// 检查头信息,函数内做了以下几件事
// image_check_magic
再次检查幻数
// image_check_hcrc 
头信息校验
// image_print_contents (hdr);
打印头信息
// image_check_dcrc
数据校验,这里是校验kernel实际的数据
// image_check_target_arch
检查运行的cpu架构

00609:         if (!hdr)
00610:         return NULL;
00611:         show_boot_progress (5);

00613:
/* get os_data and os_len */
00614:         switch (image_get_type (hdr)) {
00615:             case IH_TYPE_KERNEL:
00616:                 *os_data = image_get_data (hdr);
00617:                 *os_len = image_get_data_size (hdr);
00618:                 break;
00619:             case IH_TYPE_MULTI:
00620:                 image_multi_getimg (hdr, 0, os_data,os_len);
00621:                 break;
00622:             default:
00623:                 printf ("Wrong Image Type for %s command\n", cmdtp->name);
00624:     show_boot_progress (-5);
00625:     return NULL;
00626:         }
//
这里先检查映像的类型,有
kernel, ramdisk, multi, firmware, script等,
//
根据不同的类型来获得真正的image data(即不包括头信息的imgae)的起始地址(os_data) 和大小
(os_len)
00628:
/*
00629: * copy image header to allow for image overwrites dur
00629: ing kernel
00630: * decompression.
00631: */
00632:     memmove (&images->legacy_hdr_os_copy, hdr,sizeof(image_header_t));
//
再次来看下
images

------------------------------------------------------------------------------------------------------------------------------------------------------------

00123:static bootm_headers_t images;

00194: typedef struct bootm_headers {
00195:
/*
00196: * Legacy os image header, if it is a multi component imagethen boot_get_ramdisk() and get_fdt() will attempt to get
00198: * data from second and third component accordingly.*/
00200:         image_header_t *legacy_hdr_os; /* 指向镜像头的指针 */  
00200:     
/* image header pointer */
00201:     image_header_t legacy_hdr_os_copy; /* header copy *//* 镜像头的备份 */ 
00202:     ulong legacy_hdr_valid;  /* 镜像头存在标记 */ 
00221:
00222:     int
verify; /* getenv("verify")[0] != 'n' */
00223:     struct lmb *lmb; /* for memory mgmt *//* 逻辑内存块 */  
00224: } ? end bootm_headers ? bootm_headers_t;

------------------------------------------------------------------------------------------------------------------------------------------------------------

// 可知上面的memmove实际上是将头信息都保存到images legacy_hdr_os_copy成员中
00634:     
/* save pointer to image header */
00635:     images->legacy_hdr_os = hdr;
//
保存
kernel image
头信息的地址
00637:     images->legacy_hdr_valid = 1;

// 映像文件是有效的
00638:     show_boot_progress (6);
00639:         break;

00705: default:
00706:     printf ("Wrong Image Format for %s command\n",cmdtp->name);
00707:         show_boot_progress (-108);
00708:     return NULL;
00709:     }
? end switch genimg_get_format((vo... ?
00710:
00711:     debug (" kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",*os_data, *os_len, *os_len);
00713:
00714:         return (void *)img_addr;

//最后返回映像文件的地址

//小结:从上面的分析可以看出 boot_get_kernel 的作用就是找到kernel image,并通过头信息的检查来判断其是否有效。最后返回kernel image的地址给do_bootm
00715: }
? end boot_get_kernel ?

----------------------------------------
00230:     if (os_len == 0)

{
00231:             puts ("ERROR: can't get kernel image!\n");
00232:         return 1;
00233:     }
00234:
00235:     
/* get image parameters */
00236:     switch (genimg_get_format (os_hdr)) {//boot_get_kernel中做过一次,检查幻数,判断image
00237:         case IMAGE_FORMAT_LEGACY://uImage
的方式

00238:             type = image_get_type (os_hdr);
// 映像类型
00239:             comp = image_get_comp (os_hdr);
// 压缩方式
00240:             os = image_get_os (os_hdr);
// 操作系统类型
00241:

00242:             image_end = image_get_image_end (os_hdr); // 结束地址
00243:             load_start = image_get_load (os_hdr);
// 头信息中定义的加载地址
00244:             break;
00278:            default:
00279:                 puts ("ERROR: unknown image format type!\n");
00280:         return 1;
00281:         }
? end switch genimg_get_format(os_... ?
00282:
00283:     image_start = (ulong)os_hdr;
// 映像起始地址,包括头信息
00284:     load_end = 0;
00285:         type_name = genimg_get_type_name (type);
// 映像名
00286:
00287:
/*
00288: * We have reached the point of no return: we are going to
00289: * overwrite all exception vector code, so we cannot easily

00290: * recover from any failures any more...
00291: */
00292:     iflag = disable_interrupts();// 关中断,这里为空,中断未使能
00318:
00319:     switch (comp)
//根据压缩方式来选择将要执行的动作

    {
00320:             case IH_COMP_NONE:
00321:                 if (load_start == (ulong)os_hdr)
// 头信息中的加载地址是否等于现在所在的地址

{
00322:                     printf (" XIP %s ... ", type_name);
00323:             } else

{
00324:                     printf (" Loading %s ... ", type_name);
00326:                 memmove_wd ((void *)load_start,
// 若不等的话,还要先将image复制到// 头信息指定的加载地址处
00327:                 (void *)os_data, os_len, CHUNKSZ);
00328:             }
00329:             load_end = load_start + os_len;
// 加载后,整个image的结束地址
00330:                 puts("OK\n");
00331:                break;
00332:             case IH_COMP_GZIP:
// gzip的压缩方式

00333:                 printf (" Uncompressing %s ... ", type_name);
00334:                 if (gunzip ((void *)load_start, unc_len, (uchar *)os_data, &os_len) != 0)
00335:                 {
// 先将image全部解压到头信息中指定的加载地址处
00336:                     puts ("GUNZIP: uncompress or overwrite error ""- must RESET board to recover\n");
00338:                     show_boot_progress (-6);
00339:                     do_reset (cmdtp, flag, argc, argv);
00340:                 }
00341:
00342:                 load_end = load_start + os_len;
// 加载后,整个image的结束地址
00343:                break;
00344:         default:

00366:             if (iflag) enable_interrupts();
00368:             printf ("Unimplemented compression type %d\n",comp);
00369:             show_boot_progress (-7);
00370:             return 1;
00371:         }
? end switch comp ?
00372:     puts ("OK\n");
00373:     debug (" kernel loaded at 0x%08lx, end = 0x%08lx\n",load_start, load_end);
00374:     show_boot_progress (7);
00375:
00376:     if ((load_start < image_end) && (load_end >image_start))

{debug ("image_start = 0x%lX, image_end = 0x%lx\n",image_start, image_end);
00378:         debug ("load_start = 0x%lx, load_end = 0x%lx\n", load_start,load_end);
00379:
00380:         if (images.legacy_hdr_valid)

{
00381:             if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
00382:             puts ("WARNING: legacy format multi component ""image overwritten\n");
00384:         } else {
00385:             puts ("ERROR: new format image overwritten - ""must RESET the board to recover\n");
00387:             show_boot_progress (-113);
00388:             do_reset (cmdtp, flag, argc, argv);
00389:         }
00390:     }
// load_start
load_end 是头信息中预定的
// image_start
image_end 是映像文件在内存中放的位置
//
这里主要判断存放地址和加载地址是否有重叠,因为重叠会造成未知的系统崩溃

00392:     show_boot_progress (8);
00393:
00394:         lmb_reserve(&lmb, load_start, (load_end -load_start));
00395:
00396:     #ifdefined(CONFIG_ZIMAGE_BOOT)
00397: after_header_check:
00398:     os = hdr->ih_os;
00399:     #endif
//
上面可以很清楚的看到,根据头信息中定义的操作系统类型,进入相应的入口中这里Linux就是 do_bootm_linux(cmdtp, flag, argc, argv, &images)
00401:     switch (os)

{
00402:         default:
00402:
/* handled by (original) Linux case */

00403:         case IH_OS_LINUX:
00404:                do_bootm_linux (cmdtp, flag, argc, argv, &images);
00408:             break;

总结2:第二阶段校验头信息结束,下面进入第三阶段,第三阶段主要任务是启动linux内核,调用do_bootm_linux函数来完成。

-----------------------------------

00061: void do_bootm_linux (cmd_tbl_t *cmdtp, int flag,int argc, char *argv[],bootm_headers_t *images)
00063: {
00064:         ulong
initrd_start, initrd_end;
00065:     ulong
ep = 0;
00066:     bd_t *
bd = gd->bd;
00067:     char *
s;
00068:     int
machid = bd->bi_arch_number;
00069:     void (*
theKernel)(int zero, int arch, uintparams);
00070:     int
ret;
00071:
00072:     
#ifdef CONFIG_CMDLINE_TAG
00073:         char *
commandline = getenv ("bootargs"); / 启动时的命令行参数
00074:     
#endif
00075:
00076:
/* find kernel entry point */

1)ep就是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像开头有n个字节的头信息),真正的镜像文件执行时第一句代码在镜像的中部某个字节处,相当于头是有一定的偏移量的。这个偏移量记录在头信息中。

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

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

00077:     if (images->legacy_hdr_valid) {
00078:         ep = image_get_ep (&
images->legacy_hdr_os_copy);
00079:     }
else {
00089:         
puts ("Could not find kernel entry point!\n");
00090:         
goto ¯error;
00091:     }
00092:     theKernel = (void (*)(int, int, uint))ep;
// 终于看到theKernel了,给函数指针赋地址值
00094:     s =
getenv ("machid");
00095:     
if (s) {
00096:     machid =
simple_strtoul (s, NULL, 16);

uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码是怎么确定的?第一顺序备选是环境变量machid,第二顺序备选是gd->bd->bi_arch_numx210_sd.h中硬编码配置的)
00097:     
printf ("Using machid 0x%x from environment\n", machid);
00098:     }
// 检查是否还有ramdisk,如果有的话,则将其加载到指定地址
00100:     ret =
boot_get_ramdisk
(argc, argv, images,IH_ARCH_ARM,&initrd_start, &initrd_end);
00102:        
if (ret)//只有当有
ramdisk
,且加载
ramdisk
失败时才返回
1
00103:     
goto ¯error;
00104:
00105:        
show_boot_progress (15);
00106:
00107:     
debug ("## Transferring control to Linux (at address %08lx) .
00108: (ulong) theKernel);
1)
110
行到
144
行就是
uboot
在给
linux
内核准备传递的参数处理。

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

00110: #if defined (CONFIG_SETUP_MEMORY_TAGS) || \
00111: defined (CONFIG_CMDLINE_TAG) || \
00112: defined (CONFIG_INITRD_TAG) || \
00113: defined (CONFIG_SERIAL_TAG) || \
00114: defined (CONFIG_REVISION_TAG) || \
00115: defined (CONFIG_LCD) || \
00116: defined (CONFIG_VFD) || \
00117: defined (CONFIG_MTDPARTITION)
00118:     
setup_start_tag (bd);

传参详解

1tag方式传参

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

2)tag_headertag_xxxtag_header中有这个tagsize和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。

3)tag_starttag_endkernel接收到的传参是若干个tag构成的,这些tagtag_start起始,到tag_end结束。

4)tag传参的方式是由linux kernel发明的,kernel定义了这种向我传参的方式,uboot只是实现了这种传参方式从而可以支持给kernel传参。

2x210_sd.h中配置传参宏

1)CONFIG_SETUP_MEMORY_TAGStag_mem,传参内容是内存配置信息。

2)CONFIG_CMDLINE_TAGtag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.

3)CONFIG_INITRD_TAG

4)CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。

5)起始tagATAG_CORE、结束tagATAG_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的首地址。

00119:     
#ifdef CONFIG_SERIAL_TAG
00120:         
setup_serial_tag (&params);
00121:     
#endif

00122:     #ifdef CONFIG_REVISION_TAG
00123:         
setup_revision_tag (&params);
00124:     
#endif
00125:     #ifdef CONFIG_SETUP_MEMORY_TAGS
00126:         
setup_memory_tags (bd);
00127:     
#endif
00128:         #ifdef CONFIG_CMDLINE_TAG
00129:            
setup_commandline_tag (bd, commandline);
00130:     
#endif
00131:         #ifdef CONFIG_INITRD_TAG
00132:         
if (initrd_start && initrd_end)
00133:             
setup_initrd_tag (bd, initrd_start, initrd_end);
00134:             
#endif
00135:         #if defined (CONFIG_VFD) || defined (CONFIG_LCD)
00136:             
setup_videolfb_tag ((gd_t *) gd);
00137:         
#endif
00138:
00139:         
#ifdef CONFIG_MTDPARTITION
00140:             
setup_mtdpartition_tag();
00141:         
#endif
00142:
00143:             
setup_end_tag (bd);
00144:     
#endif
00145:
00146:        
/* we assume that the kernel is in place */
00147:         printf ("\nStarting kernel ...\n\n");

/*
     *
在进入内核前要做几件事
     *
关中断 关
I/D-cache 
     */

00156:     
cleanup_before_linux ();
00157:
00158:     theKernel (0, machid, bd->bi_boot_params);

   /*
     *
进入内核,注意传递的几个参数

     * 1. 0
     * 2. mach id 
     * 3.
参数taglist地址
     */

00159:        
/* does not return */
00160: return;
00161:
00162:
error:
00163:     
do_reset (cmdtp, flag, argc, argv);
00164:
return;
00165: }
? end do_bootm_linux ?

-----------------------------------


00439:     }
? end switch os ?
00440:
00441:     show_boot_progress (-9);
00446:     if (iflag)
00447:     enable_interrupts();
00448:
00449:     return 1;
00450: }
? end do_bootm ?

1)uboot移植时一般只需要配置相应的宏即可

2)kernel启动不成功,注意传参是否成功。传参不成功首先看ubootbootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。

uboot启动内核的总结

1.启动4步骤

第一步:将内核搬移到DDR

第二步:校验内核格式、CRC

第三步:准备传参

第四步:跳转执行内核

2.涉及到的主要函数是:do_boomdo_bootm_linux

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

4.跳转与函数指针的方式运行内核

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值