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