前言

在之前的文章中已经对设备树的基本概念作了讲解, 操作系统(例如在 Android 中使用的 Linux 内核)会使用 DT 来支持 Android 设备使用的各种硬件配置。硬件供应商(ODM)会提供自己的 DT 源文件,接下来 Linux 会将这些文件编译到引导加载程序使用的设备树 Blob (DTB) 文件中。

Android在原有的DT基础上增加了设备树叠加层的处理方式。进一步的对于芯片产品的DT和开发者(ODM/OEM/产品开发者)的DT做了解耦。

设备树叠加层 (DTO) 可让主要的(ODM)设备树 Blob (DTB) 叠加在Soc设备树上。使用 DTO 的引导加载程序可以维护系统芯片 (SoC) DT,并动态叠加针对特定设备(ODM)的 DT,从而向树中添加节点并对现有树中的属性进行更改。
本篇文章主要讲述如下内容:

  • DT的专有名词
  • Bootloader 加载DT的基本流程
  • Android 下DTO的实现以及原理
  • 实际案例-MT8167 平台DTS的加载完整流程
DT相关的专有名词
DTDevice Tree
DTBDevice Tree Blob
DTBODevice Tree Blob for Overlay
DTCDevice Tree Compiler
DTODevice Tree Overlay
DTSDevice Tree Source
FDTFlattened Device Tree, a binary format contained in a .dtb blob file
Bootloader 加载DT的基本流程

在这里插入图片描述
如上图所示,系统加载DT主要包含: DTS源文件编译, .dtb分区以及对应镜像文件生成, bootloader运行将.dtb 分区的文件加载到内存中, 将对应的内存地址通过寄存器传递到kernel。

在支持DTO的Android下, DT 是由以下两个部分组成:

  • Main DT, 主要是Soc-only的部分以及默认的系统配置,例如cpu配置/内存相关配置等,soc的供应商提供, 本文所用的MT8167 那么这个Main DT就是由MTK提供的.
  • Overlay DT, 该Soc所对应的产品需要的特定配置, 主要是由ODM/OEM提供,这里可以理解为开发者自己定义(当然目前在MT8167上MTK也提供了基本的Oerlay DT基本模板)
  1. 编译阶段
  • 通过dtc(device tree compiler)将Main DT dts源文件编译为.dtb文件.
  • 通过dtc(device tree compiler)将Overlay DT的dts源文件编译为后缀名为.dtbo的文件.
    这里需要注意的是, .dtb和.dtbo 的文件格式是相同的都是FDT. 后缀名不同只是为了区分.
  1. dtb分区
    在这里插入图片描述
    在MT8167这一平台上是将dtbo划分为了独立的分区, 具体的分析在实际案例章节会详细说明.

  2. 运行

在这里插入图片描述

  • 在bootloader中将.dtb文件读取到内存中
  • 在bootloader中将.dtbo文件从dtbo分区(emmc指定分区)读取到内存中.
  • 将.dtb 和 .dtbo 合并
  • 在bootloader跳转启动kernel时,将该内存地址通过寄存器传递给到kernel
    关于这个环节的详细流程在实际案例章节会详细说明.
实际案例-MT8167 平台DTS的加载完整流程
1. MT8167 平台的Main DT和Overlay DT一览

截取了MT8167平台Main DT的部分内容, 关于Main DT 和 Oerlay DT 在上一章节"Bootloader 加载DT的基本流程"已描述.

# kernel-4.4/arch/arm/boot/dts/mt8167.dtsi

/ {
        model = "MT8167";
        compatible = "mediatek,mt8167";
        interrupt-parent = <&sysirq>;
        #address-cells = <2>;
        #size-cells = <2>;
        
        cpus {
                #address-cells = <1>;
                #size-cells = <0>;

                cpu0: cpu@0 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a53";
                        reg = <0x0>;
                        enable-method = "psci";
                        cpu-idle-states = <&CLUSTER_SLEEP_0 &CLUSTER_SLEEP_0>,
                                          <&CPU_SLEEP_0_0 &CPU_SLEEP_0_0 &CPU_SLEEP_0_0>;
                        clocks = <&infracfg CLK_IFR_MUX1_SEL>,
                                 <&topckgen CLK_TOP_MAINPLL_D2>,
                                 <&apmixedsys CLK_APMIXED_ARMPLL>;
                        clock-names = "cpu", "intermediate", "armpll";
                        operating-points-v2 = <&cluster0_opp>;
                        clock-frequency = <1300000000>;
                };
         ...
# kernel-4.4/arch/arm/boot/dts/mt8167_dtbo.dts

/dts-v1/;

#include "mt8167.dtsi"
#include "mt6392.dtsi"

/ {
        /* chosen */
        chosen {
                bootargs = "console=ttyS0,921600n1 root=/dev/ram initrd=0x44000200,0x200000";
        };

        firmware: firmware {
                android: android {
                        compatible = "android,firmware";
                        fstab: fstab {
                                compatible = "android,fstab";
                        };
                };
        };
        odm: odm {
                compatible = "simple-bus";
                /* reserved for overlay by odm */
        };
};

基于上述截取的部分内容可以看到,针对MT8167平台+Android O Main DT由以下两个文件构成,

kernel-4.4/arch/arm/boot/dts/mt8167.dtsi
# 而mt8167.dtsi 是以include的方式包含在mt8167_dtbo.dts, 类似C语言中的头文件包含
kernel-4.4/arch/arm/boot/dts/mt8167_dtbo.dts

继续看Overlay DT,

# kernel-4.4/arch/arm/boot/dts/apollo.dts

#include <generated/autoconf.h>
/dts-v1/;

#ifdef CONFIG_MTK_DTBO_FEATURE
/plugin/;

#define ROOT_NODE &odm

ROOT_NODE {

        mtcpufreq {
                compatible = "mediatek,mt8167-cpufreq";
        };

        mt8167_audio_codec: mt8167_audio_codec {
                compatible = "mediatek,mt8167-codec";
                clocks = <&topckgen CLK_TOP_AUDIO>;
                clock-names = "bus";
                mediatek,afe-regmap = <&afe>;
                mediatek,apmixedsys-regmap = <&apmixedsys>;
                mediatek,dmic-wire-mode = <1>; /* 0(ONE_WIRE) 1(TWO_WIRE) */
                mediatek,headphone-cap-sel = <1>; /* 0(10UF) 1(22UF) 2(33UF) 3(47UF) */
        };
        
        ...

这里需要注意的是,在apollo.dts 中ROOT_NODE 实际的定义是&odm, &符号在dts语法中是引用的意思,这里暂时可以理解为:

# mt8167_dtbo.dts 中odm 最终在merge apollo.dts时会将apollo.dts下的odm相关定义覆盖mt8167_dtbo.dts中的odm相关内容

# 最终合并mt8167_dtbo.dts apollo.dts,部分内容如下.
# 当然这里所指的合并 具体是如何处理的接下来的文章中会详细说明

/dts-v1/;

#include "mt8167.dtsi"
#include "mt6392.dtsi"

/ {
        /* chosen */
        chosen {
                bootargs = "console=ttyS0,921600n1 root=/dev/ram initrd=0x44000200,0x200000";
        };

        firmware: firmware {
                android: android {
                        compatible = "android,firmware";
                        fstab: fstab {
                                compatible = "android,fstab";
                        };
                };
        };
        odm: odm {
                    compatible = "simple-bus";
                    mtcpufreq {
                    compatible = "mediatek,mt8167-cpufreq";
                    };

                    mt8167_audio_codec: mt8167_audio_codec {
                        compatible = "mediatek,mt8167-codec";
                        clocks = <&topckgen CLK_TOP_AUDIO>;
                        clock-names = "bus";
                        mediatek,afe-regmap = <&afe>;
                        mediatek,apmixedsys-regmap = <&apmixedsys>;
                        mediatek,dmic-wire-mode = <1>; /* 0(ONE_WIRE) 1(TWO_WIRE) */
                        mediatek,headphone-cap-sel = <1>; /* 0(10UF) 1(22UF) 2(33UF) 3(47UF) */
                    }; 
                /* reserved for overlay by odm */
                };
};

2. Main DT 和 Overlay DT的构建

在分析之前提前揭晓下答案:

Main DT
    -> mt8167_dtbo.dts & mt8167_dtbo.dts
       -> DTC - mt8167_dtbo.dtb  
          -> CAT mt8167_dtbo.dtb && zImage > zImage-dtb中
也就是Main DT最终会包含在zImage-dtb.img中


Overlay DT
    -> apollo.dts
        -> DTC - apollo.dtb
            -> mkimage apollo.dtb -> odmdtbo.img
也就是Overlay DT最终会包含在odmdtbo.img,作为独立的分区文件写入设备的指定分区

下面来看各自的构建流程.
Main DT,
相关文件以及路径:

# 源文件 
kernel-4.4/arch/arm/boot/dts/mt8167.dtsi
kernel-4.4/arch/arm/boot/dts/mt8167_dtbo.dts

# 相关Makefile
arm/boot/Makefile
scripts/Makefile.lib   

下面来看下具体的构建过程,
在arm/boot/Makefile中有如下相关代码,

# arm/boot/Makefile

# 在kernel的配置文件apollo_defconfig中将CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES 定义为了mt8167_dtbo
# 也就是这里DTB_NAMES := mt8167_dtbo
DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES))
ifneq ($(DTB_NAMES),)
# DTB_LIST := mt8167_dtbo.dtb
DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES))
else
DTB_LIST := $(dtb-y)
endif
DTB_OBJS := $(addprefix $(obj)/dts/,$(DTB_LIST))
targets += $(addprefix dts/,$(DTB_LIST))

$(obj)/zImage:  $(obj)/compressed/vmlinux FORCE
    $(call if_changed,objcopy)
    @$(kecho) '  Kernel: $@ is ready'
# Documentation/kbuild/makefiles.txt -> --- 6.7 Commands useful for building a boot image if_changed
$(obj)/zImage-dtb:  $(obj)/zImage $(DTB_OBJS) FORCE
    $(call if_changed,cat)
    @echo '  Kernel: $@ is ready'

如上图所截取的部分Makfile, 在构建zImage-dtb时, 首先会构建zImage 和 $(DTB_OBJS)-mt8167_dtbo.dtb。
在构建构建生成zImage和mt8167_dtbo.dtb会调自定义的cat指令将mt8167_dtbo.dtb追加到zImage尾部.

# Kernel源码目录: Documentation/kbuild/makefiles.txt -> --- 6.7 Commands useful for building a boot image if_change
$(obj)/zImage-dtb:  $(obj)/zImage $(DTB_OBJS) FORCE
    # 而cat是在scripts/Makefile.lib中定义的.
    $(call if_changed,cat)
    @echo '  Kernel: $@ is ready'
   
# scripts/Makefile.lib   
# cat
# ---------------------------------------------------------------------------
# Concatentate multiple files together
quiet_cmd_cat = CAT     $@
# 这里可以看出,dtb是直接加载在kernel image 后
# $(filter-out FORCE,$^) 获取到的是 zImage 和 mt8167_dtbo.dtb,$@为zImage-dtb
cmd_cat = (cat $(filter-out FORCE,$^) > $@) || (rm -f $@; false)

以上描述了Main DT的构建以及如何追加到zImage.

Overlay DT
相关文件以及路径:

# 源文件 
arch/arm/boot/dts/apollo.dts

# Makefile
arch/arm/boot/dts/Makefile
scripts/drvgen/drvgen.mk
arch/arm/boot/Makefile

下面来看下具体的构建过程,
在arch/arm/Makefile有如下代码:

boot := arch/arm/boot

# 当make 目标zImage-dtb时,会先构建以赖的dtbs,然后是$(DTB_OVERLAY_IMAGE_TAGERT)
zImage-dtb: vmlinux scripts dtbs $(DTB_OVERLAY_IMAGE_TAGERT)
  $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
  
# 构建dtbs
dtbs: prepare scripts
  # 通过debug以及上下文分析得知如下编译指令是:
  # make arch/arm/boot/dts
  # 也就是会执行arch/arm/boot/dts/下的Makefile
  $(Q)$(MAKE) $(build)=$(boot)/dts

继续执行kernel-4.4/arch/arm/boot/dts/下的Makefile

# kernel-4.4/arch/arm/boot/dts/Makefile
# CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE是在arch/arm/configs/apollo_defconfig中定义的,在我这个案例中为CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE_NAMES="apollo"
ifeq ($(strip $(CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE)), y)
DTB_LIST += $(addsuffix .dtb, $(subst $\",,$(CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE_NAMES)))
endif
# 将构建Overlay dts的目标添加到targets中进行apollo.dts的构建, 输出目标为apollo.dtb
targets += $(DTB_LIST)

看到这里你可能会有疑问? 为什么赋值给targets 关于这一点kernel官方文档有做了初步的说明,

     # 对于dtc构建dts的规则做了基本的说明
     dtc
        Create flattened device tree blob object suitable for linking
        into vmlinux. Device tree blobs linked into vmlinux are placed
        in an init section in the image. Platform code *must* copy the
        blob to non-init memory prior to calling unflatten_device_tree().

        To use this command, simply add *.dtb into obj-y or targets, or make
        some other target depend on %.dtb

        A central rule exists to create $(obj)/%.dtb from $(src)/%.dts;
        architecture Makefiles do no need to explicitly write out that rule.

        Example:
                targets += $(dtb-y)
                clean-files += *.dtb
                DTC_FLAGS ?= -p 1024
                
      # 而dtc自定义是在scripts/Makefile.lib中实现
      
quiet_cmd_dtc = DTC     $@

cmd_dtc = mkdir -p $(dir ${dtc-tmp}) ; \
        $(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \
        $(srctree)/scripts/dtc/dtc_overlay -@ -O dtb -o $@ -b 0 \
                -i $(dir $<) $(DTC_FLAGS) \
                -d $(depfile).dtc.tmp $(dtc-tmp) ; \
        cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile)

$(obj)/%.dtb: $(src)/%.dts FORCE
        $(call if_changed_dep,dtc)

dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp)

以上的构建只是将apollo.dts编译生成了apollo.dtb还是并不是最终烧录到dtb分区的Overlay DT.
下面来继续看下中的dtb image是如何生成的.

# arch/arm/Makefile
# 前面的内容已经讲述了dtbs的构建生成了apollo.dtb,下面继续看下$(DTB_OVERLAY_IMAGE_TAGERT)的构建
zImage-dtb: vmlinux scripts dtbs $(DTB_OVERLAY_IMAGE_TAGERT)
  $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
  
  
# DTB_OVERLAY_IMAGE_TAGERT是在scripts/drvgen/drvgen.mk中定义
ifeq ($(strip $(CONFIG_MTK_DTBO_FEATURE)), y)

DTB_OVERLAY_IMAGE_TAGERT := $(DRVGEN_OUT)/odmdtbo.img
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_DTB_OVERLAY_OBJ:=$(DTB_FILES)
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ:=$(DRVGEN_OUT)/$(MTK_PROJECT).mdtb
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MULTIPLE_DTB_OVERLAY_IMG:=$(DRVGEN_OUT)/$(MTK_PROJECT).mimg
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MULTIPLE_DTB_OVERLAY_HDR:=$(srctree)/scripts/multiple_dtbo.py
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MKIMAGE_TOOL:=$(srctree)/scripts/mkimage
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MKIMAGE_CFG:=$(srctree)/scripts/odmdtbo.cfg
$(DTB_OVERLAY_IMAGE_TAGERT) : $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ) dtbs $(PRIVATE_MKIMAGE_TOOL) $(PRIVATE_MKIMAGE_CFG) $(PRIVATE_MULTIPLE_DTB_OVERLAY_HDR)
    @echo Singing the generated overlay dtbo.
    # Singing the generated overlay dtbo.
    #  cat ./arch/arm/boot/dts/apollo.dtb > ./arch/arm/boot/dts/apollo.mdtb || (rm -f ./arch/arm/boot/dts/apollo.mdtb; false)
    cat $(PRIVATE_DTB_OVERLAY_OBJ) > $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ) || (rm -f $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ); false)
    # python kernel-4.4/scripts/multiple_dtbo.py ./arch/arm/boot/dts/apollo.mdtb ./arch/arm/boot/dts/apollo.mimg
    python $(PRIVATE_MULTIPLE_DTB_OVERLAY_HDR) $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ) $(PRIVATE_MULTIPLE_DTB_OVERLAY_IMG)
    # mkimage  apollo.mimg odmdtbo.cfg > odmdtbo.img
    $(PRIVATE_MKIMAGE_TOOL) $(PRIVATE_MULTIPLE_DTB_OVERLAY_IMG) $(PRIVATE_MKIMAGE_CFG)  > $@
.PHONY: odmdtboimage
odmdtboimage : $(DTB_OVERLAY_IMAGE_TAGERT) dtbs
endif

上述截取的部分构建代码只是展示了下在MT8167+Kernel 4.4中是如何构建Overlay DT的, 对于具体的构建过程不同平台会有所差异,但核心逻辑是类似的.
但可以看下scripts/multiple_dtbo.py中实现,

# scripts/multiple_dtbo.py
# dtbo的构建规则可以查看google官方给出的规则
# https://source.android.com/devices/architecture/dto/partitions DTB/DTBO Partitions Format
def parse_dtb(input):
    dtb_list = []
    with open(input, 'rb') as f:
        img_data = f.read()
        img_size = f.tell()
        dtb_offset = 0
        while dtb_offset <= img_size - 8:
            dtb_magic = struct.unpack('>I', img_data[dtb_offset : dtb_offset+4])[0]
            if dtb_magic == 0xD00DFEED:
                dtb_size = struct.unpack('>I', img_data[dtb_offset+4 : dtb_offset+8])[0]
                dtb_list.append(dtb_offset)
                dtb_offset = dtb_offset + dtb_size
            else:
                dtb_offset = dtb_offset + 1
        print('{}.'.format(dtb_list))
        f.closed
    return dtb_list

def write_header(output_file, input_file, dtb_list):
    head[0] = struct.pack('I',0xdeaddead) #Magic number
    head[1] = struct.pack('I', os.path.getsize(input_file))#dtbo size without header
    head[2] = struct.pack('I', 512) #Header Size
    head[3] = struct.pack('I', 2) #Header version
    head[4] = struct.pack('I', len(dtb_list)) #number of dtbo
    head[5] = struct.pack('I', 0xffffffff) #Reserved
    head[6] = struct.pack('I', 0xffffffff) #Reserved
    head[7] = struct.pack('I', 0xffffffff) #Reserved

    i = 0
    for offset in dtb_list:
        head[8 + i] = struct.pack('I', offset)
        i = i + 1

    with open(output_file, 'w') as fo:
        for item in head:
            fo.write("%s" % item)
        with open(input_file, 'r') as fi:
            for line in fi.readlines():
                fo.write(line)
            fi.close
        fo.close

def main(argv):
    if len(argv) < 2:
        print("Usage: python post_process_dtbs.py odmdtbs.dtb odmdtbo.img")
        sys.exit(1)
    input_img = argv[1]
    output_img = argv[2]
    dtb_list = parse_dtb(input_img)

    if len(dtb_list) > 111 :
        print("ERROR: Append too much DTBO")
        sys.exit(1)

    write_header(output_img, input_img, dtb_list)

if __name__ == '__main__':
    main(sys.argv)

通过以上分析可以得出Overlay DT的构建过程如下:

1. 通过dtc编译器将apollo.dts编译为apollo.dtb 
2. 调用python脚本multiple_dtbo.py添加dtbo的规定格式.
3. 调用mkimage生成最终的烧录到dtb分区的odmdtbo.img
3. bootloader中如何加载dtb和dtbo

针对MT8167+Android O平台, bootloader 是由preloader和lk两部分构成,在这里就不做过多的描述。
我们直接来看下在lk中是如何加载相关DTB内容的.
在lk中是如何加载Main DT的,

// vendor/mediatek/proprietary/bootable/bootloader/lk/app/mt_boot/mt_boot.c
// 在跳转到kernel前需要先加载相关的dtb到内存中
int boot_linux_fdt(void *kernel, unsigned *tags,
           unsigned machtype,
           void *ramdisk, unsigned ramdisk_sz)
{
    void *fdt = tags;
    int ret = 0;
    int offset;
    char tmpbuf[TMPBUF_SIZE];
    
    //当前使用的kernel是32bit
    if (g_is_64bit_kernel) {
        ...
    }
    else {
        dprintf(INFO, "32 bits kernel\n");
        zimage_size = *(unsigned int *)((unsigned int)kernel + 0x2c) - *
                  (unsigned int *)((unsigned int)kernel + 0x28);
        //在“Main DT和Overlay DT的构建”这一章节中已经做了说明,在这一平台Main DT是直接跟在zImage后面.
        // 获取到dtb_addr(这里的前提是lk已经把zImage-dtb分区的内容全部加载到内存了)      
        dtb_addr = (unsigned int)kernel + zimage_size;
        wake_up_iothread();
        wait_for_iothread();
    }
    //在获取到dtb_addr后,需要校验下dtb的FD_Magic,FD_Magic=0xd00dfeed
    if (fdt32_to_cpu(*(unsigned int *)dtb_addr) == FDT_MAGIC) {
#if CFG_DTB_EARLY_LOADER_SUPPORT
                dtb_size = fdt32_to_cpu(*(unsigned int *)(fdt + 0x4));
#else
                dtb_size = fdt32_to_cpu(*(unsigned int *)(dtb_addr + 0x4));
#endif
        } else {
                dprintf(CRITICAL, "Can't find device tree. Please check your kernel image\n");
                while (1)
                        ;
        }
    //将加载到内存的Main DT memcpy 到fdt(内存指针)
    memcpy(fdt, (void *)dtb_addr, dtb_size);
    ...
    // 将Overlay DT(odmdtbo.img)从dtbo分区加载到内存中并与Main DT合并,合并后的dtb存储在指针:g_fdt
    /*The memory pointed to by "g_fdt" is the location that the Linux kernel expects to find the device tree, and it is at least a few mega-bytes free. The merged device tree is therefore copied to that space.
    */
    // dtb_overlay函数的具体实现这里就不在展开了,相关代码比较好理解.
    bool rtn = dtb_overlay(fdt, dtb_size);
    
    ...
    if (platform_atag_append) {
        //将fdt追加到atag中,最终通过r2寄存器传递给kernel
        ret = platform_atag_append(fdt);
        if (ret) {
            assert(0);
            return FALSE;
        }
    }       
}
4. kernel中是如何使用获取到合并后的DT

在kernel-4.4/arch/arm/kernel/setup.c中,

// arch/arm/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
    const struct machine_desc *mdesc;

    setup_processor();
    /* arch/arm/kernel/head-common.S中赋值的.
    .long   __atags_pointer                 @ r6
    str     r2, [r6]                        @ Save atags pointer
    而r2是lk(bootloader)用于与内存通信的媒介,传递了atags的内存地址.
    r2包含了dtb pointer.
    */
    mdesc = setup_machine_fdt(__atags_pointer);   
}
参考链接

Google官方文档-Device Tree Overlays
【Device Tree】Device Tree 基础概念
【Device Tree】Kernel中的gpio driver在DTS下是如何初始化的

Logo

更多推荐