Flattened Device Tree

This page is dedicated to the project bringing the flattened device tree (FDT) technology to the FreeBSD. For details contact RafalJaworowski. Work on this project was kindly sponsored by the FreeBSD Foundation.

Abstract

The objective of this project is to provide FreeBSD with the ability to use flattened device trees for description of hardware resources of a computer system and their dependencies, in a platform-neutral and portable way. The primary consumer of this functionality are embedded systems (based on ARM, AVR32, MIPS, PowerPC), but the mechanism can be used in all cases when hardware resources cannot be self-enumerated (as such could be seen as a future replacement for device.hints used in the legacy PC world).

Repository

The code has been committed to the FreeBSD SVN repository as of r210053, and all development is now supposed to happen in the SVN HEAD. The obsolete, no longer maintained original project development P4 branch can be found here.

Introduction

In embedded world there is great variety of systems based on similar silicon chips, but designed into custom boards and devices, where connections of individual components are different and there are no conventions or rules, even across members of the same family of products, for the interconnectios layout and resources allocation. Furthermore, some buses and interconnects are not self-enumerable by definition (unlike PCI or USB), and there has to be some prior knowledge about how they are connected and what their unique identification is. Some of the examples of typical problems are the following:

The concept of a flattened device tree (FDT) is an established and mature way of handling such problems and has been successfully used for Linux/powerpc. It has also been adopted as a basis for Power.org's embedded platform reference specification (ePAPR).

The idea is inherited from Open Firmware IEEE 1275 device-tree notion (part of the regular Open Firmware implementation), but it allows to use device tree mechanism on all systems (not based on OF). It boils down to the following:

Quick start instructions

This section is meant as a convenient helper for people, who want to quickly check out the idea of using FDT approach. It shows only the statically embedded DTB approach, with SHEEVAPLUG kernel config as an example. For other options and more detailed descriptions please see relevant parts of this manual.

Build FDT-enabled world

$ cd ${SRC}
$ make -j 8 buildworld TARGET_ARCH=arm -DWITH_FDT

This creates the dtc compiler utility and FDT-enabled loader(8).

Build FDT-enabled kernel

Enable support in the kernel, make sure the sys/arm/conf/SHEEVAPLUG kernel config file has the following entries:

# Enable FDT support.
options        FDT
makeoptions    FDT_DTS_FILE=sheevaplug.dts
options        FDT_DTB_STATIC

$ make buildkernel TARGET_ARCH=arm KERNCONF=SHEEVAPLUG

The kernel build process includes creation (compilation) of the DTS file into a binary form (DTB), so no explicit steps are required to produce the device tree blob.

Boot

The kernel with statically embedded DTB does not require any additional handling and is ready to be bootstrapped in a standard way.

Definitions

Device tree source (DTS)

The device tree source is a text file which describes hardware resources of a computer system in a human-readable form. See example snippets of a device tree source (DTS) featuring description of all major components (CPU, memory, system-on-chip peripherals, IRQ assignments etc.) as device tree nodes and their properties.

The default locations of DTS files are the following subdirectories of the FreeBSD source repository:

sys/dts
sys/gnu/dts

Device tree blob (DTB)

For regular use case the textual device tree description (DTS file) is first converted (compiled) into a binary object (the device tree blob i.e. the DTB), which is handed over to the final consumer (kernel) for parsing and processing of its contents.

Tools, environment

Device tree compiler (dtc)

A stand-alone tool executed on the host, which transforms (compiles) a textual description of a device tree (DTS) into a binary object (DTB).

Supporting library (libfdt)

Helper library providing basic routines for parsing the device tree blob and retrieving data. It is integral part of the dtc tool, and can be used in any other code which needs to process the DTB.

In FreeBSD for FDT-enabled platforms, libfdt is therefore also used by loader(8) and kernel for accessing the device tree blob.

Building the dtc tool

FreeBSD buildworld WITH_FDT

The typical way of building the device tree compiler is supplying WITH_FDT setting during buildworld procedure, either via src.conf(5) or manually. Note the dtc tool has to be built at bootstrap tools building stage of the buildworld procedure, because the build host is not guaranteed to have the compiler installed and readily available. This is similar to other elementary tools required during further stages of the build process (like config, make and others).

$ cd $SRC
$ make buildworld -DWITH_FDT

Note the WITH_FDT setting is turned on by default for ARM and PowerPC. In order to opt out FDT support on these platforms supply WITHOUT_FDT during buildworld procedure.

Building FDT-enabled kernel

In order to run a kernel driven by a flattened device tree configuration, there are two required items:

The DTB can either be statically embedded as part of the kernel image or a stand alone file, explicitly loaded and handled by loader(8).

Kernel options

There are a couple of FreeBSD kernel options available to manage FDT support.

# Flattened Device Tree (FDT) support.
#

# Enable FDT support.
options        FDT

# Provide a preferred (default) device tree source (DTS) file for the kernel.
# The indicated DTS file will be converted (compiled) into a binary form
# during kernel build stage.
makeoptions    FDT_DTS_FILE=board.dts

# Statically embed device tree blob (DTB) into a kernel image. This option
# allows using device tree on platforms which do not (cannot) run loader(8);
# in these cases we need to embed the DTB as part of kernel data. This option
# requires a DTS file to be specified with FDT_DTS_FILE makeoption.
options        FDT_DTB_STATIC

Note the FDT_DTS_FILE parameter is a file name specified relative to one of the default directories:

sys/dts
sys/gnu/dts

Currently the build system can accept DTS files only from these locations.

Stand-alone DTB

On platforms capable of running loader(8) the default scenario is to use a stand-alone DTB file, which is handled by the loader and handed over to kernel at boot time. In this approach, a DTB should be created at kernel build time:

Statically embedded DTB

Platforms without loader(8) support need to include the DTB file as integral part of the kernel image. For this case the FDT_DTB_STATIC option needs to be supplied in kernel config, as well as the FDT_DTS_FILE makeoption (which becomes mandatory in this scenario). At the buildkernel time the DTB file is automatically created from the specified DTS and embedded in the kernel without any user assistance.

Installing FDT-enabled kernel

The kernel installation procedure is no different for an FDT-enabled kernel: standard installkernel procedure applies. However, for the stand-alone DTB approach (non-static) there are caveats regarding DTB file installation:

Running FDT-enabled kernel

Using loader(8)

Basic usage

The following loader(8) commands are available for the device tree support

fdt cd <fdt_path>
fdt header
fdt ls [fdt_path]
fdt mknode [fdt_path/]<node_name>
fdt mkprop [node_path/]<property_name> <string | [ byte1 byte2 .. ] | <uint32_1 uint32_2 .. > >
fdt prop [node_path/[prop_name value_to_set]]
fdt pwd
fdt rm [node_path/]<node_name | property_name>

Load blob

loader> load -t dtb boot/mpc8555cds.dtb

loader> lsmod
 ...
 0x162f92c: boot/mpc8555cds.dtb (dtb, 0x1eb2)
loader>

Inspect blob header

loader> fdt header

Flattened device tree header (0x162f92c):
 magic                   = 0xd00dfeed
 size                    = 7858
 off_dt_struct           = 0x00000038
 off_dt_strings          = 0x000018ac
 off_mem_rsvmap          = 0x00000028
 version                 = 17
 last compatible version = 16
 boot_cpuid              = 0
 size_dt_strings         = 518
 size_dt_struct          = 6260
loader>

List the device tree

loader> fdt ls

/aliases
/cpus
/cpus/PowerPC,8555@0
/memory
/soc8555@e0000000
/soc8555@e0000000/ecm-law@0
/soc8555@e0000000/ecm@1000
/soc8555@e0000000/memory-controller@2000
/soc8555@e0000000/l2-cache-controller@20000
/soc8555@e0000000/i2c@3000
/soc8555@e0000000/dma@21300
/soc8555@e0000000/dma@21300/dma-channel@0
/soc8555@e0000000/dma@21300/dma-channel@80
/soc8555@e0000000/dma@21300/dma-channel@100
/soc8555@e0000000/dma@21300/dma-channel@180
/soc8555@e0000000/ethernet@24000
/soc8555@e0000000/ethernet@24000/mdio@520
/soc8555@e0000000/ethernet@24000/mdio@520/ethernet-phy@0
/soc8555@e0000000/ethernet@24000/mdio@520/ethernet-phy@1
/soc8555@e0000000/ethernet@24000/mdio@520/tbi-phy@11
/soc8555@e0000000/ethernet@25000
/soc8555@e0000000/ethernet@25000/mdio@520
/soc8555@e0000000/ethernet@25000/mdio@520/tbi-phy@11
/soc8555@e0000000/serial@4500
/soc8555@e0000000/serial@4600
/soc8555@e0000000/crypto@30000
/soc8555@e0000000/pic@40000
/soc8555@e0000000/cpm@919c0
/soc8555@e0000000/cpm@919c0/muram@80000
/soc8555@e0000000/cpm@919c0/muram@80000/data@0
/soc8555@e0000000/cpm@919c0/brg@919f0
/soc8555@e0000000/cpm@919c0/pic@90c00
/pci@e0008000
/pci@e0008000/i8259@19000
/pci@e0009000
loader>

Boot kernel

loader> load -t dtb boot/mpc8555cds.dtb
loader> boot -s

See full booting log.

The fdtbus and simplebus devices represent FDT hierarchy translated into newbus paradigm:

# devinfo
nexus0
  fdtbus0
    lbc0
      cfi0
        cfid0
      cfi1
        cfid1
      rtc0
    simplebus0
      i2c0
        iicbus0
          iic0
      tsec0
        miibus0
          ciphy0
      tsec1
        miibus1
          ciphy1
      uart0
      uart1
      sec0
      openpic0
#

More powerful use cases

Display node properties

loader> fdt prop /soc8555@e0000000
#address-cells = <0x00000001>
#size-cells = <0x00000001>
device_type = "soc"
compatible = "simple-bus"
ranges = <0x00000000 0xe0000000 0x00100000>
bus-frequency = <0x00000000>
loader>

Change existing property value

loader> fdt prop /cpus/PowerPC,8572@0
device_type = "cpu"
reg = <0x00000000>
d-cache-line-size = <0x00000020>
i-cache-line-size = <0x00000020>
d-cache-size = <0x00008000>
i-cache-size = <0x00008000>
timebase-frequency = <0x00000000>
bus-frequency = <0x23c34600>
clock-frequency = <0x00000000>
next-level-cache = <0x00000001>

loader> fdt prop /cpus/PowerPC,8572@0/clock-frequency <15000000>

loader> fdt prop /cpus/PowerPC,8572@0
...
clock-frequency = <0x00e4e1c0>
...

Remove property

loader> fdt prop /aliases
ethernet0 = "/soc8572@ffe00000/ethernet@24000"
ethernet1 = "/soc8572@ffe00000/ethernet@25000"
ethernet2 = "/soc8572@ffe00000/ethernet@26000"
ethernet3 = "/soc8572@ffe00000/ethernet@27000"
serial0 = "/soc8572@ffe00000/serial@4500"
serial1 = "/soc8572@ffe00000/serial@4600"
pci0 = "/pcie@ffe08000"
pci1 = "/pcie@ffe09000"
pci2 = "/pcie@ffe0a000"

loader> fdt rm /aliases/pci2

loader> fdt prop /aliases
ethernet0 = "/soc8572@ffe00000/ethernet@24000"
ethernet1 = "/soc8572@ffe00000/ethernet@25000"
ethernet2 = "/soc8572@ffe00000/ethernet@26000"
ethernet3 = "/soc8572@ffe00000/ethernet@27000"
serial0 = "/soc8572@ffe00000/serial@4500"
serial1 = "/soc8572@ffe00000/serial@4600"
pci0 = "/pcie@ffe08000"
pci1 = "/pcie@ffe09000"

Add property to a node

loader> fdt mkprop /aliases/pci2 "/pcie@ffe0a000"

loader> fdt prop /aliases
pci2 = "/pcie@ffe0a000"
ethernet0 = "/soc8572@ffe00000/ethernet@24000"
ethernet1 = "/soc8572@ffe00000/ethernet@25000"
ethernet2 = "/soc8572@ffe00000/ethernet@26000"
ethernet3 = "/soc8572@ffe00000/ethernet@27000"
serial0 = "/soc8572@ffe00000/serial@4500"
serial1 = "/soc8572@ffe00000/serial@4600"
pci0 = "/pcie@ffe08000"
pci1 = "/pcie@ffe09000"

Add node

loader> fdt ls

/aliases
/cpus
/cpus/PowerPC,8572@0
/cpus/PowerPC,8572@1
/memory
/localbus@ffe05000
/localbus@ffe05000/nor@0,0
/localbus@ffe05000/nor@0,0/ramdisk@0
...

loader> fdt mknode /chosen

loader> fdt ls

/chosen
/aliases
/cpus
/cpus/PowerPC,8572@0
/cpus/PowerPC,8572@1
/memory
/localbus@ffe05000
/localbus@ffe05000/nor@0,0
/localbus@ffe05000/nor@0,0/ramdisk@0
...

Remove node from the device tree

loader> fdt rm /chosen

loader> fdt ls

/aliases
/cpus
/cpus/PowerPC,8572@0
/cpus/PowerPC,8572@1
/memory
/localbus@ffe05000
/localbus@ffe05000/nor@0,0
/localbus@ffe05000/nor@0,0/ramdisk@0
...

Direct kernel boot

Marvell>> tftpboot 900000 sheevaplug/kernel.bin
Using egiga0 device
TFTP from server 10.0.0.204; our IP address is 10.0.2.26
Filename 'sheevaplug/kernel.bin'.
Load address: 0x900000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         ########################################
done
Bytes transferred = 2864724 (2bb654 hex)
Marvell>> go 900000

User space access to the device tree

From the device tree perspective, an FDT-driven platform is not very much different than a genuine Open Firmware. The user can inspect the device tree with ofwdump(8) command in base. The FDT is internally hooked into kernel ofw_bus infrastructure, so it is accessible to user process through the /dev/openfirm node.

# ls -al /dev/openfirm
crw-------  1 root  wheel    0,  24 Jan  1 00:00 /dev/openfirm

List all devices in the tree

# ofwdump -a
Node 0xc06309a0:
  Node 0xc0630a04: aliases
  Node 0xc0630b04: cpus
    Node 0xc0630b30: PowerPC,8555@0
  Node 0xc0630bec: memory
  Node 0xc0630c24: soc8555@e0000000
    Node 0xc0630cac: ecm-law@0
    Node 0xc0630cfc: ecm@1000
    Node 0xc0630d6c: memory-controller@2000
    Node 0xc0630dec: l2-cache-controller@20000
    Node 0xc0630ea4: i2c@3000
    Node 0xc0630f40: dma@21300
      Node 0xc0630fd8: dma-channel@0
      Node 0xc0631074: dma-channel@80
      Node 0xc0631110: dma-channel@100
      Node 0xc06311ac: dma-channel@180
    Node 0xc063124c: ethernet@24000
      Node 0xc0631360: mdio@520
        Node 0xc06313c4: ethernet-phy@0
        Node 0xc063143c: ethernet-phy@1
        Node 0xc06314b4: tbi-phy@11
    Node 0xc0631504: ethernet@25000
      Node 0xc0631618: mdio@520
        Node 0xc0631678: tbi-phy@11
    Node 0xc06316c8: serial@4500
    Node 0xc063175c: serial@4600
    Node 0xc06317f0: crypto@30000
    Node 0xc0631898: pic@40000
    Node 0xc0631930: cpm@919c0
      Node 0xc06319a8: muram@80000
        Node 0xc06319f0: data@0
      Node 0xc0631a40: brg@919f0
      Node 0xc0631aa8: pic@90c00
  Node 0xc0631b58: pci@e0008000
    Node 0xc0631fa8: i8259@19000
  Node 0xc0632068: pci@e0009000
#

List properties of a given node

# ofwdump -p /soc8555
Node 0xc0630c24: soc8555@e0000000
  #address-cells:
    00 00 00 01
  #size-cells:
    00 00 00 01
  device_type:
    73 6f 63 00
    'soc'
  compatible:
    73 69 6d 70 6c 65 2d 62 75 73 00
    'simple-bus'
  ranges:
    00 00 00 00 e0 00 00 00 00 10 00 00
  bus-frequency:
    00 00 00 00
#

# ofwdump -p /soc8555/ethernet@24000
Node 0xc063124c: ethernet@24000
  #address-cells:
    00 00 00 01
  #size-cells:
    00 00 00 01
  cell-index:
    00 00 00 00
  device_type:
    6e 65 74 77 6f 72 6b 00
    'network'
  model:
    54 53 45 43 00
    'TSEC'
  compatible:
    67 69 61 6e 66 61 72 00
    'gianfar'
  reg:
    00 02 40 00 00 00 10 00
  ranges:
    00 00 00 00 00 02 40 00 00 00 10 00
  local-mac-address:
    00 00 00 00 00 00
  interrupts:
    00 00 00 1d 00 00 00 02 00 00 00 1e 00 00 00 02 00 00 00 22
    00 00 00 02
  interrupt-parent:
    00 00 00 02
  tbi-handle:
    00 00 00 03
  phy-handle:
    00 00 00 04
#

Dump dts from loaded dtb

There is another option in case you want to see the DTS you booted with:

# sysctl -b hw.fdt.dtb | dtc -I dtb

Advanced topics

How to convert a platform to FDT

Create DTS

If the dtc has to be (re)built for whatever reason manually, it can be achieved similar to any other user land applications of the base FreeBSD system. In this case:

$ cd gnu/usr.bin/dtc/
$ make
...

$ ./dtc -v
Version: DTC 1.2.0-g46b8d261

$ ./dtc -h
Usage:
        dtc [options] <input file>

Options:
        -h
                This help text
        -q
                Quiet: -q suppress warnings, -qq errors, -qqq all
        -I <input format>
                Input formats are:
                        dts - device tree source text
                        dtb - device tree blob
                        fs - /proc/device-tree style directory
        -o <output file>
        -O <output format>
                Output formats are:
                        dts - device tree source text
                        dtb - device tree blob
                        asm - assembler source
        -V <output version>
                Blob version to produce, defaults to 17 (relevant for dtb
                and asm output only)
        -R <number>
                Make space for <number> reserve map entries (relevant for
                dtb and asm output only)
        -S <bytes>
                Make the blob at least <bytes> long (extra space)
        -p <bytes>
                Add padding to the blob of <bytes> long (extra space)
        -b <number>
                Set the physical boot cpu
        -f
                Force - try to produce output even if the input tree has errors
        -v
                Print DTC version and exit

$ ./dtc -O dtb -o mpc8555cds.dtb -b 0 -p 1024 mpc8555cds.dts
DTC: dts->dtb  on file "mpc8555cds.dts"
$ file mpc8555cds.dtb
mpc8555cds.dtb: data

$ hexdump -C mpc8555cds.dtb
00000000  d0 0d fe ed 00 00 1e b2  00 00 00 38 00 00 18 ac  |...........8....|
00000010  00 00 00 28 00 00 00 11  00 00 00 10 00 00 00 00  |...(............|
00000020  00 00 02 06 00 00 18 74  00 00 00 00 00 00 00 00  |.......t........|
00000030  00 00 00 00 00 00 00 00  00 00 00 01 00 00 00 00  |................|
00000040  00 00 00 03 00 00 00 0b  00 00 00 00 4d 50 43 38  |............MPC8|
00000050  35 35 35 43 44 53 00 00  00 00 00 03 00 00 00 16  |555CDS..........|
00000060  00 00 00 06 4d 50 43 38  35 35 35 43 44 53 00 4d  |....MPC8555CDS.M|
00000070  50 43 38 35 78 78 43 44  53 00 00 00 00 00 00 03  |PC85xxCDS.......|
00000080  00 00 00 04 00 00 00 11  00 00 00 01 00 00 00 03  |................|
00000090  00 00 00 04 00 00 00 20  00 00 00 01 00 00 00 01  |....... ........|
000000a0  61 6c 69 61 73 65 73 00  00 00 00 03 00 00 00 21  |aliases........!|
...

Use FDT infrastrusture

Convert individual components (drivers)

New bindings definitions

References

Flattened Device Trees for Embedded FreeBSD.

Embedded Power Architecture Platform Requirements (ePAPR)

ePAPR Overview

Device trees everywhere

Archives of the devicetree-discuss mailing list

eLinux Device Trees wiki


CategoryProject

FlattenedDeviceTree (last edited 2022-06-27T16:24:51+0000 by BjoernZeeb)