bootloader 要想启动内核,可以直接跳到内核的第一个指令处,即内核的起始地址,这样便可以完成内核的启动工作了。但是要想启动内核还需要满足一些条件,如下所示:
1、cpu 寄存器设置
* R0 = 0
* R1 = 机器类型 id
* R2 = 启动参数在内存中的起始地址
2、cpu 模式
* 禁止所有中断
* 必须为SVC(超级用户)模式
3、Cache、MMU
* 关闭 MMU
* 指令Cache可以开启或者关闭
* 数据Cache必须关闭
4、设备
* DMA 设备应当停止工作
5、PC为内核的起始地址
这些需求都由 boot loader 实现,在常用的 uboot 中完成一系列的初始化后最后通过 bootm 命令加载 linux 内核。bootm 向将内核映像从各种媒介中读出,存放在指定的位置;然后设置标记列表给内核传递参数;最后跳到内核的入口点去执行。
Uboot版本:u-boot-2013.01
一、bootm命令用法介绍如下:
在 common/cmd_bootm.c 中可以看到bootm 的定义:
可以看到 bootm 命令使调用了do_bootm 函数。
do_bootm 函数
在cmd_bootm.c 第586行可以看到do_bootm函数的定义(为方便阅读,对其中一些代码进行了删减,完整代码请阅读uboot源码):
- /*******************************************************************/
- /* bootm - boot application image from image in memory */
- /*******************************************************************/
- int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const 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.type == IH_TYPE_STANDALONE) {//如有需要,关闭内核的串口
- if (iflag)
- enable_interrupts();
- /* This may return when 'autostart' is 'no' */
- bootm_start_standalone(iflag, argc, argv);
- return 0;
- }
- boot_fn = boot_os[images.os.os];//获取启动参数
- arch_preboot_os();//启动前准备
- boot_fn(0, argc, argv, &images);//启动,不再返回
- #ifdef DEBUG
- puts("\n## Control returned to monitor - resetting...\n");
- #endif
- do_reset(cmdtp, flag, argc, argv);
- return 1;
- }
a -- 首先通过 bootm_start 函数分析镜像的信息;
b -- 如果满足判定条件则进入 bootm_load_os 函数进行加载;
c -- 加载完成后就可以调用 boot_fn 开始启动。
1、bootm_start
在cmd_bootm.c 第193行可以看到bootm_start函数的定义, 主要作用是填充内核相关信息
- static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
- {
- void *os_hdr;
- int ret;
- memset((void *)&images, 0, sizeof(images));
- images.verify = getenv_yesno("verify");//获取环境变量
- boot_start_lmb(&images);
- bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
- /*获取镜像头,加载地址,长度 */
- 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;
- /* 查询内核入口地址*/
- 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.type == IH_TYPE_KERNEL_NOLOAD) {
- images.os.load = images.os.image_start;
- images.ep += images.os.load;
- }
- if (((images.os.type == IH_TYPE_KERNEL) ||
- (images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
- (images.os.type == IH_TYPE_MULTI)) &&
- (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;
- }
- #if defined(CONFIG_OF_LIBFDT)
- /* 找到设备树,设备树是linux 3.XX版本特有的 */
- ret = boot_get_fdt(flag, argc, argv, &images,
- &images.ft_addr, &images.ft_len);
- if (ret) {
- puts("Could not find a valid device tree\n");
- return 1;
- }
- set_working_fdt_addr(images.ft_addr);
- #endif
- }
- images.os.start = (ulong)os_hdr;//赋值加载地址
- images.state = BOOTM_STATE_START;//更新状态
- return 0;
- }
2、bootm_load_os
在cmd_bootm.c 第317行可以看到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;
- }
在bootm_load_os 执行结束后,回到do_bootm 函数,调用boot_fn 运行linux 内核;
boot_os 为函数指针数组,在cmd_bootm.c 136行有定义
可以看出 boot_fn 函数指针指向的函数是位于 arch/arm/lib/bootm.c的 do_bootm_linux,这是内核启动前最后的一个函数,该函数主要完成启动参数的初始化,并将板子设定为满足内核启动的环境,代码如下:
可以看到 do_bootm_linux 实际调用的是 boot_jump_linux 函数。
4、boot_jump_linux
在arch/arm/lib/bootm.c 下第326行有定义
- /* Subcommand: GO */
- static void boot_jump_linux(bootm_headers_t *images)
- {
- unsigned long machid = gd->bd->bi_arch_number;//获取机器码
- char *s;
- void (*kernel_entry)(int zero, int arch, uint params);//内核入口函数
- unsigned long r2;
- kernel_entry = (void (*)(int, int, uint))images->ep;
- s = getenv("machid");//从环境变量中获取机器码
- if (s) {
- strict_strtoul(s, 16, &machid);
- printf("Using machid 0x%lx from environment\n", machid);
- }
- debug("## Transferring control to Linux (at address %08lx)" \
- "...\n", (ulong) kernel_entry);
- bootstage_mark(BOOTSTAGE_ID_RUN_OS);
- announce_and_cleanup();
- #ifdef CONFIG_OF_LIBFDT
- if (images->ft_len)
- r2 = (unsigned long)images->ft_addr;
- else
- #endif
- r2 = gd->bd->bi_boot_params;//将启动参数地址赋给 r2
- kernel_entry(0, machid, r2);
- }
kernel_entry(0, machid, r2)
真正将控制权交给内核, 启动内核;
满足arm架构linux内核启动时的寄存器设置条件:第一个参数为0 ;第二个参数为板子id需与内核中的id匹配,第三个参数为启动参数地址 。
二、为内核设置启动参数
Uboot 也是通过标记列表向内核传递参数,标记在源代码中定义为tag,是一个结构体,在arch/arm/include/asm/setup.h 中定义。
tag_header 结构体定义如下:
在一些内存标记、命令行标记的示例代码就是取自Uboot 中的 setup_memory_tags、setup_commandline_tag函数,他们都是在 arch/arm/lib/bootm.c中定义。
- #if defined(CONFIG_SETUP_MEMORY_TAGS) || \
- defined(CONFIG_CMDLINE_TAG) || \
- defined(CONFIG_INITRD_TAG) || \
- defined(CONFIG_SERIAL_TAG) || \
- defined(CONFIG_REVISION_TAG)
- static void setup_start_tag (bd_t *bd)
- {
- params = (struct tag *)bd->bi_boot_params;
- 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);
- }
- #endif
- #ifdef CONFIG_SETUP_MEMORY_TAGS
- 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);
- }
- }
- #endif
- #ifdef CONFIG_CMDLINE_TAG
- static void setup_commandline_tag(bd_t *bd, char *commandline)
- {
- char *p;
- if (!commandline)
- return;
- /* eat leading white space */
- for (p = commandline; *p == ' '; p++);
- /* skip non-existent command lines so the kernel will still
- * use its default command line.
- */
- 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);
- params = tag_next (params);
- }
- #endif
一般有 setup_memory_tags、setup_commandline_tag 这两个标记就可以了,在配置文件Include/configs/fs4412.h中定义: