meta-facebook/meta-elbert/recipes-utils/wedge-eeprom/files/lib/elbert_eeprom.c (351 lines of code) (raw):

/* * Copyright 2020-present Facebook. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "elbert_eeprom.h" #include <assert.h> #include <errno.h> #include <stdbool.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include <unistd.h> #include <openbmc/log.h> // ELBERT definition #define ELBERT_EEPROM_PATH_BASE "/sys/bus/i2c/drivers/at24/" #define ELBERT_SMB_P1_BOARD_PATH "/tmp/.smb_p1_board" #define ELBERT_EEPROM_CHS_OBJ "CHASSIS" #define ELBERT_EEPROM_SMB_OBJ "SMB" #define ELBERT_EEPROM_SMB_EXTRA_OBJ "SMB_EXTRA" #define ELBERT_EEPROM_SCM_OBJ "SCM" #define ELBERT_EEPROM_BMC_OBJ "BMC" #define ELBERT_EEPROM_PIM_OBJ "PIM" #define ELBERT_EEPROM_SCM "12-0050" #define ELBERT_EEPROM_BMC "0-0050" #define ELBERT_EEPROM_SMB "4-0050" #define ELBERT_EEPROM_SMB_EXTRA "4-0051" #define ELBERT_EEPROM_CHASSIS "7-0050" #define ELBERT_PIM_AT24_SLAVE_ADDR 0x50 #define ELBERT_EEPROM_SIZE 256 #define ELBERT_EEPROM_FORMAT_V2 "0002" #define ELBERT_EEPROM_HEADER_B1 0x0 #define ELBERT_EEPROM_HEADER_B2 0x0 #define ELBERT_EEPROM_HEADER_B3 0x0 #define ELBERT_EEPROM_HEADER_B4 0x3 #define ELBERT_EEPROM_TYPE_SIZE 4 #define ELBERT_EEPROM_PCA_SIZE 12 #define ELBERT_EEPROM_SERIAL_SIZE 11 #define ELBERT_EEPROM_KVN_SIZE 3 #define ELBERT_EEPROM_FIELD_END 0x00 #define ELBERT_EEPROM_FIELD_MFGTIME 0x02 #define ELBERT_EEPROM_FIELD_SKU 0x03 #define ELBERT_EEPROM_FIELD_ASY 0x04 #define ELBERT_EEPROM_FIELD_MACBASE 0x05 #define ELBERT_EEPROM_FIELD_FLDVAR 0x09 #define ELBERT_EEPROM_FIELD_HWREV 0x0B #define ELBERT_EEPROM_FIELD_SERIAL 0x0E #define ELBERT_EEPROM_FIELD_MFGTIME2 0x17 #define ELBERT_MAX_CHAR_LENGTH 10 #define ELBERT_PIM16CD2 "7388-16CD2" #define ELBERT_PIM16CD "7388-16CD" #define ELBERT_PIM8DDM "7388-8D" // Map between PIM and SMBus channel static int pim_bus_p1[8] = {16, 17, 18, 23, 20, 21, 22, 19}; static int pim_bus[8] = {16, 17, 18, 19, 20, 21, 22, 23}; int elbert_htoi(char a) { if ((a >= 'a') && (a <= 'f')) return a - 'a' + 10; else if ((a >= 'A') && (a <= 'F')) return a - 'A' + 10; else if ((a >= '0') && (a <= '9')) return a - '0'; // Error. return -1 return -1; } void elbert_parse_str(char *dest, char *src, int location, int len) { int i = 0; for (i = 0; i < len; i++) dest[i] = src[i + location]; dest[len] = '\0'; } void elbert_parse_mac(uint8_t *dest, char *src, int location) { int i = 0; int upper_val = 0; int lower_val = 0; for (i = 0; i < 6; i++) { upper_val = elbert_htoi(src[3*i]); lower_val = elbert_htoi(src[3*i + 1]); /* If invalid character is found, return ffffffffff */ if ((upper_val < 0) || (lower_val < 0)) { for (i = 0; i < 6; i++) dest[i] = 0xff; return; } dest[i] = upper_val * 16 + lower_val; } return; } /* Assumes that offset is in the range of [-255 , 255] */ void elbert_calculate_mac(uint8_t *base, int offset, uint8_t *result) { int i = 0; int overflow = 0; int field_result = 0; for (i = 0; i < 6; i++) result[i] = base[i]; for (i = 0; i < 6; i++) { overflow = 0; field_result = result[5-i] + offset; if (field_result >= 256) { overflow = 1; field_result -= 256; } if (field_result < 0) { overflow = -1; field_result += 256; } offset = overflow; result[5-i] = field_result%256; } return; } void elbert_str_toupper(char *str) { if (str != NULL) for (int i = 0; i < strlen(str); i++) str[i] = toupper(str[i]); return; } void elbert_parse_string(int *read_pointer, char *dest, char *buf, int len) { memcpy(dest, &buf[*read_pointer], len); dest[len] = '\0'; *read_pointer += len; } int elbert_parse_hexadecimal(int *read_pointer, unsigned int *dest, char *buf, int len) { int rc = 0; int parsed_digit = 0; int marker = 0; *dest = 0; while ((rc == 0) && (marker < len)) { *dest = *dest * 16; parsed_digit = elbert_htoi(buf[*read_pointer + marker]); if (parsed_digit < 0) rc = -1; else *dest = *dest + parsed_digit; marker++; } *read_pointer += len; return rc; } int elbert_get_pim_bus_name(const char *pim_name, char *bus_name) { int pim_number = -1; // Boundary check if (!pim_name || !bus_name) return -1; // If pim_name is PIMX format where x is a number between 2 and 9 // translate this into pim_number (0-base) if (strlen(pim_name) == 4) { if ((pim_name[0] == 'P')&&(pim_name[1]=='I')&&(pim_name[2]=='M')) { if ((pim_name[3] >= '2')&&(pim_name[3] <= '9')) { pim_number = pim_name[3] - '2'; // it is 0-base } } } if (pim_number == -1) return -1; if (access(ELBERT_SMB_P1_BOARD_PATH, F_OK) != -1) { // P1 SMB detected, use pim_bus_p1 smbus channel mapping snprintf(bus_name, 12, "%2d-00%02x", pim_bus_p1[pim_number], ELBERT_PIM_AT24_SLAVE_ADDR); } else { snprintf(bus_name, 12, "%2d-00%02x", pim_bus[pim_number], ELBERT_PIM_AT24_SLAVE_ADDR); } return 0; } /* * ELBERT EEPROM has different format and field names from FB standard * This function will read the eeprom, and try to map the ELBERT fields * to the FB standard fields */ int elbert_eeprom_parse(const char *target, struct wedge_eeprom_st *eeprom) { int rc = 0; int read_pointer = 0; uint32_t len; FILE *fin; int field_type; int field_len; int ver_major = 0; int ver_minor = 0; int dot_location = 0; int sku_length = ELBERT_MAX_CHAR_LENGTH; char local_target[256]; char field_value[ELBERT_EEPROM_SIZE]; // Will never overflow char fn[64]; char buf[ELBERT_EEPROM_SIZE]; char bus_name[32]; bool mfgtime2 = false; // set to True if MFGTIME2 field is detected if (!eeprom) { return -EINVAL; } if (!target) return -EINVAL; snprintf(local_target, sizeof(local_target), "%s", target); elbert_str_toupper((char *)local_target); if (!strcmp(local_target, ELBERT_EEPROM_SCM_OBJ)) { sprintf(fn, "%s%s/eeprom", ELBERT_EEPROM_PATH_BASE, ELBERT_EEPROM_SCM); } else if (!strcmp(local_target, ELBERT_EEPROM_BMC_OBJ)) { sprintf(fn, "%s%s/eeprom", ELBERT_EEPROM_PATH_BASE, ELBERT_EEPROM_BMC); sku_length = 8; // BMC field is only 8 chars long } else if (!strcmp(local_target, ELBERT_EEPROM_CHS_OBJ)) { sprintf(fn, "%s%s/eeprom", ELBERT_EEPROM_PATH_BASE, ELBERT_EEPROM_CHASSIS); sku_length = 4; // Chassis field is only 4 chars long } else if (!strcmp(local_target, ELBERT_EEPROM_SMB_OBJ)) { sprintf(fn, "%s%s/eeprom", ELBERT_EEPROM_PATH_BASE, ELBERT_EEPROM_SMB); } else if (!strcmp(local_target, ELBERT_EEPROM_SMB_EXTRA_OBJ)) { sprintf(fn, "%s%s/eeprom", ELBERT_EEPROM_PATH_BASE, ELBERT_EEPROM_SMB_EXTRA); } else if (elbert_get_pim_bus_name((const char *)local_target, bus_name) == 0) { sprintf(fn, "%s%s/eeprom", ELBERT_EEPROM_PATH_BASE, bus_name); } else { return -EINVAL; } fin = fopen(fn, "r"); if (fin == NULL) { rc = errno; OBMC_ERROR(rc, "Failed to open %s", fn); goto out; } /* Read the file into buffer */ rc = fread(buf, 1, sizeof(buf), fin); if (rc <= 0) { OBMC_ERROR(ENOSPC, "Failed to complete the read. Error code %d", rc); rc = ENOSPC; goto out; } /* * There are many fields in FB standard eeprom, that doesn't exist in * ELBERT eeprom. Start with all fields filled up as NULL or 0x0, * then overwrite the values as we get the information from eeprom */ memset(eeprom, 0, sizeof(struct wedge_eeprom_st)); /* Check that the eeprom field format header is as expected */ if ((buf[read_pointer] != ELBERT_EEPROM_HEADER_B1) || (buf[read_pointer + 1] != ELBERT_EEPROM_HEADER_B2) || (buf[read_pointer + 2] != ELBERT_EEPROM_HEADER_B3) || (buf[read_pointer + 3] != ELBERT_EEPROM_HEADER_B4)) { OBMC_ERROR(EINVAL, "Wrong EEPROM header! Expected %02x %02x %02x %02x, \ got %02x %02x %02x %02x", ELBERT_EEPROM_HEADER_B1, ELBERT_EEPROM_HEADER_B2, ELBERT_EEPROM_HEADER_B3, ELBERT_EEPROM_HEADER_B4, buf[read_pointer], buf[read_pointer +1], buf[read_pointer + 2], buf[read_pointer + 3]); rc = -EINVAL; goto out; } /* As we read the first four bytes, advance the read pointer */ read_pointer += ELBERT_EEPROM_TYPE_SIZE; /* Bypass the eeprom size field */ read_pointer += ELBERT_EEPROM_TYPE_SIZE; /* Check eeprom format */ if ((buf[read_pointer] != ELBERT_EEPROM_FORMAT_V2[0]) || (buf[read_pointer + 1] != ELBERT_EEPROM_FORMAT_V2[1]) || (buf[read_pointer + 2] != ELBERT_EEPROM_FORMAT_V2[2]) || (buf[read_pointer + 3] != ELBERT_EEPROM_FORMAT_V2[3])) { OBMC_ERROR(EINVAL, "Unsupported eeprom format! Expected %02x %02x %02x %02x, \ got %02x %02x %02x %02x", ELBERT_EEPROM_FORMAT_V2[0], ELBERT_EEPROM_FORMAT_V2[1], ELBERT_EEPROM_FORMAT_V2[2], ELBERT_EEPROM_FORMAT_V2[3], buf[read_pointer], buf[read_pointer + 1], buf[read_pointer + 2], buf[read_pointer + 3]); rc = -EINVAL; goto out; } read_pointer += ELBERT_EEPROM_TYPE_SIZE; /* Parse PCA and serial. If there is additional serial TLV parsed,, * this value will be overwritten */ elbert_parse_string(&read_pointer, (char *)&eeprom->fbw_odm_pcba_number, buf, ELBERT_EEPROM_PCA_SIZE); elbert_parse_string(&read_pointer, (char *)&eeprom->fbw_product_serial, buf, ELBERT_EEPROM_SERIAL_SIZE); read_pointer += ELBERT_EEPROM_KVN_SIZE; /* Product type is hardcoded to ELBERT */ sprintf(eeprom->fbw_product_name, "ELBERT"); rc = 0; /* Now go though EEPROM contents to fill out the fields */ while ((rc == 0) && (read_pointer < ELBERT_EEPROM_SIZE)) { rc = elbert_parse_hexadecimal(&read_pointer, &field_type, buf, 2); if (rc < 0) goto out; rc = elbert_parse_hexadecimal(&read_pointer, &field_len, buf, 4); if (rc < 0) goto out; /* * If this is the end of EEPROM (specified as "END" TLV), * do not read any value, but just bail out */ if (field_type == ELBERT_EEPROM_FIELD_END) break; /* Otherwise, read the field */ memcpy(field_value, &buf[read_pointer], field_len); read_pointer += field_len; /* Now, map the value into similar field in FB EEPROM */ switch(field_type) { case ELBERT_EEPROM_FIELD_MFGTIME: if (!mfgtime2) memcpy(eeprom->fbw_system_manufacturing_date, field_value, 10); break; case ELBERT_EEPROM_FIELD_MFGTIME2: mfgtime2 = true; // Do not allow MFGTIME to override MFGTIME2 memcpy(eeprom->fbw_system_manufacturing_date, field_value, 10); break; case ELBERT_EEPROM_FIELD_ASY: memcpy(eeprom->fbw_assembly_number, field_value, FBW_EEPROM_F_ASSEMBLY_NUMBER); // We allocate FBW_EEPROM_F_ASSEMBLY_NUMBER + 3 eeprom->fbw_assembly_number[FBW_EEPROM_F_ASSEMBLY_NUMBER + 2] = '\0'; // Set production state based on ASY: // Production = 1, Prototype = 0 if (isalpha(eeprom->fbw_assembly_number[FBW_EEPROM_F_ASSEMBLY_NUMBER - 2]) || isalpha(eeprom->fbw_assembly_number[FBW_EEPROM_F_ASSEMBLY_NUMBER - 1])) eeprom->fbw_production_state = 1; else eeprom->fbw_production_state = 0; break; case ELBERT_EEPROM_FIELD_SKU: memcpy(eeprom->fbw_product_asset, field_value, sku_length); // Elbert SKU names can be from 8 to 10 char long // Use product_asset field instead of product_name as it allocates // more char in the eeprom struct. /* Remove garbage characters from the end of 7388-16CD2 */ if(!strncmp( eeprom->fbw_product_asset, ELBERT_PIM16CD2, strlen(ELBERT_PIM16CD2))) { eeprom->fbw_product_asset[strlen(ELBERT_PIM16CD2)] = '\0'; } /* Remove garbage characters from the end of 7388-16CD */ else if(!strncmp( eeprom->fbw_product_asset, ELBERT_PIM16CD, strlen(ELBERT_PIM16CD))) { eeprom->fbw_product_asset[strlen(ELBERT_PIM16CD)] = '\0'; } /* Remove garbage characters from the end of 7388-8D */ else if(!strncmp( eeprom->fbw_product_asset, ELBERT_PIM8DDM, strlen(ELBERT_PIM8DDM))) { eeprom->fbw_product_asset[strlen(ELBERT_PIM8DDM)] = '\0'; } break; case ELBERT_EEPROM_FIELD_MACBASE: for (int i = 0; i < 6; i++) eeprom->fbw_mac_base[i] = elbert_htoi(field_value[2*i]) * 16 + elbert_htoi(field_value[2*i + 1]); /* Also hardcade mac size as the same value as Minipack */ eeprom->fbw_mac_size = 139; break; case ELBERT_EEPROM_FIELD_HWREV: /* Scan for period character */ dot_location = field_len; for (int i = 0; i < field_len; i++) if (field_value[i] == '.') dot_location = i; /* Parse the number before dot, and fit it into uint8_t */ for (int i = 0; i < dot_location; i++) { ver_major *= 10; ver_major += elbert_htoi(field_value[i]); } /* Parse the number after dot, and fit it into uint8_t */ ver_major = ver_major % 256; for (int i = dot_location + 1; i < field_len; i++) { ver_minor *= 10; ver_minor += elbert_htoi(field_value[i]); } /* Fit the value into uint8_t */ ver_minor = ver_minor % 256; eeprom->fbw_product_version = ver_major; eeprom->fbw_product_subversion = ver_minor; break; case ELBERT_EEPROM_FIELD_SERIAL: memcpy(&eeprom->fbw_product_serial, field_value, ELBERT_EEPROM_SERIAL_SIZE); eeprom->fbw_product_serial[ELBERT_EEPROM_SERIAL_SIZE] = '\0'; break; default: /* Unknown field */ break; } } out: if (fin) { fclose(fin); } // rc > 0 means success here. // (it will usually have the number of bytes read) return (rc >= 0) ? 0 : -rc; }