// SPDX-License-Identifier: GPL-2.0+ /* * See file CREDITS for list of people who contributed to this * project. * * Copyright (C) 2013 Curt Brune * Copyright (C) 2014 Srideep * Copyright (C) 2013 Miles Tseng * Copyright (C) 2014,2016 david_yang */ #include #include #include #include #include #include #include #include #include #include #include #include "tlv_eeprom.h" DECLARE_GLOBAL_DATA_PTR; #define MAX_TLV_DEVICES 2 /* File scope function prototypes */ static bool is_checksum_valid(u8 *eeprom); static int read_eeprom(u8 *eeprom); static void show_eeprom(u8 *eeprom); static void decode_tlv(struct tlvinfo_tlv *tlv); static void update_crc(u8 *eeprom); static int prog_eeprom(u8 *eeprom); static bool tlvinfo_find_tlv(u8 *eeprom, u8 tcode, int *eeprom_index); static bool tlvinfo_delete_tlv(u8 *eeprom, u8 code); static bool tlvinfo_add_tlv(u8 *eeprom, int tcode, char *strval); static int set_mac(char *buf, const char *string); static int set_date(char *buf, const char *string); static int set_bytes(char *buf, const char *string, int *converted_accum); static void show_tlv_devices(void); /* Set to 1 if we've read EEPROM into memory */ static int has_been_read; /* The EERPOM contents after being read into memory */ static u8 eeprom[TLV_INFO_MAX_LEN]; static struct udevice *tlv_devices[MAX_TLV_DEVICES]; static unsigned int current_dev; #define to_header(p) ((struct tlvinfo_header *)p) #define to_entry(p) ((struct tlvinfo_tlv *)p) #define HDR_SIZE sizeof(struct tlvinfo_header) #define ENT_SIZE sizeof(struct tlvinfo_tlv) static inline bool is_digit(char c) { return (c >= '0' && c <= '9'); } /** * is_valid_tlv * * Perform basic sanity checks on a TLV field. The TLV is pointed to * by the parameter provided. * 1. The type code is not reserved (0x00 or 0xFF) */ static inline bool is_valid_tlv(struct tlvinfo_tlv *tlv) { return((tlv->type != 0x00) && (tlv->type != 0xFF)); } /** * is_hex * * Tests if character is an ASCII hex digit */ static inline u8 is_hex(char p) { return (((p >= '0') && (p <= '9')) || ((p >= 'A') && (p <= 'F')) || ((p >= 'a') && (p <= 'f'))); } /** * is_checksum_valid * * Validate the checksum in the provided TlvInfo EEPROM data. First, * verify that the TlvInfo header is valid, then make sure the last * TLV is a CRC-32 TLV. Then calculate the CRC over the EEPROM data * and compare it to the value stored in the EEPROM CRC-32 TLV. */ static bool is_checksum_valid(u8 *eeprom) { struct tlvinfo_header *eeprom_hdr = to_header(eeprom); struct tlvinfo_tlv *eeprom_crc; unsigned int calc_crc; unsigned int stored_crc; // Is the eeprom header valid? if (!is_valid_tlvinfo_header(eeprom_hdr)) return false; // Is the last TLV a CRC? eeprom_crc = to_entry(&eeprom[HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen) - (ENT_SIZE + 4)]); if (eeprom_crc->type != TLV_CODE_CRC_32 || eeprom_crc->length != 4) return false; // Calculate the checksum calc_crc = crc32(0, (void *)eeprom, HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen) - 4); stored_crc = (eeprom_crc->value[0] << 24) | (eeprom_crc->value[1] << 16) | (eeprom_crc->value[2] << 8) | eeprom_crc->value[3]; return calc_crc == stored_crc; } /** * read_eeprom * * Read the EEPROM into memory, if it hasn't already been read. */ static int read_eeprom(u8 *eeprom) { int ret; struct tlvinfo_header *eeprom_hdr = to_header(eeprom); struct tlvinfo_tlv *eeprom_tlv = to_entry(&eeprom[HDR_SIZE]); if (has_been_read) return 0; /* Read the header */ ret = read_tlv_eeprom((void *)eeprom_hdr, 0, HDR_SIZE, current_dev); /* If the header was successfully read, read the TLVs */ if (ret == 0 && is_valid_tlvinfo_header(eeprom_hdr)) ret = read_tlv_eeprom((void *)eeprom_tlv, HDR_SIZE, be16_to_cpu(eeprom_hdr->totallen), current_dev); // If the contents are invalid, start over with default contents if (!is_valid_tlvinfo_header(eeprom_hdr) || !is_checksum_valid(eeprom)) { strcpy(eeprom_hdr->signature, TLV_INFO_ID_STRING); eeprom_hdr->version = TLV_INFO_VERSION; eeprom_hdr->totallen = cpu_to_be16(0); update_crc(eeprom); } has_been_read = 1; #ifdef DEBUG show_eeprom(eeprom); #endif return ret; } /** * show_eeprom * * Display the contents of the EEPROM */ static void show_eeprom(u8 *eeprom) { int tlv_end; int curr_tlv; struct tlvinfo_header *eeprom_hdr = to_header(eeprom); struct tlvinfo_tlv *eeprom_tlv; if (!is_valid_tlvinfo_header(eeprom_hdr)) { printf("EEPROM does not contain data in a valid TlvInfo format.\n"); return; } printf("TLV: %u\n", current_dev); printf("TlvInfo Header:\n"); printf(" Id String: %s\n", eeprom_hdr->signature); printf(" Version: %d\n", eeprom_hdr->version); printf(" Total Length: %d\n", be16_to_cpu(eeprom_hdr->totallen)); printf("TLV Name Code Len Value\n"); printf("-------------------- ---- --- -----\n"); curr_tlv = HDR_SIZE; tlv_end = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); while (curr_tlv < tlv_end) { eeprom_tlv = to_entry(&eeprom[curr_tlv]); if (!is_valid_tlv(eeprom_tlv)) { printf("Invalid TLV field starting at EEPROM offset %d\n", curr_tlv); return; } decode_tlv(eeprom_tlv); curr_tlv += ENT_SIZE + eeprom_tlv->length; } printf("Checksum is %s.\n", is_checksum_valid(eeprom) ? "valid" : "invalid"); #ifdef DEBUG printf("EEPROM dump: (0x%x bytes)", TLV_INFO_MAX_LEN); for (i = 0; i < TLV_INFO_MAX_LEN; i++) { if ((i % 16) == 0) printf("\n%02X: ", i); printf("%02X ", eeprom[i]); } printf("\n"); #endif } /** * Struct for displaying the TLV codes and names. */ struct tlv_code_desc { u8 m_code; char *m_name; }; /** * List of TLV codes and names. */ static struct tlv_code_desc tlv_code_list[] = { { TLV_CODE_PRODUCT_NAME, "Product Name"}, { TLV_CODE_PART_NUMBER, "Part Number"}, { TLV_CODE_SERIAL_NUMBER, "Serial Number"}, { TLV_CODE_MAC_BASE, "Base MAC Address"}, { TLV_CODE_MANUF_DATE, "Manufacture Date"}, { TLV_CODE_DEVICE_VERSION, "Device Version"}, { TLV_CODE_LABEL_REVISION, "Label Revision"}, { TLV_CODE_PLATFORM_NAME, "Platform Name"}, { TLV_CODE_ONIE_VERSION, "ONIE Version"}, { TLV_CODE_MAC_SIZE, "MAC Addresses"}, { TLV_CODE_MANUF_NAME, "Manufacturer"}, { TLV_CODE_MANUF_COUNTRY, "Country Code"}, { TLV_CODE_VENDOR_NAME, "Vendor Name"}, { TLV_CODE_DIAG_VERSION, "Diag Version"}, { TLV_CODE_SERVICE_TAG, "Service Tag"}, { TLV_CODE_VENDOR_EXT, "Vendor Extension"}, { TLV_CODE_CRC_32, "CRC-32"}, }; /** * Look up a TLV name by its type. */ static inline const char *tlv_type2name(u8 type) { char *name = "Unknown"; int i; for (i = 0; i < ARRAY_SIZE(tlv_code_list); i++) { if (tlv_code_list[i].m_code == type) { name = tlv_code_list[i].m_name; break; } } return name; } /* * decode_tlv * * Print a string representing the contents of the TLV field. The format of * the string is: * 1. The name of the field left justified in 20 characters * 2. The type code in hex right justified in 5 characters * 3. The length in decimal right justified in 4 characters * 4. The value, left justified in however many characters it takes * The validity of EEPROM contents and the TLV field have been verified * prior to calling this function. */ #define DECODE_NAME_MAX 20 /* * The max decode value is currently for the 'raw' type or the 'vendor * extension' type, both of which have the same decode format. The * max decode string size is computed as follows: * * strlen(" 0xFF") * TLV_VALUE_MAX_LEN + 1 * */ #define DECODE_VALUE_MAX ((5 * TLV_VALUE_MAX_LEN) + 1) static void decode_tlv(struct tlvinfo_tlv *tlv) { char name[DECODE_NAME_MAX]; char value[DECODE_VALUE_MAX]; int i; strncpy(name, tlv_type2name(tlv->type), DECODE_NAME_MAX); switch (tlv->type) { case TLV_CODE_PRODUCT_NAME: case TLV_CODE_PART_NUMBER: case TLV_CODE_SERIAL_NUMBER: case TLV_CODE_MANUF_DATE: case TLV_CODE_LABEL_REVISION: case TLV_CODE_PLATFORM_NAME: case TLV_CODE_ONIE_VERSION: case TLV_CODE_MANUF_NAME: case TLV_CODE_MANUF_COUNTRY: case TLV_CODE_VENDOR_NAME: case TLV_CODE_DIAG_VERSION: case TLV_CODE_SERVICE_TAG: memcpy(value, tlv->value, tlv->length); value[tlv->length] = 0; break; case TLV_CODE_MAC_BASE: sprintf(value, "%02X:%02X:%02X:%02X:%02X:%02X", tlv->value[0], tlv->value[1], tlv->value[2], tlv->value[3], tlv->value[4], tlv->value[5]); break; case TLV_CODE_DEVICE_VERSION: sprintf(value, "%u", tlv->value[0]); break; case TLV_CODE_MAC_SIZE: sprintf(value, "%u", (tlv->value[0] << 8) | tlv->value[1]); break; case TLV_CODE_VENDOR_EXT: value[0] = 0; for (i = 0; (i < (DECODE_VALUE_MAX / 5)) && (i < tlv->length); i++) { sprintf(value, "%s 0x%02X", value, tlv->value[i]); } break; case TLV_CODE_CRC_32: sprintf(value, "0x%02X%02X%02X%02X", tlv->value[0], tlv->value[1], tlv->value[2], tlv->value[3]); break; default: value[0] = 0; for (i = 0; (i < (DECODE_VALUE_MAX / 5)) && (i < tlv->length); i++) { sprintf(value, "%s 0x%02X", value, tlv->value[i]); } break; } name[DECODE_NAME_MAX - 1] = 0; printf("%-20s 0x%02X %3d %s\n", name, tlv->type, tlv->length, value); } /** * update_crc * * This function updates the CRC-32 TLV. If there is no CRC-32 TLV, then * one is added. This function should be called after each update to the * EEPROM structure, to make sure the CRC is always correct. */ static void update_crc(u8 *eeprom) { struct tlvinfo_header *eeprom_hdr = to_header(eeprom); struct tlvinfo_tlv *eeprom_crc; unsigned int calc_crc; int eeprom_index; // Discover the CRC TLV if (!tlvinfo_find_tlv(eeprom, TLV_CODE_CRC_32, &eeprom_index)) { unsigned int totallen = be16_to_cpu(eeprom_hdr->totallen); if ((totallen + ENT_SIZE + 4) > TLV_TOTAL_LEN_MAX) return; eeprom_index = HDR_SIZE + totallen; eeprom_hdr->totallen = cpu_to_be16(totallen + ENT_SIZE + 4); } eeprom_crc = to_entry(&eeprom[eeprom_index]); eeprom_crc->type = TLV_CODE_CRC_32; eeprom_crc->length = 4; // Calculate the checksum calc_crc = crc32(0, (void *)eeprom, HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen) - 4); eeprom_crc->value[0] = (calc_crc >> 24) & 0xFF; eeprom_crc->value[1] = (calc_crc >> 16) & 0xFF; eeprom_crc->value[2] = (calc_crc >> 8) & 0xFF; eeprom_crc->value[3] = (calc_crc >> 0) & 0xFF; } /** * prog_eeprom * * Write the EEPROM data from CPU memory to the hardware. */ static int prog_eeprom(u8 *eeprom) { int ret = 0; struct tlvinfo_header *eeprom_hdr = to_header(eeprom); int eeprom_len; update_crc(eeprom); eeprom_len = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); ret = write_tlv_eeprom(eeprom, eeprom_len); if (ret) { printf("Programming failed.\n"); return -1; } printf("Programming passed.\n"); return 0; } /** * show_tlv_code_list - Display the list of TLV codes and names */ void show_tlv_code_list(void) { int i; printf("TLV Code TLV Name\n"); printf("======== =================\n"); for (i = 0; i < ARRAY_SIZE(tlv_code_list); i++) { printf("0x%02X %s\n", tlv_code_list[i].m_code, tlv_code_list[i].m_name); } } /** * do_tlv_eeprom * * This function implements the tlv_eeprom command. */ int do_tlv_eeprom(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { char cmd; struct tlvinfo_header *eeprom_hdr = to_header(eeprom); // If no arguments, read the EERPOM and display its contents if (argc == 1) { read_eeprom(eeprom); show_eeprom(eeprom); return 0; } // We only look at the first character to the command, so "read" and // "reset" will both be treated as "read". cmd = argv[1][0]; // Read the EEPROM contents if (cmd == 'r') { has_been_read = 0; if (!read_eeprom(eeprom)) printf("EEPROM data loaded from device to memory.\n"); return 0; } // Subsequent commands require that the EEPROM has already been read. if (!has_been_read) { printf("Please read the EEPROM data first, using the 'tlv_eeprom read' command.\n"); return 0; } // Handle the commands that don't take parameters if (argc == 2) { switch (cmd) { case 'w': /* write */ prog_eeprom(eeprom); break; case 'e': /* erase */ strcpy(eeprom_hdr->signature, TLV_INFO_ID_STRING); eeprom_hdr->version = TLV_INFO_VERSION; eeprom_hdr->totallen = cpu_to_be16(0); update_crc(eeprom); printf("EEPROM data in memory reset.\n"); break; case 'l': /* list */ show_tlv_code_list(); break; case 'd': /* dev */ show_tlv_devices(); break; default: cmd_usage(cmdtp); break; } return 0; } // The set command takes one or two args. if (argc > 4) { cmd_usage(cmdtp); return 0; } // Set command. If the TLV exists in the EEPROM, delete it. Then if // data was supplied for this TLV add the TLV with the new contents at // the end. if (cmd == 's') { int tcode; tcode = simple_strtoul(argv[2], NULL, 0); tlvinfo_delete_tlv(eeprom, tcode); if (argc == 4) tlvinfo_add_tlv(eeprom, tcode, argv[3]); } else if (cmd == 'd') { /* 'dev' command */ unsigned int devnum; devnum = simple_strtoul(argv[2], NULL, 0); if (devnum > MAX_TLV_DEVICES || !tlv_devices[devnum]) { printf("Invalid device number\n"); return 0; } current_dev = devnum; has_been_read = 0; } else { cmd_usage(cmdtp); } return 0; } /** * This macro defines the tlv_eeprom command line command. */ U_BOOT_CMD(tlv_eeprom, 4, 1, do_tlv_eeprom, "Display and program the system EEPROM data block.", "[read|write|set |erase|list]\n" "tlv_eeprom\n" " - With no arguments display the current contents.\n" "tlv_eeprom dev [dev]\n" " - List devices or set current EEPROM device.\n" "tlv_eeprom read\n" " - Load EEPROM data from device to memory.\n" "tlv_eeprom write\n" " - Write the EEPROM data to persistent storage.\n" "tlv_eeprom set \n" " - Set a field to a value.\n" " - If no string_value, field is deleted.\n" " - Use 'tlv_eeprom write' to make changes permanent.\n" "tlv_eeprom erase\n" " - Reset the in memory EEPROM data.\n" " - Use 'tlv_eeprom read' to refresh the in memory EEPROM data.\n" " - Use 'tlv_eeprom write' to make changes permanent.\n" "tlv_eeprom list\n" " - List the understood TLV codes and names.\n" ); /** * tlvinfo_find_tlv * * This function finds the TLV with the supplied code in the EERPOM. * An offset from the beginning of the EEPROM is returned in the * eeprom_index parameter if the TLV is found. */ static bool tlvinfo_find_tlv(u8 *eeprom, u8 tcode, int *eeprom_index) { struct tlvinfo_header *eeprom_hdr = to_header(eeprom); struct tlvinfo_tlv *eeprom_tlv; int eeprom_end; // Search through the TLVs, looking for the first one which matches the // supplied type code. *eeprom_index = HDR_SIZE; eeprom_end = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); while (*eeprom_index < eeprom_end) { eeprom_tlv = to_entry(&eeprom[*eeprom_index]); if (!is_valid_tlv(eeprom_tlv)) return false; if (eeprom_tlv->type == tcode) return true; *eeprom_index += ENT_SIZE + eeprom_tlv->length; } return(false); } /** * tlvinfo_delete_tlv * * This function deletes the TLV with the specified type code from the * EEPROM. */ static bool tlvinfo_delete_tlv(u8 *eeprom, u8 code) { int eeprom_index; int tlength; struct tlvinfo_header *eeprom_hdr = to_header(eeprom); struct tlvinfo_tlv *eeprom_tlv; // Find the TLV and then move all following TLVs "forward" if (tlvinfo_find_tlv(eeprom, code, &eeprom_index)) { eeprom_tlv = to_entry(&eeprom[eeprom_index]); tlength = ENT_SIZE + eeprom_tlv->length; memcpy(&eeprom[eeprom_index], &eeprom[eeprom_index + tlength], HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen) - eeprom_index - tlength); eeprom_hdr->totallen = cpu_to_be16(be16_to_cpu(eeprom_hdr->totallen) - tlength); update_crc(eeprom); return true; } return false; } /** * tlvinfo_add_tlv * * This function adds a TLV to the EEPROM, converting the value (a string) to * the format in which it will be stored in the EEPROM. */ #define MAX_TLV_VALUE_LEN 256 static bool tlvinfo_add_tlv(u8 *eeprom, int tcode, char *strval) { struct tlvinfo_header *eeprom_hdr = to_header(eeprom); struct tlvinfo_tlv *eeprom_tlv; int new_tlv_len = 0; u32 value; char data[MAX_TLV_VALUE_LEN]; int eeprom_index; // Encode each TLV type into the format to be stored in the EERPOM switch (tcode) { case TLV_CODE_PRODUCT_NAME: case TLV_CODE_PART_NUMBER: case TLV_CODE_SERIAL_NUMBER: case TLV_CODE_LABEL_REVISION: case TLV_CODE_PLATFORM_NAME: case TLV_CODE_ONIE_VERSION: case TLV_CODE_MANUF_NAME: case TLV_CODE_MANUF_COUNTRY: case TLV_CODE_VENDOR_NAME: case TLV_CODE_DIAG_VERSION: case TLV_CODE_SERVICE_TAG: strncpy(data, strval, MAX_TLV_VALUE_LEN); new_tlv_len = min_t(size_t, MAX_TLV_VALUE_LEN, strlen(strval)); break; case TLV_CODE_DEVICE_VERSION: value = simple_strtoul(strval, NULL, 0); if (value >= 256) { printf("ERROR: Device version must be 255 or less. Value supplied: %u", value); return false; } data[0] = value & 0xFF; new_tlv_len = 1; break; case TLV_CODE_MAC_SIZE: value = simple_strtoul(strval, NULL, 0); if (value >= 65536) { printf("ERROR: MAC Size must be 65535 or less. Value supplied: %u", value); return false; } data[0] = (value >> 8) & 0xFF; data[1] = value & 0xFF; new_tlv_len = 2; break; case TLV_CODE_MANUF_DATE: if (set_date(data, strval) != 0) return false; new_tlv_len = 19; break; case TLV_CODE_MAC_BASE: if (set_mac(data, strval) != 0) return false; new_tlv_len = 6; break; case TLV_CODE_CRC_32: printf("WARNING: The CRC TLV is set automatically and cannot be set manually.\n"); return false; case TLV_CODE_VENDOR_EXT: default: if (set_bytes(data, strval, &new_tlv_len) != 0) return false; break; } // Is there room for this TLV? if ((be16_to_cpu(eeprom_hdr->totallen) + ENT_SIZE + new_tlv_len) > TLV_TOTAL_LEN_MAX) { printf("ERROR: There is not enough room in the EERPOM to save data.\n"); return false; } // Add TLV at the end, overwriting CRC TLV if it exists if (tlvinfo_find_tlv(eeprom, TLV_CODE_CRC_32, &eeprom_index)) eeprom_hdr->totallen = cpu_to_be16(be16_to_cpu(eeprom_hdr->totallen) - ENT_SIZE - 4); else eeprom_index = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); eeprom_tlv = to_entry(&eeprom[eeprom_index]); eeprom_tlv->type = tcode; eeprom_tlv->length = new_tlv_len; memcpy(eeprom_tlv->value, data, new_tlv_len); // Update the total length and calculate (add) a new CRC-32 TLV eeprom_hdr->totallen = cpu_to_be16(be16_to_cpu(eeprom_hdr->totallen) + ENT_SIZE + new_tlv_len); update_crc(eeprom); return true; } /** * set_mac * * Converts a string MAC address into a binary buffer. * * This function takes a pointer to a MAC address string * (i.e."XX:XX:XX:XX:XX:XX", where "XX" is a two-digit hex number). * The string format is verified and then converted to binary and * stored in a buffer. */ static int set_mac(char *buf, const char *string) { char *p = (char *)string; int i; int err = 0; char *end; if (!p) { printf("ERROR: NULL mac addr string passed in.\n"); return -1; } if (strlen(p) != 17) { printf("ERROR: MAC address strlen() != 17 -- %zu\n", strlen(p)); printf("ERROR: Bad MAC address format: %s\n", string); return -1; } for (i = 0; i < 17; i++) { if ((i % 3) == 2) { if (p[i] != ':') { err++; printf("ERROR: mac: p[%i] != :, found: `%c'\n", i, p[i]); break; } continue; } else if (!is_hex(p[i])) { err++; printf("ERROR: mac: p[%i] != hex digit, found: `%c'\n", i, p[i]); break; } } if (err != 0) { printf("ERROR: Bad MAC address format: %s\n", string); return -1; } /* Convert string to binary */ for (i = 0, p = (char *)string; i < 6; i++) { buf[i] = p ? simple_strtoul(p, &end, 16) : 0; if (p) p = (*end) ? end + 1 : end; } if (!is_valid_ethaddr((u8 *)buf)) { printf("ERROR: MAC address must not be 00:00:00:00:00:00, a multicast address or FF:FF:FF:FF:FF:FF.\n"); printf("ERROR: Bad MAC address format: %s\n", string); return -1; } return 0; } /** * set_date * * Validates the format of the data string * * This function takes a pointer to a date string (i.e. MM/DD/YYYY hh:mm:ss) * and validates that the format is correct. If so the string is copied * to the supplied buffer. */ static int set_date(char *buf, const char *string) { int i; if (!string) { printf("ERROR: NULL date string passed in.\n"); return -1; } if (strlen(string) != 19) { printf("ERROR: Date strlen() != 19 -- %zu\n", strlen(string)); printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", string); return -1; } for (i = 0; string[i] != 0; i++) { switch (i) { case 2: case 5: if (string[i] != '/') { printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", string); return -1; } break; case 10: if (string[i] != ' ') { printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", string); return -1; } break; case 13: case 16: if (string[i] != ':') { printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", string); return -1; } break; default: if (!is_digit(string[i])) { printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", string); return -1; } break; } } strcpy(buf, string); return 0; } /** * set_bytes * * Converts a space-separated string of decimal numbers into a * buffer of bytes. * * This function takes a pointer to a space-separated string of decimal * numbers (i.e. "128 0x55 0321") with "C" standard radix specifiers * and converts them to an array of bytes. */ static int set_bytes(char *buf, const char *string, int *converted_accum) { char *p = (char *)string; int i; uint byte; if (!p) { printf("ERROR: NULL string passed in.\n"); return -1; } /* Convert string to bytes */ for (i = 0, p = (char *)string; (i < TLV_VALUE_MAX_LEN) && (*p != 0); i++) { while ((*p == ' ') || (*p == '\t') || (*p == ',') || (*p == ';')) { p++; } if (*p != 0) { if (!is_digit(*p)) { printf("ERROR: Non-digit found in byte string: (%s)\n", string); return -1; } byte = simple_strtoul(p, &p, 0); if (byte >= 256) { printf("ERROR: The value specified is greater than 255: (%u) in string: %s\n", byte, string); return -1; } buf[i] = byte & 0xFF; } } if (i == TLV_VALUE_MAX_LEN && (*p != 0)) { printf("ERROR: Trying to assign too many bytes (max: %d) in string: %s\n", TLV_VALUE_MAX_LEN, string); return -1; } *converted_accum = i; return 0; } static void show_tlv_devices(void) { unsigned int dev; for (dev = 0; dev < MAX_TLV_DEVICES; dev++) if (tlv_devices[dev]) printf("TLV: %u%s\n", dev, (dev == current_dev) ? " (*)" : ""); } static int find_tlv_devices(struct udevice **tlv_devices_p) { int ret; int count_dev = 0; struct udevice *dev; for (ret = uclass_first_device_check(UCLASS_I2C_EEPROM, &dev); dev; ret = uclass_next_device_check(&dev)) { if (ret == 0) tlv_devices_p[count_dev++] = dev; if (count_dev >= MAX_TLV_DEVICES) break; } return (count_dev == 0) ? -ENODEV : 0; } static struct udevice *find_tlv_device_by_index(int dev_num) { struct udevice *local_tlv_devices[MAX_TLV_DEVICES] = {}; struct udevice **tlv_devices_p; int ret; if (gd->flags & (GD_FLG_RELOC | GD_FLG_SPL_INIT)) { /* Assume BSS is initialized; use static data */ if (tlv_devices[dev_num]) return tlv_devices[dev_num]; tlv_devices_p = tlv_devices; } else { tlv_devices_p = local_tlv_devices; } ret = find_tlv_devices(tlv_devices_p); if (ret == 0 && tlv_devices_p[dev_num]) return tlv_devices_p[dev_num]; return NULL; } /** * read_tlv_eeprom - read the hwinfo from i2c EEPROM */ int read_tlv_eeprom(void *eeprom, int offset, int len, int dev_num) { struct udevice *dev; if (dev_num >= MAX_TLV_DEVICES) return -EINVAL; dev = find_tlv_device_by_index(dev_num); if (!dev) return -ENODEV; return i2c_eeprom_read(dev, offset, eeprom, len); } /** * write_tlv_eeprom - write the hwinfo to i2c EEPROM */ int write_tlv_eeprom(void *eeprom, int len) { if (!(gd->flags & GD_FLG_RELOC)) return -ENODEV; if (!tlv_devices[current_dev]) return -ENODEV; return i2c_eeprom_write(tlv_devices[current_dev], 0, eeprom, len); } int read_tlvinfo_tlv_eeprom(void *eeprom, struct tlvinfo_header **hdr, struct tlvinfo_tlv **first_entry, int dev_num) { int ret; struct tlvinfo_header *tlv_hdr; struct tlvinfo_tlv *tlv_ent; /* Read TLV header */ ret = read_tlv_eeprom(eeprom, 0, HDR_SIZE, dev_num); if (ret < 0) return ret; tlv_hdr = eeprom; if (!is_valid_tlvinfo_header(tlv_hdr)) return -EINVAL; /* Read TLV entries */ tlv_ent = to_entry(&tlv_hdr[1]); ret = read_tlv_eeprom(tlv_ent, HDR_SIZE, be16_to_cpu(tlv_hdr->totallen), dev_num); if (ret < 0) return ret; if (!is_checksum_valid(eeprom)) return -EINVAL; *hdr = tlv_hdr; *first_entry = tlv_ent; return 0; } /** * mac_read_from_eeprom * * Read the MAC addresses from EEPROM * * This function reads the MAC addresses from EEPROM and sets the * appropriate environment variables for each one read. * * The environment variables are only set if they haven't been set already. * This ensures that any user-saved variables are never overwritten. * * This function must be called after relocation. */ int mac_read_from_eeprom(void) { unsigned int i; int eeprom_index; struct tlvinfo_tlv *eeprom_tlv; int maccount; u8 macbase[6]; struct tlvinfo_header *eeprom_hdr = to_header(eeprom); puts("EEPROM: "); if (read_eeprom(eeprom)) { printf("Read failed.\n"); return -1; } maccount = 1; if (tlvinfo_find_tlv(eeprom, TLV_CODE_MAC_SIZE, &eeprom_index)) { eeprom_tlv = to_entry(&eeprom[eeprom_index]); maccount = (eeprom_tlv->value[0] << 8) | eeprom_tlv->value[1]; } memcpy(macbase, "\0\0\0\0\0\0", 6); if (tlvinfo_find_tlv(eeprom, TLV_CODE_MAC_BASE, &eeprom_index)) { eeprom_tlv = to_entry(&eeprom[eeprom_index]); memcpy(macbase, eeprom_tlv->value, 6); } for (i = 0; i < maccount; i++) { if (is_valid_ethaddr(macbase)) { char ethaddr[18]; char enetvar[11]; sprintf(ethaddr, "%02X:%02X:%02X:%02X:%02X:%02X", macbase[0], macbase[1], macbase[2], macbase[3], macbase[4], macbase[5]); sprintf(enetvar, i ? "eth%daddr" : "ethaddr", i); /* Only initialize environment variables that are blank * (i.e. have not yet been set) */ if (!env_get(enetvar)) env_set(enetvar, ethaddr); macbase[5]++; if (macbase[5] == 0) { macbase[4]++; if (macbase[4] == 0) { macbase[3]++; if (macbase[3] == 0) { macbase[0] = 0; macbase[1] = 0; macbase[2] = 0; } } } } } printf("%s v%u len=%u\n", eeprom_hdr->signature, eeprom_hdr->version, be16_to_cpu(eeprom_hdr->totallen)); return 0; } /** * populate_serial_number - read the serial number from EEPROM * * This function reads the serial number from the EEPROM and sets the * appropriate environment variable. * * The environment variable is only set if it has not been set * already. This ensures that any user-saved variables are never * overwritten. * * This function must be called after relocation. */ int populate_serial_number(void) { char serialstr[257]; int eeprom_index; struct tlvinfo_tlv *eeprom_tlv; if (env_get("serial#")) return 0; if (read_eeprom(eeprom)) { printf("Read failed.\n"); return -1; } if (tlvinfo_find_tlv(eeprom, TLV_CODE_SERIAL_NUMBER, &eeprom_index)) { eeprom_tlv = to_entry(&eeprom[eeprom_index]); memcpy(serialstr, eeprom_tlv->value, eeprom_tlv->length); serialstr[eeprom_tlv->length] = 0; env_set("serial#", serialstr); } return 0; }