移植u-boot到mini2440--初始化代码分析
来源:互联网 发布:淘宝外卖粮票口令分享 编辑:程序博客网 时间:2024/05/17 05:01
本文简单的分析下u-boot2016 的初始化汇编代码,并且能从openjtag 加载启动。
代码从 arch/arm/lib/vectors.S
开始执行全局标号 _start:
_start:#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG .word CONFIG_SYS_DV_NOR_BOOT_CFG#endif b reset ldr pc, _undefined_instruction ...... ldr pc, _fiq
关于 ldr 伪指令,可见前文分析,这里直接执行b指令跳转到reset位置处。关于b指令可见这里。
下面我们再来看看 reset 代码段,reset定义在:arch/arm/cpu/arm920t/start.S文件中,reset(英文复位),系统复位时也会从这里开始,处理硬件上的复位,软件复位命令时通过看门狗操作实现。reset开始的代码段实现切换到超级用户模式(SVC 模式)。为什么要在初始化的时候设置为svc模式,可见这里。
接下来的重定位异常向量表,因为宏没有定义,所以没编译进去,关于这一点可以从生成的u-boot.dis 文件看出来。
然后就是常说的几个例行操作:关看门狗、屏蔽中断、设置系统时钟,因为现有的系统代码没有对s3c2440设置的支持,所以这里需要加上(直接上代码,具体原理可以见s3c2440芯片手册):
#ifdef CONFIG_S3C24X0 /* turn off the watchdog */# if defined(CONFIG_S3C2400)# define pWTCON 0x15300000# define INTMSK 0x14400008 /* Interrupt-Controller base addresses */# define CLKDIVN 0x14800014 /* clock divisor register */#else# define pWTCON 0x53000000# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */# define INTSUBMSK 0x4A00001C# define CLKDIVN 0x4C000014 /* clock divisor register */# endif ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] /* * mask all IRQs by setting all bits in the INTMR - default */ mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0]# if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK str r1, [r0]# elif defined(CONFIG_S3C2440) ldr r1, =0x7fff ldr r0, =INTSUBMSK str r1, [r0]# endif# if defined(CONFIG_S3C2440) # define MPLLCON 0x4C000004 /* 系统主频配置寄存器 */# define UPLLCON 0x4C000008 /* USB频率配置寄存器 */# define CAMDIVN 0x4C000018 /* CAMERA时钟分频寄存器 */# define MMDIV_405 (0x7f<<12)# define MPSDIV_405 0x21# define UMDIV_48 (0x38<<12)# define UPSDIV_48 0X22 ldr r0, =CAMDIVN mov r1, #0 str r1, [r0] /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN mov r1, #0x05 str r1, [r0] /* 如果HDIVN不等于0,CPU必须设置为异步总线模式 */ mrc p15, 0, r0, c1, c0, 0 orr r0, r0, #0xC0000000 mcr p15, 0, r0, c1, c0, 0 ldr r0, =UPLLCON mov r1, #UMDIV_48 /* USB时钟48MHz */ add r1, r1, #UPSDIV_48 str r1, [r0] /* * When you set MPLL&UPLL values, you have to set the UPLL * value first and then the MPLL value. (Needs intervals * approximately 7 NOP) */ nop nop nop nop nop nop nop ldr r0, =MPLLCON mov r1, #MMDIV_405 /* cpu时钟 400MHz */ add r1, r1, #MPSDIV_405 str r1, [r0]# else /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN mov r1, #3 str r1, [r0]#endif#endif /* CONFIG_S3C24X0 */
然后就是 cpu_init_crit ,这里主要完成dram初始化操作,因为现阶段目标是让u-boot用openjtag加载跑起来,因此先不考虑cpu_init_crit。
如果不执行 cpu_init_crit ,就会调用函数 _main。来到了arch/arm/lib/crt0.S 文件中。
ENTRY(_main)/* * Set up initial C runtime environment and call board_init_f(0). */#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK)#else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)#endif#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3#else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */#endif mov r0, sp bl board_init_f_mem mov sp, r0 mov r0, #0 bl board_init_f#if ! defined(CONFIG_SPL_BUILD)/* * Set up intermediate environment (new sp and gd) and call * relocate_code(addr_moni). Trick here is that we'll return * 'here' but relocated. */ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
宏定义 CONFIG_SYS_INIT_SP_ADDR 在文件 mini2440.h 中。注意这两行:
mov r0, sp bl board_init_f_mem
C函数 board_init_f_mem(ulong top) 的参数top,就是上面的寄存器r0,这个函数主要完成 global_data 的简单初始化,global_data 是一个很大的数据结构,放在栈中,这里先不讲解,等以后用到了再说。并且把栈减去一个合适的数值返回。
然后就来到了函数 void board_init_f(ulong boot_flags), 在文件 common/board_f.c ,这个函数主要功能就是完成 init_sequence_f 函数指针数组每个函数的调用。
1> 首先看 setup_mon_len :
gd->mon_len = (ulong)&__bss_end - (ulong)_start;
这行代码是获取u-boot代码的大小。
2> 然后就是 timer_init : arch/arm/cpu/arm920t/s3c24x0/timer.c 可以看到这个函数做了很多的初始化工作。
3> 接着是 serial_init : drivers/serial/serial.c
int serial_init(void){ gd->flags |= GD_FLG_SERIAL_READY; return get_current()->start();}
在函数 get_current() 中返回的 dev 是 default_serial_console();最后 get_current()->start(); 调用的函数是 serial_init_dev – drivers/serial/serial_s3c24x0.c,做一些特定串口初始化。
4> 接着是dram_init :board/samsung/mini2440/mini2440.c,简单的初始化 ram_size 为64M。
int dram_init(void){ /* dram_init must store complete ramsize in gd->ram_size */ gd->ram_size = PHYS_SDRAM_1_SIZE; return 0;}
dram_init实现可以通过配置文件定义宏定义来实现,也可以通过对ddrc控制器读获取dram信息。
5> setup_dest_addr 这个函数初始化 gd->ram_top(可用内存空间顶部)然后赋值
gd->relocaddr = gd->ram_top;
后面的一系列函数会对调整relocaddr 对 sdram 空间进行规划。
/* * now that we have dram mapped and working, we can * relocate the code and continue running from dram. * * reserve memory at end of ram for (top down in that order): * - area that won't get touched by u-boot and linux (optional) * - kernel log buffer * - protected ram * - lcd framebuffer * - monitor code * - board info struct */ setup_dest_addr,
static int setup_dest_addr(void){ gd->ram_top = CONFIG_SYS_SDRAM_BASE; ///#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1 ///#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */ gd->ram_top += gd->ram_size; ///到了可用sdram的顶端 gd->relocaddr = gd->ram_top;}
6 > 根据 reserve_mmu 的宏定义判断,当 icache 和 dcache 有任意一个打开的时候则预留出PATABLE_SIZE大小的tlb空间,tlb存放首地址赋值给gd->arch.tlb_addr。
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \ defined(CONFIG_ARM)static int reserve_mmu(void){ /* reserve TLB table */ gd->arch.tlb_size = PGTABLE_SIZE; gd->relocaddr -= gd->arch.tlb_size; /* round down to next 64 kB limit */ gd->relocaddr &= ~(0x10000 - 1); gd->arch.tlb_addr = gd->relocaddr; debug("TLB table from %08lx to %08lx\n", gd->arch.tlb_addr, gd->arch.tlb_addr + gd->arch.tlb_size); return 0;}#endif
7 > reserve_uboot
static int reserve_uboot(void){ /* * reserve memory for U-Boot code, data & bss * round down to next 4 kB limit */ gd->relocaddr -= gd->mon_len; gd->relocaddr &= ~(4096 - 1); gd->start_addr_sp = gd->relocaddr;}
这里 减去 gd->mon_len为 uboot 的code留出空间,到这里addr的值就确定,addr作为uboot relocate的目标addr。
先总结一下从setup_dest_addr之后到reserve_uboot 之间的函数实现了 addr 之上 sdram 空间的划分,由高到低 (根据宏定义可能还保留有其它的内存块):
top–>hide mem–>tlb space(16K)–>uboot code space–>addr
然后从 reserve_malloc 开始的函数就是调整 start_addr_sp 保留内存空间了。
8 > reserve_malloc 分配堆空间,这里是 TOTAL_MALLOC_LEN。
9 > reserve_board 为 bd_info 结构体预留空间。
10 > reserve_global_data 为 global_data 结构体预留空间。
11 > reserve_stacks() 首先栈指针16字节对齐
/* make stack pointer 16-byte aligned */ gd->start_addr_sp -= 16; gd->start_addr_sp &= ~0xf;
然后调用 arch_reserve_stacks() 这个函数为abort stack分配空间,并且初始化irq_sp,作为异常栈指针。
12 > setup_dram_config() 调用 dram_init_banksize() 后者声明为 __weak 类型,我们可以有自己的实现。这个函数初始化内存的首地址和大小。
13> show_dram_config() 打印 ram 区域分配。
14> setup_reloc() 计算出要重定位的偏移大小:
gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
gd->relocaddr 为目标addr,gd->start_addr_sp 为目标 addr_sp ,gd->reloc_off 为目标addr和现在实际code起始地址的偏移。reloc_off非常重要,会作为后面 relocate_code 函数的参数,来实现code的拷贝。
board_init_f 函数将 sdram 空间重新进行了划分,可以看出栈空间和堆空间是分开的。
board_init_f 初始化过程至此结束。
然后就是 代码重定位了,关于u-boot 的代码重定位涉及到的知识点很多,包括了编译器选项,反汇编分析,arm寻址方式 其中一位大牛已经写的很详细了,直接附上他讲解的地址吧。
这里总结几点:
1)对于函数调用用的是相对跳转,这个不受重定位影响。
2)对于全局变量,包括指针变量(指向函数,指向全局变量)在函数的尾部存放着这些变量的地址(叫做LABEL),当要访问这些变量(或者指针)的时候 用PC+相对偏移获取(全局变量,或者指针)的地址。
如果重定位,对于全局变量只要让函数尾部的(LABEL+相对偏移)就可以 就可以获取到全局变量了。
但如果这个全局变量是个指针,上面的一步完成之后,那么指针的值也要修改。这个也是通过 rel.dyn 段来完成的。也就是说对于全局指针变量 rel.dyn 有2项数据成员来完成它的重定位操作。
重定位代码之后就是 relocate_vectors 重定位异常向量,
/* * Copy the relocated exception vectors to the * correct address * CP15 c1 V bit gives us the location of the vectors: * 0x00000000 or 0xFFFF0000. */ ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */ ands r2, r2, #(1 << 13) ldreq r1, =0x00000000 /* If V=0 */ ldrne r1, =0xFFFF0000 /* If V=1 */ ldmia r0!, {r2-r8,r10} stmia r1!, {r2-r8,r10} ldmia r0!, {r2-r8,r10} stmia r1!, {r2-r8,r10}
通过注释可以看出,这里通过读取协处理器,获取异常地址,这里正好要把异常向量放到0x0地址处,这个位置是SRAM空间。(如果是从norflash启动的时候,0x0 地址处 就无法写了)
然后就是 clbss_l 清除 bss 段,bss 段存放程序中未初始化的全局变量和静态变量。特点是可读写的,在程序执行之前BSS段应该清0。
接着是 coloured_LED_init ,这个可以适当控制LED灯,实现调试作用。
最后就是 board_init_r : dest_addr 是新code地址
void board_init_r(gd_t *new_gd, ulong dest_addr)
- 移植u-boot到mini2440--初始化代码分析
- 移植u-boot到mini2440--board_init_r 分析
- u-boot移植到mini2440
- U-boot-2014.04移植到MINI2440(3) Makefile分析
- u-boot-2009.11移植到mini2440(一)
- 移植u-boot到mini2440开发板。
- u-boot-2011.03移植nandflash到mini2440
- u-boot移植到mini2440之一
- u-boot移植到mini2440之二
- u-boot移植到mini2440之三
- u-boot移植到mini2440之四
- U-boot移植到mini2440上
- 移植u-boot到mini2440--SPL初探
- u-boot移植到mini2440,u-boot版本2008.10
- 原创 u-boot移植到mini2440,u-boot版本2008.10
- u-boot移植到mini2440,u-boot版本2008.10 收藏
- u-boot移植到mini2440,u-boot版本2008.10
- u-boot移植到mini2440,u-boot版本2008.10
- 快速高效学习Java编程在线资源Top 20
- cas系列(五)--CAS+OpenLDAP实现SSO
- 调整合适的画布尺寸(游戏)
- win7下删除cygwin或者msys2的ssh服务
- 50个网络安全工具
- 移植u-boot到mini2440--初始化代码分析
- Android优雅地实现夜间模式
- FPGA第八篇:运算符、赋值语句和结构说明语句
- 静态代理
- 最近使用ut的一些体会
- 图像去模糊(逆滤波)
- SPOJ 694 Distinct Substrings(后缀数组)
- Android 获取内外置存储卡方法
- 基于webmagic的爬虫小应用--爬取知乎用户信息