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 内核。该命令用法介绍如下:

1. # help bootm  
2. bootm - boot application image from memory  
3.   
4. Usage:  
5. bootm [addr [arg ...]]  
6.     - boot application image stored in memory  
7. 'arg ...'; when booting a Linux kernel,  
8. 'arg' can be the address of an initrd image  
9.   
10. Sub-commands to do part of the bootm sequence.  The sub-commands must be  
11. issued in the order below (it's ok to not issue all sub-commands):  
12.         start [addr [arg ...]]  
13.         loados  - load OS image  
14.         cmdline - OS specific command line processing/setup  
15.         bdt     - OS specific bd_t processing  
16.         prep    - OS specific prep before relocation or go  
17.         go      - start OS

二、基础数据结构

在 bootm 中常用的数据结构有 image_info_t 和 bootm_headers_t,定义如下:

1. /* 镜像信息 */  
2. typedef struct image_info {  
3. /* start/end of blob */  
4. /* start of image within blob, len of image */  
5. /* load addr for the image */  
6. /* compression, type of image, os type */  
7. } image_info_t;  
8.   
9. /* bootm 头 */  
10. typedef struct bootm_headers {  
11. /* 指向镜像头的指针 */  
12. /* 镜像头的备份 */  
13. /* 镜像头存在标记 */  
14.   
15. /* 系统镜像信息 */  
16. /* 系统入口地址 */  
17. /* 虚拟磁盘起始地址 */  
18. /* 平坦设备树的长度 */  
19.     ulong           initrd_start;  
20.     ulong           initrd_end;  
21.     ulong           cmdline_start;  
22.     ulong           cmdline_end;  
23.     bd_t            *kbd;  
24. int             verify;             /* getenv("verify")[0] != 'n' */  
25.   
26. #define BOOTM_STATE_START       (0x00000001)  
27. #define BOOTM_STATE_LOADOS      (0x00000002)  
28. #define BOOTM_STATE_RAMDISK     (0x00000004)  
29. #define BOOTM_STATE_FDT         (0x00000008)  
30. #define BOOTM_STATE_OS_CMDLINE  (0x00000010)  
31. #define BOOTM_STATE_OS_BD_T     (0x00000020)  
32. #define BOOTM_STATE_OS_PREP     (0x00000040)  
33. #define BOOTM_STATE_OS_GO       (0x00000080)  
34. int             state;              /* 状态标记 */  
35.   
36. struct lmb  lmb;                    /* 逻辑内存块 */  
37. } bootm_headers_t;

三、代码解析

bootm 的主函数为 do_bootm,代码如下:

1. int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])  
2. {  
3.     ulong  iflag;  
4.     ulong  load_end = 0;  
5. int    ret;  
6.     boot_os_fn  *boot_fn;  
7.   
8. if (bootm_start(cmdtp, flag, argc, argv))     /* 获取镜像信息 */  
9. return 1;  
10.   
11. /* 关闭中断 */  
12. /* 关闭usb设备 */  
13.   
14. /* 加载内核 */  
15.   
16.     lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));  
17.   
18. if (images.os.os == IH_OS_LINUX)  /* 如果有需要关闭内核的串口 */  
19.         fixup_silent_linux();  
20.   
21. /* 获取启动函数 */  
22.   
23. /* 启动前准备 */  
24.   
25. /* 启动,不再返回 */  
26.   
27. "\n## Control returned to monitor - resetting...\n");  
28.     do_reset (cmdtp, flag, argc, argv);  
29.   
30. return 1;  
31. }

该函数的实现分为 3 个部分:首先通过 bootm_start 函数分析镜像的信息,如果满足判定条件则进入 bootm_load_os 函数进行加载,加载完成后就可以调用 boot_fn 开始启动。


1、bootm_start

1. static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])  
2. {  
3.     ulong       mem_start;  
4.     phys_size_t mem_size;  
5. void        *os_hdr;  
6. int     ret;  
7.   
8. void *)&images, 0, sizeof (images));  
9. "verify");  /* 获取环境变量 */  
10.   
11.     lmb_init(&images.lmb);  
12.   
13.     mem_start = getenv_bootm_low();  
14.     mem_size = getenv_bootm_size();  
15.   
16.     lmb_add(&images.lmb, (phys_addr_t)mem_start, mem_size);  
17.   
18.     arch_lmb_reserve(&images.lmb);  
19.     board_lmb_reserve(&images.lmb);  
20.   
21. /* 获取镜像头,加载地址,长度 */  
22.     os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,  
23.             &images, &images.os.image_start, &images.os.image_len);  
24. if (images.os.image_len == 0) {  
25. "ERROR: can't get kernel image!\n");  
26. return 1;  
27.     }  
28.   
29. /* 获取镜像参数 */  
30. switch (genimg_get_format (os_hdr)) {  
31. case IMAGE_FORMAT_LEGACY:  
32. /* 镜像类型 */  
33. /* 压缩类型 */  
34. /* 系统类型 */  
35.   
36. /* 镜像结束地址 */  
37. /* 加载地址 */  
38. break;  
39. default:  
40. "ERROR: unknown image format type!\n");  
41. return 1;  
42.     }  
43.   
44. /* 查询内核入口地址 */  
45. if (images.legacy_hdr_valid) {  
46.         images.ep = image_get_ep (&images.legacy_hdr_os_copy);  
47. else {  
48. "Could not find kernel entry point!\n");  
49. return 1;  
50.     }  
51.   
52. if (images.os.os == IH_OS_LINUX) {  
53. /* 查询是否存在虚拟磁盘 */  
54.         ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,  
55.                 &images.rd_start, &images.rd_end);  
56. if (ret) {  
57. "Ramdisk image is corrupt or invalid\n");  
58. return 1;  
59.         }  
60.     }  
61. /* 赋值加载地址 */  
62. /* 更新状态 */  
63.   
64. return 0;  
65. }

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

1. static image_header_t *image_get_kernel (ulong img_addr, int verify)  
2. {  
3.     image_header_t *hdr = (image_header_t *)img_addr;  
4.   
5. if (!image_check_magic(hdr)) {   /* 魔数比较 */  
6. "Bad Magic Number\n");  
7.         show_boot_progress (-1);  
8. return NULL;  
9.     }  
10.     show_boot_progress (2);  
11.   
12. if (!image_check_hcrc (hdr)) {   /* 镜像头校验和 */  
13. "Bad Header Checksum\n");  
14.         show_boot_progress (-2);  
15. return NULL;  
16.     }  
17.   
18.     show_boot_progress (3);  
19. /* 打印镜像头信息 */  
20.   
21. if (verify) {                    /* 校验镜像 */  
22. "   Verifying Checksum ... ");  
23. if (!image_check_dcrc (hdr)) {  
24. "Bad Data CRC\n");  
25.             show_boot_progress (-3);  
26. return NULL;  
27.         }  
28. "OK\n");  
29.     }  
30.     show_boot_progress (4);  
31.   
32. if (!image_check_target_arch (hdr)) {   /* 处理器类型比较 */  
33. "Unsupported Architecture 0x%x\n", image_get_arch (hdr));  
34.         show_boot_progress (-4);  
35. return NULL;  
36.     }  
37. return hdr;  
38. }  
39.   
40. static void *boot_get_kernel (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],  
41.         bootm_headers_t *images, ulong *os_data, ulong *os_len)  
42. {  
43.     ulong  img_addr;  
44.     image_header_t  *hdr;  
45.   
46. if (argc < 2) {  /* 如果没有参数则用默认加载地址 */  
47.         img_addr = load_addr;  
48. "*  kernel: default image load address = 0x%08lx\n", load_addr);  
49. else {  
50.         img_addr = simple_strtoul(argv[1], NULL, 16);  
51. "*  kernel: cmdline image address = 0x%08lx\n", img_addr);  
52.     }  
53.   
54.     *os_data = *os_len = 0;  
55. switch (genimg_get_format ((void *)img_addr)) {  
56. case IMAGE_FORMAT_LEGACY:  
57. "## Booting kernel from Legacy Image at %08lx ...\n", img_addr);  
58. /* 获取内核 */  
59. if (!hdr)  
60. return NULL;  
61.         show_boot_progress (5);  
62.   
63. /* get os_data and os_len */  
64. switch (image_get_type (hdr)) {  
65. case IH_TYPE_KERNEL:  
66. /* 内核入口地址 */  
67. /* 内核长度(不包括头部) */  
68. break;  
69. default:  
70. "Wrong Image Type for %s command\n", cmdtp->name);  
71.             show_boot_progress (-5);  
72. return NULL;  
73.         }  
74.   
75. /* 备份镜像头以防止在内核解压时被覆盖 */  
76. sizeof(image_header_t));  
77.   
78.         images->legacy_hdr_os = hdr;  
79. /* 标记存在入口点 */  
80. break;  
81. default:  
82. "Wrong Image Format for %s command\n", cmdtp->name);  
83.         show_boot_progress (-108);  
84. return NULL;  
85.     }  
86.   
87. "   kernel data at 0x%08lx, len = 0x%08lx (%ld)\n", *os_data, *os_len, *os_len);  
88.   
89. return (void *)img_addr;  /* 返回加载地址 */  
90. }

在有效性判定完成时会打印出镜像的一些信息,代码如下:


1. void image_print_contents (const void *ptr)  
2. {  
3. const image_header_t *hdr = (const image_header_t *)ptr;  
4. const char *p;  
5.   
6. "   ";  
7. "%sImage Name:   %.*s\n", p, IH_NMLEN, image_get_name (hdr));  /* 镜像名称 */  
8. #if defined(CONFIG_TIMESTAMP) || defined(CONFIG_CMD_DATE) || defined(USE_HOSTCC)  
9. "%sCreated:      ", p);  /* 创建时间 */  
10. time_t)image_get_time (hdr));  
11. #endif  
12. "%sImage Type:   ", p);  /* 镜像类型 */  
13.     image_print_type (hdr);  
14. "%sData Size:    ", p);  /* 镜像大小 */  
15.     genimg_print_size (image_get_data_size (hdr));  
16. "%sLoad Address: %08x\n", p, image_get_load (hdr));  /* 加载地址 */  
17. "%sEntry Point:  %08x\n", p, image_get_ep (hdr));    /* 入口地址 */  
18. }


2、bootm_load_os

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

1. static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress)  
2. {  
3. /* 压缩格式 */  
4. /* 加载地址 */  
5. /* 镜像起始地址 */  
6. /* 镜像结束地址 */  
7. /* 镜像起始地址 */  
8. /* 镜像长度 */  
9. /* 镜像最大长度 */  
10.   
11. const char *type_name = genimg_get_type_name (os.type);  /* 镜像类型 */  
12.   
13. switch (comp) {  /* 选择解压格式 */  
14. case IH_COMP_NONE:  /* 镜像没有压缩过 */  
15. if (load == blob_start) {   /* 判断是否需要移动镜像 */  
16. "   XIP %s ... ", type_name);  
17. else {  
18. "   Loading %s ... ", type_name);  
19.   
20. if (load != image_start) {  
21. void *)load, (void *)image_start, image_len, CHUNKSZ);  
22.             }  
23.         }  
24.         *load_end = load + image_len;  
25. "OK\n");  
26. break;  
27. case IH_COMP_GZIP:  /* 镜像采用 gzip 解压 */  
28. "   Uncompressing %s ... ", type_name);  
29. if (gunzip ((void *)load, unc_len, (uchar *)image_start, &image_len) != 0) {  /* 解压 */  
30. "GUNZIP: uncompress, out-of-mem or overwrite error "  
31. "- must RESET board to recover\n");  
32. return BOOTM_ERR_RESET;  
33.         }  
34.   
35.         *load_end = load + image_len;  
36. break;  
37.     ...  
38. default:  
39. "Unimplemented compression type %d\n", comp);  
40. return BOOTM_ERR_UNIMPLEMENTED;  
41.     }  
42. "OK\n");  
43. "   kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);  
44.   
45. if ((load < blob_end) && (*load_end > blob_start)) {  
46. "images.os.start = 0x%lX, images.os.end = 0x%lx\n", blob_start, blob_end);  
47. "images.os.load = 0x%lx, load_end = 0x%lx\n", load, *load_end);  
48. return BOOTM_ERR_OVERLAP;  
49.     }  
50.   
51. return 0;  
52. }

3、do_bootm_linux


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

1. int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)  
2. {  
3.     bd_t  *bd = gd->bd;  
4. char  *s;  
5. int   machid = bd->bi_arch_number;  
6. void  (*theKernel)(int zero, int arch, uint params);  
7.   
8. char  *commandline = getenv ("bootargs");          /* 从环境变量中获取命令参数 */  
9.   
10. if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))    /* 状态判定 */  
11. return 1;  
12.   
13. void (*)(int, int, uint))images->ep;  /* 内核入口函数 */  
14.   
15. "machid");  /* 从环境变量中获取机器id */  
16. if (s) {  
17.         machid = simple_strtoul (s, NULL, 16);  
18. "Using machid 0x%x from environment\n", machid);  
19.     }  
20.   
21. "## Transferring control to Linux (at address %08lx) ...\n", (ulong) theKernel);  
22.   
23. /* 初始化启动参数 */  
24. /* 初始化参数列表起始符 */  
25. /* 初始化串口参数 */  
26. /* 初始化版本参数 */  
27. /* 初始化内存参数 */  
28. /* 初始化命令参数 */  
29. if (images->rd_start && images->rd_end)  
30. /* 初始化虚拟磁盘参数 */  
31. /* 初始化fb参数 */  
32. /* 初始化参数列表结束符 */  
33.   
34. "\nStarting kernel ...\n\n");  
35.   
36. /* 启动前清空缓存 */  
37.   
38. /* 启动内核,满足arm架构linux内核启动时的寄存器设置条件:第一个参数为0
39.        第二个参数为板子id需与内核中的id匹配,第三个参数为启动参数地址 */  
40. /* does not return */  
41.   
42. return 1;  
43. }


这里还列举部分启动参数的初始化函数:

1. static void setup_start_tag (bd_t *bd)  
2. {  
3. struct tag *) bd->bi_boot_params; /* 启动参数保存在板子指定的内存空间 CONFIG_ATAG_ADDR */  
4.   
5. /* 起始标签 */  
6.     params->hdr.size = tag_size (tag_core);  
7.   
8.     params->u.core.flags = 0;  
9.     params->u.core.pagesize = 0;  
10.     params->u.core.rootdev = 0;  
11.   
12. /* params指向下一个结构 */  
13. }  
14.   
15. static void setup_memory_tags (bd_t *bd)  
16. {  
17. int i;  
18.   
19. for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {  
20. /* 内存参数的标签 */  
21.         params->hdr.size = tag_size (tag_mem32);  
22.   
23. /* 物理内存起始地址 */  
24. /* 物理内存结束地址 */  
25.   
26.         params = tag_next (params);  
27.     }  
28. }  
29.   
30. static void setup_commandline_tag (bd_t *bd, char *commandline)  
31. {  
32. char *p;  
33.   
34. if (!commandline)  
35. return;  
36.   
37. for (p = commandline; *p == ' '; p++);  /* 定位到第一个有效字符 */  
38.   
39. if (*p == '\0')                         /* 没有有效字符则返回 */  
40. return;  
41.   
42. /* 命令参数的标签 */  
43. sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;  /* 整个标签的长度 */  
44.   
45. /* 将命令参数拷贝到标签,结束符为'\0' */  
46.   
47.     params = tag_next (params);  
48. }  
49.   
50. static void setup_end_tag (bd_t *bd)  
51. {  
52. /* 结束标签 */  
53.     params->hdr.size = 0;  
54. }