diff --git a/.gitignore b/.gitignore index 72a8882ed48..df7cd55d871 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ *.ilk *.old *.crf +*.dtb* build Debug .vs diff --git a/documentation/6.components/device-driver/INDEX.md b/documentation/6.components/device-driver/INDEX.md index 010d0fc9108..b8600c9e79a 100644 --- a/documentation/6.components/device-driver/INDEX.md +++ b/documentation/6.components/device-driver/INDEX.md @@ -13,3 +13,4 @@ - @subpage page_device_wlan - @subpage page_device_sensor - @subpage page_device_audio +- @subpage page_device_dtc diff --git a/documentation/6.components/device-driver/ofw/dtc.md b/documentation/6.components/device-driver/ofw/dtc.md new file mode 100644 index 00000000000..2cc3c1b34c7 --- /dev/null +++ b/documentation/6.components/device-driver/ofw/dtc.md @@ -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. +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 +/* + * Used "#include" if header file need preprocessor, + * `components/drivers/include` and current directory path is default. + */ +#include +#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 = ; + 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 { + 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") +``` diff --git a/tools/dtc.py b/tools/dtc.py new file mode 100644 index 00000000000..67d7d04f610 --- /dev/null +++ b/tools/dtc.py @@ -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 = []): + 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)