# 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.