From f03fd4d6c7a4c5818dc76f713e6f208f8c04e3a9 Mon Sep 17 00:00:00 2001 From: SergiiDmytruk Date: Tue, 19 Oct 2021 19:01:54 +0300 Subject: [PATCH] Construct MVPD partition in memory on startup (#102) * First try to read VPD from EEPROM Signed-off-by: Sergii Dmytruk * WIP: Now it reads some VPD data Signed-off-by: Sergii Dmytruk * Finding and iterating VTOC Signed-off-by: Sergii Dmytruk * MVPD construction Signed-off-by: Sergii Dmytruk * Fix EEPROM addressing and MVPD construction Signed-off-by: Sergii Dmytruk * Use constructed MVPD instead of PNOR cache Signed-off-by: Sergii Dmytruk * Enable reading both EEPROM chips Signed-off-by: Sergii Dmytruk * Fixes for EEPROM access Signed-off-by: Sergii Dmytruk * Code cleanup Change-Id: Ib7b53149ec2f12c7f541cf7f60e3b023556b0c24 Signed-off-by: Sergii Dmytruk * Review corrections Change-Id: Ib5376210c5ef175aff4afddd79f42c3ab913670e Signed-off-by: Sergii Dmytruk --- src/include/cpu/power/vpd.h | 2 + src/soc/ibm/power9/Makefile.inc | 1 + src/soc/ibm/power9/mvpd.c | 324 +++++++++++++++++++++++++++----- src/soc/ibm/power9/vpd.c | 3 - 4 files changed, 282 insertions(+), 48 deletions(-) diff --git a/src/include/cpu/power/vpd.h b/src/include/cpu/power/vpd.h index 0360a36668c..d311a0b4204 100644 --- a/src/include/cpu/power/vpd.h +++ b/src/include/cpu/power/vpd.h @@ -7,6 +7,8 @@ #include #define VPD_RECORD_NAME_LEN 4 +#define VPD_KWD_NAME_LEN 2 +#define VPD_RECORD_SIZE_LEN 2 void vpd_pnor_main(void); diff --git a/src/soc/ibm/power9/Makefile.inc b/src/soc/ibm/power9/Makefile.inc index 5b2cac6e711..0e256083a19 100644 --- a/src/soc/ibm/power9/Makefile.inc +++ b/src/soc/ibm/power9/Makefile.inc @@ -40,5 +40,6 @@ ramstage-y += powerbus.c ramstage-y += pstates.c ramstage-y += xive.c ramstage-y += int_vectors.S +ramstage-y += i2c.c endif diff --git a/src/soc/ibm/power9/mvpd.c b/src/soc/ibm/power9/mvpd.c index 8a2b5238d9e..c03170094d3 100644 --- a/src/soc/ibm/power9/mvpd.c +++ b/src/soc/ibm/power9/mvpd.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,8 @@ #define MVPD_TOC_ENTRIES 32 #define MVPD_TOC_SIZE (MVPD_TOC_ENTRIES*sizeof(struct mvpd_toc_entry)) +#define EEPROM_CHIP_SIZE (64 * KiB) + /* Each entry points to a VPD record */ struct mvpd_toc_entry { char name[4]; // Name without trailing NUL byte @@ -23,6 +26,274 @@ struct mvpd_toc_entry { uint8_t reserved[2]; // Unused } __attribute__((packed)); +struct pt_record { + char record_name[4]; + /* All of these fields are in little endian */ + uint16_t record_type; + uint16_t record_offset; + uint16_t record_length; + uint16_t ecc_offset; + uint16_t ecc_length; +} __attribute__((packed)); + +/* + * Configuration of EEPROM with VPD data in talos.xml: + * + * + * EEPROM_VPD_PRIMARY_INFO + * + * + * i2cMasterPath + * + * /sys-0/node-0/motherboard-0/proc_socket-0/sforza-0/p9_proc_s/i2c-master-prom0-mvpd-primary/ + * + * + * port0 + * devAddr0xA0 + * engine1 + * byteAddrOffset0x02 + * maxMemorySizeKB0x80 + * chipCount0x02 + * writePageSize0x80 + * writeCycleTime0x0A + * + * + */ + +/* Reads from a single EEPROM chip, which is deduced from offset. Returns number + * of bytes read. */ +static int read_eeprom_chip(uint32_t offset, void *data, uint16_t len) +{ + const unsigned int bus = 1; + uint16_t addr = 0xA0; + uint16_t slave = 0; + uint16_t actual_offset = 0; + + struct i2c_msg seg[2]; + + /* Two chips at two different addresses */ + if (offset >= EEPROM_CHIP_SIZE) { + offset -= EEPROM_CHIP_SIZE; + addr += 0x02; + } + + assert(offset < EEPROM_CHIP_SIZE); + actual_offset = offset; + + /* Most-significant bit is port number */ + slave = addr >> 1; + + seg[0].flags = 0; + seg[0].slave = slave; + seg[0].buf = (uint8_t *)&actual_offset; + seg[0].len = sizeof(actual_offset); + seg[1].flags = I2C_M_RD; + seg[1].slave = slave; + seg[1].buf = data; + seg[1].len = len; + + return i2c_transfer(bus, seg, ARRAY_SIZE(seg)) - sizeof(actual_offset); +} + +/* Reads from EEPROM handling accesses across chip boundaries (64 KiB). Returns + * number of bytes read. */ +static int read_eeprom(uint32_t offset, void *data, uint32_t len) +{ + int ret_value1 = 0; + int ret_value2 = 0; + uint16_t len1 = 0; + uint16_t len2 = 0; + + assert(len != 0); + if (offset / EEPROM_CHIP_SIZE == (offset + len - 1) / EEPROM_CHIP_SIZE) + return read_eeprom_chip(offset, data, len); + + len1 = EEPROM_CHIP_SIZE - offset; + len2 = len - len1; + + ret_value1 = read_eeprom_chip(offset, data, len1); + if (ret_value1 < 0) + return ret_value1; + + ret_value2 = read_eeprom_chip(EEPROM_CHIP_SIZE, (uint8_t *)data + len1, len2); + if (ret_value2 < 0) + return ret_value2; + + return ret_value1 + ret_value2; +} + +/* Finds and extracts i-th keyword (`index` specifies which one) from a record + * in EEPROM that starts at specified offset */ +static bool eeprom_extract_kwd(uint64_t offset, uint8_t index, + const char *record_name, const char *kwd_name, + uint8_t *buf, size_t *size) +{ + uint16_t record_size = 0; + uint8_t name[VPD_RECORD_NAME_LEN]; + + if (strlen(record_name) != VPD_RECORD_NAME_LEN) + die("Record name has wrong length: %s!\n", record_name); + if (strlen(kwd_name) != VPD_KWD_NAME_LEN) + die("Keyword name has wrong length: %s!\n", kwd_name); + + if (read_eeprom(offset, &record_size, sizeof(record_size)) != VPD_RECORD_SIZE_LEN) + die("Failed to read record size from EEPROM\n"); + + offset += VPD_RECORD_SIZE_LEN; + record_size = le16toh(record_size); + + /* Skip mandatory "RT" and one byte of keyword size (always 4) */ + offset += VPD_KWD_NAME_LEN + 1; + + if (read_eeprom(offset, name, sizeof(name)) != sizeof(name)) + die("Failed to read record name from EEPROM\n"); + + if (memcmp(name, record_name, VPD_RECORD_NAME_LEN)) + die("Expected to be working with %s record, got %.4s!\n", + record_name, name); + + offset += VPD_RECORD_NAME_LEN; + + while (offset < record_size) { + uint8_t name_buf[VPD_KWD_NAME_LEN]; + uint16_t kwd_size = 0; + + if (read_eeprom(offset, name_buf, sizeof(name_buf)) != sizeof(name_buf)) + die("Failed to read keyword name from EEPROM\n"); + + /* This is always the last keyword */ + if (!memcmp(name_buf, "PF", VPD_KWD_NAME_LEN)) + break; + + offset += VPD_KWD_NAME_LEN; + + if (name_buf[0] == '#') { + /* This is a large (two-byte size) keyword */ + if (read_eeprom(offset, &kwd_size, sizeof(kwd_size)) != sizeof(kwd_size)) + die("Failed to read large keyword size from EEPROM\n"); + kwd_size = le16toh(kwd_size); + offset += 2; + } else { + uint8_t small_size; + if (read_eeprom(offset, &small_size, sizeof(small_size)) != sizeof(small_size)) + die("Failed to read small keyword size from EEPROM\n"); + kwd_size = small_size; + offset += 1; + } + + if (!memcmp(name_buf, kwd_name, VPD_KWD_NAME_LEN) && index-- == 0) { + if (*size < kwd_size) + die("Keyword buffer is too small: %llu instead of %llu\n", + (unsigned long long)*size, (unsigned long long)kwd_size); + + if (read_eeprom(offset, buf, kwd_size) != kwd_size) + die("Failed to read keyword body from EEPROM\n"); + + *size = kwd_size; + return true; + } + + offset += kwd_size; + } + + return false; +} + +/* Builds MVPD partition for a single processor (64 KiB per chip) or returns an + * already built one */ +static const uint8_t *mvpd_get(void) +{ + enum { SECTION_SIZE = 64 * KiB }; + + static uint8_t mvpd_buf[SECTION_SIZE]; + + const char *mvpd_records[] = { + "CRP0", "CP00", "VINI", + "LRP0", "LRP1", "LRP2", "LRP3", "LRP4", "LRP5", + "LWP0", "LWP1", "LWP2", "LWP3", "LWP4", "LWP5", + "VRML", "VWML", "VER0", "MER0", "VMSC", + }; + + struct mvpd_toc_entry *toc = (void *)&mvpd_buf[0]; + uint16_t mvpd_offset = MVPD_TOC_SIZE; + + uint8_t pt_buf[256]; + struct pt_record *pt_record = (void *)pt_buf; + size_t pt_size = sizeof(struct pt_record); + + uint8_t i = 0; + + /* Skip the ECC data + "large resource" byte (0x84) in the VHDR */ + uint64_t offset = 12; + + /* Partition is already constructed (filled one can't be empty) */ + if (mvpd_buf[0] != '\0') + return mvpd_buf; + + if (!eeprom_extract_kwd(offset, 0, "VHDR", "PT", pt_buf, &pt_size)) + die("Failed to find PT keyword of VHDR record in EEPROM.\n"); + + if (memcmp(pt_record->record_name, "VTOC", VPD_RECORD_NAME_LEN)) + die("VHDR in EEPROM is invalid (got %.4s instead of VTOC.\n", + pt_record->record_name); + + /* Move to the TOC record, skip "large resource" byte (0x84) */ + offset = le16toh(pt_record->record_offset) + 1; + + /* Fill whole TOC with 0xFF */ + memset(toc, 0xFF, MVPD_TOC_SIZE); + + /* Up to three PT keywords in VTOC record */ + for (i = 0; i < 3; ++i) { + uint8_t j; + uint8_t entry_count; + + pt_size = sizeof(pt_buf); + if (!eeprom_extract_kwd(offset, i, "VTOC", "PT", pt_buf, &pt_size)) { + if (i == 0) + die("Failed to find any PT keyword of VTOC record in EEPROM\n"); + break; + } + + entry_count = pt_size / sizeof(struct pt_record); + + for (j = 0; j < entry_count; ++j) { + const char *record_name = pt_record[j].record_name; + /* Skip "large resource" byte (0x84) */ + const uint16_t record_offset = le16toh(pt_record[j].record_offset) + 1; + const uint16_t record_size = le16toh(pt_record[j].record_length); + + uint8_t k; + for (k = 0; k < ARRAY_SIZE(mvpd_records); ++k) { + if (!memcmp(record_name, mvpd_records[k], 4)) + break; + } + + if (k == ARRAY_SIZE(mvpd_records)) + continue; + + if (mvpd_offset + record_size > SECTION_SIZE) + die("MVPD section doesn't have space for %.4s record of size %d\n", + record_name, record_size); + + /* Store this record to MVPD */ + + memcpy(toc->name, record_name, VPD_RECORD_NAME_LEN); + toc->offset = htole16(mvpd_offset); + toc->reserved[0] = 0x5A; + toc->reserved[1] = 0x5A; + + if (read_eeprom(record_offset, mvpd_buf + mvpd_offset, record_size) != record_size) + die("Failed to read %.4s record from EEPROM\n", record_name); + + ++toc; + mvpd_offset += record_size; + } + } + + return mvpd_buf; +} + static struct mvpd_toc_entry *find_record(struct mvpd_toc_entry *toc, const char *name) { @@ -98,60 +369,38 @@ static struct ring_hdr *find_ring(uint8_t chiplet_id, uint8_t even_odd, static const uint8_t *mvpd_get_keyword(const char *record_name, const char *kwd_name, - size_t *kwd_size, void **mmaped_data) + size_t *kwd_size) { - const struct region_device *mvpd_device = mvpd_device_ro(); - - uint8_t mvpd_buf[MVPD_TOC_SIZE]; - struct mvpd_toc_entry *mvpd_toc = (struct mvpd_toc_entry *)mvpd_buf; + const uint8_t *mvpd = mvpd_get(); + struct mvpd_toc_entry *mvpd_toc = (void *)mvpd; struct mvpd_toc_entry *toc_entry = NULL; - uint16_t record_offset = 0; - uint8_t *record_data = NULL; - uint16_t record_size = 0; + const uint8_t *record_data = NULL; const uint8_t *kwd = NULL; - /* Copy all TOC at once */ - if (rdev_readat(mvpd_device, mvpd_buf, 0, - sizeof(mvpd_buf)) != sizeof(mvpd_buf)) - die("Failed to read MVPD TOC!\n"); - toc_entry = find_record(mvpd_toc, record_name); if (toc_entry == NULL) die("Failed to find %s MVPD record!\n", record_name); - record_offset = le16toh(toc_entry->offset); - - /* Read size of the record */ - if (rdev_readat(mvpd_device, &record_size, record_offset, - sizeof(record_size)) != sizeof(record_size)) - die("Failed to read size of %s!\n", record_name); - record_data = rdev_mmap(mvpd_device, record_offset, record_size); - if (!record_data) - die("Failed to map %s record!\n", record_name); + record_data = mvpd + le16toh(toc_entry->offset); kwd = vpd_find_kwd(record_data, record_name, kwd_name, kwd_size); if (kwd == NULL) die("Failed to find %s keyword in %s!\n", kwd_name, record_name); - *mmaped_data = record_data; return kwd; } bool mvpd_extract_keyword(const char *record_name, const char *kwd_name, uint8_t *buf, uint32_t *size) { - void *mmaped_data = NULL; - const uint8_t *kwd = NULL; size_t kwd_size = 0; bool copied_data = false; - mvpd_device_init(); - - kwd = mvpd_get_keyword(record_name, kwd_name, &kwd_size, &mmaped_data); + kwd = mvpd_get_keyword(record_name, kwd_name, &kwd_size); if (kwd == NULL) die("Failed to find %s keyword in %s!\n", kwd_name, record_name); @@ -163,9 +412,6 @@ bool mvpd_extract_keyword(const char *record_name, const char *kwd_name, *size = kwd_size; - if (rdev_munmap(mvpd_device_ro(), mmaped_data)) - die("Failed to unmap %s record!\n", record_name); - return copied_data; } @@ -204,36 +450,24 @@ bool mvpd_extract_ring(const char *record_name, const char *kwd_name, uint8_t chiplet_id, uint8_t even_odd, uint16_t ring_id, uint8_t *buf, uint32_t buf_size) { - void *mmaped_data = NULL; - const uint8_t *rings = NULL; size_t rings_size = 0; struct ring_hdr *ring = NULL; uint32_t ring_size = 0; - mvpd_device_init(); - - rings = mvpd_get_keyword(record_name, kwd_name, &rings_size, - &mmaped_data); + rings = mvpd_get_keyword(record_name, kwd_name, &rings_size); if (rings == NULL) die("Failed to find %s keyword in %s!\n", kwd_name, record_name); ring = find_ring(chiplet_id, even_odd, ring_id, rings, rings_size); - if (ring == NULL) { - if (rdev_munmap(mvpd_device_ro(), mmaped_data)) - die("Failed to unmap %s record!\n", record_name); - + if (ring == NULL) return false; - } ring_size = be32toh(ring->size); if (buf_size >= ring_size) memcpy(buf, ring, ring_size); - if (rdev_munmap(mvpd_device_ro(), mmaped_data)) - die("Failed to unmap %s record!\n", record_name); - return (buf_size >= ring_size); } diff --git a/src/soc/ibm/power9/vpd.c b/src/soc/ibm/power9/vpd.c index 47a54e55007..7fa412d779b 100644 --- a/src/soc/ibm/power9/vpd.c +++ b/src/soc/ibm/power9/vpd.c @@ -17,9 +17,6 @@ /* Divisor used for section size in MEMD header */ #define MEMD_SECTION_ROUNDING_DIVISOR 1000 -#define VPD_RECORD_SIZE_LEN 2 -#define VPD_KWD_NAME_LEN 2 - /* Supported mapping layout version */ #define VPD_MAPPING_VERSION 1 /* Size of entries in MR and MT mappings */