1 简介
u-boot跨平台是为了最大化的共用代码,提高开发效率,节约开发时间。
|--u-boot (根目录)
|-- arch (平台目录)
| |--arm (arm架构)
| |--(config.mk) (指明基于arm的编译器路径)
| | |--cpu (CPU种类目录,如armv7, arm926)
| | | |--armv7 (CPU下的板级目录,和CPU有关的操作)
| | | | |--sunxi (bsp层函数,以及自定义操作)
| | | | |--(config.lds) (armv7的链接脚本,所有armv7都用它)
| | |--lib (基于函数,如字符串/除法等操作,初始化主函数)
| | |--include (所有头文件操作)
| | | |--asm
| | | | |--arch-sum(自定义IC头文件,CPU相关)
|--board (板级目录)
| |--mtk (自定义方案目录)
| | |--a9 (方案下的分支)
|--common (所有命令存放目录,以及shell)
|--disk (分区格式操作目录)
|--doc (文档目录)
|--drivers (驱动)
| |--dma (dma)
| |--i2c (i2c)
| |--net (net)
| |--others… (以及其它驱动模块)
|--fs (文件系统目录)
| |--ext2
| |--fat
| |--other fs…
|--include (头文件目录)
| |--asm
| | |--arch-sum (arch/arn/include/arch-sum的软链接)
| |--others
|--lib (C库,标准算法,如crc32)
|--nanx_sum (nand目录)
|--net (协议层目录,如dns, nfs, tftp)
|--… (其它自定义目录)
|--(boards.cfg) (新IC或者方案的配置添加)
|--(Makefile) (顶层makefile)
|--(config.mk) (顶层配置,用宏定义路径,参数)
|--(other file…) (其它文件)
通常情况下,u-boot的完整编译命令是
make distclean //清除掉所有的编译信息,包括.o文件 make xxx_config //选择一个平台进行编译,平台信息下面介绍 make –j8 //开始编译,-jx表示使用x个线程,可以没有-jx,数字选择从1-16,数字越大编译越快 |
uboot中添加一个新平台的支持,主要做好以下几方面的工作。
打开根目录下,board.cfg文件,按照如下的格式
Target ARCH CPU Board_name Vendor SoC Options |
在原有的数据下添加新的支持。中间使用一个或者多个空格或者tab隔开。
下面说明其中每个符号的含义,以及需要完成的工作。
目标名称,用于指定编译时候的名称
比如,如果添加sum,则编译时候就使用make sum_config;如果添加a9,则编译时候使用make a9_config。
此外,它也决定了所使用的编译时的总头文件,这个头文件位于include/configs目录下,文件名即为$(Target).h。比如sum.h,a9.h。
其中存放的是编译宏,决定了哪些代码需要编译,哪些不需要;或者定义了部分变量,比如目前用到的MMU_BASE,即在这个头文件中定义。
CPU架构,比如arm,mips,等等。对于我们的IC,就直接使用arm。
举例来说,当使用arm的时候,则编译器会自动到目录arch/arm目录下寻找相关内容。
我们需要记住这个添加的ARCH。
通常这里表示指令集分类,比如我们用到的armv7,以前用过的arm926ejs,等等。编译的时候,会使用这里的参数来区分使用不同指令集。
同时,它和ARCH一起决定了我们添加的代码,应该存放在arch/arm/cpu/armv7这个目录下。
CPU也需要记住,我们将在$(ARCH)/cpu/$(CPU)/目录下添加自己的代码
指基于同一指令集的不同IC,即不同的bsp。
以armv7为例,这个目录下存放了相同指令集,但是不同IC的bsp,习惯上一颗IC使用一个目录,则目录的名字就是SOC,目录存放在arch/arm/cpu/armv7目录下。编译的时候会根据SOC来寻找指定的目录。
除了代码外,还需要完成自己的底层makefile。
这是一个目录的名词,位于board目录下,说明了顶层板级方案的添加,用来指明同一颗CPU下的不同方案。习惯上,Vendor根目录下没有代码,而是有下一层目录。具体的内容包括在下级目录中。
这就是Vendor目录下的名词,包括了具体的方案内容。不同的IC可以使用相同的Vendor/Board_name的内容。
说明部分,没有实际含义。可以直接留空不写。
跨平台的要点主要有:
1. 合适的bsp接口,可以保持在bsp独立的情况下,驱动层保持不变
2. 不同平台独有的文件,可以在makefile中使用编译宏来区分
3. 文件中如果不同的平台需要使用不同的功能,比如调用不同函数,也可以在文件中使用编译宏来区分
具体如何实现,请参见下一节的举例说明。
为了添加对于a9的支持,需要完成以下的工作。
在board.cfg文件中新起一行,添加以下内容
a9 arm armv7 sunxi allwinner a9 |
在arch/arm/cpu/armv7目录下添加a9目录,在目录中添加bsp层支持。
在arch/arm/include/asm/目录下添加arch-a9目录,添加bsp的全部头文件。
在include/config目录中,添加文件a9.h。文件内容可以参照目前有的完成。
在board目录下,添加mtk目录,然后在其中添加sum目录,在里面完成自己需要的板级配置函数。
一般,板级配置函数中,包括了获取内存大小。
对于我们的方案而已,由于采用了外部配置脚本的方式,因此这里的函数很少,主要的有板级初始化(有u-boot初始化调用),其中可以添加自己的内容,比如扫描分区信息,等等。
由于这个目录下全部是逻辑操作,和硬件无关,因此所有方案/IC可以使用同样的代码。
如果代码可以给所有方案所使用,就不需要在文件对应的makefile中加入任何宏定义。否则,对于方案独有的代码,可以按照如下的格式,修改makefile。
比如,有一个C文件,名为a9.c,需要在配置a9的时候进行编译,否则不应该编译,则makefile中应该如下写法
… COBJS-$(CONFIG_A9) += a9.o … |
它表示,如果在全局头文件(a9.h)中定义了宏CONFIG_A9,则编译这个文件,否则不编译。
如果一个文件,无论如果都应该编译,则应该写作
… COBJS-y += a9.o … |
通常,我们都应该在方案的全局头文件中定义方案的宏,比如CONFIG_A9,CONFIG_A8
bsp是为驱动服务的,有必要做到只有bsp做出修改的情况下,保持驱动不变。因此,驱动编写前应该考虑好结构,抽出恰当的接口,使所有bsp提供相同的函数。
如果驱动变化很大,就需要用前面所介绍的编译宏来决定不同的代码是否参与编译。
如果一个文件中,某个函数,甚至函数中的某个部分,只有某个平台才能涌动,可以代码中用宏加以区分,比如
… #ifdef A9_CONFIG printf(“a9\n”); #else printf (“not a9\n”); #endif … |
但是不推荐这么做,因为可能导致后面维护的时候,漏掉了这些用宏加以区分的代码。
建议的做法是,把不同的地方做成函数调用,然后不同的IC维护各种的代码,最终在makefile中形成统一。
u-boot编译的时候,编译器只能使用绝对路径,不能使用相对路径。这是因为编译的时候,将进入到每个目录下之后,再调用编译器。这时候,由于目录层次的变化,最初设定的编译器路径相对关系将失效。
解决方案:
u-boot编译脚本中定义了一个全局宏CURDIR,其实就是`pwd,可以取得u-boot的绝对路径。
由于已知编译器和u-boot的相对路径关系,假设二者处于同一个目录下,其中编译器目录为gcc,则可以在文件arch/arm/cpu/config.mk中,修改如下的代码
原为 CROSS_COMPILE ?= gcc- 修改为 CROSS_COMPILE ?= $(CURDIR)/../gcc/bin/gcc- |
由于顶层编译的时候,CURDIR已经转换成了绝对路径,所以这里使用的也是绝对路径,因此编译的时候能够找到编译器。
如果修改为
原为 CROSS_COMPILE ?= gcc- 修改为 CROSS_COMPILE ?= ./../gcc/bin/gcc- |
那么,在编译顶层目录的时候正确,但编译下一级目录的时候将报错。这就是因为到了下一级目录,编译器相对关系变成了
CROSS_COMPILE ?= ./../../gcc/bin/gcc- |
但是makefile认为没有变化,因此会出现找不到编译器的错误。
原始的u-boot中,编译脚本中的.o都是绝对路径,但是这对于跨平台来说是不合适的。
比如,有必要在uboot的头部添加一个数据用于校验uboot的正确性,原来的做法是
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = 0x00000000; . = ALIGN(4); .head : { arch/arm/cpu/armv7/a9/spare_head.o (.data) } … |
它表示,把arch/arm/cpu/armv7/a9/spare_head.o中的数据段放到head段中。
如前文所述,a9表示了一颗IC的bsp,但是不同的IC有不同的bsp目录,比如a9,a8,等等,总不能一颗IC就自带一个链接脚本。
实际上,合适的做法是
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = 0x00000000; . = ALIGN(4); .head : { SOCDIR/spare_head.o (.data) } … |
SOCDIR是一个路径,它的含义是
SOCDIR=arch/$(ARCH)/cpu /$(SOC) |
这样,当选择不同的IC时,也能使用相同的代码结构了。
当然,真正使用的链接脚本中是不能够带宏的。实际上,uboot编译的时候,会根据基本的链接脚本,利用shell语法在uboot根目录下重新生成,这个过程中会执行宏展开,这样,原始定义的宏就能够被识别了。
为了让编译器能够识别这样的符号,需要在在调用的时候,把宏的含义传递给shell。
生成链接脚本的makefile语句是
$(obj)u-boot.lds: $(LDSCRIPT) $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@ |
传递的参数就在LDPPFLAGS中。
目前传递的参数如下所示。
$(obj)u-boot.lds: $(LDSCRIPT) LDPPFLAGS += \ -include $(TOPDIR)/include/u-boot/u-boot.lds.h \ -DCPUDIR=$(CPUDIR) \ -DSOCDIR=$(SOCDIR) \ $(shell $(LD) --version | \ sed -ne 's/GNU ld version \([0-9][0-9]*\)\.\([0-9][0-9]*\).*/-DLD_MAJOR=\1 -DLD_MINOR=\2/p') |
其中,-DSOCDIR表示了SOC所代表的路径。这样,重新生成uboot.lds的时候,就会自动展开成arch/arm/cpu/armv7/a9了。