Skip to content

[TOOLS] Add DTC (Devicetree Compiler) tools #10431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*.ilk
*.old
*.crf
*.dtb*
build
Debug
.vs
Expand Down
1 change: 1 addition & 0 deletions documentation/6.components/device-driver/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
- @subpage page_device_wlan
- @subpage page_device_sensor
- @subpage page_device_audio
- @subpage page_device_dtc
274 changes: 274 additions & 0 deletions documentation/6.components/device-driver/ofw/dtc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
@page page_device_dtc Devicetree Compiler

# Introduction to the DTC

Device Tree Compiler, dtc, takes as input a device-tree in a given format and outputs a device-tree in another format for booting kernels on embedded systems.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

takes a device-tree as input in a ......

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically, the input format is "dts" (device-tree source), a human readable source format, and creates a "dtb" (device-tree binary), or binary format as output.

> If the dtc tool is not installed on your host system, the dtc module will guide you through the installation.

## Generate DTS

When you have a DTB or FDT file from firmware or another runtime system, you might want to convert it into a DTS file for easier reading.
You can do this in Python or your SConscript file. For example, assuming you have `dummpy.dtb`:

```python
import os, sys

RTT_ROOT = os.getenv('RTT_ROOT')
sys.path.append(RTT_ROOT + '/tools')

from building import *
import dtc

dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb")
```

This will generate a dummpy.dts in the current directory. If a file with the same name already exists, it will be replaced.
To avoid overwriting, you can specify a different output name:

```python
[...]

dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb", "dummpy-tmp.dts")
# or
dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb", dts_name = "dummpy-tmp.dts")
```

## Generate DTB

Before generating a DTB, you may want to review the basics of DTS syntax and structure: [DeviceTree Specification](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html)

### Include and Macros

By default, dtc does not support C-style preprocessing (like cpp), but you can use the C preprocessor with your DTS files.
Don't worry — our dtc module already includes this step.

If your DTS file uses dt-bindings headers or macros, you can write something like:

```c
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dts 语法是 json,所以这里感觉用

```json

更好

/*
* Used "#include" if header file need preprocessor,
* `components/drivers/include` and current directory path is default.
*/
#include <dt-bindings/size.h>
#include "dummy.dtsi"
/* Well, if dtsi is simple, you can use "/include/", it is supported by dtc */
/include/ "chosen.dtsi"

#define MMIO_BASE 0x10000
#define MMIO_SIZE SIZE_GB
#define MEM_BASE (MMIO_BASE + MMIO_SIZE)

#ifndef CPU_HARDID
#define CPU_HARDID 0
#endif

#ifndef SOC_INTC
#define SOC_INTC intc_a
#endif

/ {
#address-cells = <2>;
#size-cells = <2>;
/*
* Macros after "&" will be replaced,
* there will affect the interrupt controller in this SoC.
*/
interrupt-parent = <&SOC_INTC>;

[...]

memory {
/* When there is a calculation, please use "()" to include them */
reg = <0x0 MEM_BASE 0x0 (3 * SIZE_GB)>;
device_type = "memory";
};

cpus {
#size-cells = <0>;
#address-cells = <1>;

/* Macros after "@" will be replaced */
cpu0: cpu@CPU_HARDID {
reg = <CPU_HARDID>;
device_type = "cpu";
};
};

/* Macros replace support phandle name, too */
intc_a: intc-a {
interrupt-controller;
};

intc_b: intc-b {
interrupt-controller;
};

[...]
};
```

To generate the DTB:

```python
import os, sys

RTT_ROOT = os.getenv('RTT_ROOT')
sys.path.append(RTT_ROOT + '/tools')

from building import *
import dtc

dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"]
```

To append more include paths, for example, SoC DM headers:

```python
[...]

dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], include_paths = ['dm/include', 'firmware'])
```

### Multiple DTB

A single SoC may have different board variants.
Example `dummy.dtsi` (common base):

```c
/* SoC dummy */
/ {
#address-cells = <2>;
#size-cells = <2>;
model = "Dummy SoC Board";

[...]

chosen {
bootargs = "cma=8M coherent_pool=2M";
};

reserved-memory {
#address-cells = <2>;
#size-cells = <2>;

isp_shm@100000 {
reg = <0x0 0x100000 0x0 0x100000>;
};

dsp_shm@200000 {
reg = <0x0 0x200000 0x0 0x100000>;
};
};

dsp {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我记得更好的做法是,对于 soc 级别的 dtsi 文件中,所有的设备都应该 disabled。在 board level 的 dts 文件中再选择性的 enable 即可。

status = "okay";
};

buddy {
isp = <&{/reserved-memory/isp_shm@100000}>;
dsp = <&{/reserved-memory/dsp_shm@200000}>;
};

uart0: uart {
status = "disabled";
};

i2c0: i2c {
status = "okay";
};

[...]
};
```

For a vendor-specific variant (Vendor A):

```c
/* vendorA dummy */
#include "dummy.dtsi"

/ {
/* No phandle name can modify in place */
chosen {
bootargs = "console=uart0 cma=8M coherent_pool=2M";
};
};

/* Reference and modify direct if has phandle name */
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_m1>;
};

&i2c0 {
status = "disabled";
};
```

To remove nodes or properties (Vendor B):

```c
/* vendorB dummy */
#include "dummy.dtsi"

/delete-node/ &dsp_shm;

/ {
/* Delete in place if no phandle name */
/delete-node/ dsp;

/* Delete property */
buddy {
/delete-property/ dsp;
};
};
```

To add new devices (Vendor C):

```c
/* vendorC dummy */
#include "dummy.dtsi"

&i2c0 {
rtc@0 {
clock-frequency = <32768>;
};
};
```

Build all DTBs together:

```python
[...]

dtc.dts_to_dtb(RTT_ROOT, ["dummpy-vendorA.dts", "dummpy-vendorB.dts", "dummpy-vendorC.dts"])
```

This will produce `dummpy-vendorA.dtb`, `dummpy-vendorB.dtb`, and `dummpy-vendorC.dtb`

## Warnings

DTC may produce warnings that are irrelevant or noisy.
To suppress specific warnings:

```python
[...]

dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], ignore_warning = ["simple_bus_reg", "unit_address_vs_reg", "clocks_is_cell", "gpios_property"])
```

Make sure your DTS is valid!

## Raw options

DTC provides additional command-line options (see dtc --help). You can pass raw options like this:

```python
[...]

dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb", options = "--quiet")
dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], options = "--quiet")
```
52 changes: 52 additions & 0 deletions tools/dtc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# Copyright (c) 2006-2023, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2023-05-10 GuEe-GUI the first version
#

import os, re

from building import *

__dtc_install_tip = """
You should install dtc (devicetree compiler) in your system:
Linux:
Debian/Ubuntu: apt-get install device-tree-compiler
Arch/Manjaro: pacman -Sy dtc

MacOS:
brew install dtc

Windows (MinGW):
msys2: pacman -S dtc
"""

def __check_dtc(value):
if value != 0 and os.system("dtc -v") != 0:
print(__dtc_install_tip)

def dts_to_dtb(RTT_ROOT, dts_list, options = "", include_paths = [], ignore_warning = []):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

主要来说是加了两个dts/dtb互转的函数?或者是否可以加scons的build?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

并不知道怎么给 scons 添加 build 规则,也没有找到相关资料

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
高级设备树编译和转换的SCons Builder
支持DTS和DTB文件之间的互相转换,包含更多编译选项
"""

import os
import SCons.Builder
import SCons.Action
import SCons.Util

def dtc_action(target, source, env):
    """DTS到DTB的转换动作"""
    dtc = env.get('DTC', 'dtc')
    dts_file = str(source[0])
    dtb_file = str(target[0])
    
    # 构建命令
    cmd_parts = [dtc]
    
    # 添加编译选项
    if env.get('DT_FLAGS'):
        cmd_parts.extend(env['DT_FLAGS'])
    
    # 添加包含路径
    for include_path in env.get('DT_INCLUDES', []):
        cmd_parts.extend(['-i', include_path])
    
    # 添加预处理器定义
    for define in env.get('DT_DEFINES', []):
        cmd_parts.extend(['-D', define])
    
    # 基本参数
    cmd_parts.extend(['-I', 'dts', '-O', 'dtb', '-o', dtb_file, dts_file])
    
    cmd = ' '.join(cmd_parts)
    return env.Execute(cmd)

def dts_action(target, source, env):
    """DTB到DTS的转换动作"""
    dtc = env.get('DTC', 'dtc')
    dtb_file = str(source[0])
    dts_file = str(target[0])
    
    # 构建命令
    cmd_parts = [dtc]
    
    # 添加反编译选项
    if env.get('DT_DECOMPILE_FLAGS'):
        cmd_parts.extend(env['DT_DECOMPILE_FLAGS'])
    
    # 基本参数
    cmd_parts.extend(['-I', 'dtb', '-O', 'dts', '-o', dts_file, dtb_file])
    
    cmd = ' '.join(cmd_parts)
    return env.Execute(cmd)

def dt_validate_action(target, source, env):
    """设备树验证动作"""
    dtc = env.get('DTC', 'dtc')
    dts_file = str(source[0])
    
    cmd = f'{dtc} -I dts -O dtb -f {dts_file}'
    return env.Execute(cmd)

def dt_overlay_action(target, source, env):
    """设备树覆盖编译动作"""
    dtc = env.get('DTC', 'dtc')
    base_dts = str(source[0])
    overlay_dts = str(source[1])
    output_dtb = str(target[0])
    
    # 先编译基础设备树
    base_dtb = str(target[0]) + '.base'
    base_cmd = f'{dtc} -I dts -O dtb -o {base_dtb} {base_dts}'
    
    # 编译覆盖
    overlay_dtb = str(target[0]) + '.overlay'
    overlay_cmd = f'{dtc} -I dts -O dtb -o {overlay_dtb} {overlay_dts}'
    
    # 应用覆盖
    apply_cmd = f'{dtc} -I dtb -O dtb -o {output_dtb} -@ {base_dtb} {overlay_dtb}'
    
    return env.Execute(base_cmd) and env.Execute(overlay_cmd) and env.Execute(apply_cmd)

def generate(env):
    """生成高级设备树builder"""
    
    # 创建DTS到DTB的builder
    dtc_builder = SCons.Builder.Builder(
        action=SCons.Action.Action(dtc_action, 'Compiling DTS to DTB: $TARGET'),
        suffix='.dtb',
        src_suffix='.dts',
        source_scanner=None,
        target_scanner=None,
        emitter=None
    )
    
    # 创建DTB到DTS的builder
    dts_builder = SCons.Builder.Builder(
        action=SCons.Action.Action(dts_action, 'Decompiling DTB to DTS: $TARGET'),
        suffix='.dts',
        src_suffix='.dtb',
        source_scanner=None,
        target_scanner=None,
        emitter=None
    )
    
    # 创建设备树验证builder
    validate_builder = SCons.Builder.Builder(
        action=SCons.Action.Action(dt_validate_action, 'Validating DTS: $SOURCE'),
        suffix='.valid',
        src_suffix='.dts',
        source_scanner=None,
        target_scanner=None,
        emitter=None
    )
    
    # 创建设备树覆盖builder
    overlay_builder = SCons.Builder.Builder(
        action=SCons.Action.Action(dt_overlay_action, 'Applying DTS overlay: $TARGET'),
        suffix='.dtb',
        src_suffix='.dts',
        source_scanner=None,
        target_scanner=None,
        emitter=None
    )
    
    # 将builder添加到环境中
    env.Append(BUILDERS={
        'DTS2DTB': dtc_builder,
        'DTB2DTS': dts_builder,
        'DTValidate': validate_builder,
        'DTOverlay': overlay_builder
    })
    
    # 设置默认的dtc工具路径
    if 'DTC' not in env:
        dtc_path = env.WhereIs('dtc')
        if dtc_path:
            env['DTC'] = dtc_path
        else:
            env['DTC'] = 'dtc'
    
    # 设置默认编译选项
    if 'DT_FLAGS' not in env:
        env['DT_FLAGS'] = ['-W', 'no-unit_address_vs_reg']
    
    if 'DT_DECOMPILE_FLAGS' not in env:
        env['DT_DECOMPILE_FLAGS'] = ['-s']
    
    # 设置默认包含路径
    if 'DT_INCLUDES' not in env:
        env['DT_INCLUDES'] = []
    
    # 设置默认预处理器定义
    if 'DT_DEFINES' not in env:
        env['DT_DEFINES'] = []

def exists(env):
    """检查builder是否存在"""
    return True 

在SConstruct中

# 导入设备树builder
import dt_builder

# 添加基本设备树builder
dt_builder.generate(env)

# 配置DTC工具路径(如果需要自定义路径)
# env['DTC'] = r'C:\path\to\your\dtc.exe'

# 设置高级编译选项
env['DT_FLAGS'] = ['-W', 'no-unit_address_vs_reg', '-W', 'no-graph_port']
env['DT_DECOMPILE_FLAGS'] = ['-s', '--sort']
env['DT_INCLUDES'] = ['./include']  # 如果有include目录
env['DT_DEFINES'] = ['CONFIG_DEBUG=1']

# 基本设备树编译任务
# DTS到DTB转换
env.DTS2DTB('example.dtb', 'example.dts')

# DTB到DTS转换
env.DTB2DTS('example_decompiled.dts', 'example.dtb')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

整体打包文件
scons-dbg.zip

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

后续再改进看看

path = GetCurrentDir() + '/'
warning_ops = ""
for warning in ignore_warning:
warning_ops += " -W no-" + warning
for dts in dts_list:
dtb = dts.replace('.dts', '.dtb')
if not os.path.exists(path + dtb) or os.path.getmtime(path + dtb) < os.path.getmtime(path + dts):
tmp_dts = dts + '.tmp'
Preprocessing(dts, None, output = tmp_dts, CPPPATH=[RTT_ROOT + '/components/drivers/include'] + include_paths)
ret = os.system("dtc -I dts -O dtb -@ -A {} {} {} -o {}".format(warning_ops, options, path + tmp_dts, path + dtb))
__check_dtc(ret)
if os.path.exists(path + tmp_dts):
os.remove(path + tmp_dts)

def dtb_to_dts(RTT_ROOT, dtb_name, dts_name = None, options = ""):
path = GetCurrentDir() + '/'
if dts_name == None:
dts_name = re.sub(r'\.dtb[o]*$', '.dts', dtb_name)
ret = os.system("dtc -I dtb -O dts {} {} -o {}".format(options, path + dtb_name, path + dts_name))
__check_dtc(ret)