1. u-boot介绍
本次移植采⽤的是U-Boot-1.2.0版本。3. U-Boot源码分析3.1 源码⼊⼝的解释
可能⼤多数的同学上⽹查资料后都了解到,stage1阶段的启动代码,主要就在start.s⽂件⾥。此start.s也是系统上电后执⾏的第⼀个代码。它全部由汇编编写。在讲述start.s之前,我们先来了解⼀下,系统怎么知道它要先去start.s⾥执⾏代码。
我们知道,每个可执⾏的映像Image,肯定会给编译器⼀个⼊⼝,⽽且是“有且只有⼀个全局的⼊⼝”。我们可以把这个⼊⼝放在flash的0x0地址上,然后让系统去找这个0x0即可。
实际上,我们可以通过编写链接⽂件(lds)和mk⽂件来告知编译器这些情况。Lds⽂件可以决定⼀个可执⾏代码的各个段的存储位置、⼊⼝地址等,详情请参考附录中的⽂章《u-boot lds⽂件详解》。这⾥来说的Mk⽂件,是在board/下对应开发板⼦⽬录中的mk⽂件。它指定了TEXT_BASE的地址。3.2 stage1:启动分析
终于开始u-boot源代码的讲述了!本⽂讲述的u-boot-1.2.0源码,是经笔者修改的代码。不过,笔者也会将它与完整的源码包进⾏⽐较分析。⾸先是start.s⽂件,刚才说过了,这个是系统启动后运⾏的第⼀个代码,我们详细地分析如下:3.2.1 中断向量表的设置.globl _start_start: b reset
ldr pc, _undefined_instructionldr pc, _software_interruptldr pc, _prefetch_abortldr pc, _data_abortldr pc, _not_usedldr pc, _irqldr pc, _fiq
_undefined_instruction: .word undefined_instruction_software_interrupt: .word software_interrupt_prefetch_abort: .word prefetch_abort_data_abort: .word data_abort_not_used: .word not_used_irq: .word irq_fiq: .word fiq
.balignl 16,0xdeadbeef
Start.s⽂件⼀开始,就定义了_start的全局变量。也即,在别的⽂件,照样能引⽤这个_start变量。这段代码验证了我们之前学过的arm体系的理论知识:中断向量表放在从0x0开始的地⽅。其中,每个异常中断的摆放次序,是事先规定的。⽐如第⼀个必须是reset异常,第⼆个必须是未定义的指令异常等等。
需要注意的是,在这⾥,我们也可以理解:为何系统⼀上电,会⾃动运⾏代码。因为系统上电后,会从0x0地⽅取指令,⽽0x0处放置的是reset标签,直接就跳去reset标签处去启动系统了。
另外,这⾥使⽤了ldr指令。⽽ldr指令中的label,分别⽤⼀个.word伪操作来定义。⽐如:_undefined_instruction: .word undefined_instruction
我们⽤source insight跟踪代码后,发现,undefined_instruction在start.s的后⾯给出了具体的操作,如下:
undefined_instruction:get_bad_stackbad_save_user_regsbl do_undefined_instruction
在跳转到中断服务⼦程序之前,先有两个宏代码,⼀个是对stack的操作,⼀个是⽤户regs的保存。然后才能跳转如中断服务⼦程序中执⾏。请参考《ARM体系结构与编程》等相关书籍,⾃然能获得详细的答案。
值得⼀提的是,当发⽣异常时,都将执⾏u-boot-1.2.0\\cpu\\arm920t\\ interrupts.c中定义的中断函数。也就是说,start.s中要跳转的这些中断⼦程序的代码,均在u-boot-1.2.0\\cpu\\arm920t\\ interrupts.c中定义。3.2.2 U-Boot存储器映射定义
该代码段主要是定义u-boot需要使⽤的⼀些映射区的label,⽐如⽤户堆区、⽤户栈区、全局数据结构区等。笔者在下页给出了⼀个图⽰,把整个u-boot映射的所有区都列出来了,这个图⾮常经典,⽹上找的,⼤家可以好好研究⼀把。_TEXT_BASE:.word TEXT_BASE.globl _armboot_start_armboot_start:.word _start
/* These are defined in the board-specific linker script. */.globl _bss_start_bss_start:.word __bss_start.globl _bss_end_bss_end:.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */.globl IRQ_STACK_STARTIRQ_STACK_START:.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */.globl FIQ_STACK_STARTFIQ_STACK_START:.word 0x0badc0de#endif
从上图也可以清晰地发现,堆和栈是有区别的。⽽且可以看到,⽤户栈区是向下递减的,即地址减少的⽅向⽣长。3.2.3 上电后CPU为SVC模式reset:
/* set the cpu to SVC32 mode */mrs r0,cpsrbic r0,r0,#0x1f
orr r0,r0,#0xd3msr cpsr,r0
这是系统复位后执⾏的“第⼀个代码段”(严格来说不是)。CPU复位后,系统会⽴即被设置成SVC模式。记得之前有⽹友发帖咨询这个问题,问系统复位后,cpu处于哪个处理器模式。这个代码,就回答了这个问题。
从这个代码中,我们也可以得到⼀个对寄存器操作的经验:读—修改--写。这⾥先把cpsr的值读到r0中,清除掉我们想修改的bit位,然后⽤orr指令来保证其他bit位不被改动,并达到修改寄存器低5位值的⽬的。最后⽤msr指令把r0的值给cpsr寄存器达到我们的修改⽬的。3.2.4 关闭看门狗
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)ldr r0, =pWTCONmov r1, #0x0str r1, [r0]
根据S3C2440的datasheet⽂档,系统启动后,看门狗寄存器是被使能的,所以,如果不在预计的时间内“喂狗”,就有“被狗咬”的可能。别说啥了,赶紧先喂狗。上⾯这段代码即为喂狗代码。u-boot代码编写者把它放在CPU上电修改SVC模式后的第⼀个代码,是可以理解的。这个代码,也是修改寄存器的代码,它的思路依旧是:读—修改—写。实际上,u-boot-1.2.0代码在喂狗代码之前,还有⼀段代码,如下:#if defined(CONFIG_S3C2400)# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */# define CLKDIVN 0x14800014 /* clock divisor register */#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000 /* 喂狗寄存器*/
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */#endif
这是定义寄存器⽤的。⽐如根据S3C2440的datasheet⽂档,喂狗寄存器pWTCON的寄存器地址是0x15300000,需要定义后才能使⽤。同理,这⾥还定义了时钟除数寄存器CLKDIVN和中断掩码的INTMSK寄存器的地址。在后续代码中会陆续⽤到。3.2.5 关掉中断
/*mask all IRQs by setting all bits in the INTMR - default */mov r1, #0xffffffffldr r0, =INTMSKstr r1, [r0]
# if defined(CONFIG_S3C2410)ldr r1, =0x3ffldr r0, =INTSUBMSKstr r1, [r0]# endif
从注释可以看出此段代码的作⽤:屏蔽掉所有的irq中断。为了屏蔽这些中断,我们只要把INTMSK的所有的bit位都置1即可。INTMSK寄存器共32bit位,每个bit对应着不同的中断源。事实上,笔者认为这个代码是多余的,只是为了“⼼⾥更踏实”⽽已。因为S3C2440的datasheet⽂档⾥明确指出,cpu在复位的时候,这个寄存器的值就是0XFFFFFFFF,以防⽌发⽣异常中断。3.2.6 修改时钟除数寄存器/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */ldr r0, =CLKDIVN
mov r1, #0 /* 原先的值是3 ,现在是1:1:1*/str r1, [r0]
在u-boot-1.2.0源码中,给CLKDIVN寄存器赋值的是#0x3,表⽰FCLK:HCLK:PCLK = 1:2:4,这⾥笔者将其⽐例改为1:1:1,没啥特殊的⽬的,调试代码的时候试验⽤的,后来调试完毕,就没有再修改了。3.2.7 调⽤cpu_init_crit
#ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_crit#endif
此段代码指明:若未定义CONFIG_SKIP_LOWLEVEL_INIT,就执⾏cpu_init_crit。我们当然不会跳过底层的初始化。因为
LOWLEVEL_INIT会对我们的SDRAM进⾏初始化,这对我们的cpu是必要的。根据source insight的索引,我们转到了cpu_init_crit的代码中:
#ifndef CONFIG_SKIP_LOWLEVEL_INITcpu_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) Alignorr r0, r0, #0x00001000 @ set bit 12 (I) I-Cachemcr p15, 0, r0, c1, c0, 0
/* before relocating, we have to setup RAM timing because memory timing is board-dependend, you will find a lowlevel_init.S in yourboard directory. */mov ip, lrbl lowlevel_initmov lr, ipmov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
⾮常符合我们的思维,我们⽆效掉了指令cache和数据cache,并禁⽌MMU与cache。为什么会有这⼀步呢?笔者曾经深受cache的伤害。在调试代码的时候,下载完修改的bin⽂件后,如果只按复位键,⽽不关掉板⼦重新上电,就会造成cache中可能残留之前对cache操作的数据。我们称之为“脏数据”,它会映像我们的调试结果,造成假象。
当然,在这⾥⽆效cache和MMU肯定还有别的原因。⽐如在初始化阶段,可以认为我们只有⼀个任务在跑,没有必要,也不允许使⽤地址变换。因此最好应该⽆效掉MMU。
由于在cpu_init_cri⼦程序中⼜⼀次调⽤⼦程序lowlevel_init,因此,需要事先保护好lr寄存器的内容。当返回时候,再恢复它。在进⼊lowlevel_init之前,有必要详细说⼀下mov ip, lr,这个语句的ip。
为了使单独编译的C语⾔程序和汇编程序之间能相互调⽤,必须为⼦程序间的调⽤规定⼀定的规则。这就是ATPCS规则。它规定了⼀些⼦程序间调⽤的基本规则。在寄存器的使⽤规则⾥,寄存器R12作⽤⼦程序间的scratch寄存器,记做ip。mov ip, lr语句的ip由此⽽来。笔者认为,这⾥使⽤别的通⽤寄存器来代替ip,实现的功能也是⼀样的。详情请参考《ARM体系结构与编程》第6章 ATPCS介绍。
3.2.8 调⽤lowlevel_init
这个函数在u-boot-1.2.0\\board\\smdk2410\\lowlevel_init.S⽂件中。这是对SDRAM的初始化。_TEXT_BASE:.word TEXT_BASE.globl lowlevel_initlowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it *//* reads SMRDATA out of FLASH rather than memory ! */ldr r0, =SMRDATAldr r1, _TEXT_BASEsub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */add r2, r0, #13*40:
ldr r3, [r0], #4str r3, [r1], #4cmp r2, r0bne 0b
/* everything is fine now */mov pc, lr.ltorg
/* the literal pools origin */SMRDATA:
.word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)).word((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)).word((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)).word((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)).word((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)).word((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)).word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)).word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT).word 0x32.word 0x30.word 0x30
该段代码是对SDRAM控制器相关的寄存器赋值,赋值过程中,采⽤了⼀个巧妙的做法,把SDRAM控制器初始化需要⽤到的13个寄存器的
值先保存在⽂字池(literal pools)中,然后通过LDR伪指令以及.ltorg来访问这个⽂字池,获取寄存器的值赋值到对应的寄存器地址中去。很多同学对此代码的两个地址不理解:SMRDATA 与_TEXT_BASE。不理解这两个地址相减之后,到底是⼀个什么值。为什么要相减呢?其实编译器进⾏编译,是按照链接⽂件进⾏的。也就是说,编译的时候所有的地址都是相对于这个TEXT_BASE计算出来的。⽽我们的程序是存放在Flash中的,ARM上电后,假设从nandflash模式启动,那么它会把Nandflash的前4K加载到内存中开始运⾏,当然是从0x0这个地址开始运⾏,所以要求我们的代码在还没有搬移到TEXT_BASE(0x38f00000)这个位置以前是不能使⽤这些label的,只能找到⼀个相对于0x0的地址出来,才能得到真正的数据。⽽且这时候,我们编译出来的bin⽂件是存放在0x0000000的,⽽不是存放在0x38f00000的。嘿嘿,说的有点乱,不知道有没有把笔者的意思表达出来。关于SDRAM初始化。3.2.9 代码的搬移
#ifndef CONFIG_SKIP_RELOCATE_UBOOTrelocate: /* relocate U-Boot to RAM */adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */cmp r0, r1 /* don't reloc during debug */beq stack_setupldr r2, _armboot_startldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */add r2, r0, r2 /* r2 <- source end address */copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */stmia r1!, {r3-r10} /* copy to target address [r1] */cmp r0, r2 /* until source end addreee [r2] */ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
在SDRAM初始化完毕后,我们开始搬移代码,把代码从原先的0x0开始的位置搬移到内存中的适当的位置继续执⾏。为啥要搬移代码?原因可能如下:1、运⾏速度的考虑。
flash的读写速度远⼩于SDRAM的读写速度,搬移到SDRAM后,可提⾼运⾏效率。2、空间的考虑。
如果是nandflash启动模式,那么只有4KB的空间供⽤户使⽤,实际的代码是永远⼤于4KB的,因此需要重新开辟空间来进⾏代码的运⾏⼯作。
有些版本的u-boot的代码搬移⽤C语⾔来实现:bl CopyCode2Ram,也是可以的。因为这时候,我们完全搭建好了C环境。在这段代码中,还有⼀个⼦程序:beq stack_setup,⽤来设置栈空间的,我们在下节中讲解。3.2.10 栈空间的设置stack_setup:
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 */
这段代码是⽤来分配各个栈空间的。包括分配动态内存区,全局数据区,IRQ和FIQ的栈空间等。3.2.11 BSS段的清零clear_bss:
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, #4cmp r0, r1ble clbss_l
本段代码先设置了BSS段的起始地址与结束地址,然后循环清楚所有的BSS段。⾄此,所有的cpu初始化⼯作(stage1阶段)已经全部结束了。后⾯的代码,将通过ldr pc, _start_armboot,进⼊C代码执⾏。这个C⼊⼝的函数,是在u-boot-1.1.6\\lib_arm\\board.c⽂件中。它标志着后续将全⾯启动C语⾔程序,同时它也是整个u-boot的主函数。3.3 stage2:C代码分析
上节提到,start_armboot函数不仅标志着后续将全⾯启动C语⾔程序,同时它也是整个u-boot的主函数。那么该函数完成什么操作呢?3.3.1 为gd与bd分配空间
/* Pointer is writable since we allocated a register for it */
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));
如同使⽤变量之前,需要声明定义⼀样,这⾥使⽤全局变量gd和bd之前,我们需要先设置它的地址,并⽤memset函数为它分配合适的空间。u-boot的注释告知我们,gd和bd是⼀个可写的指针,实际上不过是⼀个地址⽽已。
代码中的这句话:__asm__ __volatile__(\"\": : :\"memory\");⽬的就是告诉编译器内存被修改过了。更详细的关于C程序中内嵌汇编的⽂档,请参考附录中的⽂献《ARM GCC 内嵌(inline)汇编⼿册》。3.3.2 执⾏初始化列表函数
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang (); }}
这是⼀个for语句,却完成了板⼦初始化列表函数的功能。我们先来看⼀下for语句的初始值:init_sequence。⽤source insight跟踪后发现,它是⼀个指针数组:init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */board_init, /* basic board dependent setup */interrupt_init, /* set up exceptions */env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */serial_init, /* serial communications setup */console_init_f, /* stage 1 init of console */display_banner, /* say that we are here */#if defined(CONFIG_DISPLAY_CPUINFO)print_cpuinfo, /* display cpu info (and speed) */#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)checkboard, /* display board info */#endif
dram_init, /* configure available RAM banks */display_dram_config,NULL,};
指针数组的每个成员都对应着⼀个函数名(函数指针),指向的是init_fnc_t类型的函数。For语句每次都会判断当前的函数是不是NULL,如果是,则跳出for语句,完成当前的板⼦初始化列表函数的功能。
可能⼤家都注意到了⼀个类型:init_fnc_t,它表⽰什么意思呢?我们看到了在初始化列表函数之前,有⼀个新的数据类型,它是个typedef语句:
typedef int (init_fnc_t) (void);
可能有的同学对此不太理解,为啥⾮得⽤⼀个typedef呢?笔者认为,可以不⽤typedef,但是⽤了init_fnc_t后,团队中别的成员来看代码的时候,会很轻松地知道,这是⼀个初始化(init)的函数(fnc),增加了代码的可读性。如果您对typedef⽤法还不是很理解,那就赶紧咯,复习下typedef的⽤法。我们在附录C中给出了《typedef⽤法⼩结》,附录D中给出了《u-boot中typedef应⽤解析》,以上两篇⽂档均摘⾃互联⽹资料,可供参考。
现在,我们对每个初始化列表函数,都进⾏分析,由于代码量太⼤,我们不⼀⼀列出代码,⼤家可以参考u-boot-1.2.0的源码包。Cpu_init函数,并没有做实质性的⼯作,⽽且我们现在暂时没有定义CONFIG_USE_IRQ,因此,代码执⾏到这⾥,直接就return 0;Board_init函数,是初始化与硬件平台有关的函数。它的⼯作很明显:时钟的设置,引脚IO⼝的设置,并且在这⾥把数据cache和指令cache也打开了。以上⼯作的完成,标志着板⼦已经准备好⼯作了。当然,考虑到可能系统会发⽣意外中断,所以我们还需要初始化中断,让中断也准备好⼯作,因此u-boot代码中下⼀步就开始了中断的初始化。
interrupt_init函数,这实际上是定时器的中断初始化。和我们之前的培训课程相符的是,我们看到了中断初始化中的那⼏个熟悉的寄存器,⾸先是两个配置寄存器TCFG0和TCFG1。晕倒,怎么代码中只有TCFG0的设置,没有TCFG1的设置?很明显,TCFG1采⽤的是默认值。然后配置寄存器的下载值,最后打开启动开关,启动定时器⼯作。
env_init函数,这是对我们板⼦的环境做出初始化配置。顺便提⼀下,我们修改的配置⽂件⾥,⽤的是nand flash来存放环境变量的值。#define CFG_ENV_IS_IN_NAND 1#define CFG_ENV_OFFSET 0x40000
#define CFG_ENV_SIZE64 0xc000 /* Total Size of Environment Sector */#define CFG_ENV_SIZE 0x20000 /* Total Size of Environment Sector */
因此,我们在进⼊u-boot命令⾏之后,运⾏的关于环境变量的操作,只要它被保存,saveenv,肯定是save在nandflash中的某个位置。init_baudrate函数,初始化波特率。我们⼼⾥要很明确,初始化波特率,⽬的只有⼀个:让串⼝打印调试信息。因此,下⼀个函数,肯定是串⼝的初始化函数。所以,我们可以在调试的时候,先算好波特率的值,直接赋值给gd->bd->bi_baudrate,注释掉该函数中的其他代码。调试完毕,再恢复出原先的代码。这样,我们可以不⽤考虑别的因素导致串⼝打印不出信息。
serial_init函数,串⼝的初始化函数。这⾥调⽤了另⼀个函数来配置串⼝寄存器:serial_setbrg();在这个函数中,我们看到了关于串⼝的5个寄存器的配置。关于每个寄存器的更详细的配置信息,请参考ARM技术交流⽹推出的串⼝课程讲解部分。
console_init_f函数,这个函数的功能只有⼀个,就是指出我们⽬前是使⽤串⼝的,因此有此句:gd->have_console = 1;然后直接返回0。display_banner函数。OK,现在串⼝初始化完毕,我们可以打印信息了。这是u-boot代码中第⼀次打印信息。我们可以在这⾥加⼊我们⾃⼰
的代码,⽐如笔者移植的u-boot代码中,就加⼊了如下“欢迎”的代码信息:printf (\"\\n\\n\");
printf(\"*************************************************\\n\");printf(\"* *\\n\");
printf(\"* ARM技术交流⽹欢迎您! *\\n\");printf(\"* www.arm79.com *\\n\");printf(\"* *\\n\");
printf(\"*************************************************\\n\");
出现打印信息后,可以说,u-boot移植已经成功了⼀半。有了打印信息,我们可以随时⽤打印信息来调试。初始化列表函数中,还有⼏个函数,⽐较简单,我这⾥就不说了。随后开始的是⼀系列外设的初始化。3.3.3 配置可⽤的flash区:flash_init
当您跟踪到flash_init函数的时候,您会发现,这⾥只兼容AMD系列的flash芯⽚,⽐如LV400及LV800。如果您的开发板上刚好就是AMD的芯⽚,那么恭喜,您可能就不需要修改flash ID号了。可惜,笔者⽤的开发板上⽤的是EON⽣产的flash芯⽚。笔者只好把AMD的所有代码,都改成EON的代码。⽐如,笔者嫌⿇烦,直接补上#define EN29LV160AB_ID 0x2249001c再来⼀个:
#define CONFIG_EON_29LV160AB 1
后⾯再修改FLASH_BANK_SIZE、CFG_MAX_FLASH_SECT、PHYS_FLASH_1等信息,来配置笔者的板⼦上可⽤的flash区域。3.3.4 初始化内存分配函数
mem_malloc_init函数,这是⾮常关键的⼀步,请⼤家引起注意。我们必须配置好内存的起始地址和结束地址,然后把这块区域清零,以便后续来使⽤它。
3.3.5 nand flash的初始化
这部分代码,可能隐含是不执⾏的。如果您想使⽤它,需要⾃⾏打开,然后添加⾃⼰的nand flash驱动的代码。笔者⾃⼰没有写nand flash的代码,⽽是直接copy别⼈的代码,拿过来改⼀改。如果想验证⾃⼰修改或者⾃⼰写的nand flash的驱动是否正确,可以试着从nand flash中读取或写⼊⼀个数据,并⽤串⼝打印出来(笔者修改的nand flash驱动代码,将在ARM技术交流⽹上公布,需要的可以随时下载)。后⾯的代码,⼀直到main_loop函数,我们都不需要修改。main_loop函数是进⼊命令循环的函数,它接受⽤户从串⼝输⼊的命令,然后执⾏相应的⼯作,这也是整个u-boot的⼯作循环。
注意,它并没有使⽤中断来触发命令的操作,⽽是⽤循环来做这部分的⼯作:/* main_loop() can return to retry autoboot, if so just run it again. */for (;;) {main_loop ();}
⾄此,u-boot代码的分析接近尾声。4. U-Boot移植过程参考4.1 移植准备
我们采⽤的是u-boot-1.2.0版本。4.2 U-Boot移植过程分析
本章节将详细给出整个u-boot移植的过程,您只需要按照此过程操作,即可轻松地移植,并定制属于您⾃⼰的u-boot-1.2.0版本到您的开发板上!
说明:交叉编译⼯具的制作,请⾃⾏完成!事实上,许多开发板⼚商都给出了详细的制作过程供⽤户参考。4.2.1 修改Makefile⽂件
我们建议,除⾮您只是体验⼀次u-boot,⽽⾮研究u-boot。否则,请抽时间浏览⼀下u-boot根⽬录下的readme⽂档。这将对您理解u-boot⼤有帮助。
请点击您的⿏标,打开makefile⽂件。如果您是在linux环境下开发,使⽤vi makefile命令可打开该⽂件。使⽤ctrl + F键,查找“smdk2400_config”,找到后,您会看到如下代码:smdk2400_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2400 NULL s3c24x0我们解释⼀下代码:
arm,就表⽰现在⽤的是CPU的架构是arm体系结构。
arm920t,指明这是cpu的内核类型,它对应于cpu/arm920t⽬录。
Smdk2400,这是开发板的型号,它的⽬录在board/smdk2400⽬录下。您也可以⾃⼰命名您的开发板。⽐如:ARM79。NULL,表⽰开发者或者经销商是谁(vender)。
S3c24x0,表⽰开发板上的cpu是啥。对于我们的开发板,当然是S3C2440了。根据以上的解释,我们可以⾃⼰模仿着建⽴⾃⼰的编译项:arm79_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t arm79 NULL s3c24x0OK,修改完毕,可以保存、退出makefile。4.2.2 建⽴⾃⼰的开发板⽂件
为了使得u-boot具有⾃⼰的特征,我们需要在board⽬录下建⽴⾃⼰的⽂件:1、复制board/smdk2410,并更名为board/arm79。
2、复制board/smdk2410/smdk2410.c,并更名为board/arm79/arm79.c
OK,我们的开发板是⾃⼰花钱买的,现在开发板上⾯跑的u-boot,我们也可以假装是⾃⼰写的代码了。4.2.3 建⽴⾃⼰的配置⽂件
配置⽂件在:include/configs/smdk2410.h。⼤家还希望⽤别⼈的配置⽂件吗?当然不想!所以,改过来!复制
include/configs/smdk2410.h,并更名为:include/configs/arm79.h。这时候,可以暂时保留arm79.h中的配置信息。⼀会再来修改它。我们现在有更重要的事情要做。4.2.4 修改交叉编译⼯具的路径
交叉编译⼯具,您可以使⽤开发板公司为您提供的制作包即可。修改交叉编译⼯具的路径,请参考每个开发板公司的⽤户⼿册。这⾥⽆法给出⼀个定性的答案。⼀般都是在/etc/profile⽂件下修改,增加⼀个.bin⽬录。4.2.5 测试编译u-boot-1.2.0版本
其实,u-boot虽然号称经典,但是有些版本在某些特定的arm平台或者powerpc平台是编译不通过的。笔者在实习时候,在公司产品上移植了⼀个u-boot版本,就是不⾏的。换成u-boot-1.2.0版本,可以编译通过。因此,笔者本次移植也采⽤了u-boot-1.2.0版本。cd u-boot-1.2.0 /* 切换到u-boot⽬录下 */make arm79_config
这时候,命令⾏界⾯上会显⽰:Configuring for arm79 board…然后您再敲⼊make,回车。如果您的交叉编译⼯具安装正确的话,这时候就开始编译了,⼤约⼏分钟后,您就会看到窗⼝中出现了.bin⽂件的打印信息,回到您的u-bot根⽬录下,您就会发现,那⾥多出了⼀个u-boot.bin⽂件。
当然,当您在调试的时候,或许您还想得到u-boot的反汇编代码,那么,请再次打开makefile⽂件,⽤ctrl + F键,查找到“u-boot.bin”所在的⾏,⼤约在第239⾏(如果您之前没有在makefile中修改别的信息的话):ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
这⾏的代码,是指定编译后,输出啥⽂件的。可以看到,编译结果,会输出u-boot.srec⽂件,u-boot.bin⽂件,system.map⽂件,等等。这时候,您如果想让它输出u-boot的反汇编⽂件,只要这样做:
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(obj)u-boot.dis $(U_BOOT_NAND)
对⽐⼀下,发现我们现在增加了“$(obj)u-boot.dis”。对了,这就是指定编译结果,要输出u-boot反汇编⽂件。4.2.6 修改配置⽂件
之前已经提到,笔者的配置⽂件已经改为arm79.h,⽬录在include/configs/arm79.h。由于配置⽂件修改较多,⽽且是根据具体开发板进⾏配
置的,因此笔者直接给出了修改完的配置⽂件,并作出详细的注释,希望对您有所帮助!#ifndef __CONFIG_H#define __CONFIG_H
/* High Level Configuration Options (easy to change) */#define CONFIG_ARM920T 1 /* This is an ARM920T Core */#define CONFIG_S3C2410 1 /* in a SAMSUNG S3C2410 SoC */
#define CONFIG_SMDK2410 1 /* on a SAMSUNG SMDK2410 Board */ /* input clock of PLL */#define CONFIG_SYS_CLK_FREQ 12000000 /* 输⼊时钟12M */#define USE_920T_MMU 1
#undef CONFIG_USE_IRQ /* 暂时不使⽤IRQ *//* Size of malloc() pool */
#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 128*1024)
#define CFG_GBL_DATA_SIZE 128 /* size in bytes reserved for initial data *//*⽹卡的配置信息 */
#define CONFIG_DRIVER_DM9000 1#define CONFIG_DM9000_BASE 0x20000300#define DM9000_IO CONFIG_DM9000_BASE
#define DM9000_DATA (CONFIG_DM9000_BASE + 4)#define CONFIG_DM9000_USE_16BIT/* select serial console configuration */#define CONFIG_SERIAL1 1 /* 使⽤串⼝ *//****RTC *****/
#define CONFIG_RTC_S3C24X0 1/* allow to overwrite serial and ethaddr */#define CONFIG_ENV_OVERWRITE
#define CONFIG_BAUDRATE 38400 /* 波特率使⽤38400 *//********* Command definition *********/#define CONFIG_COMMANDS \\(CONFIG_CMD_DFL | \\CFG_CMD_LOADS | \\CFG_CMD_LOADB | \\CFG_CMD_CACHE | \\CFG_CMD_NAND | \\CFG_CMD_FLASH | \\CFG_CMD_PING | \\/*CFG_CMD_EEPROM |*/ \\/*CFG_CMD_I2C |*/ \\/*CFG_CMD_USB |*/ \\CFG_CMD_REGINFO | \\
CFG_CMD_DATE | \\CFG_CMD_ELF)
/* this must be included AFTER the definition of CONFIG_COMMANDS (if any) */#include #define CONFIG_BOOTDELAY 3 /* 进⼊命令⾏的等待时间3s */ /*#define CONFIG_BOOTARGS \"root=ramfs devfs=mount console=ttySA0,9600\" *//*#define CONFIG_ETHADDR 08:00:3e:26:0a:5b */#define CONFIG_NETMASK 255.255.255.0#define CONFIG_IPADDR 10.0.0.110#define CONFIG_SERVERIP 10.0.0.1/*#define CONFIG_BOOTFILE \"elinos-lart\" *//*#define CONFIG_BOOTCOMMAND \"tftp; bootm\" */#if (CONFIG_COMMANDS & CFG_CMD_KGDB) #define CONFIG_KGDB_BAUDRATE 9600 /* speed to run kgdb serial port *//* what's this ? it's not used anywhere */ #define CONFIG_KGDB_SER_INDEX 1 /* which serial port to use */#endif /* Miscellaneous configurable options */ #define CFG_LONGHELP /* undef to save memory */ #define CFG_PROMPT \"[arm79-uboot-1.2.0]# \" /* Monitor Command Prompt */#define CFG_CBSIZE 256 /* Console I/O Buffer Size */ #define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16) /* Print Buffer Size */#define CFG_MAXARGS 16 /* max number of command args */#define CFG_BARGSIZE CFG_CBSIZE /* Boot Argument Buffer Size */#define CFG_MEMTEST_START0x30000000 /* memtest works on */#define CFG_MEMTEST_END 0x33F00000 /* 63 MB in DRAM */#undef CFG_CLKS_IN_HZ /* everything, incl board info, in Hz */#define CFG_LOAD_ADDR 0x33000000 /* default load address *//* the PWM TImer 4 uses a counter of 15625 for 10 ms, so we need *//* it to wrap 100 times (total 1562500) to get 1 sec. */#define CFG_HZ 1562500/* valid baudrates */ #define CFG_BAUDRATE_TABLE { 9600, 19200, 38400, 57600, 115200 }/The stack sizes are set up in start.S using the settings below */#define CONFIG_STACKSIZE (128*1024) /* regular stack */#ifdef CONFIG_USE_IRQ #define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */#define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */#endif /* Physical Memory Map */ #define CONFIG_NR_DRAM_BANKS 1 /* we have 1 bank of DRAM */#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */#define PHYS_FLASH_1 0x00000000 /* Flash Bank #1 */#define CFG_FLASH_BASE PHYS_FLASH_1/*FLASH and environment organization */#if 0 #define CONFIG_AMD_LV400 1 /* uncomment this if you have a LV400 flash */#define CONFIG_AMD_LV800 1 /* uncomment this if you have a LV800 flash#endif #define CONFIG_EON_29LV160AB 1/*added by www.arm79.con */ #define CFG_MAX_FLASH_BANKS 1 /* max number of memory banks */#ifdef CONFIG_EON_29LV160AB #define PHYS_FLASH_SIZE 0x00200000 /* 2MB */ #define CFG_MAX_FLASH_SECT (35) /* max number of sectors on one chip */#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x1F0000) /* addr of environment */#endif #ifdef CONFIG_AMD_LV800 #define PHYS_FLASH_SIZE 0x00200000 /* 1MB */ #define CFG_MAX_FLASH_SECT (19) /* max number of sectors on one chip */#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x1F0000) /* addr of environment */#endif #ifdef CONFIG_AMD_LV400 #define PHYS_FLASH_SIZE 0x00080000 /* 512KB */ #define CFG_MAX_FLASH_SECT (11) /* max number of sectors on one chip */#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x070000) /* addr of environment */#endif /* timeout values are in ticks */ #define CFG_FLASH_ERASE_TOUT (5*CFG_HZ) /* Timeout for Flash Erase */#define CFG_FLASH_WRITE_TOUT (5*CFG_HZ) /* Timeout for Flash Write *///#define CFG_ENV_IS_IN_FLASH 1#define CFG_ENV_IS_IN_NAND 1#define CFG_ENV_OFFSET 0x40000 #define CFG_ENV_SIZE64 0xc000 /* Total Size of Environment Sector */#define CFG_ENV_SIZE 0x20000 /* Total Size of Environment Sector */#define CFG_NAND_BASE 0#define CFG_MAX_NAND_DEVICE 1 #define NAND_MAX_CHIPS 1#endif /* __CONFIG_H */4.2.7 修改start.s⽂件 这是系统启动运⾏的第⼀个⽂件。⼤部分代码是不需要修改的,毕竟S3C2410和S3C2440的启动时差别不⼤的。笔者修改了下时钟:/* FCLK:HCLK:PCLK = 1:2:4 *//* default FCLK is 120 MHz ! */ldr r0, =CLKDIVN mov r1, #0 /* 原先的值是3 ,现在是1:1:1*/str r1, [r0] 事实上,没有必要修改这个。笔者也是调试的时候修改的,调试结束,也就没有再改回去。其他地⽅就不需要修改了:SDRAM初始化部分,代码搬移部分,都可以直接⽤。4.2.8 修改board/arm79/arm79.c 这个⽂件是由原来的board/smdk2410/smdk2410.c来的。笔者修改了这段:#if FCLK_SPEED==0 /* Fout = 203MHz, Fin = 12MHz for Audio */#define M_MDIV 0xC3#define M_PDIV 0x4#define M_SDIV 0x1 #elif FCLK_SPEED==1 /* Fout = 75MHz */#define M_MDIV 42 /* 42*/#define M_PDIV 0x2 /* 0x3 */#define M_SDIV 0x2#endif 这段代码修改了MPLL的时钟。它是为了迎合波特率计算公式的设置的。然后在该⽂件⾥的board_init函数,笔者把UPLLCON的配置和MPLLCON的配置顺序颠倒下。可能这是2410与2440的区别。S3C2440的datasheet⽂档中明确规定,必须先初始化UPLLCON,然后延迟⼀段时间后才能初始化MPLLCON。代码如下:/* configure UPLL */ clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);/* some delay between MPLL and UPLL */delay(0xffff);delay(0xffff);delay(0xffff);/* configure MPLL */ clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);/* some delay between MPLL and UPLL */delay(0xffff);delay(0xffff);delay(0xffff); 另外,笔者修改了该函数⾥的IO⼝的初始化配置部分,这是根据笔者开发板上⾯的硬件结构修改的代码:/* set up the I/O ports */ gpio->GPACON = 0x007FFFFF; gpio->GPBCON = 0x00055555;gpio->GPBUP = 0x000007FF;gpio->GPCCON = 0xAAAAAAAA;gpio->GPCUP = 0x0000FFFF;gpio->GPDCON = 0xAAAAAAAA;gpio->GPDUP = 0x0000FFFF;gpio->GPECON = 0xAAAAAAAA;gpio->GPEUP = 0x0000FFFF;gpio->GPFCON = 0x000055AA;gpio->GPFUP = 0x000000FF;gpio->GPGCON = 0xFF94FFBA;gpio->GPGUP = 0x0000FFEF; gpio->GPGDAT = gpio->GPGDAT & (~(1<<4)) | (1<<4) ;gpio->GPHCON = 0x002AFAAA;gpio->GPHUP = 0x000007FF; 4.2.9 修改cpu/arm920t/s3c24x0/speed.c 修改该⽂件,是因为u-boot版本中没有S3C2440对应的版本,只有2410的版本。⽽2410与2440在计算MPLL的公式上有区别。2440芯⽚的MPLL计算公式中,多了⼀个“乘以2”。代码修改的是get_PLLCLK函数:static ulong get_PLLCLK(int pllreg){ S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();ulong r, m, p, s;if (pllreg == MPLL)r = clk_power->MPLLCON;else if (pllreg == UPLL)r = clk_power->UPLLCON;elsehang(); if (pllreg == MPLL) m = 2*(((r & 0xFF000) >> 12) + 8);else if (pllreg == UPLL)m = ((r & 0xFF000) >> 12) + 8;elsehang(); p = ((r & 0x003F0) >> 4) + 2;s = r & 0x3; return((CONFIG_SYS_CLK_FREQ * m) / (p << s));} 笔者承认,这个代码修改的很不成功。⼤家可以看到,笔者只是增加了: if (pllreg == MPLL) m = 2*(((r & 0xFF000) >> 12) + 8); ⽽这段代码,根本不具移植性。假设以后出了新的产品,升级版,那么这个代码⽆法移植,需要重新修改。最好的代码修改思路应该是,在return语句上修改:如果当前是2440的芯⽚,就return乘以2的时钟;如果是2410芯⽚,就不乘以2;或者2442的芯⽚等等。这样,有⼏个版本的CPU,只要增加这⾥的代码兼容性即可。4.2.10 修改board.c⽂件 由于在修改的时候,还未编写nand flash驱动的代码,所以这时候最好屏蔽掉nand_init函数。本⽂件中的其他函数不需要修改。4.2.11 重新编译u-boot 现在,我们可以试⼀下之前修改的u-boot是否可⾏。我们执⾏命令:cd u-boot-1.2.0进⼊u-boot根⽬录,然后make⼀下,执⾏编译。当⽣成u-boot.bin⽂件后,把它⽤JTAG软件烧到nor flash或者nand flash中,启动开发板,如果之前的修改⼯作正确的话,就会出现如下界⾯:************************************************** * ARM技术交流⽹欢迎您!* *www.arm79.com *************************************************U-Boot 1.2.0 (Dec 2 2009 - 16:51:34) U-Boot code: 33F80000 -> 33FA0A4C BSS: -> 33FA5DB4DRAM: 64 MBNor Flash: 2 MBNand Flash: 256 MiBIn: serialOut: serialErr: serial [arm79-uboot-1.2.0]#[arm79-uboot-1.2.0]# 说明您的u-boot移植⼯作基本完成。但是,我们还需要验证⼀下它是否可以执⾏我们需要的命令。所以,我们在这⾥⼀边介绍u-boot命令,⼀边演⽰。 附A、U-Boot的lds⽂件详解 对于.lds⽂件,决定⼀个可执⾏程序的各个段的存储位置,以及⼊⼝地址,这也是链接定位的作⽤。这⾥以u-boot的lds为例说明uboot的链接过程。 ⾸先看⼀下GNU官⽅⽹站上对.lds⽂件形式的完整描述:SECTIONS {... secname start BLOCK(align) (NOLOAD) : AT ( ldadr ){ contents } >region :phdr =fill...} secname和contents是必须的,前者⽤来命名这个段,后者⽤来确定代码中的什么部分放在这个段,以下是对这个描述中的⼀些关键字的解释。 1、secname:段名 2、contents:决定哪些内容放在本段,可以是整个⽬标⽂件,也可以是⽬标⽂件中的某段(代码段、数据段等) 3、start:是段的重定位地址,本段连接(运⾏)的地址,如果代码中有位置⽆关指令,程序运⾏时这个段必须放在这个地址上。start可以⽤任意⼀种描述地址的符号来描述。 4、AT(ldadr):定义本段存储(加载)的地址,如果不使⽤这个选项,则加载地址等于运⾏地址,通过这个选项可以控制各段分别保存于输出⽂件中不同的位置。例:/* nand.lds */SECTIONS { firtst 0x00000000 : { head.o init.o }second 0x30000000 : AT(4096) { main.o }} 以上,head.o放在0x00000000地址开始处,init.o放在head.o后⾯,他们的运⾏地址也是0x00000000,即连接和存储地址相同(没有AT指定);main.o放在4096(0x1000,是AT指定的,存储地址)开始处,但它的运⾏地址在0x30000000,运⾏之前需要从0x1000(加载地址处)复制到0x30000000(运⾏地址处),此过程也就需要读取 flash,把程序拷贝到相应位置才能运⾏。这就是存储地址和运⾏地址的不同,称为加载时域和运⾏时域,可以在.lds连接脚本⽂件中分别指定。ARM 技术交流⽹ 版权所有 请勿⽤于商业⽤途 违者必究 45帮助客户成功!编写好的.lds⽂件,在⽤arm-linux-ld连接命令时带-Tfilename来调⽤执⾏,如arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也⽤-Ttext参数直接指定连接地址,如arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。既然程序有了两种地址,就涉及到⼀些跳转指令的区别。 ARM汇编中,常有两种跳转⽅法:b跳转指令、ldr指令向PC赋值。要特别注意这两条指令的意思: (1)b step:b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该指令本⾝的bit[23:0]算出来的,这使得使⽤b指令的程序不依赖于要跳到的代码的位置,只看指令本⾝。 (2)ldr pc, =step :该指令是⼀个伪指令编译后会⽣成以下代码:ldr pc, 0x30008000<0x30008000>step 是从内存中的某个位置(step)读出数据并赋给PC,同样依赖当前PC的值,但是偏移量是step的连接地址(运⾏时的地址),所以可以⽤它实现从Flash到RAM的程序跳转。 (3) 此外,有必要回味⼀下adr伪指令,U-boot中那段relocate代码就是通过adr实现当前程序是在RAM中还是flash中:relocate: /* 把U-Boot重新定位到RAM */adr r0, _start /* r0是代码的当前位置 */ /* adr伪指令,汇编器⾃动通过当前PC的值算出这条指令中“_start\"的值,执⾏到_start时PC的值放到r0中: 当此段在flash中执⾏时r0 = _start = 0;当此段在RAM中执⾏时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,即u-boot在把代码拷贝到RAM中去执⾏的代码段的开始) */ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM *//* 此句执⾏的结果r1始终是0x33FF80000,因为此值是链接指定的 */cmp r0, r1 /* ⽐较r0和r1,调试的时候不要执⾏重定位 */结合u-boot.lds谈谈连接脚本 OUTPUT_FORMAT(\"elf32littlearm\;指定输出可执⾏⽂件是elf格式,32位ARM指令,⼩端OUTPUT_ARCH(arm) ;指定输出可执⾏⽂件的平台为ARMENTRY(_start) ;指定输出可执⾏⽂件的起始代码段为_start.SECTIONS{ . = 0x00000000 ; 定位当前地址为0地址. = ALIGN(4) ; 代码以4字节对齐 ARM 技术交流⽹ 版权所有 请勿⽤于商业⽤途 违者必究 46帮助客户成功!.text : ; 指定代码段{ cpu/arm920t/start.o (.text) ; 代码的第⼀个代码部分*(.text) ; 其它代码部分} . = ALIGN(4) .rodata : { *(.rodata) } ; 指定只读数据段. = ALIGN(4); .data : { *(.data) } ; 指定读/写数据段. = ALIGN(4); .got : { *(.got) } ; 指定got段, got段式是uboot⾃定义的⼀个段, ⾮标准段__u_boot_cmd_start = . ; 把__u_boot_cmd_start赋值为当前位置, 即起始位置 .u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段.__u_boot_cmd_end = . ;把__u_boot_cmd_end赋值为当前位置,即结束位置. = ALIGN(4); __bss_start = . ; 把__bss_start赋值为当前位置,即bss段的开始位置.bss : { *(.bss) } ; 指定bss段 _end = . ; 把_end赋值为当前位置,即bss段的结束位置} 附E: Ping命令使⽤的ARP协议 如果您详细查看u-boot代码的net.c⽂件,您会发现,⾥⾯的ping命令,使⽤了arp协议。所以我们简单地来介绍下arp协议。如需深⼊研究,请查看⽹络协议IEEE 802.3的官⽅⽂档。 ARP协议原理简述 ARP协议(Address Resolution Protocol 地址解析协议),在局域⽹中,⽹络中实际传输的是“帧”,帧⾥⾯有⽬标主机的MAC地址。在以太⽹中,⼀个注意要和另⼀个主机进⾏直接通信,必须要知道⽬标主机的MAC地址。这个MAC地址就是标识我们的⽹卡芯⽚唯⼀性的地址。但这个⽬标MAC地址是如何获得的呢?这就⽤到了我们这⾥讲到的地址解析协议。所有“地址解析”,就是主机在发送帧前将⽬标IP地址转换成MAC地址的过程。ARP协议的基本功能就是通过⽬标设备的IP地址,查询⽬标设备的MAC地址,以保证通信的顺利进⾏。所以在第⼀次通信前,我们知道⽬标机的IP地址,想要获知⽬标机的MAC地址,就要发送ARP报⽂(即ARP数据包)。它的传输过程简单的说就是:我知道⽬标机的IP地址,那么我就向⽹络中所有的机器发送⼀个ARP请求,请求中有⽬标机的IP地址,请求的意思是⽬标机要是收到了此请求,就把你的MAC地址告诉我。如果⽬标机不存在,那么此请求⾃然不会有⼈回应。若⽬标机接收到了此请求,它就会发送⼀个ARP应答,这个应答是明确发给请求者的,应答中有MAC地址。我接到了这个应答,我就知道了⽬标机的MAC地址,就可以进⾏以后的通信了。因为每次通信都要⽤到MAC地址。 ARP报⽂被封装在以太⽹帧头部中传输,如图为ARP请求报⽂的头部格式。 注意,以太⽹的传输存储是“⼤端格式”,即先发送⾼字节后发送低字节。例如,两个字节的数据,先发送⾼8位后发送低8位。所以接收数据 的时候要注意存储顺序。 整个报⽂分成两部分,以太⽹⾸部和ARP请求/应答。下⾯挑重点讲述。 “以太⽹⽬的地址”字段:若是发送ARP请求,应填写⼴播类型的MAC地址FF-FF-FF-FF-FF-FF,意思是让⽹络上的所有机器接收到;“帧类型”字段:填写08-06表⽰次报⽂是ARP协议; “硬件类型”字段:填写00-01表⽰以太⽹地址,即MAC地址;“协议类型”字段:填写08-00表⽰IP,即通过IP地址查询MAC地址;“硬件地址长度”字段:MAC地址长度为6(以字节为单位);“协议地址长度”字段:IP地址长度为4(以字节为单位); “操作类型”字段:ARP数据包类型,0表⽰ARP请求,1表⽰ARP应答;“⽬的以太⽹地址”字段:若是发送ARP请求,这⾥是需要⽬标机填充的。 因篇幅问题不能全部显示,请点此查看更多更全内容