Commit c781ff12 authored by Vladyslav Tarasiuk's avatar Vladyslav Tarasiuk Committed by David S. Miller
Browse files

ethtool: Allow network drivers to dump arbitrary EEPROM data



Define get_module_eeprom_by_page() ethtool callback and implement
netlink infrastructure.

get_module_eeprom_by_page() allows network drivers to dump a part of
module's EEPROM specified by page and bank numbers along with offset and
length. It is effectively a netlink replacement for get_module_info()
and get_module_eeprom() pair, which is needed due to emergence of
complex non-linear EEPROM layouts.

Signed-off-by: default avatarVladyslav Tarasiuk <vladyslavt@nvidia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent cbd31253
Loading
Loading
Loading
Loading
+34 −2
Original line number Diff line number Diff line
@@ -1338,6 +1338,38 @@ in an implementation specific way.
``ETHTOOL_A_FEC_AUTO`` requests the driver to choose FEC mode based on SFP
module parameters. This does not mean autonegotiation.

MODULE_EEPROM
=============

Fetch module EEPROM data dump.
This interface is designed to allow dumps of at most 1/2 page at once. This
means only dumps of 128 (or less) bytes are allowed, without crossing half page
boundary located at offset 128. For pages other than 0 only high 128 bytes are
accessible.

Request contents:

  =======================================  ======  ==========================
  ``ETHTOOL_A_MODULE_EEPROM_HEADER``       nested  request header
  ``ETHTOOL_A_MODULE_EEPROM_OFFSET``       u32     offset within a page
  ``ETHTOOL_A_MODULE_EEPROM_LENGTH``       u32     amount of bytes to read
  ``ETHTOOL_A_MODULE_EEPROM_PAGE``         u8      page number
  ``ETHTOOL_A_MODULE_EEPROM_BANK``         u8      bank number
  ``ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS``  u8      page I2C address
  =======================================  ======  ==========================

Kernel response contents:

 +---------------------------------------------+--------+---------------------+
 | ``ETHTOOL_A_MODULE_EEPROM_HEADER``          | nested | reply header        |
 +---------------------------------------------+--------+---------------------+
 | ``ETHTOOL_A_MODULE_EEPROM_DATA``            | nested | array of bytes from |
 |                                             |        | module EEPROM       |
 +---------------------------------------------+--------+---------------------+

``ETHTOOL_A_MODULE_EEPROM_DATA`` has an attribute length equal to the amount of
bytes driver actually read.

Request translation
===================

@@ -1415,8 +1447,8 @@ are netlink only.
  ``ETHTOOL_GET_DUMP_FLAG``           n/a
  ``ETHTOOL_GET_DUMP_DATA``           n/a
  ``ETHTOOL_GET_TS_INFO``             ``ETHTOOL_MSG_TSINFO_GET``
  ``ETHTOOL_GMODULEINFO``             n/a
  ``ETHTOOL_GMODULEEEPROM``           n/a
  ``ETHTOOL_GMODULEINFO``             ``ETHTOOL_MSG_MODULE_EEPROM_GET``
  ``ETHTOOL_GMODULEEEPROM``           ``ETHTOOL_MSG_MODULE_EEPROM_GET``
  ``ETHTOOL_GEEE``                    ``ETHTOOL_MSG_EEE_GET``
  ``ETHTOOL_SEEE``                    ``ETHTOOL_MSG_EEE_SET``
  ``ETHTOOL_GRSSH``                   n/a
+32 −1
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ enum {
#define ETH_RSS_HASH_NO_CHANGE	0

struct net_device;
struct netlink_ext_ack;

/* Some generic methods drivers may use in their ethtool_ops */
u32 ethtool_op_get_link(struct net_device *dev);
@@ -262,6 +263,31 @@ struct ethtool_pause_stats {
	u64 rx_pause_frames;
};

#define ETH_MODULE_EEPROM_PAGE_LEN	128
#define ETH_MODULE_MAX_I2C_ADDRESS	0x7f

/**
 * struct ethtool_module_eeprom - EEPROM dump from specified page
 * @offset: Offset within the specified EEPROM page to begin read, in bytes.
 * @length: Number of bytes to read.
 * @page: Page number to read from.
 * @bank: Page bank number to read from, if applicable by EEPROM spec.
 * @i2c_address: I2C address of a page. Value less than 0x7f expected. Most
 *	EEPROMs use 0x50 or 0x51.
 * @data: Pointer to buffer with EEPROM data of @length size.
 *
 * This can be used to manage pages during EEPROM dump in ethtool and pass
 * required information to the driver.
 */
struct ethtool_module_eeprom {
	__u32	offset;
	__u32	length;
	__u8	page;
	__u8	bank;
	__u8	i2c_address;
	__u8	*data;
};

/**
 * struct ethtool_ops - optional netdev operations
 * @cap_link_lanes_supported: indicates if the driver supports lanes
@@ -414,6 +440,9 @@ struct ethtool_pause_stats {
 *	cannot use the standard PHY library helpers.
 * @get_phy_tunable: Read the value of a PHY tunable.
 * @set_phy_tunable: Set the value of a PHY tunable.
 * @get_module_eeprom_by_page: Get a region of plug-in module EEPROM data from
 *	specified page. Returns a negative error code or the amount of bytes
 *	read.
 *
 * All operations are optional (i.e. the function pointer may be set
 * to %NULL) and callers must take this into account.  Callers must
@@ -519,6 +548,9 @@ struct ethtool_ops {
				   const struct ethtool_tunable *, void *);
	int	(*set_phy_tunable)(struct net_device *,
				   const struct ethtool_tunable *, const void *);
	int	(*get_module_eeprom_by_page)(struct net_device *dev,
					     const struct ethtool_module_eeprom *page,
					     struct netlink_ext_ack *extack);
};

int ethtool_check_ops(const struct ethtool_ops *ops);
@@ -542,7 +574,6 @@ int ethtool_virtdev_set_link_ksettings(struct net_device *dev,
				       const struct ethtool_link_ksettings *cmd,
				       u32 *dev_speed, u8 *dev_duplex);

struct netlink_ext_ack;
struct phy_device;
struct phy_tdr_config;

+19 −0
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ enum {
	ETHTOOL_MSG_TUNNEL_INFO_GET,
	ETHTOOL_MSG_FEC_GET,
	ETHTOOL_MSG_FEC_SET,
	ETHTOOL_MSG_MODULE_EEPROM_GET,

	/* add new constants above here */
	__ETHTOOL_MSG_USER_CNT,
@@ -84,6 +85,7 @@ enum {
	ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY,
	ETHTOOL_MSG_FEC_GET_REPLY,
	ETHTOOL_MSG_FEC_NTF,
	ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY,

	/* add new constants above here */
	__ETHTOOL_MSG_KERNEL_CNT,
@@ -646,6 +648,23 @@ enum {
	ETHTOOL_A_FEC_MAX = (__ETHTOOL_A_FEC_CNT - 1)
};

/* MODULE EEPROM */

enum {
	ETHTOOL_A_MODULE_EEPROM_UNSPEC,
	ETHTOOL_A_MODULE_EEPROM_HEADER,			/* nest - _A_HEADER_* */

	ETHTOOL_A_MODULE_EEPROM_OFFSET,			/* u32 */
	ETHTOOL_A_MODULE_EEPROM_LENGTH,			/* u32 */
	ETHTOOL_A_MODULE_EEPROM_PAGE,			/* u8 */
	ETHTOOL_A_MODULE_EEPROM_BANK,			/* u8 */
	ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS,		/* u8 */
	ETHTOOL_A_MODULE_EEPROM_DATA,			/* nested */

	__ETHTOOL_A_MODULE_EEPROM_CNT,
	ETHTOOL_A_MODULE_EEPROM_MAX = (__ETHTOOL_A_MODULE_EEPROM_CNT - 1)
};

/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
+1 −1
Original line number Diff line number Diff line
@@ -7,4 +7,4 @@ obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
ethtool_nl-y	:= netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
		   linkstate.o debug.o wol.o features.o privflags.o rings.o \
		   channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
		   tunnels.o fec.o
		   tunnels.o fec.o eeprom.o

net/ethtool/eeprom.c

0 → 100644
+171 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/ethtool.h>
#include "netlink.h"
#include "common.h"

struct eeprom_req_info {
	struct ethnl_req_info	base;
	u32			offset;
	u32			length;
	u8			page;
	u8			bank;
	u8			i2c_address;
};

struct eeprom_reply_data {
	struct ethnl_reply_data base;
	u32			length;
	u8			*data;
};

#define MODULE_EEPROM_REQINFO(__req_base) \
	container_of(__req_base, struct eeprom_req_info, base)

#define MODULE_EEPROM_REPDATA(__reply_base) \
	container_of(__reply_base, struct eeprom_reply_data, base)

static int eeprom_prepare_data(const struct ethnl_req_info *req_base,
			       struct ethnl_reply_data *reply_base,
			       struct genl_info *info)
{
	struct eeprom_reply_data *reply = MODULE_EEPROM_REPDATA(reply_base);
	struct eeprom_req_info *request = MODULE_EEPROM_REQINFO(req_base);
	struct ethtool_module_eeprom page_data = {0};
	struct net_device *dev = reply_base->dev;
	int ret;

	if (!dev->ethtool_ops->get_module_eeprom_by_page)
		return -EOPNOTSUPP;

	page_data.offset = request->offset;
	page_data.length = request->length;
	page_data.i2c_address = request->i2c_address;
	page_data.page = request->page;
	page_data.bank = request->bank;
	page_data.data = kmalloc(page_data.length, GFP_KERNEL);
	if (!page_data.data)
		return -ENOMEM;

	ret = ethnl_ops_begin(dev);
	if (ret)
		goto err_free;

	ret = dev->ethtool_ops->get_module_eeprom_by_page(dev, &page_data,
							  info->extack);
	if (ret < 0)
		goto err_ops;

	reply->length = ret;
	reply->data = page_data.data;

	ethnl_ops_complete(dev);
	return 0;

err_ops:
	ethnl_ops_complete(dev);
err_free:
	kfree(page_data.data);
	return ret;
}

static int eeprom_parse_request(struct ethnl_req_info *req_info, struct nlattr **tb,
				struct netlink_ext_ack *extack)
{
	struct eeprom_req_info *request = MODULE_EEPROM_REQINFO(req_info);

	if (!tb[ETHTOOL_A_MODULE_EEPROM_OFFSET] ||
	    !tb[ETHTOOL_A_MODULE_EEPROM_LENGTH] ||
	    !tb[ETHTOOL_A_MODULE_EEPROM_PAGE] ||
	    !tb[ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS])
		return -EINVAL;

	request->i2c_address = nla_get_u8(tb[ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS]);
	request->offset = nla_get_u32(tb[ETHTOOL_A_MODULE_EEPROM_OFFSET]);
	request->length = nla_get_u32(tb[ETHTOOL_A_MODULE_EEPROM_LENGTH]);

	if (!request->length)
		return -EINVAL;

	/* The following set of conditions limit the API to only dump 1/2
	 * EEPROM page without crossing low page boundary located at offset 128.
	 * This means user may only request dumps of length limited to 128 from
	 * either low 128 bytes or high 128 bytes.
	 * For pages higher than 0 only high 128 bytes are accessible.
	 */
	request->page = nla_get_u8(tb[ETHTOOL_A_MODULE_EEPROM_PAGE]);
	if (request->page && request->offset < ETH_MODULE_EEPROM_PAGE_LEN) {
		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_PAGE],
				    "reading from lower half page is allowed for page 0 only");
		return -EINVAL;
	}

	if (request->offset < ETH_MODULE_EEPROM_PAGE_LEN &&
	    request->offset + request->length > ETH_MODULE_EEPROM_PAGE_LEN) {
		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_LENGTH],
				    "reading cross half page boundary is illegal");
		return -EINVAL;
	} else if (request->offset >= ETH_MODULE_EEPROM_PAGE_LEN * 2) {
		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_OFFSET],
				    "offset is out of bounds");
		return -EINVAL;
	} else if (request->offset + request->length > ETH_MODULE_EEPROM_PAGE_LEN * 2) {
		NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_LENGTH],
				    "reading cross page boundary is illegal");
		return -EINVAL;
	}

	if (tb[ETHTOOL_A_MODULE_EEPROM_BANK])
		request->bank = nla_get_u8(tb[ETHTOOL_A_MODULE_EEPROM_BANK]);

	return 0;
}

static int eeprom_reply_size(const struct ethnl_req_info *req_base,
			     const struct ethnl_reply_data *reply_base)
{
	const struct eeprom_req_info *request = MODULE_EEPROM_REQINFO(req_base);

	return nla_total_size(sizeof(u8) * request->length); /* _EEPROM_DATA */
}

static int eeprom_fill_reply(struct sk_buff *skb,
			     const struct ethnl_req_info *req_base,
			     const struct ethnl_reply_data *reply_base)
{
	struct eeprom_reply_data *reply = MODULE_EEPROM_REPDATA(reply_base);

	return nla_put(skb, ETHTOOL_A_MODULE_EEPROM_DATA, reply->length, reply->data);
}

static void eeprom_cleanup_data(struct ethnl_reply_data *reply_base)
{
	struct eeprom_reply_data *reply = MODULE_EEPROM_REPDATA(reply_base);

	kfree(reply->data);
}

const struct ethnl_request_ops ethnl_module_eeprom_request_ops = {
	.request_cmd		= ETHTOOL_MSG_MODULE_EEPROM_GET,
	.reply_cmd		= ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY,
	.hdr_attr		= ETHTOOL_A_MODULE_EEPROM_HEADER,
	.req_info_size		= sizeof(struct eeprom_req_info),
	.reply_data_size	= sizeof(struct eeprom_reply_data),

	.parse_request		= eeprom_parse_request,
	.prepare_data		= eeprom_prepare_data,
	.reply_size		= eeprom_reply_size,
	.fill_reply		= eeprom_fill_reply,
	.cleanup_data		= eeprom_cleanup_data,
};

const struct nla_policy ethnl_module_eeprom_get_policy[] = {
	[ETHTOOL_A_MODULE_EEPROM_HEADER]	= NLA_POLICY_NESTED(ethnl_header_policy),
	[ETHTOOL_A_MODULE_EEPROM_OFFSET]	= { .type = NLA_U32 },
	[ETHTOOL_A_MODULE_EEPROM_LENGTH]	= { .type = NLA_U32 },
	[ETHTOOL_A_MODULE_EEPROM_PAGE]		= { .type = NLA_U8 },
	[ETHTOOL_A_MODULE_EEPROM_BANK]		= { .type = NLA_U8 },
	[ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS]	=
		NLA_POLICY_RANGE(NLA_U8, 0, ETH_MODULE_MAX_I2C_ADDRESS),
};
Loading