1. 程式人生 > >嵌入式 hi3518c平臺uboot中start.s小結

嵌入式 hi3518c平臺uboot中start.s小結

第一階段:

/*====================================Hi3518c start.S Begin 2014-04-20=============================================*/
/*
 *  armboot - Startup Code for ARM926EJS CPU-core
 *
 *  Copyright (c) 2003  Texas Instruments
 *
 *  ----- Adapted for OMAP1610 OMAP730 from ARM925t code ------
 *
 *  Copyright (c) 2001 Marius Gr?ger <

[email protected]>
 *  Copyright (c) 2002 Alex Z?pke <
[email protected]>
 *  Copyright (c) 2002 Gary Jennejohn <
[email protected]>
 *  Copyright (c) 2003 Richard Woodruff <
[email protected]>
 *  Copyright (c) 2003 Kshitij <
[email protected]>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */


#include <config.h>
#include <version.h>

/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */


.globl _start //彙編程式都要提供一個_start符號並且用.globl宣告
_start: b reset //B或BL指令引起處理器轉移到“子程式名”處開始執行 復位
 ldr     pc, _undefined_instruction
 ldr     pc, _software_interrupt
 ldr     pc, _prefetch_abort
 ldr     pc, _data_abort
 ldr     pc, _not_used
 ldr     pc, _irq
 ldr     pc, _fiq

_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 */

/*.fill
     語法:.fill repeat, size, value
     含義是反覆拷貝 size個位元組,重複 repeat 次,
         其中 size 和 value 是可選的,預設值分別為 1 和 0.
*/
__blank_zone_start:
.fill 1024*4,1,0 //給某個具體的暫存器裡填數
__blank_zone_end:

.globl _blank_zone_start
_blank_zone_start:
.word __blank_zone_start


.globl _blank_zone_end
_blank_zone_end:
.word __blank_zone_end

 .balignl 16,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
 *
 *************************************************************************
 */

_TEXT_BASE:
 .word TEXT_BASE

.globl _armboot_start
_armboot_start:
 .word _start

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

.globl _bss_end
_bss_end:
 .word _end

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
 .word 0x0badc0de

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


_clr_remap_spi_entry:
    .word   SF_TEXT_ADRS + do_clr_remap - TEXT_BASE
_clr_remap_nand_entry:
    .word   NAND_TEXT_ADRS + do_clr_remap - TEXT_BASE

/*
 * the actual reset code
 */

reset:
 /*
  * set the cpu to SVC32 mode
  */
 mrs r0,cpsr //將狀態暫存器的內容傳送至通用暫存器,將CPSR中的內容傳送至R0
 bic r0,r0,#0x1f //位清除指令 將R0最低5位清零,其餘位不變 工作模式位清零
 orr r0,r0,#0xd3 //工作模式位設定為“10011”(管理模式),並將中斷禁止位和快中斷禁止位置1 "1101 0011" 指令用於在兩個運算元上進行邏輯或運算,並把結果放置到目的暫存器中
 msr cpsr,r0 //將通用暫存器的內容傳送至狀態暫存器,將中的內容R0傳送至CPSR

 /*
  * we do sys-critical inits only at reboot,
  * not when booting from ram!
  */

 /*
  * flush v4 I/D caches
  */
 mov r0, #0 //置零ro通用暫存器
 mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ //向c7寫入0將使ICache與DCache無效 "0"表示省略opcode_2 MCR{<cond>} p15, 0, <Rd>, <CRn>, <CRm>{,<opcode_2>}
 mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ //MCR{條件} 協處理器編碼,協處理器操作碼1,源暫存器,目的暫存器1,目的暫存器2,協處理器操作碼2

 /*
  * 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 */
 mcr p15, 0, r0, c1, c0, 0 //儲存r0到控制暫存器

 mov  r0, pc, lsr#24 //LSL、LSR、ASR、ROR 暫存器移位
 cmp  r0, #0x0
 bne do_clr_remap //檢測是否需要跳轉,PC的高八位如果不為0(已經在ram中運行了)則跳轉 不等於則調轉

check_start_mode:
 ldr r0, =REG_BASE_SCTL
 ldr r0, [r0, #REG_SYSSTAT]
 mov r6, r0, lsr#5
 and r6, #0x1

 /* reg[0x2005008c:5]:
  * 0: start from spi
  * 1: start from nand
  */

 cmp r6, #BOOT_FROM_SPI
 ldreq   pc, _clr_remap_spi_entry

 ldr pc, _clr_remap_nand_entry
 @b . /* bug here */

/*
LDR和STR用來存取記憶體,關於"索引偏移",你是不是指pre-indexed addressing和post-indexed addressingpre-indexed addressing是指地址經過運算不寫回基址暫存器post-indexed addressing則回寫到基址暫存器比如pre-indexed addressing:mov r1,#0STR r0, [r1, #0x10]       ;r1+0x10這個是所用的實際地址值,但是不回寫入r1,在此句之後,r1=0post-indexed addressing:STR r0, [r1], #0x10       ;r1+0x10這個是所用的實際地址值,這個值回寫入r1,此句之後,r1=0x10
*/
do_clr_remap:
 ldr     r4, =REG_BASE_SCTL //用來從儲存器(確切地說是地址空間)中裝載資料到通用暫存器 系統控制器暫存器 0x20050000 寫地址
 ldr  r0, [r4, #REG_SC_CTRL] //載入32位的立即數或一個地址值到指定暫存器 不回寫 其實是r4+#0x0是實際地址值

 /* reg[0x20050000:8]:
  * 0: keep remap
  * 1: clear remap 重對映
  */
 @Set clear remap bit.
 orr  r0, #(1<<8) //第八位置1
 str  r0, [r4, #REG_SC_CTRL] //不回寫 @表示註釋

 @Setup TCM (ENABLED, 2KB) // TCM時鐘門控使能
 ldr     r0, =( 1 | (MEM_CONF_ITCM_SIZE<<2) | MEM_BASE_ITCM)
 mcr     p15, 0, r0, c9, c1, 1

 @enable I-Cache now
 mrc p15, 0, r0, c1, c0, 0
 orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
 mcr p15, 0, r0, c1, c0, 0

 @Check if I'm running in ddr //程式碼記憶體執行測試
 mov r0, pc, lsr#28
 cmp r0, #8
 bleq    relocate //小於等於跳轉

 ldr     r0, _blank_zone_start
 ldr     r1, _TEXT_BASE //程式碼段
 sub     r0, r0, r1 //減法 sub a,b (a-b)
 adrl    r1, _start //將相對於程式或相對於暫存器的地址載入暫存器中 adrl寬
 add     r0, r0, r1 //加法
 mov     r1, #0          /* flags: 0->normal 1->pm */
 bl      init_registers //初始化暫存器

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:
 @copy arm exception table in 0 address
 adrl r0, _start
 mov r1, #0
 mov r2, #0x100  /* copy arm Exception table to 0 addr */
 add     r2, r0, r2
copy_exception_table:
 ldmia   r0!, {r3 - r10}
 stmia   r1!, {r3 - r10}
 cmp     r0, r2
 ble     copy_exception_table

 @relocate U-Boot to RAM
 adrl 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 */

 /* Set up the stack          */
stack_setup:
 ldr r0, _TEXT_BASE  /* upper 128 KiB: relocated uboot   */
 sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area      */
 sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo      */
#ifdef CONFIG_USE_IRQ
 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
 sub sp, r0, #12  /* leave 3 words for abort-stack    */
 bic sp, sp, #7  /*8-byte alignment for ABI compliance*/

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

 ldr pc, _start_armboot

_start_armboot:
 .word start_armboot


/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
 /*
  * 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

 /*
  * Go setup Memory and board specific bits prior to relocation.
  */
 mov ip, lr  /* perserve link reg across call */
 @bl lowlevel_init /* go setup pll,mux,memory */
 mov lr, ip  /* restore link */
 mov pc, lr  /* back to my caller */
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

#ifndef CONFIG_PRELOADER
/*
 *************************************************************************
 *
 * 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
 @ carve out a frame on current user stack
 sub sp, sp, #S_FRAME_SIZE
 stmia sp, {r0 - r12} @ Save user registers (now in svc mode) r0-r12

 ldr r2, _armboot_start
 sub r2, r2, #(CONFIG_STACKSIZE+CONFIG_SYS_MALLOC_LEN)
 @ set base 2 words into abort stack
 sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE+8)
 @ get values for "aborted" pc and cpsr (into parm regs)
 ldmia r2, {r2 - r3}
 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
 @ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
 add r8, sp, #S_PC
 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
 sub r13, r13, #(CONFIG_STACKSIZE+CONFIG_SYS_MALLOC_LEN)
 @ reserved a couple spots in abort stack
 sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE+8)

 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_irq_stack   @ setup IRQ stack
 ldr sp, IRQ_STACK_START
 .endm

 .macro get_fiq_stack   @ setup FIQ stack
 ldr sp, FIQ_STACK_START
 .endm
#endif /* CONFIG_PRELOADER */

/*
 * exception handlers
 */
#ifdef CONFIG_PRELOADER
 .align 5 //加上.align彙編語句後,指令就對齊
do_hang:
 ldr sp, _TEXT_BASE   /* switch to abort stack */
1:
 bl 1b    /* hang and never return */
#else /* !CONFIG_PRELOADER */
 .align  5
undefined_instruction:
 get_bad_stack
 bad_save_user_regs
 bl do_undefined_instruction

 .align 5
software_interrupt:
 get_bad_stack
 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 effiction 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
#endif /* CONFIG_PRELOADER */
#include "lowlevel_init.S"
/*====================================Hi3518c start.S End=============================================*/

最近一直在做U-boot和Linux核心的編譯與移植的工作,就來講一講對U-boot的初步理解。我的目標板核心片是i.MX255,以下都是依據這個環境所言。

1.U-boot啟動過程:

1)/uboot/cpu/arm926ejs/start.S檔案是Uboot的入口程式。
2)/uboot/lib_arm/board.c Uboot執行的第一個C函式,完成系統的初始化。

3)init_sequence[] 是基本的初始化函式指標。
4)void start_armboot(void) 數序執行init_sequence[]陣列中的初始化函式。

我把U-boot的執行過程簡化描述如下:
check board->dram_init->flash init->nand init->env relocate->ip,mac獲取->device init->網絡卡初始化->進入main_loop函式,等待串列埠輸入(無輸入則執行bootcmd命令)。

2.U-boot和核心的主要關係式核心啟動過程中引數的傳遞。

U-boot會給Linux Kernel傳遞很多引數,如:串列埠,RAM,videofb等。而核心也會讀取和處理這些引數。兩者之間通過struct tag來傳遞引數。U-boot把要傳遞給kernel的東西儲存在struct tag資料結構中,啟動kernel時,把這個結構體的實體地址傳給kernel;Linux kernel通過這個地址,用parse_tags分析出傳遞過來的引數。
1、u-boot給kernel傳引數:
在uboot/common/cmd_bootm.c檔案中,bootm命令對應的do_bootm函式,當分析uImage中資訊發現OS是Linux時,呼叫. /lib_arm/bootm.c檔案中的do_bootm_linux函式來啟動Linux kernel。
2、核心讀取U-boot傳遞的相關引數:
對於Linux Kernel配合ARM平臺啟動時,先執行arch/arm/kernel/head.S,這個檔案會呼叫arch/arm/kernel/head-common.S中的函式,在最後呼叫start_kernel。

1. kernel執行的史前時期和記憶體佈局

在arm平臺下,zImage.bin壓縮映象是由bootloader載入到實體記憶體,然後跳到zImage.bin裡一段程式,它專門於將被壓縮的kernel解壓縮到KERNEL_RAM_PADDR開始的一段記憶體中,接著跳進真正的kernel去執行。該kernel的執行起點是stext函式,定義於arch/arm/kernel/head.S。

在分析stext函式前,先介紹此時記憶體的佈局如下圖所示

在開發板tqs3c2440中,SDRAM連線到記憶體控制器的Bank6中,它的開始記憶體地址是0x30000000,大小為64M,即0x20000000。 ARM Linux kernel將SDRAM的開始地址定義為PHYS_OFFSET。經bootloader載入kernel並由自解壓部分程式碼執行後,最終kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)地址上的一段記憶體,經此放置後,kernel程式碼以後均不會被移動。

在進入kernel程式碼前,即bootloader和自解壓縮階段,ARM未開啟MMU功能。因此kernel啟動程式碼一個重要功能是設定好相應的頁表,並開啟MMU功能。為了支援MMU功能,kernel映象中的所有符號,包括程式碼段和資料段的符號,在連結時都生成了它在開啟MMU時,所在實體記憶體地址對映到的虛擬記憶體地址。

以arm kernel第一個符號(函式)stext為例,在編譯連結,它生成的虛擬地址是0xc0008000,而放置它的實體地址為0x30008000(還記得這是PHYS_OFFSET+TEXT_OFFSET嗎?)。實際上這個變換可以利用簡單的公式進行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最終的kernel空間的頁表,就是按照這個關係來建立。

之所以較早提及arm linux 的記憶體對映,原因是在進入kernel程式碼,裡面所有符號地址值為清一色的0xCXXXXXXX地址,而此時ARM未開啟MMU功能,故在執行stext函式第一條執行時,它的PC值就是stext所在的記憶體地址(即實體地址,0x30008000)。因此,下面有些程式碼,需要使用地址無關技術。

2. 一覽stext函式

這裡的啟動流程指的是解壓後kernel開始執行的一部分程式碼,這部分程式碼和ARM體系結構是緊密聯絡在一起的,所以最好是將ARM ARCHITECTURE REFERENCE MANUL仔細讀讀,尤其裡面關於控制暫存器啊,MMU方面的內容~

stext函式定義在Arch/arm/kernel/head.S,它的功能是獲取處理器型別和機器型別資訊,並建立臨時的頁表,然後開啟MMU功能,並跳進第一個C語言函式start_kernel。

stext函式的在前置條件是:MMU, D-cache, 關閉; r0 = 0, r1 = machine nr, r2 = atags prointer.

      前面說過解壓以後,程式碼會跳到解壓完成以後的vmlinux開始執行,具體從什麼地方開始執行我們可以看看生成的vmlinux.lds(arch/arm/kernel/)這個檔案:

[cpp]view plaincopyprint?
  1. 1. OUTPUT_ARCH(arm)   
  2. 2. ENTRY(stext)   
  3. 3. jiffies = jiffies_64;   
  4. 4. SECTIONS   
  5. 5. {   
  6. 6.  . = 0x80000000 + 0x00008000;   
  7. 7.  .text.head : {    
  8. 8.   _stext = .;   
  9. 9.   _sinittext = .;   
  10. 0.   *(.text.h   
   1. OUTPUT_ARCH(arm)  
   2. ENTRY(stext)  
   3. jiffies = jiffies_64;  
   4. SECTIONS  
   5. {  
   6.  . = 0x80000000 + 0x00008000;  
   7.  .text.head : {   
   8.   _stext = .;  
   9.   _sinittext = .;  
  10.   *(.text.h  

很明顯我們的vmlinx最開頭的section是.text.head,這裡我們不能看ENTRY的內容,以為這時候我們沒有作業系統,根本不知道如何來解析這裡的入口地址,我們只能來分析他的section(不過一般來說這裡的ENTRY和我們從seciton分析的結果是一樣的),這裡的.text.head section我們很容易就能在arch/arm/kernel/head.S裡面找到,而且它裡面的第一個符號就是我們的stext:

[cpp]view plaincopyprint?
  1. # .section ".text.head", "ax" 
  2. #  
  3. # ENTRY(stext)
  4. #  
  5. #  /* 設定CPU執行模式為SVC,並關中斷 */ 
  6. #  
  7. #   msr  cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode 
  8. #  
  9. #                                      @ and irqs disabled 
  10. #  
  11. #   mrc p15, 0, r9, c0, c0        @ get processor id 
  12. #  
  13. #   bl    __lookup_processor_type         @ r5=procinfo r9=cupid 
  14. #  
  15. # /* r10指向cpu對應的proc_info記錄 */ 
  16. #  
  17. #    movs  r10, r5                         @ invalid processor (r5=0)? 
  18. #  
  19. #   beq __error_p                    @ yes, error 'p' 
  20. #  
  21. #   bl    __lookup_machine_type            @ r5=machinfo 
  22. #  
  23. # /* r8 指向開發板對應的arch_info記錄 */ 
  24. #  
  25. #    movs  r8, r5                           @ invalid machine (r5=0)? 
  26. #  
  27. #   beq __error_a                    @ yes, error 'a' 
  28. #  
  29. # /* __vet_atags函式涉及bootloader造知kernel實體記憶體的情況,我們暫時不分析它。 */ 
  30. #  
  31. #   bl    __vet_atags 
  32. #  
  33. # /*  建立臨時頁表 */ 
  34. #  
  35. #   bl    __create_page_tables 
  36. #   /*
  37. #    * The following calls CPU specific code in a position independent
  38. #    * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
  39. #    * xxx_proc_info structure selected by __lookup_machine_type
  40. #    * above.  On return, the CPU will be ready for the MMU to be
  41. #    * turned on, and r0 will hold the CPU control register value.
  42. #    */ 
  43. #  
  44. #  /* 這裡的邏輯關係相當複雜,先是從proc_info結構中的中跳進__arm920_setup函式,
  45. #   * 然後執__enable_mmu 函式。最後在__enable_mmu函式通過mov pc, r13來執行__switch_data,
  46. #   * __switch_data函式在最後一條語句,魚躍龍門,跳進第一個C語言函式start_kernel。
  47. #    */ 
  48. #  
  49. #   ldr   r13, __switch_data             @ address to jump to after 
  50. #  
  51. #                                      @ mmu has been enabled 
  52. #  
  53. #   adr  lr, __enable_mmu        @ return (PIC) address 
  54. #  
  55. #   add pc, r10, #PROCINFO_INITFUNC 
  56. #  
  57. # ENDPROC(stext)
# .section ".text.head", "ax"  
#   
# ENTRY(stext) 
#   
#  /* 設定CPU執行模式為SVC,並關中斷 */  
#   
#   msr  cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode  
#   
#                                      @ and irqs disabled  
#   
#   mrc p15, 0, r9, c0, c0        @ get processor id  
#   
#   bl    __lookup_processor_type         @ r5=procinfo r9=cupid  
#   
# /* r10指向cpu對應的proc_info記錄 */  
#   
#    movs  r10, r5                         @ invalid processor (r5=0)?  
#   
#   beq __error_p                    @ yes, error 'p'  
#   
#   bl    __lookup_machine_type            @ r5=machinfo  
#   
# /* r8 指向開發板對應的arch_info記錄 */  
#   
#    movs  r8, r5                           @ invalid machine (r5=0)?  
#   
#   beq __error_a                    @ yes, error 'a'  
#   
# /* __vet_atags函式涉及bootloader造知kernel實體記憶體的情況,我們暫時不分析它。 */  
#   
#   bl    __vet_atags  
#   
# /*  建立臨時頁表 */  
#   
#   bl    __create_page_tables  
  
#   /* 
#  
#    * The following calls CPU specific code in a position independent 
#  
#    * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of 
#  
#    * xxx_proc_info structure selected by __lookup_machine_type 
#  
#    * above.  On return, the CPU will be ready for the MMU to be 
#  
#    * turned on, and r0 will hold the CPU control register value. 
#  
#    */  
#   
#  /* 這裡的邏輯關係相當複雜,先是從proc_info結構中的中跳進__arm920_setup函式, 
#  
#   * 然後執__enable_mmu 函式。最後在__enable_mmu函式通過mov pc, r13來執行__switch_data, 
#  
#   * __switch_data函式在最後一條語句,魚躍龍門,跳進第一個C語言函式start_kernel。 
#    */  
#   
#   ldr   r13, __switch_data             @ address to jump to after  
#   
#                                      @ mmu has been enabled  
#   
#   adr  lr, __enable_mmu        @ return (PIC) address  
#   
#   add pc, r10, #PROCINFO_INITFUNC  
#   
# ENDPROC(stext)

這裡的ENTRY這個巨集實際我們可以在include/linux/linkage.h裡面找到,可以看到他實際上就是宣告一個GLOBAL Symbol,後面的ENDPROC和END唯一的區別是前面的聲明瞭一個函式,可以在c裡面被呼叫。

[cpp]view plaincopyprint?
  1. 1. #ifndef ENTRY   
  2. 2. #define ENTRY(name) /   
  3. 3.   .globl name; /   
  4. 4.   ALIGN; /   
  5. 5.   name:   
  6. 6. #endif   
  7. 7. #ifndef WEAK   
  8. 8. #define WEAK(name)     /   
  9. 9.     .weak name;    /   
  10. 10.     name:   
  11. 11. #endif   
  12. 12. #ifndef END   
  13. 13. #define END(name) /   
  14. 14.   .size name, .-name   
  15. 15. #endif   
  16. 16. /* If symbol 'name' is treated as a subroutine (gets called, and returns)
  17. 17.  * then please use ENDPROC to mark 'name' as STT_FUNC for the benefit of
  18. 18.  * static analysis tools such as stack depth analyzer.
  19. 19.  */
  20. 20. #ifndef ENDPROC   
  21. 21. #define ENDPROC(name) /   
  22. 22.   .type name, @function; /   
  23. 23.   END(name)   
  24. 24. #endif   
   1. #ifndef ENTRY  
   2. #define ENTRY(name) /  
   3.   .globl name; /  
   4.   ALIGN; /  
   5.   name:  
   6. #endif  
   7. #ifndef WEAK  
   8. #define WEAK(name)     /  
   9.     .weak name;    /  
  10.     name:  
  11. #endif  
  12. #ifndef END  
  13. #define END(name) /  
  14.   .size name, .-name  
  15. #endif  
  16. /* If symbol 'name' is treated as a subroutine (gets called, and returns) 
  17.  * then please use ENDPROC to mark 'name' as STT_FUNC for the benefit of 
  18.  * static analysis tools such as stack depth analyzer. 
  19.  */  
  20. #ifndef ENDPROC  
  21. #define ENDPROC(name) /  
  22.   .type name, @function; /  
  23.   END(name)  
  24. #endif  


找到了vmlinux的起始程式碼我們就來進行分析了,先總體概括一下這部分程式碼所完成的功能,head.S會首先檢查proc和arch以及atag的有效性,然後會建立初始化頁表,並進行CPU必要的處理以後開啟MMU,並跳轉到start_kernel這個symbol開始執行後面的C程式碼。這裡有很多變數都是我們進行kernel移植時需要特別注意的,下面會一一講到。

      在這裡我們首先看看這段彙編開始跑的時候的暫存器資訊,這裡的暫存器內容實際上是同bootloader跳轉到解壓程式碼是一樣的,就是r1=arch  r2=atag addr。下面我們就具體來看看這個head.S跑的過程:

[cpp]view plaincopyprint?
  1. 1. msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode   
  2. 2.                     @ and irqs disabled   
  3. 3. mrc p15, 0, r9, c0, c0      @ get processor id   
   1. msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode  
   2.                     @ and irqs disabled  
   3. mrc p15, 0, r9, c0, c0      @ get processor id  

首先進入SVC模式並關閉所有中斷,並從arm協處理器裡面讀到CPU ID,這裡的CPU主要是指arm架構相關的CPU型號,比如ARM9,ARM11等等。

3 __lookup_processor_type 函式

然後跳轉到__lookup_processor_type,這個函式定義在head-common.S裡面,這裡的bl指令會儲存當前的pc在lr裡面

__lookup_processor_type 函式是一個非常講究技巧的函式,如果你將它領會,也將領會kernel了一些魔法。

Kernel 程式碼將所有CPU資訊的定義都放到.proc.info.init段中,因此可以認為.proc.info.init段就是一個數組,每個元素都定義了一個或一種CPU的資訊。目前__lookup_processor_type使用該元素的前兩個欄位cpuid和mask來匹配當前CPUID,如果滿足 CPUID & mask == cpuid,則找到當前cpu的定義並返回。

下面是tqs3c2440開發板,CPU的定義資訊,cpuid = 0x41009200,mask = 0xff00fff0。如果是碼是執行在tqs3c2440開發板上,那麼函式返回下面的定義:

最後__lookup_processor_type會從這個函式返回,我們具體看看這個函式: 

[cpp]view plaincopyprint?
  1. # __lookup_processor_type: 
  2. #       /* adr 是相對定址,它的尋計算結果是將當前PC值加上3f符號與PC的偏移量,
  3. #        * 而PC是實體地址,因此r3的結果也是3f符號的實體地址 */ 
  4. #  
  5. #        adr  r3, 3f 
  6. #  
  7. #       /* r5值為__proc_info_bein, r6值為__proc_ino_end,而r7值為.,
  8. #        * 也即3f符號的連結地址。請注意,在連結期間,__proc_info_begin和
  9. #        * __proc_info_end以及.均是連結地址,也即虛執地址。
  10. #        */ 
  11. #  
  12. #        ldmda     r3, {r5 - r7} 
  13. #  
  14. #      /* r3為3f的實體地址,而r7為3f的虛擬地址。結果是r3為虛擬地址與實體地址的差值,即PHYS_OFFSET - PAGE_OFFSET。*/ 
  15. #  
  16. #        sub  r3, r3, r7                     @ get offset between virt&phys 
  17. #  
  18. #      /* r5為__proc_info_begin的實體地址, 即r5指標__proc_info陣列的首地址 */ 
  19. #  
  20. #        add r5, r5, r3                     @ convert v