作者
彭東林
pengdonglin137@163.com
平台
TQ2440
Qemu+vexpress-ca9
Linux-4.10.17
概述
在Linux自解壓完畢后,開始執行arch/arm/kernel/head.S,然后跳轉到init/main.c中的start_kernel開始執行。在head.S中為了便利Linux內核啟動,會建立臨時的段頁表。這里以TQ2440和vexpress-ca9為例,其中TQ2440使用的SoC是S3C2440,ARM核心是ARM920T,指令集是ARMv4T,而vexpress-ca9是ARM核心是Cortex-A9,指令集是ARMv7。為了便於理解,在分析的時候主要以2440為主,只是順便說一下ARMv7,因為這兩個大同小異。
下面是代碼分析時的一些條件
1、以設備樹的方式啟動Linux內核
2、下面是一些宏和變量的說明:
宏 | 說明 | TQ2440(ARM920T) | vxpress(Cortex-A9) |
CONFIG_ARM_LPAE | No | No | |
TEXT_OFFSET | 內核代碼段相對於內核地址空間的偏移量 | 0x8000 | 0x8000 |
PAGE_OFFSET | 內核地址空間的偏移量 | 0xC000_0000 | 0xC000_0000 |
KERNEL_RAM_VADDR | =PAGE_OFFSET+TEXT_OFFSET | 0xC000_8000 |
0xC000_8000 |
PG_DIR_SIZE | 一級頁表的大小 | 0x4000 (16KB) | 0x4000 (16KB) |
PMD_ORDER | 一級頁表的每個頁表項占用的字節(2^(PMD_ORDER)) | 2^2 = 4 | 2^2 = 4 |
swapper_pg_dir | 一級頁表的虛擬起始地址 KERNEL_RAM_VADDR - PG_DIR_SIZE |
0xC000_4000 | 0xC000_4000 |
CONFIG_ARM_VIRT_EXT | No | Yes | |
CONFIG_XIP_KERNEL | No | No | |
CONFIG_SMP | No | Yes | |
CONFIG_SMP_ON_UP | No | Yes | |
CONFIG_ARM_PATCH_PHYS_VIRT | Yes | Yes | |
CONFIG_CPU_32v4T | ARM指令集 | Yes | No |
CONFIG_CPU_32v7 | ARM指令集 | No | Yes |
CONFIG_CPU_V7M | ARM指令集 | No | No |
__LINUX_ARM_ARCH__ | ARM指令集 | 4 | 7 |
CONFIG_CPU_DCACHE_WRITETHROUGH | No | No |
3、地址空間:
對於TQ2440,板子上面有64MB的物理內存,所以物理內存地址范圍是: 0x3000_0000 ~ 0x3400_0000
對於express板子,分配了1GB的物理內存,所以物理內存地址范圍是: 0x6000_0000 ~ 0xA000_0000
正文
在進入head.S是,MMU和D-Cache是關閉的,r0是0,r1的值任意,r2的值是dtb鏡像在內存中的物理起始地址。
下面是對head.S精簡后的代碼:
1 ENTRY(stext) 2 3 #ifdef CONFIG_ARM_VIRT_EXT 4 bl __hyp_stub_install 5 #endif 6 @ ensure svc mode and all interrupts masked 7 safe_svcmode_maskall r9 8 9 mrc p15, 0, r9, c0, c0 @ get processor id 10 bl __lookup_processor_type @ r5=procinfo r9=cpuid 11 movs r10, r5 @ invalid processor (r5=0)? 12 beq __error_p @ yes, error 'p' 13 14 adr r3, 2f 15 ldmia r3, {r4, r8} 16 sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET) 17 add r8, r8, r4 @ PHYS_OFFSET 18 19 /* 20 * r1 = machine no, r2 = atags or dtb, 21 * r8 = phys_offset, r9 = cpuid, r10 = procinfo 22 */ 23 bl __vet_atags 24 #ifdef CONFIG_SMP_ON_UP 25 bl __fixup_smp 26 #endif 27 28 bl __fixup_pv_table 29 30 bl __create_page_tables 31 32 /* 33 * The following calls CPU specific code in a position independent 34 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of 35 * xxx_proc_info structure selected by __lookup_processor_type 36 * above. 37 * 38 * The processor init function will be called with: 39 * r1 - machine type 40 * r2 - boot data (atags/dt) pointer 41 * r4 - translation table base (low word) 42 * r5 - translation table base (high word, if LPAE) 43 * r8 - translation table base 1 (pfn if LPAE) 44 * r9 - cpuid 45 * r13 - virtual address for __enable_mmu -> __turn_mmu_on 46 * 47 * On return, the CPU will be ready for the MMU to be turned on, 48 * r0 will hold the CPU control register value, r1, r2, r4, and 49 * r9 will be preserved. r5 will also be preserved if LPAE. 50 */ 51 ldr r13, =__mmap_switched @ address to jump to after 52 @ mmu has been enabled 53 badr lr, 1f @ return (PIC) address 54 55 mov r8, r4 @ set TTBR1 to swapper_pg_dir 56 57 ldr r12, [r10, #PROCINFO_INITFUNC] 58 add r12, r12, r10 59 ret r12 60 1: b __enable_mmu 61 ENDPROC(stext) 62 .ltorg 63 2: .long . 64 .long PAGE_OFFSET
下面開始分析上面的代碼:
1、第4行的__hyp_stub_install在vexpress上會執行,而在2440上不執行,這里暫時忽略
2、第7行的 safe_svcmode_maskall r9 確保處理器進入SVC模式,同時關閉IRQ和FIQ中斷。對於2440,做了如下操作:
msr cpsr_c, #(PSR_F_BIT | PSR_I_BIT | SVC_MODE)
3、第9行 mrc p15, 0, r9, c0, c0 用於獲得processor id。
對於2440, CP15的C0的值是0x4112920x,參考手冊 ARM920T Technical Reference Manual 的2.3節 CP15 register map summary
對於vexpress,CP15的C0的值是0x414FC091,參考手冊 ARM® Cortex®‑A9 Technical Reference Manual 的 4. System Control
比如對於2440,執行完第3行代碼后,r9的值就是0x4112920x,而對於vexpress,r9的值是0x414FC091。
4、第10到12行,遍歷kernel的".proc.info.init"段,找到與該處理器ID匹配的proc_info_list結構體,如果找到的話,r5寄存器存放的是該proc_info_list的物理地址,第11行將該地址存放到r10中,如果沒有找到的話,
寄存器r5值是0,執行完第11行的movs代碼后,第12行的beq就會成立,跳轉到__error_p處,如果配置了CONFIG_DEBUG_LL,就會打印相應的錯誤信息:
Error: unrecognized/unsupported processor variant (0xXXXXXXX)
上面括號中是實際從CP15的C0里讀到的值。
下面我們看看對於2440和vexpress這兩個板子,與之匹配的proc.info.init字段都分別是什么?
對於2440,該部分定義在arch/arm/mm/proc-arm920.S中:
1 define_processor_functions arm920, dabort=v4t_early_abort, pabort=legacy_pabort, suspend=1 2 3 .section ".rodata" 4 5 string cpu_arch_name, "armv4t" 6 string cpu_elf_name, "v4" 7 string cpu_arm920_name, "ARM920T" 8 9 .align 10 11 .section ".proc.info.init", #alloc 12 13 .type __arm920_proc_info,#object 14 __arm920_proc_info: 15 .long 0x41009200 16 .long 0xff00fff0 17 .long PMD_TYPE_SECT | \ 18 PMD_SECT_BUFFERABLE | \ 19 PMD_SECT_CACHEABLE | \ 20 PMD_BIT4 | \ 21 PMD_SECT_AP_WRITE | \ 22 PMD_SECT_AP_READ 23 .long PMD_TYPE_SECT | \ 24 PMD_BIT4 | \ 25 PMD_SECT_AP_WRITE | \ 26 PMD_SECT_AP_READ 27 initfn __arm920_setup, __arm920_proc_info 28 .long cpu_arch_name 29 .long cpu_elf_name 30 .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB 31 .long cpu_arm920_name 32 .long arm920_processor_functions 33 .long v4wbi_tlb_fns 34 .long v4wb_user_fns 35 .long arm920_cache_fns 36 .size __arm920_proc_info, . - __arm920_proc_info
第1行的define_processor_functions是一個宏,定義在arch/arm/mm/proc-macros.S中,根據傳入的參數展開后如下:
.type arm920_processor_functions, #object .align 2 ENTRY(arm920_processor_functions) .word \dabort .word \pabort .word cpu_arm920_proc_init .word cpu_arm920_proc_fin .word cpu_arm920_reset .word cpu_arm920_do_idle .word cpu_arm920_dcache_clean_area .word cpu_arm920_switch_mm .word cpu_arm920_set_pte_ext .word cpu_arm920_suspend_size .word cpu_arm920_do_suspend .word cpu_arm920_do_resume .size arm920_processor_functions, . - arm920_processor_functions
第4到7行只讀,存放了一下字符串,將來在啟動階段(start_kernel --> setup_arch --> setup_processor)會被打印出來
pr_info("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n", cpu_name, read_cpuid_id(), read_cpuid_id() & 15, proc_arch[cpu_architecture()], get_cr());
如:
[ 0.000000] CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c000717f
第15到35行的數據將來可以通過一個struct proc_info_list進行訪問:
struct proc_info_list { unsigned int cpu_val; unsigned int cpu_mask; unsigned long __cpu_mm_mmu_flags; /* used by head.S */ unsigned long __cpu_io_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; };
第27行 initfn __arm920_setup, __arm920_proc_info 展開后是: __arm920_setup - __arm920_proc_info,也就是這里存放了一個這兩個符號的地址偏差,將來就可以根據__arm920_proc_info輕松地找到__arm920_setup
第33和35行的分析類似第1行,都是宏展開后生成的,直接在代碼里搜索不到。
對於v4wbi_tlb_fns 定義在arch/arm/mm/tlb-v4wbi.S中: define_tlb_functions v4wbi, v4wbi_tlb_flags ,展開如下:
.type v4wbi_tlb_fns, #object ENTRY(v4wbi_tlb_fns) .long v4wbi_flush_user_tlb_range .long v4wbi_flush_kern_tlb_range .long v4wbi_tlb_flags .size v4wbi_tlb_fns, . - v4wbi_tlb_fns
對於arm920_cache_fns, 定義在arch/arm/mm/proc-arm920.S中 define_cache_functions arm920 展開后:
.align 2 .type arm920_cache_fns, #object ENTRY(arm920_cache_fns) .long arm920_flush_icache_all .long arm920_flush_kern_cache_all .long arm920_flush_kern_cache_louis .long arm920_flush_user_cache_all .long arm920_flush_user_cache_range .long arm920_coherent_kern_range .long arm920_coherent_user_range .long arm920_flush_kern_dcache_area .long arm920_dma_map_area .long arm920_dma_unmap_area .long arm920_dma_flush_range .size arm920_cache_fns, . - arm920_cache_fns
第34行,對於v4wb_user_fns 定義在arch/arm/mm/copypage-v4wb.c中:
struct cpu_user_fns v4wb_user_fns __initdata = { .cpu_clear_user_highpage = v4wb_clear_user_highpage, .cpu_copy_user_highpage = v4wb_copy_user_highpage, };
如果將vmlinux反匯編,可以看到__arm920_proc_info這段的內容如下:
c06adf80 <__proc_info_begin>: c06adf80: 41009200 #cpu_val c06adf84: ff00fff0 #cpu_mask c06adf88: 00000c1e #__cpu_mm_mmu_flags c06adf8c: 00000c12 #__cpu_io_mmu_flags c06adf90: ff968a3c #__cpu_flush c06adf94: c04ed874 #arch_name c06adf98: c04ed87b #elf_name c06adf9c: 00000007 #elf_hwcap c06adfa0: c04ed87e #cpu_name c06adfa4: c06b4040 #proc c06adfa8: c06b4034 #tlb c06adfac: c06b402c #user c06adfb0: c00168c0 #cache
對於vexpress,對應的是proc.info.init定義在arch/arm/mm/proc-v7.S中,只留下需要關注的部分:
define_processor_functions ca9mp, dabort=v7_early_abort, pabort=v7_pabort, suspend=1 .section ".rodata" string cpu_arch_name, "armv7" string cpu_elf_name, "v7" .align .section ".proc.info.init", #alloc /* * Standard v7 proc info content */ .macro __v7_proc name, initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions ALT_SMP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \ PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags) ALT_UP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \ PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags) .long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \ PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags initfn \initfunc, \name .long cpu_arch_name .long cpu_elf_name .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \ HWCAP_EDSP | HWCAP_TLS | \hwcaps .long cpu_v7_name .long \proc_fns .long v7wbi_tlb_fns .long v6_user_fns .long v7_cache_fns .endm /* * ARM Ltd. Cortex A9 processor. */ .type __v7_ca9mp_proc_info, #object __v7_ca9mp_proc_info: .long 0x410fc090 .long 0xff0ffff0 __v7_proc __v7_ca9mp_proc_info, __v7_ca9mp_setup, proc_fns = ca9mp_processor_functions .size __v7_ca9mp_proc_info, . - __v7_ca9mp_proc_info
進一步展開后是:
1 string cpu_v7_name, "ARMv7 Processor" 2 define_processor_functions ca9mp, dabort=v7_early_abort, pabort=v7_pabort, suspend=1 3 4 .section ".rodata" 5 6 string cpu_arch_name, "armv7" 7 string cpu_elf_name, "v7" 8 .align 9 10 .section ".proc.info.init", #alloc 11 12 /* 13 * ARM Ltd. Cortex A9 processor. 14 */ 15 .type __v7_ca9mp_proc_info, #object 16 __v7_ca9mp_proc_info: 17 .long 0x410fc090 18 .long 0xff0ffff0 19 ALT_SMP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \ 20 PMD_SECT_AF | PMD_FLAGS_SMP) 21 ALT_UP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \ 22 PMD_SECT_AF | PMD_FLAGS_UP) 23 .long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \ 24 PMD_SECT_AP_READ | PMD_SECT_AF 25 initfn __v7_ca9mp_setup, __v7_ca9mp_proc_info 26 .long cpu_arch_name 27 .long cpu_elf_name 28 .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \ 29 HWCAP_EDSP | HWCAP_TLS 30 .long cpu_v7_name 31 .long ca9mp_processor_functions 32 .long v7wbi_tlb_fns 33 .long v6_user_fns 34 .long v7_cache_fns 35 .size __v7_ca9mp_proc_info, . - __v7_ca9mp_proc_info
跟2440一樣,其中的部分標號的定義如下:
ca9mp_processor_functions: 定義在arch/arm/mm/proc-v7.S中 define_processor_functions ca9mp, dabort=v7_early_abort, pabort=v7_pabort, suspend=1
.type ca9mp_processor_functions, #object .align 2 ENTRY(ca9mp_processor_functions) .word v7_early_abort .word v7_pabort .word cpu_ca9mp_proc_init .word cpu_ca9mp_proc_fin .word cpu_ca9mp_reset .word cpu_ca9mp_do_idle .word cpu_ca9mp_dcache_clean_area .word cpu_ca9mp_switch_mm .word cpu_ca9mp_set_pte_ext .word cpu_ca9mp_suspend_size .word cpu_ca9mp_do_suspend .word cpu_ca9mp_do_resume .size ca9mp_processor_functions, . - ca9mp_processor_functions
v7wbi_tlb_fns:定義在arch/arm/mm/tlb-v7.S中 define_tlb_functions v7wbi, v7wbi_tlb_flags_up, flags_smp=v7wbi_tlb_flags_smp ,展開如下:
ENTRY(v7wbi_tlb_fns) .long v7wbi_flush_user_tlb_range .long v7wbi_flush_kern_tlb_range ALT_SMP(.long flags_smp=v7wbi_tlb_flags_smp ) ALT_UP(.long v7wbi_tlb_flags_up ) .size v7wbi_tlb_fns, . - v7wbi_tlb_fns
v6_user_fns:定義在arch/arm/mm/copypage-v6.c中:
struct cpu_user_fns v6_user_fns __initdata = { .cpu_clear_user_highpage = v6_clear_user_highpage_nonaliasing, .cpu_copy_user_highpage = v6_copy_user_highpage_nonaliasing, };
v7_cache_fns:定義在arch/arm/mm/cache-v7.S中 define_cache_functions v7 ,展開如下:
.align 2 .type v7_cache_fns, #object ENTRY(v7_cache_fns) .long v7_flush_icache_all .long v7_flush_kern_cache_all .long v7_flush_kern_cache_louis .long v7_flush_user_cache_all .long v7_flush_user_cache_range .long v7_coherent_kern_range .long v7_coherent_user_range .long v7_flush_kern_dcache_area .long v7_dma_map_area .long v7_dma_unmap_area .long v7_dma_flush_range .size v7_cache_fns, . - v7_cache_fns
對vmlinux反匯編后,可以看到__v7_ca9mp_proc_info部分的數據:
c06ee5fc <__v7_ca9mp_proc_info>: c06ee5fc: 410fc090 #cpu_val c06ee600: ff0ffff0 #cpu_mask c06ee604: 00011c0e #__cpu_mm_mmu_flags c06ee608: 00000c02 #__cpu_io_mmu_flags c06ee60c: ffa2e260 #__cpu_flush c06ee610: c0701b64 #arch_name c06ee614: c0701b6a #elf_name c06ee618: 00008097 #elf_hwcap c06ee61c: c011c780 #cpu_name c06ee620: c0958094 #proc c06ee624: c09081dc #tlb c06ee628: c095802c #user c06ee62c: c0958000 #cache
回到head.S繼續分析,上面說完proc.info.init段的內容后,下面分析__lookup_processor_type:
1 __lookup_processor_type: 2 adr r3, __lookup_processor_type_data 3 ldmia r3, {r4 - r6} 4 sub r3, r3, r4 @ get offset between virt&phys 5 add r5, r5, r3 @ convert virt addresses to 6 add r6, r6, r3 @ physical address space 7 1: ldmia r5, {r3, r4} @ value, mask 8 and r4, r4, r9 @ mask wanted bits 9 teq r3, r4 10 beq 2f 11 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) 12 cmp r5, r6 13 blo 1b 14 mov r5, #0 @ unknown processor 15 2: ret lr 16 ENDPROC(__lookup_processor_type) 17 18 /* 19 * Look in <asm/procinfo.h> for information about the __proc_info structure. 20 */ 21 .align 2 22 .type __lookup_processor_type_data, %object 23 __lookup_processor_type_data: 24 .long . 25 .long __proc_info_begin 26 .long __proc_info_end 27 .size __lookup_processor_type_data, . - __lookup_processor_type_data
由於還沒有開啟MMU,所以虛擬地址就是物理地址,但是由於kernel代碼段的鏈接地址是從0xC0008000開始,而對於2440來說,物理內容的范圍是0x3000_0000到0x3400_0000,所以如果直接用虛擬地址訪問的話,程序一定會跑飛了。
所以在第2到第6行的代碼首先會對第25行__proc_info_begin和第26行的__proc_info_end的虛擬地址轉換,轉換成物理地址,分別存放在r5和r6中,轉換方法很簡單
第7到第14行開始從r5(也就是"proc.info.init"段的起始物理地址)開始,以#PROC_INFO_SZ為步長進行遍歷,尋找跟r9中的cpu id匹配的proc_info_list。匹配的方法很簡單:從之前的分析知道,proc_info_list的前兩個成員分別是cpu_val (r3)和cpu_mask (r4),將這兩個值讀出來,然后進行如下判斷:(r9 & cpu_mask) 是否等於 cpu_val,如果相等,意味着找到匹配項,然后返回,此時r5中存放的是找到的proc_info_list的物理地址。否則的話,繼續遍歷下一個proc_info_list,直到遍歷到最后一個proc_info_list,如果沒有找到,r5被賦值為0,然后返回。
回到head.S繼續分析。
5、第14到第17行代碼完成的任務是計算物理內存的起始地址,方法如下:
adr r3, 2f ldmia r3, {r4, r8} sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET) add r8, r8, r4 @ PHYS_OFFSET .ltorg 2: .long . .long PAGE_OFFSET
首先獲得2f標號的物理地址,在哪里存放的是2f標號的虛擬地址以及0xC000_0000。然后計算2f的物理地址跟虛擬地址之間的差值,再該差值加上0xC000_0000,就可以得到物理內存的起始地址。當然這里的前提是kernel被加載到(物理內存的起始地址 + 0x8000)處開始執行。
比如對於2440,執行完上面的操作后,r8的值是0x3000_0000,對於vexpress來說是,r8是0x6000_0000.
6、第23行,檢查r2中傳遞的設備樹鏡像是否合法,如果不合法的話,r2會被清0。檢查方法是:判斷r2指向的地址的前4個字節是否等於OF_DT_MAGIC,是的話,表示合法,否則不合法
7、第25和第28行暫時忽略
未完待續
8、第30行調用__create_page_tables建立段式頁表。