linux 3.5 lookup_processor_type函数

来源:互联网 发布:wifi有网络看不了视频 编辑:程序博客网 时间:2024/05/29 16:37

以下所讲的内容以 ARM 处理器展开:

每一个CPU都会有一个对应的处理器类型,它通过读取CP15中的寄存器C0主标识符来确定。

使用下面这条汇编语句进行读取:

MRC P15, 0, R0, C0, C0, 0


在linux 内核中,会使用对读取到的主标志符通过lookup_processor_type函数进行选择返回一个类型为struct proc_info_list 的结构体。

该结构体定义在:

/arch/arm/include/asm/procinfo.h
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;
};

我们需要关注的是第一个域和第二个域:

unsigned int cpu_val;

unsigned int cpu_mask;

判断的过程是:对读取到的主标注符(假如存储在R9寄存器),进行掩码与之后再跟cpu_val来比较如果相等,则找到对应的类型结构体。

汇编代码如下:

1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9                @ mask wanted bits
 teq r3, r4 

注释:r5为指向一个结构体的起始地址。


比如对于一个arm 920类型的CPU来说它的定义如下:

/arch/arm/mm/proc-arm920.s
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200  //val
.long 0xff00fff0  //mask
.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


我们现在知道了该结构体的定义,也知道了如何进行判断。现在问题就是如何在linux镜像中找到该结构体的起始地址。

其实内核中是把所有CPU类型的struct proc_info_list放在一个段(section)中,它们之间的连续的,之间的间距则为sizeof(struct proc_info_list)。

我们看一下lds是怎么描述的:

/arch/arm/kernel/vmlinux.lds.s
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
}

很明显,__arch_info_begin则为起始地址,__arch_info_end为结束地址。

既然现在也知道了如何存放这么一堆CPU类型结构体,那么看看具体的代码是如何来实现的。


/arch/arm/kernel/head-common.s


由ENTRY(lookup_processor_type)宏来导出该符号,让C代码可以调用该函数。

/*
 * This provides a C-API version of __lookup_processor_type
 */
ENTRY(lookup_processor_type)
stmfd sp!, {r4 - r6, r9, lr}              //保存一些寄存器,以免在后面的__lookup_processor_type中被破坏。
mov r9, r0                                            //r0->r9, ATPCS规定传递参数r0-r3,调用该函数传递一个参数,保存在r0.
bl __lookup_processor_type      //之所以传递给r9,是因为__lookup_processor_type假设r9存放就是CP15寄存器C0的值。
mov r0, r5                                            //r5就是找到的结构体的起始地址或为0(没有找到),传递给r0作为返回参数。
ldmfd sp!, {r4 - r6, r9, pc}            //恢复原先寄存器。
ENDPROC(lookup_processor_type)


/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 * r9 = cpuid           //假设就是这样,验证之前代码r0->r9。
 * Returns:
 * r3, r4, r6 corrupted                                                       //这几个寄存器会被使用到,所以之前需要保存寄存器的值。
 * r5 = proc_info pointer in physical address space //r5指向所找到的proc_info结构体的物理地址
 * r9 = cpuid (preserved)                                                 
 */
__CPUINIT
__lookup_processor_type:
adr r3, __lookup_processor_type_data //r3存放运行时地址
ldmia r3, {r4 - r6} //r4 存放__lookup_processor_type_data加载地址
                                    //r5 存放__proc_info_begin
                                    //r6 存放__proc_info_end
//r3 = r3 - r4 -> r3 != 0 时候代表加载时地址和运行时地址不一致
//需要对r5 r6加上一个偏移量r3
sub r3, r3, r4@ get offset between virt&phys
add r5, r5, r3@ convert virt addresses to   //  r5 指向__proc_info_begin lds里面的一个段
add r6, r6, r3@ physical address space    //  r6指向__proc_info_end 
1: ldmia r5, {r3, r4} @ value, mask           之前提到过的判断的依据:
and r4, r4, r9                @ mask wanted bits            与上掩码,同val比较。
teq r3, r4           //测试是否相等,是否找到对应的proc_info
beq 2f              //如果相等,则退出循环
add r5, r5, #PROC_INFO_SZ@ sizeof(proc_info_list)
cmp r5, r6                       //不等条件下,r5 + #PROC_INFO_SZ指向下一个结构体
blo 1b                                   //判断是否到结尾了
mov r5, #0@ unknown processor //最后没有找到proc_info -> r5 = 0 退出后则会发生错误
2: mov pc, lr     //返回
ENDPROC(__lookup_processor_type)

/*
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
.align 2
.type __lookup_processor_type_data, %object
__lookup_processor_type_data:
.long .
.long __proc_info_begin
.long __proc_info_end
.size __lookup_processor_type_data, . - __lookup_processor_type_data


上面的所有总结就是:C代码中调用loopup_processor_type函数,传递1个参数(CP15寄存器C0),该函数会返回一个参数。如果返回参数为0,

就是没有找到对应的struct proc_info,否则就是该结构体所在的物理地址。


我们现在看看linux内核中是如何使用该函数的。

/arch/arm/kernel/setup.c

static void __init setup_processor(void)
{
struct proc_info_list *list;
             
list = lookup_processor_type(read_cpuid_id());
if (!list) {
printk("CPU configuration botched (ID %08x), unable "
      "to continue.\n", read_cpuid_id());
while (1);
}

...

...

}

返回值list, 进行判断是否为空则可以知道是否找到对应的结构体。。。。

再者,可以推测read_cpuid_id就是读取CP15中寄存器C0,实际代码为:

static inline unsigned int __attribute_const__ read_cpuid_id(void)
{
return read_cpuid(CPUID_ID);
}

//加入reg = 0
//mrc p15, 0, %0, c0, c0, 0
// 读入c0 -> %0    这里"=r"(__val) 输出为__val
//作用读入CP15 主标志寄存器
#define read_cpuid(reg) \
({ \
unsigned int __val;\
asm("mrc p15, 0, %0, c0, c0, " __stringify(reg) \
   : "=r" (__val)\
   : \
   : "cc"); \//会引起改变CPU状态寄存器
__val; \
})


#define CPUID_ID 0
#define CPUID_CACHETYPE 1
#define CPUID_TCM 2
#define CPUID_TLBTYPE 3
#define CPUID_MPIDR 5


顺便提一下,read_cpuid,根据传递的参数不同所读取的也不一样。

传递0: MRC P15, 0, R0, C0, C0, 0    读取主标识符

传递1: MRC P15, 0, R0, C0, C0, 1    读取cache类型 

、、、

至于寄存器里内容的分布和定义则需要取查看相应手册了解。。。

0 0
原创粉丝点击