ILD

openwrt target分析
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2021-5-26 站点:Inside Linux Development

openwrt分为package和target。target表示一个种目标硬件架构,比如ramips。target里面有kernel、image的概率,kernel编译内核,image编译固件。

1 Board/Subtarget/Profile

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来定义所有的目标。


include/target.mk分析

主要有两个变量控制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。


Kernel

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

  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)


configure

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

  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


  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版本。

Image

前面看到,在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




Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.