分享

U-boot在开发板上移植过程详解

 rookie 2012-04-09
U-boot在开发板上移植过程详解(1)---bootloader架构分析

    本例中采用的同样是前边一贯的实验板,这里就不对板子资源做进一步介绍了。

    我们知道,bootloader是系统上电后最初加载运行的代码。它提供了处理器上电复位后最开始需要执行的初始化代码。在PC机上引导程序一般由 BIOS开始执行,然后读取硬盘中位于MBR(Main Boot Record,主引导记录)中的Bootloader(例如LILO或GRUB),并进一步引导操作系统的启动。然而在嵌入式系统中通常没有像BIOS那 样的固件程序,因此整个系统的加载启动就完全由bootloader来完成。它主要的功能是加载与引导内核映像。

    一个嵌入式的存储设备通过通常包括四个分区,第一分区存放的当然是u-boot,第二个分区存放着u-boot要传给系统内核的参数,第三个分区是系统内核(kernel),第四个分区则是根文件系统。如下图所示:

123      图一  固态存储设备的典型空间分配结构

第一部分:Bootloader启动模式

    Bootloader的启动过程可以是单阶段的,也可以是多阶段的。多阶段的bootloader比单阶段的bootloader提供更为复杂的功能。以及更好的移植性,比如U-bot。

第一阶段:

    Bootloader执行最基本的硬件初始化操作。如关闭中断,关闭看门狗以避免处理器被复位,以及关闭MMU功能,关闭处理器缓存(数据缓存一定要关 闭,指令缓存可以打开),设置系统时钟,初始化内存等。这一阶段代码通常由汇编代码编写,为了运行下一阶段的C语言程序还必须设置好堆栈。如果是从 NAND Flash启动,则必须通过NAND Flash控制器将bootloader代码复制到内存。

第二阶段:

   这一阶段一般用C语言编写,大致分为一下几步:

    1)初始化各种硬件设备,比如设置处理器正常工作的时钟频率,初始化串口等。

    2)检测系统内存,主要是确定系统内存容量以及其地址空间信息。

    3)将内核映像文件加载到内存。

    4)准备内核引导参数。

    5)跳转到内核的第一条指令处,开始执行内核初始化代码,控制权转移到内核代码,bootload的使命结束。

第二部分:Bootloader的操作模式

     一般的bootload而都包含两种不同的操作模式:启动加载模式和下载模式

    启动加载模式:这种模式也称自主模式,即bootloader从目标机上的某个固体存储设备上将操作系统加载到内存中运行,这个过程没有用户的介入。这种模式是正

常的工作模式,最终的产品发布时必须工作在这种模式下。

    下载模式:在这种模式下, 目标机上的bootloader将通过串口,网络连接或者其他通信手段从主机下载文件,比如下载内核映像或根文件系统映像等。从主机下载的文件通常被保存 在目标机的内存中,然后再写入到目标机上的Flash等固态存储设备中。这种工作模式通常在第一次安装内核与跟文件系统时使用。或者在系统更新时使用。进 行嵌入式系统调试时一般也让bootloader工作在这一模式下。

第三部分:Arm bootloader的特点

    要实现一个通用的bootloader是一件不可能的事情,但是还是可以根据Arm的体系结构,从理论上总结出一些Arm平台上bootloader的共性,这些共性只能局限于bootloader的基本功能。

    对于一个运行于Arm平台的系统来说,bootloader作为引导与加载内核映像的工具需要提供一下几个功能:

    1)bootloader必须能够初始化内存。

    2)虽然系统的启动并不一定依赖串口,但一般来说bootloader应该初始化至少一个串口,通过它与主机进行通信,以便进行开发,调试和维护工作。

    3)这是linux内核所要求的,如果不给出内核参数,则内核就会使用其默认参数。

    4)一般来说,内核映像必须在内存运行,所以必须从其他非易失存储介质上复制到内存。

    5)让执行流程跳转到内核映像的入口。

    启动内核时,系统必须处于指定的状态,包括处理器模式,MMU和缓存的设置,寄存器的设置等方面。

    处理器模式)处理器应处于SVC模式,在这种特权模式下,内核才能执行所有的指令。中断必须关闭。在异常向量表尚未初始化的情况下,如果发生中断,将导致系统崩溃。一般来说,bootloader本身也没有必要支持中断的实现,这属于内核的管理范围。

    MMU和缓存设置)MMU 必须关闭。启动MMU进入保护模式是内核的工作。而bootloader本身工作在实模式下,所有对地址的操作使用的都是物理地址,不存在虚拟地址。数据 缓存必须关闭,bootloader的主要功能是装载内核映像,映像数据必须真实写回内存中,不能仅放在处理器的缓存中,所以数据缓存必须关闭。指令缓存 可以打开,一般情况下,推荐将指令缓存也关闭。

   寄存器设置)寄存器R0的值应为0,R1的值表示机器类型,R2的值则是引导参数列表在内存中的起始地址。这三个寄存器是在最后启动内核时需要设置的。

第四部分:U-boot源码分析

   在实际使用中,U-boot被固话在CPU的上电/复位启动地址处(通常在非易失存储器中)。每当嵌入式设备上电/复位时,CPU总是从启动地址(U-boot)处启动。U-bo

ot启动后,首先初始化各种硬件设备,如CPU,缓存,存储器,MMU,总线控制器,各种I/O接口等,然后从远程主机或者本地非易失存储设备中装载可执行文件或操作系统

,为整个嵌入式系统准备运行环境。要使用U-boot,最初必须使用某种硬件支持的方式将U-boot映像写入非易失存储器中。比如我这里板子上没有任何的bootloader

可以使用JTAG接口将U-boot映像写入Flash的开始处。

   U-boot采用了一种高度模块化的编程方式,不同功能类别的代码分别放在不同的目录中,几个U-boot常用到的目录分析如下所示:

   1  board)这个目录中存放了所有U-boot支持的目标板的子目录。在这个目录中一般是针对特定目标板的初始化和操作代码。

   2  cpu)这个目录中存放了U-boot支持的所有CPU类型。

   3  common)这个目录中存放独立于处理器体系架构的通用代码,包括U-boot的一些公共命令的实现。一般来说,其中以cmd_*.c命令的文件就是相对命令的实

现。比如cmd_bootm.c就是对命令bootm的实现。

   4  drivers)这个目录中存放的是各种外设接口的驱动程序。

   5  fs)这个目录中存放了U-boot支持的文件系统。

   6  include)这个目录是存放各种CPU及目标板的头文件和配置文件的公共目录,其中的configs目录存放了各种目标板的配置头文件。针对不同的板子,里边的配置

文件要根据实际情况进行修改。

   7  lib_XXX)这个目录存放XXX体系架构的处理器的相关支持。

   8  net)这个目录用于存放与网络功能相关的文件。  

 

  下节,就要开始对U-boot源码中的关键功能的实现进行分析,主要是从一下几个方面:

   1)使用汇编语言编写的第一阶段代码

   2)第二阶段代码命令的实现

   3)第二阶段操作系统引导机制的实现

U-boot在开发板上移植过程详解(2)---U-boot实现源码分析(第一阶段)

    前边,我们说了,一般的bootloader都分为两个阶段。我在讲U-boot实现源码分析时,也是按照这连个阶段来分析,如果对这两个阶段不清楚,请看前边的博客。好了,开始今天的主题:U-boot在开发板上移植过程详解(2)---U-boot实现源码分析(start.S分析)

第一阶段:

    1)一些基本的硬件初始化工作                                                

    u-boot对应的第一阶段代码放在cpu/arm920t/start.S文件中,入口代码如下:

.globl _start                 ;global声明一个符号可被其它文件引用,相当于声明了一个全局变量,.globl与.global相同
_start:    b       reset    ;b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号出执行程序
    ldr    pc, _undefined_instruction  ;ldr相当于mov操作
    ldr    pc, _software_interrupt
    ldr    pc, _prefetch_abort
    ldr    pc, _data_abort
    ldr    pc, _not_used
    ldr    pc, _irq
    ldr    pc, _fiq

;.word伪操作用于分配一段字内存单元(分配的单元都是字对齐的),并用伪操作中的expr初始化。

_undefined_instruction:  .word undefined_instruction    ;就是在当前地址,即_undefined_instruction 处存放 undefined_instruction

_software_interrupt:      .word software_interrupt
_prefetch_abort:           .word prefetch_abort
_data_abort:                .word data_abort
_not_used:                  .word not_used
_irq:                           .word irq
_fiq:                           .word fiq

 

   这部分就是异常向量表。当系统上电或复位后,将执行第一条指令,即跳转到标签为reset的代码处执行,具体如下:

reset:                                   ;设置CPU为SVC32管理模式
    mrs    r0,cpsr                     ;mrs将状态寄存器cpsr(current program status register)的内容传送至通用寄存器
    bic    r0,r0,#0x1f               
;r0和0x1f(00011111)的反码进行位与,是把 r0后面5位清零
    orr    r0,r0,#0xd3                ;r0和0xd3(11010011)进行位或,最后得到r0=11010011,目的是设置r0的后5位为10011,让ARM进入SVC特权模式
    msr    cpsr,r0

#if defined(CONFIG_S3C2400)   ;关闭看门狗
# define pWTCON        0x15300000   ;看门狗寄存器
# define INTMSK         0x14400008    ;中断屏蔽寄存器
# define CLKDIVN        0x14800014    ;时钟分频寄存器
#elif defined(CONFIG_S3C2410)
# define pWTCON        0x53000000
# define INTMSK         0x4A000008   
# define INTSUBMSK    0x4A00001C   ;次级中断屏蔽寄存器
# define CLKDIVN    0x4C000014       
;时钟分频寄存器
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
    ldr       r0, =pWTCON
    mov     r1, #0x0
    str       r1, [r0]

    mov     r1, #0xffffffff                    ;屏蔽所有中断
    ldr       r0, =INTMSK
    str       r1, [r0]
# if defined(CONFIG_S3C2410)
    ldr       r1, =0x3ff
    ldr       r0, =INTSUBMSK
    str       r1, [r0]
# endif

    ldr    r0, =CLKDIVN                       ;设置时钟
    mov    r1, #3
    str    r1, [r0]
#endif    /* CONFIG_S3C2400 || CONFIG_S3C2410 */

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_crit                         ;跳转并把转移后面紧接的一条指令地址保存到链接寄存器LR(R14)中,以此来完成子程序的调用
#endif

   上面的代码将CPU设为管理模式,关闭看门狗,屏蔽中断并设置中断,最后调用cpu_init_crit函数进行cpu的初始化,代码如下:

cpu_init_crit:                                 ;清除指令和数据缓存
    mov    r0, #0
    mcr    p15, 0, r0, c7, c7, 0            ;mcr{条件} 协处理器编码, 协处理器操作码1, 目的寄存器, 源寄存器1, 源寄存器2, 协处理器操作码2 

    mcr    p15, 0, r0, c8, c7, 0            ;mcr指令用于将ARM处理器寄存器的数据传送到协处理器寄存器中,若协处理器不能成功完成操作,则

                                                    ;产生未定义指令异常。其中协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,目的寄存器

                                                    ;为ARM处理器的寄存器,源寄存器1和源寄存器2均为协处理器的寄存器。 

    mrc    p15, 0, r0, c1, c0, 0            ;mrc 协处理器寄存器到ARM处理器寄存器的数据传送指令
    bic    r0, r0, #0x00002300              @ clear bits 13, 9:8 (--V- --RS)
    bic    r0, r0, #0x00000087              @ clear bits 7, 2:0 (B--- -CAM)
    orr    r0, r0, #0x00000002              @ set bit 2 (A) Align
    orr    r0, r0, #0x00001000              @ set bit 12 (I) I-Cache
    mcr    p15, 0, r0, c1, c0, 0

    mov    ip, lr                                 ;设置SDRAM控制器,与具体的目标板相关
    bl    lowlevel_init
    mov    lr, ip
    mov    pc, lr

   在这个函数中做了一下工作:清除指令与数据缓存,禁用MMU与数据指令缓存,最后调用lowlevel_init函数设置SDRAM控制器。该函数的实现与具体的目标板有关的。

   2)准备RAM空间 

    所谓准备RAM空间,就是初始化内存芯片,使它可用。 在board/smdk2410/lowlevel.init.S就是这个作用,要注意这时的代码,数据都保存在NOR Flash上,内存中还没有,所以读取数据时要变换地址,如下:

_TEXT_BASE:
.word    TEXT_BASE

.globl lowlevel_init
lowlevel_init:

    ;现在起三行进行地址变化,因为这时候内存中还没有数据,不能使用连接程序时确定的地址来读取数据    
    ldr     r0, =SMRDATA                  ;SMBRDATA表示这13个寄存器的值存放在开始地址(连接地址),值为0x33F8XXXX,处于内存中        
    ldr     r1, _TEXT_BASE                ;获得代码段的起始地址(_TEXT_BASE=0X33F80000)
    sub    r0, r0, r1                         ;将r0和r1相减,这就是13个寄存器值在Nor Flash上存放的开始地址                             
    ldr     r1, =BWSCON                   ;Bus Width Status Controller
    add     r2, r0, #13*4
0:
    ldr     r3, [r0], #4
    str     r3, [r1], #4
    cmp   r2, r0
    bne    0b

    mov   pc, lr

    .ltorg

SMRDATA:                                   ;13个寄存器的值
    .word … …

    .word … …

   这里做完以后,就要将整个U-boot的代码都复制到SDRAM中,这些又都在start.S中实现,如下:

relocate:                                     ;将u-boot复制到RAM中
    adr    r0, _start                         ;r0:当前代码的开始地址
    ldr     r1, _TEXT_BASE                ;r1:代码段的连接地址
    cmp   r0, r1                              ;测试现在是在Flash中还是在RAM中
    beq    stack_setup                    
;如果已经在RAM中(这通常是调试时直接下载到RAM中),则不需要复制

    ldr     r2, _armboot_start             ;_armboot_start在前边已经定义,是第一条指令的运行地址
    ldr     r3, _bss_start                   ;在连接脚本u-boot.lds中定义,是代码的结束地址
    sub    r2, r3, r2                          ;r2=代码段的长度
    add    r2, r0, r2                         ;r2=NOR Flash上代码段的结束地址

copy_loop:
    ldmia    r0!, {r3-r10}                  ;从地址[r0]处获得数据
    stmia    r1!, {r3-r10}                  ;复制到地址[r1]处
    cmp    r0, r2                             ;复制是否复制完毕
    ble    copy_loop                       
;没复制完,则继续

   接下来,就要设置栈,栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。

stack_setup:
    ldr     r0, _TEXT_BASE                                 ;_TEXT_BASE为代码段的开始地址,值为0x33F80000
    sub    r0, r0, #CFG_MALLOC_LEN                   ;代码段下面,留出一段内存以实现malloc
    sub    r0, r0, #CFG_GBL_DATA_SIZE              ;再留出一段内存,存一些全局参数
#ifdef CONFIG_USE_IRQ
    sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)    ;IRQ,FIQ模式的栈
#endif
    sub    sp, r0, #12                                      
;最后,留下12字节的内存给abort异常,往下的内存就都是栈了

   3)跳转到第二阶段代码的C入口点

   在跳转之前,还要清除BSS段(初始值为0,无初始值的全局变量,静态变量放在BSS段),代码如下:

clear_bss:
    ldr       r0, _bss_start                 ;BSS段的开始地址,它的值在连接脚本U-boot.lds中确定
    ldr       r1, _bss_end                   ;BSS段的结束地址,它的值也在连接脚本u-boot.lds确定
    mov     r2, #0x00000000      

clbss_l:str    r2, [r0]                      ;往BSS段中写入0值
    add    r0, r0, #4
    cmp    r0, r1
    ble    clbss_l

   现在,c函数的运行环境已经完全准备好了,通过如下命令直接跳转(这之后,程序才在内存中执行),它将调用lib_arm/board.c中的start_armboot函数(这是一个C语言函数),这是第二阶段的入口点:

    ldr    pc, _start_armboot

_start_armboot:    .word start_armboot

   在第二阶段代码中,将进行更多的初始化工作,如对各种设备和接口的初始化,串口终端的初始化等。如果没有设置自动运行,则最终将进入一个循环,在循环内读取用户输入的命令并执行,这些会在下一节详细介绍。

U-boot在开发板上移植过程详解(3)---U-boot实现源码分析(第二阶段)

     U-boot的第二阶段和bootloader所完成的功能基本上是一致的,只是顺序上有点差别。另外,u-boot在启动内核之前可以让用户决定是否进入下载模式,即进入u-boot的控制界面。

     第二阶段是从lib_arm/board.c中的start_armboot函数开始的。移植u-boot的主要工作在于对硬件的初始化,驱动。这里就重点按照硬件的操作上。

     (1)初始化本阶段要用到的硬件设备

      这里最重要的是设置系统时钟,初始化串口,只要这两个设置好了,就可以从串口看到打印信息。board_init函数设置MPLL、改变系统时钟,它是开 发板相关的函数,在board/samsung/smdk2440/smdk2440.c中实现。值得注意的是board_init函数还保存了机器类型 ID,这将在调用内核的时候传递给内核。

      串口的初始化函数主要是serial_init,它设置UART控制器,是CPU的相关函数,在cpu/arm920t/s3c2440/serial.c中实现。

     (2)检测系统内存映射

      对于特定的开发板,器内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram_init函数指定了本开发板的内存起始地址为0x300

00000,大小为0x40000000.代码如下:  

int dram_init(void)
{    //这两个值都定义在include/configs/smdk2440.h中
     gd->bd->bi_dram[0] . start = PHYS_SDRAM_1;          //即0x30000000
     gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;        //即0x04000000
     return 0;
}

     这些设置的参数,将在后面向内核传递参数时用到。

   (3)U-boot命令实现

     我们已经知道,即使是内核的启动,也是通过U-boot命令来实现的。u-boot中的每个命令都通过U-BOOT-CMD宏(在include/command.h)来定义,格式如下:

U_BOOT_CMD(name, maxargs, repeatable, command, “usage”, "help”)

各项参数说明如下:
name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)
maxargs:最大的参数个数
repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行
command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *, int, int, char *[])
usage:简短的使用说明,这是个字符串

     下面以bootm命令来说明,它有如下定义:

U_BOOT_CMD(
        bootm, CFG_MAXARGS, 1, do_bootm,
        "string1”,
        "string2"
);

    利用U_BOOT_CMD的宏展开后的命令如下

cmd_tbl_t  __u_boot_cmd_boot  __attribute__  ((unused, section(".u_boot_cmd")))  = { "bootm", 
                                     CFG_MAXARGS, 1,  do_bootm, "string1", “string2”};

    对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个cmd_tbl_t结构,如下:

struct cmd_tbl_s {
    char *name;                                           //命名名称
     int maxargs;                                          //最大参数个数
      int repeatable;                                       //是否允许自动重复
     int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); //实现函数
     char *usage;                                          //帮助信息(短)
    char *help;                                           //帮助信息(长)
};
typedef struct cmd_tbl_s cmd_tbl_t;

     在u-boot的链接脚本board/smdk2410/u-boot.lds中有如下定义:

__u_boot_cmd_start = .; 

.u_boot_cmd : { *(.u_boot_cmd) } 

__u_boot_cmd_end = .;

     在程序中就是根据命令的名字在内存段__u_boot_cmd_start~__u_boot_cmd_end找到它的cmd_tbl_t结构,然后调用它的函数(请参考common/comm

and.c中的find_cmd函数)。

    (4)引导内核的实现

     U-boot也是通过标记列表向内核传递参数的。ARM Linux内核对bootloader的引导功能有一定要求,在执行内核代码前必须设置下列条件:

     & 对CPU寄存器的设置为R0=0, R1=机器类型ID,R2=引导参数列表的地址

     & 必须禁止中断(IRQ与FIQ)

     & CPU必须处于SVC模式

     & MMU必须关闭

     & 数据缓存必须关闭
     现在linux虽然支持两种格式的引导参数,这里主要介绍最常用的新的方式---即上面所说的标记列表的,这种方式灵活,且对参数的描述更细致。

     标签列表的每个标签由标签头和标签体组成。标签头说明这个标签的大小(单位是整数不是字节)以及这个标签的类型。类型是由内核定义好的一个数字。标签头用一个结构体struct tag_header表示,如下:

struct tag_header{
    u32  size;
    u32  tag;
};

     在标签头之后,根据标签的类型,所需的标签体也是不同的。标签列表的结束由一个特殊的标签类型ATAG_NONE标志,它没有标签体。

     比较重要的两个标签类型是ATAG_MEM(设置内存信息)和ATAG_CMDLINE(用来传递命令行参数,即U-boot的bootargs变量的内容),这里列出来,需要的请大家查看google。下面给出一些小细节:

     &u-boot源码中给出了一些设置标签列表的源代码,放在文件lib_arm/armlinux.c中,方法是先定义一个全局变量static struct tag *params,其中这个结构体的类型是一个将所有标签类型组合在一起的结构体,如下所示:

struct tag { 
    struct tag_header hdr; 
    union { 
      struct tag_corecore; 
      struct tag_mem32mem; 
      struct tag_videotextvideotext; 
      struct tag_ramdiskramdisk; 
      struct tag_initrdinitrd; 
      struct tag_serialnrserialnr; 
      struct tag_revisionrevision; 
      struct tag_videolfbvideolfb; 
      struct tag_cmdlinecmdline; 
      struct tag_acornacorn; 
      struct tag_memclkmemclk; 
    } u; 
}; 

    所有标签的头格式都是相同的,只是标签体不同,因此用联合体的方式将它们组合在一起。下面就是设置起始标签的函数代码:

static void setup_start_tag (bd_t *bd)
{
      params = (struct tag *) bd->bi_boot_params;
      params->hdr.tag = ATAG_CORE;
      params->hdr.size = tag_size (tag_core);
      params->u.core.flags = 0;
      params->u.core.pagesize = 0;
      params->u.core.rootdev = 0;
      params = tag_next (params);
}

    在这个函数中,首先将变量params的值设为标签列表的开始地址,然后逐个设置标签中的成员,最后params变量的值将指向下一个标签应该设置的地址。其中,tag_size是一个宏,用来得到标签的大小。最后,用于设置标签列表结束的函数如下:

static void setup_end_tag (bd_t *bd)
{
	params->hdr.tag = ATAG_NONE;
	params->hdr.size = 0;
}

    对于ARM架构的CPU,都是通过lib_arm/armlinux.c中的do_bootm_linxu函数来启动内核的,方法如下:

    首先,获得内核映像的入口地址:

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

     这样theKernel就指向内核存放的地址(对于ARM架构的CPU,通常是0x30008000),这里的hdr指向内核U-boot映像头部数据的指针,而hdr->ih_ep就是内核的入口地址,最后用下述代码调用内核:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

    这里的bd->bi_arch_number就是前面board_init函数设置的机器类型ID, bd->bi_boot_params就是标记列表的开始地址。根据ATPCS调用约定,上述函数的三个函数分别放在寄存器R0,R1,R2中,这 样就实现了内核要求的入口条件。

    讲到这里,有关的U-boot的关键源码分析分析部分就完成了,下次开始就来U-boot移植的实践操作篇。

U-boot在开发板上移植过程详解(4)---U-boot移植操作实践(u-boot框架实现)

      经过前面三节对bootloader的讲解及其典型实现u-boot的讲解,相信大家对bootloader有了很深的了解(当然,通过讲解,我也有更深的了解了)。那么今天开始,就要开始 U-boot移植操作实践 部分的讲解了。

      我们知道bootloader是分为两部分的,具体到u-boot中,这两部分实现分别在:stage1代码通常放在cpu/xxxx/start.S文件中,stage2代码通常放在lib_xxxx/bo

ard.c文件中.具体的讲解,我这里就不细讲了,若有不懂,自己到前边的博客里去翻吧。在开始之前,有必要介绍一下相应的软件版本:

主  机:Fedora 9
编译器:arm-linux-gcc-4.4.3
u-boot:u-boot-2009.08.tar.bz2

     好了,现在开始真正的移植操作:

     1)建立自己的开发板项目并测试编译

     因2440和2410的资源差不多,主频和外设有点差别,所以我们就在board/samsung/下建立自己开发板的项目,取名叫smdk2440

#tar -jxvf u-boot-2009.08.tar.bz2   
#cd u-boot-2009.08/board/samsung/   
#mkdir smdk2440                            //创建smdk2440文件夹
#cp -rf smdk2410/* smdk2440/   //将2410下所有的代码复制到2440下
#cd smdk2440                   //进入smdk2440目录
#mv smdk2410.c smdk2440.c      //将smdk2440下的smdk2410.c改名为smdk2440.c
#cd http://www.cnblogs.com/../                  //回到u-boot根目录
#cp include/configs/smdk2410.h include/configs/smdk2440.h //建立2440头文件

      这里这样做,隐含了一个事实就是,2440和2410的资源差不多,所以就以2410项目的代码作为模板,以后根据需要再修改。这个做完了修改刚才创建的smdk2440下的Makefile文件,找到COBJS    := smdk2410.o flash.o  将smdk2410.o改为smdk2440.o(原因,我们都知道是不) 。下面修改u-boot跟目录下的Makefile文件,找到smdk2410_config的地方,在它下面按照smdk2410_config的格式建立smdk2440_config的编译选项,另外还要指定交叉编译器。如下所示: 

CROSS_COMPILE ?= arm-linux-        //指定交叉编译器为arm-linux-gcc
smdk2410_config    :    unconfig   //2410编译选项格式
    @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 samsung s3c24x0
smdk2440_config    :    unconfig     //2440编译选项格式
    @$(MKCONFIG) $(@:_config=) arm arm920t smdk2440 samsung s3c24x0
说明:arm    :CPU的架构(ARCH)
     arm920t:CPU的类型
     smdk2440 :对应在board目录下建立新的开发板项目的目录
     samsung:新开发板项目目录的上级目录,如直接在board下建立新的开发板项目的目录,则这里就为NULL
     s3c24x0:CPU型号

     保存退出,在终端下运行:

#make my2440_config          //如果出现Configuring for my2440 board...则表示设置正确
#make                        //编译后在根目录下会出现u-boot.bin文件,则u-boot移植的第一步就算完成了

     说明:其实经过上面的make时,是会发生错误的,解决方法如下:

问题一:board.c:127: error: inline function 'coloured_LED_init' cannot be declared weak
board.c:129: error: inline function 'red_LED_on' cannot be declared weak
board.c:131: error: inline function 'red_LED_off' cannot be declared weak
board.c:133: error: inline function 'green_LED_on' cannot be declared weak
board.c:135: error: inline function 'green_LED_off' cannot be declared weak
board.c:137: error: inline function 'yellow_LED_on' cannot be declared weak
board.c:139: error: inline function 'yellow_LED_off' cannot be declared weak
board.c:141: error: inline function 'blue_LED_on' cannot be declared weak
board.c:143: error: inline function 'blue_LED_off' cannot be declared weak
make[1]: *** [board.o] 错误 1
make[1]: Leaving directory `/root/workspace/u-boot-2009.08/lib_arm'
make: *** [lib_arm/libarm.a] 错误 2

解决方法:

打开lib_arm/board.c,定位到127行开始,将其注释掉:

void inline __coloured_LED_init (void) {}
//void inline coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
这里注释掉了'coloured_LED_init' 的部分,自己做时对照注释掉后面几个带__attribute__的部分即可

问题二:cpu/arm920t/start.o: In function `start_code':
/root/workspace/u-boot-2009.08/cpu/arm920t/start.S:117: undefined reference to `coloured_LED_init'

/root/workspace/u-boot-2009.08/cpu/arm920t/start.S:118: undefined reference to `red_LED_on'
make: *** [u-boot] 错误 1

解决方法:

打开cpu/arm920t/start.S,搜索“coloured_LED_init”定位到117行,找到如下代码:

     bl coloured_LED_init
     bl red_LED_on

这两行是AT91RM9200DK开发板的LED初始化,注释掉即可。

     经过上面的修改,make clean/make就可以在u-boot的根目录下看到u-boot.bin文件,则u-boot移植的第一步就算完成了。但是,这里u-boot对自 己的smdk2440开发板还没有任何用处,只是搭建了一个smdk2440开发板u-boot的框架,要使其功能实现,还要根据smdk2440开发板 的具体资源情况来对u-boot源码进行修改.

     2)根据u-boot启动步骤来分析或者修改添加u-boot源码,使之适合自己的开发板

    一般在嵌入式系统软件开发中,在所有源码文件编译完成之后,链接器要读取一个链接分配文件,在该文件中定义了程序的入口点,代码段、数据段等分配情况等。 那么我们的s3c2440开发板u-boot的这个链接文件就是cpu/arm920t/u-boot.lds,打开该文件部分代码如下:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm) //定义生成文件的目标平台是arm
ENTRY(_start) //定义程序的入口点是_start

      从这里我们知道程序的入口点是_start,那么我们定位到u-boot第一个要运行的程序cpu/arm920t/start.S,查找到_start的位置如下:

.globl _start
_start:    b       start_code

 

     从这里知道,程序要从start_code处开始执行,由此可以看到,start_code处才是u-boot启动代码的真正开始处。以上就是u- boot的stage1入口的过程。知道了程序的入口处,也就是stage1的入口地址,下面就开始针对我们s3c2440的板子来修改u-boot,让 其为我们的硬件初始化做准备,当然啦,这就是下面几集的内容了。后面继续分解。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多