1、代码框架
源码解压以后,我们可以看到以下的文件和文件夹:
cpu |
与处理器相关的文件。每个子目录中都包括cpu.c和interrupt.c、start.S、u-boot.lds。 cpu.c:初始化CPU、设置指令Cache和数据Cache等 interrupt.c:设置系统的各种中断和异常 start.S:是U-boot启动时执行的第一个文件,它主要做最早期的系统初始化,代码重定向和设置系统堆栈,为进入U-boot第二阶段的C程序奠定基础。 u-boot.lds:链接脚本文件,对于代码的最后组装非常重要。 |
|
board |
已经支持的所有开发板相关文件,其中包含SDRAM初始化代码、Flash底层驱动、板级初始化文件。 其中的config.mk文件定义了TEXT_BASE,也就是代码在内存的实际地址,非常重要。 |
|
common |
与处理器体系结构无关的通用代码,U-boot的命令解析代码/common/command.c、所有命令的上层代码cmd_*.c、U-boot环境变量处理代码env_*.c等都位于该目录下 |
|
drivers |
包含几乎所有外围芯片的驱动,网卡、USB、串口、LCD、Nand Flash等等 |
|
disk fs net |
支持CPU无关的重要子系统: 磁盘驱动的分区处理代码 文件系统:FAT、JFFS2、EXT2等 网络协议:NFS、TFTP、RARP、DHCP等等 |
|
include |
头文件,包括各CPU的寄存器定义,文件系统、网络等等 configs子目录下的文件是与目标板相关的配置头文件 |
|
doc |
U-Boot的说明文档,在修改配置文件的时候可能用得上 |
|
lib_arm |
处理器体系相关的初始化文件 比较重要的是其中的board.c文件,几乎是所有架构的U-boot第二阶段代码入口函数和相关初始化函数存放的地方。 |
|
lib_avr32 lib_blackfin lib_generic lib_i386 lib_m68k lib_microblaze |
lib_mips lib_nios lib_nios2 lib_ppc lib_sh lib_sparc |
|
api examples |
外部扩展应用程序的API和范例 |
|
nand_spl onenand_ipl post |
一些特殊构架需要的启动代码和上电自检程序代码 |
|
libfdt |
支持平坦设备树(flattened device trees)的库文件 |
|
tools |
编译S-Record或U-Boot映像等相关工具,制作bootm引导的内核映像文件工具mkimage源码就在此 |
|
Makefile MAKEALL config.mk rules.mk mkconfig |
控制整个编译过程的主Makefile文件和规则文件 |
|
CHANGELOG CHANGELOG-before-U-Boot-1.1.5 COPYING CREDITS MAINTAINERS README |
一些介绍性的文档、版权说明 |
2、链接脚本
uboot.lds在u-boot/arch/arm/cpu/arm926ejs目录下。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")//定义输出elf格式文件,32位,小端模式
OUTPUT_ARCH(arm)//ARM平台
ENTRY(_start)//起始代码段 _start
SECTIONS
{
. = 0x00000000;// 指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置
. = ALIGN(4);//4字节对其
.text ://定义代码段
{
arch/arm/cpu/arm926ejs/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }//只读数据段
. = ALIGN(4);
.data : { *(.data) }//可写数据段
. = ALIGN(4);
.got : { *(.got) }//got段,程序非标准段,uboot特有段
. = .;
__u_boot_cmd_start = .;//U_BOOT_CMD宏定义的数据存放在此处
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;//bss段,通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域
.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
_end = .;
}
3、启动stage1(start.S)
代码目录在/u-boot/arch/arm/cpu/arm926ejs
相关的引用头文件在/u-boot/arch/arm/include/asm/arch-maximasp目录下定义了一些硬件地址寄存器等。
/u-boot/board/Maxim/maximasp_eek/board_init.S
/u-boot/arch/arm/lib/board.c start_armboot函数文件
启动流程:异常向量——上电复位跳转到异常向量——设置cpu为SVC32模式——跳转到cpu_init_crit——禁止MMU和缓存——跳转到lowlevel_init(初始化PLL时钟、复合管脚配置、内存模块配置)——relocate(一般加载过程是内部rom程序将nandflash前4K区域程序加载到内部ram中运行,程序判断当前是从nandflash加载运行还是再sram运行,从而确定是否执行copy_loop,copy_loop将程序从nandflash加载到外部ram运行)——初始化堆栈stack_setup——clear_bss——跳转到start_armboot
.globl _start
_start:
b reset
代码起始跳转到reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
定义的异常处理,异常处理是由硬件来跳转的,异常处理存储了一个跳转地址。
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:
.word fiq
irq、fiq在uboot中没有使用,因此是空的
.align 5
irq:
.align 5
fiq:
若需要使用irq、fiq,则需要在头文件中定义CONFIG_USE_IRQ
ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
do_fiq和do_irq定义在interrupts.c里
通过arm-linux-objdump -d start.o查看代码段,可以发现中断表的存储位置
4、启动stage2
/u-boot/arch/arm/lib/board.c start_armboot函数文件
start_armboot主要用于初始化,初始化gd变量,初始化环境变量、mmc、nand、onenand、serial、eth等等。然后进入main_loop
初始化的一些配置参数在/u-boot/include/configs/下对应的芯片头文件下
环境变量的配置参数:
#define CONFIG_ENV_IS_IN_NAND 1
#define CONFIG_ENV_SECT_SIZE 0x80000 /* 512K */
/* Use smaller environment than the sector size because we malloc() a multiple of this */
#define CONFIG_OLD_ENV_SIZE 0x1000
#define CONFIG_ENV_SIZE 0x2000
#define CONFIG_ENV_OFFSET 0x200000
/u-boot/common/env_common.c里定义了环境变量相关的函数。
/u-boot/common/main.c 中定义了main_loop
上面代码定义了命令自动补全
以上代码获取bootdelay环境变量,及开机检测按键延时时间
以上代码获取bootcmd指令,当bootdelay时间超时时执行bootcmd指令启动加载内核
for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME
if (rc >= 0) {
/* Saw enough of a valid command to
* restart the timeout.
*/
reset_cmd_timeout();
}
#endif
len = readline (CONFIG_SYS_PROMPT);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;//按回车重复执行上次的命令标志
lastcommand[0] = 0;
#endif
#ifdef CONFIG_BOOT_RETRY_TIME
else if (len == -2) {
/* -2 means timed out, retry autoboot
*/
puts ("\nTimed out waiting for command\n");
# ifdef CONFIG_RESET_TO_RETRY
/* Reinit board to run initialization code again */
do_reset (NULL, 0, 0, NULL);
# else
return; /* retry autoboot */
# endif
}
#endif
if (len == -1)
puts ("
else
rc = run_command (lastcommand, flag);
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
如果在bootdelay时间内按下按键则循环读取输入指令
/u-boot/common/env_common.c定义bootcmd命令:
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
CONFIG_BOOTCOMMAND的定义在/u-boot/include/configs/maximasp-eek.h:
# define CONFIG_BOOTCOMMAND "run bootf0"
/u-boot/env.input和env.mk定义了bootf0参数的配置
run_command 会调用bootm
/u-boot/common/cmd_bootm.c定义bootm命令:
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
do_bootm:
获取verify变量——获取镜像加载地址(启动参数,flash地址)——读取镜像头做校验——检验cpu类型和内核压缩方式——判断操作系统类型——do_bootm_linux(准备内核参数和启动环境,内核参数根据bootargs环境变量设置tag)
5、命令
uboot的命令通过U_BOOT_CMD添加,一般在/u-boot/common目录下存在指令相关的定义文件
通过main.c里的run_command解析执行命令
6、bootcmd和bootargs
bootcmd:自启动时执行的首条命令。由此可见bootcmd的重要性,要是第一条命令就错了,那后面的就无法执行下去了。bootcmd指定了要从flash的哪里读数据,读多少数据,读到内存(一般为SDRAM)的哪里去,再从内存的哪里启动(执行)。
例子:bootcmd=nand read 30008000 100000 800000;bootm 30008000。
此处,bootcmd从flash的100000(即1M)处开始读800000(即8M)大小的数据到内存的30008000地址处,再从30008000地址处执行代码。(从何处读必须与之前写的位置对应,从哪里开始写的就从哪里开始读,切记不可弄错,以保证数据的完整性。而读多少就没有硬性要求,只需要记住,读多不读少,稍微读大些就行了,但不可过大,不可读到下一段数据中,即保证读的大小在你做的内核的分区表中内核代码分区里。读到内存中就没什么好讲的,代码都是在内存中运行的。bootm即指定从30008000处启动内核。)
bootargs:传递给内核启动参数。也是非常重要的一个环境变量,如果传递了错误的参数,内核便会启动失败。
例子:bootargs=noinitrd root=/dev/mtdblock4 rootfstype=jffs2 init=/linuxrc console=ttyS0,115200
此处各参数的含义:
noinitrd:
当系统不是使用ramdisk文件系统(内嵌在内核里的文件系统)启动时,指定使用该参数。
如果使用ramdisk文件系统,需要指定initrd=r_addr,size, r_addr表示initrd在内存中的位置,size表示initrd的大小。
root:
用来指定rootfs(根文件系统)的挂载位置(root=/dev/mtdblock4在第四分区)
可在后面加上权限(如rw)
rootfstype:
当文件系统不是内嵌在内核中,而是与内核一起存放在flash中时(例如jffs2),需指定文件系统类型,否则无法挂载。
init:
init指定的是内核启起来后,进入系统中运行的第一个脚本,一般init=/linuxrc(linuxrc是系统启动时执行的脚本,用于加载模块驱动等,运行在init进程之前,退出后才调用init,一般linuxrc为一个链接,连接到bin/busybox,所以写法不固定)
console:
指定串口终端设备,指定与终端相应的串口波特率,指定之后可用secureCRT之类的终端仿真程序操作开发板。
7、uboot和内核传参
/u-boot/arch/arm/lib/bootm.c中do_bootm_linux启动kernel
int do_bootm_linux(int flag, int argc, char * const 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);
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
return 1;
theKernel = (void (*)(int, int, uint))images->ep;
#if CONFIG_SYS_MACH_TYPE == MACH_TYPE_MAXIMASP
/* Maxim ASP: Automatically put the command line at kernel address - 0x7f00 */
bd->bi_boot_params = images->ep - 0x7f00;
#endif
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
//以下存储传输给kernel的参数
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
setup_end_tag (bd);
#endif
/* we assume that the kernel is in place */
#ifdef CONFIG_FORCE_SHOWBOOTINFO
{
bool was_silent = maximasp_unsilence();
#endif
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_FORCE_SHOWBOOTINFO
maximasp_resilence(was_silent);
}
#endif
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux ();
//do_bootm_linux函数调用theKernel (0, machid, bd->bi_boot_params)去启动内核并传递参数,可以看见r0是machid, r2是bi_boot_params参数的地址。
theKernel (0, machid, bd->bi_boot_params);