Commit 7dc85b59 authored by David S. Miller's avatar David S. Miller

Merge branch 'ethtool-eeprom'

Moshe Shemesh says:

====================
ethtool: Extend module EEPROM dump API

Ethtool supports module EEPROM dumps via the `ethtool -m <dev>` command.
But in current state its functionality is limited - offset and length
parameters, which are used to specify a linear desired region of EEPROM
data to dump, is not enough, considering emergence of complex module
EEPROM layouts such as CMIS 4.0.
Moreover, CMIS 4.0 extends the amount of pages that may be accessible by
introducing another parameter for page addressing - banks.

Besides, currently module EEPROM is represented as a chunk of
concatenated pages, where lower 128 bytes of all pages, except page 00h,
are omitted. Offset and length are used to address parts of this fake
linear memory. But in practice drivers, which implement
get_module_info() and get_module_eeprom() ethtool ops still calculate
page number and set I2C address on their own.

This series tackles these issues by adding ethtool op, which allows to
pass page number, bank number and I2C address in addition to offset and
length parameters to the driver, adds corresponding netlink
infrastructure and implements the new interface in mlx5 driver.

This allows to extend userspace 'ethtool -m' CLI by adding new
parameters - page, bank and i2c. New command line format:
 ethtool -m <dev> [hex on|off] [raw on|off] [offset N] [length N] [page N] [bank N] [i2c N]

The consequence of this series is a possibility to dump arbitrary EEPROM
page at a time, in contrast to dumps of concatenated pages. Therefore,
offset and length change their semantics and may be used only to specify
a part of data within half page boundary, which size is currently limited
to 128 bytes.

As for drivers that support legacy get_module_info() and
get_module_eeprom() pair, the series addresses it by implementing a
fallback mechanism. As mentioned earlier, such drivers derive a page
number from 'global' offset, so this can be done vice versa without
their involvement thanks to standardization. If kernel netlink handler
of 'ethtool -m' command detects that new ethtool op is not supported by
the driver, it calculates offset from given page number and page offset
and calls old ndos, if they are available.
====================

\Signed-off-by: David S. Miller <davem@davemloft.net>
parents cbd31253 c97a31f6
......@@ -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
......
......@@ -1770,6 +1770,49 @@ static int mlx5e_get_module_eeprom(struct net_device *netdev,
return 0;
}
static int mlx5e_get_module_eeprom_by_page(struct net_device *netdev,
const struct ethtool_module_eeprom *page_data,
struct netlink_ext_ack *extack)
{
struct mlx5e_priv *priv = netdev_priv(netdev);
struct mlx5_module_eeprom_query_params query;
struct mlx5_core_dev *mdev = priv->mdev;
u8 *data = page_data->data;
int size_read;
int i = 0;
if (!page_data->length)
return -EINVAL;
memset(data, 0, page_data->length);
query.offset = page_data->offset;
query.i2c_address = page_data->i2c_address;
query.bank = page_data->bank;
query.page = page_data->page;
while (i < page_data->length) {
query.size = page_data->length - i;
size_read = mlx5_query_module_eeprom_by_page(mdev, &query, data + i);
/* Done reading, return how many bytes was read */
if (!size_read)
return i;
if (size_read == -EINVAL)
return -EINVAL;
if (size_read < 0) {
netdev_err(priv->netdev, "%s: mlx5_query_module_eeprom_by_page failed:0x%x\n",
__func__, size_read);
return i;
}
i += size_read;
query.offset += size_read;
}
return i;
}
int mlx5e_ethtool_flash_device(struct mlx5e_priv *priv,
struct ethtool_flash *flash)
{
......@@ -2159,6 +2202,7 @@ const struct ethtool_ops mlx5e_ethtool_ops = {
.set_wol = mlx5e_set_wol,
.get_module_info = mlx5e_get_module_info,
.get_module_eeprom = mlx5e_get_module_eeprom,
.get_module_eeprom_by_page = mlx5e_get_module_eeprom_by_page,
.flash_device = mlx5e_flash_device,
.get_priv_flags = mlx5e_get_priv_flags,
.set_priv_flags = mlx5e_set_priv_flags,
......
......@@ -353,69 +353,123 @@ static void mlx5_sfp_eeprom_params_set(u16 *i2c_addr, int *page_num, u16 *offset
*offset -= MLX5_EEPROM_PAGE_LENGTH;
}
int mlx5_query_module_eeprom(struct mlx5_core_dev *dev,
u16 offset, u16 size, u8 *data)
static int mlx5_query_mcia(struct mlx5_core_dev *dev,
struct mlx5_module_eeprom_query_params *params, u8 *data)
{
int module_num, status, err, page_num = 0;
u32 in[MLX5_ST_SZ_DW(mcia_reg)] = {};
u32 out[MLX5_ST_SZ_DW(mcia_reg)];
u16 i2c_addr = 0;
u8 module_id;
int status, err;
void *ptr;
u16 size;
err = mlx5_query_module_num(dev, &module_num);
size = min_t(int, params->size, MLX5_EEPROM_MAX_BYTES);
MLX5_SET(mcia_reg, in, l, 0);
MLX5_SET(mcia_reg, in, size, size);
MLX5_SET(mcia_reg, in, module, params->module_number);
MLX5_SET(mcia_reg, in, device_address, params->offset);
MLX5_SET(mcia_reg, in, page_number, params->page);
MLX5_SET(mcia_reg, in, i2c_device_address, params->i2c_address);
err = mlx5_core_access_reg(dev, in, sizeof(in), out,
sizeof(out), MLX5_REG_MCIA, 0, 0);
if (err)
return err;
err = mlx5_query_module_id(dev, module_num, &module_id);
status = MLX5_GET(mcia_reg, out, status);
if (status) {
mlx5_core_err(dev, "query_mcia_reg failed: status: 0x%x\n",
status);
return -EIO;
}
ptr = MLX5_ADDR_OF(mcia_reg, out, dword_0);
memcpy(data, ptr, size);
return size;
}
int mlx5_query_module_eeprom(struct mlx5_core_dev *dev,
u16 offset, u16 size, u8 *data)
{
struct mlx5_module_eeprom_query_params query = {0};
u8 module_id;
int err;
err = mlx5_query_module_num(dev, &query.module_number);
if (err)
return err;
err = mlx5_query_module_id(dev, query.module_number, &module_id);
if (err)
return err;
switch (module_id) {
case MLX5_MODULE_ID_SFP:
mlx5_sfp_eeprom_params_set(&i2c_addr, &page_num, &offset);
mlx5_sfp_eeprom_params_set(&query.i2c_address, &query.page, &query.offset);
break;
case MLX5_MODULE_ID_QSFP:
case MLX5_MODULE_ID_QSFP_PLUS:
case MLX5_MODULE_ID_QSFP28:
mlx5_qsfp_eeprom_params_set(&i2c_addr, &page_num, &offset);
mlx5_qsfp_eeprom_params_set(&query.i2c_address, &query.page, &query.offset);
break;
default:
mlx5_core_err(dev, "Module ID not recognized: 0x%x\n", module_id);
return -EINVAL;
}
if (offset + size > MLX5_EEPROM_PAGE_LENGTH)
if (query.offset + size > MLX5_EEPROM_PAGE_LENGTH)
/* Cross pages read, read until offset 256 in low page */
size -= offset + size - MLX5_EEPROM_PAGE_LENGTH;
size = min_t(int, size, MLX5_EEPROM_MAX_BYTES);
query.size = size;
MLX5_SET(mcia_reg, in, l, 0);
MLX5_SET(mcia_reg, in, module, module_num);
MLX5_SET(mcia_reg, in, i2c_device_address, i2c_addr);
MLX5_SET(mcia_reg, in, page_number, page_num);
MLX5_SET(mcia_reg, in, device_address, offset);
MLX5_SET(mcia_reg, in, size, size);
return mlx5_query_mcia(dev, &query, data);
}
EXPORT_SYMBOL_GPL(mlx5_query_module_eeprom);
err = mlx5_core_access_reg(dev, in, sizeof(in), out,
sizeof(out), MLX5_REG_MCIA, 0, 0);
int mlx5_query_module_eeprom_by_page(struct mlx5_core_dev *dev,
struct mlx5_module_eeprom_query_params *params,
u8 *data)
{
u8 module_id;
int err;
err = mlx5_query_module_num(dev, &params->module_number);
if (err)
return err;
status = MLX5_GET(mcia_reg, out, status);
if (status) {
mlx5_core_err(dev, "query_mcia_reg failed: status: 0x%x\n",
status);
return -EIO;
err = mlx5_query_module_id(dev, params->module_number, &module_id);
if (err)
return err;
switch (module_id) {
case MLX5_MODULE_ID_SFP:
if (params->page > 0)
return -EINVAL;
break;
case MLX5_MODULE_ID_QSFP:
case MLX5_MODULE_ID_QSFP28:
case MLX5_MODULE_ID_QSFP_PLUS:
if (params->page > 3)
return -EINVAL;
break;
case MLX5_MODULE_ID_DSFP:
break;
default:
mlx5_core_err(dev, "Module ID not recognized: 0x%x\n", module_id);
return -EINVAL;
}
ptr = MLX5_ADDR_OF(mcia_reg, out, dword_0);
memcpy(data, ptr, size);
if (params->i2c_address != MLX5_I2C_ADDR_HIGH &&
params->i2c_address != MLX5_I2C_ADDR_LOW) {
mlx5_core_err(dev, "I2C address not recognized: 0x%x\n", params->i2c_address);
return -EINVAL;
}
return size;
return mlx5_query_mcia(dev, params, data);
}
EXPORT_SYMBOL_GPL(mlx5_query_module_eeprom);
EXPORT_SYMBOL_GPL(mlx5_query_module_eeprom_by_page);
static int mlx5_query_port_pvlc(struct mlx5_core_dev *dev, u32 *pvlc,
int pvlc_size, u8 local_port)
......
......@@ -555,6 +555,26 @@ int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
}
EXPORT_SYMBOL_GPL(sfp_get_module_eeprom);
/**
* sfp_get_module_eeprom_by_page() - Read a page from the SFP module EEPROM
* @bus: a pointer to the &struct sfp_bus structure for the sfp module
* @page: a &struct ethtool_module_eeprom
* @extack: extack for reporting problems
*
* Read an EEPROM page as specified by the supplied @page. See the
* documentation for &struct ethtool_module_eeprom for the page to be read.
*
* Returns 0 on success or a negative errno number. More error
* information might be provided via extack
*/
int sfp_get_module_eeprom_by_page(struct sfp_bus *bus,
const struct ethtool_module_eeprom *page,
struct netlink_ext_ack *extack)
{
return bus->socket_ops->module_eeprom_by_page(bus->sfp, page, extack);
}
EXPORT_SYMBOL_GPL(sfp_get_module_eeprom_by_page);
/**
* sfp_upstream_start() - Inform the SFP that the network device is up
* @bus: a pointer to the &struct sfp_bus structure for the sfp module
......
......@@ -2330,6 +2330,30 @@ static int sfp_module_eeprom(struct sfp *sfp, struct ethtool_eeprom *ee,
return 0;
}
static int sfp_module_eeprom_by_page(struct sfp *sfp,
const struct ethtool_module_eeprom *page,
struct netlink_ext_ack *extack)
{
if (page->bank) {
NL_SET_ERR_MSG(extack, "Banks not supported");
return -EOPNOTSUPP;
}
if (page->page) {
NL_SET_ERR_MSG(extack, "Only page 0 supported");
return -EOPNOTSUPP;
}
if (page->i2c_address != 0x50 &&
page->i2c_address != 0x51) {
NL_SET_ERR_MSG(extack, "Only address 0x50 and 0x51 supported");
return -EOPNOTSUPP;
}
return sfp_read(sfp, page->i2c_address == 0x51, page->offset,
page->data, page->length);
};
static const struct sfp_socket_ops sfp_module_ops = {
.attach = sfp_attach,
.detach = sfp_detach,
......@@ -2337,6 +2361,7 @@ static const struct sfp_socket_ops sfp_module_ops = {
.stop = sfp_stop,
.module_info = sfp_module_info,
.module_eeprom = sfp_module_eeprom,
.module_eeprom_by_page = sfp_module_eeprom_by_page,
};
static void sfp_timeout(struct work_struct *work)
......
......@@ -14,6 +14,9 @@ struct sfp_socket_ops {
int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo);
int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee,
u8 *data);
int (*module_eeprom_by_page)(struct sfp *sfp,
const struct ethtool_module_eeprom *page,
struct netlink_ext_ack *extack);
};
int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev);
......
......@@ -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;
......
......@@ -45,6 +45,7 @@ enum mlx5_module_id {
MLX5_MODULE_ID_QSFP = 0xC,
MLX5_MODULE_ID_QSFP_PLUS = 0xD,
MLX5_MODULE_ID_QSFP28 = 0x11,
MLX5_MODULE_ID_DSFP = 0x1B,
};
enum mlx5_an_status {
......@@ -62,6 +63,15 @@ enum mlx5_an_status {
#define MLX5_EEPROM_PAGE_LENGTH 256
#define MLX5_EEPROM_HIGH_PAGE_LENGTH 128
struct mlx5_module_eeprom_query_params {
u16 size;
u16 offset;
u16 i2c_address;
u32 page;
u32 bank;
u32 module_number;
};
enum mlx5e_link_mode {
MLX5E_1000BASE_CX_SGMII = 0,
MLX5E_1000BASE_KX = 1,
......@@ -200,6 +210,8 @@ void mlx5_query_port_fcs(struct mlx5_core_dev *mdev, bool *supported,
bool *enabled);
int mlx5_query_module_eeprom(struct mlx5_core_dev *dev,
u16 offset, u16 size, u8 *data);
int mlx5_query_module_eeprom_by_page(struct mlx5_core_dev *dev,
struct mlx5_module_eeprom_query_params *params, u8 *data);
int mlx5_query_port_dcbx_param(struct mlx5_core_dev *mdev, u32 *out);
int mlx5_set_port_dcbx_param(struct mlx5_core_dev *mdev, u32 *in);
......
......@@ -542,6 +542,9 @@ phy_interface_t sfp_select_interface(struct sfp_bus *bus,
int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo);
int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
u8 *data);
int sfp_get_module_eeprom_by_page(struct sfp_bus *bus,
const struct ethtool_module_eeprom *page,
struct netlink_ext_ack *extack);
void sfp_upstream_start(struct sfp_bus *bus);
void sfp_upstream_stop(struct sfp_bus *bus);
void sfp_bus_put(struct sfp_bus *bus);
......@@ -587,6 +590,13 @@ static inline int sfp_get_module_eeprom(struct sfp_bus *bus,
return -EOPNOTSUPP;
}
static inline int sfp_get_module_eeprom_by_page(struct sfp_bus *bus,
const struct ethtool_module_eeprom *page,
struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
static inline void sfp_upstream_start(struct sfp_bus *bus)
{
}
......
......@@ -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
......
......@@ -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
......@@ -47,4 +47,9 @@ int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info);
extern const struct ethtool_phy_ops *ethtool_phy_ops;
int ethtool_get_module_info_call(struct net_device *dev,
struct ethtool_modinfo *modinfo);
int ethtool_get_module_eeprom_call(struct net_device *dev,
struct ethtool_eeprom *ee, u8 *data);
#endif /* _ETHTOOL_COMMON_H */
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/ethtool.h>
#include <linux/sfp.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 fallback_set_params(struct eeprom_req_info *request,
struct ethtool_modinfo *modinfo,
struct ethtool_eeprom *eeprom)
{
u32 offset = request->offset;
u32 length = request->length;
if (request->page)
offset = request->page * ETH_MODULE_EEPROM_PAGE_LEN + offset;
if (modinfo->type == ETH_MODULE_SFF_8079 &&
request->i2c_address == 0x51)
offset += ETH_MODULE_EEPROM_PAGE_LEN * 2;
if (offset >= modinfo->eeprom_len)
return -EINVAL;
eeprom->cmd = ETHTOOL_GMODULEEEPROM;
eeprom->len = length;
eeprom->offset = offset;
return 0;
}
static int eeprom_fallback(struct eeprom_req_info *request,
struct eeprom_reply_data *reply,
struct genl_info *info)
{
struct net_device *dev = reply->base.dev;
struct ethtool_modinfo modinfo = {0};
struct ethtool_eeprom eeprom = {0};
u8 *data;
int err;
modinfo.cmd = ETHTOOL_GMODULEINFO;
err = ethtool_get_module_info_call(dev, &modinfo);
if (err < 0)
return err;
err = fallback_set_params(request, &modinfo, &eeprom);
if (err < 0)
return err;
data = kmalloc(eeprom.len, GFP_KERNEL);
if (!data)
return -ENOMEM;
err = ethtool_get_module_eeprom_call(dev, &eeprom, data);
if (err < 0)
goto err_out;
reply->data = data;
reply->length = eeprom.len;
return 0;
err_out:
kfree(data);
return err;
}
static int get_module_eeprom_by_page(struct net_device *dev,
struct ethtool_module_eeprom *page_data,
struct netlink_ext_ack *extack)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
if (dev->sfp_bus)
return sfp_get_module_eeprom_by_page(dev->sfp_bus, page_data, extack);
if (ops->get_module_info)
return ops->get_module_eeprom_by_page(dev, page_data, extack);
return -EOPNOTSUPP;
}
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;
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 = 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);
if (ret == -EOPNOTSUPP)
return eeprom_fallback(request, reply, info);
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),
};
......@@ -2188,8 +2188,8 @@ static int ethtool_get_ts_info(struct net_device *dev, void __user *useraddr)
return 0;
}
static int __ethtool_get_module_info(struct net_device *dev,
struct ethtool_modinfo *modinfo)
int ethtool_get_module_info_call(struct net_device *dev,
struct ethtool_modinfo *modinfo)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
struct phy_device *phydev = dev->phydev;
......@@ -2215,7 +2215,7 @@ static int ethtool_get_module_info(struct net_device *dev,
if (copy_from_user(&modinfo, useraddr, sizeof(modinfo)))
return -EFAULT;
ret = __ethtool_get_module_info(dev, &modinfo);
ret = ethtool_get_module_info_call(dev, &modinfo);
if (ret)
return ret;
......@@ -2225,8 +2225,8 @@ static int ethtool_get_module_info(struct net_device *dev,
return 0;
}
static int __ethtool_get_module_eeprom(struct net_device *dev,
struct ethtool_eeprom *ee, u8 *data)
int ethtool_get_module_eeprom_call(struct net_device *dev,
struct ethtool_eeprom *ee, u8 *data)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
struct phy_device *phydev = dev->phydev;
......@@ -2249,12 +2249,12 @@ static int ethtool_get_module_eeprom(struct net_device *dev,
int ret;
struct ethtool_modinfo modinfo;
ret = __ethtool_get_module_info(dev, &modinfo);
ret = ethtool_get_module_info_call(dev, &modinfo);
if (ret)
return ret;
return ethtool_get_any_eeprom(dev, useraddr,
__ethtool_get_module_eeprom,
ethtool_get_module_eeprom_call,
modinfo.eeprom_len);
}
......
......@@ -246,6 +246,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_EEE_GET] = &ethnl_eee_request_ops,
[ETHTOOL_MSG_FEC_GET] = &ethnl_fec_request_ops,
[ETHTOOL_MSG_TSINFO_GET] = &ethnl_tsinfo_request_ops,
[ETHTOOL_MSG_MODULE_EEPROM_GET] = &ethnl_module_eeprom_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
......@@ -931,6 +932,16 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_fec_set_policy,
.maxattr = ARRAY_SIZE(ethnl_fec_set_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_MODULE_EEPROM_GET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
.policy = ethnl_module_eeprom_get_policy,
.maxattr = ARRAY_SIZE(ethnl_module_eeprom_get_policy) - 1,
},
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
......
......@@ -345,6 +345,7 @@ extern const struct ethnl_request_ops ethnl_pause_request_ops;
extern const struct ethnl_request_ops ethnl_eee_request_ops;
extern const struct ethnl_request_ops ethnl_tsinfo_request_ops;
extern const struct ethnl_request_ops ethnl_fec_request_ops;
extern const struct ethnl_request_ops ethnl_module_eeprom_request_ops;
extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];
extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];
......@@ -378,6 +379,7 @@ extern const struct nla_policy ethnl_cable_test_tdr_act_policy[ETHTOOL_A_CABLE_T
extern const struct nla_policy ethnl_tunnel_info_get_policy[ETHTOOL_A_TUNNEL_INFO_HEADER + 1];
extern const struct nla_policy ethnl_fec_get_policy[ETHTOOL_A_FEC_HEADER + 1];
extern const struct nla_policy ethnl_fec_set_policy[ETHTOOL_A_FEC_AUTO + 1];
extern const struct nla_policy ethnl_module_eeprom_get_policy[ETHTOOL_A_MODULE_EEPROM_DATA + 1];
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment