meta-facebook/meta-yamp/recipes-utils/wedge-eeprom/files/utils/yamp_eeprom.c (378 lines of code) (raw):
/*
* Copyright 2014-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 "yamp_eeprom.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <openbmc/log.h>
// YAMP definition
#define YAMP_EEPROM_PATH_BASE "/sys/bus/i2c/drivers/at24/"
#define YAMP_EEPROM_CHS_OBJ "CHASSIS"
#define YAMP_EEPROM_SCD_OBJ "SCD"
#define YAMP_EEPROM_SUP_OBJ "SUP"
#define YAMP_EEPROM_LC_OBJ "LC"
#define YAMP_EEPROM_CHASSIS "13-0052"
#define YAMP_EEPROM_SWITCH_CARD "4-0050"
#define YAMP_EEPROM_SIZE_MAX 255
#define YAMP_EEPROM_HEADER_B1 0x0
#define YAMP_EEPROM_HEADER_B2 0x0
#define YAMP_EEPROM_HEADER_B3 0x0
#define YAMP_EEPROM_HEADER_B4 0x3
#define YAMP_EEPROM_TYPE_SIZE 4
#define YAMP_EEPROM_PCA_SIZE 12
#define YAMP_EEPROM_SERIAL_SIZE 11
#define YAMP_EEPROM_KVN_SIZE 3
#define YAMP_EEPROM_FIELD_END 0x00
#define YAMP_EEPROM_FIELD_RESERVED1 0x01
#define YAMP_EEPROM_FIELD_MFGTIME 0x02
#define YAMP_EEPROM_FIELD_SKU 0x03
#define YAMP_EEPROM_FIELD_ASY 0x04
#define YAMP_EEPROM_FIELD_MACBASE 0x05
#define YAMP_EEPROM_FIELD_RESERVED2 0x06
#define YAMP_EEPROM_FIELD_RESERVED3 0x07
#define YAMP_EEPROM_FIELD_RESERVED4 0x08
#define YAMP_EEPROM_FIELD_FLDVAR 0x09
#define YAMP_EEPROM_FIELD_HWAPI 0x0A
#define YAMP_EEPROM_FIELD_HWREV 0x0B
#define YAMP_EEPROM_FIELD_SID 0x0C
#define YAMP_EEPROM_FIELD_PCA 0x0D
#define YAMP_EEPROM_FIELD_SERIAL 0x0E
#define YAMP_EEPROM_FIELD_RESERVED5 0x0F
#define YAMP_EEPROM_FIELD_RESERVED6 0x10
#define YAMP_EEPROM_FIELD_RESERVED7 0x11
#define YAMP_EEPROM_FIELD_RESERVED8 0x12
#define YAMP_EEPROM_FIELD_RESERVED9 0x13
#define YAMP_EEPROM_FIELD_RESERVED10 0x14
#define YAMP_EEPROM_FIELD_RESERVED11 0x15
#define YAMP_EEPROM_FIELD_RESERVED12 0x16
#define YAMP_EEPROM_FIELD_MFGTIME2 0x17
#define YAMP_EEPROM_SIZE 256
#define YAMP_LC_AT24_BUS_BASE 14
#define YAMP_LC_AT24_SLAVE_ADDR 0x50
#define YAMP_SUP_EEPROM_OFFSET 0x2000
#define YAMP_TEMP_PATH_BASE "/tmp"
#define YAMP_TEMP_FILE_NAME "sup_eeprom"
#define YAMP_RAW_FILE_NAME "sup_bios"
#define YAMP_CMD_BUF_SIZE 256
#define YAMP_BIOS_CMD "/usr/local/bin/bios_util.sh"
#define YAMP_FILE_NAME_SIZE 64
int yamp_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 yamp_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 yamp_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 = yamp_htoi(src[3*i]);
lower_val = yamp_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 yamp_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 yamp_str_toupper(char *str)
{
if (str != NULL)
for (int i = 0; i < strlen(str); i++)
str[i] = toupper(str[i]);
return;
}
void yamp_parse_string(int *read_pointer, char *dest,
char *buf, int len)
{
memcpy(dest, &buf[*read_pointer], len);
dest[len] = '\0';
*read_pointer += len;
}
int yamp_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 = yamp_htoi(buf[*read_pointer + marker]);
if (parsed_digit < 0)
rc = -1;
else
*dest = *dest + parsed_digit;
marker++;
}
*read_pointer += len;
return rc;
}
int yamp_get_lc_bus_name(const char *lc_name, char *bus_name)
{
int lc_number = -1;
// Boundary check
if (!lc_name || !bus_name)
return -1;
// If lc_name is LCx format where x is a number between 1 and 8
// translate this into lc_number (0-base)
if (strlen(lc_name) == 3) {
if ((lc_name[0] == 'L')&&(lc_name[1]=='C')) {
if ((lc_name[2] >= '1')&&(lc_name[2] <= '8')) {
lc_number = lc_name[2] - '1'; // it is 0-base
}
}
}
if (lc_number == -1)
return -1;
snprintf(bus_name, 12, "%2d-00%02x", lc_number + YAMP_LC_AT24_BUS_BASE,
YAMP_LC_AT24_SLAVE_ADDR);
return 0;
}
void yamp_get_eeprom_filename(char *fn)
{
sprintf(fn, "%s/%s", YAMP_TEMP_PATH_BASE,
YAMP_TEMP_FILE_NAME);
return;
}
void yamp_get_raw_filename(char *fn)
{
sprintf(fn, "%s/%s", YAMP_TEMP_PATH_BASE,
YAMP_RAW_FILE_NAME);
return;
}
int yamp_prepare_sup_eeprom_cache()
{
int rc = 0;
char sup_eeprom_filename[YAMP_FILE_NAME_SIZE];
char sup_raw_filename[YAMP_FILE_NAME_SIZE];
char cmd_buf[YAMP_CMD_BUF_SIZE];
yamp_get_eeprom_filename(sup_eeprom_filename);
yamp_get_raw_filename(sup_raw_filename);
// First, read raw BIOS contents
snprintf(cmd_buf, YAMP_CMD_BUF_SIZE, "%s read %s\n",
YAMP_BIOS_CMD, sup_raw_filename);
rc = system(cmd_buf);
if (rc != 0) {
OBMC_ERROR(ENOSPC, "Failed to read raw BIOS binary. rc = %d\n", rc);
return -ENOSPC;
}
// Then, parse the EEPROM block only
snprintf(cmd_buf, YAMP_CMD_BUF_SIZE,
"/bin/dd if=%s of=%s bs=1 skip=%d count=%d\n",
sup_raw_filename, sup_eeprom_filename, YAMP_SUP_EEPROM_OFFSET,
YAMP_EEPROM_SIZE);
rc = system(cmd_buf);
if (rc != 0) {
OBMC_ERROR(ENOSPC,
"Failed to parse BIOS binary into an EEPROM block. rc = %d\n",
rc);
rc = -ENOSPC;
}
// Remove interim temp file anyway
remove(sup_raw_filename);
return rc;
}
/*
* YAMP EEPROM has different format and field names from FB standard
* This function will read the eeprom, and try to map the YAMP fields
* to the FB standard fields
*/
int yamp_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 parse_sup_eeprom = 0;
char local_target[256];
char field_value[YAMP_EEPROM_SIZE]; // Will never overflow
char fn[64];
char buf[YAMP_EEPROM_SIZE];
char bus_name[32];
char sup_eeprom_filename[YAMP_FILE_NAME_SIZE];
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);
yamp_str_toupper((char *)local_target);
if (!strcmp(local_target, YAMP_EEPROM_CHS_OBJ)) {
sprintf(fn, "%s%s/eeprom", YAMP_EEPROM_PATH_BASE,
YAMP_EEPROM_CHASSIS);
} else if (!strcmp(local_target, YAMP_EEPROM_SCD_OBJ)) {
sprintf(fn, "%s%s/eeprom", YAMP_EEPROM_PATH_BASE,
YAMP_EEPROM_SWITCH_CARD);
} else if (!strcmp(local_target, YAMP_EEPROM_SUP_OBJ)) {
rc = yamp_prepare_sup_eeprom_cache();
if (rc != 0) {
OBMC_ERROR(ENOSPC, "Unable to dump the SUP EEPROM contents to ramdisk."
" Likely due to the lack of disk space. rc = %d\n", rc);
return -ENOSPC;
}
yamp_get_eeprom_filename(sup_eeprom_filename);
sprintf(fn, "%s", sup_eeprom_filename);
// Note that SUP's EEPROM format is a bit different from others,
// in that it doesn't start with eeprom header.
// Instead, it starts from prefdl field.
parse_sup_eeprom = 1;
} else if (yamp_get_lc_bus_name((const char *)local_target, bus_name) == 0) {
sprintf(fn, "%s%s/eeprom", YAMP_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
* YAMP 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));
/*
* The following check is only for NON-SUP eeproms, which
* has all these fields.
*/
if (parse_sup_eeprom != 1) {
/* First, make sure the eeprom header is correct */
if ((buf[read_pointer] != YAMP_EEPROM_HEADER_B1) ||
(buf[read_pointer + 1] != YAMP_EEPROM_HEADER_B2) ||
(buf[read_pointer + 2] != YAMP_EEPROM_HEADER_B3) ||
(buf[read_pointer + 3] != YAMP_EEPROM_HEADER_B4)) {
OBMC_ERROR(EINVAL, "Wrong EEPROM header! expected %02x %02x %02x %02x, \
got %02x %02x %02x %02x",
YAMP_EEPROM_HEADER_B1, YAMP_EEPROM_HEADER_B2,
YAMP_EEPROM_HEADER_B3, YAMP_EEPROM_HEADER_B4,
buf[read_pointer], buf[read_pointer+1],
buf[read_pointer + 2], buf[read_pointer+3]);
rc = -EINVAL;
}
/* As we read the first four bytes, advance the read pointer */
read_pointer += YAMP_EEPROM_TYPE_SIZE;
/* Bypass the eeprom size field */
read_pointer += YAMP_EEPROM_TYPE_SIZE;
}
/* Bypass prefdl symbol and PCA */
read_pointer += YAMP_EEPROM_TYPE_SIZE + YAMP_EEPROM_PCA_SIZE;
/* Parse serial number. If there is additional serial TLV,
* this value will be overwritten */
yamp_parse_string(&read_pointer, (char *)&eeprom->fbw_product_serial,
buf, YAMP_EEPROM_SERIAL_SIZE);
read_pointer += YAMP_EEPROM_KVN_SIZE;
/* Product type is hardcoded to YAMP */
sprintf(eeprom->fbw_product_name, "YAMP");
rc = 0;
/* Now go though EEPROM contents to fill out the fields */
while ((rc == 0) && (read_pointer < YAMP_EEPROM_SIZE)) {
rc = yamp_parse_hexadecimal(&read_pointer, &field_type, buf, 2);
if (rc < 0)
goto out;
rc = yamp_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 == YAMP_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 YAMP_EEPROM_FIELD_MFGTIME:
if (!mfgtime2)
memcpy(eeprom->fbw_system_manufacturing_date, field_value, 10);
break;
case YAMP_EEPROM_FIELD_MFGTIME2:
mfgtime2 = true; // Do not allow MFGTIME to override MFGTIME2
memcpy(eeprom->fbw_system_manufacturing_date, field_value, 10);
break;
case YAMP_EEPROM_FIELD_ASY:
memcpy(eeprom->fbw_assembly_number, field_value,
FBW_EEPROM_F_ASSEMBLY_NUMBER);
eeprom->fbw_assembly_number[FBW_EEPROM_F_ASSEMBLY_NUMBER -1] = 0;
break;
case YAMP_EEPROM_FIELD_SKU:
memcpy(eeprom->fbw_product_number, field_value,
FBW_EEPROM_F_PRODUCT_NUMBER);
eeprom->fbw_assembly_number[FBW_EEPROM_F_PRODUCT_NUMBER -1] = 0;
break;
case YAMP_EEPROM_FIELD_MACBASE:
for (int i = 0; i < 6; i++)
eeprom->fbw_mac_base[i] = yamp_htoi(field_value[2*i]) * 16 +
yamp_htoi(field_value[2*i + 1]);
/* Also hardcade mac size as the same value as Minipack */
eeprom->fbw_mac_size = 139;
break;
case YAMP_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 += yamp_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 += yamp_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 YAMP_EEPROM_FIELD_SERIAL:
memcpy(&eeprom->fbw_product_serial, field_value,
YAMP_EEPROM_SERIAL_SIZE);
eeprom->fbw_product_serial[YAMP_EEPROM_SERIAL_SIZE] = '\0';
break;
case YAMP_EEPROM_FIELD_RESERVED1:
case YAMP_EEPROM_FIELD_RESERVED2:
case YAMP_EEPROM_FIELD_RESERVED3:
case YAMP_EEPROM_FIELD_RESERVED4:
case YAMP_EEPROM_FIELD_RESERVED5:
case YAMP_EEPROM_FIELD_RESERVED6:
case YAMP_EEPROM_FIELD_RESERVED7:
case YAMP_EEPROM_FIELD_FLDVAR:
case YAMP_EEPROM_FIELD_HWAPI:
case YAMP_EEPROM_FIELD_SID:
case YAMP_EEPROM_FIELD_PCA:
/* These fields are not really useful. Do nothing (discard) */
break;
default:
/* unregonize field type. Bail out and print warning log*/
OBMC_WARN("unrecognize fied type of 0x%02x\n", field_type);
break;
}
}
out:
if (fin) {
fclose(fin);
}
if (parse_sup_eeprom == 1) {
// Remove temporary file created for SUP EEPROM read
remove(sup_eeprom_filename);
}
// rc > 0 means success here.
// (it will usually have the number of bytes read)
return (rc >= 0) ? 0 : -rc;
}