1、背景:Board ar7240(ap93)Cpu mips1、首先弄清楚什么是 u-bootUboot 是德国 DENX 小组的开发,它用于多种嵌入式 CPU 的 bootloader 程序, uboot 不仅支持嵌入式 linux 系统的引导,当前,它还支持其他的很多嵌入式操作系统。除了 PowerPC 系列,还支持 MIPS,x86,ARM,NIOS,XScale 。2、下载完 uboot 后解压,在根目录下,有如下重要的信息(目录或者文件):根目录(u-boot)board commoncpu disk Doc drivers fs include Lib_xxxnet tools以下为
2、为每个目录的说明:Board:和一些已有开发板有关的文件。每一个开发板都以一个子目录出现在当前目录中,子目录存放和开发板相关的配置文件。它的每个子文件夹里都有如下文件(以 ar7240/ap93为例):MakefileConfig.mkAp93.c 和板子相关的代码Flash.c Flash 操作代码u-boot.lds 对应的链接文件common:实现 uboot 命令行下支持的命令,每一条命令都对应一个文件。例如 bootm 命令对应就是 cmd_bootm.ccpu:与特定 CPU 架构相关目录,每一款 Uboot 下支持的 CPU 在该目录下对应一个子目录,比如有子目录 mips 等。
3、它的每个子文件夹里都有入下文件: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 使用的头文件,还有对各种硬件平台支持的汇编文件,系统的配置文件和对文
4、件系统支持的文件。该目下 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
5、)初始化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/lowlev
6、el_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) /* softwar
7、e 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原来
8、这只是一个简单的跳转指令,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,设置
9、所使用的协处理器,中断以及 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
10、 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_i
11、nit 环境变量初始化(取得环境变量存放的地址)Init_baudrate 串口速率Serial_init 串口初始化Console_init_f 配置控制台Display_banner 显示 u-boot 启动信息,版本号等。Checkboard 执行 board 相关的操作Init_func_ram 初始化内存,配置 ddr controller这一系列工作完成后,串口和内存都已经可以用了。然后,就要把内存进行划分,在内存的最后一部分,留出 u-boot 代码大小的空间,准备把u-boot 代码从 flash 搬移到这里。然后,是堆的空间,malloc 的内存就来自于这里。紧接着放两个全局
12、数据结构 bd_infoglobal_data 和环境变量 boot_params。最后,是栈的空间。当内存划分好后,就准备进行 relocate code 了。(relocate code 含义:通常 u-boot 的执行代码肯定是在 flash 上(调试可以在 ram 上).当启动起来之后,要把它从flash 上搬移到 ram 里运行)但是,存在的问题是,flash 地址和 ram 地址是不同的。当我们把代码从 flash 搬移到 ram中后,当执行函数跳转时,代码里的函数地址还是 flash 的地址,一跳,又重新跳回去了(跳回了 flash)。IPC(position-independe
13、nt code) 由此引出了。原理:当使用 IPC 方式时,在用 gcc 编译时需要加上-fpic 的选项。编译器会为你的可执行代码建立一个 GOT(global offset table)的段。一个地址在 GOT 表中有一项,里面存放地址的信息,在使用这个地址时,只要根据这个地址的编号(也可以叫做偏移量 offset)找到表中相应的项目,就可以取得那个地址了。而如果位置发生变化,只要对 GOT 表中的地址进行修改就可以了。例:Lw t9,1088(gp)Jalr t9这里,gp 存放的就是 GOT 表的起始地址,而 1088 就是要调用函数 offset,也就说 GOT 表的那个位置存放着它
14、的地址。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
15、以及外设(如:网口) ,最后进入命令行或者直接启动 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_mip
16、s/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); /执行这
17、个命令行,即 bootmc) do_bootm() command/cmd_bootm.c/printf(“#Booting image at %08lxn”,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();/*调用 i
18、nit_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, /时
19、钟初始化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 设备,则初始
20、化 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*
21、/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 bi_enetaddri = s?simple_strtoul(s,If (s)S = (*e)?e + 1:e;/*IP Add
22、ress*/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 platfo
23、rm 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_l
24、oop()void main_loop(void)S = getenv(“bootdelay”); /从环境变量中取得 bootdelay 内核等待延时Bootdelay = s ? (int)simple_strtol(s,NULL,10) : CONFIG_BOOTDELAY;Debug(“#main_loop entered:bootdelay = %dnn”, bootdelay);S = getenv(“bootcmd”); /从环境变量中取得 bootcmd 启动命令行/*例:bootcmd = tftp;bootm 或者 bootcmd = bootm 0xbf020000*/
25、Char *s1 = getenv(“bootargs”); /从环境变量中取得 bootargs 启动参数Debug(“#main_loop:bootcmd = ”%s”n”, s ? s : “”);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 *c
26、mdtp, 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”);theKern
27、el = (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 nn”);theKernel(linux_argc, linux_argv, linux_env,0); /启动内核u-boot 向内核传递启动参数由一系列在 include/configs.h 中的宏控制,启动参数传递的地址在 board_init 中初始化