bootm 用于将内核镜像加载到内存的指定地址处,如果有需要还要解压镜像,然后根据操作系统和体系结构的不同给内核传递不同的启动参数,最后启动内核。
一、arm 架构处理器对 linux 内核启动之前环境的五点需求
1、cpu 寄存器设置
* R0 = 0
* R1 = 板级 id
* R2 = 启动参数在内存中的起始地址
2、cpu 模式
* 禁止所有中断
* 必须为SVC(超级用户)模式
3、缓存、MMU
* 关闭 MMU
* 指令缓存可以开启或者关闭
* 数据缓存必须关闭并且不能包含任何脏数据
4、设备
* DMA 设备应当停止工作
5、boot loader 需要跳转到内核镜像的第一条指令处
这些需求都由 boot loader 实现,在常用的 uboot 中完成一系列的初始化后最后通过 bootm 命令加载 linux 内核。该命令用法介绍如下:
# help bootm bootm - boot application image from memory Usage: bootm [addr [arg ...]] - boot application image stored in memory passing arguments 'arg ...'; when booting a Linux kernel, 'arg' can be the address of an initrd image Sub-commands to do part of the bootm sequence. The sub-commands must be issued in the order below (it's ok to not issue all sub-commands): start [addr [arg ...]] loados - load OS image cmdline - OS specific command line processing/setup bdt - OS specific bd_t processing prep - OS specific prep before relocation or go go - start OS二、基础数据结构
在 bootm 中常用的数据结构有 image_info_t 和 bootm_headers_t,定义如下:
/* 镜像信息 */ typedef struct image_info { ulong start, end; /* start/end of blob */ ulong image_start, image_len; /* start of image within blob, len of image */ ulong load; /* load addr for the image */ uint8_t comp, type, os; /* compression, type of image, os type */ } image_info_t; /* bootm 头 */ typedef struct bootm_headers { image_header_t *legacy_hdr_os; /* 指向镜像头的指针 */ image_header_t legacy_hdr_os_copy; /* 镜像头的备份 */ ulong legacy_hdr_valid; /* 镜像头存在标记 */ image_info_t os; /* 系统镜像信息 */ ulong ep; /* 系统入口地址 */ ulong rd_start, rd_end; /* 虚拟磁盘起始地址 */ ulong ft_len; /* 平坦设备树的长度 */ ulong initrd_start; ulong initrd_end; ulong cmdline_start; ulong cmdline_end; bd_t *kbd; int verify; /* getenv("verify")[0] != 'n' */ #define BOOTM_STATE_START (0x00000001) #define BOOTM_STATE_LOADOS (0x00000002) #define BOOTM_STATE_RAMDISK (0x00000004) #define BOOTM_STATE_FDT (0x00000008) #define BOOTM_STATE_OS_CMDLINE (0x00000010) #define BOOTM_STATE_OS_BD_T (0x00000020) #define BOOTM_STATE_OS_PREP (0x00000040) #define BOOTM_STATE_OS_GO (0x00000080) int state; /* 状态标记 */ struct lmb lmb; /* 逻辑内存块 */ } bootm_headers_t;三、代码解析
bootm 的主函数为 do_bootm,代码如下:
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { ulong iflag; ulong load_end = 0; int ret; boot_os_fn *boot_fn; if (bootm_start(cmdtp, flag, argc, argv)) /* 获取镜像信息 */ return 1; iflag = disable_interrupts(); /* 关闭中断 */ usb_stop(); /* 关闭usb设备 */ ret = bootm_load_os(images.os, &load_end, 1); /* 加载内核 */ lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load)); if (images.os.os == IH_OS_LINUX) /* 如果有需要关闭内核的串口 */ fixup_silent_linux(); boot_fn = boot_os[images.os.os]; /* 获取启动函数 */ arch_preboot_os(); /* 启动前准备 */ boot_fn(0, argc, argv, &images); /* 启动,不再返回 */ puts ("\n## Control returned to monitor - resetting...\n"); do_reset (cmdtp, flag, argc, argv); return 1; }该函数的实现分为 3 个部分:首先通过 bootm_start 函数分析镜像的信息,如果满足判定条件则进入 bootm_load_os 函数进行加载,加载完成后就可以调用 boot_fn 开始启动。
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { ulong mem_start; phys_size_t mem_size; void *os_hdr; int ret; memset ((void *)&images, 0, sizeof (images)); images.verify = getenv_yesno ("verify"); /* 获取环境变量 */ lmb_init(&images.lmb); mem_start = getenv_bootm_low(); mem_size = getenv_bootm_size(); lmb_add(&images.lmb, (phys_addr_t)mem_start, mem_size); arch_lmb_reserve(&images.lmb); board_lmb_reserve(&images.lmb); /* 获取镜像头,加载地址,长度 */ 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; } /* 获取镜像参数 */ 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; } /* 查询内核入口地址 */ if (images.legacy_hdr_valid) { images.ep = image_get_ep (&images.legacy_hdr_os_copy); } else { puts ("Could not find kernel entry point!\n"); return 1; } if (images.os.os == IH_OS_LINUX) { /* 查询是否存在虚拟磁盘 */ 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; }该函数主要进行镜像的有效性判定、校验、计算入口地址等操作,大部分工作通过 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)) { /* 魔数比较 */ puts ("Bad Magic Number\n"); show_boot_progress (-1); return NULL; } show_boot_progress (2); if (!image_check_hcrc (hdr)) { /* 镜像头校验和 */ puts ("Bad Header Checksum\n"); show_boot_progress (-2); return NULL; } show_boot_progress (3); image_print_contents (hdr); /* 打印镜像头信息 */ 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)) { /* 处理器类型比较 */ printf ("Unsupported Architecture 0x%x\n", image_get_arch (hdr)); show_boot_progress (-4); return NULL; } return hdr; } 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) { ulong img_addr; image_header_t *hdr; if (argc < 2) { /* 如果没有参数则用默认加载地址 */ img_addr = load_addr; debug ("* kernel: default image load address = 0x%08lx\n", load_addr); } else { img_addr = simple_strtoul(argv[1], NULL, 16); debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr); } *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); /* 获取内核 */ 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; /* 返回加载地址 */ }
在有效性判定完成时会打印出镜像的一些信息,代码如下:
void image_print_contents (const void *ptr) { const image_header_t *hdr = (const image_header_t *)ptr; const char *p; p = " "; printf ("%sImage Name: %.*s\n", p, IH_NMLEN, image_get_name (hdr)); /* 镜像名称 */ #if defined(CONFIG_TIMESTAMP) || defined(CONFIG_CMD_DATE) || defined(USE_HOSTCC) printf ("%sCreated: ", p); /* 创建时间 */ genimg_print_time ((time_t)image_get_time (hdr)); #endif printf ("%sImage Type: ", p); /* 镜像类型 */ image_print_type (hdr); printf ("%sData Size: ", p); /* 镜像大小 */ genimg_print_size (image_get_data_size (hdr)); printf ("%sLoad Address: %08x\n", p, image_get_load (hdr)); /* 加载地址 */ printf ("%sEntry Point: %08x\n", p, image_get_ep (hdr)); /* 入口地址 */ }
2、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) { /* 判断是否需要移动镜像 */ printf (" XIP %s ... ", type_name); } else { printf (" Loading %s ... ", type_name); if (load != image_start) { memmove_wd ((void *)load, (void *)image_start, image_len, CHUNKSZ); } } *load_end = load + image_len; puts("OK\n"); break; 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; } *load_end = load + image_len; break; ... default: printf ("Unimplemented compression type %d\n", comp); return BOOTM_ERR_UNIMPLEMENTED; } puts ("OK\n"); debug (" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end); if ((load < blob_end) && (*load_end > blob_start)) { debug ("images.os.start = 0x%lX, images.os.end = 0x%lx\n", blob_start, blob_end); debug ("images.os.load = 0x%lx, load_end = 0x%lx\n", load, *load_end); return BOOTM_ERR_OVERLAP; } return 0; }3、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 (¶ms); /* 初始化串口参数 */ setup_revision_tag (¶ms); /* 初始化版本参数 */ 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; }
这里还列举部分启动参数的初始化函数:
static void setup_start_tag (bd_t *bd) { params = (struct tag *) bd->bi_boot_params; /* 启动参数保存在板子指定的内存空间 CONFIG_ATAG_ADDR */ 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); /* params指向下一个结构 */ } static void setup_memory_tags (bd_t *bd) { int i; for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { params->hdr.tag = ATAG_MEM; /* 内存参数的标签 */ params->hdr.size = tag_size (tag_mem32); params->u.mem.start = bd->bi_dram[i].start; /* 物理内存起始地址 */ params->u.mem.size = bd->bi_dram[i].size; /* 物理内存结束地址 */ params = tag_next (params); } } static void setup_commandline_tag (bd_t *bd, char *commandline) { char *p; if (!commandline) return; for (p = commandline; *p == ' '; p++); /* 定位到第一个有效字符 */ if (*p == '\0') /* 没有有效字符则返回 */ return; params->hdr.tag = ATAG_CMDLINE; /* 命令参数的标签 */ params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2; /* 整个标签的长度 */ strcpy (params->u.cmdline.cmdline, p); /* 将命令参数拷贝到标签,结束符为'\0' */ params = tag_next (params); } static void setup_end_tag (bd_t *bd) { params->hdr.tag = ATAG_NONE; /* 结束标签 */ params->hdr.size = 0; }