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