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;
}