powerpc uboot链接脚本大改造

Posted kerneler_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了powerpc uboot链接脚本大改造相关的知识,希望对你有一定的参考价值。

在做完了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来指定。

以上是关于powerpc uboot链接脚本大改造的主要内容,如果未能解决你的问题,请参考以下文章

powerpc 交叉编译错误

嵌入式Linux8.链接脚本 u-boot.lds

嵌入式Linux8.链接脚本 u-boot.lds

uboot是啥?uboot的命令是干嘛的?

uboot移植总结

嵌入式开发扯淡系列1-浅谈uboot