uboot分析:
linux的启动过程:嵌入式系统上电后先执行uboot,然后uboot负责初始化DDR,初始化FLASH,然后将OS(操作系统)从FLASH中读取到DDR中,然后启动OS,OS启动后uboot就没有用处啦。
uboot的作用:1.用来启动操作系统的内核;
2.部署整个计算机系统
3.包含操作flash等板子上的硬盘驱动
4.提供一个命令行界面用来操作。
uboot的最终目的是启动内核。
uboot的入口是开机自动启动,唯一出口是启动内核。
启动流程
大多数BootLoader都分为stage1和stage2两大部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
1.stage1(start.s代码结构)
U-boot的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:
(1) 定义入口。由于一个可执行的image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(exception vector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化内存控制器 。
(5)将rom中的程序复制到ram中。
(6)初始化堆栈 。
(7)转到ram中执行,该工作可使用指令ldrpc来完成。
2.stage2(C语言代码部分)
lib_arm/board.c中的start armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数主要完成如下操作:
(1)调用一系列的初始化函数。
(2)初始化flash设备。
(3)初始化系统内存分配函数。
(4)如果目标系统拥有nand设备,则初始化nand设备。
(5)如果目标系统有显示设备,则初始化该类设备。
(6)初始化相关网络设备,填写ip,c地址等。
(7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。
****************************************************************************************************************************
文件介绍:(主要文件)
mkconfig:uboot配置阶段主要的配置脚本;
mkmovi:一个脚本,和iNand/SD卡启动有关;
rules.mk:uboot的makefile使用规则。
需要认真看的有2个:mkconfig和Makefile。一个负责uboot的配置,一个负责编译。
uboot的配置阶段(其实就是根目录下面的mkconfig脚本和Makefile中配置有关的部分)主要解决的问题就是在可移植性领域能够帮助我们确定具体的文件夹的路径,然后编译时可以找到应该找到的文件,才能编译成功。因此board目录下的不同会造成配置时的不同。如果移植时没注意这里肯定要失败。
文件夹里面比较重要的,后面会分析涉及到的有:board、common、cpu、drivers、include、lib_arm、lib_generic、sd_fusing
uboot如何启动内核:
第一步:加载内核到DDR中;
第二步:校验内核格式、CRC等;
第三步:准备传参;
第四步:跳转执行内核。
ARM处理器在复位的时候从地址0x00000000取第一条指令,嵌入式系统的开发板都要把板上ROM 或Flash 映射到这个地址。
bootloader两种加载模式:本地和远程;最终目的:加载内核;
norflash启动方式(可以随机访问);Bootloader 一般放在Flash 的底端或者顶端,这要根据处理器的复位向量设置。要使
Bootloader 的入口位于处理器上电执行第一条指令的位置。
Bootloader与内核的交互是单向的;
****************************************************************************************************************************
uboot的编译:
1.顶层目录下的Makefile:负责uboot整体配置编译
关键代码段:
mx6q_tqimx6q_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 mx6q_tqimx6q freescale mx6
(ARCH CPU BOARD SOC)
定义交叉编译工具:
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
工具链的定义可以在Makefile中定义,也可以在编译执行的命令行中再定义;
export CROSS_COMPILE=/home/linux/rothwell/toolchain/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-none-linux-gnueabi-
uboot映像编译的依赖:
Always append ALL so that arch config.mk's can add custom ones
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)
all: $(ALL)
$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) -O srec $< $@
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(obj)u-boot.ldr: $(obj)u-boot
$(obj)tools/envcrc --binary > $(obj)env-ldr.o
$(LDR) -T $(CONFIG_BFIN_CPU) -c $@ $< $(LDR_FLAGS)
............
2.开发板配置头文件
/include/configs/mx6q_tqimx6q.h 根据同目录下的mx6q_sabresd.h复制
这个头文件中主要定义了两类变量。
一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等。例如:
#define CONFIG_MX6Q_TQIMX6Q
#define CONFIG_FLASH_HEADER
#define CONFIG_FLASH_HEADER_OFFSET 0x400
#define CONFIG_MX6_CLK32 32768
一类是参数,用来定义总线频率、串口波特率、Flash 地址等参数。例如:
#define DISPLAY_DATA_POLARITY 0
#define DISPLAY_DATA_ENABLE_POLARITY 1
#define IPU_NUM 2 // 1 for IPU1, 2 for IPU2.
#define DI_NUM 0 // 0 for DI0, 1 for DI1.
3.编译指令:
export ARCH=arm
export CROSS_COMPILE=/home/linux/rothwell/toolchain/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-none-linux-gnueabi-
make mx6q_tqimx6q_config
make -j8
或者在build.sh修改shell:
#!/bin/sh
export CPUS=`grep -c processor /proc/cpuinfo`
export ARCH=arm
export CROSS_COMPILE=/home/linux/rothwell/toolchain/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-none-linux-gnueabi-
export PATH=/home/linux/rothwell/toolchain/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin:$PATH
make distclean
make mx6q_tqimx6q_config
make -j${CPUS}
然后直接执行./build.sh就可以啦。
需要注意的是,在添加交叉编译工具的时候,路径要与工具链一致。
安装编译工具配置环境变量的时候有两种方式:
一种是:在/etc/bash.bashrc;
一种是:在/etc/profile;
都只在最后一行添加export PATH=/home/linux/rothwell/toolchain/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin
区别在于
profile用于设置系统级的环境变量和启动程序,在这个文件下配置会对所有用户生效;
.bashrc文件,这种方法更为安全,它可以把使用这些环境变量的权限控制到用户级别,这里是针对某一个特定的用户。
我使用的是第一种方式。
移植uboot基本步骤:
1.在顶层makefile中为开发板添加新的配置选项;
2.创建一个新目录存放开发板相关代码,并添加相关文件;
3.为开发板添加新的配置文件;
4.配置开发板 make ..._config
5.编译uboot;
6.添加驱动或者功能选项;
7.调试。
****************************************************************************************************************************
uboot启动:
board/freescal/mx6q_tqimx6q/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。
第一个要链接的是cpu/arm_cortexa8/start.o,那么U-Boot 的入口指令一定位于这个程序中。
代码开始处:(include/asm-arm/U-boot-arm.h)
extern ulong _armboot_start; /* code start */
extern ulong _bss_start; /* code + data end == BSS start */
extern ulong _bss_end; /* BSS end */
第一阶段:
1.cpu/arm_cortexa8/start.S 硬件设备初始化
这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。
将CPU 的工作模式设为管理模式(svc)。
2.为加载Bootloader 的第二阶段代码准备RAM 空间。
所谓准备RAM 空间,就是初始化内存芯片,使它可用。对于freescal/mx6q_tqimx6q,通过
在start.S 中调用lowlevel_init 函数来设置存储控制器,使得外接的SDRAM 可用。代码在
board/freescal/mx6q_tqimx6q/lowlevel_init.S 中。lowlevel_init.S文件是开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件中的相关宏。
3.复制Bootloader 的第二阶段代码到 RAM 空间中。
这里将整个U-Boot 的代码(包括第一、第二阶段)都复制到SDRAM 中,这在cpu/arm_cortexa8/start.S 中实现。
4.设置好栈。
栈的设置灵活性很大,只要让sp 寄存器指向一段没有使用的内存即可。
第二阶段:
从lib_arm/board.c 中的start_armboot 函数开始。
1.start_armboot 是U-Boot 执行的第一个C语言函数,完成系统初始化工作,进入主循环,
处理用户输入的命令。
下面的函数在start_armboot中首先调用,进行一系列的初始化工作;
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /*cpu的初始化*/
#endif
board_init, /* 基板初始化 */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* 中断*//*初始化不做任何事,函数中只有 return 0*/
#endif
timer_init, /* 时钟 *//*初始化调用函数 setup_gpt(void)*/
env_init, /* 环境变量*/
init_baudrate, /* 波特率 */
serial_init, /* 串口通信 */
console_init_f, /* 控制台初始化第一阶段*/
display_banner, /* 通知代码运行到此处*/
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c, /*i2c*/
#endif
dram_init, /* 配置可用的内存区 */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
arm_pci_init,
#endif
display_dram_config,
NULL,
};
****************************************************************************************************************************
初始化内存分配函数:malloc可用内存由mem_malloc_start,mem_malloc_end指定。而当前分配的位置则是mem_malloc_brk。mem_malloc_init负责初始化这三个变量。malloc则通过sbrk函数来使用和管理这片内存。
mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN);
_armboot_start的指定是:extern ulong _armboot_start; /* code start */
_armboot_start具体的值要到链接的时候才能确定_armboot_start.
static ulong mem_malloc_start = 0;
static ulong mem_malloc_end = 0;
static ulong mem_malloc_brk = 0;
static void mem_malloc_init (ulong dest_addr)
{
mem_malloc_start = dest_addr;
mem_malloc_end = dest_addr + CONFIG_SYS_MALLOC_LEN;
mem_malloc_brk = mem_malloc_start;
memset ((void *) mem_malloc_start, 0,
mem_malloc_end - mem_malloc_start);
}
在start_armboot函数中的nand_init 和 onenand_init 说明:
OneNand是针对消费类电子和下一代移动手机市场而设计的,一种高可靠性嵌入式存储设备。
OneNand既实现NOR Flash的高速读取速度,又保留了Nand Flash的大容量数据存储的优点。
****************************************************************************************************************************
Makefile分析
VERSION = 2009 (主版本号)
PATCHLEVEL = 08 (次版本号)
SUBLEVEL = (再次版本号)
EXTRAVERSION =
ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif
TIMESTAMP_FILE = $(obj)include/timestamp_autogenerated.h
VERSION_FILE = $(obj)include/version_autogenerated.h (这类头文件原本是没有的,只有在编译之后才会有)
HOSTARCH := $(shell uname -m | \ (HOSTARCH:主机+架构)(shell uname 得到当前CPU的版本号) (| 管道)
sed -e s/i.86/i386/ \ (sed:替换)
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
-e s/sa110/arm/ \
-e s/powerpc/ppc/ \
-e s/ppc64/ppc/ \
-e s/macppc/ppc/)
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
静默编译:(编译不打印信息)(使用方法就是编译时make -s,-s会作为MAKEFLAGS传给Makefile,在这段代码作用下XECHO变量就会被变成空(默认等于echo),于是实现了静默编译。)
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE := $(CURDIR)
TOPDIR := $(SRCTREE)
LNDIR := $(OBJTREE)
export TOPDIR SRCTREE OBJTREE
(1)OBJTREE:编译出的.o文件存放的目录的根目录;
(2)SRCTREE: 源码目录,其实就是源代码的根目录,也就是当前目录。
在默认编译下,OBJTREE和SRCTREE相等;在O=xx这种编译下OBJTREE和SRCTREE不相等。Makefile中定义这两个变量,其实就是为了记录编译后的.o文件往哪里放,就是为了实现O=xx的这种编译方式的。
# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
config.mk不是源码自带的,要在配置过程中才会生成这个文件,在下一行export导出了这5个变量作为环境变量。所以着两行加起来其实就是为当前 makefile定义了5个环境变量而已。
ARCH的值来自于我们的配置过程,它的值会影响后面的CROSS_COMPILE环境变量的值。ARCH的意义是定义当前编译的目标CPU的架构。
CROSS_COMPILE是定义交叉编译工具链的前缀的(用前缀加上后缀来定义编译过程中用到的各种工具链中的工具)。实际运用时,可以在Makefile中去更改设置CROSS_COMPILE的值,也可以在编译时用make CROSS_COMPILE=xxxx来设置,而且编译时传参的方法可以覆盖Makefile里面的设置。
mx6q_sabresd_iram_config : unconfig
@[ -z "$(findstring iram_,$@)" ] || \
{ echo "TEXT_BASE = 0x00907000" >$(obj)board/freescale/mx6q_sabresd/config.tmp ; \
echo "... with iram configuration" ; \
}