板子:qq2440
內核:2.6.24
BootLoader在引導啓動內核的時候需要設置3個寄存器
R0 – 0
R1 – 板子的ID號
R2 – 內核的參數鏈表地址,也就是TAG鏈表
注意:查看代碼前要清楚連接腳本,arm彙編,linux gcc彙編的知識,硬件相關的最好查看芯片手冊
內核在編譯之後會進行再連接,連接的腳本在/arch/arm/kernel/vmlinux.lds.S中
SECTIONS { #ifdef CONFIG_XIP_KERNEL . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR); #else . = PAGE_OFFSET + TEXT_OFFSET; #endif .text.head : { _stext = .; _sinittext = .; *(.text.head) }
.init : { /* Init code and data */ *(.init.text) _einittext = .; __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .; __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .; __tagtable_begin = .; *(.taglist.init) __tagtable_end = .; . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; __early_begin = .; *(.early_param.init) __early_end = .; __initcall_start = .; INITCALLS __initcall_end = .; __con_initcall_start = .; *(.con_initcall.init)
|
PAGE_OFFSET爲0xC000 0000 是內核空間的虛擬地址起始處
TEXT_OFFSET 爲0x8000 是相對於內核空間的代碼段起始處偏移值
這裏PAGE_OFFSET + TEXT_OFFSET也就是內核代碼段起始處的虛擬地址,爲0xC000 8000
而在這個地址的代碼爲_stext
_stext在/arch/arm/kernel/head.S中
.section ".text.head", "ax" .type stext, %function ENTRY(stext)
//設置cpu運行在svc模式 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled
//獲取硬件的cpu id 可以查看cp15寫處理器幫助得到跟詳細的幫助 mrc p15, 0, r9, c0, c0 @ get processor id
//調用查看處理器類型的函數(對比編譯的內核是否支持此cpu類型) bl __lookup_processor_type @ r5=procinfo r9=cpuid
//下面是出錯判斷,如果返回值r5爲0,表示出錯,調用相應的出錯處理 movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p'
//如果沒有出錯,調用函數,查看開發板類型 bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a'
//檢查tag鏈表 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. */
//把switch_data的地址讀入r13;等調用完__enable_mmu函數後調用的 ldr r13, __switch_data @ address to jump to after @ mmu has been enabled
//啓用mmu支持 adr lr, __enable_mmu @ return (PIC) address add pc, r10, #PROCINFO_INITFUNC
|
首先是__lookup_processor_type,它負責尋找處理器ID號對應的proc_info_list結構
__lookup_processor_type在arch/arm/kernel/head-common.S中
.type __lookup_processor_type, %function __lookup_processor_type: //讀取下面標號3處的地址到R3中,這個r3是物理地址 adr r3, 3f //將標號3處地址的內容裝載到R5-R7中,這些都是虛擬地址 //R7 - . //R6 - __proc_info_end //R5 - __proc_info_begin ldmda r3, {r5 - r7} //計算物理地址和虛擬地址之間的差值,這個是負值 sub r3, r3, r7 // get offset between virt&phys //補償差值 add r5, r5, r3 // convert virt addresses to //補償差值 add r6, r6, r3 // physical address space //讀取proc_info_list結構中的內容到R3和R4 //R3 -cpu_val //R4 -cpu_mask 1: ldmia r5, {r3, r4} // value, mask //用R4與上R9,只關注需要的位 and r4, r4, r9 // mask wanted bits //比較R3和R4是否相等 teq r3, r4 //相等則跳轉到下面標號2處 beq 2f //不等則取得下一個proc_info_list結構 add r5, r5, #PROC_INFO_SZ // sizeof(proc_info_list) //測試R5和R6是否相等,相等則說明proc_info_list結構歷遍完畢 cmp r5, r6 //R5和R6不等則跳轉到上面的標號1處 blo 1b //R5和R6相等則將R5設置爲0 mov r5, #0 // unknown processor //將LR寄存器中的值賦給PC
//下面這些僞代碼有鏈接腳本填充 2: mov pc, lr .long __proc_info_begin .long __proc_info_end 3: .long . .long __arch_info_begin .long __arch_info_end
|
由於剛進入引導程序,這個時候MMU還有沒開啓,所以需要手工計算虛擬地址和物理地址之間的差值
__proc_info_begin和__proc_info_end在/arch/arm/kernel/vmlinux.lds.S的連接腳本中,用於標註proc_info_list結構的起始和結束地址
這裏處理器爲Arm920T,所以對應的proc_info_list結構在/arch/arm/mm/proc-arm920.S中
執行完畢後回到stext,來到__lookup_machine_type,它負責尋找板子ID號對應的machine_desc結構
__lookup_machine_type在arch/arm/kernel/head-common.S中
.long __proc_info_begin .long __proc_info_end 3: .long . .long __arch_info_begin .long __arch_info_end .type __lookup_machine_type, %function __lookup_machine_type: //將上面標號3處的地址賦給R3 adr r3, 3b //讀取R3中的內容到R4-R6 //R4 - . //R5 - __arch_info_begin //R6 - __arch_info_end ldmia r3, {r4, r5, r6} //計算物理地址和虛擬地址之間的差值 sub r3, r3, r4 // get offset between virt&phys //補償差值 add r5, r5, r3 // convert virt addresses to //補償差值 add r6, r6, r3 // physical address space //讀取R5所指的machine_desc結構中的machinfo_type成員到R3中 1: ldr r3, [r5, #MACHINFO_TYPE] // get machine type //比較R3和R1是否相等 teq r3, r1 // matches loader number? //相等則跳轉到下面的標號2處 beq 2f // found //不等則將R5指向下一個machine_desc結構 add r5, r5, #SIZEOF_MACHINE_DESC // next machine_desc //檢測R5和R6是否相等 cmp r5, r6 //不等則跳轉到上面的標號1處 blo 1b //相等則將R5賦爲0 mov r5, #0 // unknown machine //將LR寄存器中的值賦給PC 2: mov pc, lr
|
__arch_info_begin和__arch_info_end在/arch/arm/kernel/vmlinux.lds.S的連接腳本中,用於標註machine_desc結構的起始和結束地址
這裏板子ID號對應的machine_desc結構在/arch/arm/mach-s3c2440/qq2440.c中
MACHINE_START(QQ2440, "Friendly-ARM QQ2440 Development Board") /* Maintainer: Kasim Ling <[email protected]> */ .phys_io = S3C2410_PA_UART, .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, .boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = qq2440_init_irq, .map_io = qq2440_map_io, .init_machine = qq2440_init, .timer = &s3c24xx_timer, MACHINE_END
|
MACHINE_START和MACHINE_END都是宏,在/include/asm/mach/arch.h中
#define MACHINE_START(_type,_name) \ static const struct machine_desc __mach_desc_##_type \ __attribute_used__ \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = MACH_TYPE_##_type, \ .name = _name,
#define MACHINE_END \ };
|
這裏machine_desc結構所在的文件是由用戶自己編寫的執行完畢後就回到stext,來到__create_page_tables,它負責執行第一階段,也就是內核引導階段所要使用的分頁初始化
__create_page_tables在/arch/arm/kernel/head.S中
.type __create_page_tables, %function __create_page_tables: // .macro pgtbl, rd // ldr \rd, =(__virt_to_phys(KERNEL_RAM_ADDR - 0x4000)) // .endm //#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET) //PAGE_OFFSET = 0xC000 0000 //PHYS_OFFSET = 0x3000 0000 //#define KERNEL_RAM_ADDR (PAGE_OFFSET + TEXT_OFFSET) //PAGE_OFFSET = 0xC000 0000 //TEXT_OFFSET = 0x8000 //R4 = 0x3000 4000 pgtbl r4 // page table address /* * Clear the 16K level 1 swapper page table */ //將R4的值賦給R0 mov r0, r4 //將R3設爲0 mov r3, #0 //R6 = R0 + 0x4000 //R6 = 0x3000 8000 add r6, r0, #0x4000 //將0x30004000 - 0x30008000區域的值清零 //將R3的值賦給R0所指的地址,並且R0的值自加4 1: str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 //當R0 = 0x30008000時初始化完畢 teq r0, r6 //R0未到達0x30008000時則返回上面的標號1處繼續初始化 bne 1b //讀取proc_info_list結構中的__cpu_mm_mmu_flags成員到R7中 //這個值爲0xC1D ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] // mm_mmuflags /* * Create identity mapping for first MB of kernel to * cater for the MMU enable. This identity mapping * will be removed by paging_init(). We use our current program * counter to determine corresponding section base address. */ //將PC寄存器的值向右移20位,取得高12位賦給R6 //這裏R6爲0x300,因爲將內核解壓到了物理地址0x3000 8000,則PC的最高12位爲0x300 mov r6, pc, lsr #20 // start of kernel section //將R6的值向左移20位後或上R7保存在R3中 orr r3, r7, r6, lsl #20 // flags + kernel base //[0x3000 4000] = 0x3000 0C1D str r3, [r4, r6, lsl #2] // identity mapping /* * Now setup the pagetables for our kernel direct * mapped region. We round TEXTADDR down to the * nearest megabyte boundary. It is assumed that * the kernel fits within 4 contigous 1MB sections. */ //PAGE_OFFSET = 0xC000 0000 //TEXT_OFFSET = 0x8000 //#define KERNEL_RAM_ADDR (PAGE_OFFSET + TEXT_OFFSET) //#define TEXTADDR KERNEL_RAM_ADDR add r0, r4, #(TEXTADDR & 0xff000000) >> 18 // start of kernel //[0x3000 7000] = 0x3000 0C1D str r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]! add r3, r3, #1 << 20 //[0x3000 7004] = 0x3010 0C1D str r3, [r0, #4]! // KERNEL + 1MB add r3, r3, #1 << 20 //[0x3000 7008] = 0x3020 0C1D str r3, [r0, #4]! // KERNEL + 2MB add r3, r3, #1 << 20 //[0x3000 700C] = 0x3030 0C1D str r3, [r0, #4] // KERNEL + 3MB /* * Then map first 1MB of ram in case it contains our boot params. */ //R0 = 0x3000 4000 + 0x3000 add r0, r4, #PAGE_OFFSET >> 18 //R6 = R7 | 0x3000 0000 orr r6, r7, #PHYS_OFFSET //[0x3000 7000] = 0x3000 0C1D str r6, [r0] mov pc, lr
|
上面代碼中還有一部分宏判斷語句,因爲這裏不會執行,我就不貼出來了
PAGE_OFFSET爲0xC000 0000 是內核空間的虛擬地址起始處
TEXT_OFFSET 爲0x8000 是相對於內核空間的代碼段起始處偏移值
PHYS_OFFSET 爲0x3000 0000 是RAM所在的BANK物理地址的起始處
這是我的板子上的設置,因爲RAM是接在了BANK6上,而BANK6的起始地址爲0x3000 0000,所以PHYS_OFFSET 爲0x3000 0000
小結一下,這裏將物理地址0x3000 4000 – 0x3000 8000處的內容全部清0
然後設置了以下地址的描述符
[0x3000 4000] = 0x3000 0C1D
[0x3000 7000] = 0x3000 0C1D
[0x3000 7004] = 0x3010 0C1D
[0x3000 7008] = 0x3020 0C1D
[0x3000 700C] = 0x3030 0C1D
__create_page_tables執行完後回到stext中,接下來是以下3步
//將__switch_data處的地址賦給R13
ldr r13, __switch_data
//將__enable_mmu處的地址賦給LR寄存器
adr lr, __enable_mmu
//將proc_info_list結構中的__cpu_flush成員的值賦給pc
//也就是跳轉到__cpu_flush中執行
add pc, r10, #PROCINFO_INITFUNC
__enable_mmu和__switch_data等用到的時候再說
現在先來看看add pc, r10, #PROCINFO_INITFUNC
R10在之前指向了ARM920所對應的proc_info_list結構
這個結構在/arch/arm/mm/proc-arm920.S中
結構如下:
__arm920_proc_info: //cpu_val .long 0x41009200 //cpu_mask .long 0xff00fff0 //__cpu_mm_mmu_flags .long PMD_TYPE_SECT | \ PMD_SECT_BUFFERABLE | \ PMD_SECT_CACHEABLE | \ PMD_BIT4 | \ PMD_SECT_AP_WRITE | \ PMD_SECT_AP_READ //__cpu_io_mmu_flags .long PMD_TYPE_SECT | \ PMD_BIT4 | \ PMD_SECT_AP_WRITE | \ PMD_SECT_AP_READ //__cpu_flush b __arm920_setup //arch_name .long cpu_arch_name //elf_name .long cpu_elf_name //elf_hwcap .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB //cpu_name .long cpu_arm920_name //proc .long arm920_processor_functions //tlb .long v4wbi_tlb_fns //user .long v4wb_user_fns //cache #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH .long arm920_cache_fns #else .long v4wt_cache_fns
|
對應的結構聲明在/include/asm-arm/procinfo.h中
這裏PROCINFO_INITFUNC取的是__cpu_flush,也就是__arm920_setup
__arm920_setup在/arch/arm/mm/proc-arm920.S中,如下:
__INIT .type __arm920_setup, #function __arm920_setup: mov r0, #0 //Invalidate ICache and DCache SBZ MCR p15,0,Rd,c7,c7,0 mcr p15, 0, r0, c7, c7 // invalidate I,D caches on v4 //Drain write buffer SBZ MCR p15,0,Rd,c7,c10,4 //Stops execution until the write buffer has drained. mcr p15, 0, r0, c7, c10, 4 // drain write buffer on v4 #ifdef CONFIG_MMU //Invalidate TLB(s) SBZ MCR p15,0,Rd,c8,c7,0 mcr p15, 0, r0, c8, c7 // invalidate I,D TLBs on v4 #endif //加載下面標號爲arm920_crval的地址 adr r5, arm920_crval //加載R5所指的地址內容到R5和R6中 //R5 - clear //當CONFIG_MMU爲真時 //R6 - mmuset //當CONFIG_MMU爲假時 //R6 - ucset ldmia r5, {r5, r6} //MRC p15, 0, Rd, c1, c0, 0 ; read control register //讀取控制寄存器信息到R0中 mrc p15, 0, r0, c1, c0 // get control register v4 //清除不需要的位 bic r0, r0, r5 //置需要的位爲真 orr r0, r0, r6 mov pc, lr .size __arm920_setup, . - __arm920_setup /* * R * .RVI ZFRS BLDP WCAM * ..11 0001 ..11 0101 * */ .type arm920_crval, #object arm920_crval: // .macro crval, clear, mmuset, ucset //#ifdef CONFIG_MMU // .word \clear // .word \mmuset //#else // .word \clear // .word \ucset //#endif // .endm crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
|
SBZ的意思爲0,這裏也就是需要的參數爲0,所以需要先把R0置0
crval是一個宏
.macro crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
.word \clear
.word \mmuset
#else
.word \clear
.word \ucset
#endif
.endm
當CONFIG_MMU爲真時則
arm920_crval:
.word 0x00003f3f
.word 0x00003135
爲假時則
arm920_crval:
.word 0x00003f3f
.word 0x00001130
最後執行mov pc, lr
在之前內核將LR設爲了__enable_mmu
__enable_mmu在/arch/arm/kernel/head.S中,如下
.type __enable_mmu, %function __enable_mmu: #ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, #CR_A #else bic r0, r0, #CR_A #endif #ifdef CONFIG_CPU_DCACHE_DISABLE bic r0, r0, #CR_C #endif #ifdef CONFIG_CPU_BPREDICT_DISABLE bic r0, r0, #CR_Z #endif #ifdef CONFIG_CPU_ICACHE_DISABLE bic r0, r0, #CR_I #endif //#define domain_val(dom,type) ((type) << (2*(dom))) // #define DOMAIN_KERNEL 2 //#define DOMAIN_TABLE 2 //#define DOMAIN_USER 1 //#define DOMAIN_IO 0 //#define DOMAIN_MANAGER 3 //#define DOMAIN_CLIENT 1 mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \ domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \ domain_val(DOMAIN_IO, DOMAIN_CLIENT)) //MCR p15, 0, Rd, c3, c0, 0 ; write domain 15:0 access permissions mcr p15, 0, r5, c3, c0, 0 // load domain access register //MCR p15, 0, Rd, c2, c0, 0 ; write TTB register //填寫基地址 //R4在之前設置爲了0x3000 4000 mcr p15, 0, r4, c2, c0, 0 // load page table pointer b __turn_mmu_on
|
主要就是填寫了節基地址寄存器,設置基地址爲0x3000 4000
然後轉到__turn_mmu_on
__turn_mmu_on也在/arch/arm/kernel/head.S中,如下
__turn_mmu_on: mov r0, r0 //MCR p15, 0, Rd, c1, c0, 0 ; write control register mcr p15, 0, r0, c1, c0, 0 // write control reg //MRC p15,0,Rd,c0,c0,0 ; returns ID register mrc p15, 0, r3, c0, c0, 0 // read id reg mov r3, r3 mov r3, r3 mov pc, r13
|
ARM9是5級流水線,分別爲
1. 取指
2. 譯碼
3. 執行
4. 緩衝
5. 回寫
第一步mov r0, r0和之前的b __turn_mmu_on一起考慮
在之前的mcr p15, 0, r4, c2, c0, 0 的指令中會裝載節基地址,但是這個時候只是取指,到執行還需要2個指令週期, b __turn_mmu_on是第一個指令週期,所以還需要mov r0, r0做第二個指令週期來讓mcr p15, 0, r4, c2, c0, 0得以真正的執行
下面的mov r3, r3同理
最後mov pc, r13
R13在之前設置爲__switch_data
__switch_data在/arch/arm/kernel/head-common.S中,如下:
.type __switch_data, %object __switch_data: .long __mmap_switched .long __data_loc // r4 .long __data_start // r5 .long __bss_start // r6 .long _end // r7 .long processor_id // r4 .long __machine_arch_type // r5 .long cr_alignment // r6 .long init_thread_union + THREAD_START_SP // sp
|
R13中就是__mmap_switched的地址, mov pc, r13等於去執行__mmap_switched所指的指令
__mmap_switched在/arch/arm/kernel/head-common.S中,如下:
.type __mmap_switched, %function __mmap_switched: //加載__switch_data+4處的地址給R3 //也就是__data_loc的地址 adr r3, __switch_data + 4 //加載R3處的內容給R4-R7 //並且將地址回寫到R3,最後R3指向processor_id //R4 - __data_loc 數據存放的位置 //R5 - __data_start 數據開始的位置 //R6 - __bss_start BSS段開始的位置 //R7 - _end BSS段結束位位置,也是內核結束的位置 ldmia {r4, r5, r6, r7} //檢測__data_loc和__data_start是否相等 cmp r4, r5 // Copy data segment if needed //不等則執行拷貝 //將__data_loc開始處的內容拷貝到__data_start開始的位置 1: cmpne r5, r6 ldrne fp, [r4], #4 strne fp, [r5], #4 bne 1b //將FP指針置0 mov fp, #0 // Clear BSS (and zero fp) //將__bss_start到_end中的內容清0 1: cmp r6, r7 strcc fp, [r6],#4 bcc 1b //加載R3處的內容給R4-R6,SP //R4 - processor_id //R5 - __machine_arch_type //R6 - cr_alignment //SP - init_thread_union + THREAD_START_SP ldmia r3, {r4, r5, r6, sp} //將R9中的值保存到processor_id //也就是保存處理器ID號 str r9, [r4] // Save processor ID //將R1中的值保存到__machine_arch_type //也就是保存板子的ID號 str r1, [r5] // Save machine type //清除R0中的A位後保存到R4中 bic r4, r0, #CR_A // Clear 'A' bit //將R0和R4中的值保存到R6所指的地址 //R6所指的地址在arch/arm/kernel/entry-armv.S // .globl cr_alignment // .globl cr_no_alignment //cr_alignment: // .space 4 //cr_no_alignment: // .space 4 //cr_alignment <-R0 //cr_no_alignment <-R4 stmia r6, {r0, r4} // Save control register values //進入到start_kernel b start_kernel
|
註釋都有了~ 最後就是跳轉到start_kernel,進行第二階段,也就是內核的初始化
下面對ARM的分頁進行一下介紹
ARM的分頁分爲兩層,第一層爲必選,稱爲分節,將內存分爲每個1MB的區域,第二層爲可選,是將第一層中的1MB區域再進行劃分成1KB,4KB或者64KB大小的頁
引導啓動中只使用了第一層分節,未使用第二層分頁,下圖描述了分節的取址
上圖中的RS爲系統使用的屬性~ 我這裏就不介紹了~
分節取址主要分成了2步,我這裏以虛擬地址0xC000 8000介紹之前分頁初始化進行的設置:
1. 取得節描述符,使用節基地址寄存器中的節基地址與虛擬地址中的節索引進行組合,這裏節基地址爲0x3000 4000 它的31-14位爲0011 0000 0000 0000 01 ,虛擬地址爲0xC000 8000,所以節索引爲1100 0000 0000 ,組合得 0011 0000 0000 0000 0111 0000 0000 0000 ,也就是0x3000 7000,取物理地址0x3000 7000處的節描述符
2. 取得物理地址,使用節描述符中的節物理基地址和虛擬地址中的節偏移進行組合,這裏0x3000 7000處的節描述符爲0x3000 0C1D,則其節物理基地址爲0011 0000 0000,虛擬地址爲0xC000 8000,所以節偏移爲0000 1000 0000 0000 0000,與節物理基地址進行組合,得0011 0000 0000 0000 1000 0000 0000,也就是0x3000 8000
虛擬地址0xC000 8000也就是物理地址0x3000 8000