mips架构u-boot启动流程
u-boot的启动过程大致做如下工作:
1、cpu初始化
2、时钟、串口、内存(ddr ram)初始化
3、内存划分、分配栈、数据、配置参数、以及u-boot代码在内存中的位置。
4、对u-boot代码作relocate
5、初始化malloc、flash、pci以及外设(比如,网口)
6、进入命令行或者直接启动Linux kernel
整个启动中要涉及到四个文件:
start_bootstrap.S à cpu/mips/start_bootstrap.S
Lowlevel_init.S à board/ar7240/common/lowlevel_init.S
Cache.S à cpu/mips/cache.S
Board.c à lib_mips/board.c
整个启动过程分为两个阶段来看:
Stage1:系统上电后执行汇编代码
Stage2:通过一些列设置搭建了C环境,通过汇编指令跳转到C语言执行.
Stage1:
程序从cpu/mips/start_bootstrap.S的_start_bootstrap开始执行.(至于为什么,参考u-boot.lds分析.doc)
先查看start_bootstrap.S文件吧!~
从_start_bootstrap标记开始会看到一长串莫名奇妙的代码:
RVECENT(reset,0) /* U-boot entry point */ /*U-Boot开始执行的代码起始地址*/
RVECENT(reset,1) /* software reboot */ /*软重启时U-Boot开始执行的起始地址*/
RVECENT(romReserved,2) /*保留本代码所在的地址,重新映射调试异常向量时可以使用该空间*/
RVECENT(romReserved,3)
RVECENT(romReserved,4)
RVECENT(romReserved,5)
RVECENT(romReserved,6)
RVECENT(romReserved,7)
RVECENT(romReserved,8)
RVECENT(romReserved,9)
…
…
回过头看刚开始的定义有这样的代码:
可以找到:
#defineRVECENT(f,n) \
b f; nop
原来这只是一个简单的跳转指令,f为一个标记,b为跳转指令。
然后看最后,发现:
romReserved:
b romReserved
romExcHandle:
b romExcHandle
这两个标记都构建了无意义的死循环。
通过_start标记处的语句RVECENT(reset,0) 代码跳转到标记reset的地方,该段代码的操作就是对寄存器的清零操作了。Mfc0和mtc0指令是对寄存器的一些读写.
Mips 寄存器:http://blog.csdn.net/flyingqr/article/details/7073088
在接下来是对协处理器的操作了,其中包括:
CP0_WATCHLO,
CP0_WATCHHI,
CP0_STATUS
CP0_CAUSE,
CP0_COUNT,
CP0_COMPARE
CP0_CONFIG
之后,配置寄存器CP0_STATUS,设置所使用的协处理器,中断以及cpu运行级别(核心级)。
配置gp寄存器,把GOT段的地址赋给gp寄存器。(gp寄存器的用处会在后面relocate code部分详细解释)
下面就是do_reset:
// load reset register0x1806001c
// bit24, fullchip reset
接下来执行/u-boot/board/ar7240/common/lowlevle_init.S的lowlevel_init(la t9, lowlevel_init)函数,主要目的是工作频率配置,比如wlan-reset,HORNET_BOOTSTRAP_STATUS,RTCreset, AHB/APH reset ,MAC reset ,AR7240_CPU_CLOCK_CONTROL ,DDR工作频率,等,
/******************************************************************************
* first level initialization:
*
* 0) If clock cntrl reset switch is alreadyset, we're recovering from
* "divider reset"; goto 3.
* 1) Setup divide ratios.
* 2) Reset.
* 3) Setup pll's, wait for lock.
*
*****************************************************************************/
转到了/board/ap7240/ap121/hornet_pll_init.S中执行:
hornet_pll_init:
先wlan reset:
set_reg(0xb806001c,0x00c06b30) //1100_0000_0110_1011_0011_0000
nop
set_reg(0xb806001c,0x00c06330) //1100_0000_0110_0011_0011_0000
接着设置0x180600AC的第2bit(spi启动),检查check_val,设置0x180600AC的值为
set_reg(HORNET_BOOTSTRAP_STATUS,0x0002110e) //0010_0001_0001_0000_1110
RTCreset:
set_reg(0x1810704c, 0x00000003) // 0x1810704c :RTC_FORCE_WAKE
set_reg(0x18107040, 0x00000000) // 0x18107040: RTC_RESET
set_reg(0x18107040, 0x00000001)
wait_loop1:
li t6, KSEG1ADDR(0x18107044) //0x18107044:RTC_STATUS
lw t7, 0(t6)
li t8, 0x2 // 看第1bit是否为1,RTC in on state
and t7, t7, t8
bne t8, t7,wait_loop1
nop
/*AHB/APH reset */
set_reg(0x18104000,0x00000003) TODO:写3的意思
set_reg(0x18104000, 0x00000000) // 0x18104000:HOST_INTF_RESET_CONTROL
/* MACreset */
set_reg(0x18107000,0x0000000F)
TODO:RTC registers occupy the offset range 0x18107000–0x18107FFC in the AR9331 addressspace.
set_reg(0x18107000, 0x00000000)
接下来有2个
otp_loop0: TODO
otp_loop1: TODO
fetch_otp: TODO
然后有:
*Program PMU */ TODO
/*Program ki, kd */
/*Program phase shift */
先对PLL设置了:PLLControl Registers 0x18050000-0x18050044 ap121.h 中解释比较明确
* PLL = (25 MHz * DIV_INT) / (2 ^ OUTDIV) (25 MHz * 32/2 = 400 MHz)
// DIV_INT =32 (25 MHz * 32/2 = 400 MHz)
// REFDIV =1
//RANGE = 0
//OUTDIV = 1
* CPU = PLL / CPU_POST_DIV
* DDR = PLL / DDR_POST_DIV
* AHB = PLL / AHB_POST_DIV
1./* setPLL bypass(Bit 2), CPU_POST_DIV, DDR_POST_DIV, AHB_POST_DIV inCPU clock control */
set_reg(AR7240_CPU_CLOCK_CONTROL,CPU_CLK_CONTROL_VAL1)
#if(CFG_PLL_FREQ == CFG_PLL_400_400_200)
#define CFG_HZ (400000000/2)
// CPU_DIV = 1, RAM_DIV = 1, AHB_DIV = 2
#define CPU_CLK_CONTROL_VAL1 0x00018004 // 1_1000--_0000_0000_0100
#define CPU_CLK_CONTROL_VAL2 0x00008000// 1000_0000_0000_0000
2. /* setSETTLE_TIME in CPU PLL */
set_reg(AR7240_USB_PLL_CONFIG,CPU_PLL_SETTLE_TIME_VAL)
#defineCPU_PLL_SETTLE_TIME_VAL 0x00000352
3. /* set nint, frac, refdiv, outdiv, rangein CPU PLL configuration resiter */
set_reg(AR7240_CPU_PLL_CONFIG,CPU_PLL_CONFIG_VAL1)
#define CPU_PLL_CONFIG_VAL10x40818000//100_0000_1000_0001_1000_0000_0000_0000
#defineCPU_PLL_CONFIG_VAL2 0x00818000// 1000_0001_1000_0000_0000_0000
//DIV_INT = 32
//REFDIV = 1
// RANGE = 0
//OUTDIV = 1
4.读取AR7240_CPU_PLL_CONFIG第31bit –》PLLupdate :1 ,pending 0,complete
5./* putfrac bit19:10 configuration */
set_reg(AR7240_PCIE_PLL_CONFIG,CPU_PLL_DITHER_FRAC_VAL)
6. /* clear PLL bypass(Bit 2), CPU_POST_DIV,DDR_POST_DIV, AHB_POST_DIV in CPU clock control */
set_reg(AR7240_CPU_CLOCK_CONTROL,CPU_CLK_CONTROL_VAL2)
7. /*Sync mode , Set Bit 8 of DDR Tap Conrtol 3 register */set_reg(AR7240_DDR_TAP_CONTROL3, 0x10105);
TODO:没有的
下面是/u-boot/cpu/mips/ar7240/hornet_ddr_init.S中的hornet_ddr_init,反正就是初始化ddr(ddr2)
然后执行/u-boot/cpu/mips/cache.S中的simple_mips_cache_reset (la t9, simple_mips_cache_reset)对cache进行初始化。
接着调用mips_cache_lock(la t9, mips_cache_lock)
(这个调用的目的:当代码执行到这个时候,ddr ram还没有配置好,而如果直接调用C语言的函数必须完成栈的设置,而栈必定要在ram中。所以,只有先把一部分cache拿来当做ram用。做法就是把一部分cache配置为栈的地址,锁定。这样,当读写栈的内存空间时,只会访问cache,而不会访问真的ram地址了。)
接着有:mips_cache_lock_24k
这时,配置栈的地址,进行调用函数bootstrap_board_init_f(/lib_bootstrap/bootstrap_board.c)进入函数board_init_f(la t9, board_init_f)后,首先做一些列的初始化:
Timer_init 时钟初始化
Serial_init 串口初始化
Init_func_ram 初始化内存,配置ddr controller
这一系列工作完成后,串口和内存都已经可以用了。
然后,就要把内存进行划分,在内存的最后一部分,留出u-boot代码大小的空间,准备把u-boot代码从flash搬移到这里。
然后,是堆的空间,malloc的内存就来自于这里。
紧接着放两个全局数据结构bd_infoglobal_data和环境变量boot_params。
最后,是栈的空间。
当内存划分好后,就准备进行relocate code了。
(relocate code含义:
通常u-boot的执行代码肯定是在flash上(调试可以在ram上).当启动起来之后,要把它从flash上搬移到ram里运行
)
但是,存在的问题是,flash地址和ram地址是不同的。当我们把代码从flash搬移到ram中后,当执行函数跳转时,代码里的函数地址还是flash的地址,一跳,又重新跳回去了(跳回了flash)。
IPC(position-independentcode) 由此引出了。
原理:
当使用IPC方式时,在用gcc编译时需要加上-fpic的选项。编译器会为你的可执行代码建立一个GOT(global offset table)的段。一个地址在GOT表中有一项,里面存放地址的信息,在使用这个地址时,只要根据这个地址的编号(也可以叫做偏移量offset)找到表中相应的项目,就可以取得那个地址了。
而如果位置发生变化,只要对GOT 表中的地址进行修改就可以了。
例:
Lw t9,1088(gp)
Jalr t9
这里,gp存放的就是GOT表的起始地址,而1088就是要调用函数offset,也就说GOT表的那个位置存放着它的地址。Lw t9,1088(gp)把函数地址放入t9寄存器,然后调用就可以了。
Relocate code说简单一点就是:把u-boot的执行代码直接从flash里copy到ram的相应区域。
然后,把GOT表中的地址都加上一个偏移量,这个偏移量就是flash里的地址与ram里的地址差。
这里完成的操作还有一些其他工作,比如:设置新的栈指针,从flash代码里跳转到ram代码里等等.
之后,进入board.c的board_init_r函数。进入stage2。
Stage2:
在board_init_r函数中初始化malloc,flash,pci以及外设(如:网口),最后进入命令行或者直接启动Linux Kernel.
这样,u-boot的启动工作完成。
本文转自:onejacky的专栏
联系客服