1、在阅读 uboot 源码之前,肯定是要先看一下顶层 Makefile,分析 gcc 版本代码的时候一定是
先从
顶层 Makefile
开始的,然后再是
子 Makefile
,这样通过
层层分析 Makefile
即可了解整个工
程的
组织结构
。
版本号
MAKEFLAGS
变量
make 是支持递归调用的,也就是
在Makefile中使用“make”命令来执行其他的 Makefile
文件,一般都是子目录中的 Makefile 文件。假如在当前目录下存在一个“subdir”子目录,这个
子目录中又有其对应的 Makefile 文件,那么这个工程在编译的时候其主目录中的 Makefile 就可
以调用子目录中的 Makefile,以此来完成所有子目录的编译
。
$(MAKE) -C subdir
一般大项目里面所有的源代码都不会放 到同一个目录中,各个功能模块的源代码都是分开的,各自存放在各自的目录中。
主目录的
Makefile
可以使用如下 代码来编译这个子目录:
$(MAKE)
就是调用“
make
”命令,
-C
指定子目录。有时候我们需要向子
make
传递变量,
这个时候使用“
export
”来导出要传递给子
make
的变量即可,如果不希望哪个变量传递给子
make
的话就使用“
unexport
”来声明不导出
MAKEFLAGS
+= -
rR
--
include
-
dir
=
$
(
CURDIR
)
make 过程
配置好
uboot
以后就可以直接
make
编译了,因为没有指明目标,所以会使用默认目标,主
Makefile
中的默认目标如下:
uboot.bin -> uboot.nodtb.bin->u-boot-> u-boot_init u-boot-main u-boot_init
u-boot-init := $(head-y)
head-y := arch/arm/cpu/$(CPU)/start.o
在顶层
Makefile
中被定义为
uboot
所有子目录下
build-in.o
的集合.
libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
相当于将 libs-y 改为所有子目录中 built-in.o 文件.的集合。那么 u
boot-main 就等于所有子目录中 built-in.o 的集合.
终极总结:
这个规则就相当于将以 u-boot.lds 为链接脚本,将 arch/arm/cpu/armv7/start.o 和各个子目录 下的 built-in.o 链接在一起生成 u-boot
。
于
uboot
的顶层
Makefile
就分析到这里,重
点是“
make xxx_defconfig
”和“
make
”这两个命令的执行流程:
make xxx_defconfig
:
用于配置
uboot
,这个命令最主要的目的就是生成
.config
文件。
make
:用于编译
uboot
,这个命令的主要工作就是生成二进制的
u-boot.bin
文件和其他的一
些与
uboot
有关的文件,比如
u-boot.imx
等等。
_main
中会调用
board_init_f
函数,
board_init_f
函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化
gd
的各个成员变量,
uboot
会将自己重定位到
DRAM
最后面的地址区域,也就
是将自己拷贝到
DRAM
最后面的内存区域中。这么做的目的是给
Linux
腾出空间,防止
Linux
kernel
覆盖掉
uboot
,将
DRAM
前面的区域完整的空出来。在拷贝之前肯定要给
uboot
各部分
分配好内存位置和大小,比如
gd
应该存放到哪个位置,
malloc
内存池应该存放到哪个位置等
等。这些信息都保存在
gd
的成员变量中,因此要对
gd
的这些成员变量做初始化。最终形成一
个完整的内存“分配图”,在后面重定位
uboot
的时候就会用到这个内存“分配图”。
board_f.c
common/board_f.c 中 的
init_sequence_f
第 2 行,setup_mon_len 函数设置 gd 的 mon_len 成员变量,此处为__bss_end -_start,也就
是整个代码的长度。0X878A8E74-0x87800000=0XA8E74,这个就是代码长度。
第 3 行,initf_malloc 函数初始化 gd 中跟 malloc 有关的成员变量,比如 malloc_limit,此函
数会设置 gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400。malloc_limit 表示 malloc
内存池大小。
第
2
行,
initr_trace
函数,如果定义了宏
CONFIG_TRACE
的话就会调用函数
trace_init
,
初始化和调试跟踪有关的内容。
第
3
行,
initr_reloc
函数用于设置
gd->flags
,标记重定位完成。
第
4
行,
initr_caches
函数用于初始化
cache
,使能
cache
。
第
5
行,
initr_reloc_global_data
函数,初始化重定位后
gd
的一些成员变量。
第
6
行,
initr_barrier
函数,
I.MX6ULL
未用到。
第
7
行,
initr_malloc
函数,初始化
malloc
。
第
8
行,
initr_console_record
函数,初始化控制台相关的内容,
I.MX6ULL
未用到,空函数。
第
9
行,
bootstage_relocate
函数,启动状态重定位。
第
10
行,
initr_bootstage
函数,初始化
bootstage
什么的。
第
11
行,
board_init
函数,板级初始化,包括
74XX
芯片,
I2C
、
FEC
、
USB
和
QSPI
等。
这里执行的是
mx6ull_alientek_emmc.c
文件中的
board_init
函数。
第
12
行,
stdio_init_tables
函数,
stdio
相关初始化。
第 13 行,initr_serial 函数,初始化串口。
第
14
行,
initr_announce
函数,与调试有关,通知已经在
RAM
中运行。
第
18
行,
power_init_board
函数,初始化电源芯片,正点原子的
I.MX6ULL
开发板没有用
到。
第
19
行,
initr_flash
函数,对于
I.MX6ULL
而言,没有定义宏
CONFIG_SYS_NO_FLASH
的话函数
initr_flash
才有效。但是
mx6_common.h
中定义了宏
CONFIG_SYS_NO_FLASH
,所以
此函数无效。
第
21
行,
initr_nand
函数,初始化
NAND
,如果使用
NAND
版本核心板的话就会初始化
NAND
。
第
22
行,
initr_mmc
函数,初始化
EMMC
,如果使用
EMMC
版本核心板的话就会初始化
EMMC
,串口输出如图
32.2.8.1
所示信息:
图
32.2.8.1 EMMC
信息输出
从图
32.2.8.1
可以看出,此时有两个
EMCM
设备,
FSL_SDHC:0
和
FSL_SDHC:1
。
第
23
行,
initr_env
函数,初始化环境变量。
第
25
行,
initr_secondary_cpu
函数,初始化其他
CPU
核,
I.MX6ULL
只有一个核,因此此
函数没用。
第 27 行,stdio_add_devices 函数,各种输入输出设备的初始化,如 LCD driver,I.MX6ULL
使用 drv_video_init 函数初始化 LCD。会输出如图 32.2.8.2 所示信息:
图 32.2.8.2 LCD 信息
第
28
行,
initr_jumptable
函数,初始化跳转表。
第
29
行 ,
console_init_r
函 数 , 控 制 台 初 始 化 , 初 始 化 完 成 以 后 此 函 数 会 调 用
stdio_print_current_devices
函数来打印出当前的控制台设备,如图
32.2.8.3
所示:
图
32.2.8.3
控制台信息
第 31 行,interrupt_init 函数,初始化中断。
第 32 行,initr_enable_interrupts 函数,使能中断。
第 33 行,initr_ethaddr 函数,初始化网络地址,也就是获取 MAC 地址。读取环境变量
“ethaddr”的值。
第
34
行,
board_late_init
函数,板子后续初始化,此函数定义在文件
mx6ull_alientek_emmc.c
中,如果环境变量存储在
EMMC
或者
SD
卡中的话此函数会调用
board_late_mmc_env_init
函数
初始化
EMMC/SD
。会切换到正在时候用的
emmc
设备,代码如图
32.2.8.4
所示: