MIPS U-Boot ——by M.C Uboot启动流程 关中断
设置kseg0no cache 初始化gp指针 lowlevel_init mips_cache_lock
Board_init_f relocate_code Board_init_r
启动内核根据CPU rate初始化外部时钟、内存初始化cache cache未初始化前不能用 cache已初始化 设置临时cache栈
把uboot程序从flash搬到ram执行 start.S
mtc0zero, CP0_WATCHLO mtc0zero, CP0_WATCHHI 清除硬件数据断点,防止产生调试断点,导致程序停止。
芯片在复位后,某些寄存器的内容也许是你想要的结果,但是谁知道呢,为了保证准确无误,最好还是重新进行手动初始化
mfc0k0, CP0_STATUS li k1, ~ST0_IE and k0, k1
mtc0k0, CP0_STATUS 禁止全局中断
mtc0zero, CP0_CAUSE初始化异常寄存器,清除异常原因指示 mtc0zero, CP0_COUNT mtc0zero, CP0_COMPARE
初始化时钟寄存器,防止产生计数器中断
mips_cache_reset
设
置
kseg0cache
li t0, CONF_CM_UNCACHED mtc0t0, CP0_CONFIG
设置kseg0区不经过cache。cache需要先初始化才能使用。 bal1f nop .word_gp 1:
lw gp, 0(ra)bal分支调用,ra返回地址指向下下一条指令,即.word _gp
把.word_gp的存储位置载入gp寄存器,即设置GOT表的起始位置
la t9, lowlevel_init jalr t9
nop 根据CPU rate初始化外部时钟、内存。lowlevel_init函数定义见lowlevel_init.S
la t9, mips_cache_reset jalr t9 nop 初始化高速缓存cache。
mips_cache_reset函数定义见cache.S li t0, CONF_CM_CACHABLE_NONCOHERENT mtc0t0, CP0_CONFIG 设置kseg0区经过cache
li a0, CFG_INIT_SP_OFFSET la t9, mips_cache_lock jalr t9 nop
li t0, CFG_SDRAM_BASE + CFG_INIT_SP_OFFSET
la sp, 0(t0)C语言程序(接下来的函数board_init_f)调用需要栈,而ram还没有初始化不能使用,所以用CACHE_LOCK_SIZE大小的L1 D-cache锁定作为临时栈使用
#define CFG_INIT_SP_OFFSET 0x400000 以sp开始,CACHE_LOCK_SIZE大小的堆栈,使用 D-cache临时替代
la t9, board_init_f j t9 nop 初始化。
board_init_f函数定义见board.c kseg0CACHE_LOCK_SIZE CFG_INIT_SP_OFFSET sp start.S
relocate_code:对应board.c里的relocate_code(addr_sp, id, addr),此时a0=addr_sp,a1=id,a3=addr。
move sp, a0设置新的sp= addr_sp li t0, CFG_MONITOR_BASE la t3, in_ram lw t2, -12(t3) move
t1,
a2board/xxx/config.mk
#define
CFG_MONITOR_BASE TEXT_BASE
t0= uboot程序在flash的超始地址,t1 = 准备把uboot程序移到ram中的起始地址t2 = uboot程序的结束地址
move t6, gp
sub gp, CFG_MONITOR_BASE add gp, a2 sub t6, gp, t6t6 = 旧gp
计算旧的gp与相对于TEXT_BASE的偏移 计算新的gp在移动目的地内存中的地址
t6 = 新gp(内存中)与旧gp(flash中)的地址偏移量,这个值也是整个uboot从flash移到内存的偏移量。等同于(Destination Address -CFG_MONITOR_BASE)
1:
lw t3, 0(t0)
sw t3, 0(t1) addu t0, 4
ble t0, t2, 1b addu t1, 4按字拷贝,把uboot bin的内容从源地址(flash)拷贝到目的地址(内存),拷贝的长度是从uboot开始到uboot_end_data为止,uboot_end_data后面是bss段,详见u-
boot.lds
addi t0, a2, in_ram-_start j t0 nop
计算in_ram在内存中的新地址保存到t0,然后跳转到内存中运行in_ram。从此开始,uboot的代码将在ram中运行了
.gpword_GLOBAL_OFFSET_TABLE .word uboot_end_data .word uboot_end .word num_got_entries in_ram:
t3 = num_got_entries,t4 = 新的GOT[2] li t2, 2 1:
lw t1, 0(t4) beqz t1, 2f add t1, t6 sw t1, 0(t4) 2: addi t2, 1 blt t2, t3, 1b
addi t4, 4这里是一个循环,从GOT[2]开始,判断如果GOT表项有内容,则把内容从旧的GOT表拷贝到新的GOT表里。为什么要做拷贝呢?上面的程序已经把uboot整个程序从flash拷贝到了ram里,注意,只是内容拷贝,GOT里的地址还是老地址,所以要做重定位(如果是静态编译且属于模块内相对偏移,就不需要这个步骤了)。手工重定位的方法就是把旧GOT里的内容读取,然后加上偏移(uboot在flash与ram的偏移)赋值给新GOT里的对应表项
程序构建过程(编译和生成程序时的链接)为每个链接单元(构成动态链接程序的二进制文件,如.o、.a、.so)至少定义一个GOT,每个函数都可以找到自己的GOT,因为GOT的位置离链接单元的入口点的偏
移量是固定已知的。链接单元作为一个整体载入内存,所以GOT的内部偏移量与编译时是一样的。
u-boot在编译时使用-fpic,会生成一个.got段来存储绝对地址符号。
lw t1, -12(t0) lw t2, -8(t0) add t1, t6 add t2, t6 sub t1, 4 1: addi t1, 4 bltl t1, t2, 1b
sw zero, 0(t1)t1 = uboot_end_data,t2 = uboot_end 进行BSS段的清零,此时还没有操作系统,动态链接器,加载器等这些东东都还没有,所以要手动执行清零
move a0, a1
la t9, board_init_r j t9
move a1, a2跳转到ram中的board_init_r执行,根据MIPS ABI规范,a0、a1分别是board_init_r的第一个、第二个参数,即board_init_r(gd_t*id, ulong dest_addr)中id = a0,dest_addr= a2
lowlevel_init.S __cgu_init:
li t1, CGU_MODUL_BASE li t3, 150000000 beq a0, t3, 3f nop b5f nop 3:
li t2, 0x80000017 sw t2, CGU_DIVCR(t1) li t2, 0xC00B0001
sw t2, CGU_PLL1CR(t1) li t3, 0x80000000 5: j ra
nop #define CGU_MODUL_BASE 0xBF107000,此地址是kseg1的I/O映射区域
#define CGU_DIVCR(value) 0x0010(value) CGU分频控制寄存器(Divider Control Register)
#define CGU_PLL1SR(value) 0x000C(value) CGU锁相环状态寄存器(PLL1Status Register)
根据CPU_CLOCK_RATE跳转到数字标签3(假定是150 MHz clock),把DIV、PLL等参数设置到CGU里请根据实际芯片手册进行设置
lowlevel_init:
li a0, CPU_CLOCK_RATE CPU时钟频率载入a0寄存器
函数调用建议遵守MIPS ABI软件标准,比如使用a0寄存器作为函数的参数传递使用
move t0, ra保存返回地址,本段程序需要进行子函数调用,会修改ra bal__cgu_init
nop
设置时钟产生单元CGU(clock generation unit) bal__ebu_init nop 设置EBU(External Bus Unit) 同CGU的初始流程,请根据实际芯片手册进行设置
bal__sdram_init nop 设置DRAM时钟比、刷新频率、CAS延时,然后使能DRAM控制器同CGU的初始流程,请根据实际芯片手册进行设置
move ra, t0 j ra nop
恢复ra寄存器的值,并跳转返回到调用程序(start.S)继续执行
cache.S
mips_cache_reset: li t2, CFG_ICACHE_SIZE li t3, CFG_DCACHE_SIZE
li t4, CFG_CACHELINE_SIZE move t5, t4
li v0, MIPS_MAX_CACHE_SIZE 把cache大小载入寄存器 请根据实际芯片的DataSheet描述进行设置。如果想要更加保险,请读取芯片的Config寄存器获取芯片的这些能力值
li a0, KSEG1 addu a1, a0, v0 2: sw zero, 0(a0) sw zero, 4(a0)
sw zero, 8(a0) sw zero, 12(a0) sw zero, 16(a0) sw zero, 20(a0) sw zero, 24(a0) sw zero, 28(a0) addu a0, 32
bltu a0, a1, 2b 从kseg1的初始位置划分一块区域填充全零(需要初始化的区域大小一般设置为cache的最大值),后面会用这块区域作为填充指令cache的数据来源。这么做的原因是,CPU甚至对明显无效的高速缓存行也可能检测奇偶错误并发生自陷,因此需要用保证不会有奇偶错误的数据来填充指令cache。
mtc0zero, CP0_TAGLO在上电之后Cache内部的状态时未知的,需要先对其进行初始化,初始化的基本操作就是将Cache的Tag置为0
li a0, K0BASE move a2, t2 move a3, t4 move a1, a2
icacheopn(a0,a1,a2,a3,121,(Index_Store_Tag_I,Fill))初始化I-cache。#define K0BASE KSEG0,a0-kseg0,a1、a2-icacheSize,a3-icacheLineSize icacheopn()函数的展开结果就像下面这样:
a1 = min(a1, a2); a1 += a0
10:
cache Index_Store_Tag_I,0(a0); cache Fill,0(a0); cache Index_Store_Tag_I,0(a0)
bne a0,a1,10b; add a0,a3 li a0, K0BASE move a2, t3 move a3, t5 move a1, a2
icacheop(a0,a1,a2,a3,Index_Store_Tag_D) li a0, K0BASE move a2, t3 move a3, t5 move a1, a2
icacheopn(a0,a1,a2,a3,1lw,(dummy)) li a0, K0BASE move a2, t3 move a3, t5 move a1, a2
icacheop(a0,a1,a2,a3,Index_Store_Tag_D)D-cache的初始化与I-cache不同,因为没有类似指令那样对应数据方的Fill操作,我们只能通过从高速缓存加载而依靠通常的高速缓存失效处理icacheop()函数的展开结果就像下面这样:
a1 = min(a1, a2); a1 += a0 10:
cache Index_Store_Tag_D,0(a0) bne a0,a1,10b; add a0,a3
icacheopn()函数的展开结果就像下面这样: a1 = min(a1, a2); a1 += a0 10:
lw zero,0(a0)
bne a0,a1,10b; add a0,a3
j ra跳转返回到调用程序(start.S)继续执行
mips_cache_lock:
li a1, K0BASE -CACHE_LOCK_SIZE addu a0, a1
li a2, CACHE_LOCK_SIZE li a3, CFG_CACHELINE_SIZE move a1,a2
icacheop(a0,a1,a2,a3,0x1d)
j ra icacheop()函数的展开结果就像下面这样: a1 = min(a1, a2); a1 += a0 10:
cache 0x1d,0(a0) bne a0,a1,10b; add a0,a3
cache的指令形式是cache ops addr,其中ops有5位,低2位表示选择哪一个cache。0x1d(0x11101),表示在L1 D-cache上操作Fetch and Lock
Fetch and Lock将锁定一段D-cache,作为C代码的可写空间,作为临时堆栈使用。Lock的Cache空间发生cache miss时,不会被替换出去,即此部分映射的内存地址空间是永久存在cache中的,这样就会使得CPU访问此段内存空间时,都是直接对cache进行操作,而不会访问RAM。
void board_init_f(ulong bootflag){
board_init_f :f 表示flash 的意思,表示此函数代码还在flash 中运行
gd = &gd_data;
__asm__ __volatile__(\"\": : :\"memory\");memset ((void *)gd, 0, sizeof (gd_t));#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm (\"k0\")
__asm __ __volatile__(“”: : :“memory”):因为流水线优化,gcc 编译器会对指令进行乱序执行等优化。效果是保证
board_init_f 之前的指令在这个标志之前都已经执行完毕。即不
要将该段内嵌汇编指令与前面的指令重新排序,在这标志之前的指令一定比在这之后的指令先执行。
timer_init 计数器CP0(Count)、CP0(Compare)清零。
env_init 从fw_env.config 文件读取MTD 的环境变量分区信息(offset ,size 等)保存到envdevices 变量里,根据环境变量size 大小分配一段内存空间用于存放环境变量,并把分配的内存地址保存到environment 变量里。最大可支持两个环境变量分区,如果配置了两个分区信息,则优先选用激活的那个分区;如果两个都处于激活状态,则选用fw_env.config 文件的第一条配置分区init_baudrate serial_init console_init_f 初始化串口控制台。
从环境变量获取波特率配置,保存到gd->baudrate ,否则设置为默认值
按照芯片手册,设置串口的时钟源、波特率、数据格式、校验位、停止位、流控等display_banner 显示uboot 版本号及当前日期与时间信息checkboard 获取watchdog 芯片id 信息,cpu 频率信息
init_func_ram
获取DRAM 大小保存到gd->ram_size ,并设置DRAM 的行、列地址长度
调用incaip.c 的initdram 函数 内存空间映射设置 见右下图
relocate_code (addr_sp, id, addr); 参数见右下图 } kseg0
(CFG_SDRAM_BASE)0x80000000 0xBFFFFFFF addr_sp(SP) gd->ram_size 4K 对齐遗留空间
UBoot Code,Data,Bss
uboot_end-CFG_MONITOR_BASE MALLOC Board Info Global Data bd_t gd_t
TOTAL_MALLOC_LEN Boot Params
CFG_BOOTPARAMS_LEN 安全间隔空间 id addr
long int initdram(int board_type){ asm
volatile
(\"move
%0,
$25\"
:
\"=r\"
(our_address) :);our_address = $25(t9)寄存器的值,t9=board_init_f 函数的入口地址
if (PHYSADDR(our_address) < PHYSADDR(PHYS_FLASH_1)) {return max_sdram_size();}
如果our_address 地址小于PHYS_FLASH_1地址,说明程序不是在FLASH 中运行,而是在DRAM 中运行
如果程序在DRAM 中运行,则无法探测DRAM 的实际大小 for (cols = 0x8; cols <= 0xC; cols++){ for (rows = 0xB; rows <= 0xD; rows++){ *INCA_IP_SDRAM_MC_CFGPB0 = (0x14 << 8) | (rows << 4) | cols; size
=
get_ram_size((long
*)CFG_SDRAM_BASE,
max_sdram_size());if (size > max_size){
best_val = *INCA_IP_SDRAM_MC_CFGPB0;max_size = size;}}} 从DRAM 的起始地址到DRAM 支持的最大size 值这个范围内,通过写值、读值比较的方式进行验证DRAM 的最大有效size 大小
所以呢,如果程序是在DRAM 中运行的,这种改写DRAM 内容的方式会造成不可预知的异常;即使侥幸不发生异常,写的值被程序自身改变后,读值就发生变化了。只有当程序不在DRAM 在运行时,
才能进行DRAM 大小的实际探测
}
static ulong max_sdram_size(void){ #define CFG_DW 2#define CFG_NB 4
ulong cfgpb0 = *INCA_IP_SDRAM_MC_CFGPB0;int cols = cfgpb0 & 0xF;int rows = (cfgpb0 & 0xF0) >> 4;
ulong size = (1 << (rows + cols)) * CFG_DW * CFG_NB;return size;
根据DRAM 的访问地址、位宽、BANK 数来计算DRAM 支持的最大size 值
}
board_init_r:r表示ram的意思,表示此函数代码将在ram中运行
void board_init_r(gd_t*id, ulong dest_addr) {
flash_init获取FLASH ID与大小信息保存到flash_info结构体里 mem_malloc_init划分malloc区间,并把整个空间数据初始化为0
malloc_bin_reloc
env_relocate设置环境变量保存地址gd->env_addr getenv(\"ethaddr\")
从配置的环境变量信息里获取MAC、IP地址信息,保存到bd数据结构里
getenv_IPaddr(\"ipaddr\")
main_loop run_command(bootcmd)把内核加载到加载地址,设置uboot传递给内核的参数信息,再跳转到内核入口地址}
Uboot 启动内核 uboot 启动内核流程 run_command(bootcmd) +-do_bootm()
+-bootm_start() /* 解析uImage 头部信息到全局变量image */+-do_bootm_linux()| +-linux_params_init()| +-theKernel()
linux_params_init :
解析boot 环境变量bootargs ,bootargs 是Uboot 用来传递给内核的参数。start 是环境变量的内存起始地址(bd->bi_boot_params),从start 到
LINUX_MAX_ARGS 是指bi_boot_params 允许保存的最大参数大小(都是地址指针)。argp 是个变化的指针,指向保存bootargs 内容的地址。start 第一个保存是指向地址0x0的地址指针,后续依次存放bootargs 解析出来的参数的地址指针。
假设环境变量bootargs=ip=1 mac=2,则start[1] = char *x ,x = “ip =1”; start[2] = char *y ,y = “mac=2”。
从linux_env 开始的地方是保存一些其它参数,如gd->ram_size ,images->rd_start ,及环境变量ethaddr 等。最终传递给内核的四个参数如下:
●linux_argc :bootargs 的参数个数+1(bi_boot_params 第一个参数是零地址指针)
●linux_argv :bi_boot_params 的地址●linux_env :bootargs 之外的环境参数●0:
linux_params_init start *0 argp
LINUX_MAX_ARGS i p =1“\\0”m a c 2 “\\0” =*x *y x y linux_env m
*m m 16字节对齐
LINUX_MAX_ENVS m e s i z m e =46m
因篇幅问题不能全部显示,请点此查看更多更全内容