1. uboot源码目录分析
x210 uboot目录结构如下图所示
说明1:x210使用的uboot版本相对较早(2008年8月版本),目前较新的uboot目录结构有所调整
① 增设了arch目录,统一管理不同架构的cpu和lib_arch目录
② 将common目录中与命令相关的源文件单独建立cmd目录进行管理
③ 修改了配置方式
原先所有配置项均在主Makefile中,现在新建configs目录统一管理所有板级配置文件(板级别配置头文件仍在include/configs目录下)
④ 修改了主Makefile,将原先以.mk文件形式包含的配置文件纳入主Makefile(能这么做也就是因为将板子配置命令均移出了主Makefile)
说明2:uboot的目录的整体结构是随着层次递进的
ARCH --> CPU --> SoC --> board
arm arm920t s5pc1xx smdkc110
mips armv7 s5pc11x x210
就具体子目录而言:
A. 架构相关目录(此处架构指CPU架构 + SoC型号)
cpu/ lib_arm/ board/
B. 架构无关硬件相关目录(主要是配置片外外设,e.g. dm9000网卡芯片)
drivers/
C. 硬件无关目录(如命令解析 / 网络协议实现 / 文件系统支持)
common/ net/ fs/ api/
D. 通用以及配置头文件(尤其要注意板级配置头文件)
include/
2. uboot编译原理引入
1)功能模块配置
uboot提供的功能模块非常之多,比如网卡驱动就有许多种,但是我们往往只使用众多功能模块中的一小部分。为此,我们需要在编译时排除那些我们不使用的功能模块以减小最终编译出的uboot.bin体积。
要排除某一功能模块需要完成如下2个步骤:
A. 在.c文件中不编译相应的功能语句
对于非必须的可选功能模块语句用条件编译命令提供编译开关,为了集中管理这些功能语句开关,可以建立专门的配置头文件config.h。
·这样就可以在config.h中集中管理功能模块语句是否编译。
B. 在make时不编译相应的功能模块
经过上一个步骤,虽然.c文件中使用功能模块的语句可以根据条件不予编译,但是相应的功能模块(如led.c ---> led.o)依然会被编译。因为Makefile的依赖中依然有相应的.o文件,而根据通用规则,他们依然会被编译。
所以还要修改Makefile,对功能模块的编译进行控制。修改的思路和之前相同,我们添加这些带有参数的目标。
而在编译中仅让变量objs-y所包含的.o文件参与,这样通过控制objs-$(CONFIG_LED) += drivers/led.o中变量CONFIG_LED的值就可以决定相应的功能模块是否被编译。
此处我们同样可以建立配置文件config.mk统一管理。
这样只要在Makefile中包含了config.mk,自然就对相应功能模块是否编译进行了控制。
说明1:根据上面的介绍,只要这两个配置文件保持一致,也就是说在config.h中被定义的功能模块在config.mk中也被定义为y,那么就可以实现对相应功能模块及功能语句是否被编译进行控制。
说明2:config.h供源代码使用,config.mk供Makefile使用,在实现层面一般只要配置其中一个文件, 然后通过脚本分析产生另一个文件。
在uboot的配置中,是修改config.h文件,然后在主Makefile中使用sed解析并生成config.mk文件
在Linux内核的配置中,则是修改config.mk文件,然后自动生成config.h文件
2)跨平台编译环境配置
上面介绍的内容只是涉及某一个功能模块(在实现层面就是某个.c或.s文件)是否被编译,下面介绍的跨平台编译环境的配置则涉及整个目录是否被编译。
uboot是一个跨平台软件,他支持多种架构的CPU,对不同架构的支持往往包含在arch目录中(之前的uboot是cpu目录)
不同的架构使用不同的编译器,而且在众多架构中一次只能编译其中一种,而其余目录的内容不予编译,这点在Makefile中需要有所体现。
通过变量ARCH我们可以对不同平台的编译环境进行配置
通过这个变量也控制了对哪些目录下的内容进行编译
3. uboot配置过程解析
1)make x210_sd_config
编译uboot前的配置就是执行make x210_sd_config命令
x210_sd_config目标如下
执行该目标会完成3个任务:
A. 执行unconfig目标
unconfig伪目标用于清除上次配置生成的文件
B. 调用mkconfig脚本生成配置文件
此处调用mkconfig脚本时会传递6个参数,下面分析一下第1个参数的构成方式,
$(@:_config=):将目标([email protected])中的_config替换为等号后的内容,此处为空,所以该参数就是x210_sd
因此,传递给mkconfig的参数如下,
$1:x210_sd
$2:arm
$3:s5pc11x
$4:x210
$5:samsung
$6:s5pc110
$#:6
C. 将TEXT_BASE写入config.mk配置文件
TEXT_BASE用于在链接时指定uboot.bin的链接地址,该信息保存在board/samsung/x210/config.mk中,供主Makefile包含使用
2)mkconfig脚本分析
A. 参数对应关系
根据mkconfig脚本注释,传递给该脚本的参数对应关系如下,
Target:x210_sd
Architecture:arm
CPU:s5pc11x
Board:x210
VENDOR:samsung
SOC:s5pc110
说明1:VENDOR和SOC参数为可选项,因此合法的参数个数为4 / 5 / 6。至于这2个参数如何使用,后文有分析~~
说明2:如果没有VENDOR和SOC参数,传递时该位置可以留空也可以用NULL(mkconfig脚本中会分析),但是如果有SOC但是没有VENDOR,则VENDOR处只能使用NULL,以便占位。
B. 参数检查
说明1:while循环用于解析参数中的特殊选项,此处第1个参数就不是特殊选项,所以进入*)分支,直接跳出循环。
此处需要注意的是,使用shift命令可以实现对shell脚本参数的遍历(shift会同时修改$#和$1的值)
说明2:如前所述,合法的参数个数为[4, 6]。注意,这是去除特殊选项之后的参数个数~~
当然,根据这个while循环的结构,如果调用mkconfig脚本时需要带有特殊选项,需要在各有效参数之前
C. 建立体系结构相关的软链接
说明1:此处创建软链接的操作都是在include/目录下完成的
说明2:通过建立头文件目录的软链接,实现uboot的跨平台移植性。例如start.S中包含了下面2个头文件。
asm/proc/domain.h就是asm-arm/proc/domain.h
regs.h就是s5pc110.h,该头文件对SoC的SFR进行了定义
说明3:根据上文,对于asm-arm/mach这个软链接,首先指向了arch-s5pc110,之后将其覆盖指向arch-s5pc11x
需要注意的是,include/asm-arm目录下并没有arch-s5pc110目录,但是依然可以调用
ln -s arch-s5pc110 asm-arm/arch
因为软链接的本质是新建一个文件,该文件中存储的就是指向文件的文件名,只不过此处指向一个不存在的文件,该软链接无效而已
D. 建立config.mk供Makefile使用
生成的config.mk只是包含ARCH / CPU / BOARD / VENDOR / SOC这5个变量的定义,供主Makefile包含使用(主Makefile中会导出这些变量,使其在整个工程中生效)
E. 建立config.h供源文件使用
生成的config.h只是包含了板级配置头文件include/configs/x210_sd.h,该板级配置头文件中包含了开发板的所有配置项,因此代码中只要包含<config.h>即包含了所有配置项。
根据上文介绍,包含<config.h>后就可以在源代码中控制功能语句的条件编译
说明:如果调用mkconfig脚本时带有-a选项,则不会新建config.h,而是将内容追加到原有config.h之后(这个用法不常见~~)
4. uboot主Makefile解析
说明:本节并不是从all目标分析主Makefile,而是按Makefile的顺序分析该文件
1)生成uboot版本信息
说明1:此处需要注意的是,定义VERSION_FILE变量时使用了等号(=),因此是引用时展开,所以在定义中可以使用尚未定义的obj变量。
如果此处使用(:=),将会在定义处展开,那么此处$(obj)的值就是空
说明2:对version_autogenerated.h的使用
include/version_autogenerated.h会被include/version.h包含,因此在实际使用中,只要包含<version.h>即可获得uboot版本信息
2)获取编译主机信息
3)配置静默编译
如果调用make带上-s选项,该选项(s)会成为变量MAKEFLAGS的一部分,此时会将变量XECHO置为空,从而实现静默编译(即编译时不出现Makefile的打印信息)
4)配置原地编译 / 输出目录编译
A. 原地编译与输出目录编译
uboot提供了2种编译方式(Linux内核等复杂工程也都提供),
① 原地编译
含义:在.c/.S源码目录生成目标文件(.o)
优点:处理简单
缺点:
a. 污染源文件目录
b. 一套源代码只能按照一种配置进行编译,一旦更换配置,需要先make clean之后才能更换配置编译。
当然也可以每种配置各使用一套源代码,但这将加大维护的难度(e.g. 一个文件需要修改,需要在每套源代码中都实现)
② 输出目录编译
含义:在编译时指定输出目录,然后在输出目录生成目标文件(.o)
优点:
a. 不再污染源代码目录
b. 一套代码更换配置编译时,只需指定不同的输出目录即可
B. 如何配置输出目录编译
uboot提供2种方式指定输出目录,
① 命令行中添加 O=xxx,示例如下,
make O=/tmp/build distclean
make O=/tmp/build x210_sd_config
make O=/tmp/build all
② 导出BUILD_DIR变量,示例如下,
export BUILD_DIR=/tmp/build
make distclean
make x210_sd_config
make all
注意:对于移植后的uboot版本,如果添加的代码不支持输出目录编译,该功能就无法使用。x210的uboot版本在新增的代码中并不支持该功能,所以无法使用。
C. Makefile中如何处理输出目录编译
说明1:根据主Makefile的处理逻辑,如果在指定O=xxx的情况下,也导出了BUILD_DIR变量,那么O=xxx的优先级更高
说明2:CURDIR是Makefile的内置变量,无需定义,其值为当前目录的绝对地址
说明3:obj & src变量在uboot顶层目录的config.mk中定义,但是在包含config.mk之前某些目标需要使用这些变量,所以此处定义一次
5)导入include/config.mk
如上文分析,include/config.mk由mkconfig脚本生成,此处包含该文件并导出其中各变量
6)配置工具链前缀
此处根据导出的ARCH变量配置合适的工具链前缀,并导出CROSS_COMPILE变量
7)导入$(TOPDIR)/config.mk
uboot的顶层目录的config.mk文件用于进行进一步的配置,具体内容如下
A. 定义obj & src变量
可见此处如果采用原地编译,obj & src会被置为空
B. 定义工具链
C. 加载ARCH / CPU / SoC / Board配置文件
说明1:此处使用sinclude包含配置文件是防止如果没有该配置文件导致报错(并不是每个配置文件都要存在~~)
说明2:此处包含的include/autoconf.mk文件非常重要,该文件由板级配置头文件x210_sd.h解析而来,该文件可用于控制功能模块的编译
说明3:VENDOR & SOC变量的使用
如前文所述,在调用mkconfig脚本时,这两个变量可选,如包含的话用法如下,
board/$(VENDOR)/$(BOARD):构成开发板路径,比如三星将其开发板目录置于board/samsung目录下(当然,出于历史原因,为了保持向后兼容三星某些开发板目录并没有放置在board/samsung目录下)
cpu/$(CPU)/$(SOC):构成SoC路径,目前新版uboot中$(CPU)目录一般是ARM核的架构目录(e.g. armv7),$(SOC)目录为该架构下的SoC目录(e.g. s5pc1xx)
下图为2012-10版本的uboot示例
说明4:此处导入的$(TOPDIR)/board/$(BOARDDIR)/config.mk文件就是配置uboot时生成的文件,其中包含链接地址的定义
D. 导入链接器脚本
E. 设置编译选项
要设置的编译选项很多,此处列出比较重要的2点,
① -DTEXT_BASE=$(TEXT_BASE)
在x210中相当于在编译时定义如下的宏,
#define TEXT_BASE 0xc3e00000
该宏在uboot代码中会被多次使用,以便获取uboot的链接地址,只不过使用该值的方式是定义_TEXT_BASE变量
注:这也相当于实现了Makefile配置文件与源代码配置文件的交互
② -I选项的设置
设置-I选项时要注意对输出目录编译方式的支持
F. 设置编译默认规则
此处可见对输出目录编译方式的支持,如果使用该方式,会在指定目标生成目标文件
8)定义目标文件
可见编译时会将大多数目录下的文件编译为静态库供最终链接使用
9)定义最终目标
可将uboot最终的链接过程与我们裸机代码工程相同
说明:include/autoconf.mk文件的生成
在编译$(OBJS)和$(LIBS)时均会依赖include/autoconf.mk,因为是否编译某个源文件需要autoconf.mk的控制
而生成autoconf.mk的方式则是使用define2mk.sed脚本解析include/config.h(该头文件中就是包含板级配置头文件include/x210_sd.h),这与之前uboot编译原理引入部分的分析是一致的。
5. uboot链接器脚本分析
说明1:链接器脚本的链接起始地址指定为0x00000000,这是因为在使用链接器脚本的同时使用-Ttext选项指定了代码段链接地址,而-Ttext指定的优先级高于链接器脚本。
因此在实现中,使用链接器脚本确定ELF文件布局,使用-Ttext选项确定链接地址
说明2:由于要截取uboot.bin的前8KB作为BL1,因此必须把代码重定位可能用到的内容链接到代码段最前端(e.g. x210实现代码重定位的文件就是cpu/s5pc11x/movi.c)
说明3:注意uboot中的2个自定义段
① .u_boot_cmd段:
uboot命令的描述结构体被链接到该段,使用__u_boot_cmd_start和__u_boot_cmd_end可以实现对所有uboot命令结构体的遍历
② .mmudata段:
uboot使用的Translation Table会被链接到.mmudata段
6. 向uboot中添加功能模块
1)添加uboot中自带功能模块
说明:以添加dm9000网卡驱动为例
A. 在板级配置头文件中添加配置项
在include/configs/x210_sd.h中添加宏定义#define CONFIG_DRIVER_DM9000
至于如何知道这个配置项的名称,可以查看driver/net目录下的Makefile文件,配置头文件中的配置项名和Makefile中必须一致。
B. 在源码中引用dm9000.c的相关函数
#ifdef CONFIC_DRIVER_DM9000
dm9000_initialize();
#endif
C. 重新编译uboot
顶层Makefile会根据板级配置头文件生成autoconf.mk给各Makefile包含使用
说明:概括来说,添加一个已有模块的方法就是修改板级配置头文件,然后在make的过程中会自动生成include/autoconf.mk文件供Makefile包含以决定需要编译哪些模块。
2)添加自定义功能模块
说明:以添加farsight_net.c驱动为例
A. 添加功能模块代码
在相应驱动目录下(e.g. drivers/net)添加自定义的功能模块代码
B. 修改子Makefile
修改drivers/net目录下的子Makefile,添加相应驱动模块的编译项
C. 修改板级配置头文件
修改板级配置头文件include/configs/x210_sd.h,定义宏开关
D. 重新编译uboot
执行到此步,我们可以生成autoconf.mk文件以验证该模块是否已被编译
可见该模块已被编译,接下来就可以在我们需要的源文件中调用farsight_net.c驱动中提供的函数。
说明:其实添加自已编写的驱动模块和添加U-boot中已有的驱动模块最大的区别就是要提供相应的驱动代码。其余修改板级配置头文件(添加自己的驱动还要修改相应驱动目录下的Makefile)并make生成autoconf.mk的过程都是一致的。