• 从0移植uboot (二) _启动流程分析


    来源:Linux社区  作者:xiaojiang1025  : http://www.linuxidc.com/Linux/2017-02/141019.htm

     

    经过了上一篇的配置,我们已经执行make就可以编译出一个uboot.bin,但这还不够,首先,此时的uboot并不符合三星芯片对bootloader的格式要求,其次,此时的uboot.bin也没有结合我们的开发板进行配置,还无法使用。而要进行这样的个性化配置,前提条件就是对uboot开机流程和编译系统有所了解,本文主要讨论前者。uboot是一个两阶段bootloader,第一阶段主要做硬件直接相关的初始化,使用汇编编写;第二阶段主要为操作系统的运行准备环境,主要用C编写,这里以ARM平台为例分析其启动流程。下面是启动过程中主要涉及的文件

    arch/arm/cpu/armv7/start.S
    board/samsung/myboard/lowlevel_init.S
    arch/arm/lib/crt0.S
    arch/arm/lib/board.c
    arch/samsung/myboard/myboard.c

    第一阶段

    第一阶段的主要文件和任务如下

    arch/arm/cpu/armv7/start.S
            1. 设置CPU为SVC模式
            2. 关闭中断,MMU,Cache
    board/samsung/origen/lowlevel_init.S
            3. 关闭看门狗
            4. 初始化内存,串口
            5. 设置栈
            6. 代码自搬移
            7. 清BSS
            8. 跳转到C入口????

    start.S

     39 .globl _start
     40 _start: b       reset
     41         ldr     pc, _undefined_instruction
     42         ldr     pc, _software_interrupt
     43         ldr     pc, _prefetch_abort
     44         ldr     pc, _data_abort
     45         ldr     pc, _not_used
     46         ldr     pc, _irq
     47         ldr     pc, _fiq

    --40--> 异常向量表设置

    126 reset:
    127         bl      save_boot_params
    131         mrs     r0, cpsr
    132         bic     r0, r0, #0x1f
    133         orr     r0, r0, #0xd3
    134         msr     cpsr,r0

    --126-->设置CPU为SVC模式

    下面这三行代码非常重要,是整个uboot启动过程的交叉点

    154         bl      cpu_init_cp15
    155         bl      cpu_init_crit
    158         bl      _main

    --154-->跳转执行cpu_init_cp15,即初始化CP15协处理器
    --155-->跳转执行cpu_init_crit,
    --158-->跳转执行_main,即第二阶段

    287 ENTRY(cpu_init_cp15)
    291         mov     r0, #0                  @ set up for MCR
    292         mcr     p15, 0, r0, c8, c7, 0   @ invalidate TLBs
    293         mcr     p15, 0, r0, c7, c5, 0   @ invalidate icache
    294         mcr     p15, 0, r0, c7, c5, 6   @ invalidate BP array
    295         mcr     p15, 0, r0, c7, c10, 4  @ DSB
    296         mcr     p15, 0, r0, c7, c5, 4   @ ISB
    297 
    301         mrc     p15, 0, r0, c1, c0, 0
    302         bic     r0, r0, #0x00002000     @ clear bits 13 (--V-)
    303         bic     r0, r0, #0x00000007     @ clear bits 2:0 (-CAM)
    304         orr     r0, r0, #0x00000002     @ set bit 1 (--A-) Align
    305         orr     r0, r0, #0x00000800     @ set bit 11 (Z---) BTB
    307         bic     r0, r0, #0x00001000     @ clear bit 12 (I) I-cache
    311         mcr     p15, 0, r0, c1, c0, 0
    312         mov     pc, lr                  @ back to my caller
    313 ENDPROC(cpu_init_cp15)

    --291-->关闭Cache
    --301-->关闭MMU

    324 ENTRY(cpu_init_crit)
    331         b       lowlevel_init           @ go setup pll,mux,memory
    332 ENDPROC(cpu_init_crit)

    --331-->跳转到lowlevel_init,位于board/samsung/origen/lowlevel_init.S,进行板级相关的设置。

    lowlevel_init.S

    这是位于目录的初始化文件,主要完成特定开发板的初始化工作,包括时钟、内存和串口等。

     82         bl system_clock_init
     85         bl mem_ctrl_asm_init
     87 1:
     88         /* for UART */
     89         bl uart_asm_init
     90         bl tzpc_init
     91         pop     {pc}
    114 system_clock_init:
    329 uart_asm_init:
    357 tzpc_init:

    --82-->初始化系统时钟,即跳转到114行
    --85-->初始化系统内存
    --89-->初始化UART串口,即跳转到329行
    --90-->初始化TrustZoneProtectorController,即跳转到357行

    执行完lowlevel_init.S,依据上面那三行代码,执行流程就该回到start.S执行156行跳转到_main,开始执行第二阶段。

    第二阶段

    从start.S跳转到_main ,标致着uboot启动过程的第二阶段的开始。在第二阶段,核心文件是crt0.S,但我们最关心的是其中回调板级C程序的入口位置。第二阶段的流程如下:

    arch/arm/lib/crt0.S
            1. 初始化C运行环境,调用board_init_f()
    arch/arm/lib/board.c
            1. board_init_f对全局信息GD结构体进行填充
    arch/arm/lib/crt0.S
            1. 代码重定位
            2. 代码自搬移
            3. 执行超循环
    arch/arm/lib/board.c
            1. board_init_r()是进入定制板目录的入口

    crt0.S

    进入第二阶段是首要任务就是准备C语言运行的环境:

     96 _main:
    102 #if defined(CONFIG_NAND_SPL)
    103         /* deprecated, use instead CONFIG_SPL_BUILD */
    104         ldr     sp, =(CONFIG_SYS_INIT_SP_ADDR)
    105 #elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    106         ldr     sp, =(CONFIG_SPL_STACK)
    107 #else
    108         ldr     sp, =(CONFIG_SYS_INIT_SP_ADDR)
    109 #endif
    110         bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
    111         sub     sp, #GD_SIZE    /* allocate one GD above SP */
    112         bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
    113         mov     r8, sp          /* GD is above SP */
    114         mov     r0, #0
    115         bl      board_init_f

    _main
    --104-->初始化SP,为C语言做准备
    --110-->保存128B放GD结构体来存放全局信息,
    --111-->GD的地址放在r8中,
    --115-->跳转到board_init_f(),这个整个初始化过程中第一次执行的C代码

    board.c

    下面这个函数就是uboot初始化过程中执行的第一个C函数,可以看作这个文件的入口函数。函数比较长,我就不逐句分析了,这个函数主要的作用就是执行一些高等级的初始化。其中最重要的就是准备全局信息GD结构体

    209 typedef int (init_fnc_t) (void);
    243 init_fnc_t *init_sequence[] = {
    244         arch_cpu_init,          /* basic arch cpu dependent setup */
    245         mark_bootstage,
    246 #ifdef CONFIG_OF_CONTROL
    247         fdtdec_check_fdt,
                ...
    277 void board_init_f(ulong bootflag)
    278 {
            ...
    291         gd->mon_len = _bss_end_ofs;
    292 #ifdef CONFIG_OF_EMBED
    293         /* Get a pointer to the FDT */
    294         gd->fdt_blob = _binary_dt_dtb_start;
    295 #elif defined CONFIG_OF_SEPARATE
    296         /* FDT is at end of image */
    297         gd->fdt_blob = (void *)(_end_ofs + _TEXT_BASE);
    298 #endif
    299         /* Allow the early environment to override the fdt address */
    300         gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
    301                                                 (uintptr_t)gd->fdt_blob);
    302 
    303         for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
    304                 if ((*init_fnc_ptr)() != 0) {
    305                         hang ();
    306                 }
    307         }
            ...

    board_init_f()
    --243--> 全局的函数指针数组,每个指针都是int (*ptr)(void)型的。
    --291-->mon_len 通过链接脚本可以知道存放的是 uboot 代码大小;
    --294-->fdt_blob 存放设备数地址;
    --303--遍历函数指针数组init_sequence中的每一个成员,就是将数组中的每一个初始化函数都执行一次,这种写法可以借鉴

    crt0.S

    函数board_init_f()返回后,继续执行crt0.S中115行之后的部分,主要的工作是执行代码自搬移,代码重定位等工作,执行完这些之后,我们我们找到了最感兴趣的下面这几句

    163         /* call board_init_r(gd_t *id, ulong dest_addr) */
    164         mov     r0, r8                  /* gd_t */
    165         ldr     r1, [r8, #GD_RELOCADDR] /* dest_addr */
    166         /* call board_init_r */
    167         ldr     pc, =board_init_r       /* this is auto-relocated! */

    --167-->跳转到board_init_r函数执行,这次跳出去这个文件的语句就执行完毕了,不会再回来了

    board.c

    这也是最后一次跳转到这个文件了,执行额函数如下

    519 void board_init_r(gd_t *id, ulong dest_addr)
    520 {
    521         ulong malloc_start;
    522 #if !defined(CONFIG_SYS_NO_FLASH)
    523         ulong flash_size;
    524 #endif
    525 
    526         gd->flags |= GD_FLG_RELOC;      /* tell others: relocation done */
    527         bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
    528 
    529         monitor_flash_len = _end_ofs;
    530 
    531         /* Enable caches */
    532         enable_caches();
    533 
    534         debug("monitor flash len: %08lX
    ", monitor_flash_len);
    535         board_init();   /* Setup chipselects */
            ...
    650          /* set up exceptions */
    651         interrupt_init();
    652         /* enable exceptions */
    653         enable_interrupts(); 
    667         eth_initialize(gd->bd);
            ...
    701         /* main_loop() can return to retry autoboot, if so just run it again. */
    702         for (;;) {
    703                 main_loop();
    704         }
    705 
    

    board_init_r()
    --532-->很多紧急工作都做完了,可以打开cache了
    --535-->关键!!!这个就是我们苦苦寻找的板级定制文件的xxx.c的入口函数!!!
    --651-->中断初始化
    --653-->使能中断
    --667-->网卡初始化,函数的实现在net/eth.c,会回调板级xxx.c中的board_eth_init()
    --703-->执行超循环,主要功能是处理环境变量,解析命令,也就是uboot中和我们交互的命令的解析工作都在这里执行!!!

    main_loop()与启动内核

    main_loop()的实现在common/main.c,它的主要功能就是循环检测输入的命令并执行,其中一个环境变量bootdelay(自启动)的设置决定了是否启动内核,如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。ootloader 要想启动内核,可以直接跳到内核的第一个指令处,即内核的起始地址,这样便可以完成内核的启动工作了。但是要想启动内核还需要满足下面的一些条件,这些条件在Linux内核文档"/Documentation/kernel-parameters.txt"中有说明,
    1、cpu 寄存器设置

    * R0 = 0
    * R1 = 机器类型 id
    * R2 = 启动参数在内存中的起始地址

    2、cpu 模式

    * 禁止所有中断
    * 必须为 SVC(超级用户)模式

    3、Cache、MMU

    * 关闭 MMU
    * 指令 Cache 可以开启或者关闭
    * 数据 Cache 必须关闭

    4、设备

    * DMA 设备应当停止工作

    5、PC 为内核的起始地址

    关于uboot的启动分析,本文只是冰山一角的一丢丢,不过希望通过我的这一堆废话下来,能帮助你对uboot的启动流程有一个整体的认识,当然,如果文中有错误,欢迎批评指正^-^

  • 相关阅读:
    游标
    Linq to Sql学习
    在asp.net mvc中使用Uploadify上传文件
    Linq 笔记
    ASP.NET MVC笔记
    转:ASP.NET MVC:窗体身份验证及角色权限管理示例
    PowerDesigner
    asp.net缓存使用总结
    Bash Shell中命令行选项/参数处理
    Javascript parseFloat、parseDouble类型转换,数值加减,四舍五入
  • 原文地址:https://www.cnblogs.com/icefree/p/8491270.html
Copyright © 2020-2023  润新知