从前面的入口函数可知,内核进入时并没有知道自己到底运行在什么样的CPU里,因此就没有办法知道自己到底调用那个函数来初始化,或者用什么来做正确的工作。为了解决这个问题,就会调用__lookup_processor_type函数来查找CPU的类型。在这个函数里主要是通过预先填写CPUID的信息,跟目前获取到CPUID进行比较,如果能从预先定义的数组里找到一样的CPUID,说明就找到合适的初始化函数了。由于不同处理器的代码是分别写到不同的文件里,如何才能把这些信息组织到一起呢?其实是通过连接脚本文件来组织的,如下:
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
在这个脚本声明里,就可以把段名标记为.proc.info.init的段代码全部编译到一起,就连接成一个很大的数组。__proc_info_begin指向数据开始位置,__proc_info_end指向数组的结束位置。因而在代码里只需遍历整个数组,就可找到代码支持的CPU类型了。不过,还需要知道每个数组元素是长得什么样呢,并且有多大,这就需要了解proc_info_list结构,它的C定义如下:
structproc_info_list {
unsignedint cpu_val;
unsignedint cpu_mask;
unsignedlong __cpu_mm_mmu_flags; /* used by head.S */
unsignedlong __cpu_io_mmu_flags; /* used by head.S */
unsignedlong __cpu_flush; /* used by head.S */
constchar *arch_name;
constchar *elf_name;
unsignedint elf_hwcap;
constchar *cpu_name;
structprocessor *proc;
structcpu_tlb_fns *tlb;
structcpu_user_fns *user;
structcpu_cache_fns *cache;
};
通过这个结构,可以看到cpu_val是保存CPUID的值,cpu_mask是指明那几位数据起作用,__cpu_mm_mmu_flags是保存MMU的内存相关标志,__cpu_io_mmu_flags是保存MMU的IO相关的标志,__cpu_flush是保存架构相关的初始化代码入口,其它就是CPU相关的信息和函数,后面再继续了解,在入口函数只需要了解这几个成员就行了。有了结构的定义,就可以知道每个结构占用内存的大小,通过如下的方式获得:
DEFINE(PROC_INFO_SZ, sizeof(structproc_info_list));
这样在PROC_INFO_SZ就保存了每个CPU信息结构的大小。有了这些基本知识之后,就可以来理解__lookup_processor_type函数的具体实现过程了,它的代码如下:
__lookup_processor_type:
adr r3, 3f
计算3f标号地址。
ldmda r3, {r5 - r7}
加载处理器信息地址到r5,r6,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
比较CPUID,如果一致跳到标号2返回。
add r5, r5,#PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
增加处理器信息数组地址计数,是否到结束,如果到了就返回0.
blo 1b
mov r5, #0 @unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
/*
*Look in <asm/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
首先通过3f的标号获取到处理器信息列表的数组地址,也就是获取__proc_info_begin和__proc_info_end的值,因此通过ldmda r3,{r5 -r7}语句的执行,就把3f标号的虚拟地址保存到r7里,__proc_info_end的值保存到r6,__proc_info_begin的值保存到r5。通过编译时生产3f标号的虚拟地址与当前3f标号运行地址进行计算,就可以算出当前虚拟地址与物理地址之间的偏移,然后就可以把__proc_info_begin和__proc_info_end的值计算成虚拟地址。然后通过加载proc_info_list里的CPUID来与传送入的r9寄存器的CPUID进行比较,如果一致就说明已经找到相应的CPU信息了,接着放到r5寄存器返回找到的CPU信息结构的地址。