summaryrefslogtreecommitdiffstats
path: root/netlink/module-eeprom.c
diff options
context:
space:
mode:
Diffstat (limited to 'netlink/module-eeprom.c')
-rw-r--r--netlink/module-eeprom.c305
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;
+}