@page page_device_dtc Devicetree Compiler
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. 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.
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:
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:
[...]
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")
Before generating a DTB, you may want to review the basics of DTS syntax and structure: DeviceTree Specification
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:
/*
* 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:
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:
[...]
dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], include_paths = ['dm/include', 'firmware'])
A single SoC may have different board variants.
Example dummy.dtsi (common base):
/* 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 {
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):
/* 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):
/* 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):
/* vendorC dummy */
#include "dummy.dtsi"
&i2c0 {
rtc@0 {
clock-frequency = <32768>;
};
};
Build all DTBs together:
[...]
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
DTC may produce warnings that are irrelevant or noisy. To suppress specific warnings:
[...]
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!
DTC provides additional command-line options (see dtc --help). You can pass raw options like this:
[...]
dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb", options = "--quiet")
dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], options = "--quiet")