logo资料库

U-BOOT启动过程分析.doc

第1页 / 共85页
第2页 / 共85页
第3页 / 共85页
第4页 / 共85页
第5页 / 共85页
第6页 / 共85页
第7页 / 共85页
第8页 / 共85页
资料共85页,剩余部分请下载后查看
1.1.1             U-Boot启动第一阶段代码分析
1.1.2             U-Boot启动第二阶段代码分析
1.1.3             U-Boot启动Linux过程
1.1.4             U-Boot添加命令的方法及U-Boot命令执行过程
U-Boot 的启动过程源码分析 首先强调,本书使用的 U-Boot 从 NOR Flash 启动,下面以开发板 smdk2410 的 U-Boot 为例。 U-Boot 属 于 两 阶 段 的 Bootloader , 第 一 阶 段 的 文 件 为 cpu/arm920t/start.S 和 board/smdk2410/lowlevel_init.S,前者是平台相关,后者是开发板相关。 [编辑] U-Boot 第一阶段代码分析 它与 15.1.2 节中描述的 Bootloader 第一阶段所完成的功能可以一一对应: (1)硬件设备初始化。 依次完成如下设置:将 CPU 的工作模式设为管理模式(svc),关闭 WATCHDOG,设置 FCLK、 HCLK、PCLK 的比例(即设置 CLKDIVN 寄存器),关闭 MMU、CACHE。 代码都在 cpu/arm920t/start.S 中,注释也比较完善,读者有不明白的地方可以参考前面硬件 实验的相关章节。 (2)为加载 Bootloader 的第二阶段代码准备 RAM 空间。 所谓准备 RAM 空间,就是初始化内存芯片,使它可用。对于 S3C2410/S3C2440,通过在 start.S 中 调 用 lowlevel_init 函 数 来 设 置 存 储 控 制 器 , 使 得 外 接 的 SDRAM 可 用 。 代 码 在 board/smdk2410/lowlevel_init.S 中。 注意:lowlevel_init.S 文件是开发板相关的,这表示如果外接的设备不一样,可以修改 lowlevel_init.S 文件中的相关宏。 lowlevel_init 函数并不复杂,只是要注意这时的代码、数据都只保存在 NOR Flash 上,内存 中还没有,所以读取数据时要变换地址。代码如下: 129 _TEXT_BASE: 130 .wordTEXT_BASE 131 132 .globl lowlevel_init 133 lowlevel_init: 134 /* memory control configuration */
135 /* make r0 relative the current location so that it */ 136 /* reads SMRDATA out of FLASH rather than memory ! */ 137 ldr r0, =SMRDATA 138 ldrr1, _TEXT_BASE 139 subr0, r0, r1 140 ldrr1, =BWSCON/* Bus Width Status Controller */ 141 add r2, r0, #13*4 142 0: 143 ldr r3, [r0], #4 144 str r3, [r1], #4 145 cmp r2, r0 146 bne 0b 147 148 /* everything is fine now */ 149 movpc, lr 150 151 .ltorg 152 /* the literal pools origin */ 153 154 SMRDATA:/* 13 个寄存器的值 */ 155 .word …… 156 .word ……
第 137~139 行进行地址变换,因为这时候内存中还没有数据,不能使用连接程序时确定的 地址来读取数据: 第 137 行 中 SMRDATA 表 示 这 13 个 寄 存 器 的 值 存 放 的 开 始 地 址 ( 连 接 地 址 ) , 值 为 0x33F8xxxx,处于内存中。 第 138 行 获 得 代 码 段 的 起 始 地 址 , 它 就 是 第 130 行 中 的 “ TEXT_BASE ”, 其 值 在 board/smdk2410/config.mk 中定义:“TEXT_BASE = 0x33F80000”。 第 139 行将 0x33F8xxxx 与 0x33F80000 相减,这就是 13 个寄存器值在 NOR Flash 上存放的 开始地址。 (3)拷贝 Bootloader 的第二阶段代码到 RAM 空间中。 这 里 将 整 个 U-Boot 的 代 码 ( 包 括 第 一 、 第 二 阶 段 ) 都 复 制 到 SDRAM 中 , 这 在 cpu/arm920t/start.S 中实现: 164 relocate:/* 将 U-Boot 复制到 RAM 中 */ 165 adrr0, _start/* r0 = 当前代码的开始地址 */ 166 ldrr1, _TEXT_BASE/* r1 = 代码段的连接地址 */ 167 cmp r0, r1 /* 测试现在是在 Flash 中还是在 RAM 中 */ 168 beq stack_setup/* 如果已经在 RAM 中(这通常是调试时,直接下载到 RAM 中), * 则不需要复制 */ 169 170 ldrr2, _armboot_start/* _armboot_start 在前面定义,是第一条指令的运行地址 */ 171 ldrr3, _bss_start/* 在连接脚本 u-boot.lds 中定义,是代码段的结束地址 */ 172 subr2, r3, r2/* r2 = 代码段长度 */ 173 addr2, r0, r2/* r2 = NOR Flash 上代码段的结束地址 */ 174
175 copy_loop: 176 ldmiar0!, {r3-r10}/* 从地址[r0]处获得数据 */ 177 stmiar1!, {r3-r10}/* 复制到地址[r1]处 */ 178 cmpr0, r2/* 判断是否复制完毕 */ 179 blecopy_loop/* 没复制完,则继续 */ (4)设置好栈。 栈的设置灵活性很大,只要让 sp 寄存器指向一段没有使用的内存即可。 182 /* Set up the stack */ 183 stack_setup: 184 ldr r0, _TEXT_BASE /* _TEXT_BASE 为代码段的开始地址,值为 0x33F80000 */ 185 sub r0, r0, #CFG_MALLOC_LEN /* 代码段下面,留出一段内存以实现 malloc */ 186 sub r0, r0, #CFG_GBL_DATA_SIZE /* 再留出一段内存,存一些全局参数 */ 187 #ifdef CONFIG_USE_IRQ 188 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) /* IRQ、FIQ 模式的 栈 */ 189 #endif 190 sub sp, r0, #12 /* 最后,留出 12 字节的内存给 abort 异常, * 往下的内存就都是栈了 */ 191 到了这一步,读者可以知道内存的使用情况了,如下图所示(图中与上面的划分稍有不同, 这是因为在 cpu/arm920t/cpu.c 中的 cpu_init 函数中才真正为 IRQ、FIQ 模式划分了栈): [[Image:]] 图 15.3 U-Boot 内存使用情况
(5)跳转到第二阶段代码的 C 入口点。 在跳转之前,还要清除 BSS 段(初始值为 0、无初始值的全局变量、静态变量放在 BSS 段), 代码如下: 192 clear_bss: 193 ldrr0, _bss_start/* BSS 段的开始地址,它的值在连接脚本 u-boot.lds 中确定 */ 194 ldrr1, _bss_end/* BSS 段的结束地址,它的值在连接脚本 u-boot.lds 中确定 */ 195 mov r2, #0x00000000 196 197 clbss_l:strr2, [r0]/* 往 BSS 段中写入 0 值 */ 198 addr0, r0, #4 199 cmpr0, r1 200 bleclbss_l 201 现在,C 函数的运行环境已经完全准备好,通过如下命令直接跳转(这之后,程序才在内存 中执行),它将调用 lib_arm/board.c 中的 start_armboot 函数,这是第二阶段的入口点: 223 ldrpc, _start_armboot 224 225 _start_armboot:.word start_armboot 226 U-Boot 第二阶段代码分析 它与 15.1.2 节中描述的 Bootloader 第二阶段所完成的功能基本上一致,不过顺序有点小差别。 另外,U-Boot 在启动内核之前可以让用户决定是否进入下载模式,即进入 U-Boot 的控制界 面。 第二阶段从 lib_arm/board.c 中的 start_armboot 函数开始,先看从这个函数开始的程序流程图, 图 15.3 所示。
[[Image:]] 图 15.3 U-Boot 第二阶段流程图 移植 U-Boot 的主要工作在于对硬件的初始化、驱动,所以下面讲解时将重点放在硬件的操 作上。 (1)初始化本阶段要使用到的硬件设备。:最主要的是设置系统时钟、初始化串口,只要这 两个设置好了,就可以从串口看到打印信息。 board_init 函 数 设 置 MPLL 、 改 变 系 统 时 钟 , 它 是 开 发 板 相 关 的 函 数 , 在 board/smdk2410/smdk2410.c 中实现。值得注意的是,board_init 函数中还保存了机器类型 ID, 这将在调用内核时传给内核,代码如下: /* arch number of SMDK2410-Board */ gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; /* 值为 193 */ 串 口的 初 始化 函数 主 要是 serial_init , 它设 置 UART 控 制器 , 是 CPU 相 关的 函 数, 在 cpu/arm920t/s3c24x0/serial.c 中实现。 (2)检测系统内存映射(memory map)。对于特定的开发板,其内存的分布是明确的,所以 可以直接设置。board/smdk2410/smdk2410.c 中的 dram_init 函数指定了本开发板的内存起始 地址为 0x30000000,大小为 0x4000000。代码如下: int dram_init (void) { gd->bd->bi_dram[0].start = PHYS_SDRAM_1;/* 即 0x300000000 */ gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;/* 即 0x4000000 */ return 0; } 这些设置的参数,将在后面向内核传递参数时用到。 (3)U-Boot 命令的格式。 从图 15.3 可以知道,即使是内核的启动,也是通过 U-Boot 命令来实现的。U-Boot 中每个命 令都通过 U_BOOT_CMD 宏来定义,格式如下:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help") 各项参数的意义为: ① name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)。 ② maxargs:最大的参数个数 ③ repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行。 ④ command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *, int, int, char *[])。 ⑤ usage:简短的使用说明,这是个字符串。 ⑥ help:较详细的使用说明,这是个字符串。 宏 U_BOOT_CMD 在 include/command.h 中定义: #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \ cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help} Struct_Section 也是在 include/command.h 中定义: #define Struct_Section __attribute__ ((unused,section (".u_boot_cmd"))) 比如对于 bootm 命令,它如此定义: U_BOOT_CMD( bootm,CFG_MAXARGS,1,do_bootm, “string1”, “string2” ); 宏 U_BOOT_CMD 扩展开后就是: cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {“bootm”, CFG_MAXARGS, 1, do_bootm, “string1”, “string2”};
对于每个使用 U_BOOT_CMD 宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个 cmd_tbl_t 结构。连接脚本 u-boot.lds 中有这么一段: __u_boot_cmd_start = .; .u_boot_cmd : { *(.u_boot_cmd) } __u_boot_cmd_end = .; 程 序 中 就 是 根 据 命 令 的 名 字 在 内 存 段__u_boot_cmd_start ~__u_boot_cmd_end 找 到 它 的 cmd_tbl_t 结构,然后调用它的函数(请参考 common/command.c 中的 find_cmd 函数)。 内核的复制和启动,可以通过如下命令来完成:bootm 从内存、ROM、NOR Flash 中启动内 核,bootp 则通过网络来启动,而 nboot 从 NAND Flash 启动内核。它们都是先将内核映像 从各种媒介中读出,存放在指定的位置;然后设置标记列表以给内核传递参数;最后跳到内 核的入口点去执行。具体实现的细节不再描述,有兴趣的读者可以阅读 common/cmd_boot.c、 common/cmd_net.c、common/cmd_nand.c 来了解它们的实现。 (4)为内核设置启动参数。 与 15.1.2 小节中《Bootloader 与内核的交互》所描述的一样,U-Boot 也是通过标记列表向内 核传递参数。并且,15.1.2 小节中内存标记、命令行标记的示例代码就是取自 U-Boot 中的 setup_memory_tags、setup_commandline_tag 函数,它们都是在 lib_arm/armlinux.c 中定义。 一般而言,设置这两个标记就可以了,在配置文件 include/configs/smdk2410.h 中增加如下两 个配置项即可: #define CONFIG_SETUP_MEMORY_TAGS 1 #define CONFIG_CMDLINE_TAG 1 对于 ARM 架构的 CPU,都是通过 lib_arm/armlinux.c 中的 do_bootm_linux 函数来启动内核。 这 个 函 数 中 , 设 置 标 记 列 表 , 最 后 通 过 “ theKernel (0, bd->bi_arch_number, bd->bi_boot_params)”调用内核。其中,theKernel 指向内核存放的地址(对于 ARM 架构的 CPU,通常是 0x30008000),bd->bi_arch_number 就是前面 board_init 函数设置的机器类型 ID, 而 bd->bi_boot_params 就是标记列表的开始地址。 [编辑] 15.2.5 U-Boot 的移植 开发板 smdk2410 的配置适用于大多数 S3C2410 单板,或是只需要极少的修改即可使用。但 是目前 U-Boot 中没有对 S3C2440 的支持,需要我们自己移植。
分享到:
收藏