第一阶段:
/*====================================Hi3518c start.S Begin 2014-04-20=============================================*/
/*
* armboot - Startup Code for ARM926EJS CPU-core
*
* Copyright (c) 2003 Texas Instruments
*
* ----- Adapted for OMAP1610 OMAP730 from ARM925t code ------
*
* Copyright (c) 2001 Marius Gr?ger <[email protected]>
* Copyright (c) 2002 Alex Z?pke <[email protected]>
* Copyright (c) 2002 Gary Jennejohn <[email protected]>
* Copyright (c) 2003 Richard Woodruff <[email protected]>
* Copyright (c) 2003 Kshitij <[email protected]>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include
#include
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/
.globl _start //汇编程序都要提供一个_start符号并且用.globl声明
_start: b reset //B或BL指令引起处理器转移到“子程序名”处开始执行 复位
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
_pad: .word 0x12345678 /* now 16*4=64 */
/*.fill
语法:.fill repeat, size, value
含义是反复拷贝 size个字节,重复 repeat 次,
其中 size 和 value 是可选的,默认值分别为 1 和 0.
*/
__blank_zone_start:
.fill 1024*4,1,0 //给某个具体的寄存器里填数
__blank_zone_end:
.globl _blank_zone_start
_blank_zone_start:
.word __blank_zone_start
.globl _blank_zone_end
_blank_zone_end:
.word __blank_zone_end
.balignl 16,0xdeadbeef
/*
*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* setup Memory and board specific bits prior to relocation.
* relocate armboot to ram
* setup stack
*
*************************************************************************
*/
_TEXT_BASE:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
_clr_remap_spi_entry:
.word SF_TEXT_ADRS + do_clr_remap - TEXT_BASE
_clr_remap_nand_entry:
.word NAND_TEXT_ADRS + do_clr_remap - TEXT_BASE
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr //将状态寄存器的内容传送至通用寄存器,将CPSR中的内容传送至R0
bic r0,r0,#0x1f //位清除指令 将R0最低5位清零,其余位不变 工作模式位清零
orr r0,r0,#0xd3 //工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 "1101 0011" 指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中
msr cpsr,r0 //将通用寄存器的内容传送至状态寄存器,将中的内容R0传送至CPSR
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
/*
* flush v4 I/D caches
*/
mov r0, #0 //置零ro通用寄存器
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ //向c7写入0将使ICache与DCache无效 "0"表示省略opcode_2 MCR{
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ //MCR{条件} 协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 /* clear bits 13, 9:8 (--V- --RS) */
bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
orr r0, r0, #0x00000002 /* set bit 2 (A) Align */
mcr p15, 0, r0, c1, c0, 0 //保存r0到控制寄存器
mov r0, pc, lsr#24 //LSL、LSR、ASR、ROR 寄存器移位
cmp r0, #0x0
bne do_clr_remap //检测是否需要跳转,PC的高八位如果不为0(已经在ram中运行了)则跳转 不等于则调转
check_start_mode:
ldr r0, =REG_BASE_SCTL
ldr r0, [r0, #REG_SYSSTAT]
mov r6, r0, lsr#5
and r6, #0x1
/* reg[0x2005008c:5]:
* 0: start from spi
* 1: start from nand
*/
cmp r6, #BOOT_FROM_SPI
ldreq pc, _clr_remap_spi_entry
ldr pc, _clr_remap_nand_entry
@b . /* bug here */
/*
LDR和STR用来存取内存,关于"索引偏移",你是不是指pre-indexed addressing和post-indexed addressingpre-indexed addressing是指地址经过运算不写回基址寄存器post-indexed addressing则回写到基址寄存器比如pre-indexed addressing:mov r1,#0STR r0, [r1, #0x10] ;r1+0x10这个是所用的实际地址值,但是不回写入r1,在此句之后,r1=0post-indexed addressing:STR r0, [r1], #0x10 ;r1+0x10这个是所用的实际地址值,这个值回写入r1,此句之后,r1=0x10
*/
do_clr_remap:
ldr r4, =REG_BASE_SCTL //用来从存储器(确切地说是地址空间)中装载数据到通用寄存器 系统控制器寄存器 0x20050000 写地址
ldr r0, [r4, #REG_SC_CTRL] //加载32位的立即数或一个地址值到指定寄存器 不回写 其实是r4+#0x0是实际地址值
/* reg[0x20050000:8]:
* 0: keep remap
* 1: clear remap 重映射
*/
@Set clear remap bit.
orr r0, #(1<<8) //第八位置1
str r0, [r4, #REG_SC_CTRL] //不回写 @表示注释
@Setup TCM (ENABLED, 2KB) // TCM时钟门控使能
ldr r0, =( 1 | (MEM_CONF_ITCM_SIZE<<2) | MEM_BASE_ITCM)
mcr p15, 0, r0, c9, c1, 1
@enable I-Cache now
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
mcr p15, 0, r0, c1, c0, 0
@Check if I'm running in ddr //代码内存运行测试
mov r0, pc, lsr#28
cmp r0, #8
bleq relocate //小于等于跳转
ldr r0, _blank_zone_start
ldr r1, _TEXT_BASE //代码段
sub r0, r0, r1 //减法 sub a,b (a-b)
adrl r1, _start //将相对于程序或相对于寄存器的地址载入寄存器中 adrl宽
add r0, r0, r1 //加法
mov r1, #0 /* flags: 0->normal 1->pm */
bl init_registers //初始化寄存器
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:
@copy arm exception table in 0 address
adrl r0, _start
mov r1, #0
mov r2, #0x100 /* copy arm Exception table to 0 addr */
add r2, r0, r2
copy_exception_table:
ldmia r0!, {r3 - r10}
stmia r1!, {r3 - r10}
cmp r0, r2
ble copy_exception_table
@relocate U-Boot to RAM
adrl r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
bic sp, sp, #7 /*8-byte alignment for ABI compliance*/
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
/*
*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 /* clear bits 13, 9:8 (--V- --RS) */
bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
orr r0, r0, #0x00000002 /* set bit 2 (A) Align */
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
mcr p15, 0, r0, c1, c0, 0
/*
* Go setup Memory and board specific bits prior to relocation.
*/
mov ip, lr /* perserve link reg across call */
@bl lowlevel_init /* go setup pll,mux,memory */
mov lr, ip /* restore link */
mov pc, lr /* back to my caller */
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
#ifndef CONFIG_PRELOADER
/*
*************************************************************************
*
* Interrupt handling
*
*************************************************************************
*/
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define MODE_SVC 0x13
#define I_BIT 0x80
/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/
.macro bad_save_user_regs
@ carve out a frame on current user stack
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Save user registers (now in svc mode) r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE+CONFIG_SYS_MALLOC_LEN)
@ set base 2 words into abort stack
sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE+8)
@ get values for "aborted" pc and cpsr (into parm regs)
ldmia r2, {r2 - r3}
add r0, sp, #S_FRAME_SIZE @ grab pointer to old stack
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp @ save current stack into r0 (param register)
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
@ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm
.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into cpsr
.endm
.macro get_bad_stack
ldr r13, _armboot_start @ setup our mode stack
sub r13, r13, #(CONFIG_STACKSIZE+CONFIG_SYS_MALLOC_LEN)
@ reserved a couple spots in abort stack
sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE+8)
str lr, [r13] @ save caller lr in position 0 of saved stack
mrs lr, spsr @ get the spsr
str lr, [r13, #4] @ save spsr in position 1 of saved stack
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13 @ switch modes, make sure moves will execute
mov lr, pc @ capture return pc
movs pc, lr @ jump to next instruction & switch modes.
.endm
.macro get_irq_stack @ setup IRQ stack
ldr sp, IRQ_STACK_START
.endm
.macro get_fiq_stack @ setup FIQ stack
ldr sp, FIQ_STACK_START
.endm
#endif /* CONFIG_PRELOADER */
/*
* exception handlers
*/
#ifdef CONFIG_PRELOADER
.align 5 //加上.align汇编语句后,指令就对齐
do_hang:
ldr sp, _TEXT_BASE /* switch to abort stack */
1:
bl 1b /* hang and never return */
#else /* !CONFIG_PRELOADER */
.align 5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq
.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif
#endif /* CONFIG_PRELOADER */
#include "lowlevel_init.S"
/*====================================Hi3518c start.S End=============================================*/
最近一直在做U-boot和Linux内核的编译与移植的工作,就来讲一讲对U-boot的初步理解。我的目标板核心片是i.MX255,以下都是依据这个环境所言。
1.U-boot启动过程:
1)/uboot/cpu/arm926ejs/start.S文件是Uboot的入口程序。
2)/uboot/lib_arm/board.c Uboot执行的第一个C函数,完成系统的初始化。
3)init_sequence[] 是基本的初始化函数指针。
4)void start_armboot(void) 数序执行init_sequence[]数组中的初始化函数。
我把U-boot的运行过程简化描述如下:
check board->dram_init->flash init->nand init->env relocate->ip,mac获取->device init->网卡初始化->进入main_loop函数,等待串口输入(无输入则执行bootcmd命令)。
2.U-boot和内核的主要关系式内核启动过程中参数的传递。
U-boot会给Linux Kernel传递很多参数,如:串口,RAM,videofb等。而内核也会读取和处理这些参数。两者之间通过struct tag来传递参数。U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。
1、u-boot给kernel传参数:
在uboot/common/cmd_bootm.c文件中,bootm命令对应的do_bootm函数,当分析uImage中信息发现OS是Linux时,调用. /lib_arm/bootm.c文件中的do_bootm_linux函数来启动Linux kernel。
2、内核读取U-boot传递的相关参数:
对于Linux Kernel配合ARM平台启动时,先执行arch/arm/kernel/head.S,这个文件会调用arch/arm/kernel/head-common.S中的函数,在最后调用start_kernel。
在分析stext函数前,先介绍此时内存的布局如下图所示
在开发板tqs3c2440中,SDRAM连接到内存控制器的Bank6中,它的开始内存地址是0x30000000,大小为64M,即0x20000000。 ARM Linux kernel将SDRAM的开始地址定义为PHYS_OFFSET。经bootloader加载kernel并由自解压部分代码运行后,最终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。
在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址。
以arm kernel第一个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?)。实际上这个变换可以利用简单的公式进行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最终的kernel空间的页表,就是按照这个关系来建立。
之所以较早提及arm linux 的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一色的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数第一条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。因此,下面有些代码,需要使用地址无关技术。
这里的启动流程指的是解压后kernel开始执行的一部分代码,这部分代码和ARM体系结构是紧密联系在一起的,所以最好是将ARM ARCHITECTURE REFERENCE MANUL仔细读读,尤其里面关于控制寄存器啊,MMU方面的内容~
stext函数定义在Arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进第一个C语言函数start_kernel。
stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.
前面说过解压以后,代码会跳到解压完成以后的vmlinux开始执行,具体从什么地方开始执行我们可以看看生成的vmlinux.lds(arch/arm/kernel/)这个文件:
1. OUTPUT_ARCH(arm)
2. ENTRY(stext)
3. jiffies = jiffies_64;
4. SECTIONS
5. {
6. . = 0x80000000 + 0x00008000;
7. .text.head : {
8. _stext = .;
9. _sinittext = .;
10. *(.text.h
很明显我们的vmlinx最开头的section是.text.head,这里我们不能看ENTRY的内容,以为这时候我们没有操作系统,根本不知道如何来解析这里的入口地址,我们只能来分析他的section(不过一般来说这里的ENTRY和我们从seciton分析的结果是一样的),这里的.text.head section我们很容易就能在arch/arm/kernel/head.S里面找到,而且它里面的第一个符号就是我们的stext:
# .section ".text.head", "ax"
#
# ENTRY(stext)
#
# /* 设置CPU运行模式为SVC,并关中断 */
#
# msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
#
# @ and irqs disabled
#
# mrc p15, 0, r9, c0, c0 @ get processor id
#
# bl __lookup_processor_type @ r5=procinfo r9=cupid
#
# /* r10指向cpu对应的proc_info记录 */
#
# movs r10, r5 @ invalid processor (r5=0)?
#
# beq __error_p @ yes, error 'p'
#
# bl __lookup_machine_type @ r5=machinfo
#
# /* r8 指向开发板对应的arch_info记录 */
#
# movs r8, r5 @ invalid machine (r5=0)?
#
# beq __error_a @ yes, error 'a'
#
# /* __vet_atags函数涉及bootloader造知kernel物理内存的情况,我们暂时不分析它。 */
#
# bl __vet_atags
#
# /* 创建临时页表 */
#
# 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.
#
# */
#
# /* 这里的逻辑关系相当复杂,先是从proc_info结构中的中跳进__arm920_setup函数,
#
# * 然后执__enable_mmu 函数。最后在__enable_mmu函数通过mov pc, r13来执行__switch_data,
#
# * __switch_data函数在最后一条语句,鱼跃龙门,跳进第一个C语言函数start_kernel。
# */
#
# 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
#
# ENDPROC(stext)
这里的ENTRY这个宏实际我们可以在include/linux/linkage.h里面找到,可以看到他实际上就是声明一个GLOBAL Symbol,后面的ENDPROC和END唯一的区别是前面的声明了一个函数,可以在c里面被调用。
1. #ifndef ENTRY
2. #define ENTRY(name) /
3. .globl name; /
4. ALIGN; /
5. name:
6. #endif
7. #ifndef WEAK
8. #define WEAK(name) /
9. .weak name; /
10. name:
11. #endif
12. #ifndef END
13. #define END(name) /
14. .size name, .-name
15. #endif
16. /* If symbol 'name' is treated as a subroutine (gets called, and returns)
17. * then please use ENDPROC to mark 'name' as STT_FUNC for the benefit of
18. * static analysis tools such as stack depth analyzer.
19. */
20. #ifndef ENDPROC
21. #define ENDPROC(name) /
22. .type name, @function; /
23. END(name)
24. #endif
找到了vmlinux的起始代码我们就来进行分析了,先总体概括一下这部分代码所完成的功能,head.S会首先检查proc和arch以及atag的有效性,然后会建立初始化页表,并进行CPU必要的处理以后打开MMU,并跳转到start_kernel这个symbol开始执行后面的C代码。这里有很多变量都是我们进行kernel移植时需要特别注意的,下面会一一讲到。
在这里我们首先看看这段汇编开始跑的时候的寄存器信息,这里的寄存器内容实际上是同bootloader跳转到解压代码是一样的,就是r1=arch r2=atag addr。下面我们就具体来看看这个head.S跑的过程:
1. msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
2. @ and irqs disabled
3. mrc p15, 0, r9, c0, c0 @ get processor id
首先进入SVC模式并关闭所有中断,并从arm协处理器里面读到CPU ID,这里的CPU主要是指arm架构相关的CPU型号,比如ARM9,ARM11等等。
__lookup_processor_type 函数是一个非常讲究技巧的函数,如果你将它领会,也将领会kernel了一些魔法。
Kernel 代码将所有CPU信息的定义都放到.proc.info.init段中,因此可以认为.proc.info.init段就是一个数组,每个元素都定义了一个或一种CPU的信息。目前__lookup_processor_type使用该元素的前两个字段cpuid和mask来匹配当前CPUID,如果满足 CPUID & mask == cpuid,则找到当前cpu的定义并返回。
下面是tqs3c2440开发板,CPU的定义信息,cpuid = 0x41009200,mask = 0xff00fff0。如果是码是运行在tqs3c2440开发板上,那么函数返回下面的定义:
# __lookup_processor_type:
# /* adr 是相对寻址,它的寻计算结果是将当前PC值加上3f符号与PC的偏移量,
# * 而PC是物理地址,因此r3的结果也是3f符号的物理地址 */
#
# adr r3, 3f
#
# /* r5值为__proc_info_bein, r6值为__proc_ino_end,而r7值为.,
# * 也即3f符号的链接地址。请注意,在链接期间,__proc_info_begin和
# * __proc_info_end以及.均是链接地址,也即虚执地址。
# */
#
# ldmda r3, {r5 - r7}
#
# /* r3为3f的物理地址,而r7为3f的虚拟地址。结果是r3为虚拟地址与物理地址的差值,即PHYS_OFFSET - PAGE_OFFSET。*/
#
# sub r3, r3, r7 @ get offset between virt&phys
#
# /* r5为__proc_info_begin的物理地址, 即r5指针__proc_info数组的首地址 */
#
# add r5, r5, r3 @ convert virt addresses to
#
# /* r6为__proc_info_end的物理地址 */
#
# add r6, r6, r3 @ physical address space
#
# /* 读取r5指向的__proc_info数组元素的CPUID和mask值 */
#
# 1: ldmia r5, {r3, r4} @ value, mask
#
# /* 将当前CPUID和mask相与,并与数组元素中的CPUID比较是否相同
# * 若相同,则找到当前CPU的__proc_info定义,r5指向访元素并返回。
# */
#
# and r4, r4, r9 @ mask wanted bits
#
# teq r3, r4
#
# beq 2f
#
# /* r5指向下一个__proc_info元素 */
#
# add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
#
# /* 是否遍历完所有__proc_info元素 */
#
# cmp r5, r6
#
# blo 1b
#
# /* 找不到则返回NULL */
#
# mov r5, #0 @ unknown processor
#
# 2: mov pc, lr
#
# ENDPROC(__lookup_processor_type)
#
# .long __proc_info_begin
# .long __proc_info_end
# 3: .long .
# .long __arch_info_begin
# .long __arch_info_end
他这里的执行过程其实比较简单就是在__proc_info_begin和__proc_info_end这个段里面里面去读取我们注册在里面的proc_info_list这个结构体,这个结构体的定义在arch/arm/include/asm/procinfo.h,具体实现根据你使用的cpu的架构在arch/arm/mm/里面找到具体的实现,这里我们使用的ARM11是proc-v6.S,我们可以看看这个结构体:
1. .section ".proc.info.init", #alloc, #execinstr
2. /*
3. * Match any ARMv6 processor core.
4. */
5. .type __v6_proc_info, #object
6. _proc_info:
7. .long 0x0007b000
8. .long 0x0007f000
9. .long PMD_TYPE_SECT | /
10. PMD_SECT_BUFFERABLE | /
11. PMD_SECT_CACHEABLE | /
12. PMD_SECT_AP_WRITE | /
13. PMD_SECT_AP_READ
14. .long PMD_TYPE_SECT | /
15. PMD_SECT_XN | /
16. PMD_SECT_AP_WRITE | /
17. PMD_SECT_AP_READ
18. b __v6_setup
19. .long cpu_arch_name
20. .long cpu_elf_name
21. .long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA
22. .long cpu_v6_name
23. .long v6_processor_functions
24. .long v6wbi_tlb_fns
25. .long v6_user_fns
26. .long v6_cache_fns
27. .size __v6_proc_info, . - __v6_proc_info
对着.h我们就知道各个成员变量的含义了,他这里lookup的过程实际上是先求出这个proc_info_list的实际物理地址,并将其内容读出,然后将其中的mask也就是我们这里的0x007f000与寄存器与之后与0x007b00进行比较,如果一样的话呢就校验成功了,如果不一样呢就会读下一个proc_info的信息,因为proc一般都是只有一个的,所以这里一般不会循环,如果检测正确寄存器就会将正确的proc_info_list的物理地址赋给寄存器,如果检测不到就会将寄存器值赋0,然后通过LR返回
1. bl __lookup_machine_type @ r5=machinfo
2. movs r8, r5 @ invalid machine (r5=0)?
3. beq __error_a @ yes, error 'a'
检测完proc_info_list以后就开始检测machine_type了,这个函数的实现也在head-common.S里面,__lookup_machine_type 和__lookup_processor_type像对孪生兄弟,它们的行为都是很类似的:__lookup_machine_type根据r1寄存器的机器编号到.arch.info.init段的数组中依次查找机器编号与r1相同的记录。它使了与它孪生兄弟同样的手法进行虚拟地址到物理地址的转换计算。
我们看看它具体的实现:
1. __lookup_machine_type:
2. adr r3, 3b
3. ldmia r3, {r4, r5, r6}
4. sub r3, r3, r4 @ get offset between virt&phys
5. add r5, r5, r3 @ convert virt addresses to
6. add r6, r6, r3 @ physical address space
7. 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
8. teq r3, r1 @ matches loader number?
9. beq 2f @ found
10. add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
11. cmp r5, r6
12. blo 1b
13. mov r5, #0 @ unknown machine
14. 2: mov pc, lr
15. ENDPROC(__lookup_machine_type)
这里的过程基本上是同proc的检查是一样的,这里主要检查芯片的类型,比如我们现在的芯片是MSM7X27FFA,这也是一个结构体,它的头文件在arch/arm/include/asm/arch/arch.h里面(machine_desc),它具体的实现根据你对芯片类型的选择而不同,这里我们使用的是高通的7x27,具体实现在arch/arm/mach-msm/board-msm7x27.c里面,这些结构体最后都会注册到_arch_info_begin和_arch_info_end段里面,具体的大家可以看看vmlinux.lds或者system.map,这里的lookup会根据bootloader传过来的nr来在__arch_info里面的相匹配的类型,没有的话就寻找下一个machin_desk结构体,直到找到相应的结构体,并会将结构体的地址赋值给寄存器,如果没有的话就会赋值为0的。一般来说这里的machine_type会有好几个,因为不同的芯片类型可能使用的都是同一个cpu架构。
对processor和machine的检查完以后就会检查atags parameter的有效性,关于这个atag具体的定义我们可以在./include/asm/setup.h里面看到,它实际是一个结构体和一个联合体构成的结合体,里面的size都是以字来计算的。这里的atags param是bootloader创建的,里面包含了ramdisk以及其他memory分配的一些信息,存储在boot.img头部结构体定义的地址中,具体的大家可以看咱以后对bootloader的分析~
1. __vet_atags:
2. tst r2, #0x3 @ aligned?
3. bne 1f
4. ldr r5, [r2, #0] @ is first tag ATAG_CORE?
5. cmp r5, #ATAG_CORE_SIZE
6. cmpne r5, #ATAG_CORE_SIZE_EMPTY
7. bne 1f
8. ldr r5, [r2, #4]
9. ldr r6, =ATAG_CORE
10. cmp r5, r6
11. bne 1f
12. mov pc, lr @ atag pointer is ok
13. 1: mov r2, #0
14. mov pc, lr
15. ENDPROC(__vet_atags)
这里对atag的检查主要检查其是不是以ATAG_CORE开头,size对不对,基本没什么好分析的,代码也比较好看~ 下面我们来看后面一个重头戏,就是创建初始化页表,说实话这段内容我没弄清楚,它需要对ARM VIRT MMU具有相当的理解,这里我没有太多的时间去分析spec,只是粗略了翻了ARM V7的manu,知道这里建立的页表是arm的secition页表,完成内存开始1m内存的映射,这个页表建立在kernel和atag paramert之间,一般是4000-8000之间~具体的代码和过程我这里就不贴了,大家可以看看参考的链接,看看其他大虾的分析,我还没怎么看明白,等以后仔细研究ARM MMU的时候再回头来仔细研究了,不过代码虽然不分析,这里有几个重要的地址需要特别分析下~
这几个地址都定义在arch/arm/include/asm/memory.h,我们来稍微分析下这个头文件,首先它包含了arch/memory.h,我们来看看arch/arm/mach-msm/include/mach/memory.h,在这个里面定义了#define PHYS_OFFSET UL(0x00200000) 这个实际上是memory的物理内存初始地址,这个地址和我们以前在boardconfig.h里面定义的是一致的。然后我们再看asm/memory.h,他里面定义了我们的memory虚拟地址的首地址#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)。
另外我们在head.S里面看到kernel的物理或者虚拟地址的定义都有一个偏移,这个偏移又是从哪来的呢,实际我们可以从arch/arm/Makefile里面找到:textofs-y := 0x00008000 TEXT_OFFSET := $(textofs-y) 这样我们再看kernel启动时候的物理地址和链接地址,实际上它和我们前面在boardconfig.h和Makefile.boot里面定义的都是一致的~
建立初始化页表以后,会首先将__switch_data这个symbol的链接地址放在sp里面,然后获得__enable_mmu的物理地址,然后会跳到__proc_info_list里面的INITFUNC执行,这个偏移是定义在arch/arm/kernel/asm-offset.c里面,实际上就是取得__proc_info_list里面的__cpu_flush这个函数执行。
1. ldr r13, __switch_data @ address to jump to after
2. @ mmu has been enabled
3. adr lr, __enable_mmu @ return (PIC) address
4. add pc, r10, #PROCINFO_INITFUNC
这个__cpu_flush在这里就是我们proc-v6.S里面的__v6_setup函数了,具体它的实现我就不分析了,都是对arm控制寄存器的操作,这里转一下它对这部分操作的注释,看完之后就基本知道它完成的功能了。
/*
* __v6_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 ARMv6 cores.
*
* It is assumed that:
* - cache type register is implemented
*/
完成这部分关于CPU的操作以后,下面就是打开MMU了,这部分内容也没什么好说的,也是对arm控制寄存器的操作,打开MMU以后我们就可以使用虚拟地址了,而不需要我们自己来进行地址的重定位,ARM硬件会完成这部分的工作。打开MMU以后,会将SP的值赋给PC,这样代码就会跳到__switch_data来运行,这个__switch_data是一个定义在head-common.S里面的结构体,我们实际上是跳到它地一个函数指针__mmap_switched处执行的。
这个switch的执行过程我们只是简单看一下,前面的copy data_loc段以及清空.bss段就不用说了,它后面会将proc的信息和machine的信息保存在__switch_data这个结构体里面,而这个结构体将来会在start_kernel的setup_arch里面被使用到。这个在后面的对start_kernel的详细分析中会讲到。另外这个switch还涉及到控制寄存器的一些操作,这里我不没仔细研究spec,不懂也就不说了~
前面提及到,kernel里面的所有符号在链接时,都使用了虚拟地址值。在完成基本的初始化后,kernel代码将跳到第一个C语言函数 start_kernl来执行,在哪个时候,这些虚拟地址必须能够对它所存放在真正内存位置,否则运行将为出错。为此,CPU必须开启MMU,但在开启 MMU前,必须为虚拟地址到物理地址的映射建立相应的面表。在开启MMU后,kernel指并不马上将PC值指向start_kernl,而是要做一些C 语言运行期的设置,如堆栈,重定义等工作后才跳到start_kernel去执行。在此过程中,PC值还是物理地址,因此还需要为这段内存空间建立va = pa的内存映射关系。当然,本函数建立的所有页表都会在将来paging_init销毁再重建,这是临时过度性的映射关系和页表。
在介绍__create_table_pages前,先认识一个macro pgtbl,它将KERNL_RAM_PADDR – 0x4000的值赋给rd寄存器,从下面的使用中可以看它,该值是页表在物理内存的基础,也即页表放在kernel开始地址下的16K的地方。
# __create_page_tables:
# /* r4 = KERNEL_RAM_PADDR – 0x4000 = 0x30004000
# * 后面的C代码中的swapper_pg_dir变量,它的值也指向0x30004000
# * 内存地址,不过它的值是虚拟内存地址,即0xc0004000
# */
# pgtbl r4 @ page table address
#
# /* 将从r4到KERNEL_RAP_PADDR的16K页表空间清空。 */
#
# mov r0, r4
# mov r3, #0
# add r6, r0, #0x4000
#
# 1: str r3, [r0], #4
# str r3, [r0], #4
# str r3, [r0], #4
# str r3, [r0], #4
# teq r0, r6
# bne 1b
#
#
# /* 还记得r10指向开发板相应的proc_info元素吗?这里它将的mm_mmuflags值读到r7中。
# * PROCINFO_MM_MMUFLAGS值为8,可对应上面列出来的__arm920_proc_info结构或你相应开发板结构的值来查看该mmu_flags值。
# * 这里的flags就是用于设置目录项的flags。查看该mmu_flags的定义,发现它是要求一级页表是section。
# */
#
# ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
#
# /*
# * Create identity mapping for first MB of kernel to
# * cater for the MMU enable. This identity mapping
# * will be removed by paging_init(). We use our current program
# * counter to determine corresponding section base address.
# */
#
# /* r3 = ((pc >> 20) << 20) | r7, 即取PC以1M向下对齐的地址。R6 = pc >> 20也即r6 = 0x300(pgd_idx),
# * 即PC对所有1M内存空间,在页表中的下标。
# * R7值表明该目录项是section,即它映射的大小是1M。故刚好一个目录项就可以映射kernel上的1M空间。
# * 这个暂时的va = pa映射只建立1M大小内存的,而不需要建立整个kernel镜像范围的映射。
# * 因为这个va = pa的映射只有当前汇编语言才使用,一量跳进start_kernl后,这将不会用到了。而汇编代码在链接时,
# * 已将它安排到代码段的最前面了。
#
# mov r6, pc, lsr #20 @ start of kernel section
# orr r3, r7, r6, lsl #20 @ flags + kernel base
#
# /* 将目录内空写到页表相应位置,即((uint32_t *)r4)[pgd_idx] = r3 */
#
# str r3, [r4, r6, lsl #2] @ identity mapping
#
#
# /* 上面代码段为[pc &(~0xfffff), (pc + 0xfffff) &(~0xfffff)]的物理内存空间建立了va = pa的映射关系。*/
#
# /* 下面为kernel镜像所占有空间,即KERNL_START到KERNEL_END建立内存映射,
# * 映射关系为:va = pa – PHYS + PAGR_OFFSET。注意,这里的KENEL_START是kernel空间开始的虚拟地址。
# * 这里的目录表项同样是section,即一个项映射1M的内存。
# */
#
#
# /* KERNEL_START = PAGE_OFFSET + TEXT_OFFSET,
# * r0 = ((uint32_t *)(r4))[ (KERNEL_START & 0xff000000) >> 20],
# * 即r0指向KERNEL_START& 0xff000000(即kernel以16M向下对齐的)虚拟地址,所在项表目录中的位置。
#
# add r0, r4, #(KERNEL_START & 0xff000000) >> 18
#
# /* r0 = ((uint32_t *)r0)[(KERNEL_START & 0x00f00000) >> 20]
# * 执行前r0指向kernel以16M向下对齐的虚执地址,而这里再加上KERNEL_START未以16M向对齐部分的偏移量。
# * 将原来r3的值写到页表目录中。R3的值就是之前已建立好va=pa映射的那个PA值。
# */
#
# str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
#
# /* r6为kernel镜像的尾部虚拟地址。*/
#
# ldr r6, =(KERNEL_END - 1)
#
# /* 指向下一个即将要填写的目录项 */
#
# add r0, r0, #4
#
# /* r6指向KERNEL_END- 1虚拟地址所在的目录表项的位置 */
#
# add r6, r4, r6, lsr #18
# 1: cmp r0, r6
#
# /* 每填一个目录项,后一个比前一个所指向的物理地址大1M。*/
# add r3, r3, #1 << 20
# strls r3, [r0], #4
# bls 1b
#
# #ifdef CONFIG_XIP_KERNEL
# /* 忽略,不分析这种情况 */
# #endif
#
# /* 通常kernel的启动参数由bootloader放到了物理内存的第1个M上,所以需要为RAM上的第1个M建立映射。
# * 上面已为PHYS_OFFSET + TEXT_OFFSET建立了映射,如果TEXT_OFFSET小于0x00100000的话,
# * 上面代码应该也为SDRAM的第一个M建立了映射,但如果大于0x0010000则不会。
# * 所以这里无论如何均为SDRAM的第一个M建立映射(不知分析对否,还请指正)。
# */
# add r0, r4, #PAGE_OFFSET >> 18
# orr r6, r7, #(PHYS_OFFSET & 0xff000000)
# .if (PHYS_OFFSET & 0x00f00000)
# orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
# .endif
# str r6, [r0]
#
# #ifdef CONFIG_DEBUG_LL
# /*略去 */
# #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
# /* 略去 */
# #endif
#
# #ifdef CONFIG_ARCH_RPC
# /* 略去 */
# #endif
#
# #endif
#
# mov pc, lr
# ENDPROC(__create_page_tables)
看完页表的建立,想必开启MMU的代码也是小菜一碟吧。此函数的主要功能是将页表的基址加到cp15中的面表指针寄存器,同时设置域访问(domain access)寄存器。
1. /*
2. * Setup common bits before finally enabling the MMU. Essentially
3. * this is just loading the page table pointer and domain access
4. * registers.
5. */
6. __enable_mmu:
7. /* 这里设置是否为非对齐内存访问产生异常 */
8. #ifdef CONFIG_ALIGNMENT_TRAP
9. orr r0, r0, #CR_A
10. #else
11. bic r0, r0, #CR_A
12. #endif
13. /* 是否禁用数据缓存功能*/
14. #ifdef CONFIG_CPU_DCACHE_DISABLE
15. bic r0, r0, #CR_C
16. #endif
17. /* 是否禁用CPU_BPREDICT ?,不是很清楚此选项 */
18. #ifdef CONFIG_CPU_BPREDICT_DISABLE
19. bic r0, r0, #CR_Z
20. #endif
21. /* 是否禁用指令缓存功能 */
22. #ifdef CONFIG_CPU_ICACHE_DISABLE
23. bic r0, r0, #CR_I
24. #endif
25.
26. /* 设置域访问寄存器的值。这里设置每个domain的属性是否上面建立的页表中,
27. * 每个目录项的damon值一起进行访问控制检查。具体情况请参考ARM处理器手册。
28. */
29.
30. mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
31. domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
32. domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
33. domain_val(DOMAIN_IO, DOMAIN_CLIENT))
34. mcr p15, 0, r5, c3, c0, 0 @ load domain access register
35. mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
36. b __turn_mmu_on
37. ENDPROC(__enable_mmu)
38.
39. /*
40. * Enable the MMU. This completely changes the structure of the visible
41. * memory space. You will not be able to trace execution through this.
42. * If you have an enquiry about this, *please* check the linux-arm-kernel
43. * mailing list archives BEFORE sending another post to the list.
44. *
45. * r0 = cp#15 control register
46. * r13 = *virtual* address to jump to upon completion
47. *
48. * other registers depend on the function called upon completion
49. */
50.
51. .align 5
52. __turn_mmu_on:
53. mov r0, r0
54.
55. /* 将r0的值写到控制寄存器中。这里,终于开启MMU功能了。
56. * 查阅手册说控制寄存器的0位置1表示开启MMU,但这里r0的第0是多少呢(还请大家指正)
57. */
58.
59. mcr p15, 0, r0, c1, c0, 0 @ write control reg
60. mrc p15, 0, r3, c0, c0, 0 @ read id reg
61.
62. /* 这里的两个mov似乎是否流水线有关的,开启MMU语句后面几条是不能进行内存寻址的。但仍未搞明白具体东西的。*/
63. mov r3, r3
64. mov r3, r3
65.
66. /* 转跳到r13的函数中去,r13为__mmap_switched函数的虚拟地址,
67. * 从stext函数的未尾可以找到它的赋值。故从此开始pc的值就真正在内存的虚拟地址空间了。
68. */
69.
70. mov pc, r13
71. ENDPROC(__turn_mmu_on)
__mmap_switched函数专用来设置C语言的执行环境,比如重定位工作,堆栈,以及BSS段的清零。
__switch_data变量先定义了一系里面处量的数据,如重定位和数据段的地址,BSS段的地址,pocessor_id和__mach_arch_type变量的地址等。