BootLoader指系統啓動後,在操作系統內核運行之前運行的一段小程序。通過BootLoader,我們可以初始化硬件設備、建立內存空間的映射圖,從而將系統的軟硬件環境帶到一個合適的狀態,以便爲最終調用操作系統內核準備好正確的環境。通常,BootLoader是嚴重地依賴於硬件而實現的,特別是在嵌入式世界。因此,在嵌入式世界裏建立一個通用的 BootLoader 幾乎是不可能的。儘管如此,我們仍然可以對BootLoader歸納出一些通用的概念來,以指導用戶特定的BootLoader設計與實現。
BootLoader 的實現依賴於CPU的體系結構,因此大多數 BootLoader 都分爲stage1 和stage2 兩大部分。依賴於CPU體系結構的代碼,比如設備初始化代碼等,通常都放在 stage1中,而且通常都用匯編語言來實現,以達到短小精悍的目的。而stage2 則通常用C 語言來實現,這樣可以實現更復雜的功能,而且代碼會具有更好的可讀性和可移植性。
BootLoader 的 stage1 通常包括以下步驟:
Ø 硬件設備初始化;
Ø 爲加載Boot Loader的stage2準備 RAM 空間;
Ø 拷貝Boot Loader的stage2 到RAM空間中;
Ø 設置好堆棧;
Ø 跳轉到 stage2 的 C 入口點。
Boot Loader的stage2通常包括以下步驟:
Ø 初始化本階段要使用到的硬件設備;
Ø 檢測系統內存映射(memory map);
Ø 將kernel 映像和根文件系統映像從flash上讀到 RAM 空間中;
Ø 爲內核設置啓動參數;
Ø 調用內核。
本系統中的BootLoader參照韓國mizi公司的vivi進行修改。 1.開發環境
我們購買了武漢創維特信息技術有限公司開發的具有自主知識產權的應用於嵌入式軟件開發的集成軟、硬件開發平臺ADT(ARM Development Tools)它爲基於ARM 核的嵌入式應用提供了一整套完備的開發方案,包括程序編輯、工程管理和設置、程序編譯、程序調試等。
ADT嵌入式開發環境由ADT Emulator for ARM 和ADT IDE for ARM組成。ADT Emulator for ARM 通過JTAG 實現主機和目標機之間的調試支持功能。它無需目標存儲器,不佔用目標系統的任何端口資源。目標程序直接在目標板上運行,通過ARM 芯片的JTAG 邊界掃描口進行調試,屬於完全非插入式調試,其仿真效果接近真實系統。
ADT IDE for ARM 爲用戶提供高效明晰的圖形化嵌入式應用軟件開發環境,包括一整套完備的面向嵌入式系統的開發和調試工具:源碼編輯器、工程管理器、工程編譯器(編譯器、彙編器和連接器)、集成調試環境、ADT Emulator for ARM 調試接口等。其界面同Microsoft Visual Studio 環境相似,用戶可以在ADT IDE for ARM 集成開發環境中創建工程、打開工程,建立、打開和編輯文件,編譯、連接、設置、運行、調試嵌入式應用程序。
ADT嵌入式軟件開發環境採用主機-目標機交叉開發模型。ADT IDE for ARM 運行於主機端,而ADT Emulator for ARM 實現ADT IDE for ARM 與目標機之間的連接。開發時,首先由ADT IDE for ARM 編譯連接生成目標代碼,然後建立與ADT Emulator for ARM 之間的調試通道,調試通道建立成功後,就可以在ADT IDE for ARM 中通過ADT Emulator for ARM 控制目標板實現目標程序的調試,包括將目標代碼下載到目標機中,控制程序運行,調試信息觀察等等。
2.ARM彙編
ARM本身屬於RISC指令系統,指令條數就很少,而其編程又以C等高級語言爲主,我們僅需要在Bootloader的第一階段用到少量彙編指令:
(1)+-運算
ADD r0, r1, r2
―― r0 := r1 + r2
SUB r0, r1, r2
―― r0 := r1 - r2
其中的第二個操作數可以是一個立即數:
ADD r3, r3, #1
―― r3 := r3 + 1
第二個操作數還可以是位移操作後的結果:
ADD r3, r2, r1, LSL #3
―― r3 := r2 + 8.r1
(2)位運算
AND r0, r1, r2
―― r0 := r1 and r2
ORR r0, r1, r2
―― r0 := r1 or r2
EOR r0, r1, r2
―― r0 := r1 xor r2
BIC r0, r1, r2
―― r0 := r1 and not r2
(3)寄存器搬移
MOV r0, r2
―― r0 := r2
MVN r0, r2
―― r0 := not r2
(4)比較
CMP r1, r2
―― set cc on r1 - r2
CMN r1, r2
―― set cc on r1 + r2
TST r1, r2
―― set cc on r1 and r2
TEQ r1, r2
―― set cc on r1 or r2
這些指令影響CPSR寄存器中的 (N, Z, C, V) 位
(5)內存操作
LDR r0, [r1]
―― r0 := mem [r1]
STR r0, [r1]
―― mem [r1] := r0
LDR r0, [r1, #4]
―― r0 := mem [r1+4]
LDR r0, [r1, #4] !
―― r0 := mem [r1+4] r1 := r1 + 4
LDR r0, [r1], #4
―― r0 := mem [r1] r1 := r1 +4
LDRB r0 , [r1]
―― r0 := mem8 [r1]
LDMIA r1, {r0, r2, r5}
―― r0 := mem [r1] r2 := mem [r1+4] r5 := mem [r1+8] {..} 可以包括r0~r15中的所有寄存器,若包括r15 (PC)將導致程序的跳轉。
(6)控制流
例1:
MOV r0, #0 ; initialize counter
LOOP:
ADD r0, r0, #1 ; increment counter
CMP r0, #10 ; compare with limit
BNE LOOP ; repeat if not equal
例2:
CMP r0, #5
ADDNE r1, r1, r0
SUBNE r1, r1, r2
――
if (r0 != 5) {
r1 := r1 + r0 - r2
}
3.BootLoader第一階段
3.1硬件設備初始化
基本的硬件初始化工作包括:
Ø 屏蔽所有的中斷;
Ø 設置CPU的速度和時鐘頻率;
Ø RAM初始化;
Ø 初始化LED
ARM的中斷向量表設置在0地址開始的8個字空間中,如下表:
每當其中的某個異常發生後即將PC值置到相應的中斷向量處,每個中斷向量處放置一個跳轉指令到相應的中斷服務程序去進行處理,中斷向量表的程序如下:
@ 0x00: Reset
b Reset
@ 0x04: Undefined instruction exception
UndefEntryPoint:
b HandleUndef
@ 0x08: Software interrupt exception
SWIEntryPoint:
b HandleSWI
@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)
PrefetchAbortEnteryPoint:
b HandlePrefetchAbort
@ 0x10: Data Access Memory Abort
DataAbortEntryPoint:
b HandleDataAbort
@ 0x14: Not used
NotUsedEntryPoint:
b HandleNotUsed
@ 0x18: IRQ(Interrupt Request) exception
IRQEntryPoint:
b HandleIRQ
@ 0x1c: FIQ(Fast Interrupt Request) exception
FIQEntryPoint:
b HandleFIQ
Reset:
@ disable watch dog timer
mov r1, #0x53000000
mov r2, #0x0
str r2, [r1]
@ disable all interrupts
mov r1, #INT_CTL_BASE
mov r2, #0xffffffff
str r2, [r1, #oINTMSK]
ldr r2, =0x7ff
str r2, [r1, #oINTSUBMSK]
設置系統時鐘:
@init clk
@ 1:2:4
mov r1, #CLK_CTL_BASE
mov r2, #0x3
str r2, [r1, #oCLKDIVN]
mrc p15, 0, r1, c1, c0, 0 @ read ctrl register
orr r1, r1, #0xc0000000 @ Asynchronous
mcr p15, 0, r1, c1, c0, 0 @ write ctrl register
@ now, CPU clock is 200 Mhz
mov r1, #CLK_CTL_BASE
ldr r2, mpll_200mhz
str r2, [r1, #oMPLLCON]
點亮所有的用戶LED:
@ All LED on
mov r1, #GPIO_CTL_BASE
add r1, r1, #oGPIO_F
ldr r2,=0x55aa
str r2, [r1, #oGPIO_CON]
mov r2, #0xff
str r2, [r1, #oGPIO_UP]
mov r2, #0x00
str r2, [r1, #oGPIO_DAT]
設置(初始化)內存映射:
ENTRY(memsetup)
@ initialise the static memory
@ set memory control registers
mov r1, #MEM_CTL_BASE
adrl r2, mem_cfg_val
add r3, r1, #52
1: ldr r4, [r2], #4
str r4, [r1], #4
cmp r1, r3
bne 1b
mov pc, lr @ set GPIO for UART
mov r1, #GPIO_CTL_BASE
add r1, r1, #oGPIO_H
ldr r2, gpio_con_uart
str r2, [r1, #oGPIO_CON]
ldr r2, gpio_up_uart
str r2, [r1, #oGPIO_UP]
bl InitUART
@ Initialize UART
@
@ r0 = number of UART port
InitUART:
ldr r1, SerBase
mov r2, #0x0
str r2, [r1, #oUFCON]
str r2, [r1, #oUMCON]
mov r2, #0x3
str r2, [r1, #oULCON]
ldr r2, =0x245
str r2, [r1, #oUCON]
#define UART_BRD ((50000000 / (UART_BAUD_RATE * 16)) - 1)
mov r2, #UART_BRD
str r2, [r1, #oUBRDIV]
mov r3, #100
mov r2, #0x0
1: sub r3, r3, #0x1
tst r2, r3
bne 1b
#if 0
mov r2, #'U'
str r2, [r1, #oUTXHL]
1: ldr r3, [r1, #oUTRSTAT]
and r3, r3, #UTRSTAT_TX_EMPTY
tst r3, #UTRSTAT_TX_EMPTY
bne 1b
mov r2, #'0'
str r2, [r1, #oUTXHL]
1: ldr r3, [r1, #oUTRSTAT]
and r3, r3, #UTRSTAT_TX_EMPTY
tst r3, #UTRSTAT_TX_EMPTY
bne 1b
#endif
mov pc, lr@ PrintChar : prints the character in R0
@ r0 contains the character
@ r1 contains base of serial port
@ writes ro with XXX, modifies r0,r1,r2
@ TODO : write ro with XXX reg to error handling
PrintChar:
TXBusy:
ldr r2, [r1, #oUTRSTAT]
and r2, r2, #UTRSTAT_TX_EMPTY
tst r2, #UTRSTAT_TX_EMPTY
beq TXBusy
str r0, [r1, #oUTXHL]
mov pc, lr
@ PrintWord : prints the 4 characters in R0
@ r0 contains the binary word
@ r1 contains the base of the serial port
@ writes ro with XXX, modifies r0,r1,r2
@ TODO : write ro with XXX reg to error handling
PrintWord:
mov r3, r0
mov r4, lr
bl PrintChar
mov r0, r3, LSR #8 /* shift word right 8 bits */
bl PrintChar
mov r0, r3, LSR #16 /* shift word right 16 bits */
bl PrintChar
mov r0, r3, LSR #24 /* shift word right 24 bits */
bl PrintChar
mov r0, #'r'
bl PrintChar
mov r0, #'n'
bl PrintChar
mov pc, r4
@ PrintHexWord : prints the 4 bytes in R0 as 8 hex ascii characters
@ followed by a newline
@ r0 contains the binary word
@ r1 contains the base of the serial port
@ writes ro with XXX, modifies r0,r1,r2
@ TODO : write ro with XXX reg to error handling
PrintHexWord:
mov r4, lr
mov r3, r0
mov r0, r3, LSR #28
bl PrintHexNibble
mov r0, r3, LSR #24
bl PrintHexNibble
mov r0, r3, LSR #20
bl PrintHexNibble
mov r0, r3, LSR #16
bl PrintHexNibble
mov r0, r3, LSR #12
bl PrintHexNibble
mov r0, r3, LSR #8
bl PrintHexNibble
mov r0, r3, LSR #4
bl PrintHexNibble
mov r0, r3
bl PrintHexNibble
mov r0, #'r'
bl PrintChar
mov r0, #'n'
bl PrintChar
mov pc, r4 3.2Bootloader拷貝
配置爲從NAND FLASH啓動,需要將NAND FLASH中的vivi代碼copy到RAM中:
#ifdef CONFIG_S3C2410_NAND_BOOT
bl copy_myself
@ jump to ram
ldr r1, =on_the_ram
add pc, r1, #0
nop
nop
1: b 1b @ infinite loop
#ifdef CONFIG_S3C2410_NAND_BOOT
@
@ copy_myself: copy vivi to ram
@
copy_myself:
mov r10, lr
@ reset NAND
mov r1, #NAND_CTL_BASE
ldr r2, =0xf830 @ initial value
str r2, [r1, #oNFCONF]
ldr r2, [r1, #oNFCONF]
bic r2, r2, #0x800 @ enable chip
str r2, [r1, #oNFCONF]
mov r2, #0xff @ RESET command
strb r2, [r1, #oNFCMD]
mov r3, #0 @ wait
1: add r3, r3, #0x1
cmp r3, #0xa
blt 1b
2: ldr r2, [r1, #oNFSTAT] @ wait ready
tst r2, #0x1
beq 2b
ldr r2, [r1, #oNFCONF]
orr r2, r2, #0x800 @ disable chip
str r2, [r1, #oNFCONF]
@ get read to call C functions (for nand_read())
ldr sp, DW_STACK_START @ setup stack pointer
mov fp, #0 @ no previous frame, so fp=0
@ copy vivi to RAM
ldr r0, =VIVI_RAM_BASE
mov r1, #0x0
mov r2, #0x20000
bl nand_read_ll
tst r0, #0x0
beq ok_nand_read
#ifdef CONFIG_DEBUG_LL
bad_nand_read:
ldr r0, STR_FAIL
ldr r1, SerBase
bl PrintWord
1: b 1b @ infinite loop
#endif
ok_nand_read:
#ifdef CONFIG_DEBUG_LL
ldr r0, STR_OK
ldr r1, SerBase
bl PrintWord
#endif
@ verify
mov r0, #0
ldr r1, =0x33f00000
mov r2, #0x400 @ 4 bytes * 1024 = 4K-bytes
go_next:
ldr r3, [r0], #4
ldr r4, [r1], #4
teq r3, r4
bne notmatch
subs r2, r2, #4
beq done_nand_read
bne go_next
notmatch:
#ifdef CONFIG_DEBUG_LL
sub r0, r0, #4
ldr r1, SerBase
bl PrintHexWord
ldr r0, STR_FAIL
ldr r1, SerBase
bl PrintWord
#endif
1: b 1b
done_nand_read:
#ifdef CONFIG_DEBUG_LL
ldr r0, STR_OK
ldr r1, SerBase
bl PrintWord
#endif
mov pc, r10
@ clear memory
@ r0: start address
@ r1: length
mem_clear:
mov r2, #0
mov r3, r2
mov r4, r2
mov r5, r2
mov r6, r2
mov r7, r2
mov r8, r2
mov r9, r2
clear_loop:
stmia r0!, {r2-r9}
subs r1, r1, #(8 * 4)
bne clear_loop
mov pc, lr
#endif @ CONFIG_S3C2410_NAND_BOOT3.3進入C代碼
首先要設置堆棧指針sp,堆棧指針的設置是爲了執行C語言代碼作好準備。設置好堆棧後,調用C語言的main函數:
@ get read to call C functions
ldr sp, DW_STACK_START @ setup stack pointer
mov fp, #0 @ no previous frame, so fp=0
mov a2, #0 @ set argv to NULL
bl main @ call main
mov pc, #FLASH_BASE @ otherwise, reboot
4. BootLoader第二階段
vivi Bootloader的第二階段又分成了八個小階段,在main函數中分別調用這幾個小階段的相關函數:
int main(int argc, char *argv[])
{
int ret;
/*
* Step 1:
*/
putstr("rn");
putstr(vivi_banner);
reset_handler();
/*
* Step 2:
*/
ret = board_init();
if (ret) {
putstr("Failed a board_init() procedurern");
error();
}
/*
* Step 3:
*/
mem_map_init();
mmu_init();
putstr("Succeed memory mapping.rn");
/*
* Now, vivi is running on the ram. MMU is enabled.
*/
/*
* Step 4:
*/
/* initialize the heap area*/
ret = heap_init();
if (ret) {
putstr("Failed initailizing heap regionrn");
error();
}
/* Step 5:
*/
ret = mtd_dev_init();
/* Step 6:
*/
init_priv_data();
/* Step 7:
*/
misc();
init_builtin_cmds();
/* Step 8:
*/
boot_or_vivi();
return 0;
} STEP1的putstr(vivi_banner)語句在串口輸出一段字符說明vivi的版本、作者等信息,vivi_banner定義爲:
const char *vivi_banner =
"VIVI version " VIVI_RELEASE " (" VIVI_COMPILE_BY "@"
VIVI_COMPILE_HOST ") (" VIVI_COMPILER ") " UTS_VERSION "rn";
reset_handler進行相應的復位處理:
void
reset_handler(void)
{
int pressed;
pressed = is_pressed_pw_btn();
if (pressed == PWBT_PRESS_LEVEL) {
DPRINTK("HARD RESETrn");
hard_reset_handle();
} else {
DPRINTK("SOFT RESETrn");
soft_reset_handle();
}
}
hard_reset_handle會clear內存,而軟件復位處理則什麼都不做:
static void
hard_reset_handle(void)
{
clear_mem((unsigned long)USER_RAM_BASE, (unsigned long)USER_RAM_SIZE);
}
STEP2進行板初始化,設置時間和可編程I/O口:
int board_init(void)
{
init_time();
set_gpios();
return 0;
}
STEP3進行內存映射及MMU初始化:
void mem_map_init(void)
{
#ifdef CONFIG_S3C2410_NAND_BOOT
mem_map_nand_boot();
#else
mem_map_nor();
#endif
cache_clean_invalidate();
tlb_invalidate();
}要調用通用的arm920 MMU初始化函數:
static inline void arm920_setup(void)
{
unsigned long ttb = MMU_TABLE_BASE;
__asm__(
/* Invalidate caches */
"mov r0, #0n"
"mcr p15, 0, r0, c7, c7, 0n" /* invalidate I,D caches on v4 */
"mcr p15, 0, r0, c7, c10, 4n" /* drain write buffer on v4 */
"mcr p15, 0, r0, c8, c7, 0n" /* invalidate I,D TLBs on v4 */
/* Load page table pointer */
"mov r4, %0n"
"mcr p15, 0, r4, c2, c0, 0n" /* load page table pointer */
/* Write domain id (cp15_r3) */
"mvn r0, #0n" /* Domains 0, 1 = client */
"mcr p15, 0, r0, c3, c0, 0n" /* load domain access register */
/* Set control register v4 */
"mrc p15, 0, r0, c1, c0, 0n" /* get control register v4 */
/* Clear out 'unwanted' bits (then put them in if we need them) */
/* .RVI ..RS B... .CAM */
"bic r0, r0, #0x3000n" /* ..11 .... .... .... */
"bic r0, r0, #0x0300n" /* .... ..11 .... .... */
"bic r0, r0, #0x0087n" /* .... .... 1... .111 */
/* Turn on what we want */
/* Fault checking enabled */
"orr r0, r0, #0x0002n" /* .... .... .... ..1. */
#ifdef CONFIG_CPU_D_CACHE_ON
"orr r0, r0, #0x0004n" /* .... .... .... .1.. */
#endif
#ifdef CONFIG_CPU_I_CACHE_ON
"orr r0, r0, #0x1000n" /* ...1 .... .... .... */
#endif
/* MMU enabled */
"orr r0, r0, #0x0001n" /* .... .... .... ...1 */
"mcr p15, 0, r0, c1, c0, 0n" /* write control register */
: /* no outputs */
: "r" (ttb) );
}STEP4設置堆棧;STEP5進行mtd設備的初始化,記錄MTD分區信息;STEP6設置私有數據;STEP7初始化內建命令。
STEP8啓動一個SHELL,等待用戶輸出命令並進行相應處理。在SHELL退出的情況下,啓動操作系統:
#define DEFAULT_BOOT_DELAY 0x30000000
void boot_or_vivi(void)
{
char c;
int ret;
ulong boot_delay;
boot_delay = get_param_value("boot_delay", &ret);
if (ret) boot_delay = DEFAULT_BOOT_DELAY;
/* If a value of boot_delay is zero,
* unconditionally call vivi shell */
if (boot_delay == 0) vivi_shell();
/*
* wait for a keystroke (or a button press if you want.)
*/
printk("Press Return to start the LINUX now, any other key for vivin");
c = awaitkey(boot_delay, NULL);
if (((c != 'r') && (c != 'n') && (c != ''))) {
printk("type "help" for help.n");
vivi_shell();
}
run_autoboot();
return;
}
SHELL中讀取用戶從串口輸出的命令字符串,執行該命令:
void
vivi_shell(void)
{
#ifdef CONFIG_SERIAL_TERM
serial_term();
#else
#error there is no terminal.
#endif
}
void serial_term(void)
{
char cmd_buf[MAX_CMDBUF_SIZE];
for (;;) {
printk("%s> ", prompt);
getcmd(cmd_buf, MAX_CMDBUF_SIZE);
/* execute a user command */
if (cmd_buf[0])
exec_string(cmd_buf);
}
}5.電路板調試
在電路板的調試過程中,我們首先要在ADT新建的工程中添加第一階段的彙編代碼head.S文件,修改Link腳本,將代碼和數據映射到S3C2410A自帶的0x40000000開始的4KB內存空間內:
SECTIONS
{
. = 0x40000000;
.text : { *(.text) }
Image_RO_Limit = .;
Image_RW_Base = .;
.data : { *(.data) }
.rodata : { *(.rodata) }
Image_ZI_Base = .;
.bss : { *(.bss) }
Image_ZI_Limit = .;
__bss_start__ = .;
__bss_end__ = .;
__EH_FRAME_BEGIN__ = .;
__EH_FRAME_END__ = .;
PROVIDE (__stack = .);
end = .;
_end = .;
.debug_info 0 : { *(.debug_info) }
.debug_line 0 : { *(.debug_line) }
.debug_abbrev 0 : { *(.debug_abbrev)}
.debug_frame 0 : { *(.debug_frame) }
}
藉助萬用表、示波器等儀器儀表,調通SDRAM,並將vivi中自帶的串口、NAND FLASH驅動添加到工程中,調試通過板上的串口和FLASH。如果板電路的原理與三星公司DEMO板有差距,則vivi中硬件的操作要進行相應的修改。全部調試通過後,修改vivi源代碼,重新編譯vivi,將其燒錄入NAND FLASH就可以在復位後啓動這個Bootloader了。
調試板上的新增硬件時,宜在ADT中添加相應的代碼,在不加載操作系統的情況下,單純地操作這些硬件。如果電路板設計有誤,要進行飛線和割線等處理。
6.小結
本章講解了ARM彙編、Bootloader的功能,Bootloader的調試環境及ARM電路板的調試方法。