背景:Board →ar7240(ap93)Cpu →mips1、首先弄清楚什么是u-bootUboot是德国DENX小组的开发,它用于多种嵌入式CPU的bootloader程序, uboot不仅支持嵌入式linux系统的引导,当前,它还支持其他的很多嵌入式操作系统。
除了PowerPC系列,还支持MIPS,x86,ARM,NIOS,XScale。
2、下载完uboot后解压,在根目录下,有如下重要的信息(目录或者文件):以下为为每个目录的说明:Board:和一些已有开发板有关的文件。
每一个开发板都以一个子目录出现在当前目录中,子目录存放和开发板相关的配置文件。
它的每个子文件夹里都有如下文件(以ar7240/ap93为例):MakefileConfig.mkAp93.c 和板子相关的代码Flash.c Flash操作代码u-boot.lds 对应的链接文件common:实现uboot命令行下支持的命令,每一条命令都对应一个文件。
例如bootm命令对应就是cmd_bootm.ccpu:与特定CPU架构相关目录,每一款Uboot下支持的CPU在该目录下对应一个子目录,比如有子目录mips等。
它的每个子文件夹里都有入下文件:MakefileConfig.mkCpu.c 和处理器相关的代码sInterrupts.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代码作relocate5、初始化malloc、flash、pci以及外设(比如,网口)6、进入命令行或者直接启动Linux kernel刚一开始由于参考网上代码,我一个劲的对基于smdk2410的板子,arm926ejs的cpu看了N 久,启动过程和这个大致相同。
整个启动中要涉及到四个文件:Start.S →cpu/mips/start.SCache.S →cpu/mips/cache.SLowlevel_init.S →board/ar7240/common/lowlevel_init.SBoard.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 romReservedromExcHandle: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函数,主要目的是工作频率配置,比如cpu的主频,总线(AHB),DDR 工作频率等。
然后执行cache.S中的mips_cache_reset对cache进行初始化。
接着调用mips_cache_lock(这个调用的目的:当代码执行到这个时候,ddr ram还没有配置好,而如果直接调用C语言的函数必须完成栈的设置,而栈必定要在ram中。
所以,只有先把一部分cache拿来当做ram 用。
做法就是把一部分cache配置为栈的地址,锁定。
这样,当读写栈的内存空间时,只会访问cache,而不会访问真的ram地址了。
)这时,配置栈的地址,进行调用函数board_init_f(board.c)进入函数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_f函数中初始化malloc,flash,pci以及外设(如:网口),最后进入命令行或者直接启动Linux Kernel.这样,u-boot的启动工作完成。
流程分析1、最开始系统加电。
ENTRY(_start)程序入口点是_start (原因参考u-boot.lds分析.doc)2、_start:cpu/mips/start.S3、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.cb) main_loop()common/main.cs = getenv(“bootcmd”) 取得环境变量中的启动命令行,如:bootcmd = bootm 0xbf020000run_command(s,0); //执行这个命令行,即bootmc) 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(); //初始化flashDisplay_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中初始化。