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
B%&&&&&% 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
设置(初始化)UART:
@ 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
此外,vivi还供给了几个汇编情况下经过串口打印字符的函数PrintChar、PrintWord和PrintHexWord:
@ 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_BOOT
3.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(“\r\n”);
putstr(vivi_banner);
reset_handler();
/*
* Step 2:
*/
ret = board_init();
if (ret) {
putstr(“Failed a board_init() procedure\r\n”);
error();
}
/*
* Step 3:
*/
mem_map_init();
mmu_init();
putstr(“Succeed memory mapping.\r\n”);
/*
* 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 region\r\n”);
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 “\r\n”;
reset_handler进行相应的复位处理:
void
reset_handler(void)
{
int pressed;
pressed = is_pressed_pw_btn();
if (pressed == PWBT_PRESS_LEVEL) {
DPRINTK(“HARD RESET\r\n”);
hard_reset_handle();
} else {
DPRINTK(“SOFT RESET\r\n”);
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();
}
S3C2410A的MMU初始化只需要调用通用的arm920 MMU初始化函数:
static inline void arm920_setup(void)
{
unsigned long ttb = MMU_TABLE_BASE;
__asm__(
/* Invalidate caches */
“mov r0, #0\n”
“mcr p15, 0, r0, c7, c7, 0\n” /* invalidate I,D caches on v4 */
“mcr p15, 0, r0, c7, c10, 4\n” /* drain write buffer on v4 */
“mcr p15, 0, r0, c8, c7, 0\n” /* invalidate I,D TLBs on v4 */
/* Load page table pointer */
“mov r4, %0\n”
“mcr p15, 0, r4, c2, c0, 0\n” /* load page table pointer */
/* Write domain id (cp15_r3) */
“mvn r0, #0\n” /* Domains 0, 1 = client */
“mcr p15, 0, r0, c3, c0, 0\n” /* load domain access register */
/* Set control register v4 */
“mrc p15, 0, r0, c1, c0, 0\n” /* get control register v4 */
/* Clear out unwanted bits (then put them in if we need them) */
/* .RVI ..RS B… .CAM */
“bic r0, r0, #0x3000\n” /* ..11 …. …. …. */
“bic r0, r0, #0x0300\n” /* …. ..11 …. …. */
“bic r0, r0, #0x0087\n” /* …. …. 1… .111 */
/* Turn on what we want */
/* Fault checking enabled */
“orr r0, r0, #0x0002\n” /* …. …. …. ..1. */
#ifdef CONFIG_CPU_D_CACHE_ON
“orr r0, r0, #0x0004\n” /* …. …. …. .1.. */
#endif
#ifdef CONFIG_CPU_I_CACHE_ON
“orr r0, r0, #0x1000\n” /* …1 …. …. …. */
#endif
/* MMU enabled */
“orr r0, r0, #0x0001\n” /* …. …. …. …1 */
“mcr p15, 0, r0, c1, c0, 0\n” /* 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 vivi\n”);
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电路板的调试办法。