openwrt分为package和target。target表示一个种目标硬件架构,比如ramips。target里面有kernel、image的概率,kernel编译内核,image编译固件。
BOARD是SOC的名字,target/linux/${BOARD},是BOARD所在的目录。配置文件中的配置项为:CONFIG_TARGET_BOARD。比如ramips。SUBTARGET是子类型,配置项CONFIG_TARGET_SUBTARGET,
PROFILE是具体的一个机型,CONFIG_TARGET_PROFILE。
BOARD的choice选择的是:CONFIG_TARGET_ipq806x=y
SUBTARGET的choice选择的是:CONFIG_TARGET_ipq806x_generic=y
PROFILE的choice选择的是:CONFIG_TARGET_ipq806x_generic_DEVICE_asrock_g10=y
subtarget在board目录下都有同名目录,下面的规则可以用来获得SUBTARGET。
target/linux/${BOARD}/*/target.mk
有的BOARD没有SUBTARGET,比如ath25,BOARDNAME:Atheros AR231x/AR5312
一个典型的board的Makefile,如target/linux/realtek/Makefile,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # SPDX-License-Identifier: GPL-2.0-only include $(TOPDIR)/rules.mk ARCH:=mips CPU_TYPE:=4kec BOARD:=realtek BOARDNAME:=Realtek MIPS DEVICE_TYPE:=basic FEATURES:=ramdisk squashfs SUBTARGETS:=generic KERNEL_PATCHVER:=5.4 define Target/Description Build firmware images for Realtek RTL83xx based boards. endef include $(INCLUDE_DIR)/target.mk FEATURES := $(filter-out mips16,$(FEATURES)) DEFAULT_PACKAGES += uboot-envtools ethtool kmod-gpio-button-hotplug \ dnsmasq firewall ip6tables iptables odhcp6c odhcpd-ipv6only \ ip-full ip-bridge tc $(eval $(call BuildTarget)) |
target的主入口Makefile是:include/target.mk,BOARD的Makefile定义了一些变了,包含include/target.mk,然后eval一个变量BuildTarget来定义所有的目标。
主要有两个变量控制target.mk的行为:DUMP和BUILD_TARGET,前者表示是否是DUMP,DUMP是prepare-tmpinfo用来生成target的信息。BUILD_TARGET表示是target还是image,编译image的时候,这个变量不是1,其它都是1。
如果是DUMP,定义all: dumpinfo,dumpinfo这个目标用来生成target信息。
几个变量:
PLATFORM_DIR:BOARD所在的目录。
SUBTARGETS:所有的subtarget,通过$(wildcard */target.mk)匹配得到。DUMP才用这个变量。
SUBTARGET:当前的subtarget。
PLATFORM_SUBDIR:SUBTARGET的目录,如果SUBTARGET为空,则等于PLATFORM_DIR。
Profile:
Profile定义了一个具体的机型。target和subtarget都可以有Profile,在target或subtarget目录的profiles子目录下。一个典型的profile文件如下,一个profile文件可以包含多个profile。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ cat target/linux/bcm47xx/mips74k/profiles/101-Broadcom-brcsmac.mk # SPDX-License-Identifier: GPL-2.0-only # # Copyright (C) 2014 OpenWrt.org define Profile/Broadcom-mips74k-brcmsmac NAME:=Broadcom SoC, BCM43xx WiFi (brcmsmac) PACKAGES:=kmod-brcmsmac endef define Profile/Broadcom-mips74k-brcmsmac/Description Package set for devices with BCM43xx WiFi including mac80211 and brcmsmac driver. endef $(eval $(call Profile,Broadcom-mips74k-brcmsmac)) |
Profile变量如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ifndef Profile define Profile $(eval $(call ProfileDefault)) $(eval $(call Profile/$(1))) dumpinfo : $(call shexport,Profile/$(1)/Description) PACKAGES := $(filter-out -%,$(PACKAGES)) DUMPINFO += \ echo "Target-Profile: $(1)"; \ $(if $(PRIORITY), echo "Target-Profile-Priority: $(PRIORITY)"; ) \ echo "Target-Profile-Name: $(NAME)"; \ echo "Target-Profile-Packages: $(PACKAGES) $(call extra_packages,$(DEFAULT_PACKAGES) $(PACKAGES))"; \ echo "Target-Profile-Description:"; \ echo "$$$$$$$$$(call shvar,Profile/$(1)/Description)"; \ echo "@@"; \ echo; endef endif |
ProfileDefault是将NAME/PRIORITY/PACKAGE 3个变量清空,然后调用Profile/$1,它设置这3个变量。然后将这个Profile的信息,追加到DUMPINFO变量。这个变量后面会用到。
Subtarget:
subtarget的makeifle为target.mk,只是定义了一些变量,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ cat target/linux/ramips/mt7621/target.mk # # Copyright (C) 2009 OpenWrt.org # SUBTARGET:=mt7621 BOARDNAME:=MT7621 based boards FEATURES+=nand ramdisk rtc usb minor CPU_TYPE:=24kc KERNELNAME:=vmlinux vmlinuz # make Kernel/CopyImage use $LINUX_DIR/vmlinuz IMAGES_DIR:=../../.. DEFAULT_PACKAGES += wpad-basic-wolfssl define Target/Description Build firmware images for Ralink MT7621 based boards. endef |
DUMP:
有一个新变量CUR_SUBTARGET,等于SUBTARGET。如果SUBTARGETS为空,则CUR_SUBTARGET等于default。
下面来看dumpinfo的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | define BuildTargets/DumpCurrent .PHONY: dumpinfo dumpinfo : export DESCRIPTION=$$(Target/Description) dumpinfo: @echo 'Target: $(TARGETID)'; \ echo 'Target-Board: $(BOARD)'; \ echo 'Target-Name: $(BOARDNAME)$(if $(SUBTARGETS),$(if $(SUBTARGET),))'; \ echo 'Target-Arch: $(ARCH)'; \ echo 'Target-Arch-Packages: $(if $(ARCH_PACKAGES),$(ARCH_PACKAGES),$(ARCH)$(if $(CPU_TYPE),_$(CPU_TYPE))$(if $(CPU_SUBTYPE),_$(CPU_SUBTYP E)))'; \ echo 'Target-Features: $(FEATURES)'; \ echo 'Target-Depends: $(DEPENDS)'; \ echo 'Target-Optimization: $(if $(CFLAGS),$(CFLAGS),$(DEFAULT_CFLAGS))'; \ echo 'CPU-Type: $(CPU_TYPE)$(if $(CPU_SUBTYPE),+$(CPU_SUBTYPE))'; \ echo 'Linux-Version: $(LINUX_VERSION)'; \ $(if $(LINUX_TESTING_VERSION),echo 'Linux-Testing-Version: $(LINUX_TESTING_VERSION)';) \ echo 'Linux-Release: $(LINUX_RELEASE)'; \ echo 'Linux-Kernel-Arch: $(LINUX_KARCH)'; \ $(if $(SUBTARGET),,$(if $(DEFAULT_SUBTARGET), echo 'Default-Subtarget: $(DEFAULT_SUBTARGET)'; )) \ echo 'Target-Description:'; \ echo "$$$$DESCRIPTION"; \ echo '@@'; \ echo 'Default-Packages: $(DEFAULT_PACKAGES) $(call extra_packages,$(DEFAULT_PACKAGES))'; \ $(DUMPINFO) $(if $(CUR_SUBTARGET),$(SUBMAKE) -r --no-print-directory -C image -s DUMP=1 SUBTARGET=$(CUR_SUBTARGET)) $(if $(SUBTARGET),,@$(foreach SUBTARGET,$(SUBTARGETS),$(SUBMAKE) -s DUMP=1 SUBTARGET=$(SUBTARGET); )) endef |
DUMP流程如下:
首次进来的时候,SUBTARGET为空,会获取SUBTARGETS。dumpinfo目标会再次进来,带上SUBTARGET变量。
然后每次进来都会包含PLATFORM_DIR和PLATFORM_SUBDIR的profiles子目录下得所有profile文件,然后就更新DUMPINFO变量就包含这些Profile了。这些Profile文件只对DUMP有用,正常编译的时候其实可以不用包含的。
dumpinfo首先会打印Target的信息,TARGETID的定义是
TARGETID:=$(BOARD)$(if $(SUBTARGET),/$(SUBTARGET))
然后打印DUMPINFO中的Profile信息,第一次进来,是主BOARD,SUBTARGET为空,所以只打印BOARD下面的profile,后面带SUBTARGET进来的时候,PLATFORM_SUBDIR下面的profiles也会包含。
然后如果没有SUBTARGET,或者是SUBTARGET的时候,dumpinfo进入image目录,编译:
$(SUBMAKE) -r --no-print-directory -C image -s DUMP=1 SUBTARGET=$(CUR_SUBTARGET)
如果SUBTARGET,为空,则遍历SUBTARGETS,重新执行:
$(SUBMAKE) -s DUMP=1 SUBTARGET=$(SUBTARGET)
也就是在第一次进来的时候,就会遍历所有的subtarget。
kernel流程:
内核配置文件列表LINUX_KCONFIG_LIST变量:
以下目录下如果存在config-$(KERNEL_PATCHVER)和config-default,则包含
GENERIC_LINUX_CONFIG:target/linux/generic/
LINUX_TARGET_CONFIG: target/linux/$(BOARD)
LINUX_SUBTARGET_CONFIG:target/linux/$(BOARD)/$(SUBTARGET)。
LINUX_RECONFIG_LIST是上述配置文件
LINUX_RECONFIG_TARGET,如果Subtarget存在配置文件,则用subtarget的,否则用target的。
包含include/kernel-version.mk,include/kernel.mk,如果是TARGET_BUILD=1,还包含include/kernel-build.mk,并且让BuildTarget=BuildKernel。
include/kernel-version.mk,确定内核的版本,通常是BOARD下面的Makefile通过KERNEL_PATCHVER指定大版本。然后这个mk,通过LINUX_VERSION-5.4 = .101,指定小版本。
include/kernel.mk,这个文件定义内核相关的基本变量:KERNEL_BUILD_DIR, LINUX_DIR等。还定义了内核模块包KernelPackage的相关东西。
include/kernel-build.mk,编译target的Makefile,它会编译内核,安装的时候还会进入image目录,编译image。
编译的流程如下:
target/Makefile -> target/linux/Makefile -> target/linux/$(BOARD)/Makefile
编译target/xxx,都是进入BOARD目录,执行xxx目标,target/linux/Makefile里面写的xxx目标有:
prereq clean download prepare compile install oldconfig menuconfig nconfig xconfig update refresh
BOARD最终通过$(eval $(call BuildTarget)),包含了kernel-build.mk定义的所有的编译目标。
kernel-build.mk包含kernel-defaults.mk,所有的操作都是defaults中定义的。
Kernel/Prepare -> Kernel/Prepare/Default
Kernel/Configure - > Kernel/Configure/Default
Kernel/CompileModules -> Kernel/CompileModules/Default
Kernel/CompileImage -> Kernel/CompileImage/Default Kernel/CompileImage/Initramfs
Kernel/Patch -> Kernel/Patch/Default,但是后者定义再quilt.mk
prepare: $(STAMP_PREPARED)
$(STAMP_PREPARED): $(if $(LINUX_SITE),$(DL_DIR)/$(LINUX_SOURCE))
-rm -rf $(KERNEL_BUILD_DIR)
-mkdir -p $(KERNEL_BUILD_DIR)
$(Kernel/Prepare)
touch $$@
define Kernel/Prepare/Default
$(LINUX_CAT) $(DL_DIR)/$(LINUX_SOURCE) | $(TAR) -C $(KERNEL_BUILD_DIR) $(TAR_OPTIONS)
$(Kernel/Patch)
$(if $(QUILT),touch $(LINUX_DIR)/.quilt_used)
endef
kernel_files=$(foreach fdir,$(GENERIC_FILES_DIR) $(FILES_DIR),$(fdir)/.)
define Kernel/Patch/Default
$(if $(QUILT),rm -rf $(LINUX_DIR)/patches; mkdir -p $(LINUX_DIR)/patches)
$(if $(kernel_files),$(CP) $(kernel_files) $(LINUX_DIR)/)
find $(LINUX_DIR)/ -name \*.rej -or -name \*.orig | $(XARGS) rm -f
$(call PatchDir,$(LINUX_DIR),$(GENERIC_BACKPORT_DIR),generic-backport/)
$(call PatchDir,$(LINUX_DIR),$(GENERIC_PATCH_DIR),generic/)
$(call PatchDir,$(LINUX_DIR),$(GENERIC_HACK_DIR),generic-hack/)
$(call PatchDir,$(LINUX_DIR),$(PATCH_DIR),platform/)
首先删除、然后创建内核目录,然后解压内核源码。然后就是打patch啦。
1 拷下列目录的文件
target/linux/generic/files
target/linux/generic/files-$(KERNEL_PATCHVER)
target/linux/$(BOARD)/files
target/linux/$(BOARD)/files-$(KERNEL_PATCHVER)
2 打下列目录的patch
target/linux/generic/backport-$(KERNEL_PATCHVER)
target/linux/generic/pending-$(KERNEL_PATCHVER)
target/linux/generic/hack-$(KERNEL_PATCHVER)
target/linux/$(BOARD)/patches-$(KERNEL_PATCHVER)
LINUX_KCONFIG_LIST,是预置配置文件列表:
target/linux/generic/
target/linux/$(BOARD)/
target/linux/$(BOARD)/$(SUBTARGET)
这3个目录下的config-$(KERNEL_PATCHVER)和config-default,组成。
生成.config的流程:
1 scripts/kconfig.pl脚本,合并上述几个配置文件到 .config.target文件。
2 将openwrt的.config配置文件中的CONFIG_KERNEL_配置项追加到.config.target
3 执行下面的命令将那些开启的内核package对应的KernelConfig开启到.config.override中去。
scripts/package-metadata.pl kconfig tmp/.packageinfo ${openwrtdir}/.config 4.4 > .config.override
4 scripts/kconfig.pl脚本.config.target和.config.override合并到.config.set
5 将.config.set拷贝到.config
compile: $(LINUX_DIR)/.modules
$(MAKE) -C image compile TARGET_BUILD=
$(LINUX_DIR)/.modules: $(STAMP_CONFIGURED) $(LINUX_DIR)/.config FORCE
$(Kernel/CompileModules)
touch $$@
define Kernel/CompileModules/Default
rm -f $(LINUX_DIR)/vmlinux $(LINUX_DIR)/System.map
+$(KERNEL_MAKE) $(if $(KERNELNAME),$(KERNELNAME),all) modules
endef
会执行$(KERNELNAME)和modules两个目标,如果KERNELNAME没有定义,则是all modules两个目标。KERNELNAME通常定义在BOARD或者SUBTARGET的Makefile中,比如定义成zImage。
install: $(LINUX_DIR)/.image
+$(MAKE) -C image compile install TARGET_BUILD=
$(LINUX_DIR)/.image:
$(Kernel/CompileImage)
$(Kernel/CollectDebug)
touch $$@
define Kernel/CompileImage
$(call Kernel/CompileImage/Default)
$(call Kernel/CompileImage/Initramfs)
endef
define Kernel/CopyImage
cmp -s $(LINUX_DIR)/vmlinux $(KERNEL_BUILD_DIR)/vmlinux$(1).debug || { \
$(KERNEL_CROSS)objcopy -O binary $(OBJCOPY_STRIP) -S $(LINUX_DIR)/vmlinux $(LINUX_KERNEL)$(1); \
$(KERNEL_CROSS)objcopy $(OBJCOPY_STRIP) -S $(LINUX_DIR)/vmlinux $(KERNEL_BUILD_DIR)/vmlinux$(1).elf; \
$(CP) $(LINUX_DIR)/vmlinux $(KERNEL_BUILD_DIR)/vmlinux$(1).debug; \
$(foreach k, \
$(if $(KERNEL_IMAGES),$(KERNEL_IMAGES),$(filter-out vmlinux dtbs,$(KERNELNAME))), \
$(CP) $(LINUX_DIR)/arch/$(LINUX_KARCH)/boot/$(IMAGES_DIR)/$(k) $(KERNEL_BUILD_DIR)/$(k)$(1); \
) \
}
endef
define Kernel/CompileImage/Default
rm -f $(TARGET_DIR)/init
+$(KERNEL_MAKE) $(KERNEL_MAKEOPTS_IMAGE) $(if $(KERNELNAME),$(KERNELNAME),all)
$(call Kernel/CopyImage)
endef
编译内核,然后拷贝内核编译产物,如果定义了CONFIG_TARGET_ROOTFS_INITRAMFS,还会编译Initramfs版本。
前面看到,在DUMP,compile, install的时候,都会进入到image目录去编译。看一个典型的Image Make文件:target/linux/realtek/image/Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/image.mk KERNEL_LOADADDR = 0x80000000 KERNEL_ENTRY = 0x80000400 。。。 define Device/zyxel_gs1900-8hp-v2 SOC := rtl8380 IMAGE_SIZE := 6976k DEVICE_VENDOR := ZyXEL DEVICE_MODEL := GS1900-8HP DEVICE_VARIANT := v2 DEVICE_PACKAGES += lua-rs232 UIMAGE_MAGIC := 0x83800000 KERNEL_INITRAMFS := kernel-bin | append-dtb | gzip | zyxel-vers AAHI | uImage gzip endef TARGET_DEVICES += zyxel_gs1900-8hp-v2 $(eval $(call BuildImage)) |
它包含了include/image.mk,然后给TARGET_DEVICES添加了很多机型,这跟Profile是一样的,可以添加机型。 最后执行了BuildImage宏。
只执行打包:
TOPDIR=代码路径 make -C target/linux/realtek/image install
include/image.mk