移植U-boot 到TQ2440实验板(一)-------Norflash 启动,代码重定位

注:本博文是记录本人学习移植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的代码重定位结束。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值