Devicetree
Introduction to the coreboot devicetree
The first thing that may come to mind when one hears “DeviceTree” is a different sort of description file that is generally passed to the Linux kernel to describe a system’s components. Both that devicetree and coreboot’s devicetree serve fundamentally the same purpose, but are otherwise unrelated and have completely different syntax. The term devicetree was used long before either version was created, and was initially used in coreboot as a generic term.
coreboot’s devicetree’s main use is to define and describe the runtime configuration and settings of the hardware on a board, chip or device level. It defines which of the functions of the chips on the board are enabled, and how they’re configured.
The devicetree file is parsed during the build process by a utility
named sconfig
, which translates the devicetree into a tree of C
structures containing the included devices. This code is placed in the
file static.c
and a couple of header files, all under the build
directory. This file is then built into the binaries for the various
coreboot stages and is referred to during the coreboot boot process.
For the early stages of the coreboot boot process, the data that is
generated by sconfig
is a useful resource, but this structure is the
critical architectural glue of ramstage. This structure gets filled in
with pointers to every chip’s initialization code, allowing ramstage to
find and initialize those devices through the chip_operations
structures.
History of coreboot’s devicetree
The initial devicetree in coreboot was introduced in 2003 by Ron Minnich as a part of the linuxbios config file, ‘config.lb’. At this point both the devicetree and config options were in the same file. In 2009, when Kconfig was added into the coreboot build, devicetree was split out into its own file for each mainboard in a commit with this message:
devicetree.cb
The devicetree that formerly resided in src/mainboard/*/*/Config.lb.
Just without the build system crap
The devicetree structure was initially mainly used only in ramstage for PCI device enumeration, configuration and resource allocation. It has since expanded for use in the pre-ram stages as a read-only structure.
The language used in the devicetree has been expanded greatly since it was first introduced as well, adding new features every year or so.
Devicetree Registers
In coreboot, the devicetree register setting is one of the two main methods used to configure a board’s properties. In this way, devicetree is similar in function to Kconfig. It’s more flexible in many ways as it can specify not only single values, but also arrays or structures. It’s also even more static than Kconfig because there’s no update mechanism for it other than editing the devicetree files.
Chip-specific configuration values are often set using register
definitions within a chip
block, corresponding to a struct
defined
in the chip’s chip.h
file.
For example, in a chip drivers/gpio/acpi
block, you might set a GPE:
register "gpe0_sts" = "0x42"
Adding new static configuration options: Devicetree or Kconfig
When adding options for a new board or chip, there is frequently a decision that needs to be made about how the option should be added. Using the devicetree or Kconfig are the two typical methods of build-time configuration. Below are some general guidelines on when to use each.
Kconfig should be used if the option configures the build in a Makefile, or if the option is something that should be user selectable. Kconfig is also preferred if the configuration is a global option and not limited to a single chip. Another thing Kconfig excels at is handling decisions based on other configuration options, which devicetree cannot do.
Devicetree should obviously be used to define the hardware hierarchy. It’s also preferred if the option is only used in C code and is static for a mainboard, or if the option is chip-specific. As mentioned earlier, devicetree registers can also define structures or arrays, which Kconfig cannot.
Both Kconfig and devicetree can be used in C code for runtime
configuration, but there’s a significant difference in how they are
handled. Because Kconfig generates a #define
for the choice, the
compiler can eliminate code paths not used by the option. Devicetree
options, however, are actual runtime selections, and the code for all
choices remains in the final build.
Basic Devicetree Syntax
The coreboot devicetree uses a custom language parsed by the sconfig
utility. Here’s a brief overview of the main keywords and concepts:
chip <directory>
: Defines a collection of devices associated with the code in the specified directory.sconfig
may also parse achip.h
file within this directory for register definitions.device <type> <id> [on|off] [alias <name>] ... end
: Defines a specific hardware device.<type>
: Specifies the device type (e.g.,pci
,cpu_cluster
,i2c
).<id>
: A unique identifier for the device within its type/bus (e.g., PCI BDF17.0
, I2C address0x50
).on
/off
: Enables or disables the device definition.alias <name>
: Assigns a human-readable alias for referencing this device elsewhere (often used inchipset.cb
).end
: Marks the end of the device definition block. Registers and other properties are defined betweendevice
andend
.
register "<name>" = <value>
: Sets the value of a configuration register defined in the correspondingchip.h
structure. The value can be a number, string, or complex structure initialization.probe <field> <option>
: Used for firmware configuration (fw_config
), indicating a setting should be probed at runtime.ops "<identifier>"
: Associates achip_operations
structure with the device, used primarily in ramstage for device initialization.fw_config field <name> size <bits> ... end
: Defines a firmware configuration field, often used for passing board-specific data to payloads. Options within the field are defined usingoption
.ref <alias>
: Used withindevice
definitions indevicetree.cb
oroverridetree.cb
to refer to a device defined (usually via analias
) in a lower-level file likechipset.cb
.# <comment text>
: Single-line comments.
Device definitions can be nested within chip
blocks. end
keywords
close the current block (device
or chip
).
Three levels of devicetree files
There are currently three different levels of devicetrees used to build
up the structure of components and register values in coreboot. From
the lowest, most general level to the highest and most specific, they
are chipset.cb
, devicetree.cb
, and overridetree.cb
.
Unless there’s a specific reason to name them something other than these names, they should be used.
For newer SoCs and chipsets, there will generally be a chipset.cb
file.
Every mainboard requires a devicetree.cb
file, although it can be empty
if everything is inherited from the chipset.cb
. An overridetree.cb
file is only required if variants have differences from the primary
mainboard’s devicetree.cb
.
SoC / chipset level, chipset.cb
The chipset.cb
file was added in October 2020, allowing a single
chipset or SoC to provide a “base level” devicetree, reducing
duplication between mainboards.
The chipset.cb
file also typically defines human-readable “aliases”
for particular devices so that mainboards can use those instead of PCI
device/function numbers or other hardware identifiers.
The use of the chipset.cb
file is specified in Kconfig by the
CHIPSET_DEVICETREE
symbol, which provides the path to the file.
In a chipset.cb
file, you might see lines like this:
# Chip definition for the SoC/chipset itself
chip soc/intel/common/block
# Define PCI device 17.0, alias it to "sata", and default it off
device pci 17.0 alias sata off end
# Define PCI device 1e.0, alias it to "uart0", and default it off
device pci 1e.0 alias uart0 off end
end # chip soc/intel/common/block
This defines the devices, assigns aliases, and sets their default state.
Primary mainboard level, devicetree.cb
Each mainboard must have a devicetree.cb
file. The filename and path are
typically set by the DEVICETREE
Kconfig symbol, defaulting to
src/mainboard/<VENDOR>/<BOARD>/devicetree.cb
.
If a mainboard using the above chipset.cb
wanted both devices enabled,
its devicetree.cb
might contain:
# Reference the SATA device by its alias and enable it
device ref sata on end
# Reference the UART0 device by its alias and enable it
device ref uart0 on end
The ref
keyword looks up the device (usually by alias) defined in a
lower-level file (chipset.cb
in this case) and modifies its properties.
Mainboard variant level, overridetree.cb
Introduced in 2018 to reduce duplication and maintenance for board
variants, the overridetree.cb
file is the most specific level.
This allows a base devicetree.cb
at the top mainboard level shared by
all variants. Each variant then only needs an overridetree.cb
to
specify its differences.
The override tree filename is set in Kconfig with the
OVERRIDE_DEVICETREE
symbol and is typically named overridetree.cb
.
Finally, if one variant of the mainboard lacked a SATA connector, it
could disable the SATA device again using the following in its specific
overridetree.cb
:
# Reference the SATA device by alias and disable it for this variant
device ref sata off end
Additional files
chip.h
files
coreboot looks at a “chip” as a collection of devices. This collection
can be a single logical device or multiple different ones. The chip
keyword starts this collection. Following the chip
keyword is a
directory path (relative to src/
) containing the code for that chip
or logical block of hardware.
There may optionally be a chip.h
file in that directory. If present,
sconfig
parses this file to define a C structure containing the
“register definitions” for the chip. The values for this structure’s
members are set using the register
keyword in one of the devicetree
files (chipset.cb
, devicetree.cb
, overridetree.cb
). If not
explicitly set, members typically default to 0 or follow standard C
initialization rules. The chip.h
file frequently also contains C
macros, enums, and sub-structures used for setting the members of the
main register structure.
The C structure for the chip’s register definition is named after the
directory containing the chip.h
file, with slashes (/
) changed to
underscores (_
), and _config
appended. The leading src/
is omitted.
This means that a line in a devicetree file like:
chip drivers/i2c/hid
would cause sconfig
to look for src/drivers/i2c/hid/chip.h
. If found,
the register definition structure it contains would be named
drivers_i2c_hid_config
.
Here is the content of src/drivers/i2c/hid/chip.h
:
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __DRIVERS_I2C_HID_CHIP_H__
#define __DRIVERS_I2C_HID_CHIP_H__
#include <drivers/i2c/generic/chip.h>
#define I2C_HID_CID "PNP0C50"
struct drivers_i2c_hid_config {
struct drivers_i2c_generic_config generic;
uint8_t hid_desc_reg_offset;
};
#endif /* __I2C_HID_CHIP_H__ */
In a devicetree, you could set hid_desc_reg_offset
like this:
chip drivers/i2c/hid
device i2c 0x2c on
# Set the HID descriptor register offset
register "hid_desc_reg_offset" = "0x01"
end
end
The sconfig
utility and generated files
util/sconfig
sconfig
is the tool that parses the coreboot devicetrees and turns
them into a collection of C structures. This is a coreboot-specific
tool, built using flex & bison to define and parse the domain-specific
language used by coreboot’s devicetree.
sconfig
is called by the makefiles during the build process and doesn’t
generally need to be run directly. If run manually (e.g.,
build/util/sconfig/sconfig --help
), it shows its command-line options.
The exact options might vary slightly, but typically include:
usage: sconfig <options>
-c | --output_c : Path to output static.c file (required)
-r | --output_h : Path to header static.h file (required)
-d | --output_d : Path to header static_devices.h file (required)
-f | --output_f : Path to header static_fw_config.h file (required)
-m | --mainboard_devtree : Path to mainboard devicetree file (required)
-o | --override_devtree : Path to override devicetree file (optional)
-p | --chipset_devtree : Path to chipset/SOC devicetree file (optional)
sconfig
inputs
The sconfig
input files chip.h
, chipset.cb
, devicetree.cb
, and
overridetree.cb
were discussed previously. As the usage above shows,
the only required input file is the mainboard devicetree (-m
). The
additional devicetree files, chipset.cb
(-p
) and overridetree.cb
(-o
), are optional. The chip.h
files do not need to be specified on
the command line; their locations are determined by the chip
directory
paths within the .cb
files.
Constructing the devicetree input files will be discussed later.
sconfig
outputs
static.c
This is the primary C file generated by sconfig
. It contains the static
definitions of the device tree structures, including device nodes, bus
links, and register configuration data.
For historic reasons, static.c
is generated in the
build/mainboard/<VENDOR>/<BOARD>
directory.
static.h
The static.h
file is the main header file included by most coreboot C
files that need access to the devicetree data. It is included by
src/include/device/device.h
, which provides the primary API
(definitions, structures, function prototypes) for interacting with the
devicetree-generated output.
static.h
used to contain all generated declarations directly. As of
October 2020, it simply includes the other two generated header files
(static_devices.h
and static_fw_config.h
). This separation allows
the firmware config options (fw_config
) to be used independently, for
example, by a payload.
static_devices.h
The file static_devices.h
contains extern
declarations for all the
device structures (struct device
) defined in static.c
. This allows
other C files to reference the generated device tree nodes.
static_fw_config.h
static_fw_config.h
contains only the FW_CONFIG_FIELD_*
macro results,
derived from fw_config
entries in the devicetree. This makes it easily
consumable by payloads or other components needing platform FW_CONFIG
data without pulling in the full device tree structure.
Devicetree Example
A very simple devicetree
This is the devicetree.cb
file from
src/mainboard/sifive/hifive-unleashed
, with line numbers added for
reference. Non-x86 devicetree files are often simpler than their x86
counterparts.
1 # SPDX-License-Identifier: GPL-2.0-only
2 chip soc/sifive/fu540
3 device cpu_cluster 0 on end
4 end
This can be broken down as follows:
Line 1: Comments start with #
. This line is the SPDX license
identifier for the file.
Line 2: chip soc/sifive/fu540
starts a block for the SiFive FU540 SoC.
sconfig
will look for code and potentially a chip.h
in
src/soc/sifive/fu540/
.
Line 3: device cpu_cluster 0 on end
defines a device of type
cpu_cluster
with ID 0
. It’s marked as enabled (on
). Since there are
no registers or other properties defined between device
and end
, this
is a simple enablement entry.
Line 4: end
closes the block started by the chip
keyword on line 2.
Generated files
Continuing with the simple sifive/hifive-unleashed
mainboard example,
these are the files generated by sconfig
from the devicetree above (as
of mid-2022; exact output can change). Because the input devicetree is
minimal, the generated files are also quite sparse.
build/static.h
#ifndef __STATIC_DEVICE_TREE_H
#define __STATIC_DEVICE_TREE_H
#include <static_fw_config.h>
#include <static_devices.h>
#endif /* __STATIC_DEVICE_TREE_H */
(Includes the other generated headers.)
build/static_devices.h
#ifndef __STATIC_DEVICES_H
#define __STATIC_DEVICES_H
#include <device/device.h>
/* expose_device_names */
#endif /* __STATIC_DEVICE_NAMES_H */
(Includes device/device.h
but contains no actual device externs beyond
the implicit root device, as the simple example didn’t define complex
devices requiring separate structs.)
build/static_fw_config.h
#ifndef __STATIC_FW_CONFIG_H
#define __STATIC_FW_CONFIG_H
#endif /* __STATIC_FW_CONFIG_H */
(Empty because the example devicetree.cb
did not use fw_config
.)
build/mainboard/sifive/hifive-unleashed/static.c
Includes
1 #include <boot/coreboot_tables.h>
2 #include <device/device.h>
3 #include <device/pci.h>
4 #include <fw_config.h>
5 #include <static.h>
Lines 1-5: Includes header files required for the following structure definitions and macros.
Declarations for chip-ops
6
7 #if !DEVTREE_EARLY
8 __attribute__((weak)) struct chip_operations mainboard_ops = {};
9 extern struct chip_operations soc_sifive_fu540_ops;
10 #endif
Lines 7 & 10: The ops
structures inside this #if !DEVTREE_EARLY
block
are only relevant and linked in ramstage.
Lines 8-9: Declarations for chip_operations
structures. This section
expands as more chips are added to the devicetree.
Line 8:
mainboard_ops
is always present. It’s defined asweak
because the mainboard C code may or may not provide this structure.Line 9: This
extern
is generated by thechip soc/sifive/fu540
declaration in thedevicetree.cb
. There will be a similar line for everychip
declared.
STORAGE
definition
11
12 #define STORAGE static __unused DEVTREE_CONST
Line 12: This macro conditionally adds const
based on the build stage.
It resolves to static __unused const
in early stages (pre-RAM) and
static __unused
in ramstage, where the structures might be modified.
Structure definitions
13
14
15 /* pass 0 */
16 STORAGE struct bus dev_root_links[];
17 STORAGE struct device _dev_0;
18 DEVTREE_CONST struct device * DEVTREE_CONST last_dev = &_dev_0;
Lines 16-18: Forward declarations of the static structures generated by
sconfig
based on the devicetree input. _dev_0
corresponds to the
cpu_cluster 0
device.
Register Structures
19
20 /* chip configs */
Line 20: This section is empty for this mainboard because the
soc/sifive/fu540/chip.h
file (if it exists) does not define a register
structure, or the devicetree did not instantiate it using register
.
Otherwise, this section would contain the static initialization of chip
configuration structures based on register
entries.
dev_root
structure
Lines 21-44: dev_root
. This structure represents the root of the
coreboot device tree. It is always generated, regardless of the content
of the devicetree.cb
file. It serves as the entry point for traversing
the tree.
21
22 /* pass 1 */
23 DEVTREE_CONST struct device dev_root = {
24 #if !DEVTREE_EARLY
25 .ops = &default_dev_ops_root,
26 #endif
27 .bus = &dev_root_links[0],
28 .path = { .type = DEVICE_PATH_ROOT },
29 .enabled = 1,
30 .hidden = 0,
31 .mandatory = 0,
32 .on_mainboard = 1,
33 .link_list = &dev_root_links[0],
34 .sibling = NULL,
35 #if !DEVTREE_EARLY
36 .chip_ops = &mainboard_ops,
37 .name = mainboard_name,
38 #endif
39 .next=&_dev_0,
40 #if !DEVTREE_EARLY
41 #if CONFIG(GENERATE_SMBIOS_TABLES)
42 #endif
43 #endif
44 };
Lines 24-26: Points to a default ramstage
device_operation
structure (default_dev_ops_root
) found insrc/device/root_device.c
. This structure typically does little by default but can be overridden or utilized by mainboard code via thechip_operations->enable_dev()
hook for tasks like ACPI table generation.Line 27:
.bus
: Pointer to the bus structure associated with this device. For the root device, this points to its own bus structure.Line 28:
.path
: The unique path identifier for this device. The type isDEVICE_PATH_ROOT
.Lines 29-32: Device status flags.
enabled
: Set based onon
/off
in the devicetree (always on fordev_root
). Can be modified later (e.g., during enumeration in ramstage).hidden
,mandatory
: Set only by corresponding keywords in the devicetree (not used here).on_mainboard
: Indicates the device was defined in the static devicetree, as opposed to being discovered dynamically (e.g., via PCI enumeration). Always true fordev_root
.
Line 33:
.link_list
: Pointer to the list of child buses attached to this device.Line 34:
.sibling
: Pointer to the next device at the same level in the tree. Should always beNULL
fordev_root
.Line 36:
.chip_ops
: Pointer to the mainboard’schip_operations
structure (theweak
mainboard_ops
). Although not a physical chip, the mainboard gets this to hook into the boot process like other chips.Line 37:
.name
: A string identifier, typically the mainboard name, set at build time (fromsrc/device/root_device.c
).Line 39:
.next
: Pointer used internally bysconfig
during tree construction. Points to the next device structure processed (_dev_0
).
dev_root_links
Lines 45-52: The dev_root
bus structure array.
This array (struct bus
) holds pointers defining the bus topology. Each
element represents a link on a bus. dev_root
acts as the bridge for the
top-level bus.
A new bus structure array is typically created for each distinct bus type or domain originating from a bridge device in the devicetree (e.g., PCI domain 0, LPC bus).
45 STORAGE struct bus dev_root_links[] = {
46 [0] = {
47 .link_num = 0,
48 .dev = &dev_root,
49 .children = &_dev_0,
50 .next = NULL,
51 },
52 };
Line 47:
.link_num
: Index of this link within the bus array.Line 48:
.dev
: Pointer back to the bridge device structure for this bus (dev_root
).Line 49:
.children
: Pointer to the first child device structure on this bus (_dev_0
).Line 50:
.next
: Pointer to the next bridge device on the parent bus. Sincedev_root
has no parent bus, this isNULL
.
_dev_0
Lines 53-72: The cpu_cluster
device structure (_dev_0
).
This structure corresponds directly to the
device cpu_cluster 0 on end
line in the devicetree.cb
.
53 STORAGE struct device _dev_0 = {
54 #if !DEVTREE_EARLY
55 .ops = NULL,
56 #endif
57 .bus = &dev_root_links[0],
58 .path = {.type=DEVICE_PATH_CPU_CLUSTER,{.cpu_cluster={ .cluster = 0x0 }}},
59 .enabled = 1,
60 .hidden = 0,
61 .mandatory = 0,
62 .on_mainboard = 1,
63 .link_list = NULL,
64 .sibling = NULL,
65 #if !DEVTREE_EARLY
66 .chip_ops = &soc_sifive_fu540_ops,
67 #endif
68 #if !DEVTREE_EARLY
69 #if CONFIG(GENERATE_SMBIOS_TABLES)
70 #endif
71 #endif
72 };
Lines 54-56:
.ops
: Pointer to adevice_operations
structure. This isNULL
because this entry represents thechip
itself, not a specific functional sub-device requiring device-level operations. The chip-level operations are handled bychip_ops
.Line 57:
.bus
: Pointer to the bus structure this device resides on. Since it’s directly underdev_root
, it points todev_root_links[0]
.Line 58:
.path
: The unique device path structure (defined insrc/include/device/path.h
). Type isDEVICE_PATH_CPU_CLUSTER
, and the cluster ID is0
, matching the devicetree entry. This path is used when searching the tree (e.g., withdev_find_path()
).Lines 59-62: Enumeration Status. Similar to
dev_root
.enabled = 1
comes from theon
keyword.Line 63:
.link_list
: Pointer to child buses.NULL
because thiscpu_cluster
device doesn’t bridge to any further buses in this simple example.Line 64:
.sibling
: Pointer to the next device at the same level (i.e., another device directly underdev_root
).NULL
as it’s the only child.Lines 65-67:
.chip_ops
: Pointer to the processor’schip_operations
structure (soc_sifive_fu540_ops
), used in ramstage for SoC/CPU initialization steps. This link comes from thechip soc/sifive/fu540
declaration.Lines 68-71: Placeholder for SMBIOS information, enabled by Kconfig. Not used in this example.