Arm开发板实验(2)---- uboot初步

用英语写起来太类了,而且时间长了自己没准都看不明白,改中文吧!!


Chapter 5: uboot启动流程
1. cpu/arm920t/start.s
1) 进入管理者模式

入口函数_start通过b命令直接跳转到reset执行。

这里首先是要设置cpsr寄存器的内容。Cpsr寄存器格式如下:

 

 

Mrs is used to transfer the PSR register’s value to common register. PSR contains two register: CPSR and SPSR. Msr is used to transfer the common register’s value to PSR register.

 

/* set the cpu to SVC32 mode */

Mrs  r0,cpsr 

Bic  r0,r0,#0x1f   /*  clear mode */

Orr  r0,r0,#0xd3   /*  set mode */

Msr  cpsr,r0

 

结合寄存器说明,可以明白此处是为了禁止IRQ中断与FIRQ中断,并进入管理者模式SVC

 


2) 禁止看门狗

ldr     r0, =pWTCON

mov    r1, #0x0

str     r1, [r0]

pWTCON是看门狗寄存器的地址,我们定义如下:

#define   pWTCON   0x53000000

 

Bit5用于使能/禁止WatchDog,所以给WTCON寄存器清零会禁止看门狗。


3) 关中断               

 

       /* mask all IRQs by setting all bits in the INTMR - default */

       mov       r1, #0xffffffff

       ldr   r0, =INTMSK

       str   r1, [r0]

# if defined(CONFIG_S3C2410)

       ldr   r1, =0x3ff

       ldr   r0, =INTSUBMSK

       str   r1, [r0]

# endif

 # define INTMSK           0x4A000008

# define INTSUBMSK   0x4A00001C


4) 时钟设置

S3c2440的内部有两个PLL,他们的源都是OSC。一个是MPLL,用于内核与总线;另一个是UPLL,用于USB设备。 

 

 时钟控制逻辑给整个芯片提供三种时钟:FCLK用于CPU核;HCLK用于AHB(Advanced High performance Bus)总线上的设备;PCLK用于APB(Advanced Peripheral Bus)总线上的设备上。其中,FCLK为PLL送进来的时钟频率,其他两个都是通过对FCLK分频得到的。

为了降低电磁干扰、降低布线难度,CPU外接的晶振频率都非常低,需要通过PLL倍频。刚上电时,PLL没有启动,此时FCLK频率就等于外部输入频率。启动后,软件会设置MPLL。下表是

下面是2440可以使用的频率表:

 从上图中,可以看到HCLK与PCLK的频率除受HDIVN与PDIVN的影响外,还受到HCLK3_HALF与HCLK4_HALF的影响。这两个值可以通过CAMDIVN寄存器来设置:

HDIVN与PDIVN可以在CLKDINV寄存器设置:

 可通过下列的会编码设置:

       /* FCLK:HCLK:PCLK = 1:2:4 */

       /* default FCLK is 120 MHz ! */

       ldr   r0, =CLKDIVN

       mov       r1, #3

       str   r1, [r0]

# define CLKDIVN 0x4C000014

此处使用默认值就可以。

 5) cpu_init_crit
Cpu_init_crit is used to setup important registersand memory timing.

CP15 register is used for MMU, include TLB and Cache. As the chart following, It contains 16 registers and provides fully operations. For more details, browse the datasheet Arm920t.pdf.

 /* flush v4 I/D caches */

      mov       r0, #0

      mcr p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */

      mcr p15, 0, r0, c8, c7, 0    /* flush v4 TLB */   

 /* disable MMU stuff and caches */

      mrc p15, 0, r0, c1, c0, 0

      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

It is really hard to understand this code. Fortunitely, All of these code can be find in the ARM920T.pdf.


6) lowlevel_init (board\smdk2440nand\lowlevel_init.S)

lowlevel_init was used to transfer the memory controller paras from Flash to SDRAM. So we can set the memory controller registers by it. It will be used both uboot and linux.

      /* memory control configuration */

      /* make r0 relative the current location so that it */

      /* reads SMRDATA out of FLASH rather than memory ! */

      ldr     r0, =SMRDATA

      ldr   r1, _TEXT_BASE

      sub  r0, r0, r1

      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:

    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))

    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))

    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))

    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))

    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))

    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))

    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))

    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))

    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))

    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)

    .word 0x32

    .word 0x30

    .word 0x30


7) 复制bootloader的第二阶段代码到RAM空间

这里会将整个uboot的代码都复制到SDRAM中。

 

relocate:                            /* relocate U-Boot to RAM         */

      adr  r0, _start              /* r0 <- current position of code   */

      ldr   r1, _TEXT_BASE            /* test if we run from flash or RAM */

      cmp     r0, r1                  /* don't reloc during debug         */

      beq     stack_setup

 

      ldr   r2, _armboot_start

      ldr   r3, _bss_start

      sub  r2, r3, r2              /* r2 <- size of armboot            */

      add r2, r0, r2              /* r2 <- source end address         */

 

copy_loop:

      ldmia     r0!, {r3-r10}        /* copy from source address [r0]    */

      stmia      r1!, {r3-r10}        /* copy to   target address [r1]    */

      cmp       r0, r2                    /* until source end addreee [r2]    */

      ble  copy_loop

#endif   /* CONFIG_SKIP_RELOCATE_UBOOT */


8) set stack

Set the SP register to a free momory address. Some memory is reserved to malloc area and global infos which include gd and bd_t.

/* Set up the stack         */

stack_setup:

      ldr   r0, _TEXT_BASE            /* upper 128 KiB: relocated uboot   */

      sub  r0, r0, #CFG_MALLOC_LEN   /* malloc area   */

      sub  r0, r0, #CFG_GBL_DATA_SIZE /* global info    */

      sub  sp, r0, #12           /* leave 3 words for abort-stack    */


9) clear bss

clear_bss:

      ldr   r0, _bss_start              /* find start of bss segment    */

      ldr   r1, _bss_end        /* stop here           */

      mov      r2, #0x00000000        /* clear        */

clbss_l:str    r2, [r0]          /* clear loop...       */

      add r0, r0, #4

      cmp       r0, r1

      ble  clbss_l

 


10) 进入第二阶段

      ldr   pc, _start_armboot

 


2 lib_arm /Board.c
1) start_armboot

_armboot_start is the first function in text segment. Some of the varibles are stored here.

typedef struct     global_data {

      bd_t              *bd;

      unsigned long      flags;

      unsigned long      baudrate;

      unsigned long      have_console;      /* serial_init() was called */

      unsigned long      reloc_off;     /* Relocation Offset */

      unsigned long      env_addr;     /* Address  of Environment struct */

      unsigned long      env_valid;    /* Checksum of Environment valid? */

      unsigned long      fb_base; /* base address of frame buffer */

#ifdef CONFIG_VFD

      unsigned char      vfd_type;     /* display type */

#endif

      void              **jt;              /* jump table */

} gd_t;

 

typedef struct bd_info {

    int               bi_baudrate; /* serial console baudrate */

    unsigned long    bi_ip_addr;   /* IP Address */

    unsigned char    bi_enetaddr[6]; /* Ethernet adress */

    struct environment_s              *bi_env;

    ulong           bi_arch_number;      /* unique id for this board */

    ulong           bi_boot_params;       /* where this board expects params */

    struct                        /* RAM configuration */

    {

      ulong start;

      ulong size;

    }                bi_dram[CONFIG_NR_DRAM_BANKS];

#ifdef CONFIG_HAS_ETH1

    /* second onboard ethernet port */

    unsigned char   bi_enet1addr[6];

#endif

} bd_t;

 

Text 段之前的一段空间被用于存储系统的启动变量gd_t与bd_t。在函数start_armboot中会完成该段的初始化。Uboot执行完后会切换到linux,Uboot需要传给linux的参数就是通过此处的参数区实现的。

 


2) 设备初始化

设备的初始化分两部分,第一部分是通过下面的代码实现:

      for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

             if ((*init_fnc_ptr)() != 0) {

                    hang ();

             }

      }

其中,init_sequence 中存储了需要初始化的设别的初始化函数。

init_fnc_t *init_sequence[] = {

      cpu_init,              /* basic cpu dependent setup */

      board_init,           /* basic board dependent setup */

      interrupt_init,             /* set up exceptions */

      env_init,              /* initialize environment */

      init_baudrate,             /* initialze baudrate settings */

      serial_init,            /* serial communications setup */

      console_init_f,           /* stage 1 init of console */

      dram_init,            /* configure available RAM banks */

      NULL,

};

 

第二部分为start_armboot函数后续调用的初始化函数。

初始化flash:flash_init()

初始化malloc:mem_malloc_init()

读入环境参数:env_relocate()

设置mac与ip:

初始化额外设备:devices_init()/*  注:此处的设备初始化不是必须的,可以根据实际情况裁剪 */

存储用到的一些函数指针:jumptable_init()

初始化控制台:console_init_r()

使能中断,这样才可以接收串口与网口数据:enable_interrupts()

初始化以太网:eth_initialize()

 


3) 进入接收命令的状态

      for (;;) {

             main_loop ();

      }

Main_loop会直接调用内核启动函数,因此不需要再返回。

 


3 common/Main.c
1) main_loop

这个函数不需要多讲,主要就是打印提示信息并等待用户输入有输入则会进入run_command函数。

 


2) run_command

Run_command实现了从输入命令道函数指针的调用,内容为字符串解析。其中比较有意思的是find_cmd函数。

 


4 common/command.c
1) board\smdk2440nand\U-boot.lds

u-boot.lds是连接脚本,其中定义了一个特殊的段(.u_boot_cmd),用于存储bootmenu中可以使用的命令。

在文件u-boot.lds中有如下定义:

      __u_boot_cmd_start = .;

      .u_boot_cmd : { *(.u_boot_cmd) }

      __u_boot_cmd_end = .;

该段的含义为定义一个新段.u_boot_cmd。__u_boot_cmd_start为该段的第一个符号;__u_boot_cmd_end为该段的最后一个符号。

如果要把一个变量放入.u_boot_cmd段,需要通过宏U_BOOOT_CMD实现。宏的定义如下:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

#define U_BOOT_CMD(name, maxargs, rep,cmd, usage, help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}

例如:

我们定义变量

U_BOOT_CMD(bootm, 16, 1, do_bootm, “bootm--usage”, “help”)

展开后为:

cmd_tbl_t  __u_boot_cmd_ bootm  Struct_Section =

{“bootm”, 16, 1, do_bootm, “bootm--usage”}

如果把Struct_Section也展开,则为:

cmd_tbl_t  __u_boot_cmd_ bootm  __attribute__ ((unused,section (".u_boot_cmd"))) = \

{“bootm”, 16, 1, do_bootm, “bootm--usage”}

__attribute__((section(“bar”)))命令用于把某个符号放到特定段中,因此在这里符号__u_boot_cmd_ bootm会被放入段.u_boot_cmd中。

Uboot中所有命令都是存放在这个特殊段中的。当用户输入一个命令时(需要与定义时的name变量同名),会在函数find_cmd中从__u_boot_cmd_start开始循环匹配,匹配到后就执行对应变量中的cmd项;如果直到__u_boot_cmd_end也没有发现,则匹配失败。

例如:用户输入bootm,会找到__u_boot_cmd_bootm这个变量,然后会取出cmd命令(这里注册的是do_bootm)来执行。


2) find_cmd(const char *cmd)

依据上面的规则匹配到cmd后开始执行。常见的为下面几个命令:

bootm   从内存或rom加载

bootp   从网络加载

nboot   从flash加载


3) do_bootm

do_bootm中用到了宏SHOW_BOOT_PROGRESS来表示进度,一般这个宏不会做任何事情,但我们可以利用这个函数把do_bootm分为八部分。

前五部是基本的校验工作:

第一步:拷贝Image头并判断magic字段是否正确;

第二步:Image头crc校验;

第三步:Image文件进行crc校验;

第四步:校验Architecture字段;

第五步:校验Image类型

从第六步开始回进行异常向量等关键的操作:

第六步:根据压缩类型ih_comp解压Image;

第七步:根据ih_type进行相应操作,如果是kernel则不作任何操作;

第八步:根据操作系统类型ih_os调用不同的启动函数,linux会调用do_bootm_linux;


4) do_bootm_ linux

do_bootm_linux中大部分不会运行到,比较重要的是cleanup_before_linux函数,这个函数会禁止中断并禁止I-Cache与D-Cache,然后会根据ih_ep指向的地址跳入内核。

至此,uboot就结束自己的使命了。

作者: fireaxe   发布时间: 2010-09-05