1、背景: 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 inc
2、lude 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:与
3、特定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现在支持c
4、ramfs、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的工具,如:mki
5、mage等等。 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久,启动过程和这个大致相同。 整个启动中要涉及到四个文
6、件: 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.
7、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(romReser
8、ved,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
9、 这两个标记都构建了无意义的死循环。 通过_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 c
10、ode部分详细解释) 接下来执行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中
11、所以,只有先把一部分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 配置控制台 Di
12、splay_banner 显示u-boot启动信息,版本号等。 Checkboard 执行board相关的操作 Init_func_ram 初始化内存,配置ddr controller 这一系列工作完成后,串口和内存都已经可以用了。 然后,就要把内存进行划分,在内存的最后一部分,留出u-boot代码大小的空间,准备把u-boot代码从flash搬移到这里。 然后,是堆的空间,malloc的内存就来自于这里。 紧接着放两个全局数据结构bd_infoglobal_data和环境变量boot_params。 最后,是栈的空间。 当内存划分好后,就准备进行r
13、elocate code了。 (relocate code含义: 通常u-boot的执行代码肯定是在flash上(调试可以在ram上).当启动起来之后,要把它从flash上搬移到ram里运行 ) 但是,存在的问题是,flash地址和ram地址是不同的。当我们把代码从flash搬移到ram中后,当执行函数跳转时,代码里的函数地址还是flash的地址,一跳,又重新跳回去了(跳回了flash)。 IPC(position-independent code) 由此引出了。 原理: 当使用IPC方式时,在用gcc编译时需要加上-fpic的选项。编译器会为你的可执行代码建立一个GOT
14、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的执
15、行代码直接从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、 最开始系统加电。 E
16、NTRY(_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中继续执行
17、 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() co
18、mmand/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)() !=
19、 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_boa
20、rd_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,
21、 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
22、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.*/
23、 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初始化配置
24、/**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(); P
25、uts(“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
26、 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或者bootcm
27、d = bootm 0xbf020000
*/
Char *s1 = getenv(“bootargs”); //从环境变量中取得bootargs启动参数
Debug(“###main_loop:bootcmd = \”%s\”\n”, s ? s : “
28、行这个命令 } #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, ch
29、ar *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中初始化






