本文以盛行的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代码,版别较新,供给的特性十分丰富,而且在forum.linuxbj.com能够自在阅览和下载。此u-boot代表了业界的较高水平,能够直接构建新版的嵌入式产品设计,有较高的运用价值。
u-boot总的发动流程如下
->reset
-> 设置CPU形式
-> 封闭看门狗/中止
-> 设置处理器时钟/片上总线
-> 初始化调试串口
-> MMU/外部总线/SDRAM等初始化
-> rom代码/数据搬移到ram
-> 初始化函数调用栈
-> 初始化外围设备/参数
->发动结束,进入main_loop循环
嵌入式体系离不开bootloader初始化硬件以及引导操作体系。
现在,专用的嵌入式板子运转嵌入式Linux体系现已变得十分盛行,u-boot是一种十分合适此类体系的bootloader。
u-boot首要供给以下功用:
- 设置方针板硬件参数并初始化;
- 为操作体系传递必要信息;
- 履行交互式的底层操作;
- 智能化装载操作体系;
- 引导和运转的固件程序;
- 支撑大容量存储和USB接口
运用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_co
DE>.globl _start _start: DE> DE>b start_co de DE> DE> DE>ldr pc, _undefined_instruction DE> DE>ldr pc, _software_interrupt DE> DE>ldr pc, _prefetch_abort DE> DE>ldr pc, _da ta_abort DE> DE>ldr pc, _not_used DE> DE>ldr pc, _irq DE> DE>ldr pc, _fiq
复位指令跳转之后来到第154行,开端履行arm920t处理器的根本初始化。
首要修正当时程序状况寄存器CPSR,使处理器进入Supervisor|32 bit ARM形式,
并封闭ARM9TDMI中止和快速中止,这是经过设置CPSR相应掩码完结的:
DE>start_co de: DE> DE> DE> DE>/* DE> DE> DE> DE> * set the cpu to SVC32 mode DE> DE> DE> DE> */ DE> DE> DE> DE>mrs r0,cpsr DE> DE> DE> DE>bic r0,r0,#0x1f DE> DE> DE> DE>orr r0,r0,#0xd3 DE> DE> DE> DE>msr cpsr,r0 DE>
紧接着,将S3C2410特有的WTCON寄存器清零,此举仅为封闭看门狗,代码方位是234行:
DE> DE> DE>ldr r0, =pWTCON DE> DE> DE> DE>mov r1, #0x0 DE> DE> DE> DE>str r1, [r0] DE>
然后在241行,将S3C2410中止操控器INTMSK寄存器置为全1,
INTSUBMSK置为0x7ff,制止悉数中止源。S3C2410手册358页起对此有详细描绘:
DE> DE> DE>mov r1, #0xffffffff DE> DE> DE> DE>ldr r0, =INTMSK DE> DE> DE> DE>str r1, [r0] # if defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440) || defined(CONFIG_S3C2442) || \ DE> DE> DE> DE>defined(CONFIG_S3C2443) DE> DE> DE> DE>ldr r1, =INTSUBMSK_val DE> DE> DE> DE>ldr r0, =INTSUBMSK DE> DE> DE> DE>str r1, [r0] # endif DE>
接下来在259行,拜访arm920t操控寄存器CP15,并置位最高两位〔31,30〕。
此两方位为0b11后,处理器时钟被设置为异步形式,答应处理器异步拜访总线:
DE> DE> DE>mrc p15, 0, r1, c1, c0, 0 DE> DE> DE> DE>orr r1, r1, #0xc0000000 DE> DE> DE> DE>mcr p15, 0, r1, c1, c0, 0 DE>
至此arm920t相关的装备完结,后边开端设定S3C2410时钟组成参数。
经过设置UPLL,MPLL和CLKDIVN三个寄存器(在S3C2410手册237页起叙说),
得到需求的处理器作业频率,分别在308行:
DE> DE> DE>ldr r0, =UPLLCON DE> DE> DE> DE>ldr r1, =UPLLCON_val DE> DE> DE> DE>str r1, [r0] DE>
321行:
DE> DE> DE>ldr r1, =MPLLCON_val DE> DE> DE> DE>str r1, [r0, #-4] DE> DE> DE> DE>/* MPLLCON */ DE> DE> DE> DE> DE> DE>/* FCLK:HCLK:PCLK = 1:2:4 */ DE> DE> DE> DE>ldr r0, =CLKDIVN DE> DE> DE> DE>mov r1, #CLKDIVN_val DE> DE> DE> DE>str r1, [r0] DE>
S3C2410的UART0得到初始化,以便于尽早经过UART0打印信息。
此段代码从332行开端,其间触及到的寄存器读者可参阅S3C2410手册293页起:
DE> DE> DE>/* enable uart */ DE> DE> DE> DE>ldr r0, =0x4c00000c /* clkcon */ DE> DE> DE> DE>ldr r1, =0x7fff0 /* all clocks on */ DE> DE> DE> DE>str r1, [r0] DE> DE> DE> DE> DE> DE>/* gpio UART0 init */ DE> DE> DE> DE>ldr r0, =0x56000070 DE> DE> DE> DE>mov r1, #0xaa DE> DE> DE> DE>str r1, [r0] DE> DE> DE> DE> DE> DE>/* init uart */ DE> DE> DE> DE>ldr r0, =0x50000000 DE> DE> DE> DE>mov r1, #0x03 DE> DE> DE> DE>str r1, [r0] DE> DE> DE> DE>ldr r1, =0x245 DE> DE> DE> DE>str r1, [r0, #0x04] DE> DE> DE> DE>mov r1, #0x01 DE> DE> DE> DE>str r1, [r0, #0x08] DE> DE> DE> DE>mov r1, #0x00 DE> DE> DE> DE>str r1, [r0, #0x0c] DE> DE> DE> DE>mov r1, #0x1a DE> DE> DE> DE>str r1, [r0, #0x28] DE>
完结UART0设置之后,依据不同的编译时选项和运转时参数,代码会在360行进入相应的分支,分别是
- 从nand发动,代码履行lowlevel_init,首要是铲除cpu cache,以及封闭mmu和i-cache,
而且依据板极硬件装备初始化外部存储器总线和GPIO,最终把代码从nandflash中复制到ram中并持续履行。 - 从nor发动,与第1种状况比较,仅仅把代码复制部分简化,将DA
TA段从flash中复制到ram中,其他相同 - 从ram发动,因为u-boot现已处于装备好的ram中,
所以会越过一切cache,mmu,sdram,nand和nor相关代码,跳转到done_relocate履行
下面以最杂乱的nand发动状况为例剖析。首要会跳转到572行履行cpu_init_crit,
经过操作CP15完结flush处理器arm920t的cache和tlb,并封闭mmu和i-cache:
DE>cpu_init_crit: DE> DE> DE> DE>/* DE> DE> DE> DE> * flush v4 I/D caches DE> DE> DE> DE>*/ DE> DE> DE> DE>mov r0, #0 DE> DE> DE> DE>mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ DE> DE> DE> DE>mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ DE> DE> DE> DE> DE> DE>/* DE> DE> DE> DE> * disable MMU stuff and caches DE> DE> DE> DE>*/ DE> DE> DE> DE>mrc p15, 0, r0, c1, c0, 0 DE> DE> DE> DE>bic r0, r0, #0x00002300 DE> DE> DE> DE>@ clear bits 13, 9:8 (--V- --RS) DE> DE> DE> DE>bic r0, r0, #0x00000087 DE> DE> DE> DE>@ clear bits 7, 2:0 (B--- -CAM) DE> DE> DE> DE>orr r0, r0, #0x00000002 DE> DE> DE> DE>@ set bit 2 (A) Align DE> DE> DE> DE>orr r0, r0, #0x00001000 DE> DE> DE> DE>@ set bit 12 (I) I-Cache DE> DE> DE> DE>mcr p15, 0, r0, c1, c0, 0 DE>
然后跳转到board/neo1973/common/lowlevel_init.S文件的139行履行,
进行总线数据宽度、时序、SDRAM操控、GPIO等装备,装备结束后会回来start.S持续履行。
因为该代码是与板相关,故放在board目录里边。因为代码较多,只张贴开端部分:
DE> DE> DE>/* memory control configuration */ DE> DE> DE> DE>/* make r0 relative the current location so that it */ DE> DE> DE> DE>/* reads SMRDATA out of FLASH rather than memory ! */ DE> DE> DE> DE>adr r0, SMRDATA DE> DE> DE> DE>ldr r1, =BWSCON /* Bus Width Status Controller */ DE> DE> DE> DE>add r2, r0, #13*4 DE>
完结板级设置后,在cpu/arm920t/start.S的373行判别代码本身的履行方位。假如从stepping stone内履行,
而且u-boot装备为nandboot形式,则跳转到nand_load复制代码:
DE> DE> DE>ldr r1, =BWSCON /* Z = CPU booted from NAND */ DE> DE> DE> DE>ldr r1, [r1] DE> DE> DE> DE>tst r1, #6 /* BWSCON[2:1] = OM[1:0] */ DE> DE> DE> DE>teqeq r0, #0 /* Z &= running at address 0 */ DE> DE> DE> DE>beq nand_load DE>
在417行是nand_load代码,首要会跳转到614行履行may_resume
以检测体系是从待机形式唤醒仍是上电发动。假如唤醒,则会依据之前保存的现场进行相应处理,
本文不做更多叙说;假如是发动,则会回来nand_load持续履行。在nand_load里初始化s3c2410的nandcontroller,
触及存储器映射和寄存器NFCONF等,拜见S3C2410手册215页起。相同,仅张贴开端部分的代码:
DE> DE> DE>mov r1, #S3C2410_NAND_BASE DE> DE> DE> DE>ldr r2, =0xf842 @ initial value enable tacls=3,rph0=6,rph1=0 DE> DE> DE> DE>str r2, [r1, #oN FCONF] DE> DE> DE> DE>ldr r2, [r1, #oN FCONF] DE> DE> DE> DE>bic r2, r2, #0x800 @ enable chip DE>
在451行持续依据装备设定栈指针,为后边调用C函数履行复制作预备:
DE> DE> DE>ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ DE> DE> DE> DE>sub r0, r0, #CFG_MALLOC_LEN /* malloc area */ DE> DE> DE> DE>sub r0, r0, #CFG_GBL_DA TA_SIZE /* bdinfo */ #ifdef CONFIG_USE_IRQ DE> DE> DE> DE>sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif DE> DE> DE> DE>sub sp, r0, #12 /* leave 3 words for abort-stack */ DE>
然后在460行,将SDRAM中的方针地址存入r0,0x0地址存入r1,u-boot长度存入r2,
跳入cpu/arm920t/s3c24x0/nand_read.c文件第154行履行nand_read_ll函数,该函数承受前面3个寄存器中的值作为参数,并将回来值放回r0:
DE> DE> DE>ldr r0, _TEXT_BASE DE> DE> DE> DE>mov r1, #0x0 DE> DE> DE> DE>mov r2, #CFG_UBOOT_SIZE DE> DE> DE> DE>bl nand_read_ll DE>
在nand_read_ll函数中完结了nandflash拜访代码,而且支撑主动越过坏块的特性,函数循环履行nand页面读取并存入SDRAM,直到u-boot悉数复制完,并回来0,该C代码留给读者自己阅览。nand_read_ll回来0后,会跳转到ok_nand_read,并482行对复制的头4K字节进行校验:
DE> @ verify mov r0, #0 @ldr r1, =0x33f00000 ldr r1, _TEXT_BASE mov r2, #0x400 DE> DE> DE> DE>@ 4 bytes * 1024 = 4K-bytes go_next: DE> DE> DE> DE>ldr r3, [r0], #4 DE> DE> DE> DE>ldr r4, [r1], #4 DE> DE> DE> DE>teq r3, r4 DE> DE> DE> DE>bne notmatch DE> DE> DE> DE>subs r2, r2, #4 DE> DE> DE> DE>beq done_nand_read DE> DE> DE> DE>bne go_next DE>
校验经过后代码506行,在地址为_booted_from_nand的SDRAM方位保存1,以便告知上层软件本次发动是从nand引导:
DE>done_nand_read: DE> DE> DE> DE>ldr r0, _booted_from_nand DE> DE> DE> DE>mov r1, #1 DE> DE> DE> DE>strb r1, [r0] DE>
然后在518行,将中止向量表复制到0x0:
DE> DE> DE>mov r0, #0 DE> DE> DE> DE>ldr r1, _TEXT_BASE DE> DE> DE> DE>mov r2, #0x40 irqvec_cpy_next: DE> DE> DE> DE>ldr r3, [r1], #4 DE> DE> DE> DE>str r3, [r0], #4 DE> DE> DE> DE>subs r2, r2, #4 DE> DE> DE> DE>bne irqvec_cpy_next DE>
在532行,设置栈指针:
DE> DE> DE>ldr r0, _TEXT_BASE DE> DE> DE> DE>/* upper 128 KiB: relocated uboot */ DE> DE> DE> DE>sub r0, r0, #CFG_MALLOC_LEN DE> DE> DE> DE>/* malloc area */ DE> DE> DE> DE>sub r0, r0, #CFG_GBL_DA TA_SIZE /* bdinfo */ #ifdef CONFIG_USE_IRQ DE> DE> DE> DE>sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif DE> DE> DE> DE>sub sp, r0, #12 DE> DE> DE> DE>/* leave 3 words for abort-stack */ DE>
在541行,铲除bss段并跳转到真实的C函数start_armboot,进入更高档的硬件初始化代码,汇编初始化部分也悉数完结使命:
DE> DE> DE>ldr r0, _bss_start /* find start of bss segment */ DE> DE> DE> DE>ldr r1, _bss_end /* stop here */ DE> DE> DE> DE>mov r2, #0x00000000 /* clear */ DE> DE> clbss_l: DE> DE> DE> DE>str r2, [r0] /* clear loop... */ DE> DE> DE> DE>add r0, r0, #4 DE> DE> DE> DE>cmp r0, r1 DE> DE> DE> DE>ble clbss_l DE> DE> DE> DE> DE> DE>ldr pc, _start_armboot DE>
start_armboot函数坐落lib_arm/board.c文件第277行,首要初始化globel_da
DE> DE> DE>gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); DE> DE> DE> DE>/* compiler optimization barrier needed for GCC >= 3.4 */__asm__ __volatile__("": : :"memory"); DE> DE> DE> DE>memset ((void*)gd, 0, sizeof (gd_t));gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); DE> DE> DE> DE>memset (gd->bd, 0, sizeof (bd_t)); DE>
然后在一个for循环中从init_sequence地址开端,逐一调用初始化C函数至NULL停止。这些routine函数按调用次序分别是
- cpu_init() ——在common/main.c文件,履行初始化中止栈操作
- board_init() ——在board/neo1973/gta01/gta01.c文件中,履行板级初始化,首要是更新GPIO和PLL设置
- interrupt_init() ——在/cpu/arm920t/s3c24x0/interrupts.c文件中,履行时钟中止初始化
- env_init() ——在common/env_nand.c文件中,设置缺省环境变量
- init_baudrate() ——在lib_arm/board.c文件中,将环境变量中的baudrate存入bd_info结构bd
- serial_init() ——在common/serial.c文件中,调用驱动中真实的init()初始化串口
- console_init_f() ——在common/console.c文件中,更新global_da
ta结构gd的have_console标记为1 - display_banner() ——在lib_arm/board.c文件中,打印u-boot banner,输出版别、运转地址等信息。比如在操控台看到的
- init_func_i2c() ——在lib_arm/board.c文件中,初始化i2c总线
- dram_init() ——在board/neo1973/gta01/gta01.c文件中,填充bd->bi_dram[0]的start和size成员,用来描绘u-boot可用的ram
- display_dram_config() ——在board/neo1973/gta01/gta01.c文件中,打印当时ram装备。在操控台能够看到相应的 DRAM: 128 MB
运用gdb能够明晰的看到调用进程:
DE> DE> DE>for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { DE> DE> DE> DE> DE> DE>if ((*init_fnc_ptr)() != 0) { DE> DE> DE> DE> DE> DE> DE> DE>hang (); DE> DE> DE> DE> DE> DE>} DE> DE> DE> DE>} DE>
接着是一些可选外设的初始化,如显示屏、nor、nand、dataflash、网卡等,此进程履行后悉数初始化作业完结。下面仅张贴nor代码:
DE>#ifndef CFG_NO_FLASH DE> DE> DE> DE>/* configure available FLASH banks */ DE> DE> DE> DE>size = flash_init (); DE> DE> DE> DE>display_flash_config (size); #endif /* CFG_NO_FLASH */ DE>
之后在457行进入无限循环,调用common/main.c文件的278行main_loop()函数,u-boot完结发动进程。main_loop供给一个交互式命令行,可经过串口或usb操控台操作,也能够进一步引导操作体系:
DE> DE> DE>for (;;) { DE> DE> DE> DE> DE> DE>main_loop (); DE> DE> DE> DE>} DE>