* linux/arch/arm/kernel/head.S
* Kernel startup code for all 32-bit CPUs
*/
/* 內核啓動入口點
* Kernel startup entry point.
* 這裏通常在解壓後直接調用。
* 處理器基本狀態要求:
* MMU關閉,D-cach關閉,I-cache不用關係;
* r0 = 0,r1 = 系統號(machine number)
* 這段代碼幾乎是位置無關的。
* 如果鏈接內核在0xc0008000,調用的地址爲相應的物理地址__pa(0xc0008000)。
* r1的系統號參考arch/arm/tools/mach-types文件的列表。
* 儘量不要在這裏添加系統號相關的代碼,那應該放在bootloader的代碼中。
* 保持這裏的代碼的整潔。
*/
__INIT
.type stext, %function
/*――――――――――――――――――――――――――――――――――――――――――――――
這個地方就是 kernel 的入口點
―――――――――――――――――――――――――――――――――――――――――――――― */
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | MODE_SVC @ ensure svc mode
@ and irqs disabled
/*――――――――――――――――――――――――――――――――――――――――――――――
調用 __lookup_processor_type 檢查現在運行的 cpu 的 ID 值和 linux 編譯支持的
id 值是否相等。
――――――――――――――――――――――――――――――――――――――――――――――*/
bl __lookup_processor_type @ r5=procinfo r9=cpuid
/*――――――――――――――――――――――――――――――――――――――――――――――
從該函數返回後,寄存器內容如下:
R9 = cpu ID
R5 = pointer to processor structure
詳細的內容請看__lookup_processor_type 的分析 */
.type __lookup_processor_type, %function
__lookup_processor_type:
/*――――――――――――――――――――――――――――――――――――――――――――――
把標號 2 的地址送給 r3, 3f = lable 3 forward
―――――――――――――――――――――――――――――――――――――――――――――― */
adr r3, 3f
/*――――――――――――――――――――――――――――――――――――――――――――――
把 r3 指向內存的地址的內容賦值給 r5,r6,r9
所以,參照標號 3處的聲明,我們可以知道:
__proc_info_begin r5
__proc_info_end r6
3b r9
__proc_info_end 和 __proc_info_begin 這兩個標號都是在
/linux/arch/arm/vmlinux.ld 這個腳本中定義的。在連接的時候,ld 會把相應cpu信息
proc_info 放到這兩個標號之間。
__proc_info_begin = .;
*(.proc.info)
__proc_info_end = .;
――――――――――――――――――――――――――――――――――――――――――――――*/
ldmda r3, {r5, r6, r9} @ ldmda彈棧順序是從右到左,[r3]->r9,[r3-4]->r6,[r3-8]->r5
/*r3 = 標號 3 的加載地址地址,r9 = 標號 3 的連接地址 ,r3是根據pc值確定的,r9是鏈接階段就確定的是鏈接地址*/
sub r3, r3, r9 @ get offset between virt&phys
// r3 = 加載地址和連接地址的差值
//現在,r5 = __proc_info_begin 的加載地址,即在 RAM 中的地址
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
mrc p15, 0, r9, c0, c0 @ get processor id協處理器指令獲取cpu id號r9=0x41807202(sep4020)
/*――――――――――――――――――――――――――――――――――――――――――――――
在本例中, r5 = _arm720_proc_info 這 個 標記定義在
linux/arch/arm/mm/proc-arm720.S
__arm720_proc_info:
.long 0x41807200 r3 = cpu_value
.long 0xffffff00 r4 = cpu_mask
.long 0x00000c1e mmuflags,一級段描述符
b __arm720_setup
.
.
―――――――――――――――――――――――――――――――――――――――――――――― */
//ldmia彈棧順序是從左到右,[r5]->r3,[r5+4]->r4 ,即低地址的內容放到低編號的寄存器,高地址的內容放到高編號的寄存器,指令結束後r5的指依然爲_arm720_proc_info
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
//將r9屏上0xffffff00看sep4020是否是arm720t的內核
teq r3, r4
beq 2f @若是arm720t內核則直接跳轉到標籤2
/*――――――――――――――――――――――――――――――――――――――――――――――
proc_info_list 定義在 linux/include/asm-arm/procinfo.h
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mmu_flags; // used by head.S
unsigned long __cpu_flush; // used by head.S
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
每一項都是 4 個字節,所以 sizeof(proc_info_list) = 48 byte
――――――――――――――――――――――――――――――――――――――――――――――*/
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)=48
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
* This provides a C-API version of the above function.
*/
ENTRY(lookup_processor_type)
stmfd sp!, {r4 - r6, r9, lr}
bl __lookup_processor_type
mov r0, r5
ldmfd sp!, {r4 - r6, r9, pc}
* Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.long __proc_info_begin
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end
/*―――――――――――――從__lookup_processor_type返回――――――――――――――――――――――――――――――――― */
movs r10, r5 @ 是有效720t核嗎 (r5=0)?
beq __error_p @ yes, error 'p'
/*――――――――――――――――――――――――――――――――――――――――――――――
__lookup_machine_type 通過 R1 寄存器,判斷體系類型,R1 = machine
architecture number
―――――――――――――――――――――――――――――――――――――――――――――― */
bl __lookup_machine_type @ r5=machinfo
/*―――――――――――――――――――――――――――――――――――――――――――――― */
/* r1 = machine architecture number
* Returns:
* r3, r4, r6 corrupted
* r5 = mach_info pointer in physical address space
*/
.type __lookup_machine_type, %function
__lookup_machine_type:
adr r3, 3b
/*――――――――――――――――――――――――――――――――――――――――――――――
把 r3 指向內存的地址的內容賦值給 r4,r5,r6
所以,參照標號 3處的聲明,我們可以知道:
3b r4
__arch_info_begin r5
__arch_info_end r6
__arch_info_end 和 __arch_info_begin 這兩個標號都是在
/linux/arch/arm/vmlinux.ld 這個腳本中定義的。在連接的時候,ld 會把相應體系架構
arch_info 放到這兩個標號之間。
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
――――――――――――――――――――――――――――――――――――――――――――――*/
ldmia r3, {r4, r5, r6}
sub r3, r3, r4 @ get offset between virt&phys
//r5=__arch_info_begin的加載地址
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
/*――――――――――――――――――――――――――――――――――――――――――――――
__arch_info_begin 和__arch_info_end 的類型都是 struct machine_desc。其實
就是指向一個 machine_desc 結構首尾的兩個地址標號。
struct machine_desc 定義在 linux/include/asm-arm/mach/arch.h 中
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S
*/
unsigned int nr; /* architecture number */
unsigned int __deprecated phys_ram; /* start of physical ram */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
unsigned long boot_params; /* tagged list */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
而對於我們的SEP4020其真正的定義是在/arch/arm/mach-sep4020/4020.c中
MACHINE_START(GFD4020, "4020 board")
.phys_io = 0x10000000,
.io_pg_offst = ((0xe0000000) >> 18) & 0xfffc,
.boot_params = 0x30000100,
.fixup = fixup_gfd4020,
.map_io = sep4020_map_io,
.init_irq = sep4020_init_irq,
.init_machine = sep4020_init,
.timer = &sep4020_timer,
MACHINE_END
看到這裏,我們就不難明白下邊這條指令了,struct machine_desc 中第一個就是
nr,即 architecture number
r3 = MACH_TYPE_GFD4020
――――――――――――――――――――――――――――――――――――――――――――――*/
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type ,MACHINFO_TYPE = 0
//r1是由解壓縮程序/arch/arm/boot/compressed/head.S最後傳過來的,或者是uboot傳過來的體系結構號
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
/*―――――――――――――――――從__lookup_machine_type返回――――――――――――――――――――――――――――― */
beq __error_a @ yes, error 'a'
/*――――――――――――――――――――――――――――――――――――――――――――――
設置 mmu 之前,設置臨時內核頁表
―――――――――――――――――――――――――――――――――――――――――――――― */
bl __create_page_tables
/*――――――――――――――――――――――――――――――――――――――――――――――
/* 我們在這裏只映射內核啓動的臨時頁表
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* r8 = machinfo 體系結構信息
* r9 = cpuid cpu 的ID
* r10 = procinfo cpu信息
*
* Returns:
* r0, r3, r6, r7 corrupted
* r4 = physical page table address
*/
.type __create_page_tables, %function
__create_page_tables:
/*―――――――――――――――――――――――――――――――――――――――――――――― */
// Page offset: 3GB 內核頁表的偏移在/inculde/asm/memory.h
#define PAGE_OFFSET UL(0xc0000000)
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
#endif
而這其中的PHYS_OFFSET則是我們需要在我們的SEP4020的定義自己的主存ram的基址的物理地址,我們是在/include/asm-arm/arch-sep4020/memory.h中定義的
#define PHYS_OFFSET UL(0x30000000)
第90行有
textofs-y := 0x00008000
所以TEXT_OFFSET := 0x00008000
在153行有export TEXT_OFFSET將此變量輸出。*/
#define KERNEL_RAM_ADDR (PAGE_OFFSET + TEXT_OFFSET) @其中TEXT_OFFSET = 0x8000
//swapper_pg_dir是放啓動時的臨時頁表的頁表基址(虛地址)
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_ADDR - 0x4000
//這個宏就是根據內核ram首址(虛擬地址)計算出我們內核頁表的頁表基址(物理地址)
.macro pgtbl, rd
ldr \rd, =(__virt_to_phys(KERNEL_RAM_ADDR - 0x4000))
.endm
―――――――――――――――――――――――――――――――――――――――――――――― */
//這樣r4 = 內核頁表的頁表基址(物理地址)
/*
* Clear the 16K level 1 swapper page table
*/
mov r0, r4
mov r3, #0
//r6 = 內核的KERNEL_RAM_ADDR
add r6, r0, #0x4000
//首先對16k的一級頁表內容清0
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
//PROCINFO_MMUFLAGS = 8;這樣 r7 = 0x00000c1e mmuflags,一級段描述符 ,在proc-arm720.S 中定義
ldr r7, [r10, #PROCINFO_MMUFLAGS] @ 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.
這次一致映射主要是映射kernel前1MB的地址,這個映射最終會被後面paging_init()更新頁表
*/
//獲取當前程序的段地址 = r6
mov r6, pc, lsr #20 @ start of kernel section
//將段地址 或上mmuflags,然後賦值給r3
orr r3, r7, r6, lsl #20 @ flags + kernel base
//開始放內核代碼的段映射描述符,段表基址爲r4,段表內的索引爲r6<<2;
str r3, [r4, r6, lsl #2] @ identity mapping
//這樣做是爲了解決後面剛開MMU是防止pc值飛掉了,在__turn_mmu_on函數中
/*
* 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.
將kernel往後的4MB的地址建立虛實映射
*/
//#define TEXTADDR KERNEL_RAM_ADDR即等於0xc0008000
//這裏的TEXTADDR是0xc0008000段對應的代碼和前面的pc對應的段地址是同一代碼,這樣做是爲了解決後面剛開MMU是防止pc值飛掉了
add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel
//把start of kernel(0xc0008000)的虛擬地址映射起來,即將它的段描述符保存到段表的相應索引處位置
str r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!
//建立start of kernel+1MB的虛擬地址映射
add r3, r3, #1 << 20
str r3, [r0, #4]! @ KERNEL + 1MB
//建立start of kernel+2MB的虛擬地址映射
add r3, r3, #1 << 20
str r3, [r0, #4]! @ KERNEL + 2MB
//建立start of kernel+3MB的虛擬地址映射
add r3, r3, #1 << 20
str r3, [r0, #4] @ KERNEL + 3MB
* Then map first 1MB of ram in case it contains our boot params.
建立PAGE_OFFSET=0XC0000000地址的映射,因爲這個地址附近包含我們的uboot傳給linux的啓動參數
*/
add r0, r4, #PAGE_OFFSET >> 18
orr r6, r7, #PHYS_OFFSET
str r6, [r0]
―――――――――――――從__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 = __mmap_switched(函數指針)
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
//將__enable_mmu(這是個與地址無關代碼)賦值給lr,等會返回執行,模擬一個函數棧
adr lr, __enable_mmu @ return (PIC) address
/*********************************************************************************************************
//#define PROCINFO_INITFUNC 12,在這裏跳轉執行arm720t架構的相應初始化代碼
__arm720_proc_info:
.long 0x41807200 @ cpu_val
.long 0xffffff00 @ cpu_mask
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm720_setup @ cpu_flush
****************************************************************************************************/
add pc, r10, #PROCINFO_INITFUNC
/*********************************************************************************************************/
//轉到__arm720_setup函數來執行
.type __arm720_setup, #function
__arm720_setup:
mov r0, #0
//寫CP15的c7寄存器使cache的數據無效
mcr p15, 0, r0, c7, c7, 0 @ invalidate caches
//使整個TLB內部的地址變換條目無效
mcr p15, 0, r0, c8, c7, 0 @ flush TLB (v4)
//把CP15的寄存器c1傳給r0
mrc p15, 0, r0, c1, c0 @ get control register
/*********************************************************************************************************
arm720_cr1_clear,arm720_cr1_set宏的定義
.type arm710_cr1_clear, #object
.type arm710_cr1_set, #object
arm710_cr1_clear:
.word 0x0f3f
arm710_cr1_set:
.word 0x013d(mmu使能,禁用地址對齊,cache使能,寫緩衝使能,小印地安序,系統保護,rom不保護)
****************************************************************************************************/
ldr r5, arm720_cr1_clear
bic r0, r0, r5
ldr r5, arm720_cr1_set
//r0 = 0x013d(mmu使能,禁用地址對齊,cache使能,寫緩衝使能,小印地安序,系統保護,rom不保護)
orr r0, r0, r5
//跳轉到__enable_mmu,相當於執行函數__enable_mmu
mov pc, lr @ __ret (head.S)
.size __arm720_setup, . - __arm720_setup
/****************************************從__arm720_setup返回************************************************************/
/*********************************************************************************************************
/*
* Setup common bits before finally enabling the MMU. Essentially
* this is just loading the page table pointer and domain access
* registers.
*/
.type __enable_mmu, %function
__enable_mmu:
//#define CONFIG_ALIGNMENT_TRAP 1是在/include/linux/Autoconfig.h定義
//Autoconfig.h是在make時根據Kconfig文件產生的
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #CR_A @CR_A=1 地址對齊
#else
bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
bic r0, r0, #CR_C @CR_C= 2 D cache使能
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z @CR_Z = 11 分支預測
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I @CR_I = 12 I cache使能
#endif
//#define domain_val(dom,type) ((type) << (2*(dom)))在/include/asm-arm/Domain.h定義
//MMU中的域是一些段,大頁或小頁的集合,而在MMU的頁表描述符中有些位表示該描述符的域,指明該存儲空間所屬的域號0~15
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))
//配置cp15的域訪問控制寄存器
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
//配置cp15的頁表基址寄存器c2,r4就是頁表的首地址
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
* Enable the MMU. This completely changes the structure of the visible
* memory space. You will not be able to trace execution through this.
* If you have an enquiry about this, *please* check the linux-arm-kernel
* mailing list archives BEFORE sending another post to the list.
*
* r0 = cp #15 control register
* r13 = *virtual* address to jump to upon completion
* r13是開MMU後將執行的第一個函數
* other registers depend on the function called upon completion
開啓MMU,這時你看到的世界將徹底是虛擬世界了,
*/
.align 5
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0
//配置cp15的c1,開啓MMU
mcr p15, 0, r0, c1, c0, 0 @ write control reg
//因爲 ARM 720T 是三級流水線,所以運行三條指令,讓流水線充滿指令
//讀取id寄存器
mrc p15, 0, r3, c0, c0, 0 @ read id reg
//這時候剛開MMU的虛擬地址和實地址是一一映射,pc就不需要跳轉了
mov r3, r3
mov r3, r3
//跳轉到__mmap_switched函數執行
mov pc, r13
/****************************************從__enable_mmu返回************************************************************/
/******************************************__mmap_switched***************************************************************
/*
* The following fragment of code is executed with the MMU on, and uses
* absolute addresses; this is not position independent.
*這段代碼是在開啓MMU後執行的,用的是絕對地址,不是地址無關的代碼
* r0 = cp#15 control register
* r1 = machine ID 是Uboot傳過來的體系架構號
* r9 = processor ID CPU的id號
*/
.type __mmap_switched, %function
__mmap_switched:
//這是把__data_loc對應的地址賦值給r3
adr r3, __switch_data + 4
/*********************************************************************************************************
__switch_data這個數據塊就在其後定義的
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4,__data_loc ,__data_start,__bss_start都是在vmlinux.lds中定義的
.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
*********************************************************************************************************/
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
//把動態數據拷貝到全局數據區
1: cmpne r5, r6
ldrne fp, [r4], #4 @fp是幀指針,即r11
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4 @cc是無符號小於
bcc 1b
//將cpu id保存到r4中
str r9, [r4] @ Save processor ID
//將機器號保存到r5當中
str r1, [r5] @ Save machine type
/*********************************************************************************************************
cr_alignment是爲中斷向量表是放在高地址還是放在低地址服務的,在中斷分析中會見到的
.globl cr_alignment
.globl cr_no_alignment
cr_alignment:
.space 4
cr_no_alignment:
.space 4
//將MMU control寄存器分別保存到cr_alignment,和cr_no_alignment中
*********************************************************************************************************/
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
//進入偉大的start_kernel函數
b start_kernel
/****************************************從__enable_mmu返回************************************************************/
.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