lowlevel_init.S的引入
注:本次分析的u-boot是九鼎官方的u-boot代码
下载地址:链接:http://pan.baidu.com/s/1gfpDZqj 密码:7cqe
继续上一章节的第四小节;
第一次设置完栈之后,跳转到lowlevel_init.S中去,代码如下:
bl lowlevel_init
通过Source Insight找到lowevel_init.S的实际位置在:
uboot/board/samsumg/x210/lowlevel_init.S
1.压栈
lowlevel_init:
push {lr}
接下来还会再次调用函数,所以要先压栈。
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
(1)复杂CPU允许多种复位情况。譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。
(2)判断哪种复位的意义在于:冷上电时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]
4.关看门狗
* Disable Watchdog */
ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
mov r1, #0
str r1, [r0]
5.供电锁存
ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
ldr r1, [r0]
orr r1, r1, #0x300
orr r1, r1, #0x1
str r1, [r0]
注:之所以将#0x301分两步进行或运算,是因为#0x301不是合法立即数。
6.判断当前代码执行位置
ldr r0, =0xff000fff
bic r1, pc, r0
ldr r2, _TEXT_BASE
bic r2, r2, r0
cmp r1, r2
beq 1f
/* init system clock */
bl system_clock_init
/* Memory initialize */
bl mem_ctrl_asm_init
(1)这几行代码的作用就是判定当前代码执行的位置在SRAM中还是在DDR中。
(2)为什么要做这个判定?
原因1:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份,因此如果是冷启动那么当前代码应该是在SRAM中运行的BL1,如果是低功耗状态的复位这时候应该就是在DDR中运行的。
原因2:我们判定当前运行代码的地址是有用的,可以指导后面代码的运行。比如:根据上述代码可以知道,判定代码之后是时钟初始化代码和DDR初始化代码,如果当前代码是在SRAM中,说明冷启动,那么时钟和DDR都需要初始化;如果当前代码是在DDR中,那么说明是热启动则时钟和DDR都不用再次初始化。
(3)对代码进行分析:
ldr r0, =0xff000fff
bic r1, pc, r0
这两句代码可以转换为:r1 = pc & ~(ff000fff),这里的pc是当前运行地址;
ldr r2, _TEXT_BASE
bic r2, r2, r0
这两句代码可以转换为:r2 = TEXT_BASE & ~(ff000fff),这里的_TEXT_BASE相当于一个指针,实际内容是链接地址TEXT_BASE。
cmp r1, r2
beq 1f
然后比较r1和r2,这里的1f的意思是:“1”为标号,“f”代表方向,往下找的意思。这两句代码的就是是:如果r1 = r2的话,跳转到下面代码标号为1的位置,忽略中间的时钟初始化和DDR初始化。
注:一般的判定方法是比较运行的起始地址和链接的起始地址,而这里加载的是当前运行地址和链接起始地址,所以是绝对不相等的。所以要将加载的地址部分位清零来比较,也就是将加载过来的地址的前8位和后12位进行清理。
举例:加载的pc地址为c3e00321,链接地址为c3e00000,将前8位和后12位清零之后,发现相等,则不用初始化时钟和DDR。
7.时钟初始化
/* init system clock */
bl system_clock_init
(1)使用SI搜索功能,确定这个函数就在当前文件的205行,一直到第385行。
(2)在x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可。
8.DDR初始化
/* Memory initialize */
bl mem_ctrl_asm_init
(1)该函数用来初始化DDR
(2)函数位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S文件中。
(3)在uboot中,可用的物理地址范围为:0x30000000-0x4FFFFFFF。一共512MB,其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。
(4)我们需要的内存配置值在x210_sd.h的438行到468行之间。分析的时候要注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值,这个的主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。
9.串口初始化并打印“OK”返回
/* for UART */
bl uart_asm_init
这个函数用来初始化串口,初始化完了后通过串口发送了一个’O’。
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
pop {pc}
打印出’O’,并返回。
所以lowlevel_init.S执行完如果没错那么就会串口打印出"OK"字样。
总结:lowlevel_init.S的主要内容有:
检查复位状态、IO恢复、关看门狗、开发板供电锁存、时钟初始化、DDR初始化、串口初始化并打印出“OK”。