平台
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=%08lxn",
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