2.1. uboot编译流程分析

u-boot官方在线文档 https://u-boot.readthedocs.io/en/latest/index.html

在分析uboot编译过程之前,必须了解Makefile语法。由于u-boot的Makefile中存在相互调用,这里介绍以下make -f和make -c的区别

  • -c选项:Makefile中的-c是递归调用子目录的Makefile,-c选项后跟目录,表示到子目录下执行子目录的makefile,顶层Makefile的export的变量还有make默认的变量是可以传递给子目录中的Makefile的

  • -f选项:顶层Makefile使用make -f调用子目录中的文件(文件名可以随便,不必一定是Makefile)作为Makefile,顶层export的变量可以传递给底层

注解

在顶层Makefile中使用make -f调用子目录中的文件工作目录仍是顶层目录,即CURDIR变量仍是顶层目录

Makefile的核心是依赖和命令,对于每个目标,首先会检查依赖如果依赖存在则执行命令更新目标,如果依赖不存在,则会以依赖为目标先生成依赖,最后执行命令生成目标

2.1.1. make xxx_defconfig配置分析

2.1.1.1. xxx_defconfig依赖检查更新

在编译uboot时首先执行make xxx_defconfig生成.config文件。uboot根目录中Makefile中有唯一的规则目标

config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

build 在 scripts/Kbuild.include 中定义

build := -f $(srctree)/scripts/Makefile.build obj

所以最后执行的命令就是

make -f scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

scripts_basic依赖

目标xxx_defconfig的生成依赖于scripts_basic,outputmakefile和FORCE,只有scripts_basic需要执行命令

    # Basic helpers built in scripts/
    PHONY += scripts_basic
scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic
    $(Q)rm -f .tmp_quiet_recordmcount
    #展开后,规则如下
    make -f ./scripts/Makefile.build obj=scripts/basic
    #make命令会转到文件scripts/Makefile.build去执行

事实上大量实际目标的编译都是调用scripts/Makefile.build完成的,文件scripts/Makefile.build的开头会根据传入的obj=scripts/basic参数设置src=sripts/basic

# Modified for U-Boot
prefix := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif

kbuild-dir根据src变量(等于obj变量)是绝对路径还是相对路径来确定当前编译的目录,若为绝对路径则该目录即src变量的值,若为相对路径则该变量就是src相对于源码根目录的目录。kbuild-file即 在该目录下查找kbuild文件,若能找到则使用kbuild作为该目录的编译文件,若找不到则使用该目录下的Makefile作为该目录的编译文件,然后将该文件包含进来

# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

这里展开替换后相当于

include ./scripts/basic/Makefile

文件scripts/basic/Makefile中定义了编译在主机上执行的工具fixdep

# fixdep:        Used to generate dependency information during build process

hostprogs-y     := fixdep
always          := $(hostprogs-y)

# fixdep is needed to compile other host programs
$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep

fixdep用于更新每一个生成目标的依赖文件*.cmd。上面定义的这个$(always)在scripts/Makefile.build里会被添加到targets中

targets += $(extra-y) $(MAKECMDGOALS) $(always)

简而言之,scripts_basic的规则是

scripts_basic:
        make -f ./scripts/Makefile.build obj=scripts/basic

最终的结果就是编译scripts/basic/fixdep.c 生成主机上的可执行文件fixdep

outputmakefile依赖

PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
    $(Q)ln -fsn $(srctree) source
    $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
        $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

如果此时执行 make xxx_defconfig O=out,那么所有生成的目标都将放到out目录,outputmakefile会导出一个makefile到out目录进行编译

FORCE依赖

PHONY += FORCE
FORCE:

FORCE被定义为一个空目标,如果一个目标添加FORCE依赖每次编译都会西安去执行FORCE(实际上什么都不做),然后运行命令更新目标,这样就能确保目标每次都被更新

2.1.1.2. xxx_defconfig目标命令执行

完成对xxx_defconfig的依赖更新后,接下来就是执行对顶层目标的命令完成对xxx_defconfig的更新,也就是执行以下命令

xxx_defconfig: scripts_basic outputmakefile FORCE
    make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

这个命令会转到srcipts/Makefile.kbuild去执行,文件scripts/Makefile.kbuild的开头会根据传入的obj=scripts/kconfig参数设置src=scripts/kconfig, 然后搜寻$(srctree)/$(src)子目录下的makefile, 由于src=scripts/kconfig参数不用于第一次调用的参数(src=scripts/basic)此处包含的makefile也不用于第一次的makefile了

# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

这里展开后相当于

include ./scripts/kconfig/Makefile

文件scripts/kconfig/Makefile中定义了所有匹配%config的目标

PHONY += xconfig gconfig menuconfig config syncconfig update-po-config \
%_defconfig: $(obj)/conf
    $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

展开为

xxx_defconfig: scripts/kconfig/conf
    scripts/kconfig/conf --defconfig=arch/arm/configs/xxx_defconfig Kconfig

此处xxx_defconfig依赖scripts/kconfig/conf,接下来检查并生成依赖

hostprogs-y := conf nconf mconf kxgettext qconf gconf
conf-objs   := conf.o  zconf.tab.o

hostprogs-y指出conf被定义为主机上执行的程序,其依赖于另外两个文件 conf.o zconf.tab.o。通过编译conf.c和zconf.tab.c生成conf-objs并链接为scripts/kconfig/conf。生成 依赖后就是执行目标的命令了

conf工具从根目录下开始树状读取默认的kconf文件,分析其配置并保存在内存中,分析完默认的kconfig后再读取指定的文件(arch/arm/configs/xxx_defconfig)更新得到最后的符号表,并输出到.config文件中, 至此完成了make xxx_defconfig执行配置涉及到的所有依赖和命令的分析

make defconfig配置流程简图

../_images/make_defconfig.png

2.1.2. make执行流程分析

2.1.2.1. 目标_all和all对$(ALL-y)的依赖

从顶层的Makefile开始查找,找到第一个目标为_all

PHONY := _all
_all:

PHONY += all
ifeq ($(KBUILD_EXTMOD),)    ##当我们定义了KBUILD_EXTMOD编译一个外部模块时,_all依赖Modules否则依赖all
_all: all
else
_all: modules
endif

在Makefile中.PHONY后面的target表示也是一个伪造的target,而不是真实存在的文件target,注意makefile的target默认是文件

接着往下分析,all自身依赖于$(ALL-y)

all:                $(ALL-y) cfg
ifeq ($(CONFIG_DM_I2C_COMPAT)$(CONFIG_SANDBOX),y)
    @echo "===================== WARNING ======================"
    @echo "This board uses CONFIG_DM_I2C_COMPAT. Please remove"
    @echo "(possibly in a subsequent patch in your series)"
    @echo "before sending patches to the mailing list."
    @echo "===================================================="
endif
    @# Check that this build does not use CONFIG options that we do not
    @# know about unless they are in Kconfig. All the existing CONFIG
    @# options are whitelisted, so new ones should not be added.
    $(call cmd,cfgcheck,u-boot.cfg)

目标$(ALL-y)

# Always append ALL so that arch config.mk's can add custom ones
ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check

ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
ifeq ($(CONFIG_SPL_FSL_PBL),y)
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
else
ifneq ($(CONFIG_SECURE_BOOT), y)
# For Secure Boot The Image needs to be signed and Header must also
# be included. So The image has to be built explicitly
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
endif
endif
ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
ifeq ($(CONFIG_MX6)$(CONFIG_SECURE_BOOT), yy)
ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot-ivt.img
else
ifeq ($(CONFIG_MX7)$(CONFIG_SECURE_BOOT), yy)
ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot-ivt.img
else
ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
endif
endif
ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
ifeq ($(CONFIG_SPL_FRAMEWORK),y)
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
endif
ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
ifneq ($(CONFIG_SPL_TARGET),)
ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
endif
ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi

ifneq ($(BUILD_ROM)$(CONFIG_BUILD_ROM),)
ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
endif

# Build a combined spl + u-boot image for sunxi
ifeq ($(CONFIG_ARCH_SUNXI)$(CONFIG_SPL),yy)
ALL-y += u-boot-sunxi-with-spl.bin
endif

# enable combined SPL/u-boot/dtb rules for tegra
ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)
ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.bin
endif

# Add optional build target if defined in board/cpu/soc headers
ifneq ($(CONFIG_BUILD_TARGET),)
ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
endif

ifneq ($(CONFIG_SYS_INIT_SP_BSS_OFFSET),)
ALL-y += init_sp_bss_offset_check
endif

以上的$(ALL-y)目标中看起来很复杂,但除了第一行的通用目标外,其他目标都是在特殊条件下才会生成,这里暂时不提

$(ALL-y)依赖u-boot.srec

u-boot.hex u-boot.srec: u-boot FORCE
    $(call if_changed,objcopy)

$(ALL-y)依赖u-boot.bin

ifeq ($(CONFIG_MULTI_DTB_FIT),y)

fit-dtb.blob: dts/dt.dtb FORCE
    $(call if_changed,mkimage)

MKIMAGEFLAGS_fit-dtb.blob = -f auto -A $(ARCH) -T firmware -C none -O u-boot \
    -a 0 -e 0 -E \
    $(patsubst %,-b arch/$(ARCH)/dts/%.dtb,$(subst ",,$(CONFIG_OF_LIST))) -d /dev/null

u-boot-fit-dtb.bin: u-boot-nodtb.bin fit-dtb.blob
    $(call if_changed,cat)

u-boot.bin: u-boot-fit-dtb.bin FORCE
    $(call if_changed,copy)
else ifeq ($(CONFIG_OF_SEPARATE),y)
u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
    $(call if_changed,cat)

u-boot.bin: u-boot-dtb.bin FORCE
    $(call if_changed,copy)
else
u-boot.bin: u-boot-nodtb.bin FORCE
    $(call if_changed,copy)
endif

如果打开了device tree的支持,则有依赖关系

u-boot.bin---->u-boot-dtb.bin----->u-boot-nodtb.bin + dts/dt.dtb

如果没有定义CONFIG_OF_SEPARATE则依赖关系如下

u-boot.bin ----> u-boot-nodtb.bin

u-boot-nodtb.bin的依赖关系以及执行命令如下

u-boot-nodtb.bin: u-boot FORCE
    $(call if_changed,objcopy)
    $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
    $(BOARD_SIZE_CHECK)

命令中if_changed函数定义在scripts/Kbuild.include文件中,顶层Makefile中通过以下命令包含

scripts/Kbuild.include: ;
include scripts/Kbuild.include

if_changed函数定义如下

if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
    @set -e;                                                             \
    $(echo-cmd) $(cmd_$(1));                                             \
    printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)

该命令外层是一个if函数,然后又内嵌了一个strip函数

OBJCOPY             = $(CROSS_COMPILE)objcopy

# Normally we fill empty space with 0xff
quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) --gap-fill=0xff $(OBJCOPYFLAGS) \
    $(OBJCOPYFLAGS_$(@F)) $< $@

所以$(call if_changed,objcopy)展开后:

echo objcopy $@; objcopy $< $@

就是说利用objcopy命令将u-boot转换为u-boot-nodtb.bin

$(ALL-y)依赖u-boot.sym

u-boot.sym: u-boot FORCE
    $(call if_changed,sym)

$(ALL-y)依赖System.map

System.map: u-boot
        @$(call SYSTEM_MAP,$<) > $@

$(ALL-y)依赖u-boot.cfg

u-boot.cfg spl/u-boot.cfg tpl/u-boot.cfg: include/config.h FORCE
    $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf $(@)

include/config.h在make xxx_defconfig时创建,include/config.h文件中会包含板级配置文件如#include <configs/holo_ark_v3.h>

$(ALL-y)依赖binary_size_check

binary_size_check: u-boot-nodtb.bin FORCE
    @file_size=$(shell wc -c u-boot-nodtb.bin | awk '{print $$1}') ; \
    map_size=$(shell cat u-boot.map | \
        awk '/_image_copy_start/ {start = $$1} /_image_binary_end/ {end = $$1} END {if (start != "" && end != "") print "ibase=16; " toupper(end) " - " toupper(start)}' \
        | sed 's/0X//g' \
        | bc); \
    if [ "" != "$$map_size" ]; then \
        if test $$map_size -ne $$file_size; then \
            echo "u-boot.map shows a binary size of $$map_size" >&2 ; \
            echo "  but u-boot-nodtb.bin shows $$file_size" >&2 ; \
            exit 1; \
        fi \
    fi

以上通用目标$(ALL-y)的依赖有一个共同点,除了u-boot.cfg依赖于include/config.h外其余目标都依赖于u-boot, 以下图中表示了_all依赖简图

../_images/_all_dep.png

2.1.2.2. u-boot目标编译

u-boot目标依赖及执行命令如下

u-boot:     $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
    +$(call if_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
    $(call cmd,smap)
    $(call cmd,u-boot__) common/system_map.o
endif

其中u-boot-init和u-boot-main被定义为

u-boot-init := $(head-y)
u-boot-main := $(libs-y)

依赖项head-y libs-y

head-y 在arch/arm/Makefile中定义

head-y := arch/arm/cpu/$(CPU)/start.o

所以head-y指的是start.S

在顶层目录Makefile中搜索libs-y可以发现其包含许多目录,

libs-y += lib/
libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
libs-$(CONFIG_OF_EMBED) += dts/
libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
libs-y += drivers/dma/
libs-y += drivers/gpio/
libs-y += drivers/i2c/
libs-y += drivers/net/
libs-y += drivers/net/phy/
libs-y += drivers/pci/
libs-y += drivers/power/ \
    drivers/power/domain/ \
    drivers/power/fuel_gauge/ \
    drivers/power/mfd/ \
    drivers/power/pmic/ \
    drivers/power/battery/ \
    drivers/power/regulator/
libs-y += drivers/spi/
libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/
libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/
libs-$(CONFIG_SYS_FSL_MMDC) += drivers/ddr/fsl/
libs-$(CONFIG_ALTERA_SDRAM) += drivers/ddr/altera/
libs-y += drivers/serial/
libs-y += drivers/usb/dwc3/
libs-y += drivers/usb/common/
libs-y += drivers/usb/emul/
libs-y += drivers/usb/eth/
libs-y += drivers/usb/gadget/
libs-y += drivers/usb/gadget/udc/
libs-y += drivers/usb/host/
libs-y += drivers/usb/musb/
libs-y += drivers/usb/musb-new/
libs-y += drivers/usb/phy/
libs-y += drivers/usb/ulpi/
libs-y += cmd/
libs-y += common/
libs-y += env/
libs-$(CONFIG_API) += api/
libs-$(CONFIG_HAS_POST) += post/
libs-y += test/
libs-y += test/dm/
libs-$(CONFIG_UT_ENV) += test/env/
libs-$(CONFIG_UT_OVERLAY) += test/overlay/

另外libs-y还有如下规则定义

libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)

libs-y := $(sort $(libs-y))

libs-y              := $(patsubst %/, %/built-in.o, $(libs-y))

这条规则使得libs-y中的每个条目的最后一个斜杠替换成/built-in.o,可见libs-y被定义为各层驱动目录下built-in.o的集合,而这些built-in.o则由kbuild makefile将obj-y所 包含的各个文件编译而成,具体可以研究 scripts/Kbuild.includescripts/Makefile.build

ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif

$(builtin-target): $(obj-y) FORCE
    $(call if_changed,link_o_target)

u-boot文件目标依赖:

../_images/u-boot_dep.png

依赖项u-boot.lds

u-boot.lds: $(LDSCRIPT) prepare FORCE
    $(call if_changed_dep,cpp_lds)

ifndef LDSCRIPT
    #LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug
    ifdef CONFIG_SYS_LDSCRIPT
        # need to strip off double quotes
        LDSCRIPT := $(srctree)/$(CONFIG_SYS_LDSCRIPT:"%"=%)
    endif
endif

# If there is no specified link script, we look in a number of places for it
ifndef LDSCRIPT
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds
    endif
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot.lds
    endif
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds
    endif
endif

如果没有定义LDSCRIPT和CONFIG_SYS_LDSCRIPT则默认使用u-boot自带的lds文件,包括board/$(BOARDDIR)和$(CPUDIR)目录下定制的针对board或cpu的lds文件,如果没有定制的lds文件则采用 arch/arm/cpu目录下默认的lds链接文件u-boot.lds

2.1.2.2.1. prepare编译

实际上prepare是一些列prepare伪目标和动作的组合,完成编译前的准备工作

# Listed in dependency order
PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3

prepare3: include/config/uboot.release
ifneq ($(KBUILD_SRC),)
    @$(kecho) '  Using $(srctree) as source for U-Boot'
    $(Q)if [ -f $(srctree)/.config -o -d $(srctree)/include/config ]; then \
        echo >&2 "  $(srctree) is not clean, please run 'make mrproper'"; \
        echo >&2 "  in the '$(srctree)' directory.";\
        /bin/false; \
    fi;
endif

# prepare2 creates a makefile if using a separate output directory
prepare2: prepare3 outputmakefile

prepare1: prepare2 $(version_h) $(timestamp_h) \
                   include/config/auto.conf

archprepare: prepare1 scripts_basic

prepare0: archprepare FORCE
    $(Q)$(MAKE) $(build)=.

# All the preparing..
prepare: prepare0

各个prepare目标的依赖关系如下

../_images/prepare_dep.png

在prepare1的依赖列表中,除了include/config/auto.conf之外,还有$(version_h)和$(timestamp_h),他们的依赖关系如下

$(version_h): include/config/uboot.release FORCE
    $(call filechk,version.h)

$(timestamp_h): $(srctree)/Makefile FORCE
    $(call filechk,timestamp.h)

对于位于最后的prepare3的依赖include/config/uboot.release它还有下级依赖

include/config/uboot.release: include/config/auto.conf FORCE
    $(call filechk,uboot.release)

对于include/config/auto.conf,Makefile还有一个匹配规则

include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
    $(Q)$(MAKE) -f $(srctree)/Makefile syncconfig
    @# If the following part fails, include/config/auto.conf should be
    @# deleted so "make silentoldconfig" will be re-run on the next build.
    $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
        { rm -f include/config/auto.conf; false; }
    @# include/config.h has been updated after "make silentoldconfig".
    @# We need to touch include/config/auto.conf so it gets newer
    @# than include/config.h.
    @# Otherwise, 'make silentoldconfig' would be invoked twice.
    $(Q)touch include/config/auto.conf

include/config/auto.conf依赖于$(KCONFIG_CONFIG)和include/config/auto.conf.cmd,其中: - $(KCONFIG_CONFIG)实际上就是.config文件 - include/config/auto.conf.cmd是由fixdep在编译时生成的依赖文件

make编译流程图

../_images/make.png

完成目标依赖分析后,剩下的就是基于完整的目标依赖关系图,从最底层的依赖开始,逐行运行命令生成目标,直到生成顶层目标

补充 —- config.h文件生成

此处进行函数定义

##scripts/Kbuild.include文件中
define filchk
    $(Q)set -e;             \
    $(kecho) '  CHK     $@';        \
    mkdir -p $(dir $@);         \
    $(filechk_$(1)) < $< > $@.tmp;      \
    if [ -r $@ ] && cmp -s $@ $@.tmp; then  \
        rm -f $@.tmp;           \
    else                    \
        $(kecho) '  UPD     $@';    \
        mv -f $@.tmp $@;        \
    fi
endef

具体的文件生成则在以下文件中实现

# scripts/Makefile.autoconf文件中
# Prior to Kconfig, it was generated by mkconfig. Now it is created here.
define filechk_config_h
    (echo "/* Automatically generated - do not edit */";        \
    for i in $$(echo $(CONFIG_SYS_EXTRA_OPTIONS) | sed 's/,/ /g'); do \
        echo \#define CONFIG_$$i                \
        | sed '/=/ {s/=/    /;q; } ; { s/$$/    1/; }'; \
    done;                               \
    echo \#define CONFIG_BOARDDIR board/$(if $(VENDOR),$(VENDOR)/)$(BOARD);\
    echo \#include \<config_defaults.h\>;               \
    echo \#include \<config_uncmd_spl.h\>;              \
    echo \#include \<configs/$(CONFIG_SYS_CONFIG_NAME).h\>;     \
    echo \#include \<asm/config.h\>;                \
    echo \#include \<linux/kconfig.h\>;             \
    echo \#include \<config_fallbacks.h\>;)
endef

include/config.h: scripts/Makefile.autoconf create_symlink FORCE
    $(call filechk,config_h)


u-boot.cfg: include/config.h FORCE
$(call cmd,u_boot_cfg)

spl/u-boot.cfg: include/config.h FORCE
    $(Q)mkdir -p $(dir $@)
    $(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD)

tpl/u-boot.cfg: include/config.h FORCE
    $(Q)mkdir -p $(dir $@)
    $(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD -DCONFIG_TPL_BUILD)

include/autoconf.mk: u-boot.cfg
    $(call cmd,autoconf)