| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,353 @@ | ||
| # Firmware Configuration Interface in coreboot | ||
|
|
||
| ## Motivation | ||
|
|
||
| The firmware configuration interface in coreboot is designed to support a wide variety of | ||
| configuration options in that are dictated by the hardware at runtime. This allows a single | ||
| BIOS image to be used across a wide variety of devices which may have key differences but are | ||
| otherwise similar enough to use the same coreboot build target. | ||
|
|
||
| The initial implementation is designed to take advantage of a bitmask returned by the Embedded | ||
| Controller on Google Chrome OS devices which allows the manufacturer to use the same firmware | ||
| image across multiple devices by selecting various options at runtime. See the Chromium OS | ||
| [Firmware Config][1] documentation for more information. | ||
|
|
||
| This firmware configuration interface differs from the CMOS option interface in that this | ||
| bitmask value is not intended as a user-configurable setting as the configuration values must | ||
| match the actual hardware. In the case where a user was to swap their hardware this value | ||
| would need to be updated or overridden. | ||
|
|
||
| ## Device Presence | ||
|
|
||
| One common example of why a firmware configuration interface is important is determining if a | ||
| device is present in the system. With some bus topologies and hardware mechanisms it is | ||
| possible to probe and enumerate this at runtime: | ||
|
|
||
| - PCI is a self-discoverable bus and is very easy to handle. | ||
| - I2C devices can often be probed with a combination of bus and address. | ||
| - The use of GPIOs with external strap to ground or different voltages can be used to detect | ||
| presence of a device. | ||
|
|
||
| However there are several cases where this is insufficient: | ||
|
|
||
| - I2C peripherals that require different drivers but have the same bus address cannot be | ||
| uniquely identified at runtime. | ||
| - A mainboard may be designed with multiple daughter board combinations which contain devices | ||
| and configurations that cannot be detected. | ||
| - While presence detect GPIOs are a convenient way for a single device presence, they are | ||
| unable to distinguish between different devices so it can require a large number of GPIOs to | ||
| support relatively few options. | ||
|
|
||
| This presence detection can impact different stages of boot: | ||
|
|
||
| ### ACPI | ||
|
|
||
| Devices that are not present should not provide an ACPI device indicating that they are | ||
| present or the operating system may not be able to handle it correctly. | ||
|
|
||
| The ACPI devices are largely driven by chips defined in the mainboard `devicetree.cb` and | ||
| the variant overridetree.cb. This means it is important to be able to specify when a device | ||
| is present or not directly in `devicetree.cb` itself. Otherwise each mainboard needs custom | ||
| code to parse the tree and disable unused devices. | ||
|
|
||
| ### GPIO | ||
|
|
||
| GPIOs with multiple functions may need to be configured correctly depending on the attached | ||
| device. Given the wide variety of GPIO configuration possibilities it is not feasible to | ||
| specify all combinations directly in `devicetree.cb` and it is best left to code provided by | ||
| the mainboard. | ||
|
|
||
| ### FSP UPD | ||
|
|
||
| Enabling and disabling devices may require altering FSP UPD values that are provided to the | ||
| various stages of FSP. These options are also not easy to specify multiple times for | ||
| different configurations in `devicetree.cb` and can be provided by the mainboard as code. | ||
|
|
||
| ## Firmware Configuration Interface | ||
|
|
||
| The firmware configuration interface can be enabled by selecting `CONFIG_FW_CONFIG` and also | ||
| providing a source for the value by defining an additional Kconfig option defined below. | ||
|
|
||
| If the firmware configuration interface is disabled via Kconfig then all probe attempts will | ||
| return true. | ||
|
|
||
| ## Firmware Configuration Value | ||
|
|
||
| The 32bit value used as the firmware configuration bitmask is meant to be determined at runtime | ||
| but could also be defined at compile time if needed. | ||
|
|
||
| There are two supported sources for providing this information to coreboot. | ||
|
|
||
| ### CBFS | ||
|
|
||
| The value can be provided with a 32bit raw value in CBFS that is read by coreboot. The value | ||
| can be set at build time but also adjusted in an existing image with `cbfstool`. | ||
|
|
||
| To enable this select the `CONFIG_FW_CONFIG_CBFS` option in the build configuration and add a | ||
| raw 32bit value to CBFS with the name of the current prefix at `CONFIG_FW_PREFIX/fw_config`. | ||
|
|
||
| When `fw_config_probe_device()` or `fw_config_probe()` is called it will look for the specified | ||
| file in CBFS use the value it contains when matching fields and options. | ||
|
|
||
| ### Embedded Controller | ||
|
|
||
| Google Chrome OS devices support an Embedded Controller interface for reading and writing the | ||
| firmware configuration value, along with other board-specific information. It is possible for | ||
| coreboot to read this value at boot on systems that support this feature. | ||
|
|
||
| This option is selected by default for the mainboards that use it with | ||
| `CONFIG_FW_CONFIG_CHROME_EC_CBI` and it is not typically necessary to adjust the value. It is | ||
| possible by enabling the CBFS source and coreboot will look in CBFS first for a valid value | ||
| before asking the embedded controller. | ||
|
|
||
| It is also possible to adjust the value in the embedded controller *(after disabling write | ||
| protection)* with the `ectool` command in a Chrome OS environment. | ||
|
|
||
| For more information on the firmware configuration field on Chrome OS devices see the Chromium | ||
| documentation for [Firmware Config][1] and [Board Info][2]. | ||
|
|
||
| [1]: http://chromium.googlesource.com/chromiumos/docs/+/master/design_docs/firmware_config.md | ||
| [2]: http://chromium.googlesource.com/chromiumos/docs/+/master/design_docs/cros_board_info.md | ||
|
|
||
| ## Firmware Configuration Table | ||
|
|
||
| The firmware configuration table itself is defined in the mainboard `devicetree.cb` with | ||
| special tokens for defining fields and options. | ||
|
|
||
| The table itself is enclosed in a `fw_config` token and terminated with `end` and it contains | ||
| a mix of field and option definitions. | ||
|
|
||
| Each field is defined by providing the field name and the start and end bit marking the exact | ||
| location in the bitmask. Field names must be at least three characters long in order to | ||
| satisfy the sconfig parser requirements and they must be unique with non-overlapping masks. | ||
|
|
||
| field <name> <start-bit> <end-bit> [option...] end | ||
|
|
||
| For single-bit fields only one number is needed: | ||
|
|
||
| field <name> <bit> [option...] end | ||
|
|
||
| Each `field` definition starts a new block that can be composed of zero or more field options, | ||
| and it is terminated with `end`. | ||
|
|
||
| Inside the field block the options can be defined by providing the option name and the field | ||
| value that this option represents when the bit offsets are used to apply a mask and shift. | ||
| Option names must also be at least three characters for the sconfig parser. | ||
|
|
||
| option <name> <value> | ||
|
|
||
| It is possible for there to be multiple `fw_config` blocks and for subsequent `field` blocks | ||
| to add additional `option` definitions to the existing field. These subsequent definitions | ||
| should not provide the field bitmask as it has already been defined earlier in the file and | ||
| this is just matching an existing field by name. | ||
|
|
||
| field <name> [option...] end | ||
|
|
||
| This allows a baseboard to define the major fields and options in `devicetree.cb` and a board | ||
| variant to add specific options to fields in or define new fields in the unused bitmask in | ||
| `overridetree.cb`. | ||
|
|
||
| It is not possible to redefine a field mask or override the value of an existing option this | ||
| way, only to add new options to a field or new fields to the table. | ||
|
|
||
| ### Firmware Configuration Table Example | ||
|
|
||
| In this example a baseboard defines a simple boolean feature that is enabled or disabled | ||
| depending on the value of bit 0, and a field at bits 1-2 that indicates which daughter board | ||
| is attached. | ||
|
|
||
| The baseboard itself defines one daughter board and the variant adds two more possibilities. | ||
| This way each variant can support multiple possible daughter boards in addition to the one | ||
| that was defined by the baseboard. | ||
|
|
||
| #### devicetree.cb | ||
|
|
||
| fw_config | ||
| field FEATURE 0 | ||
| option DISABLED 0 | ||
| option ENABLED 1 | ||
| end | ||
| field DAUGHTER_BOARD 1 2 | ||
| option NONE 0 | ||
| option REFERENCE_DB 1 | ||
| end | ||
| end | ||
|
|
||
| #### overridetree.cb | ||
|
|
||
| fw_config | ||
| field DAUGHTER_BOARD | ||
| option VARIANT_DB_ONE 2 | ||
| option VARIANT_DB_TWO 3 | ||
| end | ||
| end | ||
|
|
||
| The result of this table defined in `devicetree.cb` is a list of constants that can be used | ||
| to check if fields match the firmware configuration options determined at runtime with a | ||
| simple check of the field mask and the option value. | ||
|
|
||
| #### static.h | ||
|
|
||
| ```c | ||
| /* field: FEATURE */ | ||
| #define FW_CONFIG_FIELD_FEATURE_NAME "FEATURE" | ||
| #define FW_CONFIG_FIELD_FEATURE_MASK 0x00000001 | ||
| #define FW_CONFIG_FIELD_FEATURE_OPTION_DISABLED_NAME "DISABLED" | ||
| #define FW_CONFIG_FIELD_FEATURE_OPTION_DISABLED_VALUE 0x00000000 | ||
| #define FW_CONFIG_FIELD_FEATURE_OPTION_ENABLED_NAME "ENABLED" | ||
| #define FW_CONFIG_FIELD_FEATURE_OPTION_ENABLED_VALUE 0x00000001 | ||
|
|
||
| /* field: DAUGHTER_BOARD */ | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_NAME "DAUGHTER_BOARD" | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_MASK 0x00000006 | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_NONE_NAME "NONE" | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_NONE_VALUE 0x00000000 | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_REFERENCE_DB_NAME "REFERENCE_DB" | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_REFERENCE_DB_VALUE 0x00000002 | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_NAME "VARIANT_DB_ONE" | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_VALUE 0x00000004 | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_NAME "VARIANT_DB_TWO" | ||
| #define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_VALUE 0x00000006 | ||
| ``` | ||
| ## Device Probing | ||
| One use of the firmware configuration interface in devicetree is to allow device probing to be | ||
| specified directly with the devices themselves. A new `probe` token is introduced to allow a | ||
| device to be probed by field and option name. Multiple `probe` entries may be present for | ||
| each device and any successful probe will consider the device to be present. | ||
| ### Probing Example | ||
| Continuing with the previous example this device would be considered present if the field | ||
| `DAUGHTER_BOARD` was set to either `VARIANT_DB_ONE` or `VARIANT_DB_TWO`: | ||
| #### overridetree.cb | ||
| chip drivers/generic/example | ||
| device generic 0 on | ||
| probe DAUGHTER_BOARD VARIANT_DB_ONE | ||
| probe DAUGHTER_BOARD VARIANT_DB_TWO | ||
| end | ||
| end | ||
| If the field were set to any other option, including `NONE` and `REFERENCE_DB` and any | ||
| undefined value then the device would be disabled. | ||
| ### Probe Overrides | ||
| When a device is declared with a probe in the baseboard `devicetree.cb` and the same device | ||
| is also present in the `overridetree.cb` then the probing information from the baseboard | ||
| is discarded and the override device must provide all necessary probing information. | ||
| In this example a device is listed in the baseboard with `DAUGHTER_BOARD` field probing for | ||
| `REFERENCE_DB` as a field option, It is also defined as an override device with the field | ||
| probing for the `VARIANT_DB_ONE` option instead. | ||
| In this case only the probe listed in the override is checked and a field option of | ||
| `REFERENCE_DB` will not mark this device present. If both options are desired then the | ||
| override device must list both. This allows an override device to remove a probe entry that | ||
| was defined in the baseboard. | ||
| #### devicetree.cb | ||
| chip drivers/generic/example | ||
| device generic 0 on | ||
| probe DAUGHTER_BOARD REFERENCE_DB | ||
| end | ||
| end | ||
| #### overridetree.cb | ||
| chip drivers/generic/example | ||
| device generic 0 on | ||
| probe DAUGHTER_BOARD VARIANT_DB_ONE | ||
| end | ||
| end | ||
| ### Automatic Device Probing | ||
| At boot time the firmware configuration interface will walk the device tree and apply any | ||
| probe entries that were defined in `devicetree.cb`. This probing takes effect before the | ||
| `BS_DEV_ENUMERATE` step during the boot state machine in ramstage. | ||
| Devices that have a probe list but do do not find a match are disabled by setting | ||
| `dev->enabled = 0` but the chip `enable_dev()` and device `enable()` handlers will still | ||
| be executed to allow any device disable code to execute. | ||
| The result of this probe definition is to provide an array of structures describing each | ||
| field and option to check. | ||
| #### fw_config.h | ||
| ```c | ||
| /** | ||
| * struct fw_config - Firmware configuration field and option. | ||
| * @field_name: Name of the field that this option belongs to. | ||
| * @option_name: Name of the option within this field. | ||
| * @mask: Bitmask of the field. | ||
| * @value: Value of the option within the mask. | ||
| */ | ||
| struct fw_config { | ||
| const char *field_name; | ||
| const char *option_name; | ||
| uint32_t mask; | ||
| uint32_t value; | ||
| }; | ||
| ``` | ||
|
|
||
| #### static.c | ||
|
|
||
| ```c | ||
| STORAGE struct fw_config __devN_probe_list[] = { | ||
| { | ||
| .field_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_NAME, | ||
| .option_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_NAME, | ||
| .mask = FW_CONFIG_FIELD_DAUGHTER_BOARD_MASK, | ||
| .value = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_VALUE | ||
| }, | ||
| { | ||
| .field_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_NAME, | ||
| .option_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_NAME, | ||
| .mask = FW_CONFIG_FIELD_DAUGHTER_BOARD_MASK, | ||
| .value = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_VALUE | ||
| }, | ||
| { } | ||
| }; | ||
| ``` | ||
|
|
||
| ### Runtime Probing | ||
|
|
||
| The device driver probing allows for seamless integration with the mainboard but it is only | ||
| effective in ramstage and for specific devices declared in devicetree.cb. There are other | ||
| situations where code may need to probe or check the value of a field in romstage or at other | ||
| points in ramstage. For this reason it is also possible to use the firmware configuration | ||
| interface directly. | ||
|
|
||
| ```c | ||
| /** | ||
| * fw_config_probe() - Check if field and option matches. | ||
| * @match: Structure containing field and option to probe. | ||
| * | ||
| * Return %true if match is found, %false if match is not found. | ||
| */ | ||
| bool fw_config_probe(const struct fw_config *match); | ||
| ``` | ||
| The argument provided to this function can be created from a macro for easy use: | ||
| FW_CONFIG(field, option) | ||
| This example has a mainboard check if a feature is disabled and set an FSP UPD before memory | ||
| training. This example expects that the default value of this `register` is set to `true` in | ||
| `devicetree.cb` and this code is disabling that feature before FSP is executed. | ||
| ```c | ||
| #include <fw_config.h> | ||
| void mainboard_memory_init_params(FSPM_UPD *mupd) | ||
| { | ||
| if (fw_config_probe_one(FW_CONFIG(FEATURE, DISABLED)) | ||
| mupd->ExampleFeature = false; | ||
| } | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # OCP Tioga Pass | ||
|
|
||
| This page describes coreboot support status for the [OCP] (Open Compute Project) | ||
| Tioga Pass server platform. | ||
|
|
||
| ## Introduction | ||
|
|
||
| OCP Tioga Pass server platform was contributed by Facebook, and was accepted | ||
| in 2019. The design collateral including datasheet can be found at [OCP Tioga Pass]. | ||
|
|
||
| Since complete EE design collateral is open sourced, anyone can build server | ||
| as-is or a variant based on the original design. It can also be purchased from [OCP Market Place]. | ||
| An off-the-shelf version is available, as well as rack ready version. With the | ||
| off-the-shelf version, the server can be plugged into wall power outlet. | ||
|
|
||
| With the off-the-shelf version of Tioga Pass, a complete software solution is | ||
| available. [Off-the-shelf Host Firmware] takes the approach of UEFI/Linuxboot. | ||
|
|
||
| coreboot as of release 4.13 is a proof-of-concept project between Facebook, | ||
| Intel, Wiwynn and Quanta. The context is described at [OCP Tioga Pass POC Blog]. | ||
|
|
||
| ## Required blobs | ||
|
|
||
| This board currently requires: | ||
| - FSP blob: The blob (Intel Skylake Scalable Processor Firmware Support Package) | ||
| is not yet available to the public. The binary is at POC status, hopefully | ||
| someday an IBV is able to obtain the privilege to maintain it. | ||
| - Microcode: `3rdparty/intel-microcode/intel-ucode/06-55-04` | ||
| - ME binary: The binary can be extracted from [Off-the-shelf Host Firmware]. | ||
|
|
||
| ## Payload | ||
| - Linuxboot: This is necessary only if you use Linuxboot as coreboot payload. | ||
| U-root as initramfs, is used in the POC activity. It can be extracted from | ||
| [Off-the-shelf Host Firmware], or it can be built following [All about u-root]. | ||
|
|
||
| ## Flashing coreboot | ||
|
|
||
| To do in-band FW image update, use [flashrom]: | ||
| flashrom -p internal:ich_spi_mode=hwseq -c "Opaque flash chip" --ifd \ | ||
| -i bios --noverify-all -w <path to coreboot image> | ||
|
|
||
| From OpenBMC, to update FW image: | ||
| fw-util mb --force --update <path to coreboot image> | ||
|
|
||
| To power off/on the host: | ||
| power-util mb off | ||
| power-util mb on | ||
|
|
||
| To connect to console through SOL (Serial Over Lan): | ||
| sol-util mb | ||
|
|
||
| ## Known issues / feature gaps | ||
| - C6 state is not supported. Workaround is to disable C6 support through | ||
| target OS and Linuxboot kernel paramter, such as "cpuidle.off=1". | ||
| - SMI handlers are not implemented. | ||
| - xSDT tables are not fully populated, such as processor/socket devices, | ||
| PCIe bridge devices. | ||
| - There is boot stability issue. Occasionally the boot hangs at ramstage | ||
| with following message "BIOS PCU Misc Config Read timed out." | ||
| - If [CB 40500 patchset] is not merged, when PCIe riser card is used, | ||
| boot fails. | ||
| - PCIe devices connected to socket 1 may not work, because FSP | ||
| does not support PCIe topology input for socket 1.k | ||
| - SMBIOS type 7 and type 17 are not populated. | ||
|
|
||
| ## Working | ||
| The solution was developed using Linuxboot payload. The Linuxboot | ||
| kernel versions tried are 4.16.18 and 5.2.9. The initramfs image is | ||
| u-root. | ||
| - Most SMBIOS types | ||
| - BMC integration: | ||
| - BMC readiness check | ||
| - IPMI commands | ||
| - watchdog timer | ||
| - POST complete pin acknowledgement | ||
| - SEL record generation | ||
| - Early serial output | ||
| - port 80h direct to GPIO | ||
| - ACPI tables: APIC/DMAR/DSDT/FACP/FACS/HPET/MCFG/SPMI/SRAT/SLIT/SSDT | ||
|
|
||
| ## Technology | ||
|
|
||
| ```eval_rst | ||
| +------------------------+---------------------------------------------+ | ||
| | Processor (2 sockets) | Intel Skylake Scalable Processor LGA3647 | | ||
| +------------------------+---------------------------------------------+ | ||
| | BMC | Aspeed AST 2500 | | ||
| +------------------------+---------------------------------------------+ | ||
| | PCH | Intel Lewisburg C621 | | ||
| +------------------------+---------------------------------------------+ | ||
| ``` | ||
|
|
||
| [flashrom]: https://flashrom.org/Flashrom | ||
| [OCP]: https://www.opencompute.org/ | ||
| [OCP Tioga Pass]: https://www.opencompute.org/contributions?query=Tioga%20Pass%20v1.0 | ||
| [OCP Market Place]: https://www.opencompute.org/products/109/wiwynn-tioga-pass-advanced-2u-ocp-server-up-to-768gb-12-dimm-slots-4-ssds-for-io-performance | ||
| [Off-the-shelf Host Firmware]: https://github.com/linuxboot/book/blob/master/case_studies/TiogaPass/README.md | ||
| [OCP Tioga Pass POC Blog]: https://www.opencompute.org/blog/linux-firmware-boots-up-server-powered-by-intelr-xeonr-scalable-processor | ||
| [All about u-root]: https://github.com/linuxboot/book/tree/master/u-root | ||
| [CB 40500 patchset]: https://review.coreboot.org/c/coreboot/+/40500 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # Hermes | ||
|
|
||
| Hermes is a regular ATX board designed for workstation PCs. | ||
|
|
||
| The board features: | ||
| * 5 PCIe 16x Gen3 slots | ||
| * 4 ECC capable DDR4 DIMMs | ||
| * 5 dedicated Ethernet ports | ||
| * 1 BMC Ethernet port | ||
| * VGA | ||
| * COM port | ||
| * 2 COM port headers | ||
| * 4 SATA ports, | ||
| * NVMe M2 slot | ||
| * CNVi M2 slot | ||
| * 3 optional DisplayPort outputs | ||
| * optional TPM2 | ||
|
|
||
| ## Required proprietary blobs | ||
|
|
||
| - [Intel FSP2.0] | ||
| - Intel SPS | ||
|
|
||
| ## Flashing coreboot | ||
|
|
||
| * The BIOS flash can be updated over the BMC, but the update file has a proprietary format | ||
| * For development a dediprog compatible pinheader is present which allows to use an EM100 | ||
|
|
||
| ## Known issues | ||
|
|
||
| - MRC caching does not work on cold boot with Intel SPS (see [Intel FSP2.0]) | ||
|
|
||
| ## Technology | ||
|
|
||
| ```eval_rst | ||
| +------------------+--------------------------------------------------+ | ||
| | CPU | CoffeeLake + CoffeeLake R (Core + Xeon) | | ||
| +------------------+--------------------------------------------------+ | ||
| | PCH | Intel C246 | | ||
| +------------------+--------------------------------------------------+ | ||
| | Coprocessor | Intel SPS (server version of the ME) | | ||
| +------------------+--------------------------------------------------+ | ||
| | Super I/O | none | | ||
| +------------------+--------------------------------------------------+ | ||
| | BMC | Aspeed AST2500 | | ||
| +------------------+--------------------------------------------------+ | ||
| ``` | ||
|
|
||
| ## Extra links | ||
|
|
||
| [flashrom]: https://flashrom.org/Flashrom | ||
| [flashing tutorial]: ../../../../flash_tutorial/ext_power.md | ||
| [Intel FSP2.0]: ../../../../soc/intel/fsp/index.md | ||
| [AST2500]: https://www.aspeedtech.com/products.php?fPath=20&rId=440 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # x86 System Managment Mode | ||
|
|
||
| ## Introduction | ||
|
|
||
| The code running in System Management Mode (SMM) provides runtime services | ||
| to applications running in [ring0]. It has a higher privilege level than | ||
| [ring0] and resides in the SMRAM region which cannot be accessed from [ring0]. | ||
|
|
||
| SMM can be entered by issuing System Managment Interrupts (SMIs). | ||
|
|
||
| ## Secure data exchange | ||
|
|
||
| In order to not leak SMM internals or accidentally overwrite parts of SMM, | ||
| [ring0] provided data (pointers, offsets, sizes, ...) must be checked before | ||
| using them in SMM. | ||
|
|
||
| There exist two methods to verify data: | ||
|
|
||
| ```C | ||
| /* Returns true if the region overlaps with the SMM */ | ||
| bool smm_region_overlaps_handler(struct region *r); | ||
| ``` | ||
| ```C | ||
| /* Returns true if the memory pointed to overlaps with SMM reserved memory. */ | ||
| static inline bool smm_points_to_smram(const void *ptr, const size_t len); | ||
| ``` | ||
|
|
||
| [ring0]: https://en.wikipedia.org/wiki/Protection_ring |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,384 @@ | ||
| # Writing unit tests for coreboot | ||
|
|
||
| ## Introduction | ||
| General thoughts about unit testing coreboot can be found in | ||
| [Unit testing coreboot](../technotes/2020-03-unit-testing-coreboot.md). | ||
|
|
||
| This document aims to guide developers through the process of adding and writing | ||
| unit tests for coreboot modules. | ||
|
|
||
| As an example of unit under test, `src/device/i2c.c` (referred hereafter as UUT | ||
| "Unit Under Test") will be used. This is simple module, thus it should be easy | ||
| for the reader to focus solely on the testing logic, without the need to spend | ||
| too much time on digging deeply into the source code details and flow of | ||
| operations. That being said, a good understanding of what the unit under test is | ||
| doing is crucial for writing unit tests. | ||
|
|
||
| This tutorial should also be helpful for developers who want to follow | ||
| [TDD](https://en.wikipedia.org/wiki/Test-driven_development). Even though TDD | ||
| has a different work flow of building tests first, followed by the code that | ||
| satisfies them, the process of writing tests and adding them to the tree is the | ||
| same. | ||
|
|
||
| ## Analysis of unit under test | ||
| First of all, it is necessary to precisely establish what we want to test in a | ||
| particular module. Usually this will be an externally exposed API, which can be | ||
| used by other modules. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| In case of our UUT, API consist of two methods: | ||
| .. code-block:: c | ||
| int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg, uint8_t *data, | ||
| uint8_t mask, uint8_t shift) | ||
| int i2c_write_field(unsigned int bus, uint8_t chip, uint8_t reg, uint8_t data, | ||
| uint8_t mask, uint8_t shift) | ||
| For sake of simplicity, let's focus on `i2c_read_field` in this document. | ||
| ``` | ||
|
|
||
| Once the API is defined, the next question is __what__ this API is doing (or | ||
| what it will be doing in case of TDD). In other words, what outputs we are | ||
| expecting from particular functions, when providing particular input parameters. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| .. code-block:: c | ||
| int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg, uint8_t *data, | ||
| uint8_t mask, uint8_t shift) | ||
| This is a method which means to read content of register `reg` from i2c device | ||
| on i2c `bus` and slave address `chip`, applying bit `mask` and offset `shift` | ||
| to it. Returned data should be placed in `data`. | ||
| ``` | ||
|
|
||
| The next step is to determine all external dependencies of UUT in order to mock | ||
| them out. Usually we want to isolate the UUT as much as possible, so that the | ||
| test result depends __only__ on the behavior of UUT and not on the other | ||
| modules. While some software dependencies may be hard to be mock (for example | ||
| due to complicated dependencies) and thus should be simply linked into the test | ||
| binaries, all hardware dependencies need to be mocked out, since in the | ||
| user-space host environment, targets hardware is not available. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| `i2c_read_field` is calling `i2c_readb`, which eventually invokes | ||
| `i2c_transfer`. This method simply calls `platform_i2c_transfer`. The last | ||
| function in the chain is a hardware-touching one, and defined separately for | ||
| different SOCs. It is responsible for issuing transactions on the i2c bus. | ||
| For the purpose of writing unit test, we should mock this function. | ||
| ``` | ||
|
|
||
| ## Adding new tests | ||
| In order to keep the tree clean, the `tests/` directory should mimic the `src/` | ||
| directory, so that test harness code is placed in a location corresponding to | ||
| UUT. Furthermore, the naming convention is to add the suffix `-test` to the UUT | ||
| name when creating a new test harness file. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| Considering that UUT is `src/device/i2c.c`, test file should be named | ||
| `tests/device/i2c-test.c`. When adding a new test file, it needs to be | ||
| registered with the coreboot unit testing infrastructure. | ||
| ``` | ||
|
|
||
| Every directory under `tests/` should contain a Makefile.inc, similar to what | ||
| can be seen under the `src/`. Register a new test in Makefile.inc, by | ||
| __appending__ test name to the `tests-y` variable. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| .. code-block:: c | ||
| tests-y += i2c-test | ||
| ``` | ||
|
|
||
| Next step is to list all source files, which should be linked together in order | ||
| to create test binary. Usually a tests requires only two files - UUT and test | ||
| harness code, but sometimes more is needed to provide the test environment. | ||
| Source files are registered in `<test_name>-srcs` variable. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| .. code-block:: c | ||
| i2c-test-srcs += tests/device/i2c-test.c | ||
| i2c-test-srcs += src/device/i2c.c | ||
| ``` | ||
|
|
||
| Above minimal configuration is a basis for further work. One can try to build | ||
| and run test binary either by invoking `make tests/<test_dir>/<test_name>` or by | ||
| running all unit tests (whole suite) for coreboot `make unit-tests`. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| .. code-block:: c | ||
| make tests/device/i2c-test | ||
| or | ||
| .. code-block:: c | ||
| make unit-tests | ||
| ``` | ||
|
|
||
| When trying to build test binary, one can often see linker complains about | ||
| `undefined reference` to couple of symbols. This is one of solutions to | ||
| determine all external dependencies of UUT - iteratively build test and resolve | ||
| errors one by one. At this step, developer should decide either it's better to | ||
| add an extra module to provide necessary definitions or rather mock such | ||
| dependency. Quick guide through adding mocks is provided later in this doc. | ||
|
|
||
| ## Writing new tests | ||
| In coreboot, [Cmocka](https://cmocka.org/) is used as unit test framework. The | ||
| project has exhaustive [API documentation](https://api.cmocka.org/). Let's see | ||
| how we may incorporate it when writing tests. | ||
|
|
||
| ### Assertions | ||
| Testing the UUT consists of calling the functions in the UUT and comparing the | ||
| returned values to the expected values. Cmocka implements | ||
| [a set of assert macros](https://api.cmocka.org/group__cmocka__asserts.html) to | ||
| compare a value with an expected value. If the two values do not match, the test | ||
| fails with an error message. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| In our example, the simplest test is to call UUT for reading our fake devices | ||
| registers and do all calculation in the test harness itself. At the end, let's | ||
| compare integers with `assert_int_equal`. | ||
| .. code-block:: c | ||
| #define MASK 0x3 | ||
| #define SHIFT 0x1 | ||
| static void i2c_read_field_test(void **state) | ||
| { | ||
| int bus, slave, reg; | ||
| int i, j; | ||
| uint8_t buf; | ||
| mock_expect_params_platform_i2c_transfer(); | ||
| /* Read particular bits in all registers in all devices, then compare | ||
| with expected value. */ | ||
| for (i = 0; i < ARRAY_SIZE(i2c_ex_devs); i++) | ||
| for (j = 0; j < ARRAY_SIZE(i2c_ex_devs[0].regs); j++) { | ||
| i2c_read_field(i2c_ex_devs[i].bus, | ||
| i2c_ex_devs[i].slave, | ||
| i2c_ex_devs[i].regs[j].reg, | ||
| &buf, MASK, SHIFT); | ||
| assert_int_equal((i2c_ex_devs[i].regs[j].data & | ||
| (MASK << SHIFT)) >> SHIFT, buf); | ||
| }; | ||
| } | ||
| ``` | ||
|
|
||
| ### Mocks | ||
|
|
||
| #### Overview | ||
| Many coreboot modules are low level software that touch hardware directly. | ||
| Because of this, one of the most important and challenging part of | ||
| writing tests is to design and implement mocks. A mock is a software component | ||
| which implements the API of another component so that the test can verify that | ||
| certain functions are called (or not called), verify the parameters passed to | ||
| those functions, and specify the return values from those functions. Mocks are | ||
| especially useful when the API to be implemented is one that accesses hardware | ||
| components. | ||
|
|
||
| When writing a mock, the developer implements the same API as the module being | ||
| mocked. Such a mock may, for example, register a set of driver methods. Behind | ||
| this API, there is usually a simulation of real hardware. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| For purpose of our i2c test, we may introduce two i2c devices with set of | ||
| registers, which simply are structs in memory. | ||
| .. code-block:: c | ||
| /* Simulate two i2c devices, both on bus 0, each with three uint8_t regs | ||
| implemented. */ | ||
| typedef struct { | ||
| uint8_t reg; | ||
| uint8_t data; | ||
| } i2c_ex_regs_t; | ||
| typedef struct { | ||
| unsigned int bus; | ||
| uint8_t slave; | ||
| i2c_ex_regs_t regs[3]; | ||
| } i2c_ex_devs_t; | ||
| i2c_ex_devs_t i2c_ex_devs[] = { | ||
| {.bus = 0, .slave = 0xA, .regs = { | ||
| {.reg = 0x0, .data = 0xB}, | ||
| {.reg = 0x1, .data = 0x6}, | ||
| {.reg = 0x2, .data = 0xF}, | ||
| } }, | ||
| {.bus = 0, .slave = 0x3, .regs = { | ||
| {.reg = 0x0, .data = 0xDE}, | ||
| {.reg = 0x1, .data = 0xAD}, | ||
| {.reg = 0x2, .data = 0xBE}, | ||
| } }, | ||
| }; | ||
| These fake devices will be accessed instead of hardware ones: | ||
| .. code-block:: c | ||
| reg = tmp->buf[0]; | ||
| /* Find object for requested device */ | ||
| for (i = 0; i < ARRAY_SIZE(i2c_ex_devs); i++, i2c_dev++) | ||
| if (i2c_ex_devs[i].slave == tmp->slave) { | ||
| i2c_dev = &i2c_ex_devs[i]; | ||
| break; | ||
| } | ||
| if (i2c_dev == NULL) | ||
| return -1; | ||
| /* Write commands */ | ||
| if (tmp->len > 1) { | ||
| i2c_dev->regs[reg].data = tmp->buf[1]; | ||
| }; | ||
| /* Read commands */ | ||
| for (i = 0; i < count; i++, tmp++) | ||
| if (tmp->flags & I2C_M_RD) { | ||
| *(tmp->buf) = i2c_dev->regs[reg].data; | ||
| }; | ||
| ``` | ||
|
|
||
| Cmocka uses a feature that gcc provides for breaking dependencies at the link | ||
| time. It is possible to override implementation of some function, with the | ||
| method from test harness. This allows test harness to take control of execution | ||
| from binary (during the execution of test), and stimulate UUT as required | ||
| without changing the source code. | ||
|
|
||
| coreboot unit test infrastructure supports overriding of functions at link time. | ||
| This is as simple as adding a `name_of_function` to be mocked into | ||
| <test_name>-mocks variable in Makefile.inc. The result is that every time the | ||
| function is called, `wrap_name_of_function` will be called instead. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| .. code-block:: c | ||
| i2c-test-mocks += platform_i2c_transfer | ||
| Now, dev can write own implementation of `platform_i2c_transfer` and define it | ||
| as `wrap_platform_i2c_transfer`. This implementation instead of accessing real | ||
| i2c bus, will write/read from fake structs. | ||
| .. code-block:: c | ||
| int __wrap_platform_i2c_transfer(unsigned int bus, struct i2c_msg *segments, | ||
| int count) | ||
| { | ||
| } | ||
| ``` | ||
|
|
||
| #### Checking mock's arguments | ||
| A test can verify the parameters provided by the UUT to the mock function. The | ||
| developer may also verify that number of calls to mock is correct and the order | ||
| of calls to particular mocks is as expected (See | ||
| [this](https://api.cmocka.org/group__cmocka__call__order.html)). The Cmocka | ||
| macros for checking parameters are described | ||
| [here](https://api.cmocka.org/group__cmocka__param.html). In general, in mock | ||
| function, one makes a call to `check_expected(<param_name>)` and in the | ||
| corresponding test function, `expect*()` macro, with description which parameter | ||
| in which mock should have particular value, or be inside a described range. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| In our example, we may want to check that `platform_i2c_transfer` is fed with | ||
| number of segments bigger than 0, each segment has flags which are in | ||
| supported range and each segment has buf which is non-NULL. We are expecting | ||
| such values for _every_ call, thus the last parameter in `expect*` macros is | ||
| -1. | ||
| .. code-block:: c | ||
| static void mock_expect_params_platform_i2c_transfer(void) | ||
| { | ||
| unsigned long int expected_flags[] = {0, I2C_M_RD, I2C_M_TEN, | ||
| I2C_M_RECV_LEN, I2C_M_NOSTART}; | ||
| /* Flags should always be only within supported range */ | ||
| expect_in_set_count(__wrap_platform_i2c_transfer, segments->flags, | ||
| expected_flags, -1); | ||
| expect_not_value_count(__wrap_platform_i2c_transfer, segments->buf, | ||
| NULL, -1); | ||
| expect_in_range_count(__wrap_platform_i2c_transfer, count, 1, INT_MAX, | ||
| -1); | ||
| } | ||
| And the checks below should be added to our mock | ||
| .. code-block:: c | ||
| check_expected(count); | ||
| for (i = 0; i < count; i++, segments++) { | ||
| check_expected_ptr(segments->buf); | ||
| check_expected(segments->flags); | ||
| } | ||
| ``` | ||
|
|
||
| #### Instrument mocks | ||
| It is possible for the test function to instrument what the mock will return to | ||
| the UUT. This can be done by using the `will_return*()` and `mock()` macros. | ||
| These are described in | ||
| [the Mock Object section](https://api.cmocka.org/group__cmocka__mock.html) of | ||
| the Cmocka API documentation. | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: Example | ||
| There is an non-coreboot example for using Cmocka available | ||
| `here <https://lwn.net/Articles/558106/>`_. | ||
| ``` | ||
|
|
||
| ### Test runner | ||
| Finally, the developer needs to implement the test `main()` function. All tests | ||
| should be registered there and cmocka test runner invoked. All methods for | ||
| invoking Cmocka test are described | ||
| [here](https://api.cmocka.org/group__cmocka__exec.html). | ||
|
|
||
| ```eval_rst | ||
| .. admonition:: i2c-test example | ||
| We don't need any extra setup and teardown functions for i2c-test, so let's | ||
| simply register test for `i2c_read_field` and return from main value which is | ||
| output of Cmocka's runner (it returns number of tests that failed). | ||
| .. code-block:: c | ||
| int main(void) | ||
| { | ||
| const struct CMUnitTest tests[] = { | ||
| cmocka_unit_test(i2c_read_field_test), | ||
| }; | ||
| return cmocka_run_group_tests(tests, NULL, NULL); | ||
| } | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # type this to get working .config: | ||
| # make defconfig KBUILD_DEFCONFIG=configs/builder/config.ocp.deltalake | ||
|
|
||
| CONFIG_VENDOR_OCP=y | ||
| CONFIG_BOARD_OCP_DELTALAKE=y | ||
| CONFIG_HAVE_IFD_BIN=y | ||
| CONFIG_HAVE_ME_BIN=y | ||
| CONFIG_DO_NOT_TOUCH_DESCRIPTOR_REGION=y | ||
| CONFIG_USE_CPU_MICROCODE_CBFS_BINS=y | ||
| CONFIG_CPU_MICROCODE_CBFS_EXTERNAL_BINS=y | ||
| CONFIG_CPU_UCODE_BINARIES="site-local/deltalake/mbf5065a.mcb" | ||
| CONFIG_ADD_FSP_BINARIES=y | ||
| CONFIG_FSP_T_FILE="site-local/deltalake/Server_T.fd" | ||
| CONFIG_FSP_M_FILE="site-local/deltalake/Server_M.fd" | ||
| CONFIG_FSP_S_FILE="site-local/deltalake/Server_S.fd" | ||
| CONFIG_ME_BIN_PATH="site-local/deltalake/flashregion_2_intel_me.bin" | ||
| CONFIG_IFD_BIN_PATH="site-local/deltalake/flashregion_0_flashdescriptor.bin" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-only */ | ||
|
|
||
| #include <acpi/acpi_gnvs.h> | ||
| #include <types.h> | ||
| #include <ec/google/chromeec/ec.h> | ||
| #include <vendorcode/google/chromeos/gnvs.h> | ||
|
|
||
| void gnvs_assign_chromeos(void) | ||
| { | ||
| chromeos_acpi_t *gnvs_chromeos = gnvs_chromeos_ptr(); | ||
| chromeos_init_chromeos_acpi(gnvs_chromeos); | ||
|
|
||
| /* EC can override to ECFW_RW. */ | ||
| gnvs_chromeos->vbt2 = ACTIVE_ECFW_RO; | ||
|
|
||
| if (CONFIG(EC_GOOGLE_CHROMEEC) && !google_ec_running_ro()) | ||
| gnvs_chromeos->vbt2 = ACTIVE_ECFW_RW; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-only */ | ||
|
|
||
| #include <acpi/acpi_gnvs.h> | ||
| #include <acpi/acpigen.h> | ||
| #include <cbmem.h> | ||
| #include <console/console.h> | ||
| #include <string.h> | ||
| #include <types.h> | ||
|
|
||
| static void *gnvs; | ||
|
|
||
| void *acpi_get_gnvs(void) | ||
| { | ||
| if (gnvs) | ||
| return gnvs; | ||
|
|
||
| gnvs = cbmem_find(CBMEM_ID_ACPI_GNVS); | ||
| if (gnvs) | ||
| return gnvs; | ||
|
|
||
| printk(BIOS_ERR, "Unable to locate Global NVS\n"); | ||
| return NULL; | ||
| } | ||
|
|
||
| static void gnvs_assign_cbmc(void) | ||
| { | ||
| uint32_t *gnvs_cbmc = gnvs_cbmc_ptr(); | ||
| if (gnvs_cbmc) | ||
| *gnvs_cbmc = (uintptr_t)cbmem_find(CBMEM_ID_CONSOLE); | ||
| } | ||
|
|
||
| void *gnvs_get_or_create(void) | ||
| { | ||
| size_t gnvs_size; | ||
|
|
||
| if (gnvs) | ||
| return gnvs; | ||
|
|
||
| gnvs = cbmem_find(CBMEM_ID_ACPI_GNVS); | ||
| if (gnvs) | ||
| return gnvs; | ||
|
|
||
| gnvs_size = gnvs_size_of_array(); | ||
|
|
||
| gnvs = cbmem_add(CBMEM_ID_ACPI_GNVS, gnvs_size); | ||
| if (!gnvs) | ||
| return gnvs; | ||
|
|
||
| memset(gnvs, 0, gnvs_size); | ||
|
|
||
| if (CONFIG(CONSOLE_CBMEM)) | ||
| gnvs_assign_cbmc(); | ||
|
|
||
| if (CONFIG(CHROMEOS)) | ||
| gnvs_assign_chromeos(); | ||
|
|
||
| return gnvs; | ||
| } | ||
|
|
||
| void acpi_inject_nvsa(void) | ||
| { | ||
| uintptr_t gnvs_address = (uintptr_t)acpi_get_gnvs(); | ||
| if (!gnvs_address) | ||
| return; | ||
|
|
||
| acpigen_write_scope("\\"); | ||
| acpigen_write_name_dword("NVSA", gnvs_address); | ||
| acpigen_pop_len(); | ||
| } |