From aa9e60441095ee3f20a109742e3ba5cdfd28458b Mon Sep 17 00:00:00 2001 From: Nikita Kiryanov Date: Sat, 16 Apr 2016 17:55:03 +0300 Subject: [PATCH] cmd: eeprom: add support for layout aware commands Introduce the (optional) eeprom print and eeprom update commands. These commands are eeprom layout aware: * The eeprom print command prints the contents of the eeprom in a human readable way (eeprom layout fields, and data formatted to be fit for human consumption). * The eeprom update command allows user to update eeprom fields by specifying the field name, and providing the new data in a human readable format (same format as displayed by the eeprom print command). * Both commands can either auto detect the layout, or be told which layout to use. New CONFIG options: CONFIG_CMD_EEPROM_LAYOUT - enables commands. CONFIG_EEPROM_LAYOUT_HELP_STRING - tells user what layout names are supported Feature API: __weak int parse_layout_version(char *str) - override to provide your own layout name parsing __weak void __eeprom_layout_assign(struct eeprom_layout *layout, int layout_version); - override to setup the layout metadata based on the version __weak int eeprom_layout_detect(unsigned char *data) - override to provide your own algorithm for detecting layout version eeprom_field.c - contains various printing and updating functions for common types of eeprom fields. Can be used for defining custom layouts. Cc: Heiko Schocher Cc: Marek Vasut Cc: Simon Glass Cc: Igor Grinberg Cc: Tom Rini Signed-off-by: Nikita Kiryanov --- README | 1 + cmd/eeprom.c | 148 +++++++++++++++++++- common/Makefile | 3 + common/eeprom/eeprom_field.c | 250 ++++++++++++++++++++++++++++++++++ common/eeprom/eeprom_layout.c | 125 +++++++++++++++++ include/eeprom_field.h | 39 ++++++ include/eeprom_layout.h | 33 +++++ 7 files changed, 598 insertions(+), 1 deletion(-) create mode 100644 common/eeprom/eeprom_field.c create mode 100644 common/eeprom/eeprom_layout.c create mode 100644 include/eeprom_field.h create mode 100644 include/eeprom_layout.h diff --git a/README b/README index 94e9943b04..6f4c09a3a1 100644 --- a/README +++ b/README @@ -1003,6 +1003,7 @@ The following options need to be configured: CONFIG_CMD_ECHO echo arguments CONFIG_CMD_EDITENV edit env variable CONFIG_CMD_EEPROM * EEPROM read/write support + CONFIG_CMD_EEPROM_LAYOUT* EEPROM layout aware commands CONFIG_CMD_ELF * bootelf, bootvx CONFIG_CMD_ENV_CALLBACK * display details about env callbacks CONFIG_CMD_ENV_FLAGS * display details about env flags diff --git a/cmd/eeprom.c b/cmd/eeprom.c index 208b4138c1..39ebee8dd9 100644 --- a/cmd/eeprom.c +++ b/cmd/eeprom.c @@ -207,6 +207,131 @@ int eeprom_write(unsigned dev_addr, unsigned offset, return ret; } +#ifdef CONFIG_CMD_EEPROM_LAYOUT +#include + +__weak int eeprom_parse_layout_version(char *str) +{ + return LAYOUT_VERSION_UNRECOGNIZED; +} + +static unsigned char eeprom_buf[CONFIG_SYS_EEPROM_SIZE]; + +#ifndef CONFIG_EEPROM_LAYOUT_HELP_STRING +#define CONFIG_EEPROM_LAYOUT_HELP_STRING "" +#endif + +enum eeprom_action { + EEPROM_PRINT, + EEPROM_UPDATE, + EEPROM_ACTION_INVALID, +}; + +static enum eeprom_action parse_action(char *cmd) +{ + if (!strncmp(cmd, "print", 5)) + return EEPROM_PRINT; + if (!strncmp(cmd, "update", 6)) + return EEPROM_UPDATE; + + return EEPROM_ACTION_INVALID; +} + +static int parse_numeric_param(char *str) +{ + char *endptr; + int value = simple_strtol(str, &endptr, 16); + + return (*endptr != '\0') ? -1 : value; +} + +static int eeprom_execute_command(enum eeprom_action action, int i2c_bus, + int i2c_addr, int layout_ver, char *key, + char *value) +{ + int rcode; + struct eeprom_layout layout; + + if (action == EEPROM_ACTION_INVALID) + return CMD_RET_USAGE; + + eeprom_init(i2c_bus); + rcode = eeprom_read(i2c_addr, 0, eeprom_buf, CONFIG_SYS_EEPROM_SIZE); + if (rcode < 0) + return rcode; + + eeprom_layout_setup(&layout, eeprom_buf, CONFIG_SYS_EEPROM_SIZE, + layout_ver); + + if (action == EEPROM_PRINT) { + layout.print(&layout); + return 0; + } + + layout.update(&layout, key, value); + + rcode = eeprom_write(i2c_addr, 0, layout.data, CONFIG_SYS_EEPROM_SIZE); + + return rcode; +} + +#define NEXT_PARAM(argc, index) { (argc)--; (index)++; } +static int do_eeprom_layout(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + int layout_ver = LAYOUT_VERSION_AUTODETECT; + enum eeprom_action action = EEPROM_ACTION_INVALID; + int i2c_bus = -1, i2c_addr = -1, index = 0; + char *field_name = ""; + char *field_value = ""; + + if (argc <= 1) + return CMD_RET_USAGE; + + NEXT_PARAM(argc, index); /* Skip program name */ + + action = parse_action(argv[index]); + NEXT_PARAM(argc, index); + + if (argc <= 1) + return CMD_RET_USAGE; + + if (!strcmp(argv[index], "-l")) { + NEXT_PARAM(argc, index); + + layout_ver = eeprom_parse_layout_version(argv[index]); + NEXT_PARAM(argc, index); + } + + if (argc <= 1) + return CMD_RET_USAGE; + + i2c_bus = parse_numeric_param(argv[index]); + NEXT_PARAM(argc, index); + + i2c_addr = parse_numeric_param(argv[index]); + NEXT_PARAM(argc, index); + + if (action == EEPROM_PRINT) + goto done; + + if (argc) { + field_name = argv[index]; + NEXT_PARAM(argc, index); + } + + if (argc) { + field_value = argv[index]; + NEXT_PARAM(argc, index); + } + +done: + return eeprom_execute_command(action, i2c_bus, i2c_addr, layout_ver, + field_name, field_value); +} + +#endif + static int do_eeprom(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { const char *const fmt = @@ -216,6 +341,13 @@ static int do_eeprom(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) ulong dev_addr, addr, off, cnt; int bus_addr; +#ifdef CONFIG_CMD_EEPROM_LAYOUT + if (argc >= 2) { + if (!strcmp(argv[1], "update") || !strcmp(argv[1], "print")) + return do_eeprom_layout(cmdtp, flag, argc, argv); + } +#endif + switch (argc) { #ifdef CONFIG_SYS_DEF_EEPROM_ADDR case 5: @@ -261,9 +393,23 @@ static int do_eeprom(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) } U_BOOT_CMD( - eeprom, 7, 1, do_eeprom, + eeprom, 8, 1, do_eeprom, "EEPROM sub-system", "read addr off cnt\n" "eeprom write addr off cnt\n" " - read/write `cnt' bytes from `devaddr` EEPROM at offset `off'" +#ifdef CONFIG_CMD_EEPROM_LAYOUT + "\n" + "eeprom print [-l ] bus devaddr\n" + " - Print layout fields and their data in human readable format\n" + "eeprom update [-l ] bus devaddr \n" + " - Update a specific eeprom field with new data.\n" + " The new data must be written in the same human readable format as shown by the print command.\n" + "\n" + "LAYOUT VERSIONS\n" + "The -l option can be used to force the command to interpret the EEPROM data using the chosen layout.\n" + "If the -l option is omitted, the command will auto detect the layout based on the data in the EEPROM.\n" + "The values which can be provided with the -l option are:\n" + CONFIG_EEPROM_LAYOUT_HELP_STRING"\n" +#endif ) diff --git a/common/Makefile b/common/Makefile index f9b26b7bbe..0562d5cea4 100644 --- a/common/Makefile +++ b/common/Makefile @@ -156,6 +156,9 @@ obj-y += fb_nand.o endif endif +ifdef CONFIG_CMD_EEPROM_LAYOUT +obj-y += eeprom/eeprom_field.o eeprom/eeprom_layout.o +endif # We always have this since drivers/ddr/fs/interactive.c needs it obj-$(CONFIG_CMDLINE) += cli_simple.o diff --git a/common/eeprom/eeprom_field.c b/common/eeprom/eeprom_field.c new file mode 100644 index 0000000000..7f095a64a2 --- /dev/null +++ b/common/eeprom/eeprom_field.c @@ -0,0 +1,250 @@ +/* + * (C) Copyright 2009-2016 CompuLab, Ltd. + * + * Authors: Nikita Kiryanov + * Igor Grinberg + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include + +static void __eeprom_field_print_bin(const struct eeprom_field *field, + char *delimiter, bool reverse) +{ + int i; + int from = reverse ? field->size - 1 : 0; + int to = reverse ? 0 : field->size - 1; + + printf(PRINT_FIELD_SEGMENT, field->name); + for (i = from; i != to; reverse ? i-- : i++) + printf("%02x%s", field->buf[i], delimiter); + + printf("%02x\n", field->buf[i]); +} + +static int __eeprom_field_update_bin(struct eeprom_field *field, + const char *value, bool reverse) +{ + int len = strlen(value); + int k, j, i = reverse ? len - 1 : 0; + unsigned char byte; + char *endptr; + + /* each two characters in the string fit in one byte */ + if (len > field->size * 2) + return -1; + + memset(field->buf, 0, field->size); + + /* i - string iterator, j - buf iterator */ + for (j = 0; j < field->size; j++) { + byte = 0; + char tmp[3] = { 0, 0, 0 }; + + if ((reverse && i < 0) || (!reverse && i >= len)) + break; + + for (k = 0; k < 2; k++) { + if (reverse && i == 0) { + tmp[k] = value[i]; + break; + } + + tmp[k] = value[reverse ? i - 1 + k : i + k]; + } + + byte = simple_strtoul(tmp, &endptr, 0); + if (*endptr != '\0' || byte < 0) + return -1; + + field->buf[j] = byte; + i = reverse ? i - 2 : i + 2; + } + + return 0; +} + +static int __eeprom_field_update_bin_delim(struct eeprom_field *field, + char *value, char *delimiter) +{ + int count = 0; + int i, val; + const char *tmp = value; + char *tok; + char *endptr; + + tmp = strstr(tmp, delimiter); + while (tmp != NULL) { + count++; + tmp++; + tmp = strstr(tmp, delimiter); + } + + if (count > field->size) + return -1; + + tok = strtok(value, delimiter); + for (i = 0; tok && i < field->size; i++) { + val = simple_strtoul(tok, &endptr, 0); + if (*endptr != '\0') + return -1; + + /* here we assume that each tok is no more than byte long */ + field->buf[i] = (unsigned char)val; + tok = strtok(NULL, delimiter); + } + + return 0; +} + +/** + * eeprom_field_print_bin() - print a field which contains binary data + * + * Treat the field data as simple binary data, and print it as two digit + * hexadecimal values. + * Sample output: + * Field Name 0102030405060708090a + * + * @field: an initialized field to print + */ +void eeprom_field_print_bin(const struct eeprom_field *field) +{ + __eeprom_field_print_bin(field, "", false); +} + +/** + * eeprom_field_update_bin() - Update field with new data in binary form + * + * @field: an initialized field + * @value: a string of values (i.e. "10b234a") + */ +int eeprom_field_update_bin(struct eeprom_field *field, char *value) +{ + return __eeprom_field_update_bin(field, value, false); +} + +/** + * eeprom_field_update_reserved() - Update reserved field with new data in + * binary form + * + * @field: an initialized field + * @value: a space delimited string of byte values (i.e. "1 02 3 0x4") + */ +int eeprom_field_update_reserved(struct eeprom_field *field, char *value) +{ + return __eeprom_field_update_bin_delim(field, value, " "); +} + +/** + * eeprom_field_print_bin_rev() - print a field which contains binary data in + * reverse order + * + * Treat the field data as simple binary data, and print it in reverse order + * as two digit hexadecimal values. + * + * Data in field: + * 0102030405060708090a + * Sample output: + * Field Name 0a090807060504030201 + * + * @field: an initialized field to print + */ +void eeprom_field_print_bin_rev(const struct eeprom_field *field) +{ + __eeprom_field_print_bin(field, "", true); +} + +/** + * eeprom_field_update_bin_rev() - Update field with new data in binary form, + * storing it in reverse + * + * This function takes a string of byte values, and stores them + * in the field in the reverse order. i.e. if the input string was "1234", + * "3412" will be written to the field. + * + * @field: an initialized field + * @value: a string of byte values + */ +int eeprom_field_update_bin_rev(struct eeprom_field *field, char *value) +{ + return __eeprom_field_update_bin(field, value, true); +} + +/** + * eeprom_field_print_mac_addr() - print a field which contains a mac address + * + * Treat the field data as simple binary data, and print it formatted as a MAC + * address. + * Sample output: + * Field Name 01:02:03:04:05:06 + * + * @field: an initialized field to print + */ +void eeprom_field_print_mac(const struct eeprom_field *field) +{ + __eeprom_field_print_bin(field, ":", false); +} + +/** + * eeprom_field_update_mac() - Update a mac address field which contains binary + * data + * + * @field: an initialized field + * @value: a colon delimited string of byte values (i.e. "1:02:3:ff") + */ +int eeprom_field_update_mac(struct eeprom_field *field, char *value) +{ + return __eeprom_field_update_bin_delim(field, value, ":"); +} + +/** + * eeprom_field_print_ascii() - print a field which contains ASCII data + * @field: an initialized field to print + */ +void eeprom_field_print_ascii(const struct eeprom_field *field) +{ + char format[8]; + + sprintf(format, "%%.%ds\n", field->size); + printf(PRINT_FIELD_SEGMENT, field->name); + printf(format, field->buf); +} + +/** + * eeprom_field_update_ascii() - Update field with new data in ASCII form + * @field: an initialized field + * @value: the new string data + * + * Returns 0 on success, -1 of failure (new string too long). + */ +int eeprom_field_update_ascii(struct eeprom_field *field, char *value) +{ + if (strlen(value) >= field->size) { + printf("%s: new data too long\n", field->name); + return -1; + } + + strncpy((char *)field->buf, value, field->size - 1); + field->buf[field->size - 1] = '\0'; + + return 0; +} + +/** + * eeprom_field_print_reserved() - print the "Reserved fields" field + * + * Print a notice that the following field_size bytes are reserved. + * + * Sample output: + * Reserved fields (64 bytes) + * + * @field: an initialized field to print + */ +void eeprom_field_print_reserved(const struct eeprom_field *field) +{ + printf(PRINT_FIELD_SEGMENT, "Reserved fields\t"); + printf("(%d bytes)\n", field->size); +} diff --git a/common/eeprom/eeprom_layout.c b/common/eeprom/eeprom_layout.c new file mode 100644 index 0000000000..c05923328a --- /dev/null +++ b/common/eeprom/eeprom_layout.c @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2009-2016 CompuLab, Ltd. + * + * Authors: Nikita Kiryanov + * Igor Grinberg + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include + +#define NO_LAYOUT_FIELDS "Unknown layout. Dumping raw data\n" + +struct eeprom_field layout_unknown[1] = { + { NO_LAYOUT_FIELDS, 256, NULL, eeprom_field_print_bin, + eeprom_field_update_bin }, +}; + +/* + * eeprom_layout_detect() - detect layout based on the contents of the data. + * @data: Pointer to the data to be analyzed. + * + * Returns: the detected layout version. + */ +__weak int eeprom_layout_detect(unsigned char *data) +{ + return LAYOUT_VERSION_UNRECOGNIZED; +} + +/* + * __eeprom_layout_assign() - set the layout fields + * @layout: A pointer to an existing struct layout. + * @layout_version: The version number of the desired layout + */ +__weak void __eeprom_layout_assign(struct eeprom_layout *layout, + int layout_version) +{ + layout->fields = layout_unknown; + layout->num_of_fields = ARRAY_SIZE(layout_unknown); +} +void eeprom_layout_assign(struct eeprom_layout *layout, int layout_version) \ + __attribute__((weak, alias("__eeprom_layout_assign"))); + +/* + * eeprom_layout_print() - print the layout and the data which is assigned to it + * @layout: A pointer to an existing struct layout. + */ +static void eeprom_layout_print(const struct eeprom_layout *layout) +{ + int i; + struct eeprom_field *fields = layout->fields; + + for (i = 0; i < layout->num_of_fields; i++) + fields[i].print(&fields[i]); +} + +/* + * eeprom_layout_update_field() - update a single field in the layout data. + * @layout: A pointer to an existing struct layout. + * @field_name: The name of the field to update. + * @new_data: The new field data (a string. Format depends on the field) + * + * Returns: 0 on success, negative error value on failure. + */ +static int eeprom_layout_update_field(struct eeprom_layout *layout, + char *field_name, char *new_data) +{ + int i, err; + struct eeprom_field *fields = layout->fields; + + if (new_data == NULL) + return 0; + + if (field_name == NULL) + return -1; + + for (i = 0; i < layout->num_of_fields; i++) { + if (fields[i].name == RESERVED_FIELDS || + strcmp(fields[i].name, field_name)) + continue; + + err = fields[i].update(&fields[i], new_data); + if (err) + printf("Invalid data for field %s\n", field_name); + + return err; + } + + printf("No such field '%s'\n", field_name); + + return -1; +} + +/* + * eeprom_layout_setup() - setup layout struct with the layout data and + * metadata as dictated by layout_version + * @layout: A pointer to an existing struct layout. + * @buf: A buffer initialized with the eeprom data. + * @buf_size: Size of buf in bytes. + * @layout version: The version number of the layout. + */ +void eeprom_layout_setup(struct eeprom_layout *layout, unsigned char *buf, + unsigned int buf_size, int layout_version) +{ + int i; + + if (layout_version == LAYOUT_VERSION_AUTODETECT) + layout->layout_version = eeprom_layout_detect(buf); + else + layout->layout_version = layout_version; + + eeprom_layout_assign(layout, layout_version); + layout->data = buf; + for (i = 0; i < layout->num_of_fields; i++) { + layout->fields[i].buf = buf; + buf += layout->fields[i].size; + } + + layout->data_size = buf_size; + layout->print = eeprom_layout_print; + layout->update = eeprom_layout_update_field; +} diff --git a/include/eeprom_field.h b/include/eeprom_field.h new file mode 100644 index 0000000000..94e259ff73 --- /dev/null +++ b/include/eeprom_field.h @@ -0,0 +1,39 @@ +/* + * (C) Copyright 2009-2016 CompuLab, Ltd. + * + * Authors: Nikita Kiryanov + * Igor Grinberg + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _FIELD_ +#define _FIELD_ + +#define PRINT_FIELD_SEGMENT "%-30s" + +struct eeprom_field { + char *name; + int size; + unsigned char *buf; + + void (*print)(const struct eeprom_field *eeprom_field); + int (*update)(struct eeprom_field *eeprom_field, char *value); +}; + +void eeprom_field_print_bin(const struct eeprom_field *field); +int eeprom_field_update_bin(struct eeprom_field *field, char *value); + +void eeprom_field_print_bin_rev(const struct eeprom_field *field); +int eeprom_field_update_bin_rev(struct eeprom_field *field, char *value); + +void eeprom_field_print_mac(const struct eeprom_field *field); +int eeprom_field_update_mac(struct eeprom_field *field, char *value); + +void eeprom_field_print_ascii(const struct eeprom_field *field); +int eeprom_field_update_ascii(struct eeprom_field *field, char *value); + +void eeprom_field_print_reserved(const struct eeprom_field *field); +int eeprom_field_update_reserved(struct eeprom_field *field, char *value); + +#endif diff --git a/include/eeprom_layout.h b/include/eeprom_layout.h new file mode 100644 index 0000000000..459b99d861 --- /dev/null +++ b/include/eeprom_layout.h @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2009-2016 CompuLab, Ltd. + * + * Authors: Nikita Kiryanov + * Igor Grinberg + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _LAYOUT_ +#define _LAYOUT_ + +#define RESERVED_FIELDS NULL +#define LAYOUT_VERSION_UNRECOGNIZED -1 +#define LAYOUT_VERSION_AUTODETECT -2 + +struct eeprom_layout { + struct eeprom_field *fields; + int num_of_fields; + int layout_version; + unsigned char *data; + int data_size; + void (*print)(const struct eeprom_layout *eeprom_layout); + int (*update)(struct eeprom_layout *eeprom_layout, char *field_name, + char *new_data); +}; + +void eeprom_layout_setup(struct eeprom_layout *layout, unsigned char *buf, + unsigned int buf_size, int layout_version); +__weak void __eeprom_layout_assign(struct eeprom_layout *layout, + int layout_version); + +#endif -- 2.25.1