U-Boot在44B0X開發板上的移植以及代碼分析

以《U-Boot在44B0X開發板上的移植以及代碼分析》爲基礎,根據自己的開發板改寫。

張磊 2005-2-25

REVISION HISTORY:

開發平臺:Redhat 9.0
開發工具:arm-elf-gcc
www.taoydy.com


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 */

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<CFG_MAX_FLASH_BANKS; ++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);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章