在做完了linux由arm处理器核移植到ppc处理器核的工作后,还需要进行uboot的移植,之前对uboot的分析文章都是基于arm平台,感兴趣的朋友可以看看,链接如下:
http://blog.csdn.net/column/details/uboot-note.html
借这次ppc移植工作,也学习了ppc uboot的相关知识,顺道写几篇文章记录下。
工作背景是公司处理器由arm处理器核cortex A8换为ppc处理器核ppc460s,现在处于FPGA仿真验证阶段,SOC的外设控制器都没有变化.
uboot版本号:2014.04
平台:powerpc460s
根据之前arm版本uboot的移植经验,一次完整的uboot移植我觉得可以分为3步:
(1)根据需求,对uboot进行配置,链接等方面的修改,加入必须的板级支持函数(先为空函数即可),保证uboot可以编译链接通过。
(2)由start.s开始,按照uboot启动流程对调用到的各个关键函数进行功能调试。保证uboot正常启动进入命令行。
(3)对各个模块驱动单独调试。
因此我修改boards.cfg,修改配置文件,确定代码段首地址,内存首地址等基本要素。期望能够直接编过。
但是看了ppc460s处理器的链接脚本后,我觉得问题比我想象中要复杂一点。
根据我的实际需求需要对ppc的链接脚本进行改造,其实就是将uboot镜像中各个section进行重新布局。今天趁十一假期,来记录下对ppc uboot链接脚本的改造过程。
首先来分析下原版uboot针对ppc460s处理器各个section的布局,ppc460s属于ppc4xx系列,因此链接脚本是arch/powerpc/cpu/ppc4xx/u-boot.lds。结合arch/powerpc/cpu/ppc4xx/start.S等启动代码,我画出了ppc4xx系列处理器uboot段布局图如下。
bootpg和resetvec位于4G地址空间顶端的4k空间,其余段则位于指定的地址。虚线表明了处理器上电后执行流程。
这里就需要简单说明下ppc4xx系列处理器的一点特性,ppc4xx系列处理器很大的一个特点是MMU常开,上电即开启。
上电后ppc4xx有一个shadow TLB将地址空间最顶端的4k进行映射,具体映射到哪里,这就要看处理器内部逻辑如何填该TLB,以及外部总线的地址仲裁如何布局物理地址空间,这点询问SOC的IC设计人员就可以知道。
初上电的ppc4xx处理器只能访问顶端4K空间,并且上电取指地址是0xfffffffc,因此在针对ppc4xx系列处理器,bootloader的设计一般就是0xfffffffc处为跳转指令,跳转至4k起始地址执行,在该4k代码中对需要进行访问的地址空间进行TLB映射,保证之后主代码所在物理地址的映射,最后跳转到主代码段执行。
可以看出uboot的设计就是遵循这个特点,上电首先执行jump _start_440,跳转到4k起点执行,初始化TLB之后jump _start,进入代码段执行。
因此uboot完全可以作为ppc4xx处理器的bootloader来使用,上电直接执行uboot。
但是对于我们公司处理器,却产生了一些问题,问题如下:
(1)公司处理器逻辑将顶端4k空间映射到片内64k的ROM中,该ROM存放公司开发的bootloader,uboot太大放不进去
(2)uboot实际相当于一个二级bootloader,由公司一级bootloader加载uboot启动,uboot进行必要的配置和参数准备,再去启动kernel
(3)公司处理器的需求是uboot直接运行在内存中,仅仅起一个承上启下的作用,为kernel传参。
从公司处理器的需求来看,所需要的uboot并不是一个完整的bootloader,而仅仅是在内存中运行,为kernel准备参数,加载启动kernel就可以。这样uboot的段布局就需要改造一下。
根据这个需求,对uboot的链接布局我提出来的改进方案如下:
(1)顶部跳转和地址空间映射的任务由一级bootloader完成,uboot仅需要直接在内存中运行即可,因此考虑将bootpg以及resetvec去掉。
(2)原代码段执行是由_start_440–>_start跳转来切入的,现在需要手动指定uboot入口为_start。
针对第一条改进方案,起初考虑过保留bootpg,但是由于bootpg在地址空间的顶端4k,根据实际的处理器地址空间分布,我的代码段启动地址CONFIG_SYS_TEXT_BASE是在内存的0x80e80000,需要注意的是这个地址是一个虚拟地址,因为公司一级bootloader中已经完成了地址空间的映射,都是平映射。但是这样uboot最终链接生成u-boot.bin时,就会有1G大小,这是因为最高位置的段bootpg和最低位置的段text之间间隔了将近1G,生成镜像时这之间的空隙是需要预留出来的,来保证加载是能够将各个段加载正确位置。
这里就想到一个问题,难道别的ppc4xx处理器,如果使用uboot作为一级bootloader,text段在内存中,而内存地址距离地址空间顶端很远,编译出来的u-boot.bin就会非常大吗?
查看别的ppc4xx处理器的相关代码,发现解决方法是在4k代码中将内存映射到接近顶端4k的地址,同时修改CONFIG_SYS_TEXT_BASE为该地址。这样编译链接出来的u-boot.bin就不大了。
因此我上面的考虑,有一个很重要的前提是我在4k代码中为需要使用的地址空间做了平映射。公司处理器的一级bootloader中的确是这样做的。
这样想来,对于我的uboot,bootpg段还有一种解决方法,是保留bootpg,修改其中TLB映射,将内存代码段地址映射到接近顶端4k的位置,并且修改CONFIG_SYS_TEXT_BASE。
但是由于一级bootloader的存在,已经将跳转以及TLB映射工作做完,所以uboot并不需要4k代码,并且uboot本身不算很大,平映射完全满足需求,为了移植简单快捷,我还是采用将bootpg等段删掉的方法。
因此我将arch/powerpc/cpu/ppc4xx/u-boot.lds中如下代码注掉。
#if 0
#ifndef CONFIG_SPL
#ifdef CONFIG_440
.bootpg RESET_VECTOR_ADDRESS - 0xffc :
{
arch/powerpc/cpu/ppc4xx/start.o (.bootpg)
/* * PPC440 board need a board specific object with the * TLB definitions. This needs to get included right after * start.o, since the first shadow TLB only covers 4k * of address space. */
#ifdef CONFIG_INIT_TLB
CONFIG_INIT_TLB (.bootpg)
#else
CONFIG_BOARDDIR/init.o (.bootpg)
#endif
} :text = 0xffff
#endif
.resetvec RESET_VECTOR_ADDRESS :
{
KEEP(*(.resetvec))
} :text = 0xffff
. = RESET_VECTOR_ADDRESS + 0x4;
/* * Make sure that the bss segment isn't linked at 0x0, otherwise its * address won't be updated during relocation fixups. Note that * this is a temporary fix. Code to dynamically the fixup the bss * location will be added in the future. When the bss relocation * fixup code is present this workaround should be removed. */
#if (RESET_VECTOR_ADDRESS == 0xfffffffc)
. |= 0x10;
#endif
#endif /* CONFIG_SPL */
#endif
__bss_start = .;
.bss (NOLOAD) :
{
*(.bss*)
*(.sbss*)
*(COMMON)
} :bss
. = ALIGN(4);
__bss_end = . ;
PROVIDE (end = .);
将u-boot.lds中的bootpg resetvec段定义注释掉。这里需要注意的一点是bss段。在最后再进行讨论。
修改链接脚本后,还需要将start.S中bootpg段代码以及resetvec.S中resetvec段代码也注释掉,这里就不列了。
到这里,整个bootpg resetvec段就去掉了,原来u-boot的执行是按照由上电入口地址0xfffffffc开始,一路跳转进入_start,进入text代码段执行的。
现在没有了bootpg resetvec,u-boot.bin的入口地址就需要指定下了。
链接器ld可以通过参数-Ttext指定text段起始地址,通过参数-e指定程序入口地址。在uboot的Makefile中,如下:
MKIMAGEFLAGS_u-boot.kwb = -n $(srctree)/$(CONFIG_SYS_KWD_CONFIG:"%"=%) \
-T kwbimage -a $(CONFIG_SYS_TEXT_BASE) -e $(CONFIG_SYS_TEXT_BASE)
默认是uboot入口地址与代码段起始地址一致。但是根据上面u-boot段分布图可以看出,text段真正的入口地址是在_start,前面0x2100大小的代码是版本字符串以及异常向量表。
因此需要指定下uboot的入口地址。我的修改方法是在u-boot.lds中加入如下代码。
ENTRY(_start)
指定入口地址在_start,_start地址是text段起始地址加上0x2100.
对链接脚本进行如上修改之后,编译通过uboot还需要一些板级支持函数,对于powerpc平台,如initsdram board_early_init_r等,可以先为空函数,待功能调试时再完善,最终uboot编译通过。
通过readelf查看生成的u-boot,如下:
zk@server2:~/u-boot$ powerpc-linux-readelf -h u-boot
ELF Header:
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, big endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: PowerPC
Version: 0x1
Entry point address: 0x80e82100
Start of program headers: 52 (bytes into file) Start of section headers: 600904 (bytes into file) Flags: 0x18000, relocatable, relocatable-lib Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 2 Size of section headers: 40 (bytes) Number of section headers: 20 Section header string table index: 17
可以看出,入口地址正是我指定的_start地址,再查看各个段的分布,如下:
zk@server2:~/u-boot$ powerpc-linux-readelf -S u-boot
There are 20 section headers, starting at offset 0x92b48:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 80e80000 000074 017510 00 AX 0 0 4
[ 2] .rodata PROGBITS 80e97510 017584 005717 00 A 0 0 4
[ 3] .reloc PROGBITS 80e9cd00 01cd74 0014b8 04 WAX 0 0 4
[ 4] .data PROGBITS 80e9e1b8 01e22c 001120 00 WA 0 0 4
[ 5] .u_boot_list PROGBITS 80e9f2d8 01f34c 0004f8 00 WA 0 0 4
[ 6] .bss NOBITS 80e9f800 01f844 002798 00 WA 0 0 4
[ 7] .debug_line PROGBITS 00000000 01f844 00c63c 00 0 0 1
[ 8] .debug_info PROGBITS 00000000 02be80 0338e8 00 0 0 1
[ 9] .debug_abbrev PROGBITS 00000000 05f768 00b114 00 0 0 1
[10] .debug_aranges PROGBITS 00000000 06a880 0018d8 00 0 0 8
[11] .comment PROGBITS 00000000 06c158 000027 01 MS 0 0 1
[12] .gnu.attributes LOOS+ffffff5 00000000 06c17f 000012 00 0 0 1
[13] .debug_frame PROGBITS 00000000 06c194 0046dc 00 0 0 4
[14] .debug_str PROGBITS 00000000 070870 0063f9 01 MS 0 0 1
[15] .debug_loc PROGBITS 00000000 076c69 01934f 00 0 0 1
[16] .debug_ranges PROGBITS 00000000 08ffb8 002ac8 00 0 0 8
[17] .shstrtab STRTAB 00000000 092a80 0000c7 00 0 0 1
[18] .symtab SYMTAB 00000000 092e68 004080 10 19 533 4
[19] .strtab STRTAB 00000000 096ee8 0031b5 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
代码段起始于0x80e80000.都符合了我的要求。
这样针对公司处理器的需求,对u-boot链接脚本的改造就完成了。
总结下,在改造过程中我觉得有2个地方值得思考。
1 bss段的几个疑问
在修改u-boot.lds去掉bootpg resetvec时,我注意到bss定义位于resetvec之上,resetvec已经到了地址空间的顶点,难道bss段超出了4G?
后来实际编译我才发现,根据u-boot.lds中的定义,resetvec位于0xfffffffc时,bss位于(0xfffffffc+4)| 0x10的位置,地址就是0x10.如果resetvec定义在其他位置,则bss就在resetvec之上了。
根据编译生成的elf文件u-boot,获取到各个段的大小,与u-boot.bin比较,如下:
zk@server2:~/u-boot$ size u-boot
text data bss dec hex filename
123103 5656 10136 138895 21e8f u-boot
zk@server2:~/u-boot$ ls -lh u-boot.bin
-rwxr-xr-x 1 zk git 126k 2015-10-01 15:22 u-boot.bin
计算下可以发现,u-boot.bin基本等于text与data段之和,问题来了,bss段哪里去了?
在网上查找资料才知道,原来在最终的程序镜像中,是没有bss段。
为什么去掉bss段,想来想去,我的理解是bss段中包括了未初始化以及初始化为0的全局变量和静态变量,该段的数据全部为0,但是占据空间大,为了减小镜像尺寸,才在objcopy生成镜像时将bss段删掉。
而根据uboot代码可以知道,对于uboot这样的裸编程序来说,在其启动过程中会开辟bss段,并对bss段进行清空,来保证其中数据全部为0.
这样就不会影响后续的全局变量和静态变量的使用了。
那么我的疑问又来了,在其开辟和清空bss段的过程中,是如何知道bss段的地址范围的。
这个问题想来倒是也好解答,在编译链接过程中,我们就可以确定下来bss段的起止地址,在u-boot.lds中用__bss_start和__bss_end来表示了。
并且该起止地址记录到了u-boot这个elf文件的段表中,也就是上面readelf看到的段表列表的结果,
最终生成u-boot.bin时去掉了bss,也的确是应该去掉,占据空间大还全部为0,完全可以在程序运行时再根据__bss_start和__bss_end来分配清空就可以。
总结下,uboot中bss段的生成过程可以分为如下步骤:
(1)链接脚本中定义bss段地址范围__bss_start __bss_end。
(2)编译链接elf时,根据链接脚本确定下__bss_start __bss_end的绝对地址,记录在elf文件的段表中。
(3)elf objcopy生成u-boot.bin时,去掉bss段。
(4)加载u-boot.bin启动运行,根据__bss_start __bss_end开辟bss段,并全部清空为0。
(5)后续运行代码中访问未初始化和初始化为0的全局变量以及静态变量则会访问到bss段中。
一句话,bss段在uboot生成镜像时删掉,在运行时动态分配清空,从而达到减小镜像尺寸的目的。
2 程序起始地址与入口地址的关系
从这次修改ppc u-boot连接脚本就可以看出来,程序起始地址与入口地址不是一个概念,完全可以不相等。由于u-boot.lds中将text段的定义放在了最开始的位置,所以text段的起始地址就是整个uboot镜像的起始地址了,代码段在最前面,这也是最常用的链接方式。程序入口地址可以通过链接器的参数-e来指定。