1. u-boot 介绍
u-boot 是一个open source 的bootloader,目前版本是1.1.2。u-boot 是在ppcboot 以及armboot 的基础上发展而来,相当的成熟和稳定,已经在许多嵌入式系统开发过程中被采用。由于其开发源代码,其支持的开发板众多。
为什么我们需要u-boot?显然可以将uClinux 直接烧入flash,从而不需要额外的引导装载程序(bootloader)。但是从软件升级的角度以及程序修补的来说,软件的自动更新非常重要。事实上,引导装载程序(bootloader)的用途不仅如此,但仅从软件的自动更新的需要就说明我们的开发是必要的。同时,u-boot 移植的过程也是一个对嵌入式系统包括软硬件以及操作系统加深理解的一个过程。
2. u-boot 移植的框架
移植u-boot 到新的开发板上仅需要修改和硬件相关的部分。在代码结构上:
1) 在board 目录下创建gold44b 目录,创建gold44b.c 以及flash.c,memsetup.S,u-boot.lds等。不需要从零开始,可选择一个相似的目录,直接复制过来,修改文件名以及内容。我在移植u-boot 过程中,选择的是Dave/B2目录。由于u-boot 已经包含基于s3c24b0 的开发板目录,作为参考,也可以复制相应的目录。
2) 在cpu 目录下创建s3c44b0x 目录,主要包含start.S,interrupts.c 以及cpu.c,serial.c几个文件。同样不需要从零开始建立文件,直接从arm720t 复制,然后修改相应内容。
3) 在include/configs 目录下添加gold44b.h,在这里放上全局的宏定义等。
4) 找到u-boot 根目录下Makefile 修改加入
gold44b_config : unconfig
@./mkconfig $(@:_config=) arm s3c44b0 gold44b
5) 运行make ev44bii_config,如果没有错误就可以开始硬件相关代码移植的工作
3. u-boot 的体系结构
1) 总体结构
u-boot 是一个层次式结构。做移植工作的软件人员应当提供串口驱动(UART Driver),以太网驱动(Ethernet Driver),Flash 驱动(Flash 驱动),USB 驱动(USB Driver)。目前,通过USB 口下载程序显得不是十分必要,而且开发板上也没有USB接口,所以暂时没有移植USB 驱动。驱动层之上是u-boot 的应用,command 通过串口提供人机界面。我们可以使用一些命令做一些常用的工作,比如内存查看命令md。
Kermit 应用主要用来支持使用串口通过超级终端下载应用程序。TFTP 则是通过网络方式来下载应用程序,例如uClinux 操作系统。
2) 内存分布
gold44b 的flash 大小2M(8bits),现在将0-40000 共256k 作为u-boot 的存储空间。由于u-boot 中有一些环境变量,例如ip 地址,引导文件名等,可在命令行通过setenv 配置好,通过saveenv 保存在40000-50000(共64k)这段空间里。如果存在保存好的环境变量,u-boot 引导将直接使用这些环境变量。正如从代码分析中可以看到,我们会把flash 引导代码搬移到DRAM 中运行。u-boot 的代码在DRAM中的位置在u-boot-1.1.2/board/gold44b/config.mk配置如下:TEXT_BASE = 0x0C700000。这样,引导代码u-boot将从0x0000 0000 处搬移到0x0C700000 处。特别注意的由于gold44b uClinux 中断向量程序地址在0x0c000 0000 处,所以不能将程序下载到0x0c000 0000 出,通常下载到0x0c008 0000 处。
4. start.S 代码结构
1) 定义入口
一个可执行的Image 必须有一个入口点并且只能有一个唯一的全局入口,通常这个入口放在Rom(flash)的0x0 地址。例如start.S 中的
.globl _start
_start:
值得注意的是你必须告诉编译器知道这个入口,这个工作主要是修改连接器脚本文件(lds)。
开发板上的u-boot.lds如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/s3c44b0/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
armboot_end_data = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
2) 设置异常向量(Exception Vector)
异常中断矢量表(Exception Vector Table)是u-boot与uClinux内核发生联系关键的地方之一。即使uClinux内核已经得到处理器的控制权运行,一旦发生中断,处理器还是会自动跳转到从0x0地址开始的第一级异常中断矢量表中的某个表项(依据于中断类型)处读取指令运行。
异常中断矢量表必须是从0 地址开始,连续的存放。如下面的就包括了复位(reset),未定义处理(undef),软件中断(SWI),预去指令错误(Pabort),数据错误 (Dabort),保留,以及IRQ,FIQ 等。注意这里的值必须与uClinux 的vector_base 一致。这就是说如果uClinux 中vector_base(include/armnommu/proc-armv/system.h)定义为0x0c00 0000,则HandleUndef 应该在
0x0c00 0004。
.globl _start
_start: b reset
/*Modfied by zl 2005-2-21 */
/* add pc, pc, #0x0c000000
add pc, pc, #0x0c000000
add pc, pc, #0x0c000000
add pc, pc, #0x0c000000
add pc, pc, #0x0c000000
add pc, pc, #0x0c000000
add pc, pc, #0x0c000000
*/
ldr pc, =0x0c000004
ldr pc, =0x0c000008
ldr pc, =0x0c00000c
ldr pc, =0x0c000010
ldr pc, =0x0c000014
ldr pc, =0x0c000018
ldr pc, =0x0c00001c
.balignl 16,0xdeadbeef
这里,地址0x0处的一级异常中断矢量表只简单地包含向二级异常中断矢量表的跳转指令就可以。这样,就能够正确地将发生的事件交给uClinux的中断处理程序来处理。这样设计是因为在本u-boot移植里不使用中断,8019和Timer都是轮询中断的,如果u-boot要使用中断(如要用到USB设备),就需要建立二级异常中断矢量表了。代码如下(没有调试通过):
- .globl _start
- _start: b reset
- add pc, pc, #0x0c000000
- add pc, pc, #0x0c000000
- add pc, pc, #0x0c000000
- add pc, pc, #0x0c000000
- add pc, pc, #0x0c000000
- add pc, pc, #0x0c000000
- add pc, pc, #0x0c000000
- .balignl 16,0xdeadbeef
- ....
在Reset是复制中断矢量表
- /*
- now copy to sram the interrupt vector
- */
- adr r0, real_vectors
- add r2, r0, #1024
- ldr r1, =0x0c000000
- add r1, r1, #0x08
- vector_copy_loop:
- ldmia r0!, {r3-r10}
- stmia r1!, {r3-r10}
- cmp r0, r2
- ble vector_copy_loop
....
建立三级中断跳转
- /*************************************************/
- /* interrupt vectors */
- /*************************************************/
- /*
- real_vectors:
- b reset
- b undefined_instruction
- b software_interrupt
- b prefetch_abort
- b data_abort
- b not_used
- b irq
- b fiq
- */
- /*************************************************/
- undefined_instruction:
- mov r6, #3
- b reset
- software_interrupt:
- mov r6, #4
- b reset
- prefetch_abort:
- mov r6, #5
- b reset
- data_abort:
- mov r6, #6
- b reset
- not_used:
- /* we *should* never reach this */
- mov r6, #7
- b reset
- irq:
- mov r6, #8
- b reset
- fiq:
- mov r6, #9
- b reset
3) 初始化CPU 相关的pll,clock,中断控制寄存器
依次为关闭watch dog timer,关闭中断,设置LockTime,PLL(phase lock loop),以及时钟。
这些值(除了LOCKTIME)都可从Samsung 44b0 的手册中查到。
- /*
- *************************************************************************
- *
- * CPU_init_critical registers
- *
- * setup important registers
- * setup memory timing
- *
- *************************************************************************
- */
- #define INTCON (0x01c00000+0x200000)
- #define INTMSK (0x01c00000+0x20000c)
- #define LOCKTIME (0x01c00000+0x18000c)
- #define PLLCON (0x01c00000+0x180000)
- #define CLKCON (0x01c00000+0x180004)
- #define WTCON (0x01c00000+0x130000)
- cpu_init_crit:
- /* disable watch dog */
- ldr r0, =WTCON
- ldr r1, =0x0
- str r1, [r0]
- /*
- * mask all IRQs by clearing all bits in the INTMRs
- */
- ldr r1,=INTMSK
- ldr r0, =0x03fffeff
- str r0, [r1]
- ldr r1, =INTCON
- ldr r0, =0x05
- str r0, [r1]
- /* Set Clock Control Register */
- ldr r1, =LOCKTIME
- ldrb r0, =800
- strb r0, [r1]
- ldr r1, =PLLCON
- #if CONFIG_S3C44B0_CLOCK_SPEED==64
- ldr r0, =0x38021 /* smdk4110: Xtal=8MHz Fclk=64MHz */
- #elif CONFIG_S3C44B0_CLOCK_SPEED==66
- ldr r0, =0x34031 /* 66MHz (Quartz=11MHz) */
- #elif CONFIG_S3C44B0_CLOCK_SPEED==75
- ldr r0, =0x610c1 /*B2: Xtal=20mhz Fclk=75MHz */
- #else
- # error CONFIG_S3C44B0_CLOCK_SPEED undefined
- #endif
- str r0, [r1]
- ldr r1,=CLKCON
- ldr r0, =0x7ff8
- str r0, [r1]
- mov pc, lr
4) 初始化SDRAM控制器
内存控制器(主要是SDRAM控制器),主要通过设置13 个从1c80000 开始的寄存器来设置,包括总线宽度,8 个内存bank,bank 大小,sclk,以及两个bank mode。
- #ifdef CONFIG_INIT_CRITICAL
- bl cpu_init_crit
- /*
- * before relocating, we have to setup RAM timing
- * because memory timing is board-dependend, you will
- * find a memsetup.S in your board directory.
- */
- bl memsetup
- #endif
初始化内存控制器的代码存放在u-boot-1.1.2/board/gold44b/memsetup.S中
与ADS或者SDT下的boot代码(memcfg.s和44binit.s)一致,只是汇编格式有点不一样。
5) 将rom 中的程序复制到RAM 中
首先利用PC 取得bootloader 在flash 的起始地址,再通过标号之差计算出这个程序代
码的大小。这些标号,编译器会在连接(link)的时候生成正确的分布的值。取得正
确信息后,通过寄存器(r3 到r10)做为复制的中间媒介,将代码复制到RAM 中。
- relocate: /* relocate U-Boot to RAM */
- adr r0, _start /* r0 <- current position of code */
- ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
- cmp r0, r1 /* don't reloc during debug */
- beq stack_setup
- ldr r2, _armboot_start
- ldr r3, _bss_start
- sub r2, r3, r2 /* r2 <- size of armboot */
- add r2, r0, r2 /* r2 <- source end address */
- copy_loop:
- ldmia r0!, {r3-r10} /* copy from source address [r0] */
- stmia r1!, {r3-r10} /* copy to target address [r1] */
- cmp r0, r2 /* until source end addreee [r2] */
- ble copy_loop
6) 初始化堆栈
进入各种模式设置相应模式的堆栈.
- /* Set up the stack */
- stack_setup:
- ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
- sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
- sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
- #ifdef CONFIG_USE_IRQ
- sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
- #endif
- sub sp, r0, #12 /* leave 3 words for abort-stack */