blob: 49833a2a6a38a8b0a678dbe6795b49a228a78b7c [file] [log] [blame] [edit]
/*
* 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;
}