summaryrefslogtreecommitdiffstats
path: root/netlink/module-eeprom.c
blob: fe02c5ab2b65f56f6e07baa492b862350313da70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/*
 * module-eeprom.c - netlink implementation of module eeprom get command
 *
 * ethtool -m <dev>
 */

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>

#include "../sff-common.h"
#include "../qsfp.h"
#include "../cmis.h"
#include "../internal.h"
#include "../common.h"
#include "../list.h"
#include "netlink.h"
#include "parser.h"

#define ETH_I2C_ADDRESS_LOW	0x50
#define ETH_I2C_MAX_ADDRESS	0x7F

struct cmd_params {
	u8 dump_hex;
	u8 dump_raw;
	u32 offset;
	u32 length;
	u32 page;
	u32 bank;
	u32 i2c_address;
};

static const struct param_parser getmodule_params[] = {
	{
		.arg		= "hex",
		.handler	= nl_parse_u8bool,
		.dest_offset	= offsetof(struct cmd_params, dump_hex),
		.min_argc	= 1,
	},
	{
		.arg		= "raw",
		.handler	= nl_parse_u8bool,
		.dest_offset	= offsetof(struct cmd_params, dump_raw),
		.min_argc	= 1,
	},
	{
		.arg		= "offset",
		.handler	= nl_parse_direct_u32,
		.dest_offset	= offsetof(struct cmd_params, offset),
		.min_argc	= 1,
	},
	{
		.arg		= "length",
		.handler	= nl_parse_direct_u32,
		.dest_offset	= offsetof(struct cmd_params, length),
		.min_argc	= 1,
	},
	{
		.arg		= "page",
		.handler	= nl_parse_direct_u32,
		.dest_offset	= offsetof(struct cmd_params, page),
		.min_argc	= 1,
	},
	{
		.arg		= "bank",
		.handler	= nl_parse_direct_u32,
		.dest_offset	= offsetof(struct cmd_params, bank),
		.min_argc	= 1,
	},
	{
		.arg		= "i2c",
		.handler	= nl_parse_direct_u32,
		.dest_offset	= offsetof(struct cmd_params, i2c_address),
		.min_argc	= 1,
	},
	{}
};

static struct list_head eeprom_page_list = LIST_HEAD_INIT(eeprom_page_list);

struct eeprom_page_entry {
	struct list_head list;	/* Member of eeprom_page_list */
	void *data;
};

static int eeprom_page_list_add(void *data)
{
	struct eeprom_page_entry *entry;

	entry = malloc(sizeof(*entry));
	if (!entry)
		return -ENOMEM;

	entry->data = data;
	list_add(&entry->list, &eeprom_page_list);

	return 0;
}

static void eeprom_page_list_flush(void)
{
	struct eeprom_page_entry *entry;
	struct list_head *head, *next;

	list_for_each_safe(head, next, &eeprom_page_list) {
		entry = (struct eeprom_page_entry *) head;
		free(entry->data);
		list_del(head);
		free(entry);
	}
}

static int get_eeprom_page_reply_cb(const struct nlmsghdr *nlhdr, void *data)
{
	const struct nlattr *tb[ETHTOOL_A_MODULE_EEPROM_DATA + 1] = {};
	struct ethtool_module_eeprom *request = data;
	DECLARE_ATTR_TB_INFO(tb);
	u8 *eeprom_data;
	int ret;

	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
	if (ret < 0)
		return ret;

	if (!tb[ETHTOOL_A_MODULE_EEPROM_DATA])
		return MNL_CB_ERROR;

	eeprom_data = mnl_attr_get_payload(tb[ETHTOOL_A_MODULE_EEPROM_DATA]);
	request->data = malloc(request->length);
	if (!request->data)
		return MNL_CB_ERROR;
	memcpy(request->data, eeprom_data, request->length);

	ret = eeprom_page_list_add(request->data);
	if (ret < 0)
		goto err_list_add;

	return MNL_CB_OK;

err_list_add:
	free(request->data);
	return MNL_CB_ERROR;
}

int nl_get_eeprom_page(struct cmd_context *ctx,
		       struct ethtool_module_eeprom *request)
{
	struct nl_context *nlctx = ctx->nlctx;
	struct nl_socket *nlsock;
	struct nl_msg_buff *msg;
	int ret;

	if (!request || request->i2c_address > ETH_I2C_MAX_ADDRESS)
		return -EINVAL;

	nlsock = nlctx->ethnl_socket;
	msg = &nlsock->msgbuff;

	ret = nlsock_prep_get_request(nlsock, ETHTOOL_MSG_MODULE_EEPROM_GET,
				      ETHTOOL_A_MODULE_EEPROM_HEADER, 0);
	if (ret < 0)
		return ret;

	if (ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_LENGTH,
			   request->length) ||
	    ethnla_put_u32(msg, ETHTOOL_A_MODULE_EEPROM_OFFSET,
			   request->offset) ||
	    ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_PAGE,
			  request->page) ||
	    ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_BANK,
			  request->bank) ||
	    ethnla_put_u8(msg, ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS,
			  request->i2c_address))
		return -EMSGSIZE;

	ret = nlsock_sendmsg(nlsock, NULL);
	if (ret < 0)
		return ret;
	return nlsock_process_reply(nlsock, get_eeprom_page_reply_cb,
				    (void *)request);
}

static int eeprom_dump_hex(struct cmd_context *ctx)
{
	struct ethtool_module_eeprom request = {
		.length = 128,
		.i2c_address = ETH_I2C_ADDRESS_LOW,
	};
	int ret;

	ret = nl_get_eeprom_page(ctx, &request);
	if (ret < 0)
		return ret;

	dump_hex(stdout, request.data, request.length, request.offset);

	return 0;
}

static int eeprom_parse(struct cmd_context *ctx)
{
	struct ethtool_module_eeprom request = {
		.length = 1,
		.i2c_address = ETH_I2C_ADDRESS_LOW,
	};
	int ret;

	/* Fetch the SFF-8024 Identifier Value. For all supported standards, it
	 * is located at I2C address 0x50, byte 0. See section 4.1 in SFF-8024,
	 * revision 4.9.
	 */
	ret = nl_get_eeprom_page(ctx, &request);
	if (ret < 0)
		return ret;

	switch (request.data[0]) {
#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
	case SFF8024_ID_GBIC:
	case SFF8024_ID_SOLDERED_MODULE:
	case SFF8024_ID_SFP:
		return sff8079_show_all_nl(ctx);
	case SFF8024_ID_QSFP:
	case SFF8024_ID_QSFP28:
	case SFF8024_ID_QSFP_PLUS:
		return sff8636_show_all_nl(ctx);
	case SFF8024_ID_QSFP_DD:
	case SFF8024_ID_OSFP:
	case SFF8024_ID_DSFP:
	case SFF8024_ID_QSFP_PLUS_CMIS:
	case SFF8024_ID_SFP_DD_CMIS:
	case SFF8024_ID_SFP_PLUS_CMIS:
		return cmis_show_all_nl(ctx);
#endif
	default:
		/* If we cannot recognize the memory map, default to dumping
		 * the first 128 bytes in hex.
		 */
		return eeprom_dump_hex(ctx);
	}
}

int nl_getmodule(struct cmd_context *ctx)
{
	struct cmd_params getmodule_cmd_params = {};
	struct ethtool_module_eeprom request = {0};
	struct nl_context *nlctx = ctx->nlctx;
	int ret;

	if (netlink_cmd_check(ctx, ETHTOOL_MSG_MODULE_EEPROM_GET, false))
		return -EOPNOTSUPP;

	nlctx->cmd = "-m";
	nlctx->argp = ctx->argp;
	nlctx->argc = ctx->argc;
	nlctx->devname = ctx->devname;
	ret = nl_parser(nlctx, getmodule_params, &getmodule_cmd_params, PARSER_GROUP_NONE, NULL);
	if (ret < 0)
		return ret;

	if (getmodule_cmd_params.dump_hex && getmodule_cmd_params.dump_raw) {
		fprintf(stderr, "Hex and raw dump cannot be specified together\n");
		return -EINVAL;
	}

	/* When complete hex/raw dump of the EEPROM is requested, fallback to
	 * ioctl. Netlink can only request specific pages.
	 */
	if ((getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) &&
	    !getmodule_cmd_params.page && !getmodule_cmd_params.bank &&
	    !getmodule_cmd_params.i2c_address) {
		nlctx->ioctl_fallback = true;
		return -EOPNOTSUPP;
	}

#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
	if (getmodule_cmd_params.page || getmodule_cmd_params.bank ||
	    getmodule_cmd_params.offset || getmodule_cmd_params.length)
#endif
		getmodule_cmd_params.dump_hex = true;

	request.offset = getmodule_cmd_params.offset;
	request.length = getmodule_cmd_params.length ?: 128;
	request.page = getmodule_cmd_params.page;
	request.bank = getmodule_cmd_params.bank;
	request.i2c_address = getmodule_cmd_params.i2c_address ?: ETH_I2C_ADDRESS_LOW;

	if (request.page && !request.offset)
		request.offset = 128;

	if (getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) {
		ret = nl_get_eeprom_page(ctx, &request);
		if (ret < 0)
			goto cleanup;

		if (getmodule_cmd_params.dump_raw)
			fwrite(request.data, 1, request.length, stdout);
		else
			dump_hex(stdout, request.data, request.length,
				 request.offset);
	} else {
		ret = eeprom_parse(ctx);
		if (ret < 0)
			goto cleanup;
	}

cleanup:
	eeprom_page_list_flush();
	return ret;
}