Zephyr Devicetree详解 生成过程和结果

前文提到过Zephyr Devicetree会在构建过程中被转化为C宏,本文将详细描述这一过程。

Devicetree的输入和输出 链接到标题

输入文件 链接到标题

Zephyr中Devicetree文件分为4类

  • source文件:.dts
    • board dts
      • 各个厂商的开发板Devicetree, 描述了板级硬件信息,存放在zephyr/boards下各个厂商的开发板子目录下,例如./zephyr/boards/madmachine/mm_feather/mm_feather.dts
  • include文件:.dtsi
    • .dtsi文件会被.dts文件include
      • soc dtsi
        • 各个厂商提供的soc级dtsi文件,描述soc上的硬件信息,集中放在zephyr/dts下的各个厂商的子目录下,例如./zephyr/dts/arm/nxp/nxp_rt1024.dtsi
      • pinctrl dtsi
        • 各个厂商soc的pin mux的可选配置值,分散存放在
          • modules/hal下各个厂商的子目录,例如,modules/hal/nxp/dts/nxp/nxp_imx/mimx8mn6dvtjz-pinctrl.dtsi
          • zephyr/dts下各厂商的子目录,例如 zephyr/dts/arm/adi/max32/max32680-pinctrl.dtsi
        • 各个开发板的pin mux配置信息,将引用可选配置值配置pin,存放在zephyr/boards下各个厂商的开发板子目录下,例如zephyr/boards/madmachine/mm_feather/mm_feather-pinctrl.dtsi
  • overlay文件: .overlay
    • ​ 针对板级dts,需要新增/修改/禁用一些和硬件相关的功能时,通过overlay文件将原来dts内的配置覆盖达到这一目的. 通常zephyr/boards/下各个厂商与shields的子目录下和zephyr/sample下会放,例如
      • ./zephyr/boards/nxp/mimxrt1170_evk/mimxrt1170_evk_mimxrt1176_cm7_B.overlay
      • ./zephyr/boards/nxp/mimxrt1170_evk/mimxrt1170_evk_mimxrt1176_cm4_B.overlay
      • ./zephyr/boards/shields/esp_8266/esp_8266_arduino.overlay
      • ./zephyr/samples/subsys/zbus/uart_bridge/boards/hifive1_fe310_B.overlay
      • ./zephyr/samples/subsys/zbus/uart_bridge/boards/nrf52840dk_nrf52840.overlay
  • 绑定文件: .yaml - 描述 dts、dtsi和overlay需要遵守的规则。 zephyr/dts/bindings 目录包含所有的绑定文件 链接到标题

输出文件 链接到标题

Zephyr中Devicetree文件的输出 - build/zephyr/zephyr.dts.pre:预处理的后的dts。是一个中间输出文件,它是 gen_defines.py 的输入并用于创建 zephyr.dts 和 devicetree_generated.h - build/zephyr/include/generated/zephyr/devicetree_generated.h 生成的宏,会被devicetree.h include,Zephyr的source code中都是以C宏来访问Devicetree - build/zephyr/zephyr.dts 由dts,dtsi,overlay合并而成的最终dts。由 gen_defines.py 输出。对于调试很有用。 - build/zephyr/edt.pickle, 由 gen_defines.py 输出,可被python识别,用于生成dts.cmake

Devicetree的脚本: 链接到标题

脚本完成Zephyr从Devicetree输入到Devicetree输出的转换,这些脚本存放在zephyr/script/dts下, 主要文件如下

.
├── gen_defines.py
├── gen_driver_kconfig_dts.py
├── gen_dts_cmake.py
├── python-devicetree
│   ├── requirements.txt
│   ├── setup.py
│   ├── src
│   │   └── devicetree
│   │       ├── dtlib.py
│   │       ├── edtlib.py
│   │       ├── grutils.py
  • python-devicetree/下是python解析Devicetree的脚本,可以用到任何Devicetree上
  • gen_defines.py 使用 edtlib 从Devicetree和绑定生成 C 预处理器宏
  • gen_driver_kconfig_dts.py 从Devicetree生成Kconfig选项
  • gen_dts_cmake.py 从Devicetree生成cmake配置信息

下图描述了Zephyr的转化过程 alt text 图片摘自Zephyr官方文档

Zephyr Devicetree构建过程 链接到标题

在Zerphyr中Devicetree在构建过程中被转化为C宏,整个构建过程由CMake控制,体现到Devicetree的CMake处理流程如下:

  • app/CMakeLists.txt
    • zephyr/share/zephyr-package/cmake/ZephyrConfigVersion.cmake
      • zephyr/share/zephyr-package/cmake/ZephyrConfig.cmake
        • zephyr/cmake/modules/zephyr_default.cmake
          • zephyr/cmake/modules/dts.cmake

从应用的CMakeLists.txt开始,最后进入到zephyr/cmake/modules/dts.cmake, dts.cmake 处理Zephyr的Devicetree, 它让Zephyr Devicetree中的信息能够被各个构建阶段和其他的Python脚本所访问和使用。

Devicetree生成结果的使用 链接到标题

各个阶段使用Devicetree的手段和内容: - 对于Zephyr和应用程序源代码文件:调用zephyr/devicetree.h中定义的C宏API访问Devicetree - 访问生成内容build/zephyr/include/generated/zephyr/devicetree_generated.h - 对于其他任意的Python脚本(如twister),使用Python pickle格式序列化的edtlib.EDT对象 - 访问生成内容build/zephyr/edt.pickle - 对于开发者,可以阅读Devicetree合成后的完整.dts文件,可用于调试 - 访问生成内容build/zephyr/zephyr.dts - 对于CMake文件, 使用cmake/modules/extensions.cmake中定义的Devicetree扩展访问 - 对于Kconfig文件,既使用生成的一些Kconfig符号,也使用scripts/kconfig/kconfigfunctions.py中定义的扩展函数

Devicetree生成依赖的工具 链接到标题

dts.cmake 转换Zephyr的Devicetree,依赖下面工具 - 使用pre_dt.cmake进行预先处理 - 遍历app,board,shield,arch,dts,dts/common,include, include/zephyr目录生成DTS_ROOTDTS_ROOT_SYSTEM_INCLUDE_DIRS变量 - C预处理器 - devicetree python - scripts/dts脚本 - dtc工具

Devicetree生成过程 链接到标题

Devicetree的生成过程由zephyr/cmake/modules/dts.cmake控制,具体步骤如下

  1. 获取dts/overlay文件列表:
    • 找到board下的dts文件,结果放在DTS_SOURCE 链接到标题

      ```
        set(DTS_SOURCE ${BOARD_DIR}/${board_string}.dts)
      
        zephyr_file(CONF_FILES ${BOARD_DIR} DTS DTS_SOURCE)
      ```
      
    • 找board/extension下的dts文件,结果放在board_extension_dts_files 链接到标题

        ```
        zephyr_file(CONF_FILES ${BOARD_EXTENSION_DIRS} DTS board_extension_dts_files)
        ```
      
    • 将要处理的dts文件列表放到dts_files中,下面的shield_dts_files变量由shield.cmake输出 链接到标题

        ```
        set(dts_files
        ${DTS_SOURCE}
        ${board_extension_dts_files}
        ${shield_dts_files}
        )
        ```
      
    • 将overlay dts和extension下的overlay dts文件列表放到dts_files中,DTC_OVERLAY_FILEEXTRA_DTC_OVERLAY_FILE变量由 - ``` if(DTC_OVERLAY_FILE) zephyr_list(TRANSFORM DTC_OVERLAY_FILE NORMALIZE_PATHS OUTPUT_VARIABLE DTC_OVERLAY_FILE_AS_LIST) list(APPEND dts_files ${DTC_OVERLAY_FILE_AS_LIST} ) endif()

          if(EXTRA_DTC_OVERLAY_FILE)
            zephyr_list(TRANSFORM EXTRA_DTC_OVERLAY_FILE NORMALIZE_PATHS
                        OUTPUT_VARIABLE EXTRA_DTC_OVERLAY_FILE_AS_LIST)
            list(APPEND
              dts_files
              ${EXTRA_DTC_OVERLAY_FILE_AS_LIST}
              )
          endif()
          ```
      
  2. 获取绑定文件列表:
    • dts_root中获取绑定文件的列表放在变量DTS_ROOT_BINDINGS
    • unset(DTS_ROOT_BINDINGS)
      foreach(dts_root ${DTS_ROOT})
      set(bindings_path ${dts_root}/dts/bindings)
      if(EXISTS ${bindings_path})
          list(APPEND
          DTS_ROOT_BINDINGS
          ${bindings_path}
          )
      endif()
      
      set(vendor_prefixes ${dts_root}/${VENDOR_PREFIXES})
      if(EXISTS ${vendor_prefixes})
          list(APPEND EXTRA_GEN_DEFINES_ARGS --vendor-prefixes ${vendor_prefixes})
      endif()
      endforeach()
      
  3. 预处理dts:
    • 设置预处理器dts_preprocessor, 一般情况都是C预处理器 链接到标题

        ```
        set(dts_preprocessor ${CMAKE_C_COMPILER})
        ```
      
    • dts_files进行预处理,预处理相当于是将dts_files列出的各种文件整合为一个文件zephyr.dts.pre(DTS_POST_CPP)并将其依赖的dtsi和头文件路径列表在zephyr.dts.d(DTS_DEPS)内 链接到标题

        ```
        zephyr_dt_preprocess(
        CPP ${dts_preprocessor}
        SOURCE_FILES ${dts_files}
        OUT_FILE ${DTS_POST_CPP}
        DEPS_FILE ${DTS_DEPS}
        EXTRA_CPPFLAGS ${DTS_EXTRA_CPPFLAGS}
        INCLUDE_DIRECTORIES ${DTS_ROOT_SYSTEM_INCLUDE_DIRS}
        WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR}
        )
        ```
      
  4. 使用gen_defines.py转换得到C和Python可用的文件:
    • 输入:
      • 预处理产生的zephyr.dts.pre
      • Devicetree绑定文件
    • 输出:
      • 并转化生成Devicetree的C宏头文件devicetree_generated.h
      • 合成后的Devicetree文件zephyr.dts
      • python可用的edt.pickle
    • #设置命令变量
      set(CMD_GEN_DEFINES ${PYTHON_EXECUTABLE} ${GEN_DEFINES_SCRIPT}
      --dts ${DTS_POST_CPP}
      --dtc-flags '${EXTRA_DTC_FLAGS_RAW}'
      --bindings-dirs ${DTS_ROOT_BINDINGS}
      --header-out ${DEVICETREE_GENERATED_H}.new
      --dts-out ${ZEPHYR_DTS}.new # for debugging and dtc
      --edt-pickle-out ${EDT_PICKLE}
      ${EXTRA_GEN_DEFINES_ARGS}
      )
      
      #执行命令
      execute_process(
      COMMAND ${CMD_GEN_DEFINES}
      WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
      RESULT_VARIABLE ret
      )
      #移除过程文件
      #命令生成的是devicetree.h.new和zephyr.dts.new,将起拷贝为devicetree.h和zephyr.dts
      zephyr_file_copy(${ZEPHYR_DTS}.new ${ZEPHYR_DTS} ONLY_IF_DIFFERENT)
      zephyr_file_copy(${DEVICETREE_GENERATED_H}.new ${DEVICETREE_GENERATED_H} ONLY_IF_DIFFERENT)
      file(REMOVE ${ZEPHYR_DTS}.new ${DEVICETREE_GENERATED_H}.new)
      
  5. 使用gen_driver_kconfig_dts.py转换得到Kconfig可用的文件:
    • 输入:
      • Devicetree绑定文件
    • 输出:
      • build/Kconfig/Kconfig.dts, 从Devicetree转换成Kconfig的配置项
      • execute_process(
        COMMAND ${PYTHON_EXECUTABLE} ${GEN_DRIVER_KCONFIG_SCRIPT}
        --kconfig-out ${DTS_KCONFIG}
        --bindings-dirs ${DTS_ROOT_BINDINGS}
        WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
        RESULT_VARIABLE ret
        )
        
  6. 使用gen_dts_cmake.py转换得到CMake可用的文件:
    • 输入
      • edt.pickle
    • 输出
      • build/zephyr/dts.cmake,该文件包含了的Devicetree中各节点的属性,方便被CMake系统访问判断节点属性
    • execute_process(
      COMMAND ${PYTHON_EXECUTABLE} ${GEN_DTS_CMAKE_SCRIPT}
      --edt-pickle ${EDT_PICKLE}
      --cmake-out ${DTS_CMAKE}
      WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
      RESULT_VARIABLE ret
      )
      
      #使用前面生成的dts.cmake
      include(${DTS_CMAKE})
      
  • 使用dtc检查前面生成的zephyr.dts是否合法: 链接到标题

      ```
      execute_process(
      COMMAND ${DTC}
      -O dts
      -o - # Write output to stdout, which we discard below
      -b 0
      -E unit_address_vs_reg
      ${DTC_NO_WARN_UNIT_ADDR}
      ${DTC_WARN_UNIT_ADDR_IF_ENABLED}
      ${EXTRA_DTC_FLAGS} # User settable
      ${ZEPHYR_DTS}
      OUTPUT_QUIET # Discard stdout
      WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
      RESULT_VARIABLE ret
      )
      ```
    

参考 链接到标题

https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html