u-boot 是一个open source 的bootloader,目前版本是1.1.2。u-boot 是在ppcboot 以及armboot 的基础上发展而来,相当的成熟和稳定,已经在许多嵌入式系统开发过程中被采用。由于其开发源代码,其支持的开发板众多。
为什么我们需要u-boot?显然可以将uClinux 直接烧入flash,从而不需要额外的引导装载程序(bootloader)。但是从软件升级的角度以及程序修补的来说,软件的自动更新非常重要。事实上,引导装载程序(bootloader)的用途不仅如此,但仅从软件的自动更新的需要就说明我们的开发是必要的。同时,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,如果没有错误就可以开始硬件相关代码移植的工作
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 处。
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 */
7) 转到RAM 中执行
使用指令ldr,pc,RAM 中C 函数地址就可以转到RAM 中去执行。
ldr pc, _start_armboot
5. 系统初始化部分
1) 串口部分(u-boot-1.1.2/cpu/s3c44b0/serial.c)
串口的设置主要包括初始化串口部分,值得注意的串口的Baudrate 与时钟MCLK 有很大关系,是通过:rUBRDIV0=( (int)(MCLK/16./(gd ->baudrate) + 0.5) -1 )计算得出。这可以在手册中查到。由于u-boot支持可变的波特率,所以采用宏定义设置默认波特率(64Mhz,115200bps)和其他波特率。代码如下:
void serial_setbrg (void)
{
DECLARE_GLOBAL_DATA_PTR;
u32 divisor = 0;
/* get correct divisor */
switch(gd->baudrate) {
case 1200:
#if CONFIG_S3C44B0_CLOCK_SPEED==66
divisor = 3124;
#elif CONFIG_S3C44B0_CLOCK_SPEED==75
divisor = 3905;
#elif CONFIG_S3C44B0_CLOCK_SPEED==64 /默认
divisor = 3332;
#else
# error CONFIG_S3C44B0_CLOCK_SPEED undefined
#endif
break;
case 9600:
#if CONFIG_S3C44B0_CLOCK_SPEED==66
divisor = 390;
#elif CONFIG_S3C44B0_CLOCK_SPEED==75
divisor = 487;
#elif CONFIG_S3C44B0_CLOCK_SPEED==64 /默认
divisor = 416;
#else
# error CONFIG_S3C44B0_CLOCK_SPEED undefined
#endif
break;
case 19200:
#if CONFIG_S3C44B0_CLOCK_SPEED==66
divisor = 194;
#elif CONFIG_S3C44B0_CLOCK_SPEED==75
divisor = 243;
#elif CONFIG_S3C44B0_CLOCK_SPEED==64 /默认
divisor = 207;
#else
# error CONFIG_S3C44B0_CLOCK_SPEED undefined
#endif
break;
case 38400:
#if CONFIG_S3C44B0_CLOCK_SPEED==66
divisor = 97;
#elif CONFIG_S3C44B0_CLOCK_SPEED==75
divisor = 121;
#elif CONFIG_S3C44B0_CLOCK_SPEED==64 /默认
divisor = 103;
#else
# error CONFIG_S3C44B0_CLOCK_SPEED undefined
#endif break;
case 57600:
#if CONFIG_S3C44B0_CLOCK_SPEED==66
divisor = 64;
#elif CONFIG_S3C44B0_CLOCK_SPEED==75
divisor = 80;
#elif CONFIG_S3C44B0_CLOCK_SPEED==64 /默认
divisor = 68;
#else
# error CONFIG_S3C44B0_CLOCK_SPEED undefined
#endif break;
case 115200:
#if CONFIG_S3C44B0_CLOCK_SPEED==66
divisor = 32;
#elif CONFIG_S3C44B0_CLOCK_SPEED==64
divisor = 34;
#elif CONFIG_S3C44B0_CLOCK_SPEED==75 /默认
divisor = 40;
#else
# error CONFIG_S3C44B0_CLOCK_SPEED undefined
#endif break;
}
serial_flush_output();
serial_flush_input();
UFCON0 = 0x0;
ULCON0 = 0x03;
UCON0 = 0x05;
UBRDIV0 = divisor;
UFCON1 = 0x0;
ULCON1 = 0x03;
UCON1 = 0x05;
UBRDIV1 = divisor;
for(divisor=0; divisor<100; divisor++) {
/* NOP */
}
}
其他的函数包括发送,接收。这个时候没有中断,是通过循环等待来判断是否动作完成。
例如,接收函数:
static int serial_flush_input(void)
{
volatile u32 tmp;
/* keep on reading as long as the receiver is not empty */
while(UTRSTAT0&0x01) {
tmp = REGB(URXH0);
}
return 0;
}
2) 时钟部分(u-boot-1.1.2/cpu/s3c44b0/interrupt.c)
实现了延时函数udelay。
这里的get_timer 由于没有使用中断,是使用全局变量来累加的。
void udelay (unsigned long usec)
{
ulong tmo;
tmo = usec / 1000;
tmo *= CFG_HZ;
tmo /= 8;
tmo += get_timer (0);
while (get_timer_masked () < tmo)
/*NOP*/;
}
3) flash 部分(u-boot-1.1.2/board/gold44b.c)
flash 作为内存的一部分,读肯定没有问题,关键是flash 的写部分。
Flash 的写必须先擦除,然后再写。
flash_init 完成初始化部分,这里的主要目的是检验flash 的型号是否正确。
unsigned long flash_init (void)
{
#ifdef __DEBUG_START_FROM_SRAM__
return CFG_DUMMY_FLASH_SIZE;
#else
unsigned long size_b0;
int i;
/* Init: no FLASHes known */
for (i=0; i flash_info[i].flash_id = FLASH_UNKNOWN; } /* Static FLASH Bank configuration here - FIXME XXX */ size_b0 = flash_get_size((vu_long *)CFG_FLASH_BASE, &flash_info[0]); if (flash_info[0].flash_id == FLASH_UNKNOWN) { printf ("## Unknown FLASH on Bank 0 - Size = 0x%08lx = %ld MB\n", size_b0, size_b0<<20); } /* Setup offsets */ flash_get_offsets (0, &flash_info[0]); /* Monitor protection ON by default */ (void)flash_protect(FLAG_PROTECT_SET, -CFG_MONITOR_LEN, 0xffffffff, &flash_info[0]); flash_info[0].size = size_b0; return (size_b0); #endif } flash_erase 擦除flash,BlankCheck 则检查该部分内容是否擦除成功。 int flash_erase (flash_info_t *info, int s_first, int s_last) { volatile CFG_FLASH_WORD_SIZE *addr = (CFG_FLASH_WORD_SIZE *)(info->start[0]); volatile CFG_FLASH_WORD_SIZE *addr2; int flag, prot, sect, l_sect; ulong start, now, last; int i; if ((s_first < 0) || (s_first > s_last)) { if (info->flash_id == FLASH_UNKNOWN) { printf ("- missing\n"); } else { printf ("- no sectors to erase\n"); } return 1; } if (info->flash_id == FLASH_UNKNOWN) { printf ("Can't erase unknown flash type - aborted\n"); return 1; } prot = 0; for (sect=s_first; sect<=s_last; ++sect) { if (info->protect[sect]) { prot++; } } if (prot) { printf ("- Warning: %d protected sectors will not be erased!\n", prot); } else { printf ("\n"); } l_sect = -1; /* Disable interrupts which might cause a timeout here */ flag = disable_interrupts(); /* Start erase on unprotected sectors */ for (sect = s_first; sect<=s_last; sect++) { if (info->protect[sect] == 0) { /* not protected */ addr2 = (CFG_FLASH_WORD_SIZE *)(info->start[sect]); if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_SST) { addr[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00AA00AA; addr[CFG_FLASH_ADDR1] = (CFG_FLASH_WORD_SIZE)0x00550055; addr[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00800080; addr[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00AA00AA; addr[CFG_FLASH_ADDR1] = (CFG_FLASH_WORD_SIZE)0x00550055; addr2[0] = (CFG_FLASH_WORD_SIZE)0x00500050; /* block erase */ for (i=0; i<50; i++) udelay(1000); /* wait 1 ms */ } else { if (sect == s_first) { addr[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00AA00AA; addr[CFG_FLASH_ADDR1] = (CFG_FLASH_WORD_SIZE)0x00550055; addr[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00800080; addr[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00AA00AA; addr[CFG_FLASH_ADDR1] = (CFG_FLASH_WORD_SIZE)0x00550055; } addr2[0] = (CFG_FLASH_WORD_SIZE)0x00300030; /* sector erase */ } l_sect = sect; } } /* re-enable interrupts if necessary */ if (flag) enable_interrupts(); /* wait at least 80us - let's wait 1 ms */ udelay (1000); /* * We wait for the last triggered sector */ if (l_sect < 0) goto DONE; start = get_timer (0); last = start; addr = (CFG_FLASH_WORD_SIZE *)(info->start[l_sect]); while ((addr[0] & (CFG_FLASH_WORD_SIZE)0x00800080) != (CFG_FLASH_WORD_SIZE)0x00800080) { if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT) { printf ("Timeout\n"); return 1; } /* show that we're waiting */ if ((now - last) > 50000000) { /* every second */ putc ('.'); last = now; } } DONE: /* reset to read mode */ addr = (CFG_FLASH_WORD_SIZE *)info->start[0]; addr[0] = (CFG_FLASH_WORD_SIZE)0x00F000F0; /* reset bank */ printf (" done\n"); return 0; } wirte_word 则想flash 里面写入unsigned long 类型的data,因为flash 一次只能写入16bits,所以这里分两次写入。 /*----------------------------------------------------------------------- * Write a word to Flash, returns: * 0 - OK * 1 - write timeout * 2 - Flash not erased */ static int write_word (flash_info_t *info, ulong dest, ulong data) { volatile CFG_FLASH_WORD_SIZE *addr2 = (CFG_FLASH_WORD_SIZE *)(info->start[0]); volatile CFG_FLASH_WORD_SIZE *dest2 = (CFG_FLASH_WORD_SIZE *)dest; volatile CFG_FLASH_WORD_SIZE *data2 = (CFG_FLASH_WORD_SIZE *)&data; ulong start; int flag; int i; /* Check if Flash is (sufficiently) erased */ if ((*((volatile ulong *)dest) & data) != data) { return (2); } /* Disable interrupts which might cause a timeout here */ flag = disable_interrupts(); for (i=0; i<4/sizeof(CFG_FLASH_WORD_SIZE); i++) { addr2[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00AA00AA; addr2[CFG_FLASH_ADDR1] = (CFG_FLASH_WORD_SIZE)0x00550055; addr2[CFG_FLASH_ADDR0] = (CFG_FLASH_WORD_SIZE)0x00A000A0; dest2[i] = data2[i]; /* re-enable interrupts if necessary */ if (flag) enable_interrupts(); /* data polling for D7 */ start = get_timer (0); while ((dest2[i] & (CFG_FLASH_WORD_SIZE)0x00800080) != (data2[i] & (CFG_FLASH_WORD_SIZE)0x00800080)) { if (get_timer(start) > CFG_FLASH_WRITE_TOUT) { return (1); } } } return (0); }