0.1mpa是多少pa:7.4 移植U-Boot
来源:百度文库 编辑:偶看新闻 时间:2024/04/27 23:45:24
7.4 移植U-Boot
U-Boot变得如此流行,原因之一是它很容易支持新的平台。移植新开发板,必须提供一个下级的makefile文件,该文件提供了构建过程中用到的板级相关的定义。这些makefile文件都以config.mk命名,并保存在U-Boot顶层源文件目录下的.../board/xxx子目录中,其中xxx指的是一个特殊的开发板。
在最新的U-Boot 1.1.4版本中,.../boards子目录下有240多个不同的开发板配置文件,它们都命名为config.mk。在这个版本中,支持29个不同的CPU配置(按同样方式计数后得出)。注意在一些情况下,一种CPU配置会涉及一个系列的芯片,例如ppc4xx就支持了PowerPC 4xx系列的几款处理器。目前,U-Boot支持了很多目前在使用的流行的处理器和处理器系列,基于这些处理器的参考板也得到了U-Boot的支持。
如果开发板上的CPU是所支持的CPU之一,那么移植U-Boot就非常简单了。如果必须增加新的CPU,那么就需要下一番苦功了。不过有幸的是,也许有人已经在你之前完成了大部分工作。不管是基于现有的CPU移植新的CPU还是移植新的开发板,都应该仔细研究与之相对应的源代码,确定最贴近你使用的那款CPU,复制CPU相关目录下的功能函数。最后,修改源代码,增加对新CPU需求的特殊支持。7.4.1 为EP405开发板移植U-Boot
将U-Boot移植到一个新平台会使用相同的方法,下面举例说明。我们将要使用的开发板叫作EP405(Embedded Planet),板上使用了AMCC PowerPC 405GP处理器。本例中使用的这块特殊的开发板上有64MB的SDRAM和16MB的Flash,以及其他一些设备。
第一步是看一下U-Boot中已经支持的开发板与我们使用的开发板有多少差距。U-Boot源码树中,有很多支持405GP处理器的开发板,使用grep快速查找对开发板进行配置的头文件:
- $ cd .../u-boot/include/configs$ grep -l CONFIG_405GP *
在最新的U-Boot中,针对405GP处理器进行配置的文件有25个。在查看几个以后,选择AR405.h文件作为我们移植工作的基线,文件中提供了对LXT971以太网的支持。我们的目标就是借鉴开源,尽量减少我们的开发工作量。先完成简单的步骤:复制一份开发板的配置文件,这个新文件的文件名是根据你的开发板自行设置的,这里叫EP405.h。在U-Boot源码树的顶层目录执行下面命令,完成上述步骤:
- $ cp .../include/configs/AR405.h .../include/configs/EP405.h
接下来创建与开发板相关的目录,将AR405开发板的文件复制到该目录中。此时,我们还不知道是否需要其中的全部文件,这一步会在后面讲到。复制这些文件后,修改文件名,以匹配自己的开发板。
- $ cd board <<< from top level U-Boot source directory
- $ mkdir ep405
- $ cp esd/ar405/* ep405
最难的一步到来了。Jerry Van Baren是对U-Boot有贡献的开发者,在U-Boot邮件列表中,他用诙谐幽默的语言详细说明了移植U-Boot的过程。他所提供的完整移植过程可以在U-Boot的README文件中找到,是用C语言写的。下面使用Jerry的风格来总结移植过程的难点部分:
正如这里总结的,Jerry的移植过程简单而又正确。当你确定了基线后,必须增加、删除和修改源代码,直到编译,然后在正常运行之前进行调试。这个过程没有捷径。将任何引导装入程序移植到一个新的开发板都需要你掌握很多领域的知识,包括软件和硬件。其中一些知识点相当专业和复杂,例如SDRAM控制器。事实上,所有这些工作都包括具备硬件底层的细节知识。忠告:把大量的娱乐时间用到阅读处理器硬件参考手册上以及板上其他组件的参考手册。
- while (!running) {
- do {
- Add / modify source code
- } until (compiles);
- Debug;
- ...
- }
7.4.2 U-Boot的makefile配置目标
既然我们是从已有的代码开始的,那么必须在U-Boot顶层目录的makefile中做些修改,在里面增加针对我们自己开发板的配置。在查看这个makefile以后,我们可以找到一段针对所支持的不同开发板配置U-Boot源代码的内容,在这里增加我们的新开发板以进行编译。因为我们的开发板是从ESD AR405衍生的,因此要将其规则作为一个模板,用它进行编译。如果你继续往下阅读U-Boot的源代码,会看到这些规则在makefile中是用配置名按照字母顺序排列的。我们是开源世界的公民,理应遵循其规则。与U-Boot的约定一致,我们称这个配置为EP405_config。
- EBONY_config: unconfig
- @./mkconfig $(@:_config=) ppc ppc4xx ebony
- +EP405_config: unconfig
- + @./mkconfig $(@:_config=) ppc ppc4xx ep405
- +
- ERIC_config: unconfig
- @./mkconfig $(@:_config=) ppc ppc4xx eric
我们的新配置规则已经插入其中,即前面加有+字符的三行(标准的diff格式)。
完成了这些步骤后,就有了代表起点的U-Boot源码树。它可能不会被无误地编译,但这是我们的第一步。至少编译过程可以给一些提示,以便我们知道从哪里入手。7.4.3 EP405处理器初始化
新移植的U-Boot的第一个必须正确完成的任务,是初始化处理器和内存(DRAM)子系统。复位后,405GP处理器核设计为从0xFFFF_FFFC地址处开始取指。处理器核尝试执行此地址处的指令。因为这是内存的最高端,所以这里的指令必须是非条件分支指令。
这个处理器核也是通过硬编码配置了最高2MB的内存空间的,从而即使不对外部总线控制器进行编程也可以访问到它,这段空间通常供闪存设备使用。这迫使分支指令必须落在这段地址空间里,因为处理器不能编码,除非引导装入程序初始化了额外的内存空间。我们必须跳转到在0xFFE0_0000地址附近或之上。我们是怎么知道这些的呢?因为我们阅读了405GP用户手册。
正如前面描述,405GP处理器核启动的必要条件通过硬件设计完成,以确保加电后非易失内存(Flash)能被映射到所需要的2MB内存空间里。这段初始内存空间的某些属性假定了复位时的默认值。例如,这个最高2MB的空间将被配置为256个等待状态、3个芯片地址选择延迟周期、3个片选输出使能延迟周期以及7个保持周期 。这样,硬件设计工程师在选择适当设备时,或在系统复位后想直接获得处理器执行的指令代码时,就能获得最大程度的自主权。
在代码清单7-2中,我们已经看到了如何在Flash的最上面安装复位向量表。.../cpu/ ppc4xx/start.S文件中的前几行代码是为405GP处理器核进行配置的。U-Boot开发人员有意地把这段代码写成和处理器无关的。理论上,这个文件中不需要板级相关的代码。你可以看看该文件是怎么完成的。
不论你对start.S中的逻辑流理解到什么程度,都不需要理解PowerPC汇编语言。关于修改底层汇编代码的内容,在U-Boot邮件列表的很多常见问题(FAQ)中已经给出了答案。在几乎所有的情况下,如果U-Boot移植到所支持的处理器上,都不需要修改这段代码。这是成熟的代码,很多成功案例移植的都是这段程序。移植时,你只需要修改板级相关的代码(最少)。如果你发现自己身陷困境,或是在处理器早期启动的汇编代码处遇到麻烦,极有可能是你的方向不正确。
代码清单7-6给出了4xx体系结构的start.S文件的一部分。
代码清单7-6 U-Boot中4xx的启动代码
405GP处理器的start.S文件中,第一条执行的代码大约在文件的三分之一处,这里将清除或设置很多处理器的默认值。接着禁用指令和数据缓存,开启指令缓存以加速初始的加载过程。建立了两块128MB可缓存的区域:一个在高端内存(闪存区域),另一个在底部(一般是在系统DRAM的开始处)。最后,U-Boot被复制到这个区域的RAM中,并在那里执行。这样做是出于性能方面的考虑:从RAM读取数据的速度比从闪存中读取要快上几个数量级(甚至更快)。但是,对于4xx处理器来说,开启指令缓存有另一个巧妙的原因,我们很快揭晓其中的奥妙。
- ...
- #if defined(CONFIG_405GP) || defined(CONFIG_405CR) ||
- defined(CONFIG_405) || defined(CONFIG_405EP)
- /*--------------------------------- */
- /* Clear and set up some registers. */
- /*--------------------------------- */
- addi r4,r0,0x0000
- mtspr sgr,r4
- mtspr dcwr,r4
- mtesr r4 /* clear Exception Syndrome Reg */
- mttcr r4 /* clear Timer Control Reg */
- mtxer r4 /* clear Fixed-Point Exception Reg */
- mtevpr r4 /* clear Exception Vector Prefix Reg */
- addi r4,r0,0x1000 /* set ME bit (Machine Exceptions) */
- oris r4,r4,0x0002 /* set CE bit (Critical Exceptions) */
- mtmsr r4 /* change MSR */
- addi r4,r0,(0xFFFF-0x10000) /* set r4 to 0xFFFFFFFF (status in the */
- /* dbsr is cleared by setting bits to 1) */
- mtdbsr r4 /* clear/reset the dbsr */
- /*---------------------------------- */
- /* Invalidate I and D caches. Enable I cache for defined memory regions */
- /* to speed things up. Leave the D cache disabled for now. It will be */
- /* enabled/left disabled later based on user selected menu options. */
- /* Be aware that the I cache may be disabled later based on the menu */
- /* options as well. See miscLib/main.c. */
- /*------------------------------------- */
- bl invalidate_icache
- bl invalidate_dcache
- /*-------------------------------------- */
- /* Enable two 128MB cachable regions. */
- /*----------------------------------- */
- addis r4,r0,0x8000
- addi r4,r4,0x0001
- mticcr r4 /* instruction cache */
- isync
- addis r4,r0,0x0000
- addi r4,r4,0x0000
- mtdccr r4 /* data cache */
7.4.4 特定开发板的初始化
特定板初始化第一个时机是在.../cpu/ppc4xx/start.S文件中缓存区被初始化之后。这里我们可以找到一个叫作ext_bus_cntlr_init的外部汇编语言例程。
- bl ext_bus_cntlr_init /* Board specific bus cntrl init */
这个例程在.../board/ep405/init.S文件中定义,init.S文件在特定开发板目录中。它提供一个针对非常早期的硬件初始化的钩子,是我们为EP405平台定制的文件之一。这个文件包含板级相关的代码,用来初始化405GP的外部总线控制器。代码清单7-7给出文件大体功能的实体部分。这就是初始化405GP的外部总线控制器的代码。
代码清单7-7 外部总线控制器初始化
- .globl ext_bus_cntlr_init
- ext_bus_cntlr_init:
- mflr r4 /* save link register */
- bl ..getAddr
- ..getAddr:
- mflr r3 /* get _this_ address */
- mtlr r4 /* restore link register */
- addi r4,0,14 /* prefetch 14 cache lines... */
- mtctr r4 /* ...to fit this function */
- /* cache (8x14=112 instr) */
- ..ebcloop:
- icbt r0,r3 /* prefetch cache line for [r3] */
- addi r3,r3,32 /* move to next cache line */
- bdnz ..ebcloop /* continue for 14 cache lines */
- /*--------------------------------------------------- */
- /* Delay to ensure all accesses to ROM are complete */
- /* before changing bank 0 timings */
- /* 200usec should be enough. */
- /* 200,000,000 (cycles/sec) X .000200 (sec) = */
- /* 0x9C40 cycles */
- /*--------------------------------------------------- */
- addis r3,0,0x0
- ori r3,r3,0xA000 /* ensure 200usec have passed t */
- mtctr r3
- ..spinlp:
- bdnz ..spinlp /* spin loop */
- /*----------------------------------------------------*/
- /* Now do the real work of this function */
- /* Memory Bank 0 (Flash and SRAM) initialization */
- /*----------------------------------------------------*/
- addi r4,0,pb0ap /* *ebccfga = pb0ap; */
- mtdcr ebccfga,r4
- addis r4,0,EBC0_B0AP@h /* *ebccfgd = EBC0_B0AP; */
- ori r4,r4,EBC0_B0AP@l
- mtdcr ebccfgd,r4
- addi r4,0,pb0cr /* *ebccfga = pb0cr; */
- mtdcr ebccfga,r4
- addis r4,0,EBC0_B0CR@h /* *ebccfgd = EBC0_B0CR; */
- ori r4,r4,EBC0_B0CR@l
- mtdcr ebccfgd,r4
- /*----------------------------------------------------*/
- /* Memory Bank 4 (NVRAM & BCSR) initialization */
- /*----------------------------------------------------*/
- addi r4,0,pb4ap /* *ebccfga = pb4ap; */
- mtdcr ebccfga,r4
- addis r4,0,EBC0_B4AP@h /* *ebccfgd = EBC0_B4AP; */
- ori r4,r4,EBC0_B4AP@l
- mtdcr ebccfgd,r4
- addi r4,0,pb4cr /* *ebccfga = pb4cr; */
- mtdcr ebccfga,r4
- addis r4,0,EBC0_B4CR@h /* *ebccfgd = EBC0_B4CR; */
- ori r4,r4,EBC0_B4CR@l
- mtdcr ebccfgd,r4
- blr /* return */
选择代码清单7-7中的示例是因为在底层处理器初始化中,这段代码采用了非常巧妙的技术。对于认识代码运行的上下文来说,这段代码很重要。它从闪存中执行,此时没有DRAM可用,没有栈,这段代码准备对控制器做一些基本改变,从而获得控制此刻正在执行的闪存的访问权。这在处理器文档中写得很清楚,如果在修改外部总线控制器的同时从所附着的闪存中执行代码,会导致数据读取错误和处理器的崩溃。
解决方案在这个示例汇编语言程序中。从..getAddr标签处开始到后面7条汇编语言指令,代码的含义是,使用icbt指令将自身预取指到指令缓存中。在整个子程序成功地读入指令缓存以后,它将继续对外部总线控制器做一些需要的改动,而无需担心崩溃,这是因为它直接从内部指令缓存中执行。微妙,但又聪明!这段代码后面是一个短暂的延时,以确保i-cache能读到全部请求。
完成预取指和延时以后,代码接下来要做的是根据开发板配置第0组和第4组内存,具体的值根据板上组件的详细内容以及它们的连接方式而定。对本例提到的PowerPC汇编语言和405GP处理器感兴趣的读者可以参考本章最后的"参考资源"。
如果没有完全明白这段代码的含义,请不要考虑改动它。也许你增加了几行,致使代码容量超出缓存范围,那么系统很可能会崩溃(更糟的是它可能只在某些时候崩溃),而且如果用调试器单步跟踪这段代码,也还是无法找出问题所在。
特定板初始化的下一个时机是在处理器数据缓存中分配临时栈之后。这段内容在.../cpu/ppc4xx/start.S文件的第727行、初始化SDRAM控制器的分支中:
- bl sdram_init
执行上下文现在包括一个栈指针和一些用于存储本地数据的临时内存,即部分C上下文,它使得开发人员可以用C语言完成系统SDRAM控制器设置和其他初始化等一些相对复杂的任务。在移植EP405时,sdram_init()函数在.../board/ep405/ep405.c文件中,该函数针对这个特定的开发板进行了定制和DRAM的配置。因为这块开发板没有使用商业上可用的DRAM内存SIMM(Single Inline Memory Module),所以它不可能动态配置DRAM。与U-Boot支持的很多其他开发板一样,这个过程在sdram_init函数中完成。
很多现成的DDR模块使用SPD(Serial Presence Detect,串行存在性检测)PROM保存定义内存模块的参数。这些参数可以通过程序的控制读取,一般使用I2C总线。这些参数还可用来作为输入,以帮助内存控制器决定合适的参数。U-Boot支持这种技术,但是它可能需要根据特定开发板做些修改。在U-Boot源代码中有很多这样的实例。CONFIG_SPD_EEPROM配置选择用来开启这个特性。你可以搜索这个选择以找到使用它的例子。7.4.5 移植概要
到目前为止,你已经了解了一些将引导装入程序移植到硬件平台的困难了。除了要深入掌握硬件知识以外,这也没有什么难的。当然,我们最好在任务期内花最少的时间在这上面。毕竟我们平时没有把精力放在理解处理器的每一处细节上,而更习惯于及时地拿出项目解决方案,以展示我们的能力。实际上,这也正是开源精神长盛不衰的主要原因之一。我们刚才说将U-Boot移植到新平台是多么的简单,不是因为我们是处理器领域的世界级专家,而是因为我们之前的很多人已经完成了大量困难的工作。
代码清单7-8是一份完整的文件列表,里面记录了将U-Boot移植到EP405时新增或修改的文件。当然,如果有U-Boot中不支持的新硬件设备,或者我们移植了一个U-Boot还不曾支持的处理器,那么所做的工作将要多得多。虽然听起来可能多余,但这里仍要指出的是,在合理的期限内,成功移植的关键是完全掌握硬件(处理器和子系统)和软件(U-Boot)的细节知识点,此外别无他法。如果你带着这种心态开始项目,你一定会成功!
代码清单7-8 将U-Boot移植到EP405时新增或修改的文件
回忆.../board/ep405目录中的文件,这些文件源于另一个目录。实际上,我们在本次移植中,并不需要从头开始创建任何文件。我们借鉴了他人的工作成果,根据我们的目标做了必要的定制。
- $ diff -purN u-boot u-boot-ep405/ | grep +++
- +++ u-boot-ep405/board/ep405/config.mk
- +++ u-boot-ep405/board/ep405/ep405.c
- +++ u-boot-ep405/board/ep405/ep405.h
- +++ u-boot-ep405/board/ep405/flash.c
- +++ u-boot-ep405/board/ep405/init.S
- +++ u-boot-ep405/board/ep405/Makefile
- +++ u-boot-ep405/board/ep405/u-boot.lds
- +++ u-boot-ep405/include/config.h
- +++ u-boot-ep405/include/config.mk
- +++ u-boot-ep405/include/configs/EP405.h
- +++ u-boot-ep405/include/ppc405.h
- +++ u-boot-ep405/Makefile
7.4.6 U-Boot映像格式
我们已经有了一个能运行在EP405开发板上的引导装入程序,现在就可以利用它载入并运行程序。理想情况下,我们希望运行操作系统,比如Linux。因此,我们需要了解U-Boot所需的映像格式。U-Boot在映像文件前面添加一段首部,以和其他映像区别开。U-Boot通过mkimage工具(U-Boot源码的一部分)创建这个映像首部。
最新的Linux内核发行版已经内建支持直接构建U-Boot可启动的映像。内核源码树中,ARM和PPC分支都支持名为uImage的映像。我们看看PPC中的uImage。下面的代码取自Linux内核中PPC的makefile,它的位置在.../arch/ppc/boot/images/Makefile。该文件提供了构建uImage映像的规则:
- quiet_cmd_uimage = UIMAGE $@
- cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A ppc \
- -O linux -T kernel -C gzip -a 00000000 -e 00000000 \
- -n 'Linux-$(KERNELRELEASE)' -d $< $@
不用理会复杂的语法,我们需要明白$(MKIMAGE)变量的含义。shell脚本将执行U-Boot的mkimage工具,并使用你看到的一些参数。mkimage工具创建U-Boot首部,并加在Linux内核映像之上。参数定义如下:
-A,指定目标映像的体系结构;
-O,指定目标映像的操作系统,本例中是Linux;
-T,指定目标映像的类型,本例中是内核;
-C,指定目标映像的压缩类型,这里是gzip;
-a,设置U-Boot的加载地址(loadaddress),本例中是0;
-e,设置U-Boot映像的入口点(entry point)地址;
-n,一段用来给用户识别映像的文本信息;
-d,信息首部加载的可执行映像文件名称。
有几个U-Boot命令使用这个首部的数据校验映像完整性(U-Boot会在首部中放置一个CRC签名),还利用这些数据指示各种命令对这个映像的处理。U-Boot有一个命令叫iminfo,它会读取映像的首部,并从目标映像中显示属性。代码清单7-9包含了通过U-Boot的tftpboot命令载入uImage(U-Boot格式的Linux内核映像)并在映像上执行iminfo命令的结果。
代码清单7-9 U-Boot的iminfo命令
- => tftpboot 400000 uImage-ep405
- ENET Speed is 100 Mbps - FULL duplex connection
- TFTP from server 192.168.1.9; our IP address is 192.168.1.33
- Filename 'uImage-ep405'.
- Load address: 0x400000
- Loading: ########## done
- Bytes transferred = 891228 (d995c hex)
- => iminfo
- ## Checking Image at 00400000 ...
- Image Name: Linux-2.6.11.6
- Image Type: PowerPC Linux Kernel Image (gzip compressed)
- Data Size: 891164 Bytes = 870.3 kB
- Load Address: 00000000
- Entry Point: 00000000
- Verifying Checksum ... OK
- =>