/*******************************************************************************
uboot启动过程第一阶段的分析(start.s文件)
核心:start.s的分析,
其他:虚拟地址的简单介绍,lowlever_init函数的分析
时间:2018年11月下旬
作者:cryil_先森
以朱有鹏课程中uboot-samsung-dev为分析对象
******************************************************************************/
/*******************************************************************************
u-boot.lds中找到start.s的入口
整个程序的入口取决于链接脚本u-boot.lds中ENTRY声明的地方。
start符号所在的文件是整个程序的起始文件,_start所在的代码是整个程序的起始代码。
找到start.s文件,他是整个程序的起始文件。目录:u-boot-samsung-dev\cpu\s5pc11x
.globl相当于C语言中的Extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问
uboot启动的阶段的技巧是小范围内有限条件下的辗转腾挪(不断地在设置栈,变换储存的位置)
*******************************************************************************/
start.s的分析
***主要的工作:
1.构建异常向量表;
2.设置CPU为SVC模式;
3.关看门狗,开发板的供电索存(lowlevel.inti.s中完成,在start.s中重复进行);
4.时钟的初始化,DDR的初始化(lowlevel.inti.s中完成);
5.调试的重要依据:串口初始化并打印“OK”;
6.重定位,并且设置了uboot启动第二部分在DDR中的起始地址;
7.建立映射表并开启MMU;
8.设置三次不同用途的栈;
9.跳转至uboot启动的第二阶段继续进行。
***头文件:
#include <config.h> config.h文件在include目录下,在配置时自动生成,详见mkconfig脚本
其实config.h文件包含了一个头文件 include/configs/smdkv210onenand.h头文件。
#include <version.h>
include/version.h中包含了include/versionn_autogeneraled.h。
这个文件在配置时自动生成,里面定义的宏 U-BOOT-VERSION是一个字符串,代表版本号。
U-BOOT-VERSION来源于主Makefile开头位置的配置值,这个宏在程序之中被调用,在开机启动时会打印出我们的所配置的板子的信息。
#include <asm/proc/domain.h>
asm目录不是uboot的原生目录,是配置时创建的一个符号链接,指向asm-arm目录。
asm/proc/domain.h实际的文件是 include/asm-arm/proc/domain.h
#include <regs.h>
regs.h文件在/include目录下,实际指向/include/s5pc110.h($6系统芯片.h)文件。
***程序部分分析:
**1.启动代码的16字节头部:
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
分析:.word是GNU的伪指令,实际上定义了16字节的内容,这16字节是SD/NAND启动时整个镜像开头所需的16字节校验头。
在uboot启动时:在开头位置放置了16字节的填充占位,这个占位的16字节只是保证image的头部确实有16字节,但是这16字节里面的内容是不正确的,需要后面去计算校验然后重新填充(类似及裸机之中的mkv210_image.c文件)
**2.异常向量表:
_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
分析:异常向量表由硬件决定,软件进行参考设计实现。
异常向量表中的每一个异常都应该被处理,否则遇到这种异常就会跑飞。
.balignl 16,0xdeadbeef:
让当前地址对其排布,如果当前地址不对齐则自动向后走直到地址对齐,并且后面的内存用0xdeadbeef填充 (deadbeef:坏牛肉,表示可以被丢弃的内容)
**3. _TEXT_BASE:
.word TEXT_BASE
分析:和主Makefile中定义的TEXT_BASE相同,是我们链接时指定的链接地址,也就是c3e00000
**4. _TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
分析:CFG_PHY_UBOOT_BASE=0x20000000+3e00000=0x23e00000 也是DDR之中的物理地址
***5.
#if defined(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
分析:uboot中与中断有关的设置,一般情况下会被删除。
**6.进行复位模式的设置,整个程序真正入口的地方
reset:
/*
* set the cpu to SVC32 mode and IRQ & FIQ disable
*/
@;mrs r0,cpsr
@;bic r0,r0,#0x1f
@;orr r0,r0,#0xd3
@;msr cpsr,r0
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
设置CPU当前工作的状态模式:禁止FIQ IRQ ARM状态,SVC模式
分析:将CPU设置为禁止FIQ IRQ ARM状态,SVC模式。(uboot工作时CPU移植处在SVC)
**7.CPU的一些初始化操作:
cpu_init_crit:
#ifndef CONFIG_EVT1 判断是否是定义了CONFIG_EVT1,后面的if~endif 可以删除
#if 0
bl v7_flush_dcache_all
#else
bl disable_l2cache
mov r0, #0x0 @
mov r1, #0x0 @ i
mov r3, #0x0
mov r4, #0x0
lp1:
mov r2, #0x0 @ j
lp2:
mov r3, r1, LSL #29 @ r3 = r1(i) <<29
mov r4, r2, LSL #6 @ r4 = r2(j) <<6
orr r4, r4, #0x2 @ r3 = (i<<29)|(j<<6)|(1<<1)
orr r3, r3, r4
mov r0, r3 @ r0 = r3
bl CoInvalidateDCacheIndex
add r2, #0x1 @ r2(j)++
cmp r2, #1024 @ r2 < 1024
bne lp2 @ jump to lp2
add r1, #0x1 @ r1(i)++
cmp r1, #8 @ r1(i) < 8
bne lp1 @ jump to lp1
bl set_l2cache_auxctrl
bl enable_l2cache
#endif
#endif
bl disable_l2cache 禁止12cache
bl set_l2cache_auxctrl_cycle 12cash 相关初始化
bl enable_l2cache 使能 12 cache
/*
* 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
总结:以上都是关于和CPU相关的cach mmu有关的,不用细看。
识别并暂时储存启动介质的选择:
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
分析:从哪里启动由SOC的OM5~OM0这6个引脚的高低电平决定。
在210内有一个寄存器(地址是0xE0000004),这个寄存器是硬件根据OM引脚的设置而自动设置的,这个值(r2)反映了OM引脚的接法(电平的高低),从而确定了启动介质是NAND还是SD其他的。
**9.进行板子的信息匹配
#ifdef CONFIG_VOGUES
/* PS_HOLD(GPH0_0) set to output high 设置输出高*/
ldr r0, =ELFIN_GPIO_BASE
ldr r1, =0x00000001
str r1, [r0, #GPH0CON_OFFSET]
ldr r1, =0x5500
str r1, [r0, #GPH0PUD_OFFSET]
ldr r1, =0x01
str r1, [r0, #GPH0DAT_OFFSET]
#endif
分析:将板子与VOGUES进行比较 一致的话则进行下面的操作 (与我们的板子不同 删除)
**10. 设置板子启动的方式:
/* 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]
分析:通过给r2赋值,确定板子的启动方式:nand,onenand,sd,nor flash
如果启动失败的话则对r2重新赋值,更换启动的方式。
**11.初始化第一个栈(SRAM内)
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */ //设置寄存器的值
sub sp, sp, #12 /* set stack */
mov fp, #0
分析:第一次设置栈,这次设置是在SRAM中设置的,因为当前整个代码还是运行在SRAM中,DDR还未初始化。在调用函数之前初始化栈主要原因是在被调用的函数内部还会再次调用函数,而BL只会返回地址储存到LR之中,但是我们只有一个LR,所以在第二层调用函数之前将LR入栈,否则函数返回时第一层的返回地址就丢了。
**12.一个重要的函数:lowlevel_init
bl lowlevel_init /* go setup pll,mux,memory */
在其他博客内会有具体的介绍。
**13.开发板的供电索存
ldr r0, =0xE010E81C /* PS_HOLD_CONTROL register */
ldr r1, =0x00005301 /* PS_HOLD output high */
str r1, [r0]
在lowlevel_init中初始化过了(可删除)。
**14.第二次设置栈(DDR内)
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
分析:由于我们的DDR已经被初始化,所以此次设置栈为内存分配更大的空间,原来SRAM内存的空间受限,过多时易发生溢出现象,及时的将它迁移至DDR。
**15.再次判断当前地址以决定是否重定位
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 */
分析:本次判断是为了决定是否执行uboot代码的重定位。
(而在lowlevel_init也有判断,以决定是否执行初始化时钟和DDR的代码)
uboot启动的阶段:
1.冷启动部分:uboot的前一部分,开机时自动从SD卡加载至SRAM中去运行;
2.第二部分(实际是整个部分):此时整个uboot还在SD卡的某个扇区开头的N个扇区中,在uboot第一阶段结束之前必须将它加载至DDR中链接地址0x23e00000处,这个过程就是重定位
**16 再次对启动方式进行判断:
#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
最终跳转至mmcsd_boot函数之中进行执行。
**17.重定位:
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
进行一个判断之后通过调用movi_bl2_copy函数程序来完成。执行完之后再跳转至after_copy函数进行执行。
而movi_bl2_copy函数在uboot/cpu/s5pc11x/Movi.c文件之中。
在这个函数值里面其实调用了另外一个函数:copy_bl2:
copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0)
分析参数:2:表示通道; MOVI_BL2_POS:是uboot的第二部分在SD卡的开始扇区,这个扇区必数字必须和烧录时的位置相同;
MOVI_BL2_BLKCNT:是uboot的长度所占据的扇区数;
CFG_PHY_UBOOT_BASE:重定位时将uboot的第二部分复制到DDR之中的起始地址。(0x23e00000)
**18.设置虚拟地址映射:
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
分析:得到转换表的基地址并将其放置在c2之中。
然后将cp15的c1寄存器赋值为1,开启MMU,
注:上层软件的地址就必须通过转换表才能发给下层的物理层去执行。
CONFIG_ENABLE_MMU在smdkv210onenand.h中有定义,这是一个和虚拟地址映射有关的关键字
cp15协处理器内部有c0~c15共计6个寄存器,我们通过mrc和mcr指令来访问这些寄存器
操作CPU协处理器其实就是操作cp15寄存器。c3在mmu之中作用就是控制域访问,控制域访问和mmu的访问控制有关。
**19 Set the TTB register:转换表基地址 (从此处开始,用的都是虚拟地址)
转换表是建立虚拟地址映射的关键,转换表分为表索引和表项两部分,表索引对应虚拟地址映射,表项对应物理地址,一个表索引和表项构成一个转换单元,能够对一个内存块进行虚拟地址转换。(内存管理和映射以快为单位)转换表放置在内存之中,放置时要求起始地址在内存之中要xx位对齐,转换表不需要软件的支持,而是将基地址TTB设置到cp15的c2寄存器之中,然后MMU工作时会自动查询转换表。
宏观上整个转换表可以看做是一个int型的数组,数组的元素值就是表项,元素的下标就是表索引。在uboot/board/samsung/smdkc110/lowlever_init.s中对TTB转换表基地址有着具体的定义。
/* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b
.word (\base << 20) | (\ap << 10) | \
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
.section .mmudata, "a"
.align 14
// the following alignment creates the mmu table at address 0x4000.
.globl mmu_table
mmu_table:
.set __base,0
// Access for iRAM
.rept 0x100
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.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
.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
#else // CONFIG_MCP_AC, CONFIG_MCP_H, CONFIG_MCP_B
分析:设置了具体的分区方式和虚拟地址映射的关系。
核心:虚拟地址映射只是将虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存之中去,其他的虚拟地址没有发生变化,映射的还是原来的物理地址。
注:DRAM的有效范围:
DMC0:0x30000000~0x3FFFFFFF
DMC1:0X40000000~0X4FFFFFFF
**20 第三次设置栈(DDR之中),清除bss段代码。
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
本次设置栈主要是为了设置一个更加安全的栈,设置了栈的起始位置,设置了栈的大小:
栈的起始位置:CFG_UBOOT_BASE + CFG_UBOOT_SIZE:uboot起始地址上方2MB处
栈的大小:CFG_UBOOT_BASE + CFG_UBOOT_SIZE-0x1000 约为1.8MB
这次设置使得这个栈离我们uboot的源码储存的地方尽量的近,从而紧凑而不浪费的使用了内存空间。(uboot中的栈是满减栈,向下进行储存,所以设置的栈在减去uboot源码的空间之后有着巨大的空间可以供开发使用)
而bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。
**21 第一阶段的结束,与第二阶段进行衔接
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
分析:实际上start_armboot是一个指针,指向的是uboot启动源码第二部分在DDR之中存放的地址。它存放在uboot/lib_arm/board.c中。
相当于进行一个远程的跳转指令,直接跳转至DDR中的第二阶段开始的地址处。这个远跳转是uboot启动过程第一阶段和第二阶段的分界线。
虚拟地址映射的简单介绍:
***1.物理地址:
物理设备设计生产时所被赋予的地址,是硬件编码的,在设计出厂之后就无法 改(只能通过数据手册进行查询并应用)
***2.虚拟地址映射层:
在软件操作和硬件操作之间增加的一个便于编程人员进行开发的层次。
映射层建立了一个从虚拟地址到物理地址的映射关系,当软件执行时可以通过访问虚拟地址来访问相应的物理地址,再控制硬件的操作。
(软件程序之中使用的都是虚拟地址,而和硬件直接相关的是物理地址,虚拟地址到物理地址的映射是通过硬件实现的)
**访问控制:
在内存管理时同时对内存进行分块处理,然后对每一块进行单独的虚拟地址映射,并同时对每一块实现访问控制(只读,只写。。。。)
例:在C语言之中出现的警告错误:segmentation fault (段错误)
原因就是访问控制导致的,当前程序只能操作自己有权访问的地址范围(若干个内存块),如果当前程序指针访问了不属于自己的内存块,就会触发段错误。
**cache:
快速缓存,速度比CPU慢,比DDR快。相当于两者之间的介质。
CPU在访问DDR之前会先访问cache之中缓存的内容,然后访问DDDR之中的内容。
CPU的速度远高于内存,当CPU直接从内存中存取数据时要等待一定时间周期,而Cache则可以保存CPU刚用过或循环使用的一部分数据,如果CPU需要再次使用该部分数据时可从Cache中直接调用,这样就避免了重复存取数据,减少了CPU的等待时间,因而提高了系统的效率。
***3.MMU单元:
内存管理单元,实际上是SOC中的一个硬件单元,主要实现虚拟地址到物理地址的映射。
MMU在cp15协处理器中被进行控制,所以对于虚拟地址映射的操控就是对cp15协处理器的寄存器进行编程。
lowlever_init函数的分析
/*******************************************************************************
lowlever_init函数存在的地方:/board/samsunug/smdkc110(board)/lowlever_init.s
具体的作用:检查复位状态,进行IO恢复,关闭看门狗、开发板的供电索存、判断代码的运行状态、时钟的初始化、DDR的初始化、后口初始化并打印“O”、tzpc初始化、打印“K”。
最核心的是:1.判断当前代码的工作区域,即DDR or SRAM,从而设置不同的启动方式;
2.通过串口的方式在uboot的启动最开始打印出“OK”可以作为很重要调试方式,初步排查问题的地方。
**********************************************************************************/
***具体的分析:
A.与启动主线无多大影响的部分:
**1.压栈操作
lowlevel_init:
push {lr}
分析:进行压栈操作,以免因为后面ld调用其他的函数而导致数据的丢失
**2.检查复位的状态:
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
ldr r1, [r0]
bic r1, r1, #0xfff6ffff
cmp r1, #0x10000
beq wakeup_reset_pre
cmp r1, #0x80000
beq wakeup_reset_from_didle
复杂CPU的复位方式情况很复杂,所以在复位代码启动之前要去检测复位的状态。
例如:冷上电时DDR需要初始化,热启动时(休眠、低功耗)不需要初始化DDR。
3.对IO和看门狗进行设置
/* IO Retention release **/
ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
ldr r1, [r0]
ldr r2, =IO_RET_REL
orr r1, r1, r2
str r1, [r0]
/* Disable Watchdog /
ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
mov r1, #0
str r1, [r0]
分析:对io相关的出处理,并关掉看门狗
4.对一些SRAM SROM GPIO进行设置以及供电索存(与主线启动代码无关)
/* SRAM(2MB) init for SMDKC110 */
/* GPJ1 SROM_ADDR_16to21 */
ldr r0, =ELFIN_GPIO_BASE
ldr r1, [r0, #GPJ1CON_OFFSET]
bic r1, r1, #0xFFFFFF
ldr r2, =0x444444
orr r1, r1, r2
str r1, [r0, #GPJ1CON_OFFSET]
ldr r1, [r0, #GPJ1PUD_OFFSET]
ldr r2, =0x3ff
bic r1, r1, r2
str r1, [r0, #GPJ1PUD_OFFSET]
/* GPJ4 SROM_ADDR_16to21 */
ldr r1, [r0, #GPJ4CON_OFFSET]
bic r1, r1, #(0xf<<16)
ldr r2, =(0x4<<16)
orr r1, r1, r2
str r1, [r0, #GPJ4CON_OFFSET]
ldr r1, [r0, #GPJ4PUD_OFFSET]
ldr r2, =(0x3<<8)
bic r1, r1, r2
str r1, [r0, #GPJ4PUD_OFFSET]
/* PS_HOLD pin(GPH0_0) set to high */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
ldr r1, [r0]
orr r1, r1, #0x300
orr r1, r1, #0x1
str r1, [r0]
B.真正有意义的地方:
**1.判定当前代码的执行位置(SRAM or DDR)
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
分析: r1 =pc & ~(0xff000fff)清零pc的特定位
注:pc加载的是运行地址的现在地址
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
分析:_TEXT_BASE是我们的链接地址,是DRAM、DDR中的内容,
bic r2, r2, r0 /* r0 <- current base addr of code */
分析:清零r2的特定位
cmp r1, r2 /* compare r0, r1 */
分析:将r2与r1进行比较,以确定我们的启动方式是热启动还是冷启动。相同的话当前运行在SRAM之中,则依次执行下面的代码;如果不同的话当前运行在DDR之中,则向下跳转至“1:”位置运行其相应的代码。
注:在裸机启动时,仅仅只是比较_start的运行地址和链接地址是否相同而确定我们当前代码的运行地址,从而确定启动的方式。而在uboot之中,pc获取的是运行地址的现在地址,显然和我们的链接地址的首地址(_TEXT_BASE)不相同,则将_TEXT_BASE和pc的地址均取相同的位进行比较。
beq 1f /* r0 == r1 then skip sdram init */
/* init PMIC chip */
bl PMIC_InitIp
/* init system clock */
bl system_clock_init
/* Memory initialize */
bl mem_ctrl_asm_init
分析:相同时直接先进行时钟和内存(DDR动态内存)的初始化(后面会有具体的函数),不相同时直接跳转“1:”执行。
通过分析mem_ctrl_asm_init(DDR动态内存)可知在uboot中DMC0、DMC1都工作了 。 它可用的物理地址范围是0x30000000~0x4fffffff 一共是512MB,
其中30000000~3fffffff是DMC0,40000000~4FFFFFFF是DMC1。
(时钟和内存的初始化的一些地址在s5pc110.h文件里有定义,如果想更改时钟的初始化只需要更改.h文件里面的定义即可。)
1:
/* for UART */
bl uart_asm_init
bl tzpc_init
分析:初始化串口(函数在后面的程序之中有具体的定义),在初始化完毕后,发送一个“o”;接着初始化可信任区域。
**2.接下来通过if判断执行一些初始化,最重要的是在最后程序正确执行完毕之后打印了“K”,这和之前打印的“O”组合一起,打印“OK”,是否打印出OK可以作为uboot启动过程的一个调试方法。
#if defined(CONFIG_ONENAND)
bl onenandcon_init
#endif
#if defined(CONFIG_NAND)
/* simple init for NAND */
bl nand_asm_init
#endif
/* check reset status */
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
ldr r1, [r0]
bic r1, r1, #0xfffeffff
cmp r1, #0x10000
beq wakeup_reset_pre
/* ABB disable */
ldr r0, =0xE010C300
orr r1, r1, #(0x1<<23)
str r1, [r0]
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
pop {pc}
**3.一些基本的的初始化操作
wakeup_reset_from_didle:
/* Wait when APLL is locked */
ldr r0, =ELFIN_CLOCK_POWER_BASE
lockloop:
ldr r1, [r0, #APLL_CON0_OFFSET]
and r1, r1, #(1<<29)
cmp r1, #(1<<29)
bne lockloop
beq exit_wakeup
wakeup_reset_pre:
mrc p15, 0, r1, c1, c0, 1 @Read CP15 Auxiliary control register
and r1, r1, #0x80000000 @Check L2RD is disable or not
cmp r1, #0x80000000
bne wakeup_reset @if L2RD is not disable jump to wakeup_reset
bl disable_l2cache
bl v7_flush_dcache_all
/* L2 cache enable at sleep.S of kernel
* bl enable_l2cache
*/
wakeup_reset:
/* init system clock */
bl system_clock_init
bl mem_ctrl_asm_init
bl tzpc_init
#if defined(CONFIG_ONENAND)
bl onenandcon_init
#endif
#if defined(CONFIG_NAND)
bl nand_asm_init
#endif
exit_wakeup:
/*Load return address and jump to kernel*/
ldr r0, =(INF_REG_BASE+INF_REG0_OFFSET)
ldr r1, [r0] /* r1 = physical address of s5pc110_cpu_resume function*/
mov pc, r1 /*Jump to kernel */
nop
nop
**4.最后就是一系列的初始化源码:
system_clock_init: uart_asm_init: nand_asm_init: tzpc_init: onenandcon_init:
完结。。。。。。。。。。。。。
时间:2018年11月下旬
作者:cryil_先森ID:qq_41464499
转载需备注来源