uboot源码分析(基于S5PV210)之启动第一阶段

目录

  • 一、start.S引入
    • 1、u-boot.lds中找到start.S入口
    • 2、SourceInsight中如何找到文件
    • 3、SI中找文件技巧
  • 二、start.S解析
    • 1、不简单的头文件包含
    • 2、启动代码的16字节头部
    • 3、异常向量表的构建
    • 4、有点意思的deadbeef
    • 5、TEXT_BASE、CFG_PHY_UBOOT_BASE
    • 6、设置CPU为SVC模式
    • 7、设置L2、L1cache和MMU(200行开始)
    • 8、识别并暂存启动介质选择
    • 9、设置栈(SRAM中的栈)并调用lowlevel_init
    • 10、检查复位状态(lowlevel_init.S 的 44-52行)
    • 11、lowlevel_init函数中的其他工作
    • 12、再次设置栈(DDR中的栈)
    • 13、再次判断当前地址以决定是否重定位(start.S 301行)
    • 14、uboot重定位详解
    • 15、MMU单元的作用
    • 16、设置使能MMU(359-382行)
    • 17、第一阶段启动剩余工作
  • 三、总结

一、start.S引入

1、u-boot.lds中找到start.S入口

(1)在C语言中,整个项目的入口就是main函数(这是C语言规定的),所以譬如说一个有10000个.c文件的项目,第一个要分析的文件就是包含了main函数的那个文件。

(2)在uboot中因为有汇编阶段参与,因此不能直接找main.c。整个程序的入口取决于链接脚本中ENTRY声明的地方ENTRY(_start)因此_start符号所在的文件就是整个程序的起始文件,_start所在处的代码就是整个程序的起始代码

2、SourceInsight中如何找到文件

(1)当前状况:我们知道在uboot中的1000多个文件中有一个符号叫_start,但是我们不知道这个符号在哪个文件中。这种情况下要查找一个符号在所有项目中文件中的引用,要使用SourceInsight的搜索功能。

(2)利用SourceInsight搜索到一共7个_start,然后分析搜索出来的7处,发现有2个是api_example,2个是onenand相关的,都不是我们要找的。剩下3个都在uboot/cpu/s5pc11x/start.S文件中。

(3)然后进入start.S文件中,发现57行中就是_start标号的定义处,于是乎我们就找到了整个uboot的入口代码,就是第57行。start.S如下所示。

/*
 *  armboot - Startup Code for S5PC110/ARM-Cortex CPU-core
 *
 *  Copyright (c) 2009	Samsung Electronics
 *
 *
 * 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
 *
 * Base codes by scsuh (sc.suh)
 */

#include 
#include 
#if defined(CONFIG_ENABLE_MMU)
#include 
#endif
#include 


/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */

#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
	.word 0x2000
	.word 0x0
	.word 0x0
	.word 0x0
#endif

.globl _start
_start: b	reset
	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 */
.global _end_vect
_end_vect:

	.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

/*
 * Below variable is very important because we use MMU in U-Boot.
 * Without it, we cannot run code correctly before MMU is ON.
 * by scsuh.
 */
_TEXT_PHY_BASE:
	.word	CFG_PHY_UBOOT_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


/*
 * the actual reset code
 */

reset:
	/*
	 * set the cpu to SVC32 mode and IRQ & FIQ disable
	 */
	msr	cpsr_c, #0xd3		@ I & F disable, Mode: 0x13 - SVC


/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */
         /*
         * we do sys-critical inits only at reboot,
         * not when booting from ram!
         */
cpu_init_crit:
	
	bl	disable_l2cache

	bl	set_l2cache_auxctrl_cycle

	bl	enable_l2cache
	
       /*
        * Invalidate L1 I/D
        */
        mov	r0, #0                  @ set up for MCR
        mcr	p15, 0, r0, c8, c7, 0   @ invalidate TLBs
        mcr	p15, 0, r0, c7, c5, 0   @ invalidate icache

       /*
        * disable MMU stuff and caches
        */
        mrc	p15, 0, r0, c1, c0, 0
        bic	r0, r0, #0x00002000     @ clear bits 13 (--V-)
        bic	r0, r0, #0x00000007     @ clear bits 2:0 (-CAM)
        orr	r0, r0, #0x00000002     @ set bit 1 (--A-) Align
        orr	r0, r0, #0x00000800     @ set bit 12 (Z---) BTB
        mcr 	p15, 0, r0, c1, c0, 0


        /* Read booting information */
        ldr	r0, =PRO_ID_BASE
        ldr	r1, [r0,#OMR_OFFSET]
        bic	r2, r1, #0xffffffc1


	/* NAND BOOT */
	cmp	r2, #0x0		@ 512B 4-cycle
	moveq	r3, #BOOT_NAND

	cmp	r2, #0x2		@ 2KB 5-cycle
	moveq	r3, #BOOT_NAND

	cmp	r2, #0x4		@ 4KB 5-cycle	8-bit ECC
	moveq	r3, #BOOT_NAND

	cmp	r2, #0x6		@ 4KB 5-cycle	16-bit ECC
	moveq	r3, #BOOT_NAND

	cmp	r2, #0x8		@ OneNAND Mux
	moveq	r3, #BOOT_ONENAND

	/* SD/MMC BOOT */
	cmp     r2, #0xc
	moveq   r3, #BOOT_MMCSD	

	/* NOR BOOT */
	cmp     r2, #0x14
	moveq   r3, #BOOT_NOR	

	/* Uart BOOTONG failed */
	cmp     r2, #(0x1<<4)
	moveq   r3, #BOOT_SEC_DEV
	
	ldr	r0, =INF_REG_BASE
	str	r3, [r0, #INF_REG3_OFFSET]     

	/*
	 * Go setup Memory and board specific bits prior to relocation.
	 */

	ldr	sp, =0xd0036000 /* end of sram dedicated to u-boot */
	sub	sp, sp, #12	/* set stack */
	mov	fp, #0
	
	bl	lowlevel_init	/* go setup pll,mux,memory */
	/* To hold max8698 output before releasing power on switch,
	 * set PS_HOLD signal to high
	 */
	ldr	r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
	ldr	r1, =0x00005301	 /* PS_HOLD output high	*/
	str	r1, [r0]

	/* get ready to call C functions */
	ldr	sp, _TEXT_PHY_BASE	/* setup temp stack pointer */
	sub	sp, sp, #12
	mov	fp, #0			/* no previous frame, so fp=0 */

	/* when we already run in ram, we don't need to relocate U-Boot.
	 * and actually, memory controller must be configured before U-Boot
	 * is running in ram.
	 */
	ldr	r0, =0xff000fff
	bic	r1, pc, r0		/* r0 <- current base addr of code */
	ldr	r2, _TEXT_BASE		/* r1 <- original base addr in ram */
	bic	r2, r2, r0		/* r0 <- current base addr of code */
	cmp     r1, r2                  /* compare r0, r1                  */
	beq     after_copy		/* r0 == r1 then skip flash copy   */

#if defined(CONFIG_EVT1)
	/* If BL1 was copied from SD/MMC CH2 */
	ldr	r0, =0xD0037488
	ldr	r1, [r0]
	ldr	r2, =0xEB200000
	cmp	r1, r2
	beq     mmcsd_boot
#endif

	ldr	r0, =INF_REG_BASE
	ldr	r1, [r0, #INF_REG3_OFFSET]
	cmp	r1, #BOOT_NAND		/* 0x0 => boot device is nand */
	beq	nand_boot
	cmp	r1, #BOOT_ONENAND	/* 0x1 => boot device is onenand */
	beq	onenand_boot
	cmp     r1, #BOOT_MMCSD
	beq     mmcsd_boot
	cmp     r1, #BOOT_NOR
	beq     nor_boot
	cmp     r1, #BOOT_SEC_DEV
	beq     mmcsd_boot

nand_boot:
	mov	r0, #0x1000
	bl	copy_from_nand
	b	after_copy

onenand_boot:
	bl	onenand_bl2_copy
	b	after_copy

mmcsd_boot:
#if DELETE
	ldr     sp, _TEXT_PHY_BASE      
	sub     sp, sp, #12
	mov     fp, #0
#endif
	bl      movi_bl2_copy
	b       after_copy

nor_boot:
	bl      read_hword
	b       after_copy


after_copy:

#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
	/* enable domain access */
	ldr	r5, =0x0000ffff
	mcr	p15, 0, r5, c3, c0, 0		@load domain access register

	/* Set the TTB register */
	ldr	r0, _mmu_table_base
	ldr	r1, =CFG_PHY_UBOOT_BASE
	ldr	r2, =0xfff00000
	bic	r0, r0, r2
	orr	r1, r0, r1
	mcr	p15, 0, r1, c2, c0, 0

	/* Enable the MMU */
mmu_on:
	mrc	p15, 0, r0, c1, c0, 0
	orr	r0, r0, #1
	mcr	p15, 0, r0, c1, c0, 0
	nop
	nop
	nop
	nop
#endif

skip_hw_init:
	/* Set up the stack						    */
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
	ldr	sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
#if defined(CONFIG_USE_IRQ)
	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
	sub	sp, r0, #12		/* leave 3 words for abort-stack    */

#endif

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

#if defined(CONFIG_ENABLE_MMU)
_mmu_table_base:
	.word mmu_table
#endif

/*
 * copy U-Boot to SDRAM and jump to ram (from NAND or OneNAND)
 * r0: size to be compared
 * Load 1'st 2blocks to RAM because U-boot's size is larger than 1block(128k) size
 */
	.globl copy_from_nand
copy_from_nand:
	push	{lr}		/* save return address */

	mov	r9, r0
	
	mov	r9, #0x100		/* Compare about 8KB */
	bl	copy_uboot_to_ram
	tst 	r0, #0x0
	bne	copy_failed

#if defined(CONFIG_EVT1)
	ldr	r0, =0xd0020000
#else	
	ldr	r0, =0xd0030000
#endif
	ldr	r1, _TEXT_PHY_BASE	/* 0x23e00000 */

#if !defined(CONFIG_SECURE_BOOT)
1:	ldr	r3, [r0], #4
	ldr	r4, [r1], #4
	teq	r3, r4
	bne	compare_failed	/* not matched */
	subs	r9, r9, #4
	bne	1b
#endif
	pop	{pc}		/* all is OK */

copy_failed:
	nop			/* copy from nand failed */
	b	copy_failed

compare_failed:
	nop			/* compare failed */
	b	compare_failed

/*
 * we assume that cache operation is done before. (eg. cleanup_before_linux())
 * actually, we don't need to do anything about cache if not use d-cache in U-Boot
 * So, in this function we clean only MMU. by scsuh
 *
 * void	theLastJump(void *kernel, int arch_num, uint boot_params);
 */
#if defined(CONFIG_ENABLE_MMU)
	.globl theLastJump
theLastJump:
	mov	r9, r0
	ldr	r3, =0xfff00000
	ldr	r4, _TEXT_PHY_BASE
	adr	r5, phy_last_jump
	bic	r5, r5, r3
	orr	r5, r5, r4
	mov	pc, r5
phy_last_jump:
	/*
	 * disable MMU stuff
	 */
	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

	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */

	mov	r0, #0
	mov	pc, r9
#endif
/*
 *************************************************************************
 *
 * 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
	sub	sp, sp, #S_FRAME_SIZE		@ carve out a frame on current user stack
	stmia	sp, {r0 - r12}			@ Save user registers (now in svc mode) r0-r12

	ldr	r2, _armboot_start
	sub	r2, r2, #(CFG_MALLOC_LEN)
	sub	r2, r2, #(CFG_GBL_DATA_SIZE+8)	@ set base 2 words into abort stack
	ldmia	r2, {r2 - r3}			@ get values for "aborted" pc and cpsr (into parm regs)
	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
	add	r8, sp, #S_PC			@ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
	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 (enter in banked mode)
	sub	r13, r13, #(CFG_MALLOC_LEN)	@ move past malloc pool
	sub	r13, r13, #(CFG_GBL_DATA_SIZE+8) @ move to reserved a couple spots for abort stack

	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_bad_stack_swi
	sub	r13, r13, #4			@ space on current stack for scratch reg.
	str	r0, [r13]			@ save R0's value.
	ldr	r0, _armboot_start		@ get data regions start
	sub	r0, r0, #(CFG_MALLOC_LEN)	@ move past malloc pool
	sub	r0, r0, #(CFG_GBL_DATA_SIZE+8)	@ move past gbl and a couple spots for abort stack
	str	lr, [r0]			@ save caller lr in position 0 of saved stack
	mrs	r0, spsr			@ get the spsr
	str	lr, [r0, #4]			@ save spsr in position 1 of saved stack
	ldr	r0, [r13]			@ restore r0
	add	r13, r13, #4			@ pop stack entry
	.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

/*
 * exception handlers
 */
	.align	5
undefined_instruction:
	get_bad_stack
	bad_save_user_regs
	bl	do_undefined_instruction

	.align	5
software_interrupt:
	get_bad_stack_swi
	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

#if defined(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
	.align 5
.global arm_cache_flush
arm_cache_flush:
       mcr     p15, 0, r1, c7, c5, 0           @ invalidate I cache
       mov     pc, lr                          @ back to caller

/*
 *     v7_flush_dcache_all()
 *
 *     Flush the whole D-cache.
 *
 *     Corrupted registers: r0-r5, r7, r9-r11
 *
 *     - mm    - mm_struct describing address space
 */
       .align 5
.global v7_flush_dcache_all
v7_flush_dcache_all:

	ldr	r0, =0xffffffff
	mrc	p15, 1, r0, c0, c0, 1 		@ Read CLIDR
	ands	r3, r0, #0x7000000
	mov	r3, r3, LSR #23       		@ Cache level value (naturally aligned)
	beq 	Finished
	mov	r10, #0
Loop1:         
	add	r2, r10, r10, LSR #1  		@ Work out 3xcachelevel
	mov	r1, r0, LSR r2        		@ bottom 3 bits are the Ctype for this level
	and	r1, r1, #7            		@ get those 3 bits alone
	cmp	r1, #2
	blt	Skip                   		@ no cache or only instruction cache at this level
	mcr	p15, 2, r10, c0, c0, 0 		@ write the Cache Size selection register
	mov	r1, #0
	mcr	p15, 0, r1, c7, c5, 4 		@ PrefetchFlush to sync the change to the CacheSizeID reg
	mrc	p15, 1, r1, c0, c0, 0 		@ reads current Cache Size ID register
	and	r2, r1, #0x7           		@ extract the line length field
	add	r2, r2, #4            		@ add 4 for the line length offset (log2 16 bytes)
	ldr	r4, =0x3FF
	ands	r4, r4, r1, LSR #3   		@ R4 is the max number on the way size (right aligned)
	clz	r5, r4                		@ R5 is the bit position of the way size increment
	ldr	r7, =0x00007FFF
	ands	r7, r7, r1, LSR #13  		@ R7 is the max number of the index size (right aligned)
Loop2:         
	mov	r9, r4                      	@ R9 working copy of the max way size (right aligned)
Loop3:         
	orr	r11, r10, r9, LSL r5        	@ factor in the way number and cache number into R11
	orr	r11, r11, r7, LSL r2        	@ factor in the index number
	mcr	p15, 0, r11, c7, c6, 2 		@ invalidate by set/way
	subs	r9, r9, #1                 	@ decrement the way number
	bge	Loop3
	subs	r7, r7, #1                 	@ decrement the index
	bge	Loop2
Skip:          
	add	r10, r10, #2                	@ increment the cache number
	cmp	r3, r10
	bgt	Loop1
Finished:
	mov	pc, lr
	
       .align  5
.global disable_l2cache
disable_l2cache:
	mrc     p15, 0, r0, c1, c0, 1
	bic     r0, r0, #(1<<1)
	mcr     p15, 0, r0, c1, c0, 1
	mov	pc, lr


       .align  5
.global enable_l2cache
enable_l2cache:
	mrc     p15, 0, r0, c1, c0, 1
	orr     r0, r0, #(1<<1)
	mcr     p15, 0, r0, c1, c0, 1
	mov     pc, lr

       .align  5
.global set_l2cache_auxctrl
set_l2cache_auxctrl:
	mov	r0, #0x0
	mcr     p15, 1, r0, c9, c0, 2
	mov     pc, lr

       .align  5
.global set_l2cache_auxctrl_cycle
set_l2cache_auxctrl_cycle:
	mrc 	p15, 1, r0, c9, c0, 2
	bic 	r0, r0, #(0x1<<29)
	bic 	r0, r0, #(0x1<<21)
	bic 	r0, r0, #(0x7<<6)
	bic 	r0, r0, #(0x7<<0)
	mcr 	p15, 1, r0, c9, c0, 2
	mov     pc,lr

	.align 5
CoInvalidateDCacheIndex:
	;/* r0 = index */
	mcr     p15, 0, r0, c7, c6, 2
	mov     pc,lr


#if defined(CONFIG_INTEGRATOR) && defined(CONFIG_ARCH_CINTEGRATOR)
/* Use the IntegratorCP function from board/integratorcp/platform.S */
#elif defined(CONFIG_S5PC11X)
/* For future usage of S3C64XX*/
#else
	.align	5
.globl reset_cpu
reset_cpu:
	ldr	r1, rstctl	/* get addr for global reset reg */
	mov	r3, #0x2	/* full reset pll+mpu */
	str	r3, [r1]	/* force reset */
	mov	r0, r0
_loop_forever:
	b	_loop_forever
rstctl:
	.word	PM_RSTCTRL_WKUP

#endif

3、SI中找文件技巧

(1)以上,找到了start.S文件,下面我们就从start.S文件开始分析uboot第一阶段。

(2)在SourceInsight中,如果我们知道我们要找的文件的名字,但是我们又不知道他在哪个目录下,我们要怎样找到并打开这个文件?方法是在SourceInsight中先打开右边的工程项目管理栏目,然后点击最左边那个(这个是以文件为单位来浏览的),然后在上面输入栏中输入要找的文件的名字。我们在输入的时候,SI在不断帮我们进行匹配,即使你不记得文件的全名只是大概记得名字,也能帮助你找到你要找的文件。
uboot源码分析(基于S5PV210)之启动第一阶段_第1张图片

二、start.S解析

1、不简单的头文件包含

(1)#include 。config.h是在include目录下的,这个文件不是源码中本身存在的文件,而是配置过程中自动生成的文件。(详见上一篇文章中所讲的mkconfig脚本)。config.h这个文件的内容其实是包含了一个头文件:#include ".
uboot源码分析(基于S5PV210)之启动第一阶段_第2张图片
(2)经过分析后,发现start.S中包含的第一个头文件就是:include/configs/x210_sd.h,这个文件是整个uboot移植时的配置文件。这里面是好多宏。因此这个头文件包含将include/configs/x210_sd.h文件和start.S文件关联了起来。因此之后在分析start.S文件时,主要要考虑的就是x210_sd.h文件。

(3)#include 。include/version.h中包含了include/version_autogenerated.h,这个头文件就是配置过程中自动生成的。里面就一行内容:#define U_BOOT_VERSION “U-Boot 1.3.4”。这里面定义的宏U_BOOT_VERSION的值是一个字符串,字符串中的版本号信息来自于Makefile中的配置值。这个宏在程序中会被调用,在uboot启动过程中会串口打印出uboot的版本号,那个版本号信息就是从这来的

(4)#include 。asm目录不是uboot中的原生目录,uboot中本来是没有这个目录的。asm目录是配置时创建的一个符号链接,实际指向的是就是asm-arm(详解本专栏上篇文章分析mkconfig脚本部分内容),经过分析后发现,实际文件是:include/asm-arm/proc-armv/domain.h。

  从这里可以看出之前配置时创建的符号链接的作用,如果没有这些符号链接则编译时根本通不过,因为找不到头文件。(所以uboot不能在windows的共享文件夹下配置编译,因为windows中没有符号链接)

  思考:为什么start.S不直接包含asm-arm/proc-armv/domain.h,而要用asm/proc/domain.h?

  这样的设计主要是为了可移植性。因为如果直接包含,则start.S文件和CPU架构(和硬件)有关了,可移植性就差了。譬如我要把uboot移植到mips架构下,则start.S源代码中所有的头文件包含全部要修改。我们用了符号链接之后,则start.S中源代码不用改,只需要在具体的硬件移植时配置不同,创建的符号链接指向的不同,则可以具有可移植性

2、启动代码的16字节头部

(1)在我的开发板SD卡启动/Nand启动等整个uboot镜像开头需要16字节的校验头。(我的开发板配套资料提供了mkv210image.c文件,就是为了计算这个校验头)。

/*
 * mkv210_image.c的主要作用就是由usb启动时使用的led.bin制作得到由sd卡启动的镜像210.bin
 *
 * 本文件来自于友善之臂的裸机教程,据友善之臂的文档中讲述,本文件是一个热心网友提供,在此表示感谢。
 */
/* 在BL0阶段,Irom内固化的代码读取nandflash或SD卡前16K的内容,
 * 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。
 */
#include 
#include 
#include 

#define BUFSIZE                 (16*1024)
#define IMG_SIZE                (16*1024)
#define SPL_HEADER_SIZE         16
//#define SPL_HEADER              "S5PC110 HEADER  "
#define SPL_HEADER              "****************"

int main (int argc, char *argv[])
{
	FILE		*fp;
	char		*Buf, *a;
	int		BufLen;
	int		nbytes, fileLen;
	unsigned int	checksum, count;
	int		i;
	
	// 1. 3个参数
	if (argc != 3)
	{
		printf("Usage: %s  \n", argv[0]);
		return -1;
	}

	// 2. 分配16K的buffer
	BufLen = BUFSIZE;
	Buf = (char *)malloc(BufLen);
	if (!Buf)
	{
		printf("Alloc buffer failed!\n");
		return -1;
	}

	memset(Buf, 0x00, BufLen);

	// 3. 读源bin到buffer
	// 3.1 打开源bin
	fp = fopen(argv[1], "rb");
	if( fp == NULL)
	{
		printf("source file open error\n");
		free(Buf);
		return -1;
	}
	// 3.2 获取源bin长度
	fseek(fp, 0L, SEEK_END);								// 定位到文件尾
	fileLen = ftell(fp);									// 得到文件长度
	fseek(fp, 0L, SEEK_SET);								// 再次定位到文件头
	// 3.3 源bin长度不得超过16K-16byte
	count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
		? fileLen : (IMG_SIZE - SPL_HEADER_SIZE);
	// 3.4 buffer[0~15]存放"S5PC110 HEADER  "
	memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE);
	// 3.5 读源bin到buffer[16]
	nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp);
	if ( nbytes != count )
	{
		printf("source file read error\n");
		free(Buf);
		fclose(fp);
		return -1;
	}
	fclose(fp);

	// 4. 计算校验和
 	// 4.1 从第16byte开始统计buffer中共有几个1
	// 4.1 从第16byte开始计算,把buffer中所有的字节数据加和起来得到的结果
	a = Buf + SPL_HEADER_SIZE;
	for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
		checksum += (0x000000FF) & *a++;
	// 4.2 将校验和保存在buffer[8~15]
	a = Buf + 8;							// Buf是210.bin的起始地址,+8表示向后位移2个字,也就是说写入到第3个字
	*( (unsigned int *)a ) = checksum;

	// 5. 拷贝buffer中的内容到目的bin
	// 5.1 打开目的bin
	fp = fopen(argv[2], "wb");
	if (fp == NULL)
	{
		printf("destination file open error\n");
		free(Buf);
		return -1;
	}
	// 5.2 将16k的buffer拷贝到目的bin中
	a = Buf;
	nbytes	= fwrite( a, 1, BufLen, fp);
	if ( nbytes != BufLen )
	{
		printf("destination file write error\n");
		free(Buf);
		fclose(fp);
		return -1;
	}

	free(Buf);
	fclose(fp);

	return 0;
}

(2)uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。
uboot源码分析(基于S5PV210)之启动第一阶段_第3张图片

3、异常向量表的构建

(1)异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。

(2)异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在uboot中并未非常细致的处理各种异常。(原因:uboot代码运行运行时间短,出问题概率较小,若出现重启即可。但操作系统内核处理十分细致。)

(3)复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,因此reset符号处才是真正的有意义的代码开始的地方。
uboot源码分析(基于S5PV210)之启动第一阶段_第4张图片

4、有点意思的deadbeef

(1).balignl 16,0xdeadbeef,这一句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。

(2)0xdeadbeef这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制数全是abcdef之中的字母,而且这8个字母刚好组成了英文的dead beef这两个单词,字面意思是坏牛肉。

(3)为什么要对齐访问?有时候是效率的要求,有时候是硬件的特殊要求。

5、TEXT_BASE、CFG_PHY_UBOOT_BASE

(1)第100行这个TEXT_BASE就是就是我们链接时指定的uboot的链接地址。(值就是c3e00000),详解阅读:uboot源码分析(基于S5PV210)之零距离初体验

(2)源代码中和配置Makefile中很多变量是可以互相运送的。简单来说有些符号的值可以从Makefile中传递到源代码中。

_TEXT_BASE:
	.word	TEXT_BASE
.word定义一个四字节变量,类似于一个数据类型。TEXT_BASE是这个四字节变量的值,
_TEXT_BASE是标号。类似于定义一个指针,_TEXT_BASE是指针变量,.word是int型,
TEXT_BASE是指针所指向的地址存放的那个数据。


_TEXT_PHY_BASE:
	.word	CFG_PHY_UBOOT_BASE

MEMORY_BASE_ADDRESS  //DDR内存起始地址为30000000
#define CFG_PHY_UBOOT_BASE	MEMORY_BASE_ADDRESS + 0x3e00000
CFG_PHY_UBOOT_BASE 	33e00000	//uboot在DDR中的物理地址

uboot源码分析(基于S5PV210)之启动第一阶段_第5张图片

6、设置CPU为SVC模式

(1)msr cpsr_c, #0xd3 将CPU设置为禁止FIQ、IRQ,ARM状态,SVC模式。

(2)其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时CPU一直处于SVC模式。
uboot源码分析(基于S5PV210)之启动第一阶段_第6张图片
uboot源码分析(基于S5PV210)之启动第一阶段_第7张图片

7、设置L2、L1cache和MMU(200行开始)

(1)bl disable_l2cache // 禁止L2 cache
(2)bl set_l2cache_auxctrl_cycle // l2 cache相关初始化
(3)bl enable_l2cache // 使能l2 cache
(4)刷新L1 cache的icache和dcache。207-213行
(5)关闭MMU 214-221行
总结:上面这5步都是和CPU的cache和mmu有关的,不用去细看,大概知道即可。

8、识别并暂存启动介质选择

(1)从哪里启动是由我的开发板的SoC的OM5:OM0这6个引脚的高低电平决定的。

(2)实际上在210内部有一个寄存器(地址是0xE0000004),这个寄存器中的值是硬件根据OM引脚的设置而自动设置值的。这个值反映的就是OM引脚的接法(电平高低),也就是真正的启动介质是谁。

(3)我们代码中可以通过读取这个寄存器的值然后判断其值来确定当前选中的启动介质是Nand还是SD还是其他的。

(4)start.S的225-227行执行完后,在r2寄存器中存储了一个数字,这个数字等于某个特定值时就表示SD启动,等于另一个特定值时表示从Nand启动····

(5)260行中给r3中赋值#BOOT_MMCSD(0x03),这个在SD启动时实际会被执行,因此执行完这一段代码后r3中存储了0x03,以后备用。
uboot源码分析(基于S5PV210)之启动第一阶段_第8张图片

9、设置栈(SRAM中的栈)并调用lowlevel_init

在这里插入图片描述
(1)284-286行第一次设置栈。这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。

(2)在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL指令只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。

(3)使用SourceInsight的Reference功能,找到lowlevel_init函数真正的地方,是在uboot/board/samsumg/x210/lowlevel_init.S中。

10、检查复位状态(lowlevel_init.S 的 44-52行)

(1)复杂CPU允许多种复位情况。譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。
uboot源码分析(基于S5PV210)之启动第一阶段_第9张图片
(2)判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;而热启动或者低功耗状态下的复位则不需要再次初始化DDR。

11、lowlevel_init函数中的其他工作

(1)IO状态恢复(lowlevel_init.S 的54-59),这个和上一个和主线启动代码都无关,因此不用去管他。

(2)关看门狗(lowlevel_init.S 的 61-64行)

(3)一些SRAM SROM相关GPIO设置(外接SROM66-97行),与主线启动代码无关,不用管

(4)供电锁存,lowlevel_init.S的第100-104行,开发板供电锁存。在前100行,lowlevel_init.S中并没有做太多有意义的事情(除了关看门狗、供电锁存外),然后下面从110行才开始进行有意义的操作。
uboot源码分析(基于S5PV210)之启动第一阶段_第10张图片
(5)判断当前代码执行位置,lowlevel_init.S的110-115行。这几行代码的作用就是判定当前代码执行的位置在SRAM中还是在DDR中。为什么要做这个判定?

原因1:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份,因此如果是冷启动
那么当前代码应该是在SRAM中运行的BL1,如果是低功耗状态的复位这时候应该就是在DDR
中运行的。

原因2:判定当前运行代码的地址是有用的,可以指导后面代码的运行。譬如在lowlevel_init.S
中判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和初始化DDR的代码。如果
当前代码是在SRAM中,说明冷启动,那么时钟和DDR都需要初始化;如果当前代码是在DDR中,
那么说明是热启动则时钟和DDR都不用再次初始化。
bic	r1, pc, r0	这句代码的意义是:将pc的值中的某些bit位清0,剩下一些特殊的bit位
赋值给r1(r0中为1的那些位清零)相等于:r1 = pc & ~(ff000fff)

ldr	r2, _TEXT_BASE	 加载链接地址到r2
bic	r2, r2, r0	     然后将r2的相应位清0剩下特定位。

最后比较r1和r2.
	cmp     r1, r2                  /* compare r0, r1                  */
	beq     1f			/* r0 == r1 then skip sdram init   */

注:
b  1b 中的b是backward的意思,------>跳到程序的前面(往上)
b  1f  中的f是forward的意思,    ------>跳到程序的后面(往下)

1是符号链接,即要跳转到那里

  总结:这一段代码是通过读取当前运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前运行是在SRAM中(不相等)还是DDR中(相等)。从而决定是否跳过下面的时钟和DDR初始化。

(6)system_clock_init,使用SI搜索功能,确定这个函数就在当前文件的205行,一直到第385行。是用汇编代码写的初始化时钟的过程。

  在x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可

(7)mem_ctrl_asm_init

S5PV210共有2个内存端口(就好象有2个内存插槽)。结合查阅数据手册中内存映射部分,
可知:两个内存端口分别叫DRAM0和DRAM1:
	DRAM0:内存地址范围:0x200000000x3FFFFFFF512MB),对应引脚是Xm1xxxx
	DRAM1: 内存地址范围:0x400000000x7FFFFFFF1024MB),对应引脚是Xm2xxxx
(1)该函数用来初始化DDR,对于参数设置理解,可查阅数据手册理解或者百度,参考学习:
https://blog.csdn.net/JerryGou/article/details/83831274

(2)函数位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S文件中。

(3)在uboot中DMC0的256MB内存地址范围为0x30000000-0x3FFFFFFF(4)从分析开发板厂商移植的uboot可以看出:DMC0上允许的地址范围是20000000-3FFFFFFF
(一共是512MB),而我们实际只接了256MB物理内存,SoC允许我们给这256MB挑选地址范围。
只要将其设置到这个512MB范围内即可

(5)总结一下:在uboot中,可用的物理地址范围为:0x30000000-0x4FFFFFFF。一共512MB,
其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。

(6)我们需要的内存配置值在x210_sd.h的438行到468行之间。分析的时候要注意条件编译的
条件,配置头文件中考虑了不同时钟配置下的内存配置值,这个的主要目的是让不同时钟需求
的客户都能找到合适自己的内存配置值。

(7)在uboot中DMC0和DMC1都工作了,所以在编写裸机程序中,只要把uboot中的配置值和
配置代码全部移植过去,应该是能够让DMC0和DMC1都工作的。

(8)beq 1f (123行开始)

(1)uart_asm_init,这个函数用来初始化串口,初始化完了后通过串口发送了一个'O'

(2)tzpc_init,trust zone初始化,没搞过,不管

(3)pop {pc}以返回,返回前通过串口打印'K'

分析;lowlevel_init.S执行完如果没错那么就会串口打印出"OK"字样。这应该是我们uboot中看到的最早的输出信息。

(9)总结回顾:lowlevel_init.S中总共做了哪些事情:检查复位状态、IO恢复、关看门狗、开发板供电锁存、时钟初始化、DDR初始化、串口初始化并打印’O’、tzpc初始化、打印’K’。其中值得关注的:关看门狗、开发板供电锁存、时钟初始化、DDR初始化、打印"OK"

12、再次设置栈(DDR中的栈)

uboot源码分析(基于S5PV210)之启动第一阶段_第11张图片

(1)再次开发板供电锁存(strat.S292-294)。第一,做2次是不会错的;第二,做2次则第2次无意义;做代码移植时有一个古怪谨慎保守策略就是尽量添加代码而不要删除代码。

(2)之前在调用lowlevel_init程序前设置过1次栈(start.S 284-287行),那时候因为DDR尚未初始化,因此程序执行都是在SRAM中,所以在SRAM中分配了一部分内存作为栈。本次因为DDR已经被初始化了,因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次(start.S 297-299行);这里实际设置的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。

(3)为什么要再次设置栈?
  DDR已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中可怜兮兮的了;原来SRAM中内存大小空间有限,栈放在那里要注意不能使用过多的栈否则栈会溢出,我们及时将栈迁移到DDR中也是为了尽可能避免栈使用时候的小心翼翼。

感慨:uboot的启动阶段主要技巧就在于小范围内有限条件下的辗转腾挪。

13、再次判断当前地址以决定是否重定位(start.S 301行)

(1)再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同(上次判断是为了决定是否要执行初始化时钟和DDR的代码)这次判断是为了决定是否进行uboot的relocate,由于目前还在SRAM中,故而结果不相等。

(2)冷启动时当前情况是uboot的前一部分(16kb或者8kb)开机自动从SD卡加载到SRAM中正在运行,uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。此时uboot的第一阶段已经即将结束了(第一阶段该做的事基本做完了),结束之前要把第二部分加载到DDR中链接地址处(33e00000),这个加载过程就叫重定位。

14、uboot重定位详解

(1)D0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB000000;从SD2通道启动时,这个值为EB200000

(2)我们在start.S的260行确定了从MMCSD启动,然后又在278行将#BOOT_MMCSD写入了INF_REG3寄存器中存储着。然后又在322行读出来,再和#BOOT_MMCSD去比较,确定是从MMCSD启动。最终跳转到mmcsd_boot函数中去执行重定位动作。
uboot源码分析(基于S5PV210)之启动第一阶段_第12张图片
uboot源码分析(基于S5PV210)之启动第一阶段_第13张图片
(3)真正的重定位是通过调用movi_bl2_copy函数完成的,在uboot/cpu/s5pc11x/movi.c中。是一个C语言的函数

copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
			CFG_PHY_UBOOT_BASE, 0);
分析参数:
2表示通道2;

MOVI_BL2_POS是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时
烧录的位置相同;

MOVI_BL2_BLKCNT是uboot的长度占用的扇区数;

CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000

15、MMU单元的作用

(1)什么是虚拟地址、物理地址

(1)物理地址就是物理设备设计生产时赋予的地址。像使用的寄存器的地址就是CPU设计时指定
的,这个就是物理地址。物理地址是硬件编码的,是设计生产时确定好的,一旦确定了就不能
改了。

(2)一个事实就是:寄存器的物理地址是无法通过编程修改的,是多少就是多少,只能通过查
询数据手册获得并操作。坏处就是不够灵活。一个解决方案就是使用虚拟地址。

(3)虚拟地址意思就是在我们软件操作和硬件被操作之间增加一个层次,叫做虚拟地址映射层。
有了虚拟地址映射后,软件操作只需要使用虚拟地址,硬件操作还是用原来的物理地址,映射
层建立一个虚拟地址到物理地址的映射表。当我们软件运行的时候,软件中使用的虚拟地址在
映射表中查询得到对应的物理地址再发给硬件去执行(虚拟地址到物理地址的映射是不可能
通过软件来实现的,因为软件本身使用的就是虚拟地址,只能通过硬件)。

我们在编程时,软件使用的都是虚拟地址,但是硬件那边用的都是物理地址。 

(2)MMU就是memory management wuunit,内存管理单元。MMU实际上是SOC中一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射。

(3)MMU单片在CP15协处理器中进行控制,也就是说要操控MMU进行虚拟地址映射,方法就是对cp15协处理器的寄存器进行编程。

地址映射的额外收益1:访问控制,实现内存块的管理。
(1)访问控制就是:在管理上对内存进行分块,然后每块进行独立的虚拟地址映射,然后在
每一块的映射关系中同时还实现了访问控制(对该块可读、可写、只读、只写、不可访问
等控制)

(2)回想在C语言中编程中经常会出现一个错误:段错误:Segmentation fault。实际上
这个段错误就和MMU实现的访问控制有关。当前程序只能操作自己有权操作的地址范围(若
干个内存块),如果当前程序指针出错访问了不该访问的内存块则就会触发段错误。

地址映射的额外收益2cache
(1)cache的工作和虚拟地址映射有关系。

(2)cache是快速缓存,意思就是比CPU慢但是比DDR块。CPU嫌DDR太慢了,于是乎把一些DDR
中常用的内容事先读取缓存在cache中,然后CPU每次需要找东西时先在cache中找。如果
cache中有就直接用cache中的;如果cache中没有才会去DDR中寻找。cache越大,CPU性能
越高。

参考阅读:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=22891521&id=2109284

16、设置使能MMU(359-382行)

(1)使能域访问(cp15的c3寄存器)

(1)cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们
通过mrc和mcr指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄
存器。

(2)c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。uboot
中只是很简单地使用了权限控制,linux中使用了复杂的权限控制,并且使用了二级映射,
两次映射才能找到物理地址

(2)设置TTB(cp15的c2寄存器)

(1)TTB就是translation table base,转换表基地址。首先要明白什么是TT(translation 
table转换表),TTB其实就是转换表的基地址。

(2)转换表是建立一套虚拟地址映射的关键。转换表分2部分,表索引和表项。表索引对应虚拟
地址,表项对应物理地址。一对表索引和表项构成一个转换表单元,能够对一个内存块进行虚
拟地址转换。(映射中基本规定中规定了内存映射和管理是以块为单位的,至于块有多大,要
看你的MMU的支持和你自己的选择。在ARM中支持3种块大小,细表1KB、粗表4KB、段1MB)。
真正的转换表就是由若干个转换表单元构成的,每个单元负责1个内存块,总体的转换表负责
整个内存空间(0-4G)的映射。

(3)整个建立虚拟地址映射的主要工作就是建立这张转换表

(4)转换表放置在内存中的,放置时要求起始地址在内存中要xx位对齐。转换表不需要软件去
干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时会自动去查转换表。

(3)使能MMU单元(cp15的c1寄存器)
  cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后,上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。

(4)通过符号查找,确定转换表在lowlevel_init.S文件的593-653行。

mmu_table:
	.set __base,0
	// Access for iRAM
	.rept 0x100      @汇编的循环语句.rept 0x100开始.endr结尾,循环次数0x100
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1  @类似于C语言的i++
	.endr

	// Not Allowed
	.rept 0x200 - 0x100
	.word 0x00000000
	.endr

	.set __base,0x200
	// should be accessed
	.rept 0x600 - 0x200
	FL_SECTION_ENTRY __base,3,0,1,1
	.set __base,__base+1
	.endr

	.rept 0x800 - 0x600
	.word 0x00000000
	.endr

	.set __base,0x800
	// should be accessed
	.rept 0xb00 - 0x800
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr

/*	.rept 0xc00 - 0xb00
	.word 0x00000000
	.endr */

	.set __base,0xB00
	.rept 0xc00 - 0xb00
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr

	// 0xC000_0000?犲皠??x2000_0000
	.set __base,0x300
	//.set __base,0x200
	// 256MB for SDRAM with cacheable
	.rept 0xD00 - 0xC00
	FL_SECTION_ENTRY __base,3,0,1,1
	.set __base,__base+1
	.endr

	// access is not allowed.
	@.rept 0xD00 - 0xC80
	@.word 0x00000000
	@.endr

	.set __base,0xD00
	// 1:1 mapping for debugging with non-cacheable
	.rept 0x1000 - 0xD00
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr	
	页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页。把内存空间按页
的大小划分为片或者页面,然后把页式虚拟地址与内存地址建立一一对应的页表,并用相应的
硬件地址转换机构来解决离散地址变换问题。页式管理采用请求调页和预调页技术来实现内外
存存储器的统一管理。 优点:没有外碎片,每个内碎片不超过页的大小。 缺点:程序全部装
入内存,要求有相应的硬件支持,如地址变换机构缺页中断的产生和选择淘汰页面等都要求有
相应的硬件支持。增加了机器成本和系统开销。

	段式管理的基本思想是把程序按内容或过程函数关系分成段,每段有自己的名字。一个用
户作业或者进程所包含的段对应一个二维线性虚拟空间,也就是一个二维虚拟存储器。段式管
理程序以段为单位分配内存,然后通过地址映射机构把段式虚拟地址转换为实际内存物理地址。
优点:可以分别编写和编译,可以针对不同类型的段采取不同的保护,可以按段为单位来进行
共享,包括通过动态链接进行代码共享。 缺点:会产生碎片。 

	段页式管理,系统必须为每个作业或者进程建立一张段表以管理内存分配与释放、缺段处
理等。另外由于一个段又被划分为若干个页,每个段必须建立一张页表以把段中的虚页变换为
内存中的实际页面。显然与页式管理时相同,页表也要有相应的实现缺页中断处理和页面保护
等功能的表项。 段页式管理是段式管理和页式管理相结合而成,具有两者的优点。 由于管理
软件的增加,复杂性和开销也增加。另外需要的硬件以及占用的内存也有所增加,使得执行速
度下降。

	/* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b			@汇编中的宏定义.macro .endm
	.word (\base << 20) | (\ap << 10) | \
	      (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
宏FL_SECTION_ENTRY:根据控制参数建立一个字大小的段描述符。以上的位操作可以通过下
图理解:

uboot源码分析(基于S5PV210)之启动第一阶段_第14张图片
  宏观上理解转换表:整个转换表可以看作是一个int类型的数组,数组中的一个元素就是一个表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。

  ARM的段式映射中长度为1MB,因此一个映射单元只能管1MB内存,那我们整个4G范围内需要4G/1MB=4096个映射单元,也就是说这个数组的元素个数是4096.实际上我们做的时候并没有依次单个处理这4096个单元,而是把4096个分成几部分,然后每部分用for循环做相同的处理。

VA					PA					length
0-10000000			0-10000000			256MB
10000000-20000000	0					256MB
20000000-60000000	20000000-60000000	1GB		512-1.5G
60000000-80000000	0					512MB	1.5G-2G
80000000-b0000000	80000000-b0000000	768MB	2G-2.75G
b0000000-c0000000	b0000000-c0000000	256MB	2.75G-3G
c0000000-d0000000	30000000-40000000	256MB	3G-3.25G
d-完				d-768MB	3.25G-4G

DRAM有效范围:
DMC0: 	0x30000000-0x3FFFFFFF
DMC1:	0x40000000-0x4FFFFFFF

结论:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头
的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。

17、第一阶段启动剩余工作

uboot源码分析(基于S5PV210)之启动第一阶段_第15张图片

(1)再次设置栈

(1)第三次设置栈。这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是
本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。

(2)我们实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小
减去0x1000等于1.8MB左右。这个空间既没有太浪费内存,又足够安全。

(2)清理bss段,注意表示bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。

(3)ldr pc, _start_armboot
  start_armboot是uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处

  远跳转的含义就是这句指令加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。

  这里这个远跳转就是uboot第一阶段和第二阶段的分界线。

三、总结

uboot的第一阶段做了哪些工作:

(1)构建异常向量表
(2)设置CPU为SVC模式
(3)关看门狗
(4)开发板供电置锁
(5)时钟初始化
(6)DDR初始化
(7)串口初始化并打印"OK"
(8)重定位
(9)建立映射表并开启MMU
(10)跳转到第二阶段

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来、引用了百度百科、部分他人博客的内容并结合自己实际开发经历,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

你可能感兴趣的:(初窥uboot与Linux内核,linux,驱动开发,BSP,uboot,arm)