天天看點

uboot-2015-07的start.S的檔案啟動過程(2)1.在檔案的最開始有這樣的注釋7.設定C運作環境并且調用 board_init_f(0)8.單闆初始化board_init_f9.跳轉運作第二第階段代碼

1.在檔案的最開始有這樣的注釋

/*
 *************************************************************************
 *
 * 啟動代碼 (被 ARM 的 reset 調用,不包括中斷)
 *
 * 隻有不從 memory 啟動的時候才做重要的初始化
 * 重定位代碼到 ram
 * 設定棧
 * 跳轉到啟動第二階段
 *
 *************************************************************************
 */
       

上面已經大緻交代了這個start.S檔案要做的事情 在 u-boot.lds檔案裡面指明了入口是 _start标号,是以開始會先加載 _start的内容,而此函數入口在vector.s裡面( 與以往核心不同的地方)。裡面會設定中斷向量表等等,當然,這些在編譯的時候已經加載過了,晶片複位的時候會跳到reset處繼續執行代碼 # 2.首先設定cpu處于管理模式

reset:
    /*
     * set the cpu to SVC32 mode
     */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0xd3
    msr cpsr, r0
           

# 3.關看門狗,屏蔽中斷

#  define pWTCON    0x53000000
#  define INTMSK    0x4A000008  /* Interrupt-Controller base addresses */
#  define INTSUBMSK 0x4A00001C
#  define CLKDIVN   0x4C000014  /* clock divisor register */
# endif
    /* 關看門狗 */
    ldr r0, =pWTCON
    mov r1, #0x0
    str r1, [r0]
    /* 屏蔽所有中斷 */
    mov r1, #0xffffffff
    ldr r0, =INTMSK
    str r1, [r0]
# if defined(CONFIG_S3C2410)
    ldr r1, =
    ldr r0, =INTSUBMSK
    str r1, [r0]
# endif
           

# 4.初始化時鐘分頻系數

/* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    ldr r0, =CLKDIVN
    mov r1, #3
    str r1, [r0]
           

# 5.CPU初始化

/* 擦除DCache與ICache */
    mov r0, #0
    mcr p15, , r0, c7, c7,    /* flush v3/v4 cache */
    mcr p15, , r0, c8, c7,    /* flush v4 TLB */

    /* 禁止MMU與Cache */
    mrc p15, , r0, c1, c0, 
    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, , r0, c1, c0, 

    /* 重定位之前, 通過lowlevel_init函數設定RAM時間參數 */
    mov ip, lr

    bl  lowlevel_init

    mov lr, ip
    mov pc, lr
           

# 6.調用 \_main 這裡開始有點疑惑,怎麼沒有記憶體設定以及重定位代碼就直接開始 \_main了,SourceInsight全局搜尋**_main**發現在 crt0.S裡面有**_main**函數體 **我們先看下_main函數的介紹**

/*
 * 1. 為調用 board_init_f() C函數設定初始化環境
 *    環境僅僅提供棧以及 GD ('global data') 資料結構的存放空間, 隻有已經初始化的靜态變量可以在該階段使用
 *
 * 2. 調用 board_init_f()。為系統從RAM啟動準備硬體環境, board_init_f() 必須使用 GD 來
 *    存放在最後階段需要用到的資料。資料包括重定位的目标位址, 未來要使用的棧, 以及未來要使用的 GD 的新位址
 *
 * 3. 為 stack and GD 設定中轉環境
 *
 * 4. 調用 relocate_code(). 函數重定位u-boot到 board_init_f() 指定的位置
 *
 * 5. 為調用 board_init_r() 設定最新的環境。初始化BSS,非靜态的全局變量和系統RAM中的棧
 *    GD保留被 board_init_f() 設定的值。 有些CPU還有一些關于記憶體的操作沒有進行, 是以要調用 call c_runtime_cpu_setup
 *
 * 6. 跳轉到 board_init_r().
 */
      

7.設定C運作環境并且調用 board_init_f(0)

ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)  /* CONFIG_SYS_INIT_SP_ADDR = 0x30000000 + 0x1000 - GENERATED_GBL_DATA_SIZE = 0x30000f50 */
bic sp, sp, #7  /* 8位元組對齊 */
mov r2, sp
sub sp, sp, #GD_SIZE    /* 在棧上面配置設定GD的空間,GD_SIZE = 168 */
bic sp, sp, #7  /* 8位元組對齊 */
mov r9, sp      /* GD is above SP */
mov r1, sp
mov r0, #0
/* 清空GD空間,以待重新指派 */
clr_gd:
    cmp r1, r2  
strlo   r0, [r1]        /* clear 32-bit GD word */
    addlo   r1, r1, #4      /* move to next */
    blo clr_gd
bl  board_init_f    /* 調用board_init_f對GD等等進行初始化,SP = 30000E80 */
           

值得說的是上面的GENERATED_GBL_DATA_SIZE宏定義,它是由DEFINE(GENERATED_GBL_DATA_SIZE,(sizeof(struct global_data) + 15) & ~15);獲得的,也就是相當于#define GENERATED_GBL_DATA_SIZE ((sizeof(struct global_data) + 15) & ~15)。

#define DEFINE(sym, val) \
    asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val))
           

這個語句在編譯的時候會被轉換為.h檔案以供别的檔案包含,這個宏定義的作用大概應該是讓形如#define GENERATED_GBL_DATA_SIZE ((sizeof(struct global_data) + 15) & ~15)可以随用随定義(此處存在疑問,為什麼不直接宏定義呢),補充:這個宏可以使全局變量gd_t可以在.S檔案裡面使用彙編代碼直接通路到,類似 ldr sp, [r9, #GD_START_ADDR_SP] 這樣的語句

另外由于 mov r9, sp 這句話的作用,以後要想在u-boot其它檔案裡面通路此時的SP(也就是gd的位置),就需要在檔案頭部加上DECLARE_GLOBAL_DATA_PTR,原型是

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9") 
//在2015版的裡面使用下面的函數得到,原理是一樣的
static inline gd_t *get_gd(void)
{
    gd_t *gd_ptr;

    __asm__ volatile("mov %0, r9\n" : "=r" (gd_ptr));

    return gd_ptr;
}
           

8.單闆初始化board_init_f

(需要注意的是,本處選擇的board_init_f為board.c裡面的,但是從2014以後就預設編譯的是board_f裡面的函數了,這裡為了學習友善,暫且選擇board.c裡面的函數進行分析,以後再去分析另一個分支代碼,切換代碼到board.c裡面的方法在以後的真正移植過程中會說到)

初始化全局變量gd,調用函數隊列

memset((void *)gd, , sizeof(gd_t));    /* 初始化gd填充為0 */
gd->mon_len = (ulong)&__bss_end - (ulong)_start;    /* 整個u-boot代碼與資料段的總大小,可以由u-boot.lds檔案得出 */
/* 調用一個初始化函數隊列 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != ) {
            hang ();
        }
    }
           

下面是init_sequence函數對列裡面的函數

board_early_init_f  /* 時鐘初始化,引腳初始化,不同的單闆要進行不同的設定 */
    timer_init          /* 定時器初始化 */
    env_init,           /* initialize environment */
    init_baudrate,      /* 波特率初始化,預設CONFIG_BAUDRATE為115200 */
        gd->baudrate = getenv_ulong("baudrate", , CONFIG_BAUDRATE);
    serial_init,        /* 序列槽初始化 */
    console_init_f,     /* console初始化 */
    display_banner,     /* 表明代碼運作到這裡了 */
    print_cpuinfo,      /* 列印cpu資訊 */
    dram_init,      /* 配置可用的RAM大小,預設PHYS_SDRAM_1_SIZE為64M */
        //dram_init 函數内部有 gd->ram_size = PHYS_SDRAM_1_SIZE; 
           

初始化程式最終存放位址addr

addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize(); /* addr指向SDRAM的結尾,0x34000000處,get_effective_memsize() = 64M */
/* 保留 TLB table,PGTABLE_SIZE預設為4K */
gd->arch.tlb_size = PGTABLE_SIZE;
addr -= gd->arch.tlb_size;  /* addr減去TLB大小 */

/* 下移到下一個64K開始處,相當于64KB對齊 */
addr &= ~( - );

gd->arch.tlb_addr = addr;   /* TLB的指向目前的addr */

/* 4KB對齊 */
addr &= ~( - );
debug("Top of RAM usable for U-Boot at: %08lx\n", addr);

/*
 * 為 U-Boot 代碼, 資料與bss保留白間,gd->mon_len;在函數剛開始的時候就指派了,大小就是u-boot整個編譯出來的檔案大小
 * 4KB對齊
 */
addr -= gd->mon_len;
addr &= ~( - );

/*
 * 為 malloc() 函數保留堆區
 */
addr_sp = addr - TOTAL_MALLOC_LEN;  /* addr的棧等于addr減去堆的大小 */

/*
 * 保留 Board Info 結構體的空間與一個gd_t的副本
 */
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;

/* 設定棧指針 */
addr_sp -= sizeof (gd_t);   /* 棧減去gd_t副本空間 */
id = (gd_t *) addr_sp;      /* id指派為目前addr_sp的值 */
gd->irq_sp = addr_sp;
/* 為中斷棧留3個位元組    */
addr_sp -= ;

/* 8位元組對齊 */
addr_sp &= ~;

gd->relocaddr = addr;   /* 重定位的目标位址為代碼區的起始位址 */
gd->start_addr_sp = addr_sp;    /* 棧位址為上面的addr_sp */
gd->reloc_off = addr - (ulong)&_start;  /* 重定位的偏移量是代碼區存放位址減去0位址 */
memcpy(id, (void *)gd, sizeof(gd_t));   /* 拷貝gd_t到重定位之後的預留gd_t副本的位址 */
           

重定位代碼之前的準備

ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp,sp為重定位代碼棧區起始位址 */
bic sp, sp, #7  /* 8位元組對齊 */
ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */
sub r9, r9, #GD_SIZE        /* 新的GD在bd下方,參見記憶體分布圖 */

adr lr, here
ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off,r0為重定位偏移量 */
add lr, lr, r0                  /* 重定位之後代碼的傳回位址 */
ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr,r0為重定位的目标位址 */
b   relocate_code
           

開始重定位代碼

ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs    r4, r0, r1      /* r4 <- relocation offset */
beq relocate_done       /* skip relocation */
ldr r2, =__image_copy_end   /* r2 <- SRC &__image_copy_end */

/* 到現在為止,比較重要的幾個寄存器的值為
 * r0 = gd->reloc_off,r0為重定位偏移量,本處也就是目标位址
 * r1 = __image_copy_start,r1為需要重定位代碼目前的起始位址,也就是代碼段的開始0
 * r4 = r0 - r1,r4為重定位的偏移值,偏移值減去0還是0
 * r2 =__image_copy_end,r2為需要重定位代碼的結束位址,r2 - r1就是需要重定位代碼長度了
 */

copy_loop:
    ldmia   r1!, {r10-r11}      /* 從源位址 [r1] 開始拷貝,pop到r10與r11裡面,一次8個位元組 */
    stmia   r0!, {r10-r11}      /* 拷貝到目标位址 [r0]    */
    cmp r1, r2          /* 一直到 [r1] 等于 [r2], 說明代碼拷貝結束    */
    blo copy_loop

    /*
     * 重定位修正 .rel.dyn 
     */
    ldr r2, =__rel_dyn_start    /* r2 <- SRC &__rel_dyn_start */
    ldr r3, =__rel_dyn_end  /* r3 <- SRC &__rel_dyn_end */
fixloop:
    ldmia   r2!, {r0-r1}        /* (r0,r1) <- (SRC location,fixup) */
    and r1, r1, #0xff
    cmp r1, #23         /* relative fixup? */
    bne fixnext

    /* relative fix: increase location by offset */
    add r0, r0, r4
    ldr r1, [r0]
    add r1, r1, r4
    str r1, [r0]
fixnext:
    cmp r2, r3
    blo fixloop
           

9.跳轉運作第二第階段代碼

到這裡已經完成了整個uboot的重定位以及一些基本裝置的初始化,接下來就是調用board_init_r,這個是uboot啟動的第二階段,包括nand flash的初始化,nor flash 等等裝置初始化,以及各種指令的初始化。

最終記憶體的規劃如下(雖然不同版本的uboot記憶體分布有所不同,但是大體上都是相同的):

uboot-2015-07的start.S的檔案啟動過程(2)1.在檔案的最開始有這樣的注釋7.設定C運作環境并且調用 board_init_f(0)8.單闆初始化board_init_f9.跳轉運作第二第階段代碼

繼續閱讀