xref: /aosp_15_r20/external/ethtool/netlink/module-eeprom.c (revision 1b481fc3bb1b45d4cf28d1ec12969dc1055f555d)
1 /*
2  * module-eeprom.c - netlink implementation of module eeprom get command
3  *
4  * ethtool -m <dev>
5  */
6 
7 #include <errno.h>
8 #include <string.h>
9 #include <stdio.h>
10 #include <stddef.h>
11 
12 #include "../sff-common.h"
13 #include "../qsfp.h"
14 #include "../cmis.h"
15 #include "../internal.h"
16 #include "../common.h"
17 #include "../list.h"
18 #include "netlink.h"
19 #include "parser.h"
20 
21 #define ETH_I2C_ADDRESS_LOW	0x50
22 #define ETH_I2C_MAX_ADDRESS	0x7F
23 
24 struct cmd_params {
25 	u8 dump_hex;
26 	u8 dump_raw;
27 	u32 offset;
28 	u32 length;
29 	u32 page;
30 	u32 bank;
31 	u32 i2c_address;
32 };
33 
34 static const struct param_parser getmodule_params[] = {
35 	{
36 		.arg		= "hex",
37 		.handler	= nl_parse_u8bool,
38 		.dest_offset	= offsetof(struct cmd_params, dump_hex),
39 		.min_argc	= 1,
40 	},
41 	{
42 		.arg		= "raw",
43 		.handler	= nl_parse_u8bool,
44 		.dest_offset	= offsetof(struct cmd_params, dump_raw),
45 		.min_argc	= 1,
46 	},
47 	{
48 		.arg		= "offset",
49 		.handler	= nl_parse_direct_u32,
50 		.dest_offset	= offsetof(struct cmd_params, offset),
51 		.min_argc	= 1,
52 	},
53 	{
54 		.arg		= "length",
55 		.handler	= nl_parse_direct_u32,
56 		.dest_offset	= offsetof(struct cmd_params, length),
57 		.min_argc	= 1,
58 	},
59 	{
60 		.arg		= "page",
61 		.handler	= nl_parse_direct_u32,
62 		.dest_offset	= offsetof(struct cmd_params, page),
63 		.min_argc	= 1,
64 	},
65 	{
66 		.arg		= "bank",
67 		.handler	= nl_parse_direct_u32,
68 		.dest_offset	= offsetof(struct cmd_params, bank),
69 		.min_argc	= 1,
70 	},
71 	{
72 		.arg		= "i2c",
73 		.handler	= nl_parse_direct_u32,
74 		.dest_offset	= offsetof(struct cmd_params, i2c_address),
75 		.min_argc	= 1,
76 	},
77 	{}
78 };
79 
80 static struct list_head eeprom_page_list = LIST_HEAD_INIT(eeprom_page_list);
81 
82 struct eeprom_page_entry {
83 	struct list_head list;	/* Member of eeprom_page_list */
84 	void *data;
85 };
86 
eeprom_page_list_add(void * data)87 static int eeprom_page_list_add(void *data)
88 {
89 	struct eeprom_page_entry *entry;
90 
91 	entry = malloc(sizeof(*entry));
92 	if (!entry)
93 		return -ENOMEM;
94 
95 	entry->data = data;
96 	list_add(&entry->list, &eeprom_page_list);
97 
98 	return 0;
99 }
100 
eeprom_page_list_flush(void)101 static void eeprom_page_list_flush(void)
102 {
103 	struct eeprom_page_entry *entry;
104 	struct list_head *head, *next;
105 
106 	list_for_each_safe(head, next, &eeprom_page_list) {
107 		entry = (struct eeprom_page_entry *) head;
108 		free(entry->data);
109 		list_del(head);
110 		free(entry);
111 	}
112 }
113 
get_eeprom_page_reply_cb(const struct nlmsghdr * nlhdr,void * data)114 static int get_eeprom_page_reply_cb(const struct nlmsghdr *nlhdr, void *data)
115 {
116 	const struct nlattr *tb[ETHTOOL_A_MODULE_EEPROM_DATA + 1] = {};
117 	struct ethtool_module_eeprom *request = data;
118 	DECLARE_ATTR_TB_INFO(tb);
119 	u8 *eeprom_data;
120 	int ret;
121 
122 	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
123 	if (ret < 0)
124 		return ret;
125 
126 	if (!tb[ETHTOOL_A_MODULE_EEPROM_DATA])
127 		return MNL_CB_ERROR;
128 
129 	eeprom_data = mnl_attr_get_payload(tb[ETHTOOL_A_MODULE_EEPROM_DATA]);
130 	request->data = malloc(request->length);
131 	if (!request->data)
132 		return MNL_CB_ERROR;
133 	memcpy(request->data, eeprom_data, request->length);
134 
135 	ret = eeprom_page_list_add(request->data);
136 	if (ret < 0)
137 		goto err_list_add;
138 
139 	return MNL_CB_OK;
140 
141 err_list_add:
142 	free(request->data);
143 	return MNL_CB_ERROR;
144 }
145 
nl_get_eeprom_page(struct cmd_context * ctx,struct ethtool_module_eeprom * request)146 int nl_get_eeprom_page(struct cmd_context *ctx,
147 		       struct ethtool_module_eeprom *request)
148 {
149 	struct nl_context *nlctx = ctx->nlctx;
150 	struct nl_socket *nlsock;
151 	struct nl_msg_buff *msg;
152 	int ret;
153 
154 	if (!request || request->i2c_address > ETH_I2C_MAX_ADDRESS)
155 		return -EINVAL;
156 
157 	nlsock = nlctx->ethnl_socket;
158 	msg = &nlsock->msgbuff;
159 
160 	ret = nlsock_prep_get_request(nlsock, ETHTOOL_MSG_MODULE_EEPROM_GET,
161 				      ETHTOOL_A_MODULE_EEPROM_HEADER, 0);
162 	if (ret < 0)
163 		return ret;
164 
165 	if (ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_LENGTH,
166 			   request->length) ||
167 	    ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_OFFSET,
168 			   request->offset) ||
169 	    ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_PAGE,
170 			  request->page) ||
171 	    ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_BANK,
172 			  request->bank) ||
173 	    ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS,
174 			  request->i2c_address))
175 		return -EMSGSIZE;
176 
177 	ret = nlsock_sendmsg(nlsock, NULL);
178 	if (ret < 0)
179 		return ret;
180 	return nlsock_process_reply(nlsock, get_eeprom_page_reply_cb,
181 				    (void *)request);
182 }
183 
eeprom_dump_hex(struct cmd_context * ctx)184 static int eeprom_dump_hex(struct cmd_context *ctx)
185 {
186 	struct ethtool_module_eeprom request = {
187 		.length = 128,
188 		.i2c_address = ETH_I2C_ADDRESS_LOW,
189 	};
190 	int ret;
191 
192 	ret = nl_get_eeprom_page(ctx, &request);
193 	if (ret < 0)
194 		return ret;
195 
196 	dump_hex(stdout, request.data, request.length, request.offset);
197 
198 	return 0;
199 }
200 
eeprom_parse(struct cmd_context * ctx)201 static int eeprom_parse(struct cmd_context *ctx)
202 {
203 	struct ethtool_module_eeprom request = {
204 		.length = 1,
205 		.i2c_address = ETH_I2C_ADDRESS_LOW,
206 	};
207 	int ret;
208 
209 	/* Fetch the SFF-8024 Identifier Value. For all supported standards, it
210 	 * is located at I2C address 0x50, byte 0. See section 4.1 in SFF-8024,
211 	 * revision 4.9.
212 	 */
213 	ret = nl_get_eeprom_page(ctx, &request);
214 	if (ret < 0)
215 		return ret;
216 
217 	switch (request.data[0]) {
218 #ifdef ETHTOOL_ENABLE_PRETTY_DUMP
219 	case SFF8024_ID_SFP:
220 		return sff8079_show_all_nl(ctx);
221 	case SFF8024_ID_QSFP:
222 	case SFF8024_ID_QSFP28:
223 	case SFF8024_ID_QSFP_PLUS:
224 		return sff8636_show_all_nl(ctx);
225 	case SFF8024_ID_QSFP_DD:
226 	case SFF8024_ID_OSFP:
227 	case SFF8024_ID_DSFP:
228 		return cmis_show_all_nl(ctx);
229 #endif
230 	default:
231 		/* If we cannot recognize the memory map, default to dumping
232 		 * the first 128 bytes in hex.
233 		 */
234 		return eeprom_dump_hex(ctx);
235 	}
236 }
237 
nl_getmodule(struct cmd_context * ctx)238 int nl_getmodule(struct cmd_context *ctx)
239 {
240 	struct cmd_params getmodule_cmd_params = {};
241 	struct ethtool_module_eeprom request = {0};
242 	struct nl_context *nlctx = ctx->nlctx;
243 	int ret;
244 
245 	if (netlink_cmd_check(ctx, ETHTOOL_MSG_MODULE_EEPROM_GET, false))
246 		return -EOPNOTSUPP;
247 
248 	nlctx->cmd = "-m";
249 	nlctx->argp = ctx->argp;
250 	nlctx->argc = ctx->argc;
251 	nlctx->devname = ctx->devname;
252 	ret = nl_parser(nlctx, getmodule_params, &getmodule_cmd_params, PARSER_GROUP_NONE, NULL);
253 	if (ret < 0)
254 		return ret;
255 
256 	if (getmodule_cmd_params.dump_hex && getmodule_cmd_params.dump_raw) {
257 		fprintf(stderr, "Hex and raw dump cannot be specified together\n");
258 		return -EINVAL;
259 	}
260 
261 	/* When complete hex/raw dump of the EEPROM is requested, fallback to
262 	 * ioctl. Netlink can only request specific pages.
263 	 */
264 	if ((getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) &&
265 	    !getmodule_cmd_params.page && !getmodule_cmd_params.bank &&
266 	    !getmodule_cmd_params.i2c_address) {
267 		nlctx->ioctl_fallback = true;
268 		return -EOPNOTSUPP;
269 	}
270 
271 #ifdef ETHTOOL_ENABLE_PRETTY_DUMP
272 	if (getmodule_cmd_params.page || getmodule_cmd_params.bank ||
273 	    getmodule_cmd_params.offset || getmodule_cmd_params.length)
274 #endif
275 		getmodule_cmd_params.dump_hex = true;
276 
277 	request.offset = getmodule_cmd_params.offset;
278 	request.length = getmodule_cmd_params.length ?: 128;
279 	request.page = getmodule_cmd_params.page;
280 	request.bank = getmodule_cmd_params.bank;
281 	request.i2c_address = getmodule_cmd_params.i2c_address ?: ETH_I2C_ADDRESS_LOW;
282 
283 	if (request.page && !request.offset)
284 		request.offset = 128;
285 
286 	if (getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) {
287 		ret = nl_get_eeprom_page(ctx, &request);
288 		if (ret < 0)
289 			goto cleanup;
290 
291 		if (getmodule_cmd_params.dump_raw)
292 			fwrite(request.data, 1, request.length, stdout);
293 		else
294 			dump_hex(stdout, request.data, request.length,
295 				 request.offset);
296 	} else {
297 		ret = eeprom_parse(ctx);
298 		if (ret < 0)
299 			goto cleanup;
300 	}
301 
302 cleanup:
303 	eeprom_page_list_flush();
304 	return ret;
305 }
306