Documentation: Add chip operations
Change-Id: I5373eab2de2e255f9e3576794b9ad02d9711a6c2 Signed-off-by: Martin Roth <gaumless@gmail.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/87201 Reviewed-by: Jérémy Compostella <jeremy.compostella@intel.com> Reviewed-by: Matt DeVillier <matt.devillier@gmail.com> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
parent
bf38f8eddc
commit
20d7eaeb0f
2 changed files with 538 additions and 1 deletions
537
Documentation/internals/chip_operations.md
Normal file
537
Documentation/internals/chip_operations.md
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
# The `chip_operations` Structure in coreboot
|
||||
|
||||
## Introduction
|
||||
|
||||
The `chip_operations` structure is a fundamental component of coreboot's
|
||||
chipset abstraction layer. It provides a standardized interface for chipset-
|
||||
specific code to interact with coreboot's device initialization framework.
|
||||
This structure enables coreboot to support a wide variety of chipsets
|
||||
while maintaining a consistent initialization flow across different hardware
|
||||
platforms.
|
||||
|
||||
In coreboot's architecture, a "chip" refers to a collection of hardware
|
||||
components that form a logical unit, such as a System-on-Chip (SoC),
|
||||
a CPU, or a distinct southbridge/northbridge. The `chip_operations`
|
||||
structure provides the hooks necessary for coreboot to discover, configure,
|
||||
and initialize these components during the boot process.
|
||||
|
||||
The `chip_operations` structure is particularly crucial for the ramstage
|
||||
portion of coreboot, where it connects the static device tree definitions
|
||||
with the actual hardware initialization code. It serves as the bridge
|
||||
between the declarative device descriptions and the imperative code that
|
||||
brings those devices to life.
|
||||
|
||||
|
||||
## Structure Definition
|
||||
|
||||
The `chip_operations` structure is defined in `src/include/device/device.h`
|
||||
as follows:
|
||||
|
||||
```c
|
||||
struct chip_operations {
|
||||
void (*enable_dev)(struct device *dev);
|
||||
void (*init)(void *chip_info);
|
||||
void (*final)(void *chip_info);
|
||||
unsigned int initialized : 1;
|
||||
unsigned int finalized : 1;
|
||||
const char *name;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
- **enable_dev**: A function pointer that takes a `struct device*`
|
||||
parameter. This function is called for each device associated with the
|
||||
chip during the device enumeration phase (specifically, within the
|
||||
`scan_bus` operations triggered by `dev_enumerate`). Its primary
|
||||
purpose is to set up device operations (`dev->ops`) based on the
|
||||
device's role in the system.
|
||||
|
||||
- **init**: A function pointer that takes a `void*` parameter pointing to
|
||||
the chip's configuration data (typically cast to a chip-specific struct).
|
||||
This function is called during the chip initialization phase
|
||||
(`BS_DEV_INIT_CHIPS`), before device enumeration. It usually performs
|
||||
early hardware setup needed before individual devices can be configured.
|
||||
|
||||
- **final**: A function pointer that takes a `void*` parameter pointing to
|
||||
the chip's configuration data (typically cast to a chip-specific struct).
|
||||
This function is called during the final table writing phase of coreboot
|
||||
initialization (`BS_WRITE_TABLES`), after all devices have been
|
||||
initialized. It performs any necessary cleanup or late initialization
|
||||
operations.
|
||||
|
||||
- **initialized**: A bit flag indicating whether the chip's init function
|
||||
has been called.
|
||||
|
||||
- **finalized**: A bit flag indicating whether the chip's final function
|
||||
has been called.
|
||||
|
||||
- **name**: A string containing the human-readable name of the chip, used
|
||||
for debugging and logging purposes.
|
||||
|
||||
|
||||
## Initialization Sequence and `chip_operations`
|
||||
|
||||
The `chip_operations` structure integrates with coreboot's boot state
|
||||
machine, which is defined in `src/lib/hardwaremain.c`. The functions in
|
||||
this structure are called at specific points during the boot process:
|
||||
|
||||
1. **BS_DEV_INIT_CHIPS** state: The `init` function is called for each
|
||||
chip in the device tree. This is handled by `dev_initialize_chips()`
|
||||
which iterates through all devices, identifies unique chip instances,
|
||||
and invokes their `init` functions.
|
||||
|
||||
2. **BS_DEV_ENUMERATE** state: During the execution of this state,
|
||||
`dev_enumerate()` is called, which triggers bus scanning
|
||||
(e.g., `pci_scan_bus`). Within these scan routines, the `enable_dev`
|
||||
function is called for devices associated with a chip. This commonly
|
||||
assigns the appropriate `device_operations` structure to each device
|
||||
based on its type and purpose.
|
||||
|
||||
3. **BS_WRITE_TABLES** state: The `final` function is called for each
|
||||
chip by `dev_finalize_chips()` after all devices have been initialized
|
||||
and just before payloads are loaded.
|
||||
|
||||
This sequence ensures that chips can perform necessary setup before their
|
||||
individual devices are configured, and also perform cleanup or finalization
|
||||
after all devices have been initialized but before the final tables are
|
||||
written and the payload is executed.
|
||||
|
||||
|
||||
## Relationship Between `chip_operations` and `device_operations`
|
||||
|
||||
It's important to understand the distinction and relationship between
|
||||
`chip_operations` and `device_operations`:
|
||||
|
||||
- **chip_operations**: Operates at the chipset or SoC level, providing
|
||||
hooks for chip-wide initialization. It's responsible for the overall
|
||||
setup of a collection of devices that belong to the same logical chip.
|
||||
|
||||
- **device_operations**: Operates at the individual device level,
|
||||
providing functions to manage specific devices within a chip. These
|
||||
operations include resource allocation, device initialization, and device-
|
||||
specific functionality.
|
||||
|
||||
The key relationship is that `chip_operations.enable_dev` is typically
|
||||
responsible for assigning the appropriate `device_operations` structure
|
||||
to each device based on its type and function. This is where the bridge
|
||||
between the chip-level and device-level abstractions occurs.
|
||||
|
||||
For example, a typical implementation of the `enable_dev` function might
|
||||
look like this:
|
||||
|
||||
```c
|
||||
static void soc_enable(struct device *dev)
|
||||
{
|
||||
if (dev->path.type == DEVICE_PATH_DOMAIN)
|
||||
dev->ops = &pci_domain_ops;
|
||||
else if (dev->path.type == DEVICE_PATH_CPU_CLUSTER)
|
||||
dev->ops = &cpu_bus_ops;
|
||||
else if (dev->path.type == DEVICE_PATH_GPIO)
|
||||
block_gpio_enable(dev);
|
||||
else if (dev->path.type == DEVICE_PATH_PCI &&
|
||||
dev->path.pci.devfn == PCH_DEVFN_PMC)
|
||||
dev->ops = &pmc_ops;
|
||||
}
|
||||
```
|
||||
|
||||
This function examines each device's path type and assigns the appropriate
|
||||
operations based on the device's role in the system.
|
||||
|
||||
|
||||
## Integration with the Devicetree
|
||||
|
||||
The `chip_operations` structure is tightly integrated with coreboot's
|
||||
devicetree mechanism. The devicetree is a hierarchical description of the
|
||||
hardware platform, defined in `.cb` files (typically `chipset.cb`,
|
||||
`devicetree.cb`, and optionally `overridetree.cb`).
|
||||
|
||||
In the devicetree, a `chip` directive starts a collection of devices
|
||||
associated with a particular chip driver. The path specified with the
|
||||
`chip` directive corresponds to a directory in the coreboot source tree
|
||||
that contains the chip driver code, including a `chip.c` file that defines
|
||||
the `chip_operations` structure for that chip.
|
||||
|
||||
For example, a devicetree might contain:
|
||||
|
||||
```
|
||||
chip soc/intel/cannonlake
|
||||
device domain 0 on
|
||||
device pci 00.0 on end # Host Bridge
|
||||
device pci 12.0 on end # Thermal Subsystem
|
||||
# ... more devices ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This connects the devices under this chip directive with the
|
||||
`chip_operations` structure defined in
|
||||
`src/soc/intel/cannonlake/chip.c`:
|
||||
|
||||
```c
|
||||
struct chip_operations soc_intel_cannonlake_ops = {
|
||||
.name = "Intel Cannonlake",
|
||||
.enable_dev = &soc_enable,
|
||||
.init = &soc_init_pre_device,
|
||||
};
|
||||
```
|
||||
|
||||
During coreboot's build process, the `sconfig` utility processes the
|
||||
devicetree files and generates code that links the devices defined in the
|
||||
devicetree with their corresponding `chip_operations` structures.
|
||||
|
||||
|
||||
## Chip Configuration Data
|
||||
|
||||
Each chip typically defines a configuration structure in a `chip.h` file
|
||||
within its source directory. This structure contains configuration settings
|
||||
that can be specified in the devicetree using `register` directives.
|
||||
|
||||
For example, a chip might define a configuration structure like:
|
||||
|
||||
```c
|
||||
/* In src/soc/intel/cannonlake/chip.h */
|
||||
struct soc_intel_cannonlake_config {
|
||||
uint8_t pcie_rp_aspm[CONFIG_MAX_ROOT_PORTS];
|
||||
uint8_t usb2_ports[16];
|
||||
uint8_t usb3_ports[10];
|
||||
/* ... more configuration options ... */
|
||||
};
|
||||
```
|
||||
|
||||
In the devicetree, you would configure these options using register
|
||||
directives:
|
||||
|
||||
```
|
||||
chip soc/intel/cannonlake
|
||||
register "pcie_rp_aspm[0]" = "ASPM_AUTO"
|
||||
register "usb2_ports[5]" = "USB2_PORT_MID(OC_SKIP)"
|
||||
# ... more register settings ...
|
||||
|
||||
device domain 0 on
|
||||
# ... devices ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
These configuration values are made available to the chip's `init` and
|
||||
`final` functions through the `chip_info` parameter, which points to
|
||||
an instance of the chip's configuration structure (after appropriate
|
||||
casting from `void *`).
|
||||
|
||||
|
||||
## Implementation Examples
|
||||
|
||||
### Minimal Implementation
|
||||
|
||||
Some chips may not need extensive initialization and can provide a
|
||||
minimal implementation of the `chip_operations` structure:
|
||||
|
||||
```c
|
||||
struct chip_operations soc_ucb_riscv_ops = {
|
||||
.name = "UCB RISC-V",
|
||||
};
|
||||
```
|
||||
|
||||
This implementation only provides a name for debugging purposes but
|
||||
doesn't define any initialization functions.
|
||||
|
||||
|
||||
### Basic Implementation with Initialization
|
||||
|
||||
A more typical implementation includes at least initialization hooks:
|
||||
|
||||
```c
|
||||
struct chip_operations soc_amd_genoa_poc_ops = {
|
||||
.name = "AMD Genoa SoC Proof of Concept",
|
||||
.init = soc_init,
|
||||
.final = soc_final,
|
||||
};
|
||||
```
|
||||
|
||||
The `init` function might perform chip-wide initialization:
|
||||
|
||||
```c
|
||||
static void soc_init(void *chip_info)
|
||||
{
|
||||
default_dev_ops_root.write_acpi_tables = soc_acpi_write_tables;
|
||||
amd_opensil_silicon_init();
|
||||
data_fabric_print_mmio_conf();
|
||||
fch_init(chip_info);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Complete Implementation
|
||||
|
||||
A complete implementation includes all three function pointers:
|
||||
|
||||
```c
|
||||
struct chip_operations soc_intel_xeon_sp_cpx_ops = {
|
||||
.name = "Intel Cooper Lake-SP",
|
||||
.enable_dev = chip_enable_dev,
|
||||
.init = chip_init,
|
||||
.final = chip_final,
|
||||
};
|
||||
```
|
||||
|
||||
The `enable_dev` function would typically assign device operations
|
||||
based on device types:
|
||||
|
||||
```c
|
||||
static void chip_enable_dev(struct device *dev)
|
||||
{
|
||||
/* PCI root complex */
|
||||
if (dev->path.type == DEVICE_PATH_DOMAIN)
|
||||
dev->ops = &pci_domain_ops;
|
||||
/* CPU cluster */
|
||||
else if (dev->path.type == DEVICE_PATH_CPU_CLUSTER)
|
||||
dev->ops = &cpu_cluster_ops;
|
||||
/* PCIe root ports */
|
||||
else if (dev->path.type == DEVICE_PATH_PCI &&
|
||||
PCI_SLOT(dev->path.pci.devfn) == PCIE_PORT1_SLOT)
|
||||
dev->ops = &pcie_rp_ops;
|
||||
/* ... other device types ... */
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Mainboard Implementation
|
||||
|
||||
It's also common for the mainboard-specific code (e.g.,
|
||||
`src/mainboard/vendor/board/mainboard.c`) to define its own
|
||||
`chip_operations`, often named `mainboard_ops`. The `mainboard_ops.init`
|
||||
can perform early board-level setup, and `mainboard_ops.enable_dev` can
|
||||
assign operations for devices specific to the mainboard or set default
|
||||
operations.
|
||||
|
||||
```c
|
||||
/* Example from src/mainboard/google/zork/mainboard.c */
|
||||
struct chip_operations mainboard_ops = {
|
||||
.enable_dev = mainboard_enable,
|
||||
.init = mainboard_init,
|
||||
.final = mainboard_final,
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Device Registration and Discovery
|
||||
|
||||
The `chip_operations` structure plays a key role in device registration
|
||||
and discovery within coreboot. Here's how it fits into this process:
|
||||
|
||||
1. **Static Device Definition**: Devices are statically defined in the
|
||||
devicetree files (`chipset.cb`, `devicetree.cb`, `overridetree.cb`).
|
||||
|
||||
2. **Code Generation**: The `sconfig` utility processes these files and
|
||||
generates code in `build/static.c` that creates the device structures
|
||||
and links them to their corresponding chip configuration data.
|
||||
|
||||
3. **Chip Initialization**: During the `BS_DEV_INIT_CHIPS` boot state,
|
||||
`dev_initialize_chips()` calls each chip's `init` function to perform
|
||||
chip-wide setup.
|
||||
|
||||
4. **Device Enumeration and Enabling**: During the `BS_DEV_ENUMERATE`
|
||||
boot state, `dev_enumerate()` initiates bus scanning. The scan
|
||||
functions call the associated chip's `enable_dev` function for each
|
||||
device, which assigns the appropriate device operations (`dev->ops`).
|
||||
|
||||
5. **Device Configuration and Initialization**: Subsequent boot states
|
||||
(`BS_DEV_RESOURCES`, `BS_DEV_ENABLE`, `BS_DEV_INIT`) configure and
|
||||
initialize the devices according to their assigned device operations.
|
||||
|
||||
6. **Chip Finalization**: After all devices have been initialized,
|
||||
`dev_finalize_chips()` calls each chip's `final` function during the
|
||||
`BS_WRITE_TABLES` boot state.
|
||||
|
||||
|
||||
## Build Process Integration
|
||||
|
||||
The `chip_operations` structures are integrated into the coreboot build
|
||||
process through several mechanisms:
|
||||
|
||||
1. **Devicetree Processing**: The `sconfig` utility processes the
|
||||
devicetree files and generates code that creates and links the device
|
||||
structures.
|
||||
|
||||
2. **Static Structure Declaration**: Each chip (and often the mainboard)
|
||||
defines its `chip_operations` structure in its respective `.c` file.
|
||||
These structures are collected during the build process.
|
||||
|
||||
3. **External References**: The generated code in `build/static.c`
|
||||
includes external references to these `chip_operations` structures.
|
||||
|
||||
4. **Linking**: The linker collects all the `chip_operations` structures
|
||||
and includes them in the final firmware image.
|
||||
|
||||
This process ensures that the appropriate chip operations are available
|
||||
during the boot process for each chip included in the devicetree.
|
||||
|
||||
|
||||
## Best Practices for Implementing `chip_operations`
|
||||
|
||||
When implementing the `chip_operations` structure for a new chip,
|
||||
follow these best practices:
|
||||
|
||||
1. **Provide a Meaningful Name**: The `name` field should be descriptive
|
||||
and identify the chip clearly for debugging purposes.
|
||||
|
||||
2. **Implement `enable_dev` Correctly**: The `enable_dev` function should
|
||||
assign the appropriate device operations based on device types and
|
||||
functions. It should handle all device types that might be part of the chip.
|
||||
Consider interactions with the mainboard `enable_dev`.
|
||||
|
||||
3. **Use Configuration Data**: The `init` and `final` functions should
|
||||
make use of the chip configuration data passed via the `chip_info`
|
||||
parameter (casting it to the correct type) to configure the chip
|
||||
according to the settings specified in the devicetree.
|
||||
|
||||
4. **Minimize Dependencies**: The `init` function should minimize
|
||||
dependencies on other chips being initialized, as the order of chip
|
||||
initialization is not guaranteed.
|
||||
|
||||
5. **Handle Resources Properly**: If the chip manages resources (memory
|
||||
regions, I/O ports, etc.), ensure that these are properly allocated and
|
||||
assigned to devices, usually within the associated `device_operations`.
|
||||
|
||||
6. **Implement Error Handling**: Include appropriate error handling in
|
||||
the initialization functions to handle hardware initialization failures
|
||||
gracefully.
|
||||
|
||||
7. **Document Special Requirements**: If the chip has special
|
||||
requirements or dependencies, document these clearly in comments or
|
||||
accompanying documentation.
|
||||
|
||||
|
||||
## Troubleshooting `chip_operations` Issues
|
||||
|
||||
When implementing or debugging `chip_operations`, you might encounter
|
||||
certain issues:
|
||||
|
||||
1. **Missing Device Operations**: If devices are not being initialized
|
||||
properly, check that the `enable_dev` function is correctly
|
||||
assigning device operations based on device types. Ensure it's being
|
||||
called during bus scanning.
|
||||
|
||||
2. **Initialization Order Problems**: If a chip's initialization depends
|
||||
on another chip being initialized first, you might need to adjust the
|
||||
initialization sequence or add explicit dependencies, possibly using
|
||||
boot state callbacks if necessary.
|
||||
|
||||
3. **Configuration Data Issues**: If chip configuration settings are not
|
||||
being applied correctly, check that the configuration structure is
|
||||
correctly defined in `chip.h`, that the register values in the
|
||||
devicetree match the expected format, and that the `chip_info` pointer
|
||||
is cast correctly in the `init`/`final` functions.
|
||||
|
||||
4. **Build Errors**: If you encounter build errors related to
|
||||
`chip_operations`, check that the structure is correctly defined and
|
||||
that all required symbols are properly exported and linked. Check for
|
||||
conflicts if multiple files define the same symbol.
|
||||
|
||||
5. **Runtime Failures**: If the chip initialization fails at runtime,
|
||||
add debug logging (using `printk`) to the `init`, `enable_dev`, and
|
||||
`final` functions to identify the specific point of failure.
|
||||
|
||||
|
||||
## Advanced `chip_operations` Patterns
|
||||
|
||||
### Hierarchical Chip Initialization
|
||||
|
||||
For complex chips with multiple components, you can implement a
|
||||
hierarchical initialization pattern within the `init` function:
|
||||
|
||||
```c
|
||||
static void soc_init(void *chip_info)
|
||||
{
|
||||
/* Initialize common components first */
|
||||
common_init(chip_info);
|
||||
|
||||
/* Initialize specific blocks */
|
||||
pcie_init(chip_info);
|
||||
usb_init(chip_info);
|
||||
sata_init(chip_info);
|
||||
|
||||
/* Final SoC-wide configuration */
|
||||
power_management_init(chip_info);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Variant Support
|
||||
|
||||
For chips with multiple variants, you can implement variant detection
|
||||
and specific initialization within the `init` function:
|
||||
|
||||
```c
|
||||
static void soc_init(void *chip_info)
|
||||
{
|
||||
uint32_t variant = read_chip_variant();
|
||||
|
||||
/* Common initialization */
|
||||
common_init(chip_info);
|
||||
|
||||
/* Variant-specific initialization */
|
||||
switch (variant) {
|
||||
case VARIANT_A:
|
||||
variant_a_init(chip_info);
|
||||
break;
|
||||
case VARIANT_B:
|
||||
variant_b_init(chip_info);
|
||||
break;
|
||||
default:
|
||||
printk(BIOS_WARNING, "Unknown variant %u\\n", variant);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Conditional Feature Initialization
|
||||
|
||||
You can conditionally initialize features based on configuration settings
|
||||
passed via `chip_info`:
|
||||
|
||||
```c
|
||||
static void soc_init(void *chip_info)
|
||||
{
|
||||
struct soc_config *config = chip_info;
|
||||
|
||||
/* Always initialize core components */
|
||||
core_init();
|
||||
|
||||
/* Conditionally initialize optional features */
|
||||
if (config->enable_xhci)
|
||||
xhci_init(config);
|
||||
|
||||
if (config->enable_sata)
|
||||
sata_init(config);
|
||||
|
||||
if (config->enable_pcie)
|
||||
pcie_init(config);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
The `chip_operations` structure is a fundamental component of coreboot's
|
||||
chipset abstraction layer. It provides a standardized interface for chipset-
|
||||
specific code to interact with coreboot's device initialization framework,
|
||||
enabling support for a wide variety of chipsets while maintaining a
|
||||
consistent initialization flow.
|
||||
|
||||
By implementing the `chip_operations` structure for a specific chipset
|
||||
(and often for the mainboard), developers can integrate their
|
||||
hardware-specific code with coreboot's device enumeration, configuration,
|
||||
and initialization process. This structure serves as the bridge between
|
||||
the declarative device descriptions in the devicetree and the imperative
|
||||
code that initializes the hardware.
|
||||
|
||||
Understanding the `chip_operations` structure and its role in the
|
||||
coreboot boot process is essential for anyone working on chipset or
|
||||
mainboard support in coreboot. By following the best practices and
|
||||
patterns outlined in this document, developers can create robust and
|
||||
maintainable hardware support code that integrates seamlessly with the
|
||||
coreboot firmware ecosystem.
|
||||
|
|
@ -10,5 +10,5 @@ programming APIs internal to coreboot
|
|||
|
||||
coreboot devicetree <devicetree.md>
|
||||
coreboot devicetree language <devicetree_language.md>
|
||||
|
||||
Chip Operations <chip_operations.md>
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue