本文基于Linux版本:linux-4.19.125 arm-V7架构
在Linux启动过程中,执行完stext的第17行 :bl __lookup_processor_type后,r5寄存器存放了procinfo,procinfo指的是processor information,即处理器信息。这篇文章我们将探索procinfo的存储及访问过程。
@arch\arm\kernel\head.S:
__HEAD
ENTRY(stext)
ARM_BE8(setend be ) @ ensure we are in BE8 mode
b .
THUMB( badr r9, 1f ) @ Kernel is always entered in ARM.
THUMB( bx r9 ) @ If this is a Thumb-2 kernel,
THUMB( .thumb ) @ switch to Thumb now.
THUMB(1: )
#ifdef CONFIG_ARM_VIRT_EXT
bl __hyp_stub_install
#endif
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall 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)?
......
ENDPROC(stext)
__lookup_processor_type的实现位于arch\arm\kernel\head-common.S文件:
/*
* 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
* Returns:
* r3, r4, r6 corrupted
* r5 = proc_info pointer in physical address space
* r9 = cpuid (preserved)
*/
__lookup_processor_type:
adr r3, __lookup_processor_type_data
/* 取__lookup_processor_type_data地址存入r3, 这里得到的是物理地址
*/
ldmia r3, {r4 - r6}
/* 从寄存器r3地址处读取数据,依次存储到寄存器 r4、r5、r6 中
* r4: __lookup_processor_type_data 虚拟地址
* r5: __lookup_processor_type_data.__proc_info_begin 虚拟地址
* r6: __lookup_processor_type_data.__proc_info_end 虚拟地址
*/
sub r3, r3, r4 @ get offset between virt&phys
/* 计算__lookup_processor_type_data物理地址和虚拟地址的的偏移量
* 用于将虚拟地址转换为物理地址
*/
add r5, r5, r3 @ convert virt addresses to
/* r5: __lookup_processor_type_data.__proc_info_begin 物理地址
*/
add r6, r6, r3 @ physical address space
/* r6: __lookup_processor_type_data.__proc_info_end 物理地址
*/
1: ldmia r5, {r3, r4} @ value, mask
/*从地址 r5 处读取数据,存储到寄存器 r3 和 r4 中
* r5: __proc_info 物理地址
* r3: value, 对应proc_info_list结构体的cpu_value
* r4: mask, 对应proc_info_list结构体的cpu_mask
*/
and r4, r4, r9 @ mask wanted bits
/* r9: cpuid
* 执行 and r4, r4, r9后
* r4: mask之后的cpuid, 便于后面的lookup
*/
teq r3, r4 /*比较 r3 和 r4 的值是否相等。*/
beq 2f /*如果相等,说明已经找到匹配的CPU了,则跳转到标签 2,返回*/
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
/* 将 r5 加上 PROC_INFO_SZ,即 sizeof(proc_info_list) 的大小
* @1: 关于proc_info_list结构体的定义,请参考后面说明
* @2: 关于PROC_INFO_SZ的计算,请参考后面说明
*/
cmp r5, r6 /*比较 r5 和 r6 的值*/
blo 1b /*如果 r5 小于 r6,则跳回标签 1 继续循环。*/
mov r5, #0 @ unknown processor
/*
* 运行到这,说明当前的CPU是不支持的类型,r5赋值0
*/
2: ret lr
ENDPROC(__lookup_processor_type)
在arch/arm/kernel/head-common.S文件中关于__lookup_processor_type_data的定义如下:
/*
* 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
__lookup_processor_type_data的定义
__lookup_processor_type_data描述了__proc_info的信息:
1. __lookup_processor_type_data地址;
2. __proc_info_begin地址;
3. __proc_info_end地址;
4. __lookup_processor_type_data的大小;
__proc_info的填充
从__lookup_processor_type中的实现我们会发现,__proc_info_begin和__proc_info_end之间填充了若干个__proc_info,那么这些__proc_info是如何填充的呢?
在arch/arm/mm/proc-v7.S中有如下实现:
(备注:为什么是proc-v7.S文件?因为我的defconfig配置了CONFIG_CPU_V7)
.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, cache_fns = v7_cache_fns
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 \cache_fns
.endm
#ifndef CONFIG_ARM_LPAE
/*
* ARM Ltd. Cortex A5 processor.
*/
.type __v7_ca5mp_proc_info, #object
__v7_ca5mp_proc_info:
.long 0x410fc050
.long 0xff0ffff0
__v7_proc __v7_ca5mp_proc_info, __v7_ca5mp_setup
.size __v7_ca5mp_proc_info, . - __v7_ca5mp_proc_info
/*
* 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
/*
* ARM Ltd. Cortex A8 processor.
*/
.type __v7_ca8_proc_info, #object
__v7_ca8_proc_info:
.long 0x410fc080
.long 0xff0ffff0
__v7_proc __v7_ca8_proc_info, __v7_setup, proc_fns = ca8_processor_functions
.size __v7_ca8_proc_info, . - __v7_ca8_proc_info
#endif /* CONFIG_ARM_LPAE */
/*
* Marvell PJ4B processor.
*/
#ifdef CONFIG_CPU_PJ4B
.type __v7_pj4b_proc_info, #object
__v7_pj4b_proc_info:
.long 0x560f5800
.long 0xff0fff00
__v7_proc __v7_pj4b_proc_info, __v7_pj4b_setup, proc_fns = pj4b_processor_functions
.size __v7_pj4b_proc_info, . - __v7_pj4b_proc_info
#endif
/*
* ARM Ltd. Cortex R7 processor.
*/
.type __v7_cr7mp_proc_info, #object
__v7_cr7mp_proc_info:
.long 0x410fc170
.long 0xff0ffff0
__v7_proc __v7_cr7mp_proc_info, __v7_cr7mp_setup
.size __v7_cr7mp_proc_info, . - __v7_cr7mp_proc_info
/*
* ARM Ltd. Cortex R8 processor.
*/
.type __v7_cr8mp_proc_info, #object
__v7_cr8mp_proc_info:
.long 0x410fc180
.long 0xff0ffff0
__v7_proc __v7_cr8mp_proc_info, __v7_cr8mp_setup
.size __v7_cr8mp_proc_info, . - __v7_cr8mp_proc_info
/*
* ARM Ltd. Cortex A7 processor.
*/
.type __v7_ca7mp_proc_info, #object
__v7_ca7mp_proc_info:
.long 0x410fc070
.long 0xff0ffff0
__v7_proc __v7_ca7mp_proc_info, __v7_ca7mp_setup
.size __v7_ca7mp_proc_info, . - __v7_ca7mp_proc_info
/*
* ARM Ltd. Cortex A12 processor.
*/
.type __v7_ca12mp_proc_info, #object
__v7_ca12mp_proc_info:
.long 0x410fc0d0
.long 0xff0ffff0
__v7_proc __v7_ca12mp_proc_info, __v7_ca12mp_setup, proc_fns = HARDENED_BPIALL_PROCESSOR_FUNCTIONS
.size __v7_ca12mp_proc_info, . - __v7_ca12mp_proc_info
/*
* ARM Ltd. Cortex A15 processor.
*/
.type __v7_ca15mp_proc_info, #object
__v7_ca15mp_proc_info:
.long 0x410fc0f0
.long 0xff0ffff0
__v7_proc __v7_ca15mp_proc_info, __v7_ca15mp_setup, proc_fns = ca15_processor_functions
.size __v7_ca15mp_proc_info, . - __v7_ca15mp_proc_info
/*
* Broadcom Corporation Brahma-B15 processor.
*/
.type __v7_b15mp_proc_info, #object
__v7_b15mp_proc_info:
.long 0x420f00f0
.long 0xff0ffff0
__v7_proc __v7_b15mp_proc_info, __v7_b15mp_setup, proc_fns = ca15_processor_functions, cache_fns = b15_cache_fns
.size __v7_b15mp_proc_info, . - __v7_b15mp_proc_info
/*
* ARM Ltd. Cortex A17 processor.
*/
.type __v7_ca17mp_proc_info, #object
__v7_ca17mp_proc_info:
.long 0x410fc0e0
.long 0xff0ffff0
__v7_proc __v7_ca17mp_proc_info, __v7_ca17mp_setup, proc_fns = HARDENED_BPIALL_PROCESSOR_FUNCTIONS
.size __v7_ca17mp_proc_info, . - __v7_ca17mp_proc_info
/* ARM Ltd. Cortex A73 processor */
.type __v7_ca73_proc_info, #object
__v7_ca73_proc_info:
.long 0x410fd090
.long 0xff0ffff0
__v7_proc __v7_ca73_proc_info, __v7_setup, proc_fns = HARDENED_BPIALL_PROCESSOR_FUNCTIONS
.size __v7_ca73_proc_info, . - __v7_ca73_proc_info
/* ARM Ltd. Cortex A75 processor */
.type __v7_ca75_proc_info, #object
__v7_ca75_proc_info:
.long 0x410fd0a0
.long 0xff0ffff0
__v7_proc __v7_ca75_proc_info, __v7_setup, proc_fns = HARDENED_BPIALL_PROCESSOR_FUNCTIONS
.size __v7_ca75_proc_info, . - __v7_ca75_proc_info
/*
* Qualcomm Inc. Krait processors.
*/
.type __krait_proc_info, #object
__krait_proc_info:
.long 0x510f0400 @ Required ID value
.long 0xff0ffc00 @ Mask for ID
/*
* Some Krait processors don't indicate support for SDIV and UDIV
* instructions in the ARM instruction set, even though they actually
* do support them. They also don't indicate support for fused multiply
* instructions even though they actually do support them.
*/
__v7_proc __krait_proc_info, __v7_setup, hwcaps = HWCAP_IDIV | HWCAP_VFPv4
.size __krait_proc_info, . - __krait_proc_info
/*
* Match any ARMv7 processor core.
*/
.type __v7_proc_info, #object
__v7_proc_info:
.long 0x000f0000 @ Required ID value
.long 0x000f0000 @ Mask for ID
__v7_proc __v7_proc_info, __v7_setup
.size __v7_proc_info, . - __v7_proc_info
我们先看第一行代码:
.section ".proc.info.init", #alloc
.section
:这是一个汇编伪指令,用于定义一个新的段(section)".proc.info.init"
:这是段的名称,就是我们关注的proc info段#alloc
:这是段的属性,告诉编译器这个段需要被分配内存
接下来是一个__v7_proc宏:
* Standard v7 proc info content
*/
.macro __v7_proc name, initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions, cache_fns = v7_cache_fns
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 \cache_fns
.endm
__v7_proc宏参数说明如下:
name
: cpu nameinitfunc
: 初始化函数名称mm_mmuflags
: mmu flags,默认为0io_mmuflags
: io mmu flags,默认为0hwcaps
: 硬件功能标志位,默认为0proc_fns
: cpu处理函数,默认为v7_processor_functions
cache_fns
: cache处理函数,默认为v7_cache_fns
__v7_proc宏的作用实际就是填充proc_info_list结构体。
__v7_proc宏下面的一系列如__v7_ca5mp_proc_info,__v7_ca9mp_proc_info等,都是arm-V7支持的一系列CPU。
所以proc info信息的填充正是在这里完成的。
proc_info_list结构体定义
arch/arm/include/asm/procinfo.h
/*
* Note! struct processor is always defined if we're
* using MULTI_CPU, otherwise this entry is unused,
* but still exists.
*
* NOTE! The following structure is defined by assembly
* language, NOT C code. For more information, check:
* arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
*/
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;
};
PROC_INFO_SZ的计算
在arch/arm/kernel/asm-offset.c中,PROC_INFO_SZ是这样计算的:
DEFINE(PROC_INFO_SZ, sizeof(struct proc_info_list));
计算的结果,存放在在编译内核后生成的include/generated/asm-offset.h文件:
#define PROC_INFO_SZ 52 /* sizeof(struct proc_info_list) */
__proc_info_begin和__proc_info_end是如何计算得到的?
前面,我们已经知道了proc info是如何填充、如何获取的,但其实我们还有一个问题忽略了,就是填充和获取proc info的关键起始地址和结束地址,即__proc_info_begin和__proc_info_end是如何计算出来的呢?
在proc_info填充一节中,我们知道了proc info都放在了.init.proc.info段中,而.init.proc.info段在code中的位置,实际是在arch/arm/kernel/vmlinux.lds.S中确定的:
.init.proc.info : {
ARM_CPU_DISCARD(PROC_INFO)
}
PROC_INFO定义在在arch/arm/kernel/vmlinux.lds.h中:
#define PROC_INFO \
. = ALIGN(4); \
__proc_info_begin = .; \
*(.proc.info.init) \
__proc_info_end = .;
由此我们知道,__proc_info_begin就是.init.proc.info段的起始地址,然后就是存放一系列的proc info数据,最后就是__proc_info_end地址。
所以__proc_info_begin和__proc_info_end的地址在编译时就已经确定了。
验证一把:
(struct proc_info_list*)0x402CC20C = 0x402CC20C -> (
cpu_val = 0x000F0000,
cpu_mask = 0x000F0000,
__cpu_mm_mmu_flags = 0x00011C0E,
__cpu_io_mmu_flags = 0x0C02,
__cpu_flush = 0xFFD496D7,
arch_name = 0xC02CD5F4 -> "armv7",
elf_name = 0xC02CD5FA -> "v7",
elf_hwcap = 0x8097,
cpu_name = 0xC00157CC -> "ARMv7 Processor",
proc = 0xC045A060 -> (
_data_abort = 0xC0014F21,
_prefetch_abort = 0xC0014F41,
_proc_init = 0xC0015765,
check_bugs = 0xC0015AF9,
_proc_fin = 0xC0015769,
reset = 0xC0008275,
_do_idle = 0xC0015781,
dcache_clean_area = 0xC0015789,
switch_mm = 0xC00156E1,
set_pte_ext = 0xC00156FD,
suspend_size = 36,
do_suspend = 0xC00157DD,
do_resume = 0xC001580D),
tlb = 0xC043ED08 -> (
flush_user_range = 0xC001566D,
flush_kern_range = 0xC001569D,
tlb_flags = 0x90F00000),
user = 0xC045A058 -> (
cpu_clear_user_highpage = 0xC00152A5,
cpu_copy_user_highpage = 0xC00151D9),
cache = 0xC045A000 -> (
flush_icache_all = 0xC0014FA5,
flush_kern_all = 0xC0015059,
flush_kern_louis = 0xC0015071,
flush_user_all = 0xC0015089,
flush_user_range = 0xC0015089,
coherent_kern_range = 0xC001508D,
coherent_user_range = 0xC001508D,
flush_kern_dcache_area = 0xC00150F1,
dma_map_area = 0xC00151C1,
dma_unmap_area = 0xC00151CD,
dma_flush_range = 0xC0015195))