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