Uboot分析(三)


Bootloader啟動大多數都分為兩個階段。第一階段主要包含依賴於CPU的體系結構硬件初始化的代碼,通常都用匯編語言來實現;第二階段通常用C語言完成,以便實現更復雜的功能,也使程序有更好的可讀性和可移植性。

U-Boot的啟動代碼分布在start.S、low_level_init.S、 board.c和main.c文件中。

start.S                是U-Boot整個程序的入口,該文件使用匯編語言編寫,不同體系結構的啟動代碼是不同的;

low_level_init.S    是特定開發板的設置代碼;


board.c               包含開發板底層設備驅動;

main.c                 是一個與平台無關的代碼,U- Boot應用程序的入口在此文件中。

第一階段對應的文件是cpu/XXX/start.S和board/samsung/XXX/lowlevel_init.S

第二階段對應的文件是lib_arm/board.c,最后跳轉到common/main.c,main_loop在標准轉入設備中接受命令行,然后分析,查找,執行。

 

一個可執行的image 必須有一個入口點,並且只能有一個全局入口點,所以要通知編譯器這個入口在哪里,入口點是通過鏈接腳本來實現的,由此我們可以找到程序的入口點是在cpu/arm_cortexa8/u-boot.lds 中指定的,其中ENTRY(_start) 說明程序從_start 開始運行,而它指向的是cpu/arm_cortexa8/start.o 文件。

因為我們用的是 cortex-a8 的 cpu 架構,在CPU復位后從iROM地址0x00000000取它的第一條指令,執行iROM代碼的功能是把flash中的前16K的代碼加載到iRAM中,系統上電后將首先執行 u-boot 程序。

 

首先我們來看一下u-boot.lds鏈接腳本,通過它我們可以知道它整個程序的各個段是怎么存放的。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;

 . = ALIGN(4);
 .text :
 {
  cpu/arm_cortexa8/start.o (.text)
  board/samsung/fsc100/lowlevel_init.o
  board/samsung/fsc100/mem_setup.o
  board/samsung/fsc100/nand_cp.o
  *(.text)//所有的其他程序的代碼段以四字節對齊放在后面
 }


 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }//只讀數據段

 . = ALIGN(4);
 .data : { *(.data) }//指定讀/寫數據段

 . = ALIGN(4);
 .got : { *(.got) }//指定got段,got段式是uboot自定義的一個段,非標准段

 __u_boot_cmd_start = .;//把__u_boot_cmd_start賦值為當前位置,即起始位置
 .u_boot_cmd : { *(.u_boot_cmd) }//指定u_boot_cmd段,uboot把所有的uboot命令放在該段
 __u_boot_cmd_end = .;//把 __u_boot_cmd_end賦值為當前位置,即結束位置

 . = ALIGN(4);
 __bss_start = .;__bss_start//賦值為當前位置,即bss段得開始位置
 .bss : { *(.bss) }
 _end = .;//把_end賦值為當前位置,即bss段得結束地址
}

1.stage1:cpu/arm_cortexa8/start.S

2.當系統啟動時, ARM CPU 會跳到 0x00000000去執行,一般 BootLoader 包括如下幾個部分:
                1. 建立異常向量表
                2. 顯示的切換到 SVC 且 32 指令模式
                3. 設置異常向量表
                4. 關閉 TLB,MMU,cache,刷新指令 cache 數據 cache
                5. 關閉內部看門狗
                6. 禁止所有的中斷
                7. 串口初始化
                8. tzpc(TrustZone Protection Controller)
                9. 配置系統時鍾頻率和總線頻率
                10. 設置內存區的控制寄存器
                11. 設置堆棧
                12. 跳到 C 代碼部分執行

 

#include <config.h>//@由頂層的mkconfig生成
#include <version.h>

@設置異常向量

.globl _start    @ 全局變量,_startGNU匯編的默認入口標簽
_start: b reset    @0x0,復位向量,直接跳轉到reset,並且不返回,正常情況下,系統 reset 后進入的入口
 ldr pc, _undefined_instruction    @0x4,未定義指令,系統出錯處理的入口
 ldr pc, _software_interrupt    @0x8,軟中斷,monitor 程序的入口
 ldr pc, _prefetch_abort    @0x0c,預取中止錯誤
 ldr pc, _data_abort    @0x10,取數據失中止錯誤(通常是保護現場)
 ldr pc, _not_used    @0x14 保留
 ldr pc, _irq    @0x18,中斷請求
 ldr pc, _fiq    @0x1c 快速中斷請求

@8*4 = 32 Byte

_undefined_instruction: .word 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
_pad:   .word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:

 .balignl 16,0xdeadbeef

@.word為GNU ARM匯編特有的偽操作,為分配一段字內存單元(分配的單元為字對齊的),可以使用.word把標志符作為常量使用。如_fiq:.word fiq即把fiq存入內存變量_fiq中,也即是把fiq放到地址_fiq中。

@.align偽操作用於表示對齊方式:通過添加填充字節使當前位置,.balignl是.balign的變體,在以當前地址開始,在地址為16的倍數的位置的前面填入四個字節內容為0xdeadbeef;.balignl的最后一個字母l代表4字節對齊,因此地址就是16*4=64,而前面已經占了15*4=60個字節,故在地址60處開始填充0xdeadbeef,0xdeadbeef作用就是為內存做標記,插在那里,就表示從這個位置往后的一段有特殊作用的內存,而這個位置往前,禁止訪問。
/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * setup Memory and board specific bits prior to relocation.
 * relocate armboot to ram
 * setup stack
 *當沒有從內存啟動時做一些重要的初始化,啟動內存和板子上特殊位來重映射。重映射armbootRAM,並初始化建立好棧
 *************************************************************************/

_TEXT_BASE:
 .word TEXT_BASE

/*TEXT_BASE這個標號的定義在如下文件中定義: 
 *board/samsung/smdkc100/config.mk 
 *TEXT_BASE = 0x34800000    @本程序運行的基地址為TEXT_BASE
 */

.globl _armboot_start
_armboot_start:
 .word _start    @_start 是uboot的第一行代碼的標號,代表的是第一行代碼的地址

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
 .word __bss_start

.globl _bss_end
_bss_end:
 .word _end

@在cpu/arm_cortexa8/u-boot.lds中定義,這樣賦值是因為代碼所在地址非編譯時的地址,直接取得該標號對應地址。

#ifdef CONFIG_USE_IRQ    @這個宏沒有定義,故不執行
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
 .word 0x0badc0de    @在IRQ_STACK_START處插入0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
 .word 0x0badc0de    @ 在FIQ_STACK_START處插入0x0badc0de
#endif

/*
 * the actual reset code    @真正的復位代碼
 */

@CPU進入SVC模式

reset:
 /*CPU一上電以后就是跳到這里執行的
  * set the cpu to SVC32 mode
  */

@更改處理器模式為管理模式
@對狀態寄存器的修改要按照:讀-改-寫的順序執行

CPSR

31 30 29 28 ---   7   6   -   4      3      2      1      0
N   Z   C  V         I   F         M4    M3   M2    M1   M0
                                        1      0      0      0      0     User模式

                                        1      0      0      0      1     FIQ模式

                                        1      0      0      1      0     IRQ模式

                                        1      0      1      1      1     SVC模式

                                        1      1      0      1      1     Abort模式

                                        1      1      1      1      1     Undef模式

                                        1      0      0      1      1     System模式

                                        1      0      1      1      0     Moniter模式(Cortex)


 mrs r0, cpsr    @將cpsr的值讀到r0中
 bic r0, r0, #0x1f    @清除M0~M4
 orr r0, r0, #0xd3    @禁止IRQ,FIQ中斷,並將處理器置於管理模式
 msr cpsr,r0

#if (CONFIG_OMAP34XX)    @這個宏沒有定義,下面的代碼不會預編譯
 /* Copy vectors to mask ROM indirect addr */
 adr r0, _start  @ r0 <- current position of code
 add r0, r0, #4  @ skip reset vector
 mov r2, #64   @ r2 <- size to copy
 add r2, r0, r2  @ r2 <- source end address
 mov r1, #SRAM_OFFSET0 @ build vect addr
 mov r3, #SRAM_OFFSET1
 add r1, r1, r3
 mov r3, #SRAM_OFFSET2
 add r1, r1, r3
next:
 ldmia r0!, {r3 - r10}  @ copy from source address [r0]
 stmia r1!, {r3 - r10}  @ copy to   target address [r1]
 cmp r0, r2   @ until source end address [r2]
 bne next   @ loop until equal */
#if !defined(CONFIG_SYS_NAND_BOOT) && !defined(CONFIG_SYS_ONENAND_BOOT)
 /* No need to copy/exec the clock code - DPLL adjust already done
  * in NAND/oneNAND Boot.
  */
 bl cpy_clk_code  @ put dpll adjust code behind vectors
#endif /* NAND Boot */
#endif
 /* the mask ROM code should have PLL and others stable */

#ifndef CONFIG_SKIP_LOWLEVEL_INIT    @這個宏沒有定義,條件成立,下面的代碼需要執行
 bl cpu_init_crit
#endif

@執行CPU初始化,BL完成跳轉的同時會把后面緊跟的一條指令地址保存到連接寄存器LR(R14)中。以使子程序執行完后正常返回。

ldr r0, =0xe03001c0
ldr r1, =0x1111
str r1, [r0]
ldr r0, =0xe03001c4
ldr r1, =0x3
str r1, [r0]

/* added */
ldr r0, =_TEXT_BASE
adr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup

ldr r0, =0xe03001c4
ldr r1, =0xf
str r1, [r0]

#ifdef CONFIG_CMD_NAND
ldr sp, =(0x22000000)
bl copy_uboot_to_ram
b stack_setup
#endif  /* CONFIG_CMD_NAND */

#ifndef CONFIG_SKIP_RELOCATE_UBOOT    @這個宏沒有定義,條件成立,下面的代碼能夠執行
relocate:    @ relocate U-Boot to RAM    U-boot自搬移到RAM
 adr r0, _start  @ r0 <- current position of code     裝載_start的地址到r0中
 ldr r1, _TEXT_BASE  @ test if we run from flash or RAM    裝載連接地址,這個地址是TEXT_BASE = 0x34800000
 cmp r0, r1   @ don't reloc during debug
 beq stack_setup

@調試階段的代碼是直接在RAM中運行的,而最后需要把這些代碼固化到Flash中,因此U-Boot需要自己從Flash轉移到RAM中運行,這@也是重定向的目的所在。
@通過adr指令得到當前代碼的地址信息:如果U-boot是從TEXT_BASE = 0x34800000,如果U-boot從Flash開始運行,即從處理器對應的地址運行,則r0=0x0000,這時將會執行copy_loop標識的那段代碼了。
@判斷 當uboot在nand當中引導時,會把前16K的代碼放到ram中,ram的地址和連接地址不一致, r0不等於r1的值,beq條件不成立;當從usb引導是這個條件就成立.成立后后面的代碼就不在執行了,后面的搬移代碼就不在執行.

 ldr r2, _armboot_start    @功能是裝載_start的地址

 / * .globl _armboot_start
   * _armboot_start:
   *         .word _start
   * /
 ldr r3, _bss_start    @ 功能是裝載

 / *.globl _bss_start
    * _bss_start:
    *        .word __bss_start
    * __bss_start這個標號在cpu/arm_cortexa8/u-boot.lds 中定義,是bss段的開始也是bss段以前的一個結束標志 
    * 因此r3的值是uboot的除去bss的末尾地址,在搬移的時候是不搬移bss段的,bss段放的是未初始化的變量 
    * /
 sub r2, r3, r2  @ r2 <- size of armboot    計算armboot的大小
 add r2, r0, r2  @ r2 <- source end address    計算源代碼結束地址

copy_loop:    @ copy 32 bytes at a time
 ldmia r0!, {r3 - r10}  @ copy from source address [r0]

@從源地址[r0]讀取8個字節到寄存器,每讀一個就更新一次r0地址    ldmia:r0安字節增長

 stmia r1!, {r3 - r10}  @ copy to   target address [r1]
 cmp r0, r2   @ until source end addreee [r2]     等到搬移完成后,r0和r2的值相等
 ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */

@LDM(STM)用於在寄存器所指的一片連續存儲器和寄存器列表的寄存@器間進行數據移動,或是進行壓棧和出棧操作。
@格式為:LDM(STM){條件}{類型}基址寄存器{!},寄存器列表{^}
@對於類型有以下幾種情況: 

    IA 每次傳送后地址加1,用於移動數據塊
    IB 每次傳送前地址加1,用於移動數據塊
    DA 每次傳送后地址減1,用於移動數據塊
    DB 每次傳送前地址減1,用於移動數據塊
    FD 滿遞減堆棧,用於操作堆棧(即先移動指針再操作數據,相當於DB)
    ED 空遞減堆棧,用於操作堆棧(即先操作數據再移動指針,相當於DA)
    FA 滿遞增堆棧,用於操作堆棧(即先移動指針再操作數據,相當於IB)
    EA 空遞增堆棧,用於操作堆棧(即先操作數據再移動指針,相當於IA)

 

 /* Set up the stack */    @設置堆棧,規划內存的使用的
stack_setup:

 ldr r0, =0xe03001c4
 ldr r1, =0x0
 str r1, [r0]

 ldr r0, _TEXT_BASE  @ upper 128 KiB: relocated uboot
 sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area    向下內存分配,為malloc預留分配空間

 /* CONFIG_SYS_MALLOC_LEN    include/configs/smdkc100.h
  *#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + (1 << 20)) 
  *環境變量大小#define CONFIG_ENV_SIZE (128 << 10) /* 128KiB, *0x20000  
  * 這句話的功能是r0 的值向低地址減去128K +1M的大小
  */ 
 sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo    預留初始化的數據的空間

 / * CONFIG_SYS_GBL_DATA_SIZE include/configs/smdkc100.h
   * #define CONFIG_SYS_GBL_DATA_SIZE 128 /* size in bytes */
   * 這句話是把地址繼續減去128 bytes
   * /
#ifdef CONFIG_USE_IRQ    @這個宏沒有定義,下面的代碼不會執行
 sub r0, r0, #(CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ)    @如果定義了中斷則還需要向下預留中斷空間
#endif
 sub sp, r0, #12  @ leave 3 words for abort-stack    @ 預留3個字給溢出堆棧
 and sp, sp, #~7  @ 8 byte alinged for (ldr/str)d

 /* Clear BSS (if any). Is below tx (watch load addr - need space) */
clear_bss:    @對bss段進行初始化
 ldr r0, _bss_start  @ find start of bss segment
 ldr r1, _bss_end  @ stop here
 mov r2, #0x00000000  @ clear value
clbss_l:
 str r2, [r0]  @ clear BSS location
 cmp r0, r1   @ are we at the end yet
 add r0, r0, #4  @ increment clear index pointer
 bne clbss_l   @ keep clearing till at end

 ldr r0, =0xe03001c4
 ldr r1, =0x1
 str r1, [r0]

 ldr pc, _start_armboot @ jump to C code    進入C代碼

_start_armboot: .word start_armboot

@進入lib_arm/board.c文件中的 void start_armboot (void)

@這句話使得pc指針也就從第一階段的匯編語言跳到了第二階段的C語言了
/*************************************************************************
 *
 * CPU_init_critical registers    @初始化關鍵的寄存器
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************/
cpu_init_crit:
 /*
  * Invalidate L1 I/D
  */

@初始化CACHES
 mov r0, #0   @ set up for MCR
 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache

 /*
  * disable MMU stuff and caches
  */

@關閉MMU和CACHES
 mrc p15, 0, r0, c1, c0, 0
 bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
 orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
 mcr p15, 0, r0, c1, c0, 0

 /*
  * Jump to board specific initialization...
  * The Mask ROM will have already initialized
  * basic memory. Go here to bump up clock rate and handle
  * wake up conditions.
  */
 mov ip, lr   @ persevere link reg across call    保存LR,以便正常返回,注意前面是通過BL跳到cpu_init_crit來的
 bl lowlevel_init  @ go setup pll,mux,memory    在重定向代碼之前,必須初始化內存時序,重定向時需要將flash中的代碼復制到內存中
 @lowlevel_init 這個函數在board/samsung/smdk100/lowlevel_init.S文件當中定義

mov lr, ip   @ restore link  
 mov pc, lr   @ back to my caller
/*
 *************************************************************************
 *
 * Interrupt handling
 *
 *************************************************************************
 */
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72

#define S_OLD_R0 68
#define S_PSR  64
#define S_PC  60
#define S_LR  56
#define S_SP  52

#define S_IP  48
#define S_FP  44
#define S_R10  40
#define S_R9  36
#define S_R8  32
#define S_R7  28
#define S_R6  24
#define S_R5  20
#define S_R4  16
#define S_R3  12
#define S_R2  8
#define S_R1  4
#define S_R0  0

#define MODE_SVC 0x13
#define I_BIT  0x80

/*
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */

 .macro bad_save_user_regs
 sub sp, sp, #S_FRAME_SIZE  @ carve out a frame on current
      @ user stack
 stmia sp, {r0 - r12}   @ Save user registers (now in
      @ svc mode) r0-r12

 ldr r2, _armboot_start
 sub r2, r2, #(CONFIG_SYS_MALLOC_LEN)
 sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ set base 2 words into abort
      @ stack
 ldmia r2, {r2 - r3}   @ get values for "aborted" pc
      @ and cpsr (into parm regs)
 add r0, sp, #S_FRAME_SIZE  @ grab pointer to old stack

 add r5, sp, #S_SP
 mov r1, lr
 stmia r5, {r0 - r3}   @ save sp_SVC, lr_SVC, pc, cpsr
 mov r0, sp    @ save current stack into r0
      @ (param register)
 .endm

 .macro irq_save_user_regs
 sub sp, sp, #S_FRAME_SIZE
 stmia sp, {r0 - r12}   @ Calling r0-r12
 add r8, sp, #S_PC   @ !! R8 NEEDS to be saved !!
      @ a reserved stack spot would
      @ be good.
 stmdb r8, {sp, lr}^   @ Calling SP, LR
 str lr, [r8, #0]   @ Save calling PC
 mrs r6, spsr
 str r6, [r8, #4]   @ Save CPSR
 str r0, [r8, #8]   @ Save OLD_R0
 mov r0, sp
 .endm

 .macro irq_restore_user_regs
 ldmia sp, {r0 - lr}^   @ Calling r0 - lr
 mov r0, r0
 ldr lr, [sp, #S_PC]   @ Get PC
 add sp, sp, #S_FRAME_SIZE
 subs pc, lr, #4   @ return & move spsr_svc into
      @ cpsr
 .endm

 .macro get_bad_stack
 ldr r13, _armboot_start  @ setup our mode stack (enter
      @ in banked mode)
 sub r13, r13, #(CONFIG_SYS_MALLOC_LEN) @ move past malloc pool
 sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ move to reserved a couple
      @ spots for abort stack

 str lr, [r13]   @ save caller lr in position 0
      @ of saved stack
 mrs lr, spsr   @ get the spsr
 str lr, [r13, #4]   @ save spsr in position 1 of
      @ saved stack

 mov r13, #MODE_SVC   @ prepare SVC-Mode
 @ msr spsr_c, r13
 msr spsr, r13   @ switch modes, make sure
      @ moves will execute
 mov lr, pc    @ capture return pc
 movs pc, lr    @ jump to next instruction &
      @ switch modes.
 .endm

 .macro get_bad_stack_swi
 sub r13, r13, #4   @ space on current stack for
      @ scratch reg.
 str r0, [r13]   @ save R0's value.
 ldr r0, _armboot_start  @ get data regions start
 sub r0, r0, #(CONFIG_SYS_MALLOC_LEN) @ move past malloc pool
 sub r0, r0, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ move past gbl and a couple
      @ spots for abort stack
 str lr, [r0]   @ save caller lr in position 0
      @ of saved stack
 mrs r0, spsr   @ get the spsr
 str lr, [r0, #4]   @ save spsr in position 1 of
      @ saved stack
 ldr r0, [r13]   @ restore r0
 add r13, r13, #4   @ pop stack entry
 .endm

 .macro get_irq_stack   @ setup IRQ stack
 ldr sp, IRQ_STACK_START
 .endm

 .macro get_fiq_stack   @ setup FIQ stack
 ldr sp, FIQ_STACK_START
 .endm

/*
 * exception handlers
 */

@異常向量處理
@每一個異常向量處其實只放了一條跳轉指令(因為每個異常向量只有4個字節不能放太多的程序),跳到相應的異常處理程序中。

 .align 5    @.align 5就是25次方對齊
undefined_instruction:
 get_bad_stack
 bad_save_user_regs
 bl do_undefined_instruction

 .align 5
software_interrupt:
 get_bad_stack_swi
 bad_save_user_regs
 bl do_software_interrupt

 .align 5
prefetch_abort:
 get_bad_stack
 bad_save_user_regs
 bl do_prefetch_abort

 .align 5
data_abort:
 get_bad_stack
 bad_save_user_regs
 bl do_data_abort

 .align 5
not_used:
 get_bad_stack
 bad_save_user_regs
 bl do_not_used

#ifdef CONFIG_USE_IRQ

 .align 5
irq:
 get_irq_stack
 irq_save_user_regs
 bl do_irq
 irq_restore_user_regs

 .align 5
fiq:
 get_fiq_stack
 /* someone ought to write a more effective fiq_save_user_regs */
 irq_save_user_regs
 bl do_fiq
 irq_restore_user_regs

#else

 .align 5
irq:
 get_bad_stack
 bad_save_user_regs
 bl do_irq

 .align 5
fiq:
 get_bad_stack
 bad_save_user_regs
 bl do_fiq

#endif

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM