start_kernel之前的汇编代码分析
Boot中执行下面两句话之后,进入uclinux内核。
theKernel = (void (*)(int, int, unsigned int))((uint32_t)0x08003001);
theKernel(0, 2189, ((uint32_t)0x20000100));
首先来到0x0800 3000处,此时携带有三个参数,R0、R1、R2,分别是0,2189,0x2000 0100.
0x0800 3000对应着下面stext的汇编代码。
代码阅读说明:
① @后面的内容标示是注释语句
② ARM或者THUMB指令的选取,在\include\asm-arm\unified.h中,定义了使用ARM,THUMB为空
③ 偏移量#PROC_INFO_SZ等,在\include\asm\asm-offset.h中宏定义
1.1 head-mommu.S
文件在arch\arm\kernel\head-nommu.S,text.head段,有stext、__after_proc_init两段组成,内核从这里开始启动,然后通过各种调用,最终进入C语言的start_kernel函数,这段代码执行流程如下:
(1) 调用__lookup_processor_type,传入R9(CPU基址寄存器的值),成功后获得当前的proc_info结构体信息,地址放入R10;
(2) 调用__lookup_machine_type,BootLoader传给内核的R1值,成功后获得当前的machine_desc结构体信息,地址放入R8;
(3) 执行__v7m_setup,进行必要的初始化;
(4) 执行__switch_data,并最终跳转到C语言的start_kernel函数。
.section ".text.head", "ax"
ENTRY(stext)
cpsid i @ disable interrupts //cortex-M3特殊指令,关闭所有中断
ldr r9, =0xe000ed00 @ CPUID register address
ldr r9, [r9] //CPUID基址寄存器,参考手册(周立功)-P80
bl __lookup_processor_type @ r5=procinfo r9=cupid //head-common.S
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‘
badr lr, __after_proc_init @ return (PIC) address //提前暂存lr的值
ARM( add pc, r10, #PROCINFO_INITFUNC ) //R10处偏移16个字节,然后赋给PC
//相当于跳转到下面的__v7m_setup
ENDPROC(stext)
__after_proc_init:
ldr pc,__switch_data //将PC 指针赋值到__switch_data标号地址处,实际执行的
ENDPROC(__after_proc_init) //是在__mmap_switched这个标号处
.ltorg
#include "head-common.S" //末尾包含下面的文件
1.2 head-common.S
文件在arch\arm\kernel\head-common.S,有__switch_data、__mmap_switched、__error_p、__error_a、__error、__lookup_processor_type、C语言的lookup_processor_type、__lookup_machine_type 、C语言的lookup_machine_type、__vet_atags等组成,这里只列出上面调用要使用的查找处理器类型和查找机器类型。
_lookup_processor_type: //r9是刚从CPU基址寄存器中取出的值
ARM( adr r3, 3f ) //执行此句后,r3的地址是后面标号3处的地址
ARM( ldmda r3, {r5 - r7} )//r5,r6,r7依次变为,__proc_info_begin,end,以及
//标号3处的末尾链接地址
sub r3, r3, r7 @ get offset between virt&phys //这三行执行虚实地址转换
add r5, r5, r3 @ convert virt addresses to//r5是proc_info_list结构体地址
add r6, r6, r3 @ physical address space //__proc_info_end的自己
1: ldmia r5, {r3, r4} @ value, mask //r3是结构体的val变量
and r4, r4, r9 @ mask wanted bits //r4是结构体的mask变量
teq r3, r4
beq 2f //相等转到后面的2标号处
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) //宏定义在asm-offset.h
cmp r5, r6 //结构体可能是个数组,需要全部查找,若r5的地址没有超出end
blo 1b //的地址,则继续转到1处理,若最后没找到,r5变为空
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
ENTRY(lookup_processor_type)
stmfd sp!, {r4 - r7, r9, lr}
mov r9, r0 //提供C语言的API结构,r0是传入的参数
bl __lookup_processor_type
mov r0, r5 //此处的R0是函数的返回值,即传出的参数
ldmfd sp!, {r4 - r7, r9, pc}
ENDPROC(lookup_processor_type)
.long __proc_info_begin //arch/arm/kernel/vmlinux.lds.s中定义
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end
/*
* Lookup machine architecture in the linker-build list of architectures.
* Note that we can‘t use the absolute addresses for the __arch_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.
*
* r1 = machine architecture number
* Returns:
* r3, r4, r6 corrupted
* r5 = mach_info pointer in physical address space
*/
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, r6} //r5是__arch_info_begin,r6是end
sub r3, r3, r4 @ get offset between virt&phys //虚实地址转换
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type //结构体偏移字节数,见上面的注释
teq r3, r1 @ matches loader number? //与传递给内核的r1相同
beq 2f @ found //r3就是结构体中第一个变量nr
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
ENDPROC(__lookup_machine_type)
/*
* This provides a C-API version of the above function.
*/
ENTRY(lookup_machine_type)
stmfd sp!, {r4 - r6, lr}
mov r1, r0 //提供C语言的API结构,r0是传入的参数
bl __lookup_machine_type
mov r0, r5 //此处的R0是函数的返回值,即传出的参数
ldmfd sp!, {r4 - r6, pc}
ENDPROC(lookup_machine_type)
1.2.1 lookup_processor_type
(1)vmlinux.lds.s
文件在arch\arm\kernel\ vmlinux.lds.s
__proc_info_begin = .; //include/asm-arm/procinfo.h中定义
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .; //include/asm-arm/mach/arch.h 中定义
*(.arch.info.init)
__arch_info_end = .;
(2)procinfo.h
文件在include/asm-arm/procinfo.h
struct proc_info_list { // pro-v7m.S对其实例化
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;
};
(3)pro-v7m.S
文件在arch\arm\mm\ pro-v7m.S,内容很多,核心就是.section ".proc.info.init",其它的都是它这个结构体中得成员,如__v7m_setup、cpu_arch_name、cpu_elf_name、cpu_v7m_name、v7m_processor_functions等。
cpu_v7m_name:
.ascii "ARMv7-M Processor"
.align
.section ".text.init", #alloc, #execinstr
.type v7m_processor_functions, #object
ENTRY(v7m_processor_functions)
.word v7m_early_abort
.word cpu_v7m_proc_init
.word cpu_v7m_proc_fin
.word cpu_v7m_reset
.word cpu_v7m_do_idle
.word cpu_v7m_dcache_clean_area
.word cpu_v7m_switch_mm
.word cpu_v7m_set_pte_ext
.word pabort_noifar
.size v7m_processor_functions, . - v7m_processor_functions
.type cpu_arch_name, #object
cpu_arch_name:
.asciz "armv7m"
.size cpu_arch_name, . - cpu_arch_name
.type cpu_elf_name, #object
cpu_elf_name:
.asciz "v7m"
.size cpu_elf_name, . - cpu_elf_name
.align
.section ".proc.info.init", #alloc, #execinstr
/*
* Match any ARMv7-M processor core.
*/
.type __v7m_proc_info, #object
__v7m_proc_info: //E000ED00是CPUID基址寄存器,其中[19:16]读作常量F
.long 0x000f0000 @ Required ID value
.long 0x000f0000 @ Mask for ID
.long 0 @ proc_info_list.__cpu_mm_mmu_flags
.long 0 @ proc_info_list.__cpu_io_mmu_flags
b __v7m_setup @ proc_info_list.__cpu_flush
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP
.long cpu_v7m_name
.long v7m_processor_functions @ proc_info_list.proc
.long 0 @ proc_info_list.tlb
.long 0 @ proc_info_list.user
.long 0 @ proc_info_list.cache
.size __v7m_proc_info, . - __v7m_proc_info
1.2.2 lookup_machine_type
(1)arch.h
文件在include/asm-arm/mach/arch.h
struct machine_desc { //在stm3210e_eval.c中实例化
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
(2)stm3210e_eval.c
文件在arch\arm\mach-stm3210e_eval\ stm3210e_eval.c
MACHINE_START(STM3210E_EVAL, "STM3210E-EVAL")
/* Maintainer: MCD Application TEAM */ /* Status */
.fixup = stm3210e_eval_fixup, //OK
.boot_params = CONFIG_EXTERNAL_RAM_BASE + 0x100, //OK
.init_irq = nvic_init_irq, //OK
.timer = &stm3210e_eval_timer, //OK
.init_machine = stm3210e_eval_init, //OK
MACHINE_END
(3)match-type.h
文件在include/asm-arm/mach-type.h
#define MACH_TYPE_STM3210E_EVAL 2189
1.3 __v7m_setup
文件在arch\arm\mm\ pro-v7m.S
/*
* __v7m_setup
*
* Initialise TLB, Caches, and MMU state ready to switch the MMU
* on. Return in r0 the new CP15 C1 control register setting.
*
* We automatically detect if we have a Harvard cache, and use the
* Harvard cache control instructions insead of the unified cache
* control instructions.
*
* This should be able to cover all ARMv7-M cores.
*
* It is assumed that:
* - cache type register is implemented
*/
__v7m_setup:
@ Configure the vector table base address //配置中断向量偏移
ldr r0, =0xe000ed08 @ vector table base address
ldr r12, =vector_table
str r12, [r0]
@ Lower the priority of the SVC and PendSV exceptions
ldr r0, =0xe000ed1c //设置SVC、PendSV的中断优先级都为128
mov r5, #0x80000000
str r5, [r0] @ set SVC priority
ldr r0, =0xe000ed20
mov r5, #0x00800000
str r5, [r0] @ set PendSV priority
@ SVC to run the kernel in this mode
badr r0, 1f
ldr r5, [r12, #11 * 4] @ read the SVC vector entry
str r0, [r12, #11 * 4] @ write the temporary SVC vector entry
mov r6, lr @ save LR
mov r7, sp @ save SP
#if !defined(CONFIG_XIP_KERNEL)
ldr sp, =__v7m_setup_stack_top
#endif
cpsie i
svc #0 //呼叫系统服务,权威指南-P182,暂时没找到?
1: cpsid i
str r5, [r12, #11 * 4] @ restore the original SVC vector entry
mov lr, r6 @ restore LR
mov sp, r7 @ restore SP
@ Special-purpose control register
mov r0, #1 //还是进入特权级的线程模式,主堆栈?
msr control, r0 @ Thread mode has unpriviledged access
@ Configure the System Control Register
ldr r0, =0xe000ed10 @ system control register
ldr r12, [r0] //不知道是干啥,不确定是否在E000 ED10上
orr r12, #1 << 9 @ STKALIGN
str r12, [r0] //E000 ED14的这个位倒是为了对齐双字节,指南-306
mov pc, lr //之前的lr值赋给PC,即PC转到那里执行
ENDPROC(__v7m_setup)
1.4 __switch_data
文件在arch\arm\kernel\head-common.S
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long _edata_loc @ r6 //added to determine the size of data segment to be copied.
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
#ifdef CONFIG_CPU_CP15
.long cr_alignment @ r7
#else
.long 0 @ r7
#endif
.long init_thread_union + THREAD_START_SP @ sp
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags pointer
* r9 = processor ID
*/
__mmap_switched: //从__v7m_setup出来以后,转到这里
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6}
cmp r4, r5 @ Copy data segment if needed
beq 2f
1: ldr fp, [r4], #4 //fp是r11
str fp, [r5], #4
cmp r4, r6
bne 1b
2: ldmia r3!,{r6,r7} @load __bss_start to r6 and _end to r7
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
itt cc //itt相当于if-then
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
str r9, [r4] @ Save processor ID被赋值
str r1, [r5] @ Save machine type被赋值
str r2, [r6] @ Save atags pointer被赋值
bic r4, r0, #CR_A @ Clear ‘A‘ bit 这句貌似没用?
#if !defined (CONFIG_CPU_V7M)
stmia r7, {r0, r4} @ Save control register values
#endif
@2: b 2b
b start_kernel //跳入C语言的函数
ENDPROC(__mmap_switched)