U-boot启动流程(Linux内核)的分析(三转)

时间:2022-08-21 16:46:15
U-boot属于两阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.S 和board\samsung\smdk2410/lowlevel_init.S,前者是平台相关的,后者是开发板相关的。 1.U-Boot第一阶段代码分析
(1)硬件设备初始化
依次完成如下设置:将CPU的工作模式设为管理模式(SVC),关闭WATCHDOG,设置FCLK,HCLK,PCLK的比例,关闭MMU,CACHE。代码在cpu/arm920t/start.S中,
(2)为加载Bootloader的第二阶段代码准备RAM空间。
所谓准备RAM空间,就是初始化内存芯片,使它可用,对于S3C24x0,通过在Start.S中调用lowlevel_init函数来设置存储控制器,使得外接
SDRAM可用,lowlevel_init.S,文件是与开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件中的相关的宏。

_TEXT_BASE:
 .word TEXT_BASE //这里是获得代码段的起始地址,我的是0x33F80000(在board/xxx/config.mk中
                  //可到找到“TEXT_BASE=0x33F80000”
.globl lowlevel_init //这里相当于定义一个全局的lowlevel_init以方便调用

lowlevel_init:
 /* memory control configuration */
 /* make r0 relative the current location so that it */
 /* reads SMRDATA out of FLASH rather than memory ! */
 ldr r0, =SMRDATA //SMDATA表示这 13个寄存器的值存放的开始地址,值为0x33F8xxxx,处于内
                       //存中,这一句的作用是把其值加载到r0中
 
 ldr r1, _TEXT_BASE // 把代码的起始地址(0x33F80000)加载到r1中

 sub r0, r0, r1 //r0减去r1其结果存入r0,也即SMDATA中的起始地址0x33F8xxxx减去
                       //0x33F80000,其结果就是13个寄存器的值在NOR Flash存放的开始地址

 ldr r1, =BWSCON /* Bus Width Status Controller */ //存储控制器的基地址
 add r2, r0, #13*//在计算出来的存放地址加上#13*4,然后其结果保存在r2中
                           //13 个寄存器,每个寄存器占4个字节

0:
 ldr r3, [r0], #//内存中r0的值加载到r3中,然后r0加4,即下一个寄存器的

 str r3, [r1], #//读出寄存器的值保存到r1中,然后r1也偏移4

 cmp r2, r0 //比较r0与r2的值,如果不等继续返回0:执行,也即13个寄存器的值
                           // 是否读完
 bne 0b
 /* everything is fine now */
 mov pc, lr //程序跳转,返回到cpu_init_crit中
 .ltorg
/* the literal pools origin */
SMRDATA:
...................

(3)复制Bootloader的第二阶段代码到RAM空间中
这里将整个U-Boot代码都复制到SDRAM中,这在cpu/arm920t/start.s中实现

relocate:                /* 将U-Boot复制到RAM中     */
    adr    r0, _start        /* r0:当前代码的开始地址 */
    ldr    r1, _TEXT_BASE        /* r1:代码段的连接地址*/
    cmp r0, r1 /* 测试现在是在FLash中,还在是RAM中,如果要从NandFlash启动的话,这里要根据需要修改 */
    beq stack_setup /*如果已经在RAM中,则不需要复制*/

    ldr    r2, _armboot_start /*_armboot_start在前面定义,是第一条指令的运行地址*/
    ldr    r3, _bss_start /*在连接脚本U-Boot.lds中定义,是代码段的结束地址*/
    sub    r2, r3, r2        /* r2 <- 代码段长度 */
    add    r2, r0, r2        /* r2 <-代码段的结束地址 */

copy_loop:
    ldmia     {r3-r10}        /* 从地址[r0]处获得数据 */
    stmia     {r3-r10}        /* 复制到地址[r1]处 */
    cmp    r0, r2            /* 判断是否复制完毕 */
    ble    copy_loop /*没有复制完,则继续*/
#endif    /* CONFIG_SKIP_RELOCATE_UBOOT */

上面这段程序,在使用NANDFlash启动时,需要修改。
(4)设置好栈
/*栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可*/

stack_setup:
    ldr    r0, _TEXT_BASE        /* _TEXT_BASE 为代码段的开始地址,值为0x33F80000 */
    sub    r0, r0, #CONFIG_SYS_MALLOC_LEN    /* 代码段下面,留出一段内存以实现malloc */
    sub    r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* 再留出一段内存,存一些全局参数 */
#ifdef CONFIG_USE_IRQ
    sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
    sub    sp, r0, #12        /* 最后,留出12字节的内存给abort异常 */

clear_bss:
    ldr    r0, _bss_start        /* 下面的都是栈 */
    ldr    r1, _bss_end        /* stop here */
    mov    r2, #0x00000000        

(5)跳转到第二阶段代码的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        /* clear */

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

    现在,C函数的运行环境已经完全准备好,通过如下命令直接跳转,这之后在内存中执行,原先在NorFlash中,它将调用lib_arm/boadr.c中的star_armboot函数,这是第二阶段的入口。

ldr    pc, _start_armboot

_start_armboot:    .word start_armboot

2 U-Boot第二阶段代码分析     U-boot在启动内核之前可以让用户决定是否进入下载模式,即进入U-Boot的控制界面。
第二阶段从lib_arm/borad.c中的start_armboot函数开始,程序的流程如下 :
U-boot启动流程(Linux内核)的分析(三转) (1)初始化本阶段要使用到的硬件设备 最主要的是设置系统时钟,初始化串口,只要这两个设置好了,就可以从串口看到打印信息。
board_init 函数设置MPLL,改变系统时钟,它是开发板相关函数。board\samsung\smdk2410/smdk2410.c中实现。串口的初始化函数主 要是serial_init,它设置UART控制器,是CPU相关的函数,在cpu/arm920t/s3c24x0/serial.c中实现。
(2)检测系统内存映射 对于特定的开发板,其内存的分布是明确的,所以可以直接设置,board\samsung\smdk2410\smdk2410.c中的dram_init函数指定了本开发板的内存起始地址为0x30000000,大小为0x40000000。  

int dram_init (void)
{
    gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
    gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;

    return 0;
}

(3) 为内核设置启动参数
   在start_armboot()函数的最后,调用main_loop()函数,进行一个无限循环,该函数在common/main.c文件中定义。

u-boot-2009.08\lib_arm\bootm.c文件中,定义了引导Linux内核的 do_bootm_linux()函数,U_Boot也是通过标记列表向内核传递参数的,一般而言,设置这以下两个标记就可以了,在配置文件 include/configs/smdk2410.h中,增加如下两个配置项即可:

#define CONFIG_SETUP_MEMORY_TAGS 1
#define CONFIG_CMDLINE_TAG 1

   对于ARM架构的CPU来说,都是通过u-boot-2009.08\lib_arm\bootm.c中的do_bootm_linux函数来启动内核 的,这个函数中,设置标记列表,最后通过“theKernel (0, machid, bd->bi_boot_params);”调用内核,其中,这里第1、2、3个参数就分别存储在r0、r1、r2中。theKernel指向内核 存放的地址,(对于ARM架构的CPU,通常是0x30008000),bd->bi_boot_params就是在board_init函数设置 的机器类型ID,而bd->bi_boot_params就是标记列表的地址