一,前言
head.S是Linux内核的首先执行代码的文件,代码首先处理uboot传递过来的参数,通过参数判断该内核是不是支持该处理器和机器类型,接着创建页表和使能mmu,代码如下。本文是基于S3C2440开发板。
ENTRY(stext)
# 设置CPU为SVC模式
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
# 处理器ID赋值到r9
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
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'
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.
*/
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
上面的代码首先设置CPU为SVC模式,然后通过读取CP15协处理器的C0寄存器的值到r9中,CP15处理器的操作方法参考ARM汇编指令3 - CP15协处理器介绍和操作方法,然后bl __lookup_processor_type查找处理器类型,本文主要分析这个函数。
二. __lookup_processor_type分析
__lookup_processor_type代码如下
.type __lookup_processor_type, %function
__lookup_processor_type:
adr r3, 3f
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
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
/*
* 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
adr r3, 3f :
获取标签3处的地址,是一个基于pc指针的地址,也就是物理地址,反汇编的代码如下所示
c000815c <__lookup_processor_type>:
c000815c: e28f304c add r3, pc, #76 ; 0x4c
......
标签3处的物理地址的值是根据uboot的theKernel函数的地址决定。
ldmda r3, {r5 - r7} :
由ldmda 功能可知,是赋值后递减,并且高地址存放编号大的寄存器,所以r7 = .,r6 = __proc_info_end,r5 = __proc_info_begin。__proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds中定义,__proc_info_begin是.proc.info.init段的开始地址,__proc_info_end为.proc.info.init的结束地址,他们都是虚拟地址,因为都是基于0xc0008000开始的。
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
因为S3C2440是arm920的架构,所以arch/arm/mm/proc-arm920.S中定义的__arm
920_proc_info会被编译到内核,并且放在内核的.proc.info.init段中。
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
.long arm920_cache_fns
#else
.long v4wt_cache_fns
#endif
.size __arm920_proc_info, . - __arm920_proc_info
__arm920_proc_info的C语言的结构体的定义在include/asm/procinfo.h中,include/asm是include/asm-arm的软链接。
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;
};
sub r3, r3, r7 :
由上可知,r3是一个基于pc的地址,是物理地址,r7=.,是虚拟地址,将物理地址减去虚拟地址,得到物理地址和虚拟地址的差值赋值给r3,这个r3的值应该为负值。
add r5, r5, r3 :
r5是一个虚拟地址,r3是一个物理地址减虚拟地址的差值,大概率是一个负值,所以此处是r5加一个负值,得到的是r5的物理地址,赋值回r5。
add r6, r6, r3 :
操作原理如add r5, r5, r3
1: ldmia r5, {r3, r4} :
r5保存的是__proc_info_begin的地址,所以是__arm920_proc_info的起始地址。由ldmia功能可知,所以r3 = 0x41009200,r4 = 0xff00fff0。
and r4, r4, r9 :
从CP15协处理器C0寄存器读出的ID与0xff00fff0进行与操作赋值到r4中。
teq r3, r4 :
判断r3和r4是否相等,也就是r4是否等于0x41009200。
beq 2f :
如果相等,则跳到后面的标签2,函数返回。如果不相等则继续运行后一句汇编指令。
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) :
PROC_INFO_SZ定义在include/asm/asm-offsets.h,表示struct proc_info_list结构体的大小。此处表示,r5保存.proc.info.init段中的后面一个struct proc_info_list的地址。
cmp r5, r6 :
看是否到了.proc.info.init段的结尾。
blo 1b :
blo表示无符号数小于则跳转到后面标签1处。为1: ldmia r5, {r3, r4}。
mov r5, #0 @ unknown processor :
最后如果没有找到匹配的processor id,则jiang0赋值给r5。
2: mov pc, lr :
表示函数返回。
movs r10, r5 :
将r5的值赋值给r10,
beq __error_p :
如果r5等于r6,则跳转到__error_p。
三. 总结
经过上述代码可知,r9为从CP15协处理器C0寄存器中读出的处理器ID,r10为指向与r9保存的处理器ID匹配的struct proc_info_list结构体地址,,r1为uboot传递过来的机器ID。