do_bootm 相关函数分析

首先有函数do_bootm,在这个函数一开始的地方,重定位boot的功能表:

/* relocate boot function table */
	if (!relocated) {
		int i;
		for (i = 0; i < ARRAY_SIZE(boot_os); i++)
			if (boot_os[i] != NULL)
				boot_os[i] += gd->reloc_off;
		relocated = 1;
	}

******************************************************************
    
在boot_os[]中,有对操作系统的一些定义,我主要关注的是linux方面的:

boot_os_fn * boot_os[] = {
#ifdef CONFIG_BOOTM_LINUX
	[IH_OS_LINUX] = do_bootm_linux,
#endif
...
};

对于宏定义CONFIG_BOOTM_LINUX有:#define CONFIG_BOOTM_LINUX 1;


*********************************************************************

接下来是对bootm命令的一个分类操作:
bootm的第一个参数是映像存储的地址。
Linux可以附带一个参数。此参数会被认为是一个initrd的起始地址,此时bootm命令有三个
步骤:首先解压Linux内核映像并将其复制到ram中,然后将ramdisk映像加载到ram中,最后将控制
权交给内核,并向内核传递ramdisk的位置和大小等信息.
单单用来启动Linux内核,而没有initrd时,可用如下命令:
 => bootm ${kernel_addr}  //do_bootm_subcommand,在这个函数中集合了一些函数,在执行boot的时候进行默认操作
如果还有initrd,则可使用下面的命令:
 => bootm ${kernel_addr} ${ramdisk_addr}
 
 **************************************************************************
然后就用到函数bootm_start啦,他用在一个if语句中:

if (bootm_start(cmdtp, flag, argc, argv))
		return 1;

 下面就来分析一下这个函数:其中有一部分代码被我省略了

  static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])  
    {  
        void        *os_hdr;  
        int     ret;  
        memset ((void *)&images, 0, sizeof (images));//images是一个bootm_headers_t类型的全局变量。  
        images.verify = getenv_yesno ("verify");//从环境变量中检查是否要对镜像的数据(不是镜像头)进行校验。  
        lmb_init(&images.lmb);//创建一个虚拟的零大小的LMB,稍后将被合并。
        /* get kernel image header, start address and length */寻找可用的内核镜像。主要根据传入的参数检查镜像的合法性并获取信息。  
        os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,  
                &images, &images.os.image_start, &images.os.image_len);//返回指向内存中镜像头的指针  
        if (images.os.image_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:  
            images.os.type = image_get_type (os_hdr);//镜像类型  
            images.os.comp = image_get_comp (os_hdr);//压缩类型  
            images.os.os = image_get_os (os_hdr);//操作系统类型  
            images.os.end = image_get_image_end (os_hdr);//当前镜像的尾地址  
            images.os.load = image_get_load (os_hdr);//镜像数据的载入地址  
            break;  
        default:  
            puts ("ERROR: unknown image format type!/n");  
            return 1;  
        }  
        /* find kernel entry point */  
        if (images.legacy_hdr_valid) {//如果镜像已经通过验证  
            images.ep = image_get_ep (&images.legacy_hdr_os_copy);//获取入口地址,填充images.ep 。  
        } else {  
            puts ("Could not find kernel entry point!/n");  
            return 1;  
        }  
       if (images.os.os == IH_OS_LINUX) {
		/* find ramdisk */
		ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,
				&images.rd_start, &images.rd_end);
			if (ret) {
				puts ("Ramdisk image is corrupt or invalid\n");
				return 1;
			}
	   }
        images.os.start = (ulong)os_hdr;//指向内存中镜像的头地址  
        images.state = BOOTM_STATE_START;//标记引导状态  
        return 0;  
    }  

从这段代码中,可以看到在bootm_start中,主要是对镜像文件进行了一些操作,用来获取镜像的一些信息,以用于接下来的步骤。

*********************************************************************************************

接下来是函数:bootm_load_os
这个函数主要判段镜像是否需要解压,并且将镜像移动到加载地址;

static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress)  
    {  
        uint8_t comp = os.comp;         /* 压缩格式 */  
        ulong load = os.load;           /* 加载地址 */  
        ulong blob_start = os.start;    /* 镜像起始地址 */  
        ulong blob_end = os.end;        /* 镜像结束地址 */  
        ulong image_start = os.image_start;    /* 镜像起始地址 */  
        ulong image_len = os.image_len;        /* 镜像长度 */  
        uint unc_len = CONFIG_SYS_BOOTM_LEN;   /* 镜像最大长度 */  
      
        const char *type_name = genimg_get_type_name (os.type);  /* 镜像类型 */  
      
        switch (comp) {  /* 选择解压格式 */  
        case IH_COMP_NONE:  /* 镜像没有压缩过 */  
            if (load == blob_start)   /* 判断是否需要移动镜像 */  
           ...
        case IH_COMP_GZIP:  /* 镜像采用 gzip 解压 */  
            printf ("   Uncompressing %s ... ", type_name);  
            if (gunzip ((void *)load, unc_len, (uchar *)image_start, &image_len) != 0) {  /* 解压 */  
                puts ("GUNZIP: uncompress, out-of-mem or overwrite error "  
                    "- must RESET board to recover\n");  
                return BOOTM_ERR_RESET;  
            }  
			...
		}
    }  

*********************************************************************************************

在 bootm_start中用到的函数:boot_get_kernel

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)  
{  
    image_header_t  *hdr;    
    if (argc < 2) {  /* 如果没有参数则用默认加载地址 */  
        img_addr = 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 开始读
        debug ("*  kernel: default image load address = 0x%08lx\n", load_addr);  
    ... 
    }  
    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);  /* 获取内核 */  
        if (!hdr)  
            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;  
        default:  
            printf ("Wrong Image Type for %s command\n", cmdtp->name);  
            show_boot_progress (-5);  
            return NULL;  
        }  
        /* 备份镜像头以防止在内核解压时被覆盖 */  
        memmove (&images->legacy_hdr_os_copy, hdr, sizeof(image_header_t));  
        images->legacy_hdr_os = hdr;  
        images->legacy_hdr_valid = 1;  /* 标记存在入口点 */  
        break;  
    default:  
        printf ("Wrong Image Format for %s command\n", cmdtp->name);  
        show_boot_progress (-108);  
        return NULL;  
    }  
    debug ("   kernel data at 0x%08lx, len = 0x%08lx (%ld)\n", *os_data, *os_len, *os_len);  
    return (void *)img_addr;  /* 返回加载地址 */  
}  

 上面函数中用到的bootm_headers_t *images;
static bootm_headers_t images; 这个静态全局变量是头信息结构。

typedef struct bootm_headers {
	image_header_t	*legacy_hdr_os;		/* image header pointer */
	image_header_t	legacy_hdr_os_copy;	/* header copy */
	void		*fit_hdr_rd;	/* init ramdisk FIT image header */
	int		fit_noffset_rd;	/* init ramdisk subimage node offset */
	image_info_t	os;		/* os image info */
	ulong		ep;		/* entry point of OS */
	......
	#define	BOOTM_STATE_START	(0x00000001) //开始执行bootm的一些准备动作。
	#define	BOOTM_STATE_LOADOS	(0x00000002) //加载操作系统
	#define	BOOTM_STATE_RAMDISK	(0x00000004) // 操作ramdisk
	#define	BOOTM_STATE_FDT		(0x00000008) //操作FDT
	#define	BOOTM_STATE_OS_CMDLINE	(0x00000010) // 操作commandline
	#define	BOOTM_STATE_OS_BD_T	(0x00000020) //
	#define	BOOTM_STATE_OS_PREP	(0x00000040) //跳转到操作系统的前的准备动作
	#define	BOOTM_STATE_OS_GO	(0x00000080) //跳转到kernel中去
} bootm_headers_t;	

1)#define IH_MAGIC    0x27051956  -->> struct image_header -->> uint32_t    ih_magic
这是一个魔数,0x27051956表示这个镜像是zImage,也就是说zImage格式的镜像中,在头部的一个固定位置存放了这个数,作为格式标记,如果我们拿到了一个image,去他的那个位置去取4个字节,判断它是否等于这个魔数IH_MAGIC。则可以知道这个镜像是不是zImage。
2)image全局变量是在bootm函数中使用,目的是用来指向images,也就是用来完成启动的。zImage校验过程先确定是不是zImage,然后再修改zImage头信息,最后再用这个头信息去初始化image,然后完成了校验。
注意:uboot本身也只支持uImage方式启动的,后来有了设备树之后,就把uImage方式命名为legacy方式,fdt方式就命名为fit方式,于是乎多了#if  #endif添加代码。后来移植的人又为了省事添加了zImage的方式,又为了省事,添加了#if  #endif .

*********************************************************************************************
boot_get_kernel函数主要进行镜像的有效性判定、校验、计算入口地址等操作,大部分工作通过 boot_get_kernel -> image_get_kernel 完成;

static image_header_t *image_get_kernel (ulong img_addr, int verify)  
{  
    image_header_t *hdr = (image_header_t *)img_addr;  
    if (!image_check_magic(hdr)) {    //检查镜像头部的魔数是否等于 IH_MAGIC  
        puts ("Bad Magic Number/n");  
        show_boot_progress (-1);  
        return NULL;  
    }  
    show_boot_progress (2);  
    if (!image_check_hcrc (hdr)) {//检查镜像头部的校验码(hcrc为0时镜像头的校验码)  
        puts ("Bad Header Checksum/n");  
        show_boot_progress (-2);  
        return NULL;  
    }  
    show_boot_progress (3);  
    image_print_contents (hdr);//根据传入的镜像头地址,打印镜像的信息,见下面的分析。  
/* 
   Image Name:   Linux-3.0.35-2666-gbdde708
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    4056048 Bytes =  3.9 MB
   Load Address: 10008000
   Entry Point:  10008000
*/  
    if (verify) {//是否要对镜像的数据部分进行校验  
        puts ("   Verifying Checksum ... ");  
        if (!image_check_dcrc (hdr)) {  
            printf ("Bad Data CRC/n");  
            show_boot_progress (-3);  
            return NULL;  
        }  
        puts ("OK/n");  
    }  
    show_boot_progress (4);  
    if (!image_check_target_arch (hdr)) {//检查是否是IH_ARCH_ARM架构  
        printf ("Unsupported Architecture 0x%x/n", image_get_arch (hdr));  
        show_boot_progress (-4);  
        return NULL;  
    }  
    return hdr;  
}  

 其中用到的重要结构体:

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;

genimg_get_format(bootm_start)检查img header的头4个字节,代表image的类型,有2种,legacy和FIT,这里使用的legacy,头4个字节为0x27051956。
image_get_kernel则会来计算header的crc是否正确,然后获取image的type,根据type来获取os的len和data起始地址。
最后将hdr的数据拷贝到images的legacy_hdr_os_copy,防止kernel image在解压是覆盖掉hdr数据,保存hdr指针到legacy_hdr_os中,置位legacy_hdr_valid。

*********************************************************************************************

下一个函数:boot_fn(0, argc, argv, &images),调用操作系统启动函数,引导操作系统启动。
功能是根据全局static bootm_headers_t images结构体的image_info_t os域中记录的os类型来将一个特定OS的内核引导函数入口赋给boot_fn变量。此处引导的是Linux内核,那么boot_fn就是do_bootm_linux。

	boot_fn = boot_os[images.os.os];    //用来确定linux操作系统
	if (boot_fn == NULL) {
		if (iflag)
			enable_interrupts();
		printf ("ERROR: booting os '%s' (%d) is not supported\n",
			genimg_get_os_name(images.os.os), images.os.os);
		show_boot_progress (-8);
		return 1;
	}
	boot_fn(0, argc, argv, &images); //如果不出错的话,这个函数应该是不会在返回了,因为在这个函数中会将控制权交由OS的内核。对于引导Linux内核来说,这里其实就是调用do_bootm_linux。

*********************************************************************************************

下面就讲一讲函数do_bootm_linux,在 arm linux 平台中 boot_fn 函数指针指向的函数是位于 lib_arm/bootm.c 的 do_bootm_linux,这是内核启动前最后的一个函数,该函数主要完成启动参数的初始化,并将板子设定为满足内核启动的环境。

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)  
    {  
        bd_t  *bd = gd->bd;  
        char  *s;  
        int   machid = bd->bi_arch_number;  
        void  (*theKernel)(int zero, int arch, uint params);        
        char  *commandline = getenv ("bootargs");          /* 从环境变量中获取命令参数 */     
        if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))    /* 状态判定 */  
            return 1;  
        theKernel = (void (*)(int, int, uint))images->ep;  /* 内核入口函数 */     
        s = getenv ("machid");  /* 从环境变量中获取机器id */  
        if (s) {  
            machid = simple_strtoul (s, NULL, 16);  
            printf ("Using machid 0x%x from environment\n", machid);  
        }        
        debug ("## Transferring control to Linux (at address %08lx) ...\n", (ulong) theKernel);        
        /* 初始化启动参数 */  
        setup_start_tag (bd);                     /* 初始化参数列表起始符 */  
        setup_serial_tag (&params);               /* 初始化串口参数 */  
        setup_revision_tag (&params);             /* 初始化版本参数 */  
		setup_revision_tag (&params);             /* 初始化版本参数 */  
        setup_memory_tags (bd);                   /* 初始化内存参数 */  
        setup_commandline_tag (bd, commandline);  /* 初始化命令参数 */  
        if (images->rd_start && images->rd_end)  
            setup_initrd_tag (bd, images->rd_start, images->rd_end);  /* 初始化虚拟磁盘参数 */  
        setup_videolfb_tag ((gd_t *) gd);         /* 初始化fb参数 */  
        setup_end_tag (bd);                       /* 初始化参数列表结束符 */       
        printf ("\nStarting kernel ...\n\n");       
        cleanup_before_linux ();  /* 启动前清空缓存 */       
        /* 启动内核,满足arm架构linux内核启动时的寄存器设置条件:第一个参数为0 
           第二个参数为板子id需与内核中的id匹配,第三个参数为启动参数地址 */  
        theKernel (0, machid, bd->bi_boot_params);     /* does not return */      
        return 1;  
    }  

猜你喜欢

转载自blog.csdn.net/qq_38022972/article/details/81222527