Uboot源码分析
源码以u-boot-1.3.4为基准,主芯片采用at91sam9260,主要介绍uboot执行流程。
uboot官网:http://www.denx.de/wiki/U-Boot/WebHome。
一.工具
1. 主要采用vi查看源码,用到最多命令为grep。
grep –r –n ‘string’ ./*
搜索该目录及其以下包含string的文件并标出其所在位置。
2. objdump –D u-boot >>www
反汇编elf格式可执行文件u-boot,并输出到www中。
使用objdump命令可以查看它的分段信息:
objdump -x u-boot | more
3. 比较两文件或目录下文件不同用diff。
diff –r file file >>exp.diff
此外,在windows环境下,可用source insight工具搜索查看源码。
二.目录结构
- 有用目录
board:开发板相关目录,平台依赖。
cpu:cpu相关目录,平台依赖。
lib_arm:ARM体系结构通用文件,主要实现ARM平台通用的函数,如软件浮点。
include:头文件目录,开发板的配置文件也在其中,configs存放所有开发板的配置文件。
common:通用多功能函数实现,主要是命令函数相关文件。
drivers:通用设备驱动程序。主要为以太网接口,nandflash驱动。
Lib_generic:通用库函数的实现。
uboot目录可分为3类:
1. 与处理器体系结构或开发板硬件直接相关的。
2. 一些通用函数或驱动程序。
3. uboot应用程序、工具或文档。
- 重要文件
1)include/configs/at91sam9260ek.h
开发板配置文件。
CONFIG_ 用来选择处理器、设备接口、命令、属性等,主要用来决定是否编译某些文件或函数。
CFG_ 用来定义总线频率,串口波特率,flash地址等参数,主要用来支持通用目录中的代码,定义板子资源。
2)cpu/arm926ejs/at91sam9/u-boot.lds
程序衔接脚本。
定义程序入口ENTRY(_start),并安排各段衔接位置,程序首先运行start.S文件。
3) cpu/arm926ejs/start.S
程序首先运行文件,程序刚开始运行汇编代码,完成基本初始化。
4)cpu/arm926ejs/at91sam9/lowlevel_init.S
内存初始化,clock、SDRAM初始化代码(bootstrap中已完成,不执行)。
5)lib_arm/board.c
C语言入口start_armboot。全局数据结构gd,bd的初始化。
6) board/atmel/at91sam9260ek/config.mk
uboot在ram中的位置(TEXT_BASE=0x23f0 0000)定义。
7)common/main.c
main_loop()定义于此。
8)irq相关
lib_arm/interrupt..c(开关中断),cpu/arm926ejs/interrupt.c(中断初始化函数)。
注:at91sam9260中并没有采用中断方式。
9)I/O相关
gpio定义在include/asm-arm/arch-at91sam9/at91sam9260.h,include/asm-arm/io.h,include/asm-arm/arch-at91sam9/io.h
include/asm-arm/arch-at91sam9/gpio.h
led定义在lib_arm/board.c,board/atmel/at91sam9260ek/led.c中。
10)linux启动相关文件与函数
main_loop()在没有按键的情况下调用bootm
11)include/asm-arm/arch-at91sam9/at91sam9260.h中定义at91sam9260芯片的各个特殊功能寄存器(SFR)的地址。
uboot实际运行三个函数对应三个文件:_start => start_armboot => main_loop
start.S => lib_arm/board.c => common/main.c
三.生成文件简介
uboot生成文件:
System.map:uboot映像的符号表,它包含了uboot的全局变量和函数的地址信息。供用户或外部程序调试使用。
uboot.bin:uboot映像原始二进制文件
uboot:uboot映像ELF格式
uboot.srec:uboot映像的s_record格式
注:uboot和uboot.srec格式映像都自带定位信息。
tool/mkimge可把其他格式映像(linux内核映像和ramdisk文件系统映像转化为uboot格式)。
四.编译过程
1. Make过程
make distclean 清除原来编译,衔接结果
make at91sam9260ek_nandflash_config 配置开发板相关信息
make 编译
2. Make分析
uboot是通过gcc和Makefile组织编译的,顶层目录下的Makefile首先配置开发板,然后递归调用子目录下的makefile文件,最后把编译结果衔接成u-boot镜像。
顶层Makefile文件可分为独立的两部分:配置开发板和编译u-boot镜像。
配置开发板代码占大部分代码,起始于unconfig,结束于文件尾的clean目标。
编译u-boot镜像占据前面部分,根据config.mk是否存在(即是否配置开发板)分两种情况编译。
条件编译语句为:
ifeq($(obj)include/config.mk, $(wildcard $(obj)include/config.mk))
all:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk
……
else #config.mk不存在
……
@echo “System not configured – see README” >&2
@exit 1
……
endif #config.mk
3. 配置开发板
顶层Makefile下定义如下:
at91sam9260ek_nandflash_config: unconfig
echo “#define CFG_USE_NANDFLASH 1” >>$(obj)include/config.h;
@$(MKCONFIG) –a at91sam9260ek arm arm926ejs at91sam9260ek atmel at91sam9
分析:
MKCONFIG :=$(SRCTREE)/mkconfig
即MKCONFIG就是执行顶层目录下mkconfig脚本。其主要目的就是生成文件include/config.mk,修改include/config.h,并软连接相应文件。
mkconfig的参数:Target Architecture CPU Board [VENDER] [SOC]
1)include/config.mk
ARCH = arm
CPU = arm926ejs
BOARD = at91sam9260ek
VENDOR = atmel
SOC = at91sam9
2)include/config.h
#define CFG_USE_NANDFLASH 1
/* Automatically generated – do not edit */
#include <configs/at91sam9260ek.h>
3)创建到目标板的文件连接
首先进入include目录,
ln –s asm-arm asm
ln –s asm-arm/arch-926ejs asm-arm/arch
ln –s asm-arm/proc-armv asm-arm/proc
unconfig:
@rm –f $(obj)include/config.h $(obj)include/config.mk
$(obj)board/*/config.tmp $(obj)board/*/*/comfig.tmp
$(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep
unconfig的主要目的是删除原来的配置文件。
make命令前加@,命令仅执行不显示。通常make会把要执行的命令行在命令执行前输出到屏幕上。
- make过程:
1)平台编译器配置:
ifeq($(ARCH), arm)
CROSS_COMPILE = arm-linux-
endif
2)导入其他配置文件
include $(TOPDIR)/config.mk
导入顶层config.mk
顶层config.mk中包含了其他配置规则,如顶层arm_config.mk,board/ateml/at91sam9260ek/config.mk等。
其中board/atmel/at91sam9260ek/config.mk中定义了TEXT_BASE如下:
TEXT_BASE=0x23f00000
uboot编译时将使用TEXT_BASE作为代码段连接的起始地址。
ifneq($(TEXT_BASE),)
LDFLAGS += -Ttext $(TEXT_BASE)
3)autoconf.mk和autoconf.mk.dep
Auto-generate the autoconf.mk file(which is included by all makefiles)
This target actually generates 2 files: autoconf.mk and autoconf.mk.dep.
the dep file is only include in this top level makefile to determine when to regerate the autoconf.mk file .
# -M的意思是说生成dependency文件.
# -MQ是用来指定生成的dependency文件中的target是什么,如不指定,模认为xxxx.o
# 即 gcc -M inlcude/common.h
>$@.dep 生成的结果为:
# include/common.o : ... 即自动以.o为target,这不是我们想要的,我们想要
# autoconfig.mk作为target,因此要-MQ来手动指定,而且,include/common.h会自动
# 作为一个prequisite.
# 结果,common.h的依赖的所有的文件,包括common.h都被写入include/autoconf.mk.dep
# 文件。这样include/autoconf.mk依赖于common.h以及common.h中的一些依赖,而接下来
# 的一些要被编译的target有全都依赖于include/autoconf.mk,因此,一旦common.h有所
# 修改,全部都的重新编译.
# $(CPP) -dM file 会将file中使用的宏全部输出到标准输出.并且将预处理器的预定义
# 宏也输出了. 因此这里有了一个查看于处理器自定义的宏的方法:
# touch foo.h; cpp -dM
foo.h,因为foo.h是个空文件,因此输出的都是预定义宏.
# sed -n是quiet模式,sed使用tools/define2mk.sed脚本来处理输出的宏.将宏处理成
# 了一种适合make处理的autoconf的格式.而且只提取出了common.h中的#define CONFIG_XXX模式的宏,即用户在common.h中自己定义的宏.
$(obj)include/autoconf.mk.dep:$(obj)include/config.h include/common.h
@$(XECHO) Generating $@;
set –e ;
:Generate the dependancies ;
$(CC) –x c –DDO_DEPS_ONLY –M $(HOST_CFLAGS) $(CPPFLAGS)
-MQ $(obj)include/autoconf.mk include/common.h >$@
$(obj)include/autoconf.mk:$(obj)include/config.h
@$(XECHO) Generating $@;
set –e;
: Extract the config macros;
$(CPP) $(CFLAGS)-DDO_DEPS_ONLY –dM include/common.h |
sed –n –f tools/scripts/define2mk.sed >$@
sinclude $(obj)include/atuoconf.mk.dep
include/autoconf.mk依赖于make <board_name>_config 命令生成的include/config.h。因此执行make <board_name>_config命令后再执行make all将更新include/autoconf.mk。
编译选项“-dM”的作用是输出include/common.h中定义的所有宏。根据上面的规则,编译器提取include/common.h中定义的宏,然后输出给tools/scripts/define2mk.sed脚本处理,处理的结果就是include/autoconf.mk文件。其中tools/scripts/define2mk.sed脚本的主要完成了在include/common.h中查找和处理以“CONFIG_”开头的宏定义的功能。
include/common.h文件包含了include/config.h文件,而include/config.h文件又包含了config_defaults.h,configs/at91sam9260ek.h文件。因此include/autoconf.mk实质上就是config_defaults.h,configs/at91sam9260ek.h两文件中“CONFIG_”开头的有效的宏定义的集合
4. uboot镜像编译
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)
all: $(ALL)
下面再来分析u-boot.bin文件生成的过程。ELF格式“u-boot”文件生成规则如下:
$(obj)u-boot: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
$(GEN_UBOOT)
ifeq ($(CONFIG_KALLSYMS),y)
smap=`$(call SYSTEM_MAP,u-boot) |
awk '$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\000"}'` ;
$(CC) $(CFLAGS) -DSYSTEM_MAP=""$${smap}""
-c common/system_map.c -o $(obj)common/system_map.o
$(GEN_UBOOT) $(obj)common/system_map.o
endif
这里生成的$(obj)u-boot目标就是ELF格式的U-Boot文件了。由于CONFIG_KALLSYMS未定义,因此ifeq ($(CONFIG_KALLSYMS),y)与endif间的代码不起作用。
其中depend,$(SUBDIRS),$(OBJS),$(LIBBOARD),$(LIBS),$(LDSCRIPT), $(obj)u-boot.lds是$(obj)u-boot的依赖,而$(GEN_UBOOT)编译命令。
depend dep: $(VERSION_FILE)
for dir in $(SUBDIRS) ; do $(MAKE) –C $$dir _depend ; done
五.程序相关
1. 相关重要定义说明
CFG_CBSIZE 256:console I/O Buffer Size
2. 环境变量的处理:
The "environment" is stored as a list of ' ' terminated "name=value" strings. The end of the list is marked by a double
' '. New entries are always added at the end. Deleting an entry shifts the remaining entries to the front.. Replacing an entry is a
combination of deleting the old value and adding the new one.
The environment is proceeded by a 32 bit CRC over the data part.
环境变量相关文件:
1). common/cmd_nvedit.c getenv,setenv,do_printenv函数等。
2). common/env_common.c 环境变量存储相关结构default_environment等。
3). common/env_nand.c nand中存储env的相关函数readenv,saveenv,writeenv等。
3. main_loop分析
1) reset_cpu() 在cpu/arm926ejs/at91sam9/timer.c中,通过写RSTCR复位控制寄存器。
2) get_tbclk()在cpu/arm926ejs/at91sam9/timer.c中,tbclk=CFG_HZ。
3) abortboot()判断在延迟时间内是否有按键,有则终止,否则动态启动。
4) int run_command(const char *cmd, int flag) 定义于common/main.c
先分析命令行,后根据命令查表执行命令函数。
WARNING:
*
* We must create a temporary copy of the command since the command we get
* may be the result from getenv(), which returns a pointer directly to
* the environment data, which may change magicly when the command we run
* creates or modifies environment variables (like "bootp" does).
5) common/console.c定义与串口控制台相关的函数。 putc,dbg,puts等
clear_ctrlc() 清除ctrl+C 状态。( ctrl_was_pressed = 0)
had_ctrlc() 检测是否有ctrl+C。(return ctrl_was_pressed;)
drivers/serial/atmel_usart.c定义串口相关函数。
serial_getc(),serial_putc(),serial_puts(),serial_init()。
该开发板采用串口3 usart3。
6) main_loop流程:
run_command(preboot,0) ==》abortboot(bootdelay)==》run_command(bootcmd,0)或解析命令执行。
无论preboot还是bootcmd都调用了bootm命令,执行do_bootm()函数(common/cmd_bootm.c),其作用是从内存中引导应用程序或内核映像。do_bootm()调用do_bootm_linux()函数(lib_arm/bootm.c)。
do_bootm_linux最后调用theKernel()函数,其只是把已装入RAM的linux内核映像当做一个函数来调用。
4. nandflash处理
坏块处理
重要文件及函数
- lib_arm/board.c中调用了nand_init()函数。
- drivers/mtd/nand/nand.c中定义了nand_init_chip()函数。
- board/atmel/at91sam9260ek/nand.c定义了board_nand_init()函数。
4.scan_bbt(),nand_scan()函数定义在driver/mtd/nand/nand_base.c,搜索处理坏块。
5. 启动文件分析
Start.s流程:
硬件环境初始化:
进入svc模式;关闭MMU和CACHE;设置时钟频率;配置SDRAM。
重定位:
如果当前代码不在连接指定的地址上,则需要把u-boot从当前的位置拷贝到RAM指定位置中;
建立堆栈,堆栈是进入C函数前必须初始化的。
清.bss区。
跳到start_armboot函数中执行。(lib_arm/board.c)
六.重要数据结构
u-boot的大部分操作都是围绕它自身的数据结构,这些数据结构是通用的,但是不同的板子初始化这些数据不一样。
1.gd 全局数据变量指针,它保存了u-boot运行需要的全局数据。
include/asm-arm/global_data.h 与ARCH相关
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /*serial_init( ) was called */
unsigned long reloc_off; /* 重定位偏移,就是实际定向的位置与编译连接时指定的位置之差,一般为0*/
unsigned long env_addr; /* 环境参数地址 */
unsigned long env_valid; /* 环境参数CRC校验有效标志*/
unsigned long fb_base; /* 帧缓冲基地址 */
#ifdef CONFIG_VFD
unsigned long vfd_type; /* display type */
#endif
void **jt; /* jump table */
} gd_t;
// Global Data Flags
#define GD_FLG_RELOC 0x00001 /* Code was relocated to RAM */
#define GD_FLG_DEVINIT 0x00002 /* Devices has been initialized */
#define GD_FLG_SILENT 0x00004 /* Silent mode */
#define GD_FLG_POSTFAIL 0x00008 /* Critical POST test failed */
#define GD_FLG_POSTSTOP 0x00010 /* POST sequence aborted */
#define GD_FLG_LOGINIT 0x00020 /* Log Buffer has been initialized */
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm(“r8”)
- bd 板子数据指针。
include/asm-arm/u-boot.h 与ARCH相关
typedef struct bd_info {
int bi_baudrate; /*串口波特率*/
unsigned long bi_ip_addr; /*IP 地址*/
unsigned char bi_enetaddr[6]; /* MAC 地址;Ethernet address */
struct environment_s *bi_env;
ulong bi_arch_number; /*unique id for this board */
ulong bi_boot_params; /*启动参数*/
struct /* RAM 配置*/
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
#define bi_env_data bi_env->data
#define bi_env_crc bi_env->crc
- 环境变量指针 env_t
env_t定义于include/environment.h中
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
#ifdef CFG_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE];
} env_t;
env_t *env_ptr = (env_t *) (&environment[0]); // common/env_flash.c
env_ptr指向环境参数区,系统启动时默认的环境参数environment[],定义于common/environment.c中
env_t environment __PPCENV__ ={
ENV_CRC, /* CRC sum*/
#ifdef CFG_REDUNDAND_ENVIRONMENT
1, /* Flags:valid*/
#endif
{
#if defined(CONFIG_BOOTARGS)
“bootargs=” CONFIG_BOOTARGS “