资源描述
背景:
Board à ar7240(ap93)
Cpu à mips
1、首先弄清楚什么是u-boot
Uboot是德国DENX小组的开发,它用于多种嵌入式CPU的bootloader程序, uboot不仅支持嵌入式linux系统的引导,当前,它还支持其他的很多嵌入式操作系统。
除了PowerPC系列,还支持MIPS,x86,ARM,NIOS,XScale。
2、下载完uboot后解压,在根目录下,有如下重要的信息(目录或者文件):
根目录
(u-boot)
board
common
cpu
disk
Doc
drivers
fs
include
Lib_xxx
net
tools
以下为为每个目录的说明:
Board:和一些已有开发板有关的文件。每一个开发板都以一个子目录出现在当前目录中,子目录存放和开发板相关的配置文件。它的每个子文件夹里都有如下文件(以ar7240/ap93为例):
Makefile
Config.mk
Ap93.c 和板子相关的代码
Flash.c Flash操作代码
u-boot.lds 对应的链接文件
common:实现uboot命令行下支持的命令,每一条命令都对应一个文件。例如bootm命令对应就是cmd_bootm.c
cpu:与特定CPU架构相关目录,每一款Uboot下支持的CPU在该目录下对应一个子目录,比如有子目录mips等。它的每个子文件夹里都有入下文件:
Makefile
Config.mk
Cpu.c 和处理器相关的代码s
Interrupts.c 中断处理代码
Serial.c 串口初始化代码
Start.s 全局开始启动代码
Disk:对磁盘的支持
Doc:文档目录。Uboot有非常完善的文档。
Drivers:Uboot支持的设备驱动程序都放在该目录,比如网卡,支持CFI的Flash,串口和USB等。
Fs:支持的文件系统,Uboot现在支持cramfs、fat、fdos、jffs2和registerfs。
Include:Uboot使用的头文件,还有对各种硬件平台支持的汇编文件,系统的配置文件和对文件系统支持的文件。该目下configs目录有与开发板相关的配置文件,如ar7240_soc.h。该目录下的asm目录有与CPU体系结构相关的头文件,比如说mips对应的有asm-mips。
Lib_xxx:与体系结构相关的库文件。如与ARM相关的库放在lib_arm中。
Net:与网络协议栈相关的代码,BOOTP协议、TFTP协议、RARP协议和NFS文件系统的实现。
Tools:生成Uboot的工具,如:mkimage等等。
3、mips架构u-boot启动流程
u-boot的启动过程大致做如下工作:
1、cpu初始化
2、时钟、串口、内存(ddr ram)初始化
3、内存划分、分配栈、数据、配置参数、以及u-boot代码在内存中的位置。
4、对u-boot代码作relocate
5、初始化malloc、flash、pci以及外设(比如,网口)
6、进入命令行或者直接启动Linux kernel
刚一开始由于参考网上代码,我一个劲的对基于smdk2410的板子,arm926ejs的cpu看了N久,启动过程和这个大致相同。
整个启动中要涉及到四个文件:
Start.S à cpu/mips/start.S
Cache.S à cpu/mips/cache.S
Lowlevel_init.S à board/ar7240/common/lowlevel_init.S
Board.c à lib_mips/board.c
整个启动过程分为两个阶段来看:
Stage1:系统上电后执行汇编代码
Stage2:通过一些列设置搭建了C环境,通过汇编指令跳转到C语言执行.
Stage1:
程序从Start.S的_start开始执行.(至于为什么,参考u-boot.lds分析.doc)
先查看start.S文件吧!~
从_start标记开始会看到一长串莫名奇妙的代码:
RVECENT(reset,0) /* U-boot entry point */ /*U-Boot开始执行的代码起始地址*/
RVECENT(reset,1) /* software reboot */ /*软重启时U-Boot开始执行的起始地址*/
RVECENT(romReserved,2) /*保留本代码所在的地址,重新映射调试异常向量时可以使用该空间*/
RVECENT(romReserved,3)
RVECENT(romReserved,4)
RVECENT(romReserved,5)
RVECENT(romReserved,6)
RVECENT(romReserved,7)
RVECENT(romReserved,8)
RVECENT(romReserved,9)
…
…
回过头看刚开始的定义有这样的代码:
可以找到:
#define RVECENT(f,n) \
b f; nop
原来这只是一个简单的跳转指令,f为一个标记,b为跳转指令。
然后看最后,发现:
romReserved:
b romReserved
romExcHandle:
b romExcHandle
这两个标记都构建了无意义的死循环。
通过_start标记处的语句RVECENT(reset,0) 代码跳转到标记reset的地方,该段代码的操作就是对寄存器的清零操作了。Mfc0和mtc0指令是对寄存器的一些读写.
在接下来是对协处理器的操作了,其中包括:
CP0_WATCHLO,
CP0_WATCHHI,
CP0_CAUSE,
CP0_COUNT,
CP0_COMPARE
之后,配置寄存器CP0_STATUS,设置所使用的协处理器,中断以及cpu运行级别(核心级)。
配置gp寄存器,把GOT段的地址赋给gp寄存器。(gp寄存器的用处会在后面relocate code部分详细解释)
接下来执行lowlevle_init.S的lowlevel_init(la t9, lowlevel_init)函数,主要目的是工作频率配置,比如cpu的主频,总线(AHB),DDR 工作频率等。
然后执行cache.S中的mips_cache_reset(la t9, simple_mips_cache_reset)对cache进行初始化。接着调用mips_cache_lock(la t9, mips_cache_lock)
(这个调用的目的:当代码执行到这个时候,ddr ram还没有配置好,而如果直接调用C语言的函数必须完成栈的设置,而栈必定要在ram中。所以,只有先把一部分cache拿来当做ram用。做法就是把一部分cache配置为栈的地址,锁定。这样,当读写栈的内存空间时,只会访问cache,而不会访问真的ram地址了。)
这时,配置栈的地址,进行调用函数board_init_f (board.c)进入函数board_init_f(la t9, board_init_f)后,首先做一些列的初始化:
Timer_init 时钟初始化
Env_init 环境变量初始化(取得环境变量存放的地址)
Init_baudrate 串口速率
Serial_init 串口初始化
Console_init_f 配置控制台
Display_banner 显示u-boot启动信息,版本号等。
Checkboard 执行board相关的操作
Init_func_ram 初始化内存,配置ddr controller
这一系列工作完成后,串口和内存都已经可以用了。
然后,就要把内存进行划分,在内存的最后一部分,留出u-boot代码大小的空间,准备把u-boot代码从flash搬移到这里。
然后,是堆的空间,malloc的内存就来自于这里。
紧接着放两个全局数据结构bd_infoglobal_data和环境变量boot_params。
最后,是栈的空间。
当内存划分好后,就准备进行relocate code了。
(relocate code含义:
通常u-boot的执行代码肯定是在flash上(调试可以在ram上).当启动起来之后,要把它从flash上搬移到ram里运行
)
但是,存在的问题是,flash地址和ram地址是不同的。当我们把代码从flash搬移到ram中后,当执行函数跳转时,代码里的函数地址还是flash的地址,一跳,又重新跳回去了(跳回了flash)。
IPC(position-independent code) 由此引出了。
原理:
当使用IPC方式时,在用gcc编译时需要加上-fpic的选项。编译器会为你的可执行代码建立一个GOT(global offset table)的段。一个地址在GOT表中有一项,里面存放地址的信息,在使用这个地址时,只要根据这个地址的编号(也可以叫做偏移量offset)找到表中相应的项目,就可以取得那个地址了。
而如果位置发生变化,只要对GOT 表中的地址进行修改就可以了。
例:
Lw t9,1088(gp)
Jalr t9
这里,gp存放的就是GOT表的起始地址,而1088就是要调用函数offset,也就说GOT表的那个位置存放着它的地址。Lw t9,1088(gp)把函数地址放入t9寄存器,然后调用就可以了。
Relocate code说简单一点就是:把u-boot的执行代码直接从flash里copy到ram的相应区域。
然后,把GOT表中的地址都加上一个偏移量,这个偏移量就是flash里的地址与ram里的地址差。
这里完成的操作还有一些其他工作,比如:设置新的栈指针,从flash代码里跳转到ram代码里等等.
之后,进入board.c的board_init_r函数。进入stage2。
Stage2:
在board_init_r函数中初始化malloc,flash,pci以及外设(如:网口),最后进入命令行或者直接启动Linux Kernel.
这样,u-boot的启动工作完成。
流程分析
1、 最开始系统加电。
ENTRY(_start)程序入口点是_start (原因参考u-boot.lds分析.doc)
2、_start:cpu/mips/start.S
3、la t9,board_init_f ;将函数board_init_f地址赋给t9寄存器
J t9 ;程序调转到t9寄存器中保存的地址指向的指令
注:(这里有点小疑问:代码运行到这里,pc指向的应该是cache中划分出来的临时ram?)
a) board_inif_f() lib_mips/board.c 初始化外部内存
relocate_code() 回到cpu/mps/start.S中继续执行
4、la t9,board_init_r cpu/mips/start.S 将函数board_init_r地址赋给t9寄存器
J t9 跳转到t9寄存器中保存的地址指向的指令
a) board_init_r() 函数 lib_mips/board.c
b) main_loop() common/main.c
s = getenv(“bootcmd”) 取得环境变量中的启动命令行,
如:
bootcmd = bootm 0xbf020000
run_command(s,0); //执行这个命令行,即bootm
c) do_bootm() command/cmd_bootm.c
//printf(“##Booting image at %08lx…\n”,addr);
5、bootm启动内核
a) do_bootm_linux() lib_mips/mips_linux.c
函数解析
1、 board_init_f()
a) void board_init_f(ulong bootflag)
{
For (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++ init_fnc_ptr)
{
If ((*init_fnc_ptr)() != 0)
{
Hang();
}
}
}
/*
调用init_sequence函数队列,对板子进行一些初始化,详细见后面初始化external memory,初始化堆栈用cache作堆栈
*/
relocate_code(addr_sp,id,addr); //回到cpu/mips/start.S中
/*NOTREACHED-relocate_code() does not return*/
b) typedef int (init_fnc_t) (void);
init_fnc_t * init_sequence[] =
{
/* Clx_board_init, //初始化GPIO,CPU速度,PLL,SDRAM等 */
Timer_init, //时钟初始化
Env_init, //环境变量初始化
Incaip_set_cpuclk, //根据环境变量设置CPU时钟
Init_baudrate, //初始化串口波特率
Serial_init, /* serial communicatioins setup */
Console_init_f, //串口初始化,后面才能显示
Display_banner, //在屏幕上输出一些显示信息
Checkboard,
Init_func_ram,
NULL,
};
2、 board_init_r()
a) 调用一些列的初始化函数
b) 初始化Flash设备
c) 初始化系统内存分配函数
d) 如果目标系统拥有NAND设备,则初始化NAND设备
e) 如果目标系统有显示设备,则初始化该类设备
f) 初始化相关网络设备,填写IP、MAC地址等
g) 进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作
Void board_init_r(gd_t *id, ulong dest_addr)
{
/*configure available FLASH banks*/ //配置可用的flash单元
Size = flash_init(); //初始化flash
Display_flash_config(size); //显示flash的大小
/*initialize malloc() area*/
Mem_malloc_init();
Malloc_bin_reloc();
Puts(“NAND”);
Nand_init(); /*go init the NAND*/ //NAND初始化
/*relocate environment function pointers etc.*/
Env_relocate(); //初始化环境变量
/*board MAC addresss*/
S = getenv(“ethaddr”); //以太网MAC地址
For (I = 0;I < 6; ++i)
{
Bd->bi_enetaddr[i] = s?simple_strtoul(s,&e,16):0;
If (s)
S = (*e)?e + 1:e;
}
/*IP Address*/
Bd->bi_ip_addr = getenv_IPaddr(“ipaddr”);
Pci_init(); //pci初始化配置
/**leave this here (after malloc(),environment and PCI are working **/
/*initialize devices*/
Devices_init();
Jumptable_init();
/*initialize the console (after the relocation and deivces init)*/
Console_init_t(); //串口初始化
/miscellaneous platform dependent initialisationss/
Misc_init_r();
Puts(“Net”);
Eth_initialize(gd->bd);
/*main_loop() can return to retry autoboot,if so just run it again.*/
For (;;)
{
Main_loop();
/*循环执行,试图自动启动,接受用户从串口输入的命令,然后进行相应的工作,设置延时时间,确定目标板是进入下载模式还是启动加载模式
*/
}
/* NOTREACHED - no way out of command loop except booting */
}
3、 main_loop()
void main_loop(void)
{
S = getenv(“bootdelay”); //从环境变量中取得bootdelay内核等待延时
Bootdelay = s ? (int)simple_strtol(s,NULL,10) : CONFIG_BOOTDELAY;
Debug(“###main_loop entered:bootdelay = %d\n\n”, bootdelay);
S = getenv(“bootcmd”); //从环境变量中取得bootcmd启动命令行
/*
例:
bootcmd = tftp;
bootm或者bootcmd = bootm 0xbf020000
*/
Char *s1 = getenv(“bootargs”); //从环境变量中取得bootargs启动参数
Debug(“###main_loop:bootcmd = \”%s\”\n”, s ? s : “<UNDEFINED>”);
Run_command(s, 0); //执行启动命令
//手动输入命令
For (;;)
{
Len = readline(CFG_PROMPT); //读取键入的命令道CFG_PROMPT中
Rc = run_command(lashcommand, flag); //执行这个命令
}
#endif /*CFG_HUSH_PARSER*/
}
4、 do_bootm()
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
这个函数看着挺长的,作用是将内核解压缩,然后调用do_bootm_linux引导内核
5、do_bootm_linux() lib_mips/mips_linux.c
打印信息Starting kernel …
Void do_bootm_linux(cmd_tbl_t * cmd tp, int flag, int argc, char *argv[],
Ulong addr, ulong * len_ptr, int verify)
{
Char * commandline = getenv(“bootargs”);
theKernel =
(void (*)(int ,char **, char **, int *)) ntohl(hdr->ih_ep);
//hdr为指向image header的指针,hr->ih_ep就是我们用mkimage创建image时-e选项的参数:内核的入口地址
Linux_params_init(UNCACHED_SDRAM(gd->bd->bi_boot_params),commandline);
/*we assume that the kernel is in place*/
Printf(“\nStarting kernel … \n\n”);
theKernel(linux_argc, linux_argv, linux_env,0); //启动内核
}
u-boot向内核传递启动参数由一系列在include/configs.h中的宏控制,启动参数传递的地址在board_init中初始化
展开阅读全文