Exynos4412 核心移植(三)—— 核心啟動過程分析
核心啟動所用函式如下:
與移植U-Boot 的過程相似,在移植Linux 之前,先了解它的啟動過程。Linux 的過程可以分為兩部分:架構/開發板相關的引導過程、後續的通用啟動過程。對於uImage、zImage ,它們首先進行自解壓得到vmlinux ,然後執行 vmlinux 開始“正常的”啟動流程。
引導階段通常使用匯編語言編寫,它首先檢查核心是否支援當前架構的處理器,然後檢查是否支援當前開發板。通過檢查後,就為呼叫下一階段的start_kernel函式作準備了。這主要分如下兩個步驟:
1)-- 連線核心時使用的虛擬地址,所以要設定頁表、使能MMU;
2)呼叫C 函式 start_kernel 之前的常規工作,包括複製資料段、清除BSS段、呼叫start_kernel 函式。
第二階段的關鍵程式碼主要使用C語言編寫。它進行核心初始化的全部工作,最後呼叫 rest_init 函式啟動init 過程,建立系統第一個程序:init 程序。在第二階段,仍有部分架構/開發板相關的程式碼,比如重新設定頁表、設定系統時鐘、初始化串列埠等。
下面是詳細解析:
一、第一階段
與Uboot 一樣,我們在連線檔案中檢視函式入口點,核心編譯完成後會在arch/arm/kernel/下生成 vmlinux.lds 檔案,開啟:
stext 在 linux/arch/arm/kernel/head.S 中被定義,做為函式入口點,linux/arch/arm/kernel/head.S是linux核心映像解壓後執行的第一個檔案
程式碼只是部分,但可以看到這一階段究竟做了些什麼:
a -- 設定為SVC模式,關閉IRQ、FIQ;
b -- 確定CPU的ID號,判定其是否有效;
c -- 確定machine的ID號,檢查合法性;
d -- 檢查bootloader傳入的引數列表atags的合法性
e -- 建立初始頁表
下面對上面遇到的程式段展開分析:
a -- 確保處於SVC模式
這沒什麼好講的,就是設定CPSR 模式位,並遮蔽中斷;
b -- 檢查CPU ID 是否匹配
獲取ID並放到 r9 暫存器中,呼叫_lookup_processor_type 函式, 函式主要用來判定核心是否和當前的CPU匹配,如果不匹配,r5暫存器的值應為0,此時會呼叫 _error_p函式,它用來列印錯誤資訊,即核心和當前的CPU不匹配,此時核心時不能啟動的
下面看_lookup_processor_type 函式,在arch/arm/kernel/head-common.S 中定義:
上面的程式碼其實就是一個地址轉換過程,因為在判定CPU架構時未開啟系統的MMU功能,所以均使用實體地址,而核心程式碼在連線時是以虛擬地址來實現的,因此要想用proc_info_list 結構體,就要先找到proc_info_list 結構的實體地址,這樣必須使用上面的轉換程式碼。
proc_info_list 結構體很重要。在Linux 核心映像中定義了很多個proc_info_list 結構,該結構表示的是核心所支援的CPU架構,這部分下面會講到,先分析上面的程式碼:
153 行:r3 儲存的是_lookup_processor_type_data 的實體地址 ;
155 行:得到虛擬地址和實體地址之間的offset;
156 - 157 行:利用offset,將 r5 和 r6 中儲存的虛擬地址轉變為實體地址,主要是獲得_proc_info_begin 及_proc_info_end 的實體地址,分別放到r5 和 r6 中;
159 行:r9 中存放的是先前讀出的 processor ID,此處遮蔽不需要的位;
160 行:檢視程式碼和CPU硬體是否匹配,如果匹配就返回,此時 r5 存放的是該CPU型別對應的結構體_proc_info_list 的基地址 ;不成功,則檢視下一個 proc_info_list 結構體;
163行:如果直到 _proc_info_end ,都沒有匹配,則定為未知CPU,向 r5 賦 0,然後返回 ;
下面來看看 proc_info_list 結構體 ,這個結構體在 arch/arm/include/asm/procinfo.h 中定義:
對於 Cortex-A9 來說,其結構體在檔案 arch/arm/mm/proc-v7.S 中初始化:
.section ".proc.info.init"表明了該結構在編譯後存放的位置。在連結檔案arch/arm/kernel/vmlinux.lds中:
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
上面兩個變數 _proc_info_begin 與 _proc_info_end 用於計算 proc_info_list 結構的實體地址。
如果CPU ID匹配,在編譯核心檔案時,會編譯 proc-v7.S 這個檔案,可以在arch/arm/mm/Makefile 中看到這個檔案
c -- 檢測 機器ID是否匹配
主要用到_lookup_machine_type 函式,其與_lookup_processor_type 函式實現程式碼很相似,這裡不予闡述;
d -- 檢查bootloader傳入的引數列表atags的合法性
_vet_atags 函式用於檢測引數列表atags的合法性
核心引數連結串列的格式和說明可以從核心原始碼目錄樹中的 中找到,引數連結串列必須以ATAG_CORE 開始,以ATAG_NONE結束。這裡的 ATAG_CORE,ATAG_NONE是各個引數的標記,本身是一個32位值,例如:ATAG_CORE=0x54410001。其它的引數標記還包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK ,ATAG_COMDLINE 等。每個引數標記就代表一個引數結構體,由各個引數結構體構成了引數連結串列。引數結構體的定義如下:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
struct tag_acorn acorn;
struct tag_memclk memclk;
} u;
};
引數結構體包括兩個部分,一個是 tag_header結構體,一個是u聯合體。
tag_header結構體的定義如下:
struct tag_header {
u32 size;
u32 tag;
};
其中 size:表示整個 tag 結構體的大小(用字的個數來表示,而不是位元組的個數),等於tag_header的大小加上 u聯合體的大小,例如,引數結構體 ATAG_CORE 的 size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通過函式 tag_size(struct * tag_xxx)來獲得每個引數結構體的 size。其中 tag:表示整個 tag 結構體的標記,如:ATAG_CORE等。
__vet_atags:
tst r2, #0x3 //r2指向該引數連結串列的起始位置,此處判斷它是否字對齊
bne 1f
ldr r5, [r2, #0] //獲取第一個tag結構的size
//#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 判斷該tag的長度是否合法
subs r5, r5, #ATAG_CORE_SIZE
bne 1f
ldr r5, [r2, #4] //獲取第一個tag結構體的標記,
ldr r6, =ATAG_CORE
cmp r5, r6 //判斷第一個tag結構體的標記是不是ATAG_CORE
bne 1f
mov pc, lr //正常退出
1: mov r2, #0
mov pc, lr //引數連表不正確
ENDPROC(__vet_atags)
e -- 建立初始頁表
其在下面被執行:
下面是詳細分析:
/*
* 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 = phys_offset, r9 = cpuid, r10 = procinfo
*
* Returns:
* r0, r3, r5-r7 corrupted
* r4 = page table (see ARCH_PGD_SHIFT in asm/memory.h)
*/
__create_page_tables:
pgtbl r4, r8 @ page table address
/*
* Clear the swapper page table
*/
mov r0, r4
mov r3, #0
add r6, r0, #PG_DIR_SIZE
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
#ifdef CONFIG_ARM_LPAE
/*
* Build the PGD table (first level) to point to the PMD table. A PGD
* entry is 64-bit wide.
*/
mov r0, r4
add r3, r4, #0x1000 @ first PMD table address
orr r3, r3, #3 @ PGD block type
mov r6, #4 @ PTRS_PER_PGD
mov r7, #1 << (55 - 32) @ L_PGD_SWAPPER
1:
#ifdef CONFIG_CPU_ENDIAN_BE8
str r7, [r0], #4 @ set top PGD entry bits
str r3, [r0], #4 @ set bottom PGD entry bits
#else
str r3, [r0], #4 @ set bottom PGD entry bits
str r7, [r0], #4 @ set top PGD entry bits
#endif
add r3, r3, #0x1000 @ next PMD table
subs r6, r6, #1
bne 1b
add r4, r4, #0x1000 @ point to the PMD tables
#ifdef CONFIG_CPU_ENDIAN_BE8
add r4, r4, #4 @ we only write the bottom word
#endif
#endif
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
/*
* Create identity mapping to cater for __enable_mmu.
* This identity mapping will be removed by paging_init().
*/
adr r0, __turn_mmu_on_loc
ldmia r0, {r3, r5, r6}
sub r0, r0, r3 @ virt->phys offset
add r5, r5, r0 @ phys __turn_mmu_on
add r6, r6, r0 @ phys __turn_mmu_on_end
mov r5, r5, lsr #SECTION_SHIFT
mov r6, r6, lsr #SECTION_SHIFT
1: orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base
str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping
cmp r5, r6
addlo r5, r5, #1 @ next section
blo 1b
/*
* Map our RAM from the start to the end of the kernel .bss section.
*/
add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
ldr r6, =(_end - 1)
orr r3, r8, r7
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1: str r3, [r0], #1 << PMD_ORDER
add r3, r3, #1 << SECTION_SHIFT
cmp r0, r6
bls 1b
#ifdef CONFIG_XIP_KERNEL
/*
* Map the kernel image separately as it is not located in RAM.
*/
#define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
mov r3, pc
mov r3, r3, lsr #SECTION_SHIFT
orr r3, r7, r3, lsl #SECTION_SHIFT
add r0, r4, #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
str r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
ldr r6, =(_edata_loc - 1)
add r0, r0, #1 << PMD_ORDER
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1: cmp r0, r6
add r3, r3, #1 << SECTION_SHIFT
strls r3, [r0], #1 << PMD_ORDER
bls 1b
#endif
/*
* Then map boot params address in r2 if specified.
* We map 2 sections in case the ATAGs/DTB crosses a section boundary.
*/
mov r0, r2, lsr #SECTION_SHIFT
movs r0, r0, lsl #SECTION_SHIFT
subne r3, r0, r8
addne r3, r3, #PAGE_OFFSET
addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
orrne r6, r7, r0
strne r6, [r3], #1 << PMD_ORDER
addne r6, r6, #1 << SECTION_SHIFT
strne r6, [r3]
#if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)
sub r4, r4, #4 @ Fixup page table pointer
@ for 64-bit descriptors
#endif
#ifdef CONFIG_DEBUG_LL
#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
/*
* Map in IO space for serial debugging.
* This allows debug messages to be output
* via a serial console before paging_init.
*/
addruart r7, r3, r0
mov r3, r3, lsr #SECTION_SHIFT
mov r3, r3, lsl #PMD_ORDER
add r0, r4, r3
mov r3, r7, lsr #SECTION_SHIFT
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
orr r3, r7, r3, lsl #SECTION_SHIFT
#ifdef CONFIG_ARM_LPAE
mov r7, #1 << (54 - 32) @ XN
#ifdef CONFIG_CPU_ENDIAN_BE8
str r7, [r0], #4
str r3, [r0], #4
#else
str r3, [r0], #4
str r7, [r0], #4
#endif
#else
orr r3, r3, #PMD_SECT_XN
str r3, [r0], #4
#endif
#else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */
/* we don't need any serial debugging mappings */
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
/*
* If we're using the NetWinder or CATS, we also need to map
* in the 16550-type serial port for the debug messages
*/
add r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)
orr r3, r7, #0x7c000000
str r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
/*
* Map in screen at 0x02000000 & SCREEN2_BASE
* Similar reasons here - for debug. This is
* only for Acorn RiscPC architectures.
*/
add r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)
orr r3, r7, #0x02000000
str r3, [r0]
add r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)
str r3, [r0]
#endif
#endif
#ifdef CONFIG_ARM_LPAE
sub r4, r4, #0x1000 @ point to the PGD table
mov r4, r4, lsr #ARCH_PGD_SHIFT
#endif
mov pc, lr
ENDPROC(__create_page_tables)
f -- 使能MMU,跳轉到start_kernel
檔案linux/arch/arm/kernel/head.S中
檔案linux/arch/arm/kernel/head.S中
在前面有過這樣的指令操作ldr r13, __switch_data ,
mov pc, r13 就是將跳轉到__switch_data處。
在檔案linux/arch/arm/kernel/head-common.S中:
.type __switch_data, %object //定義一個物件
__switch_data:
.long __mmap_switched //由此可知上面程式將跳轉到該程式段處。
.long __data_loc @ r4
.long _data @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
. = PAGE_OFFSET + TEXT_OFFSET;
#else
. = ALIGN(THREAD_SIZE);
__data_loc = .;
#endif
.data : AT(__data_loc) { //此處資料儲存在上面__data_loc處。
_data = .;
*(.data.init_task)
…………………………
.bss : {
__bss_start = .;
*(.bss)
*(COMMON)
_end = .;
}
………………………………
}
init_thread_union 是 init程序的基地址. 在 arch/arm/kernel/init_task.c 中:
00033: union thread_union init_thread_union
00034: __attribute__((__section__(".init.task"))) =
00035: { INIT_THREAD_INFO(init_task) };
對照 vmlnux.lds.S 中,我們可以知道init task是存放在 .data 段的開始8k, 並且是THREAD_SIZE(8k)對齊的
*/
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6 //將 __data_loc處資料搬移到_data處
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 //清除BSS段內容
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, r7, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel //程式跳轉到函式start_kernel進入C語言部分。
ENDPROC(__mmap_switched)