Documentation: Add Device Operations

Change-Id: I3ed78f8ce50bb3914f55b2cbb7f5eb668706949a
Signed-off-by: Martin Roth <gaumless@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/87202
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Martin Roth 2025-04-07 12:02:47 -06:00 committed by Matt DeVillier
commit 407c7d0da3
2 changed files with 697 additions and 0 deletions

View file

@ -0,0 +1,696 @@
# Device Operations in coreboot Firmware
## Introduction
The `device_operations` structure is a cornerstone of coreboot's
hardware abstraction layer. It represents a set of function pointers
defining how the firmware interacts with a specific hardware device
during various boot stages. This structure lets the coreboot
architecture establish a consistent interface for device initialization,
configuration, resource allocation, and ACPI table generation, while
allowing device-specific implementations behind this common interface.
At its core, `device_operations` applies the strategy pattern in
systems programming. It decouples algorithms (device operations) from
the core boot sequence, allowing devices to define their own behavior
while the boot process follows a predictable flow. This pattern enables
coreboot to support a wide range of hardware platforms with minimal
changes to the core boot sequence code.
## Structure Definition
The `device_operations` structure, defined in
`src/include/device/device.h`, consists of several function pointers,
each representing a specific operation performed on a device during
boot:
```c
struct device_operations {
void (*read_resources)(struct device *dev);
void (*set_resources)(struct device *dev);
void (*enable_resources)(struct device *dev);
void (*init)(struct device *dev);
void (*final)(struct device *dev);
void (*scan_bus)(struct device *bus);
void (*enable)(struct device *dev);
void (*vga_disable)(struct device *dev);
void (*reset_bus)(struct bus *bus);
int (*get_smbios_data)(struct device *dev, int *handle,
unsigned long *current);
void (*get_smbios_strings)(struct device *dev, struct smbios_type11 *t);
unsigned long (*write_acpi_tables)(const struct device *dev,
unsigned long start, struct acpi_rsdp *rsdp);
void (*acpi_fill_ssdt)(const struct device *dev);
const char *(*acpi_name)(const struct device *dev);
const char *(*acpi_hid)(const struct device *dev);
const struct pci_operations *ops_pci;
const struct i2c_bus_operations *ops_i2c_bus;
const struct spi_bus_operations *ops_spi_bus;
const struct smbus_bus_operations *ops_smbus_bus;
const struct pnp_mode_ops *ops_pnp_mode;
const struct gpio_operations *ops_gpio;
const struct mdio_bus_operations *ops_mdio;
};
```
### Core Resource Management Functions
* `read_resources`: Discovers and collects resources required by the
device (I/O ports, memory regions, IRQs, etc.). This typically
involves reading configuration registers or static device tree
information.
* `set_resources`: Assigns finalized resources to the device after the
resource allocation phase. This writes the assigned base addresses,
lengths, and other parameters back to the device structure, but not
necessarily to hardware registers yet.
* `enable_resources`: Activates the assigned resources in the device's
hardware registers (e.g., writing to PCI BARs, enabling memory
decoding).
### Device Lifecycle Functions
* `init`: Performs device-specific initialization, often requiring
access to the device's assigned resources. This is called after
resources have been enabled.
* `final`: Executes final setup or cleanup operations before the
payload is loaded. This is useful for tasks that depend on other
devices being initialized.
* `enable`: Enables the device in the hardware, often setting bits in
configuration registers to make the device active. Called after
`enable_resources`.
### Bus Management Functions
* `scan_bus`: Enumerates child devices on a bus device (e.g., scanning
a PCI bus for devices, probing I2C devices). Only applicable to
devices that act as buses.
* `reset_bus`: Resets all devices on a specific bus.
* `vga_disable`: Disables VGA decoding on a PCI device when another VGA
device is active. Used to manage legacy VGA resources.
### System Table Generation Functions
* `get_smbios_data`: Provides SMBIOS data specific to the device for
Type 9 (System Slots) or Type 41 (Onboard Devices Extended
Information).
* `get_smbios_strings`: Supplies string information for SMBIOS tables,
often related to the data provided by `get_smbios_data`.
* `write_acpi_tables`: Generates device-specific ACPI tables (like SSDTs)
or contributes data to system-wide tables.
* `acpi_fill_ssdt`: Adds device-specific objects (scopes, methods, data)
to the Secondary System Description Table (SSDT).
* `acpi_name`: Returns the ACPI name for the device (e.g.,
`\_SB.PCI0.GFX0`). This defines the device's path in the ACPI
namespace.
* `acpi_hid`: Returns the ACPI Hardware ID (HID) for the device (e.g.,
`PNP0A08`). Used by the OS to match drivers.
### Bus-Specific Operation Pointers
These fields point to bus-specific operation structures when a device
functions as a bus controller (or exposes bus-like functionality). See
the "Bus-Specific Operations" section for details.
* `ops_pci`: Operations for PCI configuration space access.
* `ops_i2c_bus`: Operations for I2C bus transactions (read, write,
transfer).
* `ops_spi_bus`: Operations for SPI bus transactions.
* `ops_smbus_bus`: Operations for SMBus transactions.
* `ops_pnp_mode`: Operations for Plug-and-Play device configuration.
* `ops_gpio`: Operations for GPIO control (get, set, configure
direction/pulls).
* `ops_mdio`: Operations for MDIO (Management Data Input/Output) bus
access, used for Ethernet PHYs.
## Device Lifecycle in coreboot
The function pointers in `device_operations` are called at specific
stages during the boot process, following a sequence defined in
coreboot's boot state machine (`src/lib/hardwaremain.c`). Understanding
this lifecycle helps developers implement appropriate behavior for each
function pointer.
### Boot Sequence and Device Operations
coreboot's main device initialization sequence involves these boot
states:
1. **BS_DEV_INIT_CHIPS** (`dev_initialize_chips()`): Initializes chip
drivers (`chip_operations`).
2. **BS_DEV_ENUMERATE** (`dev_enumerate()`): Discovers and enumerates
devices.
* Calls `scan_bus()` for each bus to detect child devices.
3. **BS_DEV_RESOURCES** (`dev_configure()`): Allocates resources across
all enumerated devices.
* Calls `read_resources()` for each device to discover required
resources.
* Calls `set_resources()` for each device to assign allocated
resources back to the `struct device`.
4. **BS_DEV_ENABLE** (`dev_enable()`): Enables devices and their
resources.
* Calls `enable_resources()` for each device to activate assigned
resources in hardware.
* Calls `enable()` for each device to perform general hardware
enablement.
5. **BS_DEV_INIT** (`dev_initialize()`): Initializes devices.
* Calls `init()` for each device to perform device-specific setup.
6. **BS_POST_DEVICE** (`dev_finalize()`): Finalizes devices before
payload loading.
* Calls `final()` for each device for any final cleanup or setup.
The sequence is primarily driven by the `boot_states` array in
`src/lib/hardwaremain.c`:
```c
static struct boot_state boot_states[] = {
/* ... other states ... */
BS_INIT_ENTRY(BS_PRE_DEVICE, bs_pre_device),
BS_INIT_ENTRY(BS_DEV_INIT_CHIPS, bs_dev_init_chips),
BS_INIT_ENTRY(BS_DEV_ENUMERATE, bs_dev_enumerate),
BS_INIT_ENTRY(BS_DEV_RESOURCES, bs_dev_resources),
BS_INIT_ENTRY(BS_DEV_ENABLE, bs_dev_enable),
BS_INIT_ENTRY(BS_DEV_INIT, bs_dev_init),
BS_INIT_ENTRY(BS_POST_DEVICE, bs_post_device),
/* ... other states ... */
};
```
Later stages include ACPI and SMBIOS table generation, where functions
like `write_acpi_tables()`, `acpi_fill_ssdt()`, `get_smbios_data()`, and
`get_smbios_strings()` are invoked as part of the table construction
process.
## Inheritance and Code Reuse Patterns
The `device_operations` structure enables several patterns for code
reuse:
### 1. Default Implementations
coreboot provides default implementations for common device types (like
root devices, PCI devices, PCI bridges), which can be used directly or
extended. Chip or mainboard code often assigns these defaults if no
specific driver is found.
```c
/* From src/device/root_device.c */
struct device_operations default_dev_ops_root = {
.read_resources = noop_read_resources,
.set_resources = noop_set_resources,
.scan_bus = scan_static_bus,
.reset_bus = root_dev_reset,
#if CONFIG(HAVE_ACPI_TABLES)
.acpi_name = root_dev_acpi_name,
#endif
};
```
### 2. No-op Functions
Simple shim functions (often static inline) are provided for cases where
a device doesn't need to implement specific operations. Using these avoids
leaving function pointers NULL.
```c
/* From src/include/device/device.h */
static inline void noop_read_resources(struct device *dev) {}
static inline void noop_set_resources(struct device *dev) {}
```
### 3. Chain of Responsibility / Delegation
Some implementations delegate to parent devices or use helper functions
when they can't handle an operation themselves or when common logic can
be shared. For example, ACPI name generation often traverses up the
device tree.
```c
/* Simplified example logic */
const char *acpi_device_name(const struct device *dev)
{
const char *name = NULL;
const struct device *pdev = dev;
/* Check for device specific handler */
if (dev->ops && dev->ops->acpi_name) {
name = dev->ops->acpi_name(dev);
if (name)
return name; /* Device handled it */
}
/* Walk up the tree to find if any parent can provide a name */
while (pdev->upstream && pdev->upstream->dev) {
pdev = pdev->upstream->dev;
if (pdev->ops && pdev->ops->acpi_name) {
/* Note: Parent's acpi_name might handle the original child 'dev' */
name = pdev->ops->acpi_name(dev);
if (name)
return name; /* Parent handled it */
}
}
/* Fallback or default logic if needed */
return NULL;
}
```
This pattern allows parent devices (like buses) to provide default
behavior or naming schemes if a child device doesn't specify its own.
## Implementation Examples
These examples show typical `device_operations` assignments. Actual
implementations might involve more conditional compilation based on
Kconfig options.
### PCI Device Operations (Default)
```c
/* From src/device/pci_device.c */
struct device_operations default_pci_ops_dev = {
.read_resources = pci_dev_read_resources,
.set_resources = pci_dev_set_resources,
.enable_resources = pci_dev_enable_resources,
#if CONFIG(HAVE_ACPI_TABLES)
.write_acpi_tables = pci_rom_write_acpi_tables,
.acpi_fill_ssdt = pci_rom_ssdt,
#endif
.init = pci_dev_init,
/* Assigns PCI-specific operations */
.ops_pci = &pci_dev_ops_pci,
};
```
### CPU Cluster Operations
```c
/* From src/soc/intel/alderlake/chip.c (representative example) */
static struct device_operations cpu_bus_ops = {
.read_resources = noop_read_resources,
.set_resources = noop_set_resources,
.enable_resources = cpu_set_north_irqs,
#if CONFIG(HAVE_ACPI_TABLES)
.acpi_fill_ssdt = cpu_fill_ssdt,
#endif
/* CPU clusters often don't need scan_bus, init, etc. */
};
```
### GPIO Controller Operations
```c
/* From src/soc/intel/common/block/gpio/gpio_dev.c */
static struct gpio_operations gpio_ops = {
.get = gpio_get,
.set = gpio_set,
/* ... other GPIO functions ... */
};
struct device_operations block_gpio_ops = {
.read_resources = noop_read_resources,
.set_resources = noop_set_resources,
/* Assigns GPIO-specific operations */
.ops_gpio = &gpio_ops,
};
```
## Registration and Discovery
How are `device_operations` structures associated with `struct device`
instances?
### 1. Static Assignment (via `chip_operations`)
For devices known at build time (defined in devicetree.cb), the
`device_operations` structure is often assigned in the SOC's or
mainboard's `chip_operations->enable_dev()` function based on the
device path type or other properties.
```c
/* Example from src/soc/intel/alderlake/chip.c */
static void soc_enable(struct device *dev)
{
/* Assign ops based on the device's role in the tree */
if (dev->path.type == DEVICE_PATH_DOMAIN)
dev->ops = &pci_domain_ops; /* Handles PCI domain resources */
else if (dev->path.type == DEVICE_PATH_CPU_CLUSTER)
dev->ops = &cpu_bus_ops; /* Handles CPU cluster setup */
else if (dev->path.type == DEVICE_PATH_GPIO)
block_gpio_enable(dev); /* Assigns block_gpio_ops */
/* ... other assignments for specific PCI devices, etc. ... */
}
```
The `enable_dev` function is part of `struct chip_operations`, which
handles broader chip-level initialization (see "Relationship with
`chip_operations`" section).
### 2. Dynamic Detection (PCI Drivers)
For PCI devices discovered during bus scanning (`scan_bus`), coreboot
looks through a list of registered PCI drivers (`_pci_drivers` array)
to find one matching the device's vendor and device IDs.
```c
/* Logic from src/device/pci_device.c::set_pci_ops() */
static void set_pci_ops(struct device *dev)
{
struct pci_driver *driver;
/* Check if ops already assigned (e.g., by chip_ops->enable_dev) */
if (dev->ops)
return;
/* Look through registered PCI drivers */
for (driver = &_pci_drivers[0]; driver != &_epci_drivers[0]; driver++) {
if ((driver->vendor == dev->vendor) &&
device_id_match(driver, dev->device)) {
/* Found a matching driver, assign its ops */
dev->ops = (struct device_operations *)driver->ops;
printk(BIOS_SPEW, "%s: Assigned ops from driver for %04x:%04x\n",
dev_path(dev), driver->vendor, driver->device);
return; /* Stop searching */
}
}
/* Fall back to default operations if no specific driver found */
if (!dev->ops) {
if ((dev->hdr_type & 0x7f) == PCI_HEADER_TYPE_BRIDGE) {
dev->ops = get_pci_bridge_ops(dev); /* Special ops for bridges */
} else {
dev->ops = &default_pci_ops_dev; /* Default for normal devices */
}
printk(BIOS_SPEW, "%s: Assigned default PCI ops\n", dev_path(dev));
}
}
```
## Build Process Integration
The `device_operations` structures are integrated into the coreboot
build process:
1. **Static Device Tree**: The mainboard's `devicetree.cb` defines the
initial device hierarchy. The build process converts this into C
code (`static.c`) containing `struct device` instances.
2. **PCI Driver Registration**: PCI drivers (containing their own
`device_operations`) register themselves using the `__pci_driver`
linker set macro.
```c
/* Example pattern */
struct pci_driver example_pci_driver __pci_driver = {
.ops = &example_device_ops, /* Pointer to device_operations */
.vendor = VENDOR_ID,
.device = DEVICE_ID, /* Or .devices for a list */
};
```
3. **Linking**: The build system collects all structures placed in the
`__pci_driver` section and creates the `_pci_drivers` array used by
`set_pci_ops()`. It ensures all necessary code (default ops, driver
ops, core device functions) is linked into the final firmware image.
## Relationship with `chip_operations`
It's important to distinguish `device_operations` from
`chip_operations` (defined in `src/include/chip.h`).
* `chip_operations`: Defines operations related to the overall chipset
or mainboard logic. It includes functions called earlier in the boot
process, like `enable_dev`, `init`, and `final`.
* `chip_operations->enable_dev()` is crucial as it often performs
initial setup for static devices and is the primary place where
`device_operations` pointers are *assigned* for non-PCI devices
based on their path or type.
* `chip_operations->init()` runs during `BS_DEV_INIT_CHIPS`, before
most `device_operations` functions.
* `device_operations`: Defines operations for *individual* devices
within the device tree. These are called *after* the corresponding
`chip_operations` stage and operate on a specific `struct device`.
Essentially, `chip_operations` sets the stage at the SoC/mainboard level,
including assigning the correct `device_operations` to static devices,
while `device_operations` handles the specific actions for each device
later in the boot process.
## Bus-Specific Operations
The `ops_*` pointers within `struct device_operations` (e.g., `ops_pci`,
`ops_i2c_bus`, `ops_spi_bus`, `ops_gpio`) provide a way for devices that
act as bus controllers or expose bus-like interfaces to offer
standardized access methods.
* **Purpose:** They abstract the low-level details of interacting with
that specific bus type. For example, a PCI host bridge device will
implement `struct pci_operations` via its `ops_pci` pointer,
allowing other code to perform PCI config reads/writes through it
without knowing the exact hardware mechanism. Similarly, an I2C
controller device implements `struct i2c_bus_operations` via
`ops_i2c_bus` to provide standard `read`, `write`, and `transfer`
functions for that bus segment.
* **Usage:** Code needing to interact with a bus first finds the
controller `struct device` in the tree, then accesses the relevant
bus operations through the appropriate `ops_*` pointer, passing the
target address or parameters. For instance, to talk to an I2C device
at address `0x50` on the bus controlled by `i2c_controller_dev`, one
might call:
`i2c_controller_dev->ops->ops_i2c_bus->transfer(...)`. Helper
functions often wrap this access pattern.
* **Implementation:** The structures like `struct pci_operations`,
`struct i2c_bus_operations`, etc., are defined in corresponding
header files (e.g., `src/include/device/pci_ops.h`,
`src/include/drivers/i2c/i2c_bus.h`). Devices acting as controllers
provide concrete implementations of these functions, tailored to their
hardware.
This mechanism allows coreboot to manage diverse bus types using a
consistent device model, where the controller device itself exposes the
necessary functions for interacting with devices on its bus.
## Best Practices
When implementing `device_operations`:
1. **Leverage Defaults/No-ops**: Use default or no-op implementations
whenever possible. Only override functions that require custom
behavior for your specific device.
2. **Error Handling**: Check return values from functions called within
your ops implementations and handle errors gracefully (e.g., log an
error, return an error code if applicable).
3. **Resource Management**: In `read_resources`, accurately declare all
resources (MMIO, I/O ports, IRQs) your device needs, specifying
flags like fixed vs. alignment, or bridge vs. standard device.
Incorrect resource declaration is a common source of issues.
4. **Initialization Order**: Be mindful of dependencies in `init`. If
your device relies on another device being fully initialized, consider
deferring that part of the initialization to the `final` callback,
which runs later.
5. **Minimal Implementation**: Only implement the functions relevant to
your device type. A simple MMIO device might only need
`read_resources`, `set_resources`, `enable_resources`, and perhaps
ACPI functions. A bus device additionally needs `scan_bus`.
6. **Bus Operations**: If implementing a bus controller, correctly
implement the corresponding bus operations structure (e.g.,
`struct pci_operations`, `struct i2c_bus_operations`) and assign it
to the appropriate `ops_*` field.
7. **ACPI/SMBIOS**: If the device needs OS visibility via ACPI or
SMBIOS, implement the relevant functions (`acpi_name`, `acpi_hid`,
`acpi_fill_ssdt`, `get_smbios_data`, etc.). Ensure ACPI names and
HIDs are correct according to specifications and platform needs.
## Logging and Debugging
Use coreboot's logging facilities (`printk`) within your `device_operations`
functions to provide visibility during development and debugging. Use
appropriate log levels (e.g., `BIOS_DEBUG`, `BIOS_INFO`, `BIOS_ERR`).
```c
static void example_device_init(struct device *dev)
{
printk(BIOS_DEBUG, "%s: Initializing device at %s\n", __func__,
dev_path(dev));
/* ... Device initialization code ... */
if (/* some condition */) {
printk(BIOS_SPEW, "%s: Condition met, applying setting X\n",
dev_path(dev));
/* ... */
}
if (/* error condition */) {
printk(BIOS_ERR, "%s: Failed to initialize feature Y!\n",
dev_path(dev));
/* Handle error */
}
printk(BIOS_DEBUG, "%s: Initialization complete for %s\n", __func__,
dev_path(dev));
}
```
Consistent logging helps trace the boot process and pinpoint where issues
occur.
## Common Troubleshooting
* **Missing Resource Declarations**:
* *Problem*: Device fails to function, or conflicts arise because a
required resource (MMIO range, I/O port, IRQ) was not declared
in `read_resources`. The resource allocator is unaware of the
need.
* *Solution*: Verify that `read_resources` correctly calls functions
like `pci_dev_read_resources` or manually adds all necessary
resources using functions like `mmio_resource()`,
`io_resource()`, etc. Check PCI BARs or device datasheets.
* **Initialization Order Issues**:
* *Problem*: `init()` fails because it depends on another device
that hasn't been fully initialized yet (e.g., accessing a shared
resource like SMBus before the SMBus controller is ready).
* *Solution*: Move the dependent initialization code to the `final`
callback if possible. Alternatively, ensure the dependency is met
by careful ordering in the device tree or using boot state
callbacks if necessary for complex scenarios.
* **Resource Conflicts**:
* *Problem*: Boot fails during resource allocation, or devices
misbehave because multiple devices requested the same
non-sharable resource (e.g., conflicting fixed MMIO regions).
* *Solution*: Review resource declarations in `read_resources` across
all relevant devices. Ensure fixed resources don't overlap. Check
if bridge windows are correctly defined and large enough. Use
coreboot's resource reporting logs to identify overlaps.
* **ACPI Table Generation Errors**:
* *Problem*: The operating system fails to recognize the device,
assigns the wrong driver, or the device doesn't function correctly
(e.g., power management issues).
* *Solution*: Double-check the `acpi_name`, `acpi_hid`, `_CRS`
(generated from assigned resources), and `acpi_fill_ssdt`
implementations. Verify names match the ACPI hierarchy and HIDs
match expected driver bindings. Ensure SSDT methods correctly
access hardware. Use OS debugging tools (e.g., `acpidump`, Device
Manager errors) to diagnose.
* **Incorrect `ops` Pointer Assigned**:
* *Problem*: Device behaves incorrectly because the wrong
`device_operations` structure was assigned (e.g., default PCI ops
assigned to a device needing a specific driver's ops).
* *Solution*: Check the logic in `chip_operations->enable_dev` (for
static devices) or the PCI driver registration (`__pci_driver`
macro and `set_pci_ops` fallback logic) to ensure the correct
`ops` structure is being selected and assigned based on device
type, path, or PCI ID. Add debug prints to verify which `ops`
structure is assigned.
## Advanced Usage
### Complex Device Hierarchies
For devices with non-standard interactions or complex initialization,
custom `device_operations` can be created, often inheriting from defaults
but overriding specific functions.
```c
static void advanced_device_init(struct device *dev)
{
/* First, perform standard PCI init */
pci_dev_init(dev);
/* Then, add custom initialization steps */
printk(BIOS_DEBUG, "%s: Performing advanced init\n", dev_path(dev));
/* ... custom register writes, configuration ... */
}
static const char *advanced_device_acpi_name(const struct device *dev)
{
/* Provide a custom ACPI name based on some property */
if (/* condition */)
return "ADV0001";
else
return "ADV0002";
}
/* Combine default and custom operations */
static struct device_operations advanced_device_ops = {
/* Inherit resource handling from default PCI ops */
.read_resources = pci_dev_read_resources,
.set_resources = pci_dev_set_resources,
.enable_resources = pci_dev_enable_resources,
/* Override init */
.init = advanced_device_init,
/* Override ACPI naming */
.acpi_name = advanced_device_acpi_name,
/* Other functions might use defaults or no-ops */
};
```
### Dynamic Configuration based on Probing
Some `init` or other op implementations might probe the device's
capabilities or read configuration data (e.g., from SPD, VPD, or straps)
and alter their behavior accordingly.
```c
static void conditional_device_init(struct device *dev)
{
uint8_t feature_flags;
/* Read capability register from the device */
feature_flags = pci_read_config8(dev, EXAMPLE_CAP_REG);
printk(BIOS_DEBUG, "%s: Feature flags: 0x%02x\n", dev_path(dev),
feature_flags);
/* Conditional initialization based on detected features */
if (feature_flags & FEATURE_X_ENABLED) {
printk(BIOS_INFO, "%s: Initializing Feature X\n", dev_path(dev));
init_feature_x(dev);
}
if (feature_flags & FEATURE_Y_ENABLED) {
printk(BIOS_INFO, "%s: Initializing Feature Y\n", dev_path(dev));
init_feature_y(dev);
}
}
```
## Conclusion
The `device_operations` structure is a powerful abstraction mechanism in
coreboot. It enables consistent handling of diverse hardware while
allowing for device-specific behavior. By providing a standard interface
for device operations throughout the boot process, it simplifies the
codebase, enhances maintainability, and provides the extensibility needed
to support new hardware platforms.
Understanding this structure, its relationship with `chip_operations`,
and its role in the boot process is essential for coreboot developers,
particularly when adding support for new devices or debugging hardware
initialization issues. By following the patterns and best practices
outlined here, developers can create robust and reusable device driver
implementations that integrate smoothly into the coreboot architecture.

View file

@ -11,4 +11,5 @@ programming APIs internal to coreboot
coreboot devicetree <devicetree.md>
coreboot devicetree language <devicetree_language.md>
Chip Operations <chip_operations.md>
Device Operations <device_operations>
```