From 9538f384b535635f79ecc33234b473a898993bd0 Mon Sep 17 00:00:00 2001
From: Ido Schimmel <idosch@nvidia.com>
Date: Tue, 12 Oct 2021 16:25:25 +0300
Subject: [PATCH] netlink: eeprom: Defer page requests to individual parsers

The individual EEPROM parsers (e.g., CMIS, SFF-8636) now request the
EEPROM pages they intend to parse and populate their memory maps before
parsing them.

Therefore, there is no need for the common netlink code to request
potentially invalid pages and pass them as blobs to these parsers.

Instead, only query the SFF-8024 Identifier Value which is located at
I2C address 0x50, byte 0 and dispatch to the relevant EEPROM parser.

Tested by making sure that the output of 'ethtool -m' does not change
for SFF-8079, SFF-8636 and CMIS before and after the patch.

Signed-off-by: Ido Schimmel <idosch@nvidia.com>
---
 netlink/module-eeprom.c | 347 +++++++---------------------------------
 1 file changed, 59 insertions(+), 288 deletions(-)

diff --git a/netlink/module-eeprom.c b/netlink/module-eeprom.c
index 6d76b8a..f359aee 100644
--- a/netlink/module-eeprom.c
+++ b/netlink/module-eeprom.c
@@ -19,7 +19,6 @@
 #include "parser.h"
 
 #define ETH_I2C_ADDRESS_LOW	0x50
-#define ETH_I2C_ADDRESS_HIGH	0x51
 #define ETH_I2C_MAX_ADDRESS	0x7F
 
 struct cmd_params {
@@ -78,267 +77,6 @@ static const struct param_parser getmodule_params[] = {
 	{}
 };
 
-struct page_entry {
-	struct list_head link;
-	struct ethtool_module_eeprom *page;
-};
-
-static struct list_head page_list = LIST_HEAD_INIT(page_list);
-
-static int cache_add(struct ethtool_module_eeprom *page)
-{
-	struct page_entry *list_element;
-
-	if (!page)
-		return -1;
-	list_element = malloc(sizeof(*list_element));
-	if (!list_element)
-		return -ENOMEM;
-	list_element->page = page;
-
-	list_add(&list_element->link, &page_list);
-	return 0;
-}
-
-static void page_free(struct ethtool_module_eeprom *page)
-{
-	free(page->data);
-	free(page);
-}
-
-static void cache_del(struct ethtool_module_eeprom *page)
-{
-	struct ethtool_module_eeprom *entry;
-	struct list_head *head, *next;
-
-	list_for_each_safe(head, next, &page_list) {
-		entry = ((struct page_entry *)head)->page;
-		if (entry == page) {
-			list_del(head);
-			free(head);
-			page_free(entry);
-			break;
-		}
-	}
-}
-
-static void cache_free(void)
-{
-	struct ethtool_module_eeprom *entry;
-	struct list_head *head, *next;
-
-	list_for_each_safe(head, next, &page_list) {
-		entry = ((struct page_entry *)head)->page;
-		list_del(head);
-		free(head);
-		page_free(entry);
-	}
-}
-
-static struct ethtool_module_eeprom *page_join(struct ethtool_module_eeprom *page_a,
-					       struct ethtool_module_eeprom *page_b)
-{
-	struct ethtool_module_eeprom *joined_page;
-	u32 total_length;
-
-	if (!page_a || !page_b ||
-	    page_a->page != page_b->page ||
-	    page_a->bank != page_b->bank ||
-	    page_a->i2c_address != page_b->i2c_address)
-		return NULL;
-
-	total_length = page_a->length + page_b->length;
-	joined_page = calloc(1, sizeof(*joined_page));
-	joined_page->data = calloc(1, total_length);
-	joined_page->page = page_a->page;
-	joined_page->bank = page_a->bank;
-	joined_page->length = total_length;
-	joined_page->i2c_address = page_a->i2c_address;
-
-	if (page_a->offset < page_b->offset) {
-		memcpy(joined_page->data, page_a->data, page_a->length);
-		memcpy(joined_page->data + page_a->length, page_b->data, page_b->length);
-		joined_page->offset = page_a->offset;
-	} else {
-		memcpy(joined_page->data, page_b->data, page_b->length);
-		memcpy(joined_page->data + page_b->length, page_a->data, page_a->length);
-		joined_page->offset = page_b->offset;
-	}
-
-	return joined_page;
-}
-
-static struct ethtool_module_eeprom *cache_get(u32 page, u32 bank, u8 i2c_address)
-{
-	struct ethtool_module_eeprom *entry;
-	struct list_head *head, *next;
-
-	list_for_each_safe(head, next, &page_list) {
-		entry = ((struct page_entry *)head)->page;
-		if (entry->page == page && entry->bank == bank &&
-		    entry->i2c_address == i2c_address)
-			return entry;
-	}
-
-	return NULL;
-}
-
-static int getmodule_page_fetch_reply_cb(const struct nlmsghdr *nlhdr,
-					 void *data)
-{
-	const struct nlattr *tb[ETHTOOL_A_MODULE_EEPROM_DATA + 1] = {};
-	DECLARE_ATTR_TB_INFO(tb);
-	struct ethtool_module_eeprom *lower_page;
-	struct ethtool_module_eeprom *response;
-	struct ethtool_module_eeprom *request;
-	struct ethtool_module_eeprom *joined;
-	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]) {
-		fprintf(stderr, "Malformed netlink message (getmodule)\n");
-		return MNL_CB_ERROR;
-	}
-
-	response = calloc(1, sizeof(*response));
-	if (!response)
-		return -ENOMEM;
-
-	request = (struct ethtool_module_eeprom *)data;
-	response->offset = request->offset;
-	response->page = request->page;
-	response->bank = request->bank;
-	response->i2c_address = request->i2c_address;
-	response->length = mnl_attr_get_payload_len(tb[ETHTOOL_A_MODULE_EEPROM_DATA]);
-	eeprom_data = mnl_attr_get_payload(tb[ETHTOOL_A_MODULE_EEPROM_DATA]);
-
-	response->data = malloc(response->length);
-	if (!response->data) {
-		free(response);
-		return -ENOMEM;
-	}
-	memcpy(response->data, eeprom_data, response->length);
-
-	if (!request->page) {
-		lower_page = cache_get(request->page, request->bank, response->i2c_address);
-		if (lower_page) {
-			joined = page_join(lower_page, response);
-			page_free(response);
-			cache_del(lower_page);
-			return cache_add(joined);
-		}
-	}
-
-	return cache_add(response);
-}
-
-static int page_fetch(struct nl_context *nlctx, const struct ethtool_module_eeprom *request)
-{
-	struct nl_socket *nlsock = nlctx->ethnl_socket;
-	struct nl_msg_buff *msg = &nlsock->msgbuff;
-	struct ethtool_module_eeprom *page;
-	int ret;
-
-	if (!request || request->i2c_address > ETH_I2C_MAX_ADDRESS)
-		return -EINVAL;
-
-	/* Satisfy request right away, if region is already in cache */
-	page = cache_get(request->page, request->bank, request->i2c_address);
-	if (page && page->offset <= request->offset &&
-	    page->offset + page->length >= request->offset + request->length) {
-		return 0;
-	}
-
-	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;
-	ret = nlsock_process_reply(nlsock, getmodule_page_fetch_reply_cb, (void *)request);
-	if (ret < 0)
-		return ret;
-
-	return nlsock_process_reply(nlsock, nomsg_reply_cb, NULL);
-}
-
-#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
-static int decoder_prefetch(struct nl_context *nlctx)
-{
-	struct ethtool_module_eeprom *page_zero_lower = cache_get(0, 0, ETH_I2C_ADDRESS_LOW);
-	struct ethtool_module_eeprom request = {0};
-	u8 module_id = page_zero_lower->data[0];
-	int err = 0;
-
-	/* Fetch rest of page 00 */
-	request.i2c_address = ETH_I2C_ADDRESS_LOW;
-	request.offset = 128;
-	request.length = 128;
-	err = page_fetch(nlctx, &request);
-	if (err)
-		return err;
-
-	switch (module_id) {
-	case SFF8024_ID_QSFP:
-	case SFF8024_ID_QSFP28:
-	case SFF8024_ID_QSFP_PLUS:
-		memset(&request, 0, sizeof(request));
-		request.i2c_address = ETH_I2C_ADDRESS_LOW;
-		request.offset = 128;
-		request.length = 128;
-		request.page = 3;
-		break;
-	case SFF8024_ID_QSFP_DD:
-	case SFF8024_ID_DSFP:
-		memset(&request, 0, sizeof(request));
-		request.i2c_address = ETH_I2C_ADDRESS_LOW;
-		request.offset = 128;
-		request.length = 128;
-		request.page = 1;
-		break;
-	}
-
-	return page_fetch(nlctx, &request);
-}
-
-static void decoder_print(struct cmd_context *ctx)
-{
-	struct ethtool_module_eeprom *page_zero = cache_get(0, 0, ETH_I2C_ADDRESS_LOW);
-	u8 module_id = page_zero->data[SFF8636_ID_OFFSET];
-
-	switch (module_id) {
-	case SFF8024_ID_SFP:
-		sff8079_show_all_nl(ctx);
-		break;
-	case SFF8024_ID_QSFP:
-	case SFF8024_ID_QSFP28:
-	case SFF8024_ID_QSFP_PLUS:
-		sff8636_show_all_nl(ctx);
-		break;
-	case SFF8024_ID_QSFP_DD:
-	case SFF8024_ID_DSFP:
-		cmis_show_all_nl(ctx);
-		break;
-	default:
-		dump_hex(stdout, page_zero->data, page_zero->length, page_zero->offset);
-		break;
-	}
-}
-#endif
-
 static struct list_head eeprom_page_list = LIST_HEAD_INIT(eeprom_page_list);
 
 struct eeprom_page_entry {
@@ -443,14 +181,64 @@ int nl_get_eeprom_page(struct cmd_context *ctx,
 				    (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_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_DSFP:
+		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 ethtool_module_eeprom *reply_page;
 	struct nl_context *nlctx = ctx->nlctx;
-	u32 dump_length;
-	u8 *eeprom_data;
 	int ret;
 
 	if (netlink_cmd_check(ctx, ETHTOOL_MSG_MODULE_EEPROM_GET, false))
@@ -479,12 +267,6 @@ int nl_getmodule(struct cmd_context *ctx)
 		return -EINVAL;
 	}
 
-	request.i2c_address = ETH_I2C_ADDRESS_LOW;
-	request.length = 128;
-	ret = page_fetch(nlctx, &request);
-	if (ret)
-		goto cleanup;
-
 #ifdef ETHTOOL_ENABLE_PRETTY_DUMP
 	if (getmodule_cmd_params.page || getmodule_cmd_params.bank ||
 	    getmodule_cmd_params.offset || getmodule_cmd_params.length)
@@ -501,33 +283,22 @@ int nl_getmodule(struct cmd_context *ctx)
 		request.offset = 128;
 
 	if (getmodule_cmd_params.dump_hex || getmodule_cmd_params.dump_raw) {
-		ret = page_fetch(nlctx, &request);
+		ret = nl_get_eeprom_page(ctx, &request);
 		if (ret < 0)
 			goto cleanup;
-		reply_page = cache_get(request.page, request.bank, request.i2c_address);
-		if (!reply_page) {
-			ret = -EINVAL;
-			goto cleanup;
-		}
 
-		eeprom_data = reply_page->data + (request.offset - reply_page->offset);
-		dump_length = reply_page->length < request.length ? reply_page->length
-								  : request.length;
 		if (getmodule_cmd_params.dump_raw)
-			fwrite(eeprom_data, 1, request.length, stdout);
+			fwrite(request.data, 1, request.length, stdout);
 		else
-			dump_hex(stdout, eeprom_data, dump_length, request.offset);
+			dump_hex(stdout, request.data, request.length,
+				 request.offset);
 	} else {
-#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
-		ret = decoder_prefetch(nlctx);
-		if (ret)
+		ret = eeprom_parse(ctx);
+		if (ret < 0)
 			goto cleanup;
-		decoder_print(ctx);
-#endif
 	}
 
 cleanup:
 	eeprom_page_list_flush();
-	cache_free();
 	return ret;
 }
-- 
2.35.3

