diff options
Diffstat (limited to 'netlink/module-eeprom.c')
-rw-r--r-- | netlink/module-eeprom.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/netlink/module-eeprom.c b/netlink/module-eeprom.c new file mode 100644 index 0000000..49833a2 --- /dev/null +++ b/netlink/module-eeprom.c @@ -0,0 +1,305 @@ +/* + * module-eeprom.c - netlink implementation of module eeprom get command + * + * ethtool -m <dev> + */ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stddef.h> + +#include "../sff-common.h" +#include "../qsfp.h" +#include "../cmis.h" +#include "../internal.h" +#include "../common.h" +#include "../list.h" +#include "netlink.h" +#include "parser.h" + +#define ETH_I2C_ADDRESS_LOW 0x50 +#define ETH_I2C_MAX_ADDRESS 0x7F + +struct cmd_params { + u8 dump_hex; + u8 dump_raw; + u32 offset; + u32 length; + u32 page; + u32 bank; + u32 i2c_address; +}; + +static const struct param_parser getmodule_params[] = { + { + .arg = "hex", + .handler = nl_parse_u8bool, + .dest_offset = offsetof(struct cmd_params, dump_hex), + .min_argc = 1, + }, + { + .arg = "raw", + .handler = nl_parse_u8bool, + .dest_offset = offsetof(struct cmd_params, dump_raw), + .min_argc = 1, + }, + { + .arg = "offset", + .handler = nl_parse_direct_u32, + .dest_offset = offsetof(struct cmd_params, offset), + .min_argc = 1, + }, + { + .arg = "length", + .handler = nl_parse_direct_u32, + .dest_offset = offsetof(struct cmd_params, length), + .min_argc = 1, + }, + { + .arg = "page", + .handler = nl_parse_direct_u32, + .dest_offset = offsetof(struct cmd_params, page), + .min_argc = 1, + }, + { + .arg = "bank", + .handler = nl_parse_direct_u32, + .dest_offset = offsetof(struct cmd_params, bank), + .min_argc = 1, + }, + { + .arg = "i2c", + .handler = nl_parse_direct_u32, + .dest_offset = offsetof(struct cmd_params, i2c_address), + .min_argc = 1, + }, + {} +}; + +static struct list_head eeprom_page_list = LIST_HEAD_INIT(eeprom_page_list); + +struct eeprom_page_entry { + struct list_head list; /* Member of eeprom_page_list */ + void *data; +}; + +static int eeprom_page_list_add(void *data) +{ + struct eeprom_page_entry *entry; + + entry = malloc(sizeof(*entry)); + if (!entry) + return -ENOMEM; + + entry->data = data; + list_add(&entry->list, &eeprom_page_list); + + return 0; +} + +static void eeprom_page_list_flush(void) +{ + struct eeprom_page_entry *entry; + struct list_head *head, *next; + + list_for_each_safe(head, next, &eeprom_page_list) { + entry = (struct eeprom_page_entry *) head; + free(entry->data); + list_del(head); + free(entry); + } +} + +static int get_eeprom_page_reply_cb(const struct nlmsghdr *nlhdr, void *data) +{ + const struct nlattr *tb[ETHTOOL_A_MODULE_EEPROM_DATA + 1] = {}; + struct ethtool_module_eeprom *request = data; + DECLARE_ATTR_TB_INFO(tb); + u8 *eeprom_data; + int ret; + + ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); + if (ret < 0) + return ret; + + if (!tb[ETHTOOL_A_MODULE_EEPROM_DATA]) + return MNL_CB_ERROR; + + eeprom_data = mnl_attr_get_payload(tb[ETHTOOL_A_MODULE_EEPROM_DATA]); + request->data = malloc(request->length); + if (!request->data) + return MNL_CB_ERROR; + memcpy(request->data, eeprom_data, request->length); + + ret = eeprom_page_list_add(request->data); + if (ret < 0) + goto err_list_add; + + return MNL_CB_OK; + +err_list_add: + free(request->data); + return MNL_CB_ERROR; +} + +int nl_get_eeprom_page(struct cmd_context *ctx, + struct ethtool_module_eeprom *request) +{ + struct nl_context *nlctx = ctx->nlctx; + struct nl_socket *nlsock; + struct nl_msg_buff *msg; + int ret; + + if (!request || request->i2c_address > ETH_I2C_MAX_ADDRESS) + return -EINVAL; + + nlsock = nlctx->ethnl_socket; + msg = &nlsock->msgbuff; + + ret = nlsock_prep_get_request(nlsock, ETHTOOL_MSG_MODULE_EEPROM_GET, + ETHTOOL_A_MODULE_EEPROM_HEADER, 0); + if (ret < 0) + return ret; + + if (ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_LENGTH, + request->length) || + ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_OFFSET, + request->offset) || + ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_PAGE, + request->page) || + ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_BANK, + request->bank) || + ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS, + request->i2c_address)) + return -EMSGSIZE; + + ret = nlsock_sendmsg(nlsock, NULL); + if (ret < 0) + return ret; + return nlsock_process_reply(nlsock, get_eeprom_page_reply_cb, + (void *)request); +} + +static int eeprom_dump_hex(struct cmd_context *ctx) +{ + struct ethtool_module_eeprom request = { + .length = 128, + .i2c_address = ETH_I2C_ADDRESS_LOW, + }; + int ret; + + ret = nl_get_eeprom_page(ctx, &request); + if (ret < 0) + return ret; + + dump_hex(stdout, request.data, request.length, request.offset); + + return 0; +} + +static int eeprom_parse(struct cmd_context *ctx) +{ + struct ethtool_module_eeprom request = { + .length = 1, + .i2c_address = ETH_I2C_ADDRESS_LOW, + }; + int ret; + + /* Fetch the SFF-8024 Identifier Value. For all supported standards, it + * is located at I2C address 0x50, byte 0. See section 4.1 in SFF-8024, + * revision 4.9. + */ + ret = nl_get_eeprom_page(ctx, &request); + if (ret < 0) + return ret; + + switch (request.data[0]) { +#ifdef ETHTOOL_ENABLE_PRETTY_DUMP + case SFF8024_ID_SFP: + return sff8079_show_all_nl(ctx); + case SFF8024_ID_QSFP: + case SFF8024_ID_QSFP28: + case SFF8024_ID_QSFP_PLUS: + return sff8636_show_all_nl(ctx); + case SFF8024_ID_QSFP_DD: + case SFF8024_ID_OSFP: + case SFF8024_ID_DSFP: + return cmis_show_all_nl(ctx); +#endif + default: + /* If we cannot recognize the memory map, default to dumping + * the first 128 bytes in hex. + */ + return eeprom_dump_hex(ctx); + } +} + +int nl_getmodule(struct cmd_context *ctx) +{ + struct cmd_params getmodule_cmd_params = {}; + struct ethtool_module_eeprom request = {0}; + struct nl_context *nlctx = ctx->nlctx; + int ret; + + if (netlink_cmd_check(ctx, ETHTOOL_MSG_MODULE_EEPROM_GET, false)) + return -EOPNOTSUPP; + + nlctx->cmd = "-m"; + nlctx->argp = ctx->argp; + nlctx->argc = ctx->argc; + nlctx->devname = ctx->devname; + ret = nl_parser(nlctx, getmodule_params, &getmodule_cmd_params, PARSER_GROUP_NONE, NULL); + if (ret < 0) + return ret; + + if (getmodule_cmd_params.dump_hex && getmodule_cmd_params.dump_raw) { + fprintf(stderr, "Hex and raw dump cannot be specified together\n"); + return -EINVAL; + } + + /* When complete hex/raw dump of the EEPROM is requested, fallback to + * ioctl. Netlink can only request specific pages. + */ + if ((getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) && + !getmodule_cmd_params.page && !getmodule_cmd_params.bank && + !getmodule_cmd_params.i2c_address) { + nlctx->ioctl_fallback = true; + return -EOPNOTSUPP; + } + +#ifdef ETHTOOL_ENABLE_PRETTY_DUMP + if (getmodule_cmd_params.page || getmodule_cmd_params.bank || + getmodule_cmd_params.offset || getmodule_cmd_params.length) +#endif + getmodule_cmd_params.dump_hex = true; + + request.offset = getmodule_cmd_params.offset; + request.length = getmodule_cmd_params.length ?: 128; + request.page = getmodule_cmd_params.page; + request.bank = getmodule_cmd_params.bank; + request.i2c_address = getmodule_cmd_params.i2c_address ?: ETH_I2C_ADDRESS_LOW; + + if (request.page && !request.offset) + request.offset = 128; + + if (getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) { + ret = nl_get_eeprom_page(ctx, &request); + if (ret < 0) + goto cleanup; + + if (getmodule_cmd_params.dump_raw) + fwrite(request.data, 1, request.length, stdout); + else + dump_hex(stdout, request.data, request.length, + request.offset); + } else { + ret = eeprom_parse(ctx); + if (ret < 0) + goto cleanup; + } + +cleanup: + eeprom_page_list_flush(); + return ret; +} |