u-boot_note

u-boot 介绍:

  对于计算机来说 , 从一开始上机通电是无法直接启动操作系统的 , 这中间需要一个引导过程 , 嵌入式Linux系统同样离不开引导程序 ,  这个启动程序就叫启动加载程序(Bootloader)  ,Bootloader 主要是进行一些基础必要硬件的初始化 (cpu_init ,memory_init , UART_init ...) , 为最终调用 kernel 作准备 .

  对于嵌入式系统而言 , Bootloader 是基于特定的硬件平台实现的 . 因此 , 几乎不可能有一个Bootloader 能适用所有的嵌入式系统 , 不同的处理器架构都有不同的Bootloader , 不同的硬件也需要不同的Bootloader , Bootloader 不仅仅依赖CPU的体系架构 , 而且依赖嵌入式开发板设备的配置 , 也就是说 , 就算是应用相同cpu 体系架构的 两块开发版 , 要想让运行在一块板子上的 Bootloader 运行在另一块板子上 , 一般都需要修改Bootloader 的源程序 .

  但是 , 大部分的Bootloader 他们都有相似之处 , 而且相同cpu体系架构的Bootloader 很多源代码都有相似之处 , 所以 , 就产生了u - boot , u - boot 能支持PowerPC , ARM , MIPS ,和 X86 等体系结构 , 支持的板子也多达上百种 , 我们通过自己的修改也可以使用自己的板子 .

我所用的u-boot 的下载地址: http://pan.baidu.com/s/1qYktHTy

  

u-boot 源码结构:

  u-boot 的 顶层目录下有30多个子目录 , 分别存放和管理不同的源程序 . 这些目录存放的文件也有其规则, 可以分为3类 .

  1 . 第一类目录与处理器体系架构和开发板硬件直接相关 .

  2 . 第二类目录是一些通用函数或驱动程序 .

  3 . 第三类是u-boot的应用程序, 工具 , 文档 .

  下面是相关目录的简介

  board       :  存放的开发板相关的目录 , 如 board/freescale 等目录.

  cpu        :  存放的cpu相关的目录 , 如 cpu/arm_cortexa8 等目录.

  lib_xxx     :  与体系架构相关的库文件 , 如与arm相关的库就存放在 lib_arm 目录当中 .

  include     : u-boot 使用的头文件 , 还有支持各种硬件平台的汇编文件 , 系统的相关配置文件和支持文件系统的文件 , 该目录下的configs 目录中有与开发板相关的配置文件 .

  common   : 实现u-boot 命令行下的支持的命令 , 每一条命令都对应一个文件 , 例如bootm 命令对应的就是cmd_bootm.c

  lib_generic  : 通用库函数的实现 .

  net       : 与网络协议相关的代码 , BOOTP , TFTP , RARP , 和NFS文件系统的实现 .

  fs       : 支持的文件系统 , 如 cramfs . fat . fdos . jffs2 和 registerfs.

  drivers             : u-boot 支持的设备驱动程序都放在该目录中 , 如串口驱动 , usb驱动 , 网卡驱动 .

  disk      : 对磁盘的支持.

  doc       :  文档目录 .

  tools     : 生成u-boot 的工具 .

  examples  : 一些独立运行的运用程序的例子 .

u-boot 配置编译 :

  u-boot 的源码是通过makefile 来整体编译的 , 顶层目录下的Makefile 完成对开发板的整体配置 , 然后递归调用各级子目录下的Makefile , 最后把所有编译过的程序链接成u-boot 的映像 , 下面以arm_cortexa8处理器的 mx6q_sabresd 为例 , 介绍u - boot 的配置编译方法 , 并简单的分析其原理 .

  

  1 . 基本的配置编译方法

  配置编译u-boot 的方法十分简单 ,  只需要在u-boot 顶层目录下执行如下两个命令.

  # make <board_name>_config

  # mkae

  第一条命令为配置u-boot , 例如我们板子的的名字是mx6q_sabresd , 我们的命令就是 make mx6q_sabresd_config , 第二条命令是编译 , 一般我会在其后面加上 , make -j100 (请不要轻易尝试, 如果是处理器不是很好的情况下) .

  

  2 . 在编译完成后 , 我们会得到以下文件

    a . 在make mx6q_sabresd_config  后 , 我们会得到在 include/config.mk文件.

      这个文件的内容是  ARCH   = arm                                                                    
                CPU    = arm_cortexa8                                                           
                BOARD  = mx6q_sabresd                                                           
                VENDOR = freescale                                                              
                SOC    = mx6

      根据上面的文件 , 我们可以得到在mx6q_sabresd  平台下 , 我们的相关目录如下

                board/freescale/mx6q_sabresd/

                cpu/arm_cortexa8/

                cpu/arm_cortexa8/mx6/

                lib_arm/

                include/asm-arm/

                include/configs/mx6q_sabresd.h

       上面这几个目录 ,就是我们真个u-boot在 mx6q_sabresd 这个平台下的主线所在.

    

     b . 在make -j100 之后 , 我们会得到以下四个文件

        System.map  u-boot映像的符号表 , 其实在我理解这是一个函数的地址表 , 我们可以充分的利用这个表在后面做很多事情.

        u-boot.bin    u-boot 映像原始的二进制格式

        u-boot     u-boot 映像的ELF格式

        u-boot.srec   u-boot映像的S-Record格式.

       

 u-boot 启动流程分析

  u-boot 的启动流程可以分为两个阶段 .

    一 . 第一阶段 .

     1 . 硬件设备初始化 (cpu)

     2 . 加载u- boot 第二阶段代码到RAM空间

     3 . 设置栈 , 为跳转c code 作准备.

     4 . 跳转到第二阶段代码入口.

    二 . 第二阶段 

     1 . 初始化本阶段使用的硬件设备 .

     2 . 为内核设置参数.

     3 .准备调用内核

   首先 , 先介绍一个文件 , 在cpu/arm_cortexa8/u-boot.lds 文件中 , 该文件内容如下.

27 /*  指定输出可执行文件是32位的ARM指令,小端模式的ELF格式 */                  
 28 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")      
 29 OUTPUT_ARCH(arm)        /*  指定可执行文件平台为ARM */                      
 30 ENTRY(_start)           /*  指定程序的入口为_start  */                      
 31 SECTIONS                                                                    
 32 {                                                                           
 33     . = 0x00000000;     /*  指明目标代码的起始代码地址为0x0位置开始,"."代表>    当前位置    */
 34                                                                             
 35     . = ALIGN(4);       /*  表示此处4字节对其       */                      
 36     .text   :                                                               
 37     {                                                                       
 38         cpu/arm_cortexa8/start.o    (.text)     /*  表示start.o是代码段的第>    一个.o文件  */
 39         *(.text)        /*  表示这是代码段的其余部分    */                  
 40     }                                                                       
 41                                                                             
 42     . = ALIGN(4);                                                           
 43     .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }   /* 这是只读段数据  */
 43     .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }   /* 这是只读段数据  */
 44                                                                             
 45     . = ALIGN(4);
 46     .data : { *(.data) }    /*  这表示数据段  */
 47
 48     . = ALIGN(4);
 49     .got : { *(.got) }
 50
 51     __u_boot_cmd_start = .;
 52     .u_boot_cmd : { *(.u_boot_cmd) }
 53     __u_boot_cmd_end = .;
 54
 55     . = ALIGN(4);
 56     __bss_start = .;
 57     .bss : { *(.bss) }
 58     _end = .;
 59 }

如上注释 ,  第28行 , 指定输出可执行文件是32位的ARM指令,小端模式的ELF格式.

     第29行 , 指定指定可执行文件平台为ARM.

     第30行 , 指定程序的入口为_start .

     第33行 , 指明目标代码的起始代码地址为0x0位置开始,"."代表>    当前位置

     第38行 , 表示start.o是代码段的第>    一个.o文件 .

     从而得出 , 我们的入口文件是 cpu/arm_cortexa8/start.o , 因此 ,u-boot 的入口代码是对应源文件cpu/arm_cortexa8/start.S

cpu/arm_cortexa8/start.S 源码分析:

一开始:      

36 .globl _start

37 _start: b   reset
 38     ldr pc, _undefined_instruction
 39     ldr pc, _software_interrupt
 40     ldr pc, _prefetch_abort
 41     ldr pc, _data_abort
 42     ldr pc, _not_used
 43     ldr pc, _irq
 44     ldr pc, _fiq
 45
 46 _undefined_instruction: .word undefined_instruction
 47 _software_interrupt:    .word software_interrupt
 48 _prefetch_abort:    .word prefetch_abort
 49 _data_abort:        .word data_abort
 50 _not_used:      .word not_used
 51 _irq:           .word irq
 52 _fiq:           .word fiq

这一段代码其实从一开始的 跳转到 reset 函数  , 但是 , 这段代码设置了一个异常向量表 , 意思就是cpu 如果执行了异常的指令就会直接跳到这里 , 比如说除于0这种代码 .

在37行直接跳到 reset  函数

  一开始  ,  u-boot 便把 cpu 设置为SVC模式(管理模式)

103 reset:
104     /*
105      * set the cpu to SVC32 mode
106      */
107     mrs r0, cpsr
108     bic r0, r0, #0x1f
109     orr r0, r0, #0xd3
110     msr cpsr,r0

跳到低水平的cpu_init :

136 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
137     bl  cpu_init_crit      //到里边看看
138 #endif

bl  指令是会回来的:

189 /*************************************************************************
190  *
191  * CPU_init_critical registers
192  *
193  * setup important registers
194  * setup memory timing
195  *  @cpu 最开始的初始化
196  *************************************************************************/
197 cpu_init_crit:
198     /*
199      * Invalidate L1 I/D
200      */
201     mov r0, #0          @ set up for MCR
202     mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs
203     mcr p15, 0, r0, c7, c5, 0   @ invalidate icache
204
205     /*
206      * disable MMU stuff and caches //关闭mmu以及caches高速缓存器
207      */
208     mrc p15, 0, r0, c1, c0, 0
209     bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
210     bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
211     orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
212     orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
213     mcr p15, 0, r0, c1, c0, 0
214
215     /*
216      * Jump to board specific initialization...
217      * The Mask ROM will have already initialized
218      * basic memory. Go here to bump up clock rate and handle
219      * wake up conditions.
220      */
221     mov ip, lr          @ persevere link reg across call
222     bl  lowlevel_init       @ go setup pll,mux,memory  //跳到低水平的初始化
223     mov lr, ip          @ restore link
224     mov pc, lr          @ back to my caller
225 /*

在222行 , 我们又跳入lowlevel_init

    152 .globl lowlevel_init
    153 lowlevel_init:
    154
    155     inv_dcache   /* invalidate the D-CACHE */
    156
    157     init_l2cc                 /*Disable L2Cache    关闭L2Cache (高速缓存器)*/
    158
    159     init_aips    /* AIPS setup - Only setup MPROTx registers    The PACR default values are good.*/
    160
    161     init_clock     //初始化时钟 */
    162
    163     mov pc, lr

  过后直接设置栈

      159     /* Set up the stack */
      160 stack_setup:
      161     ldr r0, _TEXT_BASE      @ upper 128 KiB: relocated uboot
      162     sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area
      163     sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo
      167     sub sp, r0, #12     @ leave 3 words for abort-stack
      168     and sp, sp, #~7     @ 8 byte alinged for (ldr/str)d

   清除bss段

      170     /* Clear BSS (if any). Is below tx (watch load addr - need space) */
      171 clear_bss:
      172     ldr r0, _bss_start      @ find start of bss segment
      173     ldr r1, _bss_end        @ stop here
      174     mov r2, #0x00000000     @ clear value
      175 clbss_l:1 76     str r2, [r0]        @ clear BSS location
      177     cmp r0, r1          @ are we at the end yet
      178     add r0, r0, #4      @ increment clear index pointer
      179     bne clbss_l         @ keep clearing till at end

   最后就便是跳到c  语言代码:

      184     ldr pc, _start_armboot  @ jump to C code
      185
      186 _start_armboot: .word start_armboot   @跳入c语言代码,lib_arm/board.c

lib_arm/board.c 源码分析:

    首先介绍两个重要的结构体以及一个重要的数组.

gd_t 结构体 , 该结构体是用来储存全局数据区的数据 , 这个结构体在 include/asm-arm/global_data.h 中定义如下

36 typedef struct  global_data {
 37     bd_t        *bd;
 38     unsigned long   flags;
 39     unsigned long   baudrate;
 40     unsigned long   have_console;      /* serial_init() was called */  (被serial_init()调用)
 41     unsigned long   reloc_off;          /* Relocation Offset */
 42     unsigned long   env_addr;        /* Address  of Environment struct */
 43     unsigned long   env_valid;       /* Checksum of Environment valid? */
 44     unsigned long   fb_base;          /* base address of frame buffer */  /*framebuffer 基地址  */
 54     void        **jt;                 /* jump table */
 55 } gd_t;

bd_t 结构体用于存放板级相关的全局数据 , 是gd_t 结构体指针成员 bd 的结构体类型 , 在 include/asm-arm/u-boot.h中定义如下:

39 typedef struct bd_info {
 40     int         bi_baudrate;             /* serial console baudrate */ /*串口波特率*/
 41     unsigned long   bi_ip_addr;       /* IP Address */      /* IP 地址 */
 42     struct environment_s           *bi_env;               
 43     ulong           bi_arch_number;       /* unique id for this board */   /*开发板机器码*/
 44     ulong           bi_boot_params;          /* where this board expects params */    /*内核参数开始地址 */
 45     struct                           /* RAM configuration */    /*内存配置信息结构体 */
 46     {
 47     ulong start;
 48     ulong size;
 49     }           bi_dram[CONFIG_NR_DRAM_BANKS];
 50 } bd_t;

init_sequence 数组:

278 //一系列的初始化
279 init_fnc_t *init_sequence[] = {
280 #if defined(CONFIG_ARCH_CPU_INIT)
281     arch_cpu_init,      /* basic arch cpu dependent setup   */
282 #endif
283     board_init,     /* basic board dependent setup */
284 #if defined(CONFIG_USE_IRQ)
285     interrupt_init,     /* set up exceptions */   //不进入
286 #endif
287     timer_init,     /* initialize timer */  //初始化定时器
288     env_init,       /* initialize environment */
289     init_baudrate,      /* initialze baudrate settings, 波特率由用户自己设定
     */
290     serial_init,        /* serial communications setup */
291     console_init_f,     /* stage 1 init of console */
292     display_banner,     /* say that we are here */
293 #if defined(CONFIG_DISPLAY_CPUINFO)
294     print_cpuinfo,      /* display cpu info (and speed) */
295 #endif
296 #if defined(CONFIG_DISPLAY_BOARDINFO)
297     checkboard,     /* display board info */
298 #endif
299 #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
300     init_func_i2c,
301 #endif
302     dram_init,      /* configure available RAM banks */
303 #if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
304     arm_pci_init,
305 #endif
306     display_dram_config,
307     NULL,
308 };

下面介绍start_armboot 这段代码的流程 :

首先直接进行一系列的初始化:

332     //这个是个小关键,这里面会运行一系列的初始化
333     for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
334         if ((*init_fnc_ptr)() != 0) {
335             hang ();                           
336         }                                                              
337     }

上面这段代码 , 会直接运行init_sequence 数组内的每一个函数, cpu  , 时钟 , 串口 , 控制台 , 等一系列初始化.

413     //标准输入输出初始化
414     stdio_init ();  /* get the devices list going. */

416     jumptable_init ();      /* jump table init (chen) */   /* 这里边会设置一个函数地址跳转表 */
423     console_init_r ();  /* fully init console as a device */
424     /* 对所有的控制台进行初始化并打印信息,主要是标准输入输出以及错误输出*/

436     enable_interrupts ();       /*配置寄存器使中断使能  */

479     board_late_init ();     /* mainly do one thing,setup i2c  (chen) */

最后会到

501     for (;;) {
502         main_loop ();                                                       
503     }           
今天由于时间关系 , 只能暂时写到这里 . 之后我会将该流程详细的讲解.

时间: 2024-11-13 02:12:41