Skip to content

Commit c781ff1

Browse files
Hoommusdavem330
authored andcommitted
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: Vladyslav Tarasiuk <vladyslavt@nvidia.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent cbd3125 commit c781ff1

File tree

7 files changed

+270
-4
lines changed

7 files changed

+270
-4
lines changed

Documentation/networking/ethtool-netlink.rst

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,38 @@ in an implementation specific way.
13381338
``ETHTOOL_A_FEC_AUTO`` requests the driver to choose FEC mode based on SFP
13391339
module parameters. This does not mean autonegotiation.
13401340

1341+
MODULE_EEPROM
1342+
=============
1343+
1344+
Fetch module EEPROM data dump.
1345+
This interface is designed to allow dumps of at most 1/2 page at once. This
1346+
means only dumps of 128 (or less) bytes are allowed, without crossing half page
1347+
boundary located at offset 128. For pages other than 0 only high 128 bytes are
1348+
accessible.
1349+
1350+
Request contents:
1351+
1352+
======================================= ====== ==========================
1353+
``ETHTOOL_A_MODULE_EEPROM_HEADER`` nested request header
1354+
``ETHTOOL_A_MODULE_EEPROM_OFFSET`` u32 offset within a page
1355+
``ETHTOOL_A_MODULE_EEPROM_LENGTH`` u32 amount of bytes to read
1356+
``ETHTOOL_A_MODULE_EEPROM_PAGE`` u8 page number
1357+
``ETHTOOL_A_MODULE_EEPROM_BANK`` u8 bank number
1358+
``ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS`` u8 page I2C address
1359+
======================================= ====== ==========================
1360+
1361+
Kernel response contents:
1362+
1363+
+---------------------------------------------+--------+---------------------+
1364+
| ``ETHTOOL_A_MODULE_EEPROM_HEADER`` | nested | reply header |
1365+
+---------------------------------------------+--------+---------------------+
1366+
| ``ETHTOOL_A_MODULE_EEPROM_DATA`` | nested | array of bytes from |
1367+
| | | module EEPROM |
1368+
+---------------------------------------------+--------+---------------------+
1369+
1370+
``ETHTOOL_A_MODULE_EEPROM_DATA`` has an attribute length equal to the amount of
1371+
bytes driver actually read.
1372+
13411373
Request translation
13421374
===================
13431375

@@ -1415,8 +1447,8 @@ are netlink only.
14151447
``ETHTOOL_GET_DUMP_FLAG`` n/a
14161448
``ETHTOOL_GET_DUMP_DATA`` n/a
14171449
``ETHTOOL_GET_TS_INFO`` ``ETHTOOL_MSG_TSINFO_GET``
1418-
``ETHTOOL_GMODULEINFO`` n/a
1419-
``ETHTOOL_GMODULEEEPROM`` n/a
1450+
``ETHTOOL_GMODULEINFO`` ``ETHTOOL_MSG_MODULE_EEPROM_GET``
1451+
``ETHTOOL_GMODULEEEPROM`` ``ETHTOOL_MSG_MODULE_EEPROM_GET``
14201452
``ETHTOOL_GEEE`` ``ETHTOOL_MSG_EEE_GET``
14211453
``ETHTOOL_SEEE`` ``ETHTOOL_MSG_EEE_SET``
14221454
``ETHTOOL_GRSSH`` n/a

include/linux/ethtool.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ enum {
8181
#define ETH_RSS_HASH_NO_CHANGE 0
8282

8383
struct net_device;
84+
struct netlink_ext_ack;
8485

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

266+
#define ETH_MODULE_EEPROM_PAGE_LEN 128
267+
#define ETH_MODULE_MAX_I2C_ADDRESS 0x7f
268+
269+
/**
270+
* struct ethtool_module_eeprom - EEPROM dump from specified page
271+
* @offset: Offset within the specified EEPROM page to begin read, in bytes.
272+
* @length: Number of bytes to read.
273+
* @page: Page number to read from.
274+
* @bank: Page bank number to read from, if applicable by EEPROM spec.
275+
* @i2c_address: I2C address of a page. Value less than 0x7f expected. Most
276+
* EEPROMs use 0x50 or 0x51.
277+
* @data: Pointer to buffer with EEPROM data of @length size.
278+
*
279+
* This can be used to manage pages during EEPROM dump in ethtool and pass
280+
* required information to the driver.
281+
*/
282+
struct ethtool_module_eeprom {
283+
__u32 offset;
284+
__u32 length;
285+
__u8 page;
286+
__u8 bank;
287+
__u8 i2c_address;
288+
__u8 *data;
289+
};
290+
265291
/**
266292
* struct ethtool_ops - optional netdev operations
267293
* @cap_link_lanes_supported: indicates if the driver supports lanes
@@ -414,6 +440,9 @@ struct ethtool_pause_stats {
414440
* cannot use the standard PHY library helpers.
415441
* @get_phy_tunable: Read the value of a PHY tunable.
416442
* @set_phy_tunable: Set the value of a PHY tunable.
443+
* @get_module_eeprom_by_page: Get a region of plug-in module EEPROM data from
444+
* specified page. Returns a negative error code or the amount of bytes
445+
* read.
417446
*
418447
* All operations are optional (i.e. the function pointer may be set
419448
* to %NULL) and callers must take this into account. Callers must
@@ -519,6 +548,9 @@ struct ethtool_ops {
519548
const struct ethtool_tunable *, void *);
520549
int (*set_phy_tunable)(struct net_device *,
521550
const struct ethtool_tunable *, const void *);
551+
int (*get_module_eeprom_by_page)(struct net_device *dev,
552+
const struct ethtool_module_eeprom *page,
553+
struct netlink_ext_ack *extack);
522554
};
523555

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

545-
struct netlink_ext_ack;
546577
struct phy_device;
547578
struct phy_tdr_config;
548579

include/uapi/linux/ethtool_netlink.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ enum {
4444
ETHTOOL_MSG_TUNNEL_INFO_GET,
4545
ETHTOOL_MSG_FEC_GET,
4646
ETHTOOL_MSG_FEC_SET,
47+
ETHTOOL_MSG_MODULE_EEPROM_GET,
4748

4849
/* add new constants above here */
4950
__ETHTOOL_MSG_USER_CNT,
@@ -84,6 +85,7 @@ enum {
8485
ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY,
8586
ETHTOOL_MSG_FEC_GET_REPLY,
8687
ETHTOOL_MSG_FEC_NTF,
88+
ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY,
8789

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

651+
/* MODULE EEPROM */
652+
653+
enum {
654+
ETHTOOL_A_MODULE_EEPROM_UNSPEC,
655+
ETHTOOL_A_MODULE_EEPROM_HEADER, /* nest - _A_HEADER_* */
656+
657+
ETHTOOL_A_MODULE_EEPROM_OFFSET, /* u32 */
658+
ETHTOOL_A_MODULE_EEPROM_LENGTH, /* u32 */
659+
ETHTOOL_A_MODULE_EEPROM_PAGE, /* u8 */
660+
ETHTOOL_A_MODULE_EEPROM_BANK, /* u8 */
661+
ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS, /* u8 */
662+
ETHTOOL_A_MODULE_EEPROM_DATA, /* nested */
663+
664+
__ETHTOOL_A_MODULE_EEPROM_CNT,
665+
ETHTOOL_A_MODULE_EEPROM_MAX = (__ETHTOOL_A_MODULE_EEPROM_CNT - 1)
666+
};
667+
649668
/* generic netlink info */
650669
#define ETHTOOL_GENL_NAME "ethtool"
651670
#define ETHTOOL_GENL_VERSION 1

net/ethtool/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
77
ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
88
linkstate.o debug.o wol.o features.o privflags.o rings.o \
99
channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
10-
tunnels.o fec.o
10+
tunnels.o fec.o eeprom.o

net/ethtool/eeprom.c

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
3+
#include <linux/ethtool.h>
4+
#include "netlink.h"
5+
#include "common.h"
6+
7+
struct eeprom_req_info {
8+
struct ethnl_req_info base;
9+
u32 offset;
10+
u32 length;
11+
u8 page;
12+
u8 bank;
13+
u8 i2c_address;
14+
};
15+
16+
struct eeprom_reply_data {
17+
struct ethnl_reply_data base;
18+
u32 length;
19+
u8 *data;
20+
};
21+
22+
#define MODULE_EEPROM_REQINFO(__req_base) \
23+
container_of(__req_base, struct eeprom_req_info, base)
24+
25+
#define MODULE_EEPROM_REPDATA(__reply_base) \
26+
container_of(__reply_base, struct eeprom_reply_data, base)
27+
28+
static int eeprom_prepare_data(const struct ethnl_req_info *req_base,
29+
struct ethnl_reply_data *reply_base,
30+
struct genl_info *info)
31+
{
32+
struct eeprom_reply_data *reply = MODULE_EEPROM_REPDATA(reply_base);
33+
struct eeprom_req_info *request = MODULE_EEPROM_REQINFO(req_base);
34+
struct ethtool_module_eeprom page_data = {0};
35+
struct net_device *dev = reply_base->dev;
36+
int ret;
37+
38+
if (!dev->ethtool_ops->get_module_eeprom_by_page)
39+
return -EOPNOTSUPP;
40+
41+
page_data.offset = request->offset;
42+
page_data.length = request->length;
43+
page_data.i2c_address = request->i2c_address;
44+
page_data.page = request->page;
45+
page_data.bank = request->bank;
46+
page_data.data = kmalloc(page_data.length, GFP_KERNEL);
47+
if (!page_data.data)
48+
return -ENOMEM;
49+
50+
ret = ethnl_ops_begin(dev);
51+
if (ret)
52+
goto err_free;
53+
54+
ret = dev->ethtool_ops->get_module_eeprom_by_page(dev, &page_data,
55+
info->extack);
56+
if (ret < 0)
57+
goto err_ops;
58+
59+
reply->length = ret;
60+
reply->data = page_data.data;
61+
62+
ethnl_ops_complete(dev);
63+
return 0;
64+
65+
err_ops:
66+
ethnl_ops_complete(dev);
67+
err_free:
68+
kfree(page_data.data);
69+
return ret;
70+
}
71+
72+
static int eeprom_parse_request(struct ethnl_req_info *req_info, struct nlattr **tb,
73+
struct netlink_ext_ack *extack)
74+
{
75+
struct eeprom_req_info *request = MODULE_EEPROM_REQINFO(req_info);
76+
77+
if (!tb[ETHTOOL_A_MODULE_EEPROM_OFFSET] ||
78+
!tb[ETHTOOL_A_MODULE_EEPROM_LENGTH] ||
79+
!tb[ETHTOOL_A_MODULE_EEPROM_PAGE] ||
80+
!tb[ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS])
81+
return -EINVAL;
82+
83+
request->i2c_address = nla_get_u8(tb[ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS]);
84+
request->offset = nla_get_u32(tb[ETHTOOL_A_MODULE_EEPROM_OFFSET]);
85+
request->length = nla_get_u32(tb[ETHTOOL_A_MODULE_EEPROM_LENGTH]);
86+
87+
if (!request->length)
88+
return -EINVAL;
89+
90+
/* The following set of conditions limit the API to only dump 1/2
91+
* EEPROM page without crossing low page boundary located at offset 128.
92+
* This means user may only request dumps of length limited to 128 from
93+
* either low 128 bytes or high 128 bytes.
94+
* For pages higher than 0 only high 128 bytes are accessible.
95+
*/
96+
request->page = nla_get_u8(tb[ETHTOOL_A_MODULE_EEPROM_PAGE]);
97+
if (request->page && request->offset < ETH_MODULE_EEPROM_PAGE_LEN) {
98+
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_PAGE],
99+
"reading from lower half page is allowed for page 0 only");
100+
return -EINVAL;
101+
}
102+
103+
if (request->offset < ETH_MODULE_EEPROM_PAGE_LEN &&
104+
request->offset + request->length > ETH_MODULE_EEPROM_PAGE_LEN) {
105+
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_LENGTH],
106+
"reading cross half page boundary is illegal");
107+
return -EINVAL;
108+
} else if (request->offset >= ETH_MODULE_EEPROM_PAGE_LEN * 2) {
109+
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_OFFSET],
110+
"offset is out of bounds");
111+
return -EINVAL;
112+
} else if (request->offset + request->length > ETH_MODULE_EEPROM_PAGE_LEN * 2) {
113+
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MODULE_EEPROM_LENGTH],
114+
"reading cross page boundary is illegal");
115+
return -EINVAL;
116+
}
117+
118+
if (tb[ETHTOOL_A_MODULE_EEPROM_BANK])
119+
request->bank = nla_get_u8(tb[ETHTOOL_A_MODULE_EEPROM_BANK]);
120+
121+
return 0;
122+
}
123+
124+
static int eeprom_reply_size(const struct ethnl_req_info *req_base,
125+
const struct ethnl_reply_data *reply_base)
126+
{
127+
const struct eeprom_req_info *request = MODULE_EEPROM_REQINFO(req_base);
128+
129+
return nla_total_size(sizeof(u8) * request->length); /* _EEPROM_DATA */
130+
}
131+
132+
static int eeprom_fill_reply(struct sk_buff *skb,
133+
const struct ethnl_req_info *req_base,
134+
const struct ethnl_reply_data *reply_base)
135+
{
136+
struct eeprom_reply_data *reply = MODULE_EEPROM_REPDATA(reply_base);
137+
138+
return nla_put(skb, ETHTOOL_A_MODULE_EEPROM_DATA, reply->length, reply->data);
139+
}
140+
141+
static void eeprom_cleanup_data(struct ethnl_reply_data *reply_base)
142+
{
143+
struct eeprom_reply_data *reply = MODULE_EEPROM_REPDATA(reply_base);
144+
145+
kfree(reply->data);
146+
}
147+
148+
const struct ethnl_request_ops ethnl_module_eeprom_request_ops = {
149+
.request_cmd = ETHTOOL_MSG_MODULE_EEPROM_GET,
150+
.reply_cmd = ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY,
151+
.hdr_attr = ETHTOOL_A_MODULE_EEPROM_HEADER,
152+
.req_info_size = sizeof(struct eeprom_req_info),
153+
.reply_data_size = sizeof(struct eeprom_reply_data),
154+
155+
.parse_request = eeprom_parse_request,
156+
.prepare_data = eeprom_prepare_data,
157+
.reply_size = eeprom_reply_size,
158+
.fill_reply = eeprom_fill_reply,
159+
.cleanup_data = eeprom_cleanup_data,
160+
};
161+
162+
const struct nla_policy ethnl_module_eeprom_get_policy[] = {
163+
[ETHTOOL_A_MODULE_EEPROM_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
164+
[ETHTOOL_A_MODULE_EEPROM_OFFSET] = { .type = NLA_U32 },
165+
[ETHTOOL_A_MODULE_EEPROM_LENGTH] = { .type = NLA_U32 },
166+
[ETHTOOL_A_MODULE_EEPROM_PAGE] = { .type = NLA_U8 },
167+
[ETHTOOL_A_MODULE_EEPROM_BANK] = { .type = NLA_U8 },
168+
[ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS] =
169+
NLA_POLICY_RANGE(NLA_U8, 0, ETH_MODULE_MAX_I2C_ADDRESS),
170+
};
171+

net/ethtool/netlink.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
246246
[ETHTOOL_MSG_EEE_GET] = &ethnl_eee_request_ops,
247247
[ETHTOOL_MSG_FEC_GET] = &ethnl_fec_request_ops,
248248
[ETHTOOL_MSG_TSINFO_GET] = &ethnl_tsinfo_request_ops,
249+
[ETHTOOL_MSG_MODULE_EEPROM_GET] = &ethnl_module_eeprom_request_ops,
249250
};
250251

251252
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -931,6 +932,16 @@ static const struct genl_ops ethtool_genl_ops[] = {
931932
.policy = ethnl_fec_set_policy,
932933
.maxattr = ARRAY_SIZE(ethnl_fec_set_policy) - 1,
933934
},
935+
{
936+
.cmd = ETHTOOL_MSG_MODULE_EEPROM_GET,
937+
.flags = GENL_UNS_ADMIN_PERM,
938+
.doit = ethnl_default_doit,
939+
.start = ethnl_default_start,
940+
.dumpit = ethnl_default_dumpit,
941+
.done = ethnl_default_done,
942+
.policy = ethnl_module_eeprom_get_policy,
943+
.maxattr = ARRAY_SIZE(ethnl_module_eeprom_get_policy) - 1,
944+
},
934945
};
935946

936947
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {

net/ethtool/netlink.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ extern const struct ethnl_request_ops ethnl_pause_request_ops;
345345
extern const struct ethnl_request_ops ethnl_eee_request_ops;
346346
extern const struct ethnl_request_ops ethnl_tsinfo_request_ops;
347347
extern const struct ethnl_request_ops ethnl_fec_request_ops;
348+
extern const struct ethnl_request_ops ethnl_module_eeprom_request_ops;
348349

349350
extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];
350351
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
378379
extern const struct nla_policy ethnl_tunnel_info_get_policy[ETHTOOL_A_TUNNEL_INFO_HEADER + 1];
379380
extern const struct nla_policy ethnl_fec_get_policy[ETHTOOL_A_FEC_HEADER + 1];
380381
extern const struct nla_policy ethnl_fec_set_policy[ETHTOOL_A_FEC_AUTO + 1];
382+
extern const struct nla_policy ethnl_module_eeprom_get_policy[ETHTOOL_A_MODULE_EEPROM_DATA + 1];
381383

382384
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
383385
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);

0 commit comments

Comments
 (0)