本文共 9073 字,大约阅读时间需要 30 分钟。
文章来源:
u-boot是广泛应用于嵌入式系统的bootloader,该软件主页是
解压缩开代码包后,以下几个目录中分类存放了主要的源码
——用于放置板支持代码,相当于bootloader级的BSP。与特定板相关的代码包括频率合成、GPIO、板参数、调试串口、能源管理、按键处理等。如
——用于存放体系结构无关的公共代码,主要是各种命令实现代码、环境变量实现代码等,如
——用于存放处理器和SOC特定代码,分别存在于以cpu名称命名的子目录中。如或者是本例中的的特有代码
——存放硬件驱动的代码,目前还没有做到完全清晰的分离架构相关/无关的代码。如和流行的
——存放文件系统代码,包括、以及等
——存放u-boot头文件,相当一部分和linux内核中的一致
——存放于xxx架构相关的代码,如浮点routine和实际的引导代码。如
——存放体系结构无关的网络协议,、等
——在本机运行的u-boot工具,如用来创建uImage的
本文以流行的Samsung公司的S3C2410,openmoko平台和u-boot-1.3.2(2008.5 发布)为例,介绍如何在ZIX嵌入式开发环境下探索u-boot启动过程。
虽然u-boot已经广泛应用,由于其相对的复杂性使用户在了解其内部机理和进行u-boot的移植工作时还是会碰到困难。u-boot已有一些分析文档,但多数和真正的代码不能同步或者版本老旧,难以将概念和现实的代码匹配——即硬件板上跑的代码在文档资料中却看不到,更无法紧密的跟踪。本文涉及的代码基于在s3c2410硬件运行的成熟u-boot-1.3.2代码,版本较新,提供的特性非常丰富,而且在可以和。此u-boot代表了业界的较高水平,可以直接构建新版的嵌入式产品设计,有较高的应用价值。
u-boot总的启动流程如下
->reset-> 设置CPU模式-> 关闭看门狗/中断-> 设置处理器时钟/片上总线-> 初始化调试串口-> MMU/外部总线/SDRAM等初始化-> rom代码/数据搬移到ram-> 初始化函数调用栈-> 初始化外围设备/参数-> 启动完毕,进入main_loop循环嵌入式系统离不开bootloader初始化硬件以及引导操作系统。
现在,专用的嵌入式板子运行嵌入式Linux系统已经变得非常流行,u-boot是一种非常适合此类系统的bootloader。u-boot主要提供以下功能:
利用ZIX开发环境,能够通过比较直观的方式观察u-boot内部,而且可以将代码调试和分析同时进行,是一种了解、移植u-boot的强大工具。
使用arm工具链编译u-boot源代码,得到可以烧录的u-boot.bin文件。
在ZIX开发环境里,可以将u-boot.bin载入s3c2410板运行,并利用gdb调试。gdb能通过JTAG接口访问硬件,也可以通过TCP/IP访问虚拟硬件。 建立好调试连接,即可通过gdb操纵u-boot启动过程,下面可以跟随代码的执行顺序,了解从上点开始,究竟哪些操作被执行。
s3c2410复位之后,pc指针会指向0x0地址。在u-boot代码中,该0x0地址是一个向量表,
第一条指令跳转branch到复位代码start_code。 位于cpu/arm920t/start.S汇编语言文件:.globl _start _start: b start_code ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq
复位指令跳转之后来到,开始执行arm920t处理器的基本初始化。
首先修改当前程序状态寄存器CPSR,使处理器进入Supervisor|32 bit ARM模式,并关闭ARM9TDMI中断和快速中断,这是通过设置CPSR相应掩码实现的:start_code: /* * set the cpu to SVC32 mode */ mrs r0,cpsr bic r0,r0,#0x1f orr r0,r0,#0xd3 msr cpsr,r0
紧接着,将S3C2410特有的WTCON寄存器清零,此举仅为关闭看门狗,代码位置是:
ldr r0, =pWTCON mov r1, #0x0 str r1, [r0]
然后,将S3C2410中断控制器INTMSK寄存器置为全1,
INTSUBMSK置为0x7ff,禁止全部中断源。358页起对此有详细描述:mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0] # if defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442) || / defined(CONFIG_S3C2443) ldr r1, =INTSUBMSK_val ldr r0, =INTSUBMSK str r1, [r0] # endif
接下来,访问arm920t控制寄存器CP15,并置位最高两位[31,30]。
此两位置为0b11后,处理器时钟被设置为异步模式,允许处理器异步访问总线:mrc p15, 0, r1, c1, c0, 0 orr r1, r1, #0xc0000000 mcr p15, 0, r1, c1, c0, 0
至此arm920t相关的配置完成,后面开始设定S3C2410时钟合成参数。
通过设置UPLL,MPLL和CLKDIVN三个寄存器(在237页起讲述),得到需要的处理器工作频率,分别:ldr r0, =UPLLCON ldr r1, =UPLLCON_val str r1, [r0]
:
ldr r1, =MPLLCON_val str r1, [r0, #-4] /* MPLLCON */ /* FCLK:HCLK:PCLK = 1:2:4 */ ldr r0, =CLKDIVN mov r1, #CLKDIVN_val str r1, [r0]
S3C2410的UART0得到初始化,以便于尽早通过UART0打印信息。
此段代码从开始,其中涉及到的寄存器读者可参考293页起:/* enable uart */ ldr r0, =0x4c00000c /* clkcon */ ldr r1, =0x7fff0 /* all clocks on */ str r1, [r0] /* gpio UART0 init */ ldr r0, =0x56000070 mov r1, #0xaa str r1, [r0] /* init uart */ ldr r0, =0x50000000 mov r1, #0x03 str r1, [r0] ldr r1, =0x245 str r1, [r0, #0x04] mov r1, #0x01 str r1, [r0, #0x08] mov r1, #0x00 str r1, [r0, #0x0c] mov r1, #0x1a str r1, [r0, #0x28]
完成UART0设置之后,根据不同的编译时选项和运行时参数,代码会在进入相应的分支,分别是
下面以最复杂的nand启动情况为例分析。首先会跳转到执行cpu_init_crit,
通过操作CP15完成flush处理器arm920t的cache和tlb,并关闭mmu和i-cache:cpu_init_crit: /* * flush v4 I/D caches */ mov r0, #0 mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) orr r0, r0, #0x00000002 @ set bit 2 (A) Align orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache mcr p15, 0, r0, c1, c0, 0
然后跳转到board/neo1973/common/lowlevel_init.S文件的执行,
进行总线数据宽度、时序、SDRAM控制、GPIO等配置,配置完毕后会返回start.S继续执行。因为该代码是与板相关,故放在board目录里面。由于代码较多,只粘贴开始部分:/* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ adr r0, SMRDATA ldr r1, =BWSCON /* Bus Width Status Controller */ add r2, r0, #13*4
完成板级设置后,在cpu/arm920t/start.S的判断代码自身的执行位置。如果从stepping stone内执行,
并且u-boot配置为nand boot模式,则跳转到nand_load拷贝代码:ldr r1, =BWSCON /* Z = CPU booted from NAND */ ldr r1, [r1] tst r1, #6 /* BWSCON[2:1] = OM[1:0] */ teqeq r0, #0 /* Z &= running at address 0 */ beq nand_load
在是nand_load代码,首先会跳转到执行may_resume
以检测系统是从待机模式唤醒还是上电启动。如果唤醒,则会根据之前保存的现场进行相应处理,本文不做更多叙述;如果是启动,则会返回nand_load继续执行。在nand_load里初始化s3c2410的nand controller,涉及存储器映射和寄存器NFCONF等,参见215页起。同样,仅粘贴开始部分的代码:mov r1, #S3C2410_NAND_BASE ldr r2, =0xf842 @ initial value enable tacls=3,rph0=6,rph1=0 str r2, [r1, #oNFCONF] ldr r2, [r1, #oNFCONF] bic r2, r2, #0x800 @ enable chip
在继续根据配置设定栈指针,为后面调用C函数执行拷贝作准备:
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 */
然后在,将SDRAM中的目标地址存入r0,0x0地址存入r1,u-boot长度存入r2,
跳入cpu/arm920t/s3c24x0/nand_read.c文件执行nand_read_ll函数,该函数接受前面3个寄存器中的值作为参数,并将返回值放回r0:ldr r0, _TEXT_BASE mov r1, #0x0 mov r2, #CFG_UBOOT_SIZE bl nand_read_ll
在nand_read_ll函数中实现了nand flash访问代码,并且支持自动跳过坏块的特性,函数循环执行nand页面读取并存入SDRAM,直到u-boot全部拷贝完,并返回0,该C代码留给读者自己阅读。nand_read_ll返回0后,会跳转到ok_nand_read,并对拷贝的头4K字节进行校验:
@ verify mov r0, #0 @ldr r1, =0x33f00000 ldr r1, _TEXT_BASE 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
校验通过后代码,在地址为_booted_from_nand的SDRAM位置保存1,以便告诉上层软件本次启动是从nand引导:
done_nand_read: ldr r0, _booted_from_nand mov r1, #1 strb r1, [r0]
然后在,将中断向量表拷贝到0x0:
mov r0, #0 ldr r1, _TEXT_BASE mov r2, #0x40 irqvec_cpy_next: ldr r3, [r1], #4 str r3, [r0], #4 subs r2, r2, #4 bne irqvec_cpy_next
在,设置栈指针:
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 */
在,清除bss段并跳转到真正的C函数start_armboot,进入更高级的硬件初始化代码,汇编初始化部分也全部完成使命:
ldr r0, _bss_start /* find start of bss segment */ ldr r1, _bss_end /* stop here */ mov r2, #0x00000000 /* clear */ clbss_l: str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp r0, r1 ble clbss_l ldr pc, _start_armboot
start_armboot函数位于lib_arm/board.c文件,首先初始化globel_data类型的变量gd。该变量是一个结构,其成员大多是板子的一些基本设置,如序列号、ip地址、mac地址等(欲知结构的原型可参考include/asm-arm/globel_data.h和include/asm-arm/u-boot.h):
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory"); memset ((void*)gd, 0, sizeof (gd_t)); gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); memset (gd->bd, 0, sizeof (bd_t));
然后在一个for循环中从init_sequence地址开始,逐个调用初始化C函数至NULL为止。这些routine函数按调用顺序分别是
利用gdb可以清晰的看到调用过程:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } }
接着是一些可选外设的初始化,如显示屏、nor、nand、dataflash、网卡等,此过程执行后全部初始化工作完成。下面仅粘贴nor代码:
#ifndef CFG_NO_FLASH /* configure available FLASH banks */ size = flash_init (); display_flash_config (size); #endif /* CFG_NO_FLASH */
之后在进入无限循环,调用common/main.c文件的main_loop()函数,u-boot完成启动过程。main_loop提供一个交互式命令行,可通过串口或usb控制台操作,也可以进一步引导操作系统:
for (;;) { main_loop (); }
转载地址:http://djsqi.baihongyu.com/