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. }