一、U-boot工程目錄分析
??如果要分析uboot源碼,首先要將uboot源碼進行編譯,編譯需要在Ubuntu進行,把uboot文件放在一個目錄下。
編譯完成后的文件是這樣:
我們需要看的文件夾如下。
1. arch 文件夾
??從上圖可以看出有很多架構,比如 arm、 m68k、 x86 等,我們現在用的是 ARM 芯片,所以只需要關心 arm 文件夾即可。
??mach 開頭的文件夾是跟具體的設備有關的,比如“mach-exynos”就是跟三星的 exyons 系列 CPU 有關的文件。我們使用的是 STM32MP1,所以要關注“mach-stm32mp”這個文件夾。另外“cpu”這個文件夾也是和 cpu架構有關的。
??可以看出有多種 ARM 架構相關的文件夾, STM32MP1 使用的 Cortex-A7 內核,Cortex-A7 屬于 armv7,所以我們要關心“armv7”這個文件夾。 cpu 文件夾里面有個名為“uboot.lds”的鏈接腳本文件,這個就是 ARM 芯片所使用的 u-boot 鏈接腳本文件! armv7 這個文件夾里面的文件都是跟 ARMV7 架構有關的,是我們分析 uboot 啟動源碼的時候需要重點關注。?
2. board 文件夾
??board 文件夾就是和具體的板子有關的。打開board/st/stm32mp1,這個文件夾就是針對STM32MP1系列芯片對應的板子。
3. configs 文件夾
??此文件夾為 uboot 配置文件夾, uboot 是可配置的,但是你要是自己從頭開始一個一個項目的配置,那就太麻煩了,因此一般半導體或者開發板廠商都會制作好一個配置文件。我們可以在這個做好的配置文件基礎上來添加自己想要的功能,這些半導體廠商或者開發板廠商制作好的配置文件統一命名為“xxx_defconfig”, xxx 表示開發板名字,這些 defconfig 文件都存放在configs 文件夾。
圖中的框就是STM32MP157開發板對應的配置文件。
4. ?.u-boot.xxx_cmd?文件
??.u-boot.xxx_cmd 是一系列的文件,這些文件都是編譯生成的,都是一些命令文件,比如文件.u-boot.bin.cmd,看名字應該是和 u-boot.bin 有關的,此文件的內容如下:?
cmd_u-boot.bin := cp u-boot-dtb.bin u-boot.bin
??.u-boot.bin.cmd 里面定義了一個變量: cmd_u-boot.bin,此變量的值為“cp u-boot-dtb.bin uboot.bin”,也就是拷貝一份 u-boot-dtb.bin 文件,并且重命名為 u-boot.bin,這個就是 u-boot.bin的來源,來自于文件 u-boot-dtb.bin。?
??那么u-boot-dtb.bin是怎么來的呢?文件.u-boot-dtb.bin.cmd就是用于生成u-boot.dtb.bin的,此文件內容如下:?
cmd_u-boot-dtb.bin := cat u-boot-nodtb.bin dts/dt.dtb > u-boot-dtb.bin
??cmd_u-boot-dtb.bin 用于將設備樹.dtb 文件和 uboot 的 bin 文件結合起來, STM32MP157 的uboot 用到了設備樹(Device Tree),因此最終生成的 uboot bin 文件里面要有.dtb 內容。 u-boot-nodtb.bin 就是原始的、不含 dtb 的 uboot bin 文件。?
???u-boot-nodtb.bin 是用.u-boot-nodtb.bin.cmd 文件生成的,內容如下:
cmd_u-boot-nodtb.bin := arm-none-linux-gnueabihf-objcopy --gap-fill=0xff -j .text -j .secure_text -j .secure_data -j .rodata -j .hash -j .data -j .got -j .got.plt -j .u_boot_list -j .rel.dyn -j .binman_sym_table -j .text_rest -j .dtb.init.rodata -j .efi_runtime -j .efi_runtime_rel -O binary u-boot u-boot-nodtb.bin
??這里用到了 arm-none-linux-gnueabihf-objcopy,使用 objcopy 將 ELF 格式的 u-boot 文件轉換為二進制的 u-boot-nodtb.bin 文件。?
??文件 u-boot 是 ELF 格式的文件,文件.u-boot.cmd 用于生成 u-boot,文件內容如下:?
cmd_u-boot := arm-none-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic --no-dynamic-linker -Ttext 0xC0100000 -o u-boot -T u-boot.lds arch/arm/cpu/armv7/start.o --start-group arch/arm/cpu/built-in.o arch/arm/cpu/armv7/built-in.o arch/arm/lib/built-in.o arch/arm/mach-stm32mp/built-in.o board/st/common/built-in.o board/st/stm32mp1/built-in.o cmd/built-in.o common/built-in.o disk/built-in.o drivers/built-in.o drivers/dma/built-in.o drivers/gpio/built-in.o drivers/i2c/built-in.o drivers/net/built-in.o drivers/net/phy/built-in.o drivers/power/built-in.o drivers/power/battery/built-in.o drivers/power/domain/built-in.o drivers/power/fuel_gauge/built-in.o drivers/power/mfd/built-in.o drivers/power/pmic/built-in.o drivers/power/regulator/built-in.o drivers/serial/built-in.o drivers/spi/built-in.o drivers/usb/cdns3/built-in.o drivers/usb/common/built-in.o drivers/usb/dwc3/built-in.o drivers/usb/emul/built-in.o drivers/usb/eth/built-in.o drivers/usb/gadget/built-in.o drivers/usb/gadget/udc/built-in.o drivers/usb/host/built-in.o drivers/usb/musb-new/built-in.o drivers/usb/musb/built-in.o drivers/usb/phy/built-in.o drivers/usb/ulpi/built-in.o env/built-in.o fs/built-in.o lib/built-in.o net/built-in.o --end-group arch/arm/lib/eabi_compat.o arch/arm/lib/lib.a -Map u-boot.map; true
??u-boot.cmd 使用到了 arm-none-linux-gnueabihf-ld.bfd,也就是鏈接工具,使用 ld.bfd 將各個built-in.o 文件鏈接在一起就形成了 u-boot 文件。 uboot 在編譯的時候會將同一個目錄中的所有.c文件都編譯在一起,并命名為 built-in.o,相當于將眾多的.c 文件對應的.o 文件集合在一起,這個就是 u-boot 文件的來源。?
??如果我們要向 STM32MP157 內部燒寫 uboot,此時燒寫的是 u-boot.stm32 文件,而不是 uboot.bin 文件。 u-boot.stm32 是由文件.u-boot.stm32.cmd 來完成的,此文件內容如下:?
cmd_u-boot.stm32 := ./tools/mkimage -T stm32image -a 0xC0100000 -e 0xC0100000 -d u-boot.bin u-boot.stm32 >u-boot.stm32.log && cat u-boot.stm32.log
??可以看出,這里用到了工具 tools/mkimage,和 stm32image,通過這兩個工具將 u-boot.bin轉換為 u-boot.stm32。?
??文件.u-boot.lds.cmd 就是用于生成 u-boot.lds 鏈接腳本,uboot 根目錄下的 u-boot.lds 鏈接腳本就是來源于 arch/arm/cpu/u-boot.lds文件。?
5. ?Makefile?文件
??這個是頂層 Makefile 文件, Makefile 是支持嵌套的,也就是頂層 Makefile 可以調用子目錄中的 Makefile 文件。 Makefile 嵌套在大項目中很常見,一般大項目里面所有的源代碼都不會放到同一個目錄中,各個功能模塊的源代碼都是分開的,各自存放在各自的目錄中。每個功能模塊目錄下都有一個 Makefile,這個 Makefile 只處理本模塊的編譯鏈接工作,這樣所有的編譯鏈接工作就不用全部放到一個 Makefile 中,可以使得 Makefile 變得簡潔明。
??uboot 源碼根目錄下的 Makefile 是頂層 Makefile,他會調用其它的模塊的 Makefile 文件,比如 drivers/adc/Makefile。?
6. ? u-boot.xxx 文件
??u-boot.xxx 同樣也是一系列文件,包括 u-boot、 u-boot-dtb.bin、 u-boot-nodtb.bin、 u-boot.bin、u-boot.cfg、 u-boot.dtb、 u-boot.lds、 u-boot.map、 u-boot.srec、 u-boot.stm32 和 u-boot.sym,這些文件的含義如下:?
u-boot:編譯出來的 ELF 格式的 uboot 鏡像文件。
u-boot-dtb.bin:編譯出來的含有設備樹.dtb 的 uboot 鏡像文件。
u-boot-nodtb.bin:編譯出來的不含有設備樹.dtb 的 uboot 鏡像文件,和 u-boot.bin 一樣。
u-boot.bin:編譯出來的二進制格式的 uboot 可執行鏡像文件。
u-boot.cfg: uboot 的另外一種配置文件。
u-boot.dtb: uboot 設備樹編譯后的.dtb 文件。
u-boot.lds:鏈接腳本。
u-boot.map: uboot 映射文件,通過查看此文件可以知道某個函數被鏈接到了哪個地址上。
u-boot.srec: S-Record 格式的鏡像文件。
u-boot.stm32: 最終要寫到 STM32MP157 的uboot 文件。
u-boot.sym: uboot 符號文件。
7. ? .config?文件
??uboot 配置文件,使用命令“make xxx_defconfig”配置 uboot 以后就會自動生成, .config 內容(部分)如下:?
#
# Automatically generated file; DO NOT EDIT.
# U-Boot 2020.01-stm32mp-r1 Configuration
#
CONFIG_CREATE_ARCH_SYMLINK=y
# CONFIG_ARC is not set
CONFIG_ARM=y
# CONFIG_M68K is not set
# CONFIG_MICROBLAZE is not set
# CONFIG_MIPS is not set
# CONFIG_NDS32 is not set
# CONFIG_NIOS2 is not set
# CONFIG_PPC is not set
# CONFIG_RISCV is not set
# CONFIG_SANDBOX is not set
# CONFIG_SH is not set
# CONFIG_X86 is not set
# CONFIG_XTENSA is not set
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="stm32mp"
CONFIG_SYS_VENDOR="st"
CONFIG_SYS_BOARD="stm32mp1"
CONFIG_SYS_CONFIG_NAME="stm32mp1"
# CONFIG_SYS_ICACHE_OFF is not set
# CONFIG_SYS_DCACHE_OFF is not set#
# ARM architecture
#
CONFIG_HAS_VBAR=y
CONFIG_HAS_THUMB2=y
CONFIG_ARM_ASM_UNIFIED=y
CONFIG_SYS_ARM_CACHE_CP15=y
CONFIG_SYS_ARM_MMU=y
# CONFIG_SYS_ARM_MPU is not set
CONFIG_CPU_V7A=y
CONFIG_SYS_ARM_ARCH=7
CONFIG_SYS_CACHE_SHIFT_6=y
CONFIG_SYS_CACHELINE_SIZE=64
??.config 文件中都是以“CONFIG_”開始的配置項,這些配置項就是 Makefile 中的變量,因此后面都跟有相應的值, uboot 的頂層 Makefile 或子 Makefile 會調用這些變量值。在.config 中會有大量的變量值為‘y’,這些為‘y’的變量一般用于控制某項功能是否使能,為‘y’的話就表示功能使能,比如:?
CONFIG_CMD_BOOTM=y
??如果使能了 bootd 這個命令的話, CONFIG_CMD_BOOTM 就為‘y’。在 cmd/Makefile 中有如下代碼:?
# 這個需要在Ubuntu中用vim打開或者用Vscode打開
ifndef CONFIG_SPL_BUILD
# core command
obj-y += boot.o
obj-$(CONFIG_CMD_BOOTM) += bootm.o
obj-y += help.o
obj-y += version.o......
CFLAGS_ethsw.o := -Wno-enum-conversion // 這個是在226行
obj-$(CONFIG_CMD_BOOTM) += bootm.o
CONFIG_CMD_BOOTM=y,將其展開就是:
obj-y += bootm.o
??也就是給 obj-y 追加了一個“bootm.o”, obj-y 包含著所有要編譯的文件對應的.o 文件,這里表示需要編譯文件 cmd/bootm.c。相當于通過“CONFIG_CMD_BOOTD=y”來使能 bootm 這個命令,進而編譯 cmd/bootm.c 這個文件,這個文件實現了命令 bootm。在 uboot 和 Linux 內核中都是采用這種方法來選擇使能某個功能,編譯對應的源碼文件。?
二、U-boot 頂層 Makefile 分析?
??在閱讀 uboot 源碼之前,肯定是要先看一下頂層 Makefile,分析 gcc 版本代碼的時候一定是先從頂層 Makefile 開始的,然后再是子 Makefile,這樣通過層層分析 Makefile 即可了解整個工程的組織結構。?
1. 版本號
??用VScode打開頂層Makefile:
2. ?MAKEFLAGS 變量
??make 是支持遞歸調用的,也就是在 Makefile 中使用“make”命令來執行其他的 Makefile? 文件,一般都是子目錄中的 Makefile 文件。假如在當前目錄下存在一個“subdir”子目錄,這個子目錄中又有其對應的 Makefile 文件,那么這個工程在編譯的時候其主目錄中的 Makefile 就可以調用子目錄中的 Makefile,以此來完成所有子目錄的編譯。主目錄的 Makefile 可以使用如下代碼來編譯這個子目錄:?
$(MAKE) -C subdir
??$(MAKE)就是調用“make”命令, -C 指定子目錄。有時候我們需要向子 make 傳遞變量,這個時候使用“export”來導出要傳遞給子 make 的變量即可,如果不希望哪個變量傳遞給子make 的話就使用“unexport”來聲明不導出:?
export VARIABLE …… // 導出變量給子 make 。
unexport VARIABLE…… // 不導出變量給子 make。
??有兩個特殊的變量:“SHELL”和“MAKEFLAGS”,這兩個變量除非使用“unexport”聲明,否則的話在整個make的執行過程中,它們的值始終自動的傳遞給子make。在uboot的主Makefile中有如下代碼:
MAKEFLAGS += -rR --include-dir=$(CURDIR)
??上述代碼使用“+=”來給變量 MAKEFLAGS 追加了一些值,“-rR”表示禁止使用內置的隱含規則和變量定義,“--include-dir”指明搜索路徑,“$(CURDIR)”表示當前目錄。
3.? 命令輸出
??uboot 默認編譯是不會在終端中顯示完整的命令,都是短命令:
??在終端中輸出短命令雖然看起來很清爽,但是不利于分析 uboot 的編譯過程。可以通過設置變量“V=1“來實現完整的命令輸出,這個在調試 uboot 的時候很有用:
頂層Makefile控制命令輸出代碼:
ifeq ("$(origin V)", "command line") # 判斷$(origin V)和command line是否相等,這里用到了Makefile中的origin函數# 這里我的理解是origin V是代表V的來源,command line代表命令行定義,如果在命令行輸入了V=1那么就說明V的來源是command line,與之相等,那么變量KBUILD_VERBOSE就會等于1KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSEKBUILD_VERBOSE = 0
endififeq ($(KBUILD_VERBOSE),1) # 判斷 KBUILD_VERBOSE 是否為1,如果是變量quite和Q為空,反之,變為quite_和@quiet =Q =
elsequiet=quiet_Q = @
endif
這里用到了Makefile中的origin函數,語法為:
$(origin <variable>)
??variable 是變量名, origin 函數的返回值就是變量來源,因此$(origin V)就是變量 V 的來源。如果變量 V 是在命令行定義的那么它的來源就是"command line",這樣"$(origin V)"和"commandline"就相等了。當這兩個相等的時候變量 KBUILD_VERBOSE 就等于 V 的值,比如在命令行中輸 入 “ V=1 “ 的 話 那 么 KBUILD_VERBOSE=1 。如果沒有在命令行輸入V的話KBUILD_VERBOSE=0。?
??如果 V=1的話:
quiet =
Q =
# V=0 或者命令行不定義 V 的話:
KBUILD_VERBOSE=0
quiet = quiet_
Q = @
??Makefile 中會用變量 quiet 和 Q 來控制編譯的時候是否在終端輸出完整的命令,在頂層Makefile 中有很多如下所示的命令:?
$(Q)$(MAKE) $(build)=tools
??如果 V=0 的話上述命令展開就是“@ make $(build)=tools”, make 在執行的時候默認會在終端輸出命令,但是在命令前面加上“@”就不會在終端輸出命令了。當 V=1 的時候 Q 就為空,上述命令就是“make $(build)=tools”,因此在 make 執行的過程,命令會被完整的輸出在終端上。?
# 有些命令會有兩個版本,比如:
quiet_cmd_sym ?= SYM $@
cmd_sym ?= $(OBJDUMP) -t $< > $@
??sym 命令分為“quiet_cmd_sym”和“cmd_sym”兩個版本,這兩個命令的功能都是一樣的,區別在于 make 執行的時候輸出的命令不同。 quiet_cmd_xxx 命令輸出信息少,也就是短命令,而 cmd_xxx 命令輸出信息多,也就是完整的命令。
??如果變量 quiet 為空的話,整個命令都會輸出。
??如果變量 quiet 為“quiet_”的話,僅輸出短版本。
??如果變量 quiet 為“silent_”的話,整個命令都不會輸出。?
4.? 靜默輸出(make -s)
??如果設置 V=0 或者在命令行中不定義 V 的話,編譯 uboot 的時候終端中顯示的短命令,但是還是會有命令輸出,有時候我們在編譯 uboot 的時候不需要輸出命令,這個時候就可以使用 uboot 的靜默輸出功能。編譯的時候使用“make -s”即可實現靜默輸出,頂層 Makefile中相應的代碼如下:?
# If the user is running make -s (silent mode), suppress echoing of
# commandsifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4 # 判斷當前編譯器版本是是否為4.x,去找MAKE_VERSION里面的符合4.的字符
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)quiet=silent_
endif
else # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)quiet=silent_
endif
endifexport quiet Q KBUILD_VERBOSE # 使用 export 導出變量 quiet、 Q 和 KBUILD_VERBOSE。
??這里用到了 Makefile 中的 filter函數,這是個過濾函數,函數格式如下:?
$(filter <pattern...>,<text>)
??filter 函數表示以 pattern 模式過濾 text 字符串中的單詞,僅保留符合模式 pattern 的單詞,可以有多個模式。函數返回值就是符合 pattern 的字符串。因此$(filter 4.%,$(MAKE_VERSION))的含義就是在字符串“ MAKE_VERSION”中找出符合“ 4.%”的字符(%為通配符),MAKE_VERSION 是make工具的版本號。因此$(filter 4.%,$(MAKE_VERSION))不為空,條件成立。
??如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不為空的話條件成立,變量 quiet 等于“silent_”。這里也用到了函數 filter,在$(firstword x$(MAKEFLAGS)))中過濾出符合“%s”的單詞。到了函數 firstword,函數 firstword 是獲取首單詞,函數格式如下:?
$(firstword <text>)
??firstword 函數用于取出 text 字符串中的第一個單詞,函數的返回值就是獲取到的單詞。當使用“make -s”編譯的時候,“-s”會作為 MAKEFLAGS 變量的一部分傳遞給 Makefile。? 當使用“make -s”編譯的時候,“-s”會作為 MAKEFLAGS 變量的一部分傳遞給 Makefile。? 在頂層 Makfile 添加如下代碼。
??框中的兩行代碼用于輸出$(firstword x$(MAKEFLAGS))的結果,我們用以下命令去測試靜脈輸出:?
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- -s
??可以看出第一個單詞是“xrRs”,將$(filter %s ,$(firstword x$(MAKEFLAGS)))展開就是$(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不為空,條件成立, quiet=silent_。
5. ?設置編譯結果輸出目錄?
??uboot 可以將編譯出來的目標文件輸出到單獨的目錄中,在 make 的時候使用“O”來指定輸出目錄,比如“make O=out”就是設置目標文件輸出到 out 目錄中。這么做是為了將源文件和編譯產生的文件分開,當然也可以不指定 O 參數,不指定的話源文件和編譯產生的文件都在同一個目錄內,一般我們不指定 O 參數。頂層 Makefile 中相關的代碼如下:?
# kbuild supports saving output files in a separate directory.
# To locate output files in a separate directory two syntaxes are supported.
# In both cases the working directory must be the root of the kernel src.
# 1) O=
# Use "make O=dir/to/store/output/files/"
#
# 2) Set KBUILD_OUTPUT
# Set the environment variable KBUILD_OUTPUT to point to the directory
# where the output files shall be placed.
# export KBUILD_OUTPUT=dir/to/store/output/files/
# make
#
# The O= assignment takes precedence over the KBUILD_OUTPUT environment
# variable.# KBUILD_SRC is set on invocation of make in OBJ directory
# KBUILD_SRC is not intended to be used by the regular user (for now)
ifeq ($(KBUILD_SRC),)# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
ifeq ("$(origin O)", "command line") # 判斷'O'是否來自于命令行,如果來自命令行的話條件成立, KBUILD_OUTPUT就為$(O),因此變量 KBUILD_OUTPUT 就是輸出目錄。KBUILD_OUTPUT := $(O)
endif# That's our default target when none is given on the command line
PHONY := _all
_all:# Cancel implicit rules on top Makefile
$(CURDIR)/Makefile Makefile: ;ifneq ($(KBUILD_OUTPUT),) # 判斷 KBUILD_OUTPUT 是否為空
# Invoke a second make in the output directory, passing relevant variables
# check that the output directory actually exists
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \ # 調用 mkdir 命令,創建 KBUILD_OUTPUT 目錄,并且將創建成功以后的絕對路徑賦值給 KBUILD_OUTPUT。至此,通過 O 指定的輸出目錄就存在了。&& /bin/pwd)
......
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)
6. ?代碼檢查
??uboot 支持代碼檢查,使用命令“make C=1”使能代碼檢查,檢查那些需要重新編譯的文件。“make C=2”用于檢查所有的源碼文件,頂層 Makefile 中的代碼如下:?
# Call a source code checker (by default, "sparse") as part of the
# C compilation.
#
# Use 'make C=1' to enable checking of only re-compiled files.
# Use 'make C=2' to enable checking of *all* source files, regardless
# of whether they are re-compiled or not.
#
# See the file "doc/sparse.txt" for more details, including
# where to get the "sparse" utility.ifeq ("$(origin C)", "command line") # 判斷 C 是否來源于命令行,如果 C 來源于命令行,那就將 C 賦值給變量KBUILD_CHECKSRC,如果命令行沒有 C 的話 KBUILD_CHECKSRC 就為 0。KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRCKBUILD_CHECKSRC = 0
endif
7. ?模塊編譯?
??在 uboot 中允許單獨編譯某個模塊,使用命令“ make M=dir”即可。頂層 Makefile 中的代碼如下:?
# Use make M=dir to specify directory of external module to build
# Old syntax make ... SUBDIRS=$PWD is still supported
# Setting the environment variable KBUILD_EXTMOD take precedence
ifdef SUBDIRS # 判斷是否定義了SUBDIRS,如果定義了,那就KBUILD_EXTMOD=SUBDIRSKBUILD_EXTMOD ?= $(SUBDIRS)
endififeq ("$(origin M)", "command line") # 判斷是否在命令行定義了 M,如果定義了的話 KBUILD_EXTMOD=$(M)。KBUILD_EXTMOD := $(M)
endif# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),) # 判斷 KBUILD_EXTMOD 是否為空,如果為空的話目標_all 依賴 all,因此要先編譯出 all。否則的話默認目標_all 依賴 modules,要先編譯出 modules,也就是編譯模塊。一般情況下不會在 uboot 中編譯模塊,所以此處會編譯 all 這個目標
_all: all
else
_all: modules
endififeq ($(KBUILD_SRC),) # 判斷 KBUILD_SRC 是否為空,如果為空的話就設置變量 srctree 為當前目錄,即srctree 為“.”,一般不設置 KBUILD_SRC。# building in the source treesrctree := .
elseifeq ($(KBUILD_SRC)/,$(dir $(CURDIR))) # 檢查 KBUILD_SRC 加上斜杠 / 是否與當前目錄 $(CURDIR) 的父目錄相同# building in a subdirectory of the source treesrctree := .. # 表示將 srctree 變量設置為當前目錄的父目錄 ..elsesrctree := $(KBUILD_SRC) # 表示將 srctree 變量設置為 KBUILD_SRC 變量的值endif
endif
objtree := . # 設置變量 objtree 為當前目錄
src := $(srctree) # 分別設置變量 src 和 obj,都為當前目錄
obj := $(objtree)VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD)) # 將變量 src 設置為變量 srctree 的值export srctree objtree VPATH # 導出變量 scrtree、 objtree 和 VPATH。
8. ?獲取主機架構和系統
??接下來頂層 Makefile 會獲取主機架構和系統,也就是我們電腦的架構和系統,代碼如下:?
HOSTARCH := $(shell uname -m | \ # 定義了一個變量 HOSTARCH,用于保存主機架構sed -e s/i.86/x86/ \ # sed -e 是替換命令,“sed -e s/i.86/x86/”表示將管道輸入的字符串中的“i.86”替換為“x86”,其他的“sed -e s”命令同理-e s/sun4u/sparc64/ \ # uname -m 命令用于獲取主機的架構類型,然后通過一系列的 sed 替換命令對結果進行處理,將不同的架構類型轉換為統一的名稱。最終將處理后的值賦給 HOSTARCH 變量-e s/arm.*/arm/ \-e s/sa110/arm/ \-e s/ppc64/powerpc/ \-e s/ppc/powerpc/ \-e s/macppc/powerpc/\-e s/sh.*/sh/)HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ sed -e 's/\(cygwin\).*/cygwin/') # shell 中的'|'表示管道,意思是將左邊的輸出作為右邊的輸入# uname -s 命令用于獲取主機的操作系統類型,然后通過 tr 命令將操作系統類型的字母轉換為小寫,再通過一系列的 sed 替換命令對結果進行處理,將不同的操作系統類型轉換為統一的名稱。最終將處理后的值賦給 HOSTOS 變量。
export HOSTARCH HOSTOS # export 用于將變量導出,使其在子進程的環境中可見
??這里調用 shell 命令“uname -m”獲取架構名稱:
??使用 shell 命令“uname-s”來獲取主機 OS:
9. ?設置目標架構、交叉編譯器和配置文件?
??編 譯 uboot的時候需要設置目標板架構和交叉編譯器,“ make ARCH=armCROSS_COMPILE=arm-none-linux-gnueabihf-”就是用于設置 ARCH 和 CROSS_COMPILE,在 頂層 Makefile 中代碼如下:?
# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH)) # 判斷 HOSTARCH 和 ARCH 這兩個變量是否相等,主機架構(變量 HOSTARCH)是x86_64,而我們編譯的是 ARM 版本 uboot,肯定不相等
CROSS_COMPILE ?=
endifKCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
??每次編譯 uboot 的時候都要在 make 命令后面設置 ARCH 和 CROSS_COMPILE,使用起來很麻煩,可以直接修改頂層 Makefile,在里面加入ARCH 和 CROSS_COMPILE 的定義。
??直接在頂層 Makefile 里面定義 ARCH 和 CROSS_COMPILE,這樣就不用每次編譯的時候都要在 make 命令后面定義 ARCH 和 CROSS_COMPILE。?
10.? 導出其他變量
export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
export MAKE LEX YACC AWK PERL PYTHON PYTHON2 PYTHON3
export HOSTCXX HOSTCXXFLAGS CHECK CHECKFLAGS DTC DTC_FLAGSexport KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS KBUILD_AFLAGS
??這 7 個變量在頂層 Makefile 是找不到的,說明這 7 個變量是在其他文件里面定義的,先來看一下這 7 個變量都是什么內容,在頂層 Makefile 中輸入以下內容:
修改完成后在下面的路徑執行如下命令:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- mytest
??可以看到這 7?個變量的值,這 7?個變量是從哪里來的呢?在 uboot 根目錄下有個文件叫做 config.mk,這?7個變量就是在 config.mk 里面定義的,打開 config.mk 內容如下:?
# SPDX-License-Identifier: GPL-2.0+
#
# (C) Copyright 2000-2013
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
########################################################################## This file is included from ./Makefile and spl/Makefile.
# Clean the state to avoid the same flags added twice.
#
# (Tegra needs different flags for SPL.
# That's the reason why this file must be included from spl/Makefile too.
# If we did not have Tegra SoCs, build system would be much simpler...)
PLATFORM_RELFLAGS :=
PLATFORM_CPPFLAGS :=
PLATFORM_LDFLAGS :=
LDFLAGS :=
LDFLAGS_FINAL :=
LDFLAGS_STANDALONE :=
OBJCOPYFLAGS :=
# clear VENDOR for tcsh
VENDOR :=
#########################################################################ARCH := $(CONFIG_SYS_ARCH:"%"=%) # 提取CONFIG_SYS_ARCH 里面雙引號“”之間的內容。比如 CONFIG_SYS_ARCH=“arm”, ARCH=arm
CPU := $(CONFIG_SYS_CPU:"%"=%) # 類似
ifdef CONFIG_SPL_BUILD
ifdef CONFIG_TEGRA
CPU := arm720t
endif
endif
BOARD := $(CONFIG_SYS_BOARD:"%"=%) # 類似
ifneq ($(CONFIG_SYS_VENDOR),)
VENDOR := $(CONFIG_SYS_VENDOR:"%"=%) # 類似
endif
ifneq ($(CONFIG_SYS_SOC),)
SOC := $(CONFIG_SYS_SOC:"%"=%) # 類似
endif# Some architecture config.mk files need to know what CPUDIR is set to,
# so calculate CPUDIR before including ARCH/SOC/CPU config.mk files.
# Check if arch/$ARCH/cpu/$CPU exists, otherwise assume arch/$ARCH/cpu contains
# CPU-specific code.
CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),) # CPUDIR的值是根據ARCH和CPU變量的值構造而成的。如果CPU為空,則CPUDIR為arch/$(ARCH),否則為arch/$(ARCH)/cpu/$(CPU)。這樣可以根據不同的架構和CPU類型,指定相應的CPU相關代碼所在的目錄路徑。sinclude $(srctree)/arch/$(ARCH)/config.mk # include architecture dependend rules # sinclude 和 include 的功能類似,不相同的點在于sinclude文件不存在,程序將不會終止運行,繼續執行接下來的內容。
sinclude $(srctree)/$(CPUDIR)/config.mk # include CPU specific rulesifdef SOC
sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk # include SoC specific rules
endif
ifneq ($(BOARD),)
ifdef VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
endif
ifdef BOARD
sinclude $(srctree)/board/$(BOARDDIR)/config.mk # include board specific rules
endififdef FTRACE
PLATFORM_CPPFLAGS += -finstrument-functions -DFTRACE
endif#########################################################################RELFLAGS := $(PLATFORM_RELFLAGS)PLATFORM_CPPFLAGS += $(RELFLAGS)
PLATFORM_CPPFLAGS += -pipeLDFLAGS += $(PLATFORM_LDFLAGS)
LDFLAGS_FINAL += -Bstaticexport PLATFORM_CPPFLAGS
export RELFLAGS
export LDFLAGS_FINAL
export LDFLAGS_STANDALONE
export CONFIG_STANDALONE_LOAD_ADDR
??接下來需要找到 CONFIG_SYS_ARCH、 CONFIG_SYS_CPU、 CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 這 5 個變量的值。這 5 個變量在 uboot 根目錄下的.config 文件中有定義,定義如下:?
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="stm32mp"
CONFIG_SYS_VENDOR="st"
CONFIG_SYS_BOARD="stm32mp1"
CONFIG_SYS_CONFIG_NAME="stm32mp1"
ARCH = arm
CPU = armv7
BOARD = stm32mp1
VENDOR = st
SOC = stm32mp
CPUDIR = arch/arm/cpu/armv7
BOARDDIR = st/stm32mp1# 因此可以推導出 config.mk 中讀取的文件有:
arch/arm/config.mk
arch/arm/cpu/armv7/config.mk
arch/arm/cpu/armv7/stm32mp/config.mk (此文件不存在)
board/st/stm32mp1/config.mk (此文件不存在)
11. ?make xxx_defconfig 執行流程?
12. ?make 過程
??make xxx_defconfig: 用于配置 uboot,這個命令最主要的目的就是生成.config 文件。
??make:用于編譯 uboot,這個命令的主要工作就是生成二進制的 u-boot.bin 文件和其他的一些與 uboot 有關的文件,比如 u-boot.stm32 等等。