diff --git a/src/mainboard/prodrive/atlas/Makefile.inc b/src/mainboard/prodrive/atlas/Makefile.inc index 3826c8f654..6c11876f35 100644 --- a/src/mainboard/prodrive/atlas/Makefile.inc +++ b/src/mainboard/prodrive/atlas/Makefile.inc @@ -1,6 +1,7 @@ ## SPDX-License-Identifier: GPL-2.0-only all-y += ec.c +all-y += vpd.c bootblock-y += bootblock.c bootblock-y += early_gpio.c @@ -9,3 +10,4 @@ romstage-y += romstage_fsp_params.c ramstage-y += gpio.c ramstage-y += mainboard.c +ramstage-y += smbios.c diff --git a/src/mainboard/prodrive/atlas/mainboard.c b/src/mainboard/prodrive/atlas/mainboard.c index 227e30a3c4..db808ce45a 100644 --- a/src/mainboard/prodrive/atlas/mainboard.c +++ b/src/mainboard/prodrive/atlas/mainboard.c @@ -1,14 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -#include #include -#include +#include #include -#include -#include #include +#include +#include #include "gpio.h" +#include "vpd.h" void smbios_fill_dimm_locator(const struct dimm_info *dimm, struct smbios_type17 *t) { @@ -53,6 +53,25 @@ static void mainboard_init(void *chip_info) printk(BIOS_INFO, "HSID: 0x%x\n", get_hsid()); } +static const char *get_formatted_pn(void) +{ + static char buffer[32 + ATLAS_SN_PN_LENGTH] = {0}; + const char *prefix = "P/N: "; + snprintf(buffer, sizeof(buffer), "%s%s", prefix, get_emi_eeprom_vpd()->part_number); + return buffer; +} + +static void mainboard_smbios_strings(struct device *dev, struct smbios_type11 *t) +{ + t->count = smbios_add_string(t->eos, get_formatted_pn()); +} + +static void mainboard_enable(struct device *dev) +{ + dev->ops->get_smbios_strings = mainboard_smbios_strings; +} + struct chip_operations mainboard_ops = { - .init = mainboard_init, + .init = mainboard_init, + .enable_dev = mainboard_enable, }; diff --git a/src/mainboard/prodrive/atlas/smbios.c b/src/mainboard/prodrive/atlas/smbios.c new file mode 100644 index 0000000000..1167a807ac --- /dev/null +++ b/src/mainboard/prodrive/atlas/smbios.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include + +#include "vpd.h" + +const char *smbios_mainboard_serial_number(void) +{ + return get_emi_eeprom_vpd()->serial_number; +} diff --git a/src/mainboard/prodrive/atlas/vpd.c b/src/mainboard/prodrive/atlas/vpd.c new file mode 100644 index 0000000000..12371f71e6 --- /dev/null +++ b/src/mainboard/prodrive/atlas/vpd.c @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include + +#include "ec.h" +#include "mainboard.h" +#include "vpd.h" + +const union emi_eeprom_vpd *get_emi_eeprom_vpd(void) +{ + static union emi_eeprom_vpd vpd = {0}; + + /* Check if cached VPD is valid */ + if (vpd.header.revision == VPD_LATEST_REVISION) + return &vpd; + + ec_emi_read(vpd.raw, EMI_0_IO_BASE_ADDR, 0, 0, sizeof(vpd.raw)); + + /* If the magic value doesn't match, consider EEPROM VPD unreliable */ + if (vpd.header.magic != VPD_MAGIC) { + printk(BIOS_WARNING, "Atlas VPD: Bad magic value, using fallback defaults\n"); + vpd.header.revision = 0; + } else { + printk(BIOS_DEBUG, "Atlas VPD: Got revision %u from EC\n", vpd.header.revision); + } + + /* + * For backwards compatibility, if the VPD from the EC is an older + * version, uprev it to the latest version coreboot knows about by + * filling in the remaining fields with default values. Should the + * EC provide a newer VPD revision, coreboot would downgrade it to + * the latest version it knows about as the VPD layout is designed + * to be backwards compatible. + * + * Whenever the value of `VPD_LATEST_REVISION` is incremented, add + * a new `case` label just before the `default` label that matches + * the second latest revision to initialise the newly-added fields + * of the VPD structure with a reasonable fallback value. Note the + * intentional falling through. + */ + switch (vpd.header.revision) { + case 0: + memset(vpd.raw, 0, sizeof(vpd.raw)); + vpd.header.magic = VPD_MAGIC; + vpd.serial_number[0] = '\0'; + vpd.part_number[0] = '\0'; + vpd.profile = ATLAS_PROF_UNPROGRAMMED; + __fallthrough; + default: + /* Ensure serial numbers are NULL-terminated, update revision last */ + vpd.serial_number[ATLAS_SN_PN_LENGTH - 1] = '\0'; + vpd.part_number[ATLAS_SN_PN_LENGTH - 1] = '\0'; + vpd.header.revision = VPD_LATEST_REVISION; + break; + } + + return &vpd; +} diff --git a/src/mainboard/prodrive/atlas/vpd.h b/src/mainboard/prodrive/atlas/vpd.h new file mode 100644 index 0000000000..3955c119a7 --- /dev/null +++ b/src/mainboard/prodrive/atlas/vpd.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef ATLAS_VPD_H +#define ATLAS_VPD_H + +/* + * The Atlas COMe module stores some non-volatile vital product data in + * an EC-attached I2C EEPROM. The EC firmware reads the EEPROM contents + * and provides them to the host via EMI (Embedded Memory Interface) 0. + */ + +#include + +#define VPD_MAGIC 0x56504453 /* 'VPDS' */ + +/* + * Increment this value whenever new fields are added to the structures. + * Furthermore, adapt the `get_emi_eeprom_vpd()` function accordingly to + * provide fallback values for newly-added fields. + */ +#define VPD_LATEST_REVISION 1 + +struct __packed emi_eeprom_vpd_header { + uint32_t magic; + uint8_t revision; + uint8_t _rfu[15]; /* Reserved for Future Use */ +}; + +/* For backwards compatibility reasons, do NOT reuse enum values! */ +enum atlas_profile { + ATLAS_PROF_UNPROGRAMMED = 0, /* EEPROM not initialised */ + ATLAS_PROF_DEFAULT = 1, + ATLAS_PROF_REALTIME_PERFORMANCE = 2, + ATLAS_PROF_THEMIS_LED_CONFIG = 3, +}; + +#define ATLAS_SN_PN_LENGTH 20 + +#define EMI_EEPROM_LAYOUT_LENGTH ( \ + sizeof(struct emi_eeprom_vpd_header) + \ + ATLAS_SN_PN_LENGTH + \ + ATLAS_SN_PN_LENGTH + \ + sizeof(uint16_t) \ + ) + +union emi_eeprom_vpd { + struct __packed { + struct emi_eeprom_vpd_header header; + char serial_number[ATLAS_SN_PN_LENGTH]; /* xx-xx-xxx-xxx */ + char part_number[ATLAS_SN_PN_LENGTH]; /* xxx-xxxx-xxxx.Rxx */ + uint16_t profile; + }; + uint8_t raw[EMI_EEPROM_LAYOUT_LENGTH]; +}; + +/* Always returns a non-NULL pointer to valid data */ +const union emi_eeprom_vpd *get_emi_eeprom_vpd(void); + +#endif /* ATLAS_VPD_H */