注:本博文是记录本人学习移植u-boot 中的遇到的问题,并且特定的TQ2440的实验板。其中借鉴了许多大牛的blog,在这里感谢他们。如有哪些知识表述不清,请自行学习,谢谢。 副标题代表本人觉得是难点的地方,也是本文的重点。
===========================================================================================================
编译环境: ubantu9.10
交叉编译工具: arm-linux-gcc-4.4.3
U-boot的版本: u-boot-2013.04
=============================================================================================================
本人在移植U-boot前已经具备的理论知识有:
1、 C语言 (精通)
2、 ARM 32汇编指令 (熟悉)
3、会看链接文件
4、 UART, SRAM, SDRAM, ADC, DMA, I2S, I2C,SPI ,nandflash 等(了解,真正的编写驱动时应该去查看其datesheet)
5、 Linux 环境的使用 (精通)
6、 Linux 内核方面的知识,如: Linux Makeflie, Linux内核源码树, Linux驱动模型(了解)
7、 S3C2440 芯片的特性(说实在的,这个只能是了解,每次出问题都去datesheet中查看)
8、 会看原理图(熟悉)
9、 ARM 调试工具的使用,基本的有J-Link, AXD(也即是code wrror,我并不用其编译,只用其debug),source insight。
其实上面的知识不知道的百度都能知道,学习驱动最重要的是要有正确的学习方法,和坚强的毅力。
=========================================================================================================
开始移植:
.globl _start //声明下
_start: b start_code //0x0地址的指令, 跳转到start_code 处
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 //中断向量表结束。 被烧写在 基地址+0x4——基地址+0x1c。 当系统发生异常时会自动的将PC指针指向这里,执行中断向量表中的内容。
_undefined_instruction: .word undefined_instruction // 类似C语言中的宏。
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
start_code
1、首先要搭建好工作环境。好的工作环境可以大大的提高过效率。这里简单的介绍下我当前的环境:
1)、 安装Linux虚拟机(最好安装vmware tools,然后共享windowns下的文件夹)。
2)、虚拟机中Linux 通过Samba 与Windows 进行通信(windowns中通过映射网络驱动器访问Linux下文件)。
3)、 解压u-boot-2013.04 Linux中。
4)、在windowns中安装Jlink驱动, codewarrior。
2、当u-boot 解压好后,读u-boot中readme,和makefile。这里就不一一介绍了。
3、我们在make u-boot 前必须配置u-boot, 使其支持哪种型号。可以查看boards.cfg文件,文件中每项代表什么意思网上有很多,这里就不介绍了。在这个文件中可以看到mini2440,这个应该是友善之臂添加到开源u-boot中。并且用的也是s3c2440,所以从简单的角度考虑将u-boot配置成mini2440
配置的方法:make mini2440_config
4、现在开始编译: make 。这是时候应该是可以正常的编译过的。 这时通过j-link 下载 u-boot.bin到norflash 中(注意: 板子要调成从norflash 启动)。
5、将开发板用串口和pc连接,波特率是115200.可以进入了u-boot的cmd模式。
7、 分析代码 (从头开始到代码重定位的代码结束) ---------本文的重点
u-boot的起始代码在u-boot-2013.04\arch\arm\cpu\arm920t\start.s 中。
问题: 怎么找到启动文件的? 答:查看u-boot-2013.04\arch\arm\cpu\u-boot.lds 文件中的 “ ENTRY(_start)”,告诉程序的入口是_start。
问题: 怎么确定用的是u-boot-2013.04\arch\arm\cpu\u-boot.lds ? 答:根据borad.cfg 文件的mini2440_config的配置,然后查看makefile。有经验的人根据borad.cfg可以直接找到。
浏览下代码:
.globl _start //声明下
_start: b start_code //0x0地址的指令, 跳转到start_code 处
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 //中断向量表结束。 被烧写在 基地址+0x4——基地址+0x1c。 当系统发生异常时会自动的将PC指针指向这里,执行中断向量表中的内容。
_undefined_instruction: .word undefined_instruction // 类似C语言中的宏。
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
start_code , 注释掉的都是没用到了。这样做事方便查看时清晰,简洁。
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
//没用
//#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/
// ldr r0, =_start
// ldr r1, =0x0
// mov r2, #16
//copyex:
// subs r2, r2, #1
// ldr r3, [r0], #4
// str r3, [r1], #4
// bne copyex
#endif
//#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
//查看Datasheet ,用的正是这个
# 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]
//# endif
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
//#endif /* CONFIG_S3C24X0 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
//#ifndef CONFIG_SKIP_LOWLEVEL_INIT
// bl cpu_init_crit
//#endif
bl _main
代码最后跳转到 _main中
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) //设置栈,为执行C语言做准备 。 CONFIG_SYS_INIT_SP_ADDR定义在mini2440中,等于0x40001000 - 160, 为什么是0x40001000呢? 因为s3c2440从norflash启动后,会将片内的SRAM(catch)映射到
//0x40000000 地址, 而这个sram 大小是4k, 所以是0x40001000 。 160是 gd_t结构体的长度。这里很有意思,为什么不是从0x40001000开始,而是从0x40001000-160 开始呢,是怕栈不够用,预留的?
//#endif
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ //8字节对齐
sub sp, #GD_SIZE /* allocate one GD above SP */ // 分配 GD结构图的其实地址: 0x40001000 -160 -160
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ //8字节对齐
mov r8, sp /* GD is above SP */ // 将GD结构体的地址复制给r8
mov r0, #0 // 进入borad_init_f的参数复制个r0
bl board_init_f
borad_init_f 代码是在是太长,这里我将 borad_init_f 中的debug 的信息给出,可以对照着代码查看:
debug info
monitor len: 00029804
ramsize: 04000000 // TQ2440里面的SDRAM是64M ,并且其映射地址为0x30000000
TLB table from 33ff0000 to 33ff4000
Top of RAM usable for U-Boot at: 33ff0000
Reserving 166k for U-Boot at: 33fc6000
Reserving 2080k for malloc() at: 33dbe000
Reserving 32 Bytes for Board Info at: 33dbdfe0
Reserving 160 Bytes for Global Data at: 33dbdf40
New Stack Pointer is: 33dbdf30
RAM Configuration:
Bank #0: 30000000 64 MiB
relocation Offset is: 33fc6000
没打印出来的信息就是没有执行到。 从这里就可以看出 64M SDRAM是如何进行划分的。从SDRAM 的后面开始向前分配,而SDRAM中的划分:
0x33ff0000-0x33ff4000 TLB
0x33fc6000-0x33ff0000 u-boot
0x33dbe00-0x33fc6000 malloc
0x33dbde0-0x33dbe000 board info
0x33dbdf40-0x33dbde0 global data
0x33dbdf30-0x33dbdf40 sp (栈指针地址)。
borad_init_f 函数中几个和代码重定位的有关的数据是:
gd->relocaddr = addr; // 0x33fc6000
gd->start_addr_sp = addr_sp; 0x33dbdf30
gd->reloc_off = addr - _TEXT_BASE; 0x33fc6000
返回到_main 函数中:
#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, [r8, #GD_START_ADDR_SP] /* r8 = gd->start_addr_sp */ //将sp指向sdram中的栈地址,因为原来是指向片内sram的
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ 8字节对齐
ldr r8, [r8, #GD_BD] /* r8 = gd->bd */ 找到boardinfo的首地址 执行这条语句后,R8 中的地址指向的就是SDRAM中。
sub r8, r8, #GD_SIZE /* new GD is below bd */ 因为boardinfo 的首地址是globl data的末尾地址,所以,用boardinfo首地址减去 globl data的长度就找到gd_t结构体在SDRAM中的地址。
adr lr, here //将here 地址放到lr寄存器中,当代码重定位结束后,跳转到sdram中执行时用到了
ldr r0, [r8, #GD_RELOC_OFF] /* lr = gd->start_addr_sp */ 找到gd->start_addr_sp,将这个值复制给r0寄存器
add lr, lr, r0 // here在norflash中的地址,加上相对位移就是 here在SDRAM中位置, 暂时,这个SDRAM上的这个地址还没有东西。
ldr r0, [r8, #GD_RELOCADDR] /* r0 = gd->relocaddr */ 代码放到SDRAM中的首地地址。
b relocate_code
here:
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
ldr r0, =__bss_start /* this is auto-relocated! */
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
解释: r8中存放的是gd_t 类型结构体的首地址, r8是定义可以查看 DECLARE_GLOBAL_DATA_PTR 这个宏,就会明白了。
在borad_init_f 函数的最后一行又一个memcpy工作, 它将 SRAM中的gd_t 结构体中的内容拷贝到了SDRAM中。 所以,在上面执行
ldr r8, [r8, #GD_BD]
sub r8, r8, #GD_SIZE
后,即使r8寄存器中存储的是gd_t 在sdram中地址,这个地址中内容和sram的内容是一样。
注意:borad_init_f()中执行了init_sequence数组中的函数其中包括了 对串口的初始化,和sdram的初始化。但是我发现对串口初始化后 ,debug的信息依然打印不出来。
而到第二部分才能打印出来。 原因很简单:在serial_init()函数中 get_current()->start() 这个start结构的值是 serial_init_dev()函数,而这个serial_init_dev函数中设置了串口相关的寄存器,但是没有去设置串口复用的gpio接口,所以将
board_init() ; 放到
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
前面就可以了。 这个borad_init 函数里面就初始化了GPIO的串口引脚。注意borad_init ()函数中的gpio_direction_output()函数并不适用去TQ2440中,这个不是很难,知道这个函数不适用就行了。
上面代码执行后需要注意的几个寄存器有: r0 和Lr。 重复说下很有必要: r0中存放的是u-boot 拷贝到SDRAM的地址。 lr是here 标签在SDRAM中的地址。
啰嗦了大半天终于到 relocate_code了,上代码:
relocate_code:
mov r6, r0 /* save addr of destination */ //将r0 放到 r6中
adr r0, _start // u-boot在norflash中的起始地址方到r0中
subs r9, r6, r0 /* r9 <- relocation offset */ 这两条 的意思是 用r6 -r0,如果等于0,即 u-boot的原地址和 要拷贝的目的地址相等, r9是原地址和目的地址的相对位移,后面会用到
beq relocate_done /* skip relocation */ 直接跳到 relocate_done
mov r1, r6 /* r1 <- scratch for copy_loop */ 不相等,继续执行到这里, 将 r1 赋值为u-boot要拷贝的目的地址
ldr r3, _image_copy_end_ofs // _image_copy_end_ofs 就是u-boot拷贝结束的位移,
add r2, r0, r3 /* r2 <- source end address */ u-boot的起始地址,加上u-boot拷贝结束位移的值,赋值给r2
copy_loop:
ldmia r0!, {r10-r11} /* copy from source address [r0] */ 从r0(u-boot在norflash的地址)开始 将其内容赋值给 r10 r11,然后r0应该是加8
stmia r1!, {r10-r11} /* copy to target address [r1] */ 从R10 R11 中取值赋值给r1地址, r1自动 也加8.
cmp r0, r2 /* until source end address [r2] */ 比较r0中的地址是否到达r2
blo copy_loop //没有,继续循环拷贝
#ifndef CONFIG_SPL_BUILD
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */ //u-boot已经被拷贝到SRAM中。 将_TEXT_BASE 赋值给r0
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */ // 这两句不知道干嘛的
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */ // _rel_dyn_start_ofs这个是 __rel_dyn_start - _start的位移,从__rel_dyn_start 地址开始到结束,里面的地址应该是数据引用的修正,我理解的是这个地方的数据时可以更改,方便代码重定位后调用,如果不修改的话,依然在norflash中运行。
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */ r2=_TEXT_BASE+_rel_dyn_start_ofs
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */ r3是__rel_dyn的结束位移
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */ r3 是__rel_dyn_end真正在norflash中结束的地址
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */ [r2]的意思是取r2地址的的值,这是R2的值是__rel_dyn_start地址,将__rel_dyn地址赋值给r0。
add r0, r0, r9 /* r0 <- location to fix up in RAM */ 把这个值和 r9中存储的位移值 相加,就是__rel_dyn_start在SRAM中的值
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */ 这三句是判断 __rel_dyn+4这个地址是否是 0x17(规定的格式),
beq fixrel //如果是,那么代表的是相当地址,跳到fixrel 去执行。
cmp r7, #2 /* absolute fixup? */ //如果_rel_dyn+4 这个地址的值是2,代表绝对地址,进入fixabs去执行。
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0] // r0中存储的是__rel_dyn_start的地址,取该地址中的值。例如__rel_dyn_start的地址是0x3fc60000+00023498,而这个地址里面的值是00000020, 而00000020的地址就是_undefined_instruction,是异常中断限量表的异常函数地址。 将这个值赋值给r1, r1中就是00000020
add r1, r1, r9 // r1 = 00000020+0x3fc60000,所以在发生异常中断时,就是通过查找 _rel_dyn_start,来确定这个地址。
fixnext:
str r1, [r0] // 将r0的初始值是__rel_dyn_start在SDRAM中的地址, 所以这句话的意思是将0x3fc60020 放到 0x3fc60000+__rel_dyn_start,即0x3fc60000+23498.
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */ 8字节对齐
cmp r2, r3 //比较下是否r2中的地址是否到达__rel_dyn_end,如果到达了,修改结束。
blo fixloop
#endif
跳转到 here中。
relocate_done:
mov pc, lr
到此为止,u-boot的代码重定位结束。