重生之名流巨星妈妈网:基于ARM的嵌入式Linux移植真实体验(2)――BootLoader

来源:百度文库 编辑:九乡新闻网 时间:2024/04/30 03:20:12
基于ARM的嵌入式Linux移植真实体验(2――BootLoader宋宝华 21cnbao@21cn.com 出处:dev.yesky.comBootLoader指系统启动后,在操作系统内核运行之前运行的一段小程序。通过BootLoader,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 BootLoader 几乎是不可能的。尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。BootLoader 的实现依赖于CPU的体系结构,因此大多数 BootLoader 都分为stage1 stage2 两大部分。依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在 stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而stage2 则通常用C 语言来实现,这样可以实现更复杂的功能,而且代码会具有更好的可读性和可移植性。BootLoader stage1 通常包括以下步骤:Ø         硬件设备初始化;Ø         为加载Boot Loaderstage2准备 RAM 空间;Ø         拷贝Boot Loaderstage2 RAM空间中;Ø         设置好堆栈;Ø         跳转到 stage2 C 入口点。Boot Loaderstage2通常包括以下步骤:             Ø         初始化本阶段要使用到的硬件设备;Ø         检测系统内存映射(memory map)Ø         kernel 映像和根文件系统映像从flash上读到 RAM 空间中;Ø         为内核设置启动参数;Ø         调用内核。本系统中的BootLoader参照韩国mizi公司的vivi进行修改。1.开发环境我们购买了武汉创维特信息技术有限公司开发的具有自主知识产权的应用于嵌入式软件开发的集成软、硬件开发平台ADTARM 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  +  r2SUB   r0,  r1,  r2   ―― r0  :=  r1  -  r2其中的第二个操作数可以是一个立即数:ADD r3, r3, #1 ―― r3 := r3 + 1第二个操作数还可以是位移操作后的结果:ADD r3, r2, r1, LSL #3 ―― r3 := r2 + 8.r12)位运算AND  r0, r1, r2   ―― r0 := r1 and r2ORR  r0, r1, r2―― r0 := r1 or    r2EOR  r0, r1, r2―― r0 := r1 xor  r2BIC   r0, r1, r2  ―― r0 := r1 and not r23)寄存器搬移MOV  r0,  r2 ―― r0  :=  r2MVN  r0,  r2           ―― r0  :=  not  r24)比较CMP r1, r2 ―― set cc on r1 - r2CMN r1, r2 ―― set cc on r1 + r2TST r1, r2 ―― set cc on r1 and r2TEQ 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] := r0LDR r0, [r1, #4] ――  r0 := mem [r1+4]LDR r0, [r1, #4] ! ――  r0 := mem [r1+4]     r1 := r1 + 4LDR r0, [r1], #4――  r0 := mem [r1]       r1 := r1 +4LDRB 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 counterLOOP:   ADD r0, r0, #1 ; increment counter   CMP r0, #10 ; compare with limit   BNE LOOP ; repeat if not equal2:CMP r0, #5 ADDNE r1, r1, r0 SUBNE r1, r1, r2 ――if (r0 != 5) {r1 := r1 + r0 - r2}3.BootLoader第一阶段3.1硬件设备初始化基本的硬件初始化工作包括:Ø         屏蔽所有的中断;Ø         设置CPU的速度和时钟频率;Ø         RAM初始化;Ø         初始化LEDARM的中断向量表设置在0地址开始的8个字空间中,如下表:每当其中的某个异常发生后即将PC值置到相应的中断向量处,每个中断向量处放置一个跳转指令到相应的中断服务程序去进行处理,中断向量表的程序如下:@ 0x00: Reset       b     Reset@ 0x04: Undefined instruction exceptionUndefEntryPoint:       b     HandleUndef@ 0x08: Software interrupt exceptionSWIEntryPoint:       b     HandleSWI@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)PrefetchAbortEnteryPoint:       b     HandlePrefetchAbort@ 0x10: Data Access Memory AbortDataAbortEntryPoint:       b     HandleDataAbort@ 0x14: Not usedNotUsedEntryPoint:       b     HandleNotUsed@ 0x18: IRQ(Interrupt Request) exceptionIRQEntryPoint:       b     HandleIRQ@ 0x1c: FIQ(Fast Interrupt Request) exceptionFIQEntryPoint:       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, #521:     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 portInitUART:       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, #0x01:     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还提供了几个汇编情况下通过串口打印字符的函数PrintCharPrintWordPrintHexWord@ 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 handlingPrintChar: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 handlingPrintWord:       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 handlingPrintHexWord:       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, r43.2Bootloader拷贝配置为从NAND FLASH启动,需要将NAND FLASH中的vivi代码copyRAM中:#ifdef CONFIG_S3C2410_NAND_BOOT       bl     copy_myself        @ jump to ram       ldr   r1, =on_the_ram       add  pc, r1, #0       nop       nop1:     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    1b2:     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_LLbad_nand_read:        ldr   r0, STR_FAIL       ldr   r1, SerBase       bl     PrintWord1:     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-bytesgo_next:       ldr   r3, [r0], #4       ldr   r4, [r1], #4       teq   r3, r4       bne  notmatch       subs r2, r2, #4       beq  done_nand_read            bne  go_nextnotmatch:#ifdef CONFIG_DEBUG_LL       sub  r0, r0, #4       ldr   r1, SerBase       bl     PrintHexWord       ldr   r0, STR_FAIL       ldr   r1, SerBase       bl     PrintWord#endif1:     b     1bdone_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: lengthmem_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 functionsldr   sp, DW_STACK_START      @ setup stack pointermov fp, #0                    @ no previous frame, so fp=0mov a2, #0                   @ set argv to NULL  bl     main                     @ call main  mov pc, #FLASH_BASE              @ otherwise, reboot4. 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;}STEP1putstr(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进行相应的复位处理:voidreset_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_handleclear内存,而软件复位处理则什么都不做:static voidhard_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();}S3C2410AMMU初始化只需要调用通用的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       0x30000000void 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 != '\0'))) {              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电路板的调试方法。

本文出自 “Inside SW,FW and HW R&D” 博客

基于ARM的嵌入式Linux移植真实体验(2)――BootLoader 基于ARM的嵌入式Linux移植真实体验(3)――操作系统 基于ARM的嵌入式Linux移植真实体验(5)――应用实例 基于ARM的嵌入式Linux移植真实体验(1)――基本概念 ARM的嵌入式Linux移植体验之操作系统 [Python]Python的ARM-Linux平台移植 十年经验教你如何学习嵌入式系统(基于ARM平台) - atank的专栏 - CSDN... 基于arm+uClinux的嵌入式系统的开发(个人经历 ) - 单片机 - 硬件设计 - ... 基于嵌入式操作系统VxWorks的多任务并发程序设计(2) ――任务控制 - 豆豆网 ARM Bootloader的实现 ---C和ASM混合编程 如何从零开始开发一款嵌入式产品(20年的嵌入式经验分享学习) - 嵌入式linux - 【嵌入式Linux】 [保留] 嵌入式Linux内核移植相关代码分析(zz) - ChinaUnix.net (转载)mplayer在arm 2410上的移植 基于S3C2440的Linux内核移植和yaffs2文件系统制作 -- fafen's B... 初学嵌入式开发ARM的学习方法 ARM嵌入式系统的ISP设计 基于嵌入式操作系统VxWorks的多任务并发程序设计―任务控制 学习嵌入式Linux的基础 arm-linux 启动代码分析——stage1 (2) 嵌入式Linux攻略ACE程序移植过程详细解析-Linux认证考试-考试大 ARM的固件和bootloader,STANDSTONE固件程序,OS初始化 移植bootloader到UP-ARM2410-S开发板之U-boot的编译 嵌入式Linux 无限光明的未来 嵌入式Linux操作系统的驱动程序开发要点