* [PATCH 0/4] ethdev: add API to decode module EEPROM
@ 2026-09-27 11:20 Roman Khromenok
2026-09-27 11:20 ` [PATCH 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
` (5 more replies)
0 siblings, 6 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-27 11:20 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
Since 22.07, ethdev contains SFF-8079, SFF-8472 and SFF-8636 decoders
for plugin module EEPROM (SFP, QSFP), ported from ethtool.
They are reachable only through the telemetry command
/ethdev/module_eeprom, which reads the EEPROM of a DPDK port
and returns the result as a telemetry dictionary.
An application which needs to show transceiver information
(vendor, part number, serial number, optical power) in its own
interface cannot reuse this code and has to duplicate it.
It is also common to have some ports managed by DPDK and others
by the Linux kernel; the kernel returns the EEPROM through the ethtool
interface with the same module types and layout, but there is no way
to decode it with DPDK.
This series exposes the decoders through a small experimental function:
int rte_eth_module_eeprom_parse(uint32_t type,
const uint8_t *data, uint32_t length,
rte_eth_module_eeprom_field_cb cb, void *arg);
Each decoded field is reported to the callback as a pair of strings,
the same names and values as in the telemetry output.
The function does not access any device and does not require
EAL initialization.
Patch 1 makes the decoders write to a callback instead of
the telemetry dictionary; the telemetry output is unchanged.
Patch 2 adds the buffer length checks needed by a public API:
currently SFF-8079 and SFF-8472 decoders do not receive the length.
Patch 3 adds the API with documentation and release notes.
Patch 4 adds unit tests.
Roman Khromenok (4):
ethdev: decouple SFF module EEPROM decoders from telemetry
ethdev: check module EEPROM length before decoding
ethdev: add API to decode module EEPROM
test: add ethdev module EEPROM decoding tests
.mailmap | 1 +
app/test/meson.build | 1 +
app/test/test_ethdev_module_eeprom.c | 285 ++++++++++++++++++++++++
doc/guides/prog_guide/ethdev/ethdev.rst | 44 ++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/rte_ethdev.c | 20 ++
lib/ethdev/rte_ethdev.h | 50 +++++
lib/ethdev/sff_8079.c | 20 +-
lib/ethdev/sff_8472.c | 2 +-
lib/ethdev/sff_8636.c | 22 +-
lib/ethdev/sff_common.c | 14 +-
lib/ethdev/sff_common.h | 14 +-
lib/ethdev/sff_telemetry.c | 94 +++++---
lib/ethdev/sff_telemetry.h | 25 ++-
14 files changed, 525 insertions(+), 74 deletions(-)
create mode 100644 app/test/test_ethdev_module_eeprom.c
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry
2026-09-27 11:20 [PATCH 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
@ 2026-09-27 11:20 ` Roman Khromenok
2026-09-27 11:20 ` [PATCH 2/4] ethdev: check module EEPROM length before decoding Roman Khromenok
` (4 subsequent siblings)
5 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-27 11:20 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
The SFF-8079, SFF-8472 and SFF-8636 decoders write their results
directly into a telemetry dictionary, so the decoding logic cannot
be reused outside of the telemetry command.
Introduce an internal output descriptor with a per-field callback
and pass it to the decoders instead of the telemetry data.
The telemetry handler becomes one of the callback consumers,
its output is unchanged.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
.mailmap | 1 +
lib/ethdev/sff_8079.c | 20 ++++++------
lib/ethdev/sff_8472.c | 2 +-
lib/ethdev/sff_8636.c | 22 ++++++-------
lib/ethdev/sff_common.c | 14 ++++----
lib/ethdev/sff_common.h | 14 ++++----
lib/ethdev/sff_telemetry.c | 65 ++++++++++++++++++++------------------
lib/ethdev/sff_telemetry.h | 15 ++++++---
8 files changed, 83 insertions(+), 70 deletions(-)
diff --git a/.mailmap b/.mailmap
index 57f7a9f87a..45d4e92fbd 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1445,6 +1445,7 @@ Romain Delhomel <romain.delhomel@6wind.com>
Roman Dementiev <roman.dementiev@intel.com>
Roman Fridlyand <roman.fridlyand@intel.com>
Roman Kapl <rka@sysgo.com>
+Roman Khromenok <roma55592@yandex.ru>
Roman Korynkevych <romanx.korynkevych@intel.com>
Roman Storozhenko <roman.storozhenko@intel.com>
Roman Zhukov <roman.zhukov@arknetworks.am> <roman.zhukov@oktetlabs.ru>
diff --git a/lib/ethdev/sff_8079.c b/lib/ethdev/sff_8079.c
index 91ad25ffcd..a41a1d057d 100644
--- a/lib/ethdev/sff_8079.c
+++ b/lib/ethdev/sff_8079.c
@@ -7,12 +7,12 @@
#include "sff_common.h"
-static void sff_8079_show_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_identifier(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_identifier(data, 0, d);
}
-static void sff_8079_show_ext_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_ext_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -32,12 +32,12 @@ static void sff_8079_show_ext_identifier(const uint8_t *data, struct rte_tel_dat
ssf_add_dict_string(d, "Extended identifier", val_string);
}
-static void sff_8079_show_connector(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_connector(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_connector(data, 2, d);
}
-static void sff_8079_show_transceiver(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_transceiver(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Transceiver type";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -227,12 +227,12 @@ static void sff_8079_show_transceiver(const uint8_t *data, struct rte_tel_data *
}
}
-static void sff_8079_show_encoding(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_encoding(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_encoding(data, 11, RTE_ETH_MODULE_SFF_8472, d);
}
-static void sff_8079_show_rate_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_rate_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -262,14 +262,14 @@ static void sff_8079_show_rate_identifier(const uint8_t *data, struct rte_tel_da
ssf_add_dict_string(d, "Rate identifier", val_string);
}
-static void sff_8079_show_oui(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_oui(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_oui(data, 37, d);
}
static void
sff_8079_show_wavelength_or_copper_compliance(const uint8_t *data,
- struct rte_tel_data *d)
+ struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -312,7 +312,7 @@ sff_8079_show_wavelength_or_copper_compliance(const uint8_t *data,
}
}
-static void sff_8079_show_options(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_options(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Option";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -348,7 +348,7 @@ static void sff_8079_show_options(const uint8_t *data, struct rte_tel_data *d)
ssf_add_dict_string(d, name, "Power level 3 requirement");
}
-void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d)
+void sff_8079_show_all(const uint8_t *data, struct sff_output *d)
{
sff_8079_show_identifier(data, d);
if (((data[0] == 0x02) || (data[0] == 0x03)) && (data[1] == 0x04)) {
diff --git a/lib/ethdev/sff_8472.c b/lib/ethdev/sff_8472.c
index 97f231854c..2d02e71fca 100644
--- a/lib/ethdev/sff_8472.c
+++ b/lib/ethdev/sff_8472.c
@@ -231,7 +231,7 @@ static void sff_8472_parse_eeprom(const uint8_t *data, struct sff_diags *sd)
sff_8472_calibration(data, sd);
}
-void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d)
+void sff_8472_show_all(const uint8_t *data, struct sff_output *d)
{
struct sff_diags sd = {0};
const char *rx_power_string = NULL;
diff --git a/lib/ethdev/sff_8636.c b/lib/ethdev/sff_8636.c
index 6b65f47efe..17058d4bfd 100644
--- a/lib/ethdev/sff_8636.c
+++ b/lib/ethdev/sff_8636.c
@@ -164,12 +164,12 @@ static struct sff_8636_aw_flags {
{ NULL, 0, 0 },
};
-static void sff_8636_show_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_identifier(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_identifier(data, SFF_8636_ID_OFFSET, d);
}
-static void sff_8636_show_ext_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_ext_identifier(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Extended identifier description";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -224,12 +224,12 @@ static void sff_8636_show_ext_identifier(const uint8_t *data, struct rte_tel_dat
ssf_add_dict_string(d, name, val_string);
}
-static void sff_8636_show_connector(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_connector(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_connector(data, SFF_8636_CTOR_OFFSET, d);
}
-static void sff_8636_show_transceiver(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_transceiver(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Transceiver type";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -438,13 +438,13 @@ static void sff_8636_show_transceiver(const uint8_t *data, struct rte_tel_data *
ssf_add_dict_string(d, name, "FC: 100 MBytes/sec");
}
-static void sff_8636_show_encoding(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_encoding(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_encoding(data, SFF_8636_ENCODING_OFFSET,
RTE_ETH_MODULE_SFF_8636, d);
}
-static void sff_8636_show_rate_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_rate_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[20];
@@ -452,13 +452,13 @@ static void sff_8636_show_rate_identifier(const uint8_t *data, struct rte_tel_da
ssf_add_dict_string(d, "Rate identifier", val_string);
}
-static void sff_8636_show_oui(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_oui(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_oui(data, SFF_8636_VENDOR_OUI_OFFSET, d);
}
static void sff_8636_show_wavelength_or_copper_compliance(const uint8_t *data,
- struct rte_tel_data *d)
+ struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
snprintf(val_string, sizeof(val_string), "0x%02x",
@@ -554,7 +554,7 @@ static void sff_8636_show_wavelength_or_copper_compliance(const uint8_t *data,
}
}
-static void sff_8636_show_revision_compliance(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_revision_compliance(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Revision Compliance";
@@ -638,7 +638,7 @@ static void sff_8636_dom_parse(const uint8_t *data, struct sff_diags *sd)
}
-static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d)
+static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d)
{
struct sff_diags sd = {0};
const char *rx_power_string = NULL;
@@ -723,7 +723,7 @@ static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct r
}
}
-void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d)
+void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d)
{
sff_8636_show_identifier(data, d);
if ((data[SFF_8636_ID_OFFSET] == SFF_8024_ID_QSFP) ||
diff --git a/lib/ethdev/sff_common.c b/lib/ethdev/sff_common.c
index bd425274e3..c80e841fd4 100644
--- a/lib/ethdev/sff_common.c
+++ b/lib/ethdev/sff_common.c
@@ -15,7 +15,7 @@ double sff_convert_mw_to_dbm(double mw)
void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
const char *name, unsigned int mult,
- const char *unit, struct rte_tel_data *d)
+ const char *unit, struct sff_output *d)
{
unsigned int val = data[reg];
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -25,7 +25,7 @@ void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
}
void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
- unsigned int last_reg, const char *name, struct rte_tel_data *d)
+ unsigned int last_reg, const char *name, struct sff_output *d)
{
unsigned int reg, val;
char tmp[3];
@@ -47,7 +47,7 @@ void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
ssf_add_dict_string(d, name, val_string);
}
-void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *d)
+void sff_8024_show_oui(const uint8_t *data, int id_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -56,7 +56,7 @@ void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *
ssf_add_dict_string(d, "Vendor OUI", val_string);
}
-void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel_data *d)
+void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -145,7 +145,7 @@ void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel
ssf_add_dict_string(d, "Identifier", val_string);
}
-void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_tel_data *d)
+void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -217,7 +217,7 @@ void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_te
}
void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
- int sff_type, struct rte_tel_data *d)
+ int sff_type, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -268,7 +268,7 @@ void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
ssf_add_dict_string(d, "Encoding", val_string);
}
-void sff_show_thresholds(struct sff_diags sd, struct rte_tel_data *d)
+void sff_show_thresholds(struct sff_diags sd, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
diff --git a/lib/ethdev/sff_common.h b/lib/ethdev/sff_common.h
index 2e42cbe8be..6a742e9d7b 100644
--- a/lib/ethdev/sff_common.h
+++ b/lib/ethdev/sff_common.h
@@ -158,15 +158,15 @@ struct sff_diags {
double sff_convert_mw_to_dbm(double mw);
void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
const char *name, unsigned int mult,
- const char *unit, struct rte_tel_data *d);
+ const char *unit, struct sff_output *d);
void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
- unsigned int last_reg, const char *name, struct rte_tel_data *d);
-void sff_show_thresholds(struct sff_diags sd, struct rte_tel_data *d);
+ unsigned int last_reg, const char *name, struct sff_output *d);
+void sff_show_thresholds(struct sff_diags sd, struct sff_output *d);
-void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *d);
-void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel_data *d);
-void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_tel_data *d);
+void sff_8024_show_oui(const uint8_t *data, int id_offset, struct sff_output *d);
+void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct sff_output *d);
+void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct sff_output *d);
void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
- int sff_type, struct rte_tel_data *d);
+ int sff_type, struct sff_output *d);
#endif /* _SFF_COMMON_H_ */
diff --git a/lib/ethdev/sff_telemetry.c b/lib/ethdev/sff_telemetry.c
index b3f239d967..8c8e95affe 100644
--- a/lib/ethdev/sff_telemetry.c
+++ b/lib/ethdev/sff_telemetry.c
@@ -11,9 +11,38 @@
#include "sff_telemetry.h"
#include <telemetry_data.h>
+static void
+sff_tel_add_field(const char *name_str, const char *value_str, void *arg)
+{
+ struct rte_tel_data *d = arg;
+ struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+ if (d->type != TEL_DICT)
+ return;
+ if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES) {
+ RTE_ETHDEV_LOG_LINE(ERR, "data_len has exceeded the maximum number of inserts");
+ return;
+ }
+
+ e->type = RTE_TEL_STRING_VAL;
+ /* append different values for same keys */
+ if (d->data_len > 0) {
+ struct tel_dict_entry *previous = &d->data.dict[d->data_len - 1];
+ if (strcmp(previous->name, name_str) == 0) {
+ strlcat(previous->value.sval, "; ", RTE_TEL_MAX_STRING_LEN);
+ strlcat(previous->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
+ return;
+ }
+ }
+ strlcpy(e->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
+ strlcpy(e->name, name_str, RTE_TEL_MAX_STRING_LEN);
+ d->data_len++;
+}
+
static void
sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
{
+ struct sff_output out = { .field_cb = sff_tel_add_field, .arg = d };
struct rte_eth_dev_module_info minfo;
struct rte_dev_eeprom_info einfo;
int ret;
@@ -73,15 +102,15 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
switch (minfo.type) {
/* parsing module EEPROM data base on different module type */
case RTE_ETH_MODULE_SFF_8079:
- sff_8079_show_all(einfo.data, d);
+ sff_8079_show_all(einfo.data, &out);
break;
case RTE_ETH_MODULE_SFF_8472:
- sff_8079_show_all(einfo.data, d);
- sff_8472_show_all(einfo.data, d);
+ sff_8079_show_all(einfo.data, &out);
+ sff_8472_show_all(einfo.data, &out);
break;
case RTE_ETH_MODULE_SFF_8436:
case RTE_ETH_MODULE_SFF_8636:
- sff_8636_show_all(einfo.data, einfo.length, d);
+ sff_8636_show_all(einfo.data, einfo.length, &out);
break;
default:
RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
@@ -92,33 +121,9 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
}
void
-ssf_add_dict_string(struct rte_tel_data *d, const char *name_str, const char *value_str)
+ssf_add_dict_string(struct sff_output *d, const char *name_str, const char *value_str)
{
- struct tel_dict_entry *e = &d->data.dict[d->data_len];
-
- if (d->type != TEL_DICT)
- return;
- if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES) {
- RTE_ETHDEV_LOG_LINE(ERR, "data_len has exceeded the maximum number of inserts");
- return;
- }
-
- e->type = RTE_TEL_STRING_VAL;
- /* append different values for same keys */
- if (d->data_len > 0) {
- struct tel_dict_entry *previous = &d->data.dict[d->data_len - 1];
- if (strcmp(previous->name, name_str) == 0) {
- strlcat(previous->value.sval, "; ", RTE_TEL_MAX_STRING_LEN);
- strlcat(previous->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
- goto end;
- }
- }
- strlcpy(e->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
- strlcpy(e->name, name_str, RTE_TEL_MAX_STRING_LEN);
- d->data_len++;
-
-end:
- return;
+ d->field_cb(name_str, value_str, d->arg);
}
int
diff --git a/lib/ethdev/sff_telemetry.h b/lib/ethdev/sff_telemetry.h
index 81c1fb0ffb..2a6d79a9c1 100644
--- a/lib/ethdev/sff_telemetry.h
+++ b/lib/ethdev/sff_telemetry.h
@@ -9,20 +9,27 @@
#define SFF_ITEM_VAL_COMPOSE_SIZE 64
+/* Consumer of decoded module EEPROM fields */
+struct sff_output {
+ /* Called once per decoded field, name may repeat */
+ void (*field_cb)(const char *name, const char *value, void *arg);
+ void *arg;
+};
+
/* SFF-8079 Optics diagnostics */
-void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d);
+void sff_8079_show_all(const uint8_t *data, struct sff_output *d);
/* SFF-8472 Optics diagnostics */
-void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d);
+void sff_8472_show_all(const uint8_t *data, struct sff_output *d);
/* SFF-8636 Optics diagnostics */
-void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d);
+void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d);
int eth_dev_handle_port_module_eeprom(const char *cmd __rte_unused,
const char *params,
struct rte_tel_data *d);
-void ssf_add_dict_string(struct rte_tel_data *d, const char *name_str,
+void ssf_add_dict_string(struct sff_output *d, const char *name_str,
const char *value_str);
#endif /* _ETHDEV_SFF_TELEMETRY_H_ */
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 2/4] ethdev: check module EEPROM length before decoding
2026-09-27 11:20 [PATCH 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-27 11:20 ` [PATCH 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
@ 2026-09-27 11:20 ` Roman Khromenok
2026-09-27 11:20 ` [PATCH 3/4] ethdev: add API to decode module EEPROM Roman Khromenok
` (3 subsequent siblings)
5 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-27 11:20 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
The SFF decoders assume the buffer is large enough for the module
type: SFF-8079 and SFF-8472 do not receive the length at all,
and SFF-8636 only uses it to detect the optional page 03h.
If a driver reports a length shorter than the type requires,
the decoders read past the end of the buffer.
Move the type dispatch into a common internal function which checks
the minimal length for each type before decoding:
- SFF-8079 and SFF-8436/8636 require at least 256 bytes,
- SFF-8472 requires 256 bytes for the base information,
and the diagnostics (page A2h) are decoded only if 512 bytes
are available.
This is a preparation for exposing the decoders to applications,
which may pass buffers of arbitrary length.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
lib/ethdev/sff_telemetry.c | 37 +++++++++++++++++++++++++++++--------
lib/ethdev/sff_telemetry.h | 8 ++++++++
2 files changed, 37 insertions(+), 8 deletions(-)
diff --git a/lib/ethdev/sff_telemetry.c b/lib/ethdev/sff_telemetry.c
index 8c8e95affe..72d55322b6 100644
--- a/lib/ethdev/sff_telemetry.c
+++ b/lib/ethdev/sff_telemetry.c
@@ -99,25 +99,46 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
return;
}
- switch (minfo.type) {
+ ret = sff_decode_module_eeprom(minfo.type, einfo.data, einfo.length, &out);
+ if (ret == -ENOTSUP)
+ RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
+ else if (ret != 0)
+ RTE_ETHDEV_LOG_LINE(ERR, "Port %u module EEPROM is too short: %u bytes",
+ port_id, einfo.length);
+
+ free(einfo.data);
+}
+
+int
+sff_decode_module_eeprom(uint32_t type, const uint8_t *data, uint32_t length,
+ struct sff_output *d)
+{
+ switch (type) {
/* parsing module EEPROM data base on different module type */
case RTE_ETH_MODULE_SFF_8079:
- sff_8079_show_all(einfo.data, &out);
+ if (length < RTE_ETH_MODULE_SFF_8079_LEN)
+ return -EINVAL;
+ sff_8079_show_all(data, d);
break;
case RTE_ETH_MODULE_SFF_8472:
- sff_8079_show_all(einfo.data, &out);
- sff_8472_show_all(einfo.data, &out);
+ if (length < RTE_ETH_MODULE_SFF_8079_LEN)
+ return -EINVAL;
+ sff_8079_show_all(data, d);
+ /* diagnostics are in the second page (A2h) */
+ if (length >= RTE_ETH_MODULE_SFF_8472_LEN)
+ sff_8472_show_all(data, d);
break;
case RTE_ETH_MODULE_SFF_8436:
case RTE_ETH_MODULE_SFF_8636:
- sff_8636_show_all(einfo.data, einfo.length, &out);
+ if (length < RTE_ETH_MODULE_SFF_8636_LEN)
+ return -EINVAL;
+ sff_8636_show_all(data, length, d);
break;
default:
- RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
- break;
+ return -ENOTSUP;
}
- free(einfo.data);
+ return 0;
}
void
diff --git a/lib/ethdev/sff_telemetry.h b/lib/ethdev/sff_telemetry.h
index 2a6d79a9c1..1d2c8fd444 100644
--- a/lib/ethdev/sff_telemetry.h
+++ b/lib/ethdev/sff_telemetry.h
@@ -25,6 +25,14 @@ void sff_8472_show_all(const uint8_t *data, struct sff_output *d);
/* SFF-8636 Optics diagnostics */
void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d);
+/*
+ * Decode module EEPROM of the given type (RTE_ETH_MODULE_SFF_*).
+ * Returns 0 on success, -EINVAL if the data is too short for the type,
+ * -ENOTSUP if the type is unknown.
+ */
+int sff_decode_module_eeprom(uint32_t type, const uint8_t *data, uint32_t length,
+ struct sff_output *d);
+
int eth_dev_handle_port_module_eeprom(const char *cmd __rte_unused,
const char *params,
struct rte_tel_data *d);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 3/4] ethdev: add API to decode module EEPROM
2026-09-27 11:20 [PATCH 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-27 11:20 ` [PATCH 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
2026-09-27 11:20 ` [PATCH 2/4] ethdev: check module EEPROM length before decoding Roman Khromenok
@ 2026-09-27 11:20 ` Roman Khromenok
2026-09-27 11:20 ` [PATCH 4/4] test: add ethdev module EEPROM decoding tests Roman Khromenok
` (2 subsequent siblings)
5 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-27 11:20 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
The SFF module EEPROM decoders are available only through
the telemetry command, which reads the EEPROM of a DPDK port
and returns the result as a telemetry dictionary.
An application cannot use them for its own output,
nor for EEPROM data obtained from other sources,
for example ports managed by the Linux kernel.
Add an experimental function which decodes a module EEPROM buffer
of the given type and reports each decoded field through
a user callback. The function does not access any device
and can be used without EAL initialization.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
doc/guides/prog_guide/ethdev/ethdev.rst | 44 ++++++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 ++++
lib/ethdev/rte_ethdev.c | 20 ++++++++++
lib/ethdev/rte_ethdev.h | 50 +++++++++++++++++++++++++
lib/ethdev/sff_telemetry.h | 4 +-
5 files changed, 124 insertions(+), 1 deletion(-)
diff --git a/doc/guides/prog_guide/ethdev/ethdev.rst b/doc/guides/prog_guide/ethdev/ethdev.rst
index 6521fe14a5..5c34617ecd 100644
--- a/doc/guides/prog_guide/ethdev/ethdev.rst
+++ b/doc/guides/prog_guide/ethdev/ethdev.rst
@@ -802,3 +802,47 @@ three events are available:
The application should close the port.
Query the error handling mode supported by the PMD using ``rte_eth_dev_info_get()``.
+
+
+Plugin Module EEPROM
+~~~~~~~~~~~~~~~~~~~~
+
+Pluggable transceiver modules (SFP, QSFP) store their identification
+and diagnostic data in an EEPROM with a layout defined by the SFF specifications.
+Use ``rte_eth_dev_get_module_info()`` to get the module type and the EEPROM size,
+then ``rte_eth_dev_get_module_eeprom()`` to read the raw EEPROM data.
+
+The raw data can be decoded with ``rte_eth_module_eeprom_parse()``.
+It supports the SFF-8079, SFF-8472, SFF-8436 and SFF-8636 layouts,
+and reports each decoded field, such as the vendor name, the serial number
+or the measured optical power, through an application callback
+as a pair of human readable strings:
+
+.. code-block:: c
+
+ static void
+ print_field(const char *name, const char *value, void *arg)
+ {
+ RTE_SET_USED(arg);
+ printf("%s: %s\n", name, value);
+ }
+
+ struct rte_eth_dev_module_info info;
+ struct rte_dev_eeprom_info eeprom = {0};
+
+ if (rte_eth_dev_get_module_info(port_id, &info) == 0) {
+ eeprom.data = malloc(info.eeprom_len);
+ eeprom.length = info.eeprom_len;
+ if (eeprom.data != NULL &&
+ rte_eth_dev_get_module_eeprom(port_id, &eeprom) == 0)
+ rte_eth_module_eeprom_parse(info.type, eeprom.data, eeprom.length,
+ print_field, NULL);
+ free(eeprom.data);
+ }
+
+The decoding function does not access the device,
+so it can also decode EEPROM data obtained from other sources,
+for example from the Linux ethtool interface for ports managed by the kernel,
+which uses the same module types and layout.
+The same decoding is available through the telemetry command
+``/ethdev/module_eeprom``.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index dec96ccbc7..65da38a0d5 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,13 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Added module EEPROM decoding API to ethdev.**
+
+ Added the experimental ``rte_eth_module_eeprom_parse()`` function
+ to decode plugin module EEPROM data according to the SFF specifications.
+ It can decode data read with ``rte_eth_dev_get_module_eeprom()``
+ or obtained from any other source, such as the Linux ethtool interface.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 7c57a9bb8f..3f110ee033 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -7000,6 +7000,26 @@ rte_eth_dev_get_module_eeprom(uint16_t port_id,
return ret;
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_module_eeprom_parse, 26.11)
+int
+rte_eth_module_eeprom_parse(uint32_t type, const uint8_t *data, uint32_t length,
+ rte_eth_module_eeprom_field_cb cb, void *arg)
+{
+ struct sff_output out = { .field_cb = cb, .arg = arg };
+
+ if (data == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR, "Cannot parse module EEPROM from NULL data");
+ return -EINVAL;
+ }
+
+ if (cb == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR, "Cannot parse module EEPROM with NULL callback");
+ return -EINVAL;
+ }
+
+ return sff_decode_module_eeprom(type, data, length, &out);
+}
+
RTE_EXPORT_SYMBOL(rte_eth_dev_get_dcb_info)
int
rte_eth_dev_get_dcb_info(uint16_t port_id,
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ae43420cf6..48f0b4da25 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5233,6 +5233,56 @@ int
rte_eth_dev_get_module_eeprom(uint16_t port_id, struct rte_dev_eeprom_info *info)
__rte_warn_unused_result;
+/**
+ * Callback invoked by rte_eth_module_eeprom_parse() for each decoded field.
+ *
+ * @param name
+ * Field name, for example "Vendor name" or "Laser bias current".
+ * The same name may be reported more than once,
+ * for example when a module complies with several transceiver types.
+ * @param value
+ * Field value as a human readable string, including the unit if any.
+ * @param arg
+ * The opaque argument passed to rte_eth_module_eeprom_parse().
+ */
+typedef void (*rte_eth_module_eeprom_field_cb)(const char *name,
+ const char *value, void *arg);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Decode plugin module EEPROM data according to the SFF specifications.
+ *
+ * The data may come from rte_eth_dev_get_module_eeprom()
+ * or from any other source using the same layout,
+ * for example the Linux ethtool module EEPROM interface.
+ * The function does not access any device
+ * and can be called without EAL initialization.
+ *
+ * @param type
+ * Module type, one of RTE_ETH_MODULE_SFF_*,
+ * as reported by rte_eth_dev_get_module_info().
+ * @param data
+ * Module EEPROM data starting at offset 0.
+ * @param length
+ * Length of the data in bytes.
+ * SFF-8472 diagnostics are decoded only if at least
+ * RTE_ETH_MODULE_SFF_8472_LEN bytes are provided.
+ * @param cb
+ * Callback invoked for each decoded field.
+ * @param arg
+ * Opaque argument passed to the callback.
+ * @return
+ * - (0) if successful.
+ * - (-EINVAL) if bad parameter or data is too short for the module type.
+ * - (-ENOTSUP) if the module type is not supported.
+ */
+__rte_experimental
+int
+rte_eth_module_eeprom_parse(uint32_t type, const uint8_t *data, uint32_t length,
+ rte_eth_module_eeprom_field_cb cb, void *arg);
+
/**
* Set the list of multicast addresses to filter on an Ethernet device.
*
diff --git a/lib/ethdev/sff_telemetry.h b/lib/ethdev/sff_telemetry.h
index 1d2c8fd444..fb885fefdb 100644
--- a/lib/ethdev/sff_telemetry.h
+++ b/lib/ethdev/sff_telemetry.h
@@ -7,12 +7,14 @@
#include <rte_telemetry.h>
+#include "rte_ethdev.h"
+
#define SFF_ITEM_VAL_COMPOSE_SIZE 64
/* Consumer of decoded module EEPROM fields */
struct sff_output {
/* Called once per decoded field, name may repeat */
- void (*field_cb)(const char *name, const char *value, void *arg);
+ rte_eth_module_eeprom_field_cb field_cb;
void *arg;
};
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 4/4] test: add ethdev module EEPROM decoding tests
2026-09-27 11:20 [PATCH 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
` (2 preceding siblings ...)
2026-09-27 11:20 ` [PATCH 3/4] ethdev: add API to decode module EEPROM Roman Khromenok
@ 2026-09-27 11:20 ` Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
5 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-27 11:20 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
Add unit tests for rte_eth_module_eeprom_parse().
The tests decode SFF-8079, SFF-8472 and SFF-8636 EEPROM images
built in the test, check the identification and diagnostic fields,
and check that the SFF-8472 diagnostics are skipped when page A2h
is not complete.
Invalid parameters, too short data and unknown module types
must be rejected without invoking the callback.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
app/test/meson.build | 1 +
app/test/test_ethdev_module_eeprom.c | 285 +++++++++++++++++++++++++++
2 files changed, 286 insertions(+)
create mode 100644 app/test/test_ethdev_module_eeprom.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 9dcdb069e8..c6a808d63d 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -74,6 +74,7 @@ source_file_deps = {
'test_errno.c': [],
'test_ethdev_api.c': ['ethdev'],
'test_ethdev_link.c': ['ethdev'],
+ 'test_ethdev_module_eeprom.c': ['ethdev'],
'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],
'test_event_dma_adapter.c': ['dmadev', 'eventdev', 'bus_vdev'],
'test_event_eth_rx_adapter.c': ['ethdev', 'eventdev', 'bus_vdev'],
diff --git a/app/test/test_ethdev_module_eeprom.c b/app/test/test_ethdev_module_eeprom.c
new file mode 100644
index 0000000000..8a09b870da
--- /dev/null
+++ b/app/test/test_ethdev_module_eeprom.c
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Roman Khromenok
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+
+#define MAX_FIELDS 128
+#define MAX_NAME_LEN 64
+#define MAX_VALUE_LEN 128
+
+struct decoded_fields {
+ unsigned int count;
+ struct {
+ char name[MAX_NAME_LEN];
+ char value[MAX_VALUE_LEN];
+ } field[MAX_FIELDS];
+};
+
+static struct decoded_fields fields;
+
+static void
+collect_field(const char *name, const char *value, void *arg)
+{
+ struct decoded_fields *f = arg;
+
+ if (f->count >= MAX_FIELDS)
+ return;
+ strlcpy(f->field[f->count].name, name, MAX_NAME_LEN);
+ strlcpy(f->field[f->count].value, value, MAX_VALUE_LEN);
+ f->count++;
+}
+
+static const char *
+find_field(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < fields.count; i++)
+ if (strcmp(fields.field[i].name, name) == 0)
+ return fields.field[i].value;
+ return NULL;
+}
+
+static int
+parse(uint32_t type, const uint8_t *data, uint32_t length)
+{
+ memset(&fields, 0, sizeof(fields));
+ return rte_eth_module_eeprom_parse(type, data, length, collect_field, &fields);
+}
+
+#define CHECK_FIELD(name, expected) do { \
+ const char *value = find_field(name); \
+ TEST_ASSERT_NOT_NULL(value, "Field \"%s\" not found", name); \
+ TEST_ASSERT(strcmp(value, expected) == 0, \
+ "Field \"%s\": expected \"%s\", got \"%s\"", name, expected, value); \
+} while (0)
+
+#define CHECK_NO_FIELD(name) \
+ TEST_ASSERT_NULL(find_field(name), "Unexpected field \"%s\"", name)
+
+static void
+put_u16(uint8_t *data, unsigned int offset, uint16_t value)
+{
+ data[offset] = value >> 8;
+ data[offset + 1] = value & 0xff;
+}
+
+/* SFP+ 10GBASE-LR module, SFF-8472 layout: page A0h then page A2h */
+static void
+fill_sfp(uint8_t *data)
+{
+ uint8_t *a2 = data + RTE_ETH_MODULE_SFF_8079_LEN;
+
+ memset(data, 0, RTE_ETH_MODULE_SFF_8472_LEN);
+ data[0] = 0x03; /* identifier: SFP */
+ data[1] = 0x04; /* extended identifier */
+ data[2] = 0x07; /* connector: LC */
+ data[3] = 0x20; /* 10G Base-LR */
+ data[11] = 0x06; /* encoding: 64B/66B */
+ data[12] = 103; /* nominal bit rate, units of 100 MBd */
+ memcpy(&data[20], "STARRY ", 16);
+ data[37] = 0x00;
+ data[38] = 0x1b;
+ data[39] = 0x21;
+ memcpy(&data[40], "SFP-10G-LR-20 ", 16);
+ memcpy(&data[56], "A ", 4);
+ memcpy(&data[68], "2024082600001 ", 16);
+ memcpy(&data[84], "240902 ", 8);
+ /* diagnostics implemented, internally calibrated, average RX power */
+ data[92] = 0x68;
+
+ put_u16(a2, 96, 0x2940); /* temperature: 41.25 C */
+ put_u16(a2, 98, 33205); /* voltage: 3.3205 V */
+ put_u16(a2, 100, 19790); /* TX bias: 39.580 mA */
+ put_u16(a2, 102, 7754); /* TX power: 0.7754 mW */
+ put_u16(a2, 104, 6724); /* RX power: 0.6724 mW */
+}
+
+/* QSFP28 module, SFF-8636 layout: lower page then upper page 00h */
+static void
+fill_qsfp(uint8_t *data)
+{
+ memset(data, 0, RTE_ETH_MODULE_SFF_8636_LEN);
+ data[0] = 0x11; /* identifier: QSFP28 */
+ put_u16(data, 22, 0x3040); /* temperature: 48.25 C */
+ put_u16(data, 26, 32638); /* voltage: 3.2638 V */
+ put_u16(data, 34, 1); /* channel 1 RX power: 0.0001 mW */
+ put_u16(data, 42, 21525); /* channel 1 TX bias: 43.050 mA */
+ put_u16(data, 50, 12763); /* channel 1 TX power: 1.2763 mW */
+
+ data[128] = 0x11; /* identifier: QSFP28 */
+ data[130] = 0x07; /* connector: LC */
+ memcpy(&data[148], "FINISAR CORP. ", 16);
+ data[165] = 0x00;
+ data[166] = 0x90;
+ data[167] = 0x65;
+ memcpy(&data[168], "FTLC1157RGPL6-FB", 16);
+ memcpy(&data[184], "A0", 2);
+ memcpy(&data[196], "X24A15P ", 16);
+ memcpy(&data[212], "190826 ", 8);
+ data[220] = 0x08; /* average RX power */
+}
+
+static int
+check_sfp_identity(void)
+{
+ CHECK_FIELD("Identifier", "0x03 (SFP)");
+ CHECK_FIELD("Connector", "0x07 (LC)");
+ CHECK_FIELD("Vendor name", "STARRY");
+ CHECK_FIELD("Vendor OUI", "00:1b:21");
+ CHECK_FIELD("Vendor PN", "SFP-10G-LR-20");
+ CHECK_FIELD("Vendor rev", "A");
+ CHECK_FIELD("Vendor SN", "2024082600001");
+ CHECK_FIELD("Date code", "240902");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8079(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+
+ fill_sfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8079, data, RTE_ETH_MODULE_SFF_8079_LEN),
+ "Failed to parse SFF-8079 data");
+ TEST_ASSERT_SUCCESS(check_sfp_identity(), "Wrong SFF-8079 base information");
+ CHECK_NO_FIELD("Optical diagnostics support");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8472(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+
+ fill_sfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8472, data, sizeof(data)),
+ "Failed to parse SFF-8472 data");
+ TEST_ASSERT_SUCCESS(check_sfp_identity(), "Wrong SFF-8472 base information");
+ CHECK_FIELD("Optical diagnostics support", "Yes");
+ CHECK_FIELD("Laser bias current", "39.580 mA");
+ CHECK_FIELD("Laser output power", "0.7754 mW / -1.10 dBm");
+ CHECK_FIELD("Receiver signal average optical power", "0.6724 mW / -1.72 dBm");
+ CHECK_FIELD("Module temperature", "41.25 degrees C / 106.25 degrees F");
+ CHECK_FIELD("Module voltage", "3.3205 V");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8472_short(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+ const uint32_t lengths[] = {
+ RTE_ETH_MODULE_SFF_8079_LEN,
+ RTE_ETH_MODULE_SFF_8472_LEN - 1,
+ };
+ unsigned int i;
+
+ fill_sfp(data);
+ for (i = 0; i < RTE_DIM(lengths); i++) {
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8472, data, lengths[i]),
+ "Failed to parse SFF-8472 data of %u bytes", lengths[i]);
+ TEST_ASSERT_SUCCESS(check_sfp_identity(),
+ "Wrong SFF-8472 base information from %u bytes", lengths[i]);
+ /* page A2h is not complete, diagnostics must be skipped */
+ CHECK_NO_FIELD("Optical diagnostics support");
+ CHECK_NO_FIELD("Laser bias current");
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_qsfp_8636(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8636_LEN];
+
+ fill_qsfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8636, data, sizeof(data)),
+ "Failed to parse SFF-8636 data");
+ CHECK_FIELD("Identifier", "0x11 (QSFP28)");
+ CHECK_FIELD("Connector", "0x07 (LC)");
+ CHECK_FIELD("Vendor name", "FINISAR CORP.");
+ CHECK_FIELD("Vendor OUI", "00:90:65");
+ CHECK_FIELD("Vendor PN", "FTLC1157RGPL6-FB");
+ CHECK_FIELD("Vendor rev", "A0");
+ CHECK_FIELD("Vendor SN", "X24A15P");
+ CHECK_FIELD("Module temperature", "48.25 degrees C / 118.85 degrees F");
+ CHECK_FIELD("Module voltage", "3.2638 V");
+ /* page 03h is not provided */
+ CHECK_FIELD("Alarm/warning flags implemented", "No");
+ CHECK_FIELD("Laser tx bias current (Channel 1)", "43.050 mA");
+ CHECK_FIELD("Transmit avg optical power (Channel 1)", "1.2763 mW / 1.06 dBm");
+ CHECK_FIELD("Rcvr signal avg optical power(Channel 1)", "0.0001 mW / -40.00 dBm");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_invalid(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8636_MAX_LEN] = {0};
+ const uint32_t types[] = {
+ RTE_ETH_MODULE_SFF_8079,
+ RTE_ETH_MODULE_SFF_8472,
+ RTE_ETH_MODULE_SFF_8436,
+ RTE_ETH_MODULE_SFF_8636,
+ };
+ unsigned int i;
+ int ret;
+
+ /* all supported types require at least one full page */
+ for (i = 0; i < RTE_DIM(types); i++) {
+ ret = parse(types[i], data, 255);
+ TEST_ASSERT_EQUAL(ret, -EINVAL,
+ "Type %u with 255 bytes: expected %d, got %d", types[i], -EINVAL, ret);
+ TEST_ASSERT_EQUAL(fields.count, 0,
+ "Type %u with 255 bytes: callback called %u times",
+ types[i], fields.count);
+ }
+
+ ret = parse(0, data, sizeof(data));
+ TEST_ASSERT_EQUAL(ret, -ENOTSUP, "Type 0: expected %d, got %d", -ENOTSUP, ret);
+ ret = parse(RTE_ETH_MODULE_SFF_8436 + 1, data, sizeof(data));
+ TEST_ASSERT_EQUAL(ret, -ENOTSUP, "Unknown type: expected %d, got %d", -ENOTSUP, ret);
+ TEST_ASSERT_EQUAL(fields.count, 0, "Unknown type: callback called %u times",
+ fields.count);
+
+ ret = rte_eth_module_eeprom_parse(RTE_ETH_MODULE_SFF_8079, NULL,
+ RTE_ETH_MODULE_SFF_8079_LEN, collect_field, &fields);
+ TEST_ASSERT_EQUAL(ret, -EINVAL, "NULL data: expected %d, got %d", -EINVAL, ret);
+ ret = rte_eth_module_eeprom_parse(RTE_ETH_MODULE_SFF_8079, data,
+ RTE_ETH_MODULE_SFF_8079_LEN, NULL, NULL);
+ TEST_ASSERT_EQUAL(ret, -EINVAL, "NULL callback: expected %d, got %d", -EINVAL, ret);
+
+ return TEST_SUCCESS;
+}
+
+static struct unit_test_suite module_eeprom_testsuite = {
+ .suite_name = "ethdev module EEPROM decoding",
+ .setup = NULL,
+ .teardown = NULL,
+ .unit_test_cases = {
+ TEST_CASE(test_module_eeprom_sfp_8079),
+ TEST_CASE(test_module_eeprom_sfp_8472),
+ TEST_CASE(test_module_eeprom_sfp_8472_short),
+ TEST_CASE(test_module_eeprom_qsfp_8636),
+ TEST_CASE(test_module_eeprom_invalid),
+ TEST_CASES_END()
+ }
+};
+
+static int
+test_module_eeprom(void)
+{
+ return unit_test_suite_runner(&module_eeprom_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_module_eeprom, NOHUGE_OK, ASAN_OK, test_module_eeprom);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v2 0/4] ethdev: add API to decode module EEPROM
2026-09-27 11:20 [PATCH 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
` (3 preceding siblings ...)
2026-09-27 11:20 ` [PATCH 4/4] test: add ethdev module EEPROM decoding tests Roman Khromenok
@ 2026-09-28 8:47 ` Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
` (4 more replies)
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
5 siblings, 5 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-28 8:47 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
Since 22.07, ethdev contains SFF-8079, SFF-8472 and SFF-8636 decoders
for plugin module EEPROM (SFP, QSFP), ported from ethtool.
They are reachable only through the telemetry command
/ethdev/module_eeprom, which reads the EEPROM of a DPDK port
and returns the result as a telemetry dictionary.
An application which needs to show transceiver information
(vendor, part number, serial number, optical power) in its own
interface cannot reuse this code and has to duplicate it.
It is also common to have some ports managed by DPDK and others
by the Linux kernel; the kernel returns the EEPROM through the ethtool
interface with the same module types and layout, but there is no way
to decode it with DPDK.
This series exposes the decoders through a small experimental function:
int rte_eth_module_eeprom_parse(uint32_t type,
const uint8_t *data, uint32_t length,
rte_eth_module_eeprom_field_cb cb, void *arg);
Each decoded field is reported to the callback as a pair of strings,
the same names and values as in the telemetry output.
The function does not access any device and does not require
EAL initialization.
Patch 1 makes the decoders write to a callback instead of
the telemetry dictionary; the telemetry output is unchanged.
Patch 2 adds the buffer length checks needed by a public API:
currently SFF-8079 and SFF-8472 decoders do not receive the length.
Patch 3 adds the API with documentation and release notes.
Patch 4 adds unit tests.
v2:
- patch 2: do not read the SFF-8636 alarm and warning thresholds
from page 03h when only 256 bytes are available (out of bounds read
found by ASan in the new unit test).
Roman Khromenok (4):
ethdev: decouple SFF module EEPROM decoders from telemetry
ethdev: check module EEPROM length before decoding
ethdev: add API to decode module EEPROM
test: add ethdev module EEPROM decoding tests
.mailmap | 1 +
app/test/meson.build | 1 +
app/test/test_ethdev_module_eeprom.c | 285 ++++++++++++++++++++++++
doc/guides/prog_guide/ethdev/ethdev.rst | 44 ++++
doc/guides/rel_notes/release_26_11.rst | 7 +
lib/ethdev/rte_ethdev.c | 20 ++
lib/ethdev/rte_ethdev.h | 50 +++++
lib/ethdev/sff_8079.c | 20 +-
lib/ethdev/sff_8472.c | 2 +-
lib/ethdev/sff_8636.c | 74 +++---
lib/ethdev/sff_common.c | 14 +-
lib/ethdev/sff_common.h | 14 +-
lib/ethdev/sff_telemetry.c | 94 +++++---
lib/ethdev/sff_telemetry.h | 25 ++-
14 files changed, 552 insertions(+), 99 deletions(-)
create mode 100644 app/test/test_ethdev_module_eeprom.c
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v2 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
@ 2026-09-28 8:47 ` Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 2/4] ethdev: check module EEPROM length before decoding Roman Khromenok
` (3 subsequent siblings)
4 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-28 8:47 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
The SFF-8079, SFF-8472 and SFF-8636 decoders write their results
directly into a telemetry dictionary, so the decoding logic cannot
be reused outside of the telemetry command.
Introduce an internal output descriptor with a per-field callback
and pass it to the decoders instead of the telemetry data.
The telemetry handler becomes one of the callback consumers,
its output is unchanged.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
.mailmap | 1 +
lib/ethdev/sff_8079.c | 20 ++++++------
lib/ethdev/sff_8472.c | 2 +-
lib/ethdev/sff_8636.c | 22 ++++++-------
lib/ethdev/sff_common.c | 14 ++++----
lib/ethdev/sff_common.h | 14 ++++----
lib/ethdev/sff_telemetry.c | 65 ++++++++++++++++++++------------------
lib/ethdev/sff_telemetry.h | 15 ++++++---
8 files changed, 83 insertions(+), 70 deletions(-)
diff --git a/.mailmap b/.mailmap
index 57f7a9f87a..45d4e92fbd 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1445,6 +1445,7 @@ Romain Delhomel <romain.delhomel@6wind.com>
Roman Dementiev <roman.dementiev@intel.com>
Roman Fridlyand <roman.fridlyand@intel.com>
Roman Kapl <rka@sysgo.com>
+Roman Khromenok <roma55592@yandex.ru>
Roman Korynkevych <romanx.korynkevych@intel.com>
Roman Storozhenko <roman.storozhenko@intel.com>
Roman Zhukov <roman.zhukov@arknetworks.am> <roman.zhukov@oktetlabs.ru>
diff --git a/lib/ethdev/sff_8079.c b/lib/ethdev/sff_8079.c
index 91ad25ffcd..a41a1d057d 100644
--- a/lib/ethdev/sff_8079.c
+++ b/lib/ethdev/sff_8079.c
@@ -7,12 +7,12 @@
#include "sff_common.h"
-static void sff_8079_show_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_identifier(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_identifier(data, 0, d);
}
-static void sff_8079_show_ext_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_ext_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -32,12 +32,12 @@ static void sff_8079_show_ext_identifier(const uint8_t *data, struct rte_tel_dat
ssf_add_dict_string(d, "Extended identifier", val_string);
}
-static void sff_8079_show_connector(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_connector(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_connector(data, 2, d);
}
-static void sff_8079_show_transceiver(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_transceiver(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Transceiver type";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -227,12 +227,12 @@ static void sff_8079_show_transceiver(const uint8_t *data, struct rte_tel_data *
}
}
-static void sff_8079_show_encoding(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_encoding(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_encoding(data, 11, RTE_ETH_MODULE_SFF_8472, d);
}
-static void sff_8079_show_rate_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_rate_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -262,14 +262,14 @@ static void sff_8079_show_rate_identifier(const uint8_t *data, struct rte_tel_da
ssf_add_dict_string(d, "Rate identifier", val_string);
}
-static void sff_8079_show_oui(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_oui(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_oui(data, 37, d);
}
static void
sff_8079_show_wavelength_or_copper_compliance(const uint8_t *data,
- struct rte_tel_data *d)
+ struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -312,7 +312,7 @@ sff_8079_show_wavelength_or_copper_compliance(const uint8_t *data,
}
}
-static void sff_8079_show_options(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_options(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Option";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -348,7 +348,7 @@ static void sff_8079_show_options(const uint8_t *data, struct rte_tel_data *d)
ssf_add_dict_string(d, name, "Power level 3 requirement");
}
-void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d)
+void sff_8079_show_all(const uint8_t *data, struct sff_output *d)
{
sff_8079_show_identifier(data, d);
if (((data[0] == 0x02) || (data[0] == 0x03)) && (data[1] == 0x04)) {
diff --git a/lib/ethdev/sff_8472.c b/lib/ethdev/sff_8472.c
index 97f231854c..2d02e71fca 100644
--- a/lib/ethdev/sff_8472.c
+++ b/lib/ethdev/sff_8472.c
@@ -231,7 +231,7 @@ static void sff_8472_parse_eeprom(const uint8_t *data, struct sff_diags *sd)
sff_8472_calibration(data, sd);
}
-void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d)
+void sff_8472_show_all(const uint8_t *data, struct sff_output *d)
{
struct sff_diags sd = {0};
const char *rx_power_string = NULL;
diff --git a/lib/ethdev/sff_8636.c b/lib/ethdev/sff_8636.c
index 6b65f47efe..17058d4bfd 100644
--- a/lib/ethdev/sff_8636.c
+++ b/lib/ethdev/sff_8636.c
@@ -164,12 +164,12 @@ static struct sff_8636_aw_flags {
{ NULL, 0, 0 },
};
-static void sff_8636_show_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_identifier(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_identifier(data, SFF_8636_ID_OFFSET, d);
}
-static void sff_8636_show_ext_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_ext_identifier(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Extended identifier description";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -224,12 +224,12 @@ static void sff_8636_show_ext_identifier(const uint8_t *data, struct rte_tel_dat
ssf_add_dict_string(d, name, val_string);
}
-static void sff_8636_show_connector(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_connector(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_connector(data, SFF_8636_CTOR_OFFSET, d);
}
-static void sff_8636_show_transceiver(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_transceiver(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Transceiver type";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -438,13 +438,13 @@ static void sff_8636_show_transceiver(const uint8_t *data, struct rte_tel_data *
ssf_add_dict_string(d, name, "FC: 100 MBytes/sec");
}
-static void sff_8636_show_encoding(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_encoding(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_encoding(data, SFF_8636_ENCODING_OFFSET,
RTE_ETH_MODULE_SFF_8636, d);
}
-static void sff_8636_show_rate_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_rate_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[20];
@@ -452,13 +452,13 @@ static void sff_8636_show_rate_identifier(const uint8_t *data, struct rte_tel_da
ssf_add_dict_string(d, "Rate identifier", val_string);
}
-static void sff_8636_show_oui(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_oui(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_oui(data, SFF_8636_VENDOR_OUI_OFFSET, d);
}
static void sff_8636_show_wavelength_or_copper_compliance(const uint8_t *data,
- struct rte_tel_data *d)
+ struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
snprintf(val_string, sizeof(val_string), "0x%02x",
@@ -554,7 +554,7 @@ static void sff_8636_show_wavelength_or_copper_compliance(const uint8_t *data,
}
}
-static void sff_8636_show_revision_compliance(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_revision_compliance(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Revision Compliance";
@@ -638,7 +638,7 @@ static void sff_8636_dom_parse(const uint8_t *data, struct sff_diags *sd)
}
-static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d)
+static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d)
{
struct sff_diags sd = {0};
const char *rx_power_string = NULL;
@@ -723,7 +723,7 @@ static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct r
}
}
-void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d)
+void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d)
{
sff_8636_show_identifier(data, d);
if ((data[SFF_8636_ID_OFFSET] == SFF_8024_ID_QSFP) ||
diff --git a/lib/ethdev/sff_common.c b/lib/ethdev/sff_common.c
index bd425274e3..c80e841fd4 100644
--- a/lib/ethdev/sff_common.c
+++ b/lib/ethdev/sff_common.c
@@ -15,7 +15,7 @@ double sff_convert_mw_to_dbm(double mw)
void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
const char *name, unsigned int mult,
- const char *unit, struct rte_tel_data *d)
+ const char *unit, struct sff_output *d)
{
unsigned int val = data[reg];
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -25,7 +25,7 @@ void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
}
void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
- unsigned int last_reg, const char *name, struct rte_tel_data *d)
+ unsigned int last_reg, const char *name, struct sff_output *d)
{
unsigned int reg, val;
char tmp[3];
@@ -47,7 +47,7 @@ void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
ssf_add_dict_string(d, name, val_string);
}
-void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *d)
+void sff_8024_show_oui(const uint8_t *data, int id_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -56,7 +56,7 @@ void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *
ssf_add_dict_string(d, "Vendor OUI", val_string);
}
-void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel_data *d)
+void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -145,7 +145,7 @@ void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel
ssf_add_dict_string(d, "Identifier", val_string);
}
-void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_tel_data *d)
+void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -217,7 +217,7 @@ void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_te
}
void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
- int sff_type, struct rte_tel_data *d)
+ int sff_type, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -268,7 +268,7 @@ void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
ssf_add_dict_string(d, "Encoding", val_string);
}
-void sff_show_thresholds(struct sff_diags sd, struct rte_tel_data *d)
+void sff_show_thresholds(struct sff_diags sd, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
diff --git a/lib/ethdev/sff_common.h b/lib/ethdev/sff_common.h
index 2e42cbe8be..6a742e9d7b 100644
--- a/lib/ethdev/sff_common.h
+++ b/lib/ethdev/sff_common.h
@@ -158,15 +158,15 @@ struct sff_diags {
double sff_convert_mw_to_dbm(double mw);
void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
const char *name, unsigned int mult,
- const char *unit, struct rte_tel_data *d);
+ const char *unit, struct sff_output *d);
void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
- unsigned int last_reg, const char *name, struct rte_tel_data *d);
-void sff_show_thresholds(struct sff_diags sd, struct rte_tel_data *d);
+ unsigned int last_reg, const char *name, struct sff_output *d);
+void sff_show_thresholds(struct sff_diags sd, struct sff_output *d);
-void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *d);
-void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel_data *d);
-void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_tel_data *d);
+void sff_8024_show_oui(const uint8_t *data, int id_offset, struct sff_output *d);
+void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct sff_output *d);
+void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct sff_output *d);
void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
- int sff_type, struct rte_tel_data *d);
+ int sff_type, struct sff_output *d);
#endif /* _SFF_COMMON_H_ */
diff --git a/lib/ethdev/sff_telemetry.c b/lib/ethdev/sff_telemetry.c
index b3f239d967..8c8e95affe 100644
--- a/lib/ethdev/sff_telemetry.c
+++ b/lib/ethdev/sff_telemetry.c
@@ -11,9 +11,38 @@
#include "sff_telemetry.h"
#include <telemetry_data.h>
+static void
+sff_tel_add_field(const char *name_str, const char *value_str, void *arg)
+{
+ struct rte_tel_data *d = arg;
+ struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+ if (d->type != TEL_DICT)
+ return;
+ if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES) {
+ RTE_ETHDEV_LOG_LINE(ERR, "data_len has exceeded the maximum number of inserts");
+ return;
+ }
+
+ e->type = RTE_TEL_STRING_VAL;
+ /* append different values for same keys */
+ if (d->data_len > 0) {
+ struct tel_dict_entry *previous = &d->data.dict[d->data_len - 1];
+ if (strcmp(previous->name, name_str) == 0) {
+ strlcat(previous->value.sval, "; ", RTE_TEL_MAX_STRING_LEN);
+ strlcat(previous->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
+ return;
+ }
+ }
+ strlcpy(e->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
+ strlcpy(e->name, name_str, RTE_TEL_MAX_STRING_LEN);
+ d->data_len++;
+}
+
static void
sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
{
+ struct sff_output out = { .field_cb = sff_tel_add_field, .arg = d };
struct rte_eth_dev_module_info minfo;
struct rte_dev_eeprom_info einfo;
int ret;
@@ -73,15 +102,15 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
switch (minfo.type) {
/* parsing module EEPROM data base on different module type */
case RTE_ETH_MODULE_SFF_8079:
- sff_8079_show_all(einfo.data, d);
+ sff_8079_show_all(einfo.data, &out);
break;
case RTE_ETH_MODULE_SFF_8472:
- sff_8079_show_all(einfo.data, d);
- sff_8472_show_all(einfo.data, d);
+ sff_8079_show_all(einfo.data, &out);
+ sff_8472_show_all(einfo.data, &out);
break;
case RTE_ETH_MODULE_SFF_8436:
case RTE_ETH_MODULE_SFF_8636:
- sff_8636_show_all(einfo.data, einfo.length, d);
+ sff_8636_show_all(einfo.data, einfo.length, &out);
break;
default:
RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
@@ -92,33 +121,9 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
}
void
-ssf_add_dict_string(struct rte_tel_data *d, const char *name_str, const char *value_str)
+ssf_add_dict_string(struct sff_output *d, const char *name_str, const char *value_str)
{
- struct tel_dict_entry *e = &d->data.dict[d->data_len];
-
- if (d->type != TEL_DICT)
- return;
- if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES) {
- RTE_ETHDEV_LOG_LINE(ERR, "data_len has exceeded the maximum number of inserts");
- return;
- }
-
- e->type = RTE_TEL_STRING_VAL;
- /* append different values for same keys */
- if (d->data_len > 0) {
- struct tel_dict_entry *previous = &d->data.dict[d->data_len - 1];
- if (strcmp(previous->name, name_str) == 0) {
- strlcat(previous->value.sval, "; ", RTE_TEL_MAX_STRING_LEN);
- strlcat(previous->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
- goto end;
- }
- }
- strlcpy(e->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
- strlcpy(e->name, name_str, RTE_TEL_MAX_STRING_LEN);
- d->data_len++;
-
-end:
- return;
+ d->field_cb(name_str, value_str, d->arg);
}
int
diff --git a/lib/ethdev/sff_telemetry.h b/lib/ethdev/sff_telemetry.h
index 81c1fb0ffb..2a6d79a9c1 100644
--- a/lib/ethdev/sff_telemetry.h
+++ b/lib/ethdev/sff_telemetry.h
@@ -9,20 +9,27 @@
#define SFF_ITEM_VAL_COMPOSE_SIZE 64
+/* Consumer of decoded module EEPROM fields */
+struct sff_output {
+ /* Called once per decoded field, name may repeat */
+ void (*field_cb)(const char *name, const char *value, void *arg);
+ void *arg;
+};
+
/* SFF-8079 Optics diagnostics */
-void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d);
+void sff_8079_show_all(const uint8_t *data, struct sff_output *d);
/* SFF-8472 Optics diagnostics */
-void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d);
+void sff_8472_show_all(const uint8_t *data, struct sff_output *d);
/* SFF-8636 Optics diagnostics */
-void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d);
+void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d);
int eth_dev_handle_port_module_eeprom(const char *cmd __rte_unused,
const char *params,
struct rte_tel_data *d);
-void ssf_add_dict_string(struct rte_tel_data *d, const char *name_str,
+void ssf_add_dict_string(struct sff_output *d, const char *name_str,
const char *value_str);
#endif /* _ETHDEV_SFF_TELEMETRY_H_ */
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v2 2/4] ethdev: check module EEPROM length before decoding
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
@ 2026-09-28 8:47 ` Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 3/4] ethdev: add API to decode module EEPROM Roman Khromenok
` (2 subsequent siblings)
4 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-28 8:47 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
The SFF decoders assume the buffer is large enough for the module
type: SFF-8079 and SFF-8472 do not receive the length at all,
and SFF-8636 reads the alarm and warning thresholds from page 03h
even when only 256 bytes are available.
If a driver reports a length shorter than the type requires,
the decoders read past the end of the buffer.
Move the type dispatch into a common internal function which checks
the minimal length for each type before decoding:
- SFF-8079 and SFF-8436/8636 require at least 256 bytes,
- SFF-8472 requires 256 bytes for the base information,
and the diagnostics (page A2h) are decoded only if 512 bytes
are available.
In the SFF-8636 decoder, read the thresholds only if page 03h
is available, as it is already done when printing them.
This is a preparation for exposing the decoders to applications,
which may pass buffers of arbitrary length.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
v2: do not read SFF-8636 thresholds when page 03h is not available
lib/ethdev/sff_8636.c | 52 ++++++++++++++++++++------------------
lib/ethdev/sff_telemetry.c | 37 +++++++++++++++++++++------
lib/ethdev/sff_telemetry.h | 8 ++++++
3 files changed, 64 insertions(+), 33 deletions(-)
diff --git a/lib/ethdev/sff_8636.c b/lib/ethdev/sff_8636.c
index 17058d4bfd..ac55c8e0b6 100644
--- a/lib/ethdev/sff_8636.c
+++ b/lib/ethdev/sff_8636.c
@@ -600,34 +600,36 @@ static void sff_8636_dom_parse(const uint8_t *data, struct sff_diags *sd)
{
int i = 0;
- /* Monitoring Thresholds for Alarms and Warnings */
sd->sfp_voltage[SFF_MCURR] = SFF_OFFSET_TO_U16(SFF_8636_VCC_CURR);
- sd->sfp_voltage[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HALRM);
- sd->sfp_voltage[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LALRM);
- sd->sfp_voltage[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HWARN);
- sd->sfp_voltage[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LWARN);
-
sd->sfp_temp[SFF_MCURR] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_CURR);
- sd->sfp_temp[SFF_HALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HALRM);
- sd->sfp_temp[SFF_LALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LALRM);
- sd->sfp_temp[SFF_HWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HWARN);
- sd->sfp_temp[SFF_LWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LWARN);
-
- sd->bias_cur[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HALRM);
- sd->bias_cur[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LALRM);
- sd->bias_cur[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HWARN);
- sd->bias_cur[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LWARN);
-
- sd->tx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HALRM);
- sd->tx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LALRM);
- sd->tx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HWARN);
- sd->tx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LWARN);
-
- sd->rx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HALRM);
- sd->rx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LALRM);
- sd->rx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HWARN);
- sd->rx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LWARN);
+ /* Monitoring Thresholds for Alarms and Warnings are in page 03h */
+ if (sd->supports_alarms) {
+ sd->sfp_voltage[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HALRM);
+ sd->sfp_voltage[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LALRM);
+ sd->sfp_voltage[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HWARN);
+ sd->sfp_voltage[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LWARN);
+
+ sd->sfp_temp[SFF_HALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HALRM);
+ sd->sfp_temp[SFF_LALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LALRM);
+ sd->sfp_temp[SFF_HWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HWARN);
+ sd->sfp_temp[SFF_LWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LWARN);
+
+ sd->bias_cur[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HALRM);
+ sd->bias_cur[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LALRM);
+ sd->bias_cur[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HWARN);
+ sd->bias_cur[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LWARN);
+
+ sd->tx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HALRM);
+ sd->tx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LALRM);
+ sd->tx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HWARN);
+ sd->tx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LWARN);
+
+ sd->rx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HALRM);
+ sd->rx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LALRM);
+ sd->rx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HWARN);
+ sd->rx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LWARN);
+ }
/* Channel Specific Data */
for (i = 0; i < SFF_MAX_CHANNEL_NUM; i++) {
diff --git a/lib/ethdev/sff_telemetry.c b/lib/ethdev/sff_telemetry.c
index 8c8e95affe..72d55322b6 100644
--- a/lib/ethdev/sff_telemetry.c
+++ b/lib/ethdev/sff_telemetry.c
@@ -99,25 +99,46 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
return;
}
- switch (minfo.type) {
+ ret = sff_decode_module_eeprom(minfo.type, einfo.data, einfo.length, &out);
+ if (ret == -ENOTSUP)
+ RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
+ else if (ret != 0)
+ RTE_ETHDEV_LOG_LINE(ERR, "Port %u module EEPROM is too short: %u bytes",
+ port_id, einfo.length);
+
+ free(einfo.data);
+}
+
+int
+sff_decode_module_eeprom(uint32_t type, const uint8_t *data, uint32_t length,
+ struct sff_output *d)
+{
+ switch (type) {
/* parsing module EEPROM data base on different module type */
case RTE_ETH_MODULE_SFF_8079:
- sff_8079_show_all(einfo.data, &out);
+ if (length < RTE_ETH_MODULE_SFF_8079_LEN)
+ return -EINVAL;
+ sff_8079_show_all(data, d);
break;
case RTE_ETH_MODULE_SFF_8472:
- sff_8079_show_all(einfo.data, &out);
- sff_8472_show_all(einfo.data, &out);
+ if (length < RTE_ETH_MODULE_SFF_8079_LEN)
+ return -EINVAL;
+ sff_8079_show_all(data, d);
+ /* diagnostics are in the second page (A2h) */
+ if (length >= RTE_ETH_MODULE_SFF_8472_LEN)
+ sff_8472_show_all(data, d);
break;
case RTE_ETH_MODULE_SFF_8436:
case RTE_ETH_MODULE_SFF_8636:
- sff_8636_show_all(einfo.data, einfo.length, &out);
+ if (length < RTE_ETH_MODULE_SFF_8636_LEN)
+ return -EINVAL;
+ sff_8636_show_all(data, length, d);
break;
default:
- RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
- break;
+ return -ENOTSUP;
}
- free(einfo.data);
+ return 0;
}
void
diff --git a/lib/ethdev/sff_telemetry.h b/lib/ethdev/sff_telemetry.h
index 2a6d79a9c1..1d2c8fd444 100644
--- a/lib/ethdev/sff_telemetry.h
+++ b/lib/ethdev/sff_telemetry.h
@@ -25,6 +25,14 @@ void sff_8472_show_all(const uint8_t *data, struct sff_output *d);
/* SFF-8636 Optics diagnostics */
void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d);
+/*
+ * Decode module EEPROM of the given type (RTE_ETH_MODULE_SFF_*).
+ * Returns 0 on success, -EINVAL if the data is too short for the type,
+ * -ENOTSUP if the type is unknown.
+ */
+int sff_decode_module_eeprom(uint32_t type, const uint8_t *data, uint32_t length,
+ struct sff_output *d);
+
int eth_dev_handle_port_module_eeprom(const char *cmd __rte_unused,
const char *params,
struct rte_tel_data *d);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v2 3/4] ethdev: add API to decode module EEPROM
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 2/4] ethdev: check module EEPROM length before decoding Roman Khromenok
@ 2026-09-28 8:47 ` Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 4/4] test: add ethdev module EEPROM decoding tests Roman Khromenok
2026-09-28 18:11 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Stephen Hemminger
4 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-28 8:47 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
The SFF module EEPROM decoders are available only through
the telemetry command, which reads the EEPROM of a DPDK port
and returns the result as a telemetry dictionary.
An application cannot use them for its own output,
nor for EEPROM data obtained from other sources,
for example ports managed by the Linux kernel.
Add an experimental function which decodes a module EEPROM buffer
of the given type and reports each decoded field through
a user callback. The function does not access any device
and can be used without EAL initialization.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
doc/guides/prog_guide/ethdev/ethdev.rst | 44 ++++++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 7 ++++
lib/ethdev/rte_ethdev.c | 20 ++++++++++
lib/ethdev/rte_ethdev.h | 50 +++++++++++++++++++++++++
lib/ethdev/sff_telemetry.h | 4 +-
5 files changed, 124 insertions(+), 1 deletion(-)
diff --git a/doc/guides/prog_guide/ethdev/ethdev.rst b/doc/guides/prog_guide/ethdev/ethdev.rst
index 6521fe14a5..5c34617ecd 100644
--- a/doc/guides/prog_guide/ethdev/ethdev.rst
+++ b/doc/guides/prog_guide/ethdev/ethdev.rst
@@ -802,3 +802,47 @@ three events are available:
The application should close the port.
Query the error handling mode supported by the PMD using ``rte_eth_dev_info_get()``.
+
+
+Plugin Module EEPROM
+~~~~~~~~~~~~~~~~~~~~
+
+Pluggable transceiver modules (SFP, QSFP) store their identification
+and diagnostic data in an EEPROM with a layout defined by the SFF specifications.
+Use ``rte_eth_dev_get_module_info()`` to get the module type and the EEPROM size,
+then ``rte_eth_dev_get_module_eeprom()`` to read the raw EEPROM data.
+
+The raw data can be decoded with ``rte_eth_module_eeprom_parse()``.
+It supports the SFF-8079, SFF-8472, SFF-8436 and SFF-8636 layouts,
+and reports each decoded field, such as the vendor name, the serial number
+or the measured optical power, through an application callback
+as a pair of human readable strings:
+
+.. code-block:: c
+
+ static void
+ print_field(const char *name, const char *value, void *arg)
+ {
+ RTE_SET_USED(arg);
+ printf("%s: %s\n", name, value);
+ }
+
+ struct rte_eth_dev_module_info info;
+ struct rte_dev_eeprom_info eeprom = {0};
+
+ if (rte_eth_dev_get_module_info(port_id, &info) == 0) {
+ eeprom.data = malloc(info.eeprom_len);
+ eeprom.length = info.eeprom_len;
+ if (eeprom.data != NULL &&
+ rte_eth_dev_get_module_eeprom(port_id, &eeprom) == 0)
+ rte_eth_module_eeprom_parse(info.type, eeprom.data, eeprom.length,
+ print_field, NULL);
+ free(eeprom.data);
+ }
+
+The decoding function does not access the device,
+so it can also decode EEPROM data obtained from other sources,
+for example from the Linux ethtool interface for ports managed by the kernel,
+which uses the same module types and layout.
+The same decoding is available through the telemetry command
+``/ethdev/module_eeprom``.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index dec96ccbc7..65da38a0d5 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,13 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Added module EEPROM decoding API to ethdev.**
+
+ Added the experimental ``rte_eth_module_eeprom_parse()`` function
+ to decode plugin module EEPROM data according to the SFF specifications.
+ It can decode data read with ``rte_eth_dev_get_module_eeprom()``
+ or obtained from any other source, such as the Linux ethtool interface.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 7c57a9bb8f..3f110ee033 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -7000,6 +7000,26 @@ rte_eth_dev_get_module_eeprom(uint16_t port_id,
return ret;
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_module_eeprom_parse, 26.11)
+int
+rte_eth_module_eeprom_parse(uint32_t type, const uint8_t *data, uint32_t length,
+ rte_eth_module_eeprom_field_cb cb, void *arg)
+{
+ struct sff_output out = { .field_cb = cb, .arg = arg };
+
+ if (data == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR, "Cannot parse module EEPROM from NULL data");
+ return -EINVAL;
+ }
+
+ if (cb == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR, "Cannot parse module EEPROM with NULL callback");
+ return -EINVAL;
+ }
+
+ return sff_decode_module_eeprom(type, data, length, &out);
+}
+
RTE_EXPORT_SYMBOL(rte_eth_dev_get_dcb_info)
int
rte_eth_dev_get_dcb_info(uint16_t port_id,
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ae43420cf6..48f0b4da25 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5233,6 +5233,56 @@ int
rte_eth_dev_get_module_eeprom(uint16_t port_id, struct rte_dev_eeprom_info *info)
__rte_warn_unused_result;
+/**
+ * Callback invoked by rte_eth_module_eeprom_parse() for each decoded field.
+ *
+ * @param name
+ * Field name, for example "Vendor name" or "Laser bias current".
+ * The same name may be reported more than once,
+ * for example when a module complies with several transceiver types.
+ * @param value
+ * Field value as a human readable string, including the unit if any.
+ * @param arg
+ * The opaque argument passed to rte_eth_module_eeprom_parse().
+ */
+typedef void (*rte_eth_module_eeprom_field_cb)(const char *name,
+ const char *value, void *arg);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Decode plugin module EEPROM data according to the SFF specifications.
+ *
+ * The data may come from rte_eth_dev_get_module_eeprom()
+ * or from any other source using the same layout,
+ * for example the Linux ethtool module EEPROM interface.
+ * The function does not access any device
+ * and can be called without EAL initialization.
+ *
+ * @param type
+ * Module type, one of RTE_ETH_MODULE_SFF_*,
+ * as reported by rte_eth_dev_get_module_info().
+ * @param data
+ * Module EEPROM data starting at offset 0.
+ * @param length
+ * Length of the data in bytes.
+ * SFF-8472 diagnostics are decoded only if at least
+ * RTE_ETH_MODULE_SFF_8472_LEN bytes are provided.
+ * @param cb
+ * Callback invoked for each decoded field.
+ * @param arg
+ * Opaque argument passed to the callback.
+ * @return
+ * - (0) if successful.
+ * - (-EINVAL) if bad parameter or data is too short for the module type.
+ * - (-ENOTSUP) if the module type is not supported.
+ */
+__rte_experimental
+int
+rte_eth_module_eeprom_parse(uint32_t type, const uint8_t *data, uint32_t length,
+ rte_eth_module_eeprom_field_cb cb, void *arg);
+
/**
* Set the list of multicast addresses to filter on an Ethernet device.
*
diff --git a/lib/ethdev/sff_telemetry.h b/lib/ethdev/sff_telemetry.h
index 1d2c8fd444..fb885fefdb 100644
--- a/lib/ethdev/sff_telemetry.h
+++ b/lib/ethdev/sff_telemetry.h
@@ -7,12 +7,14 @@
#include <rte_telemetry.h>
+#include "rte_ethdev.h"
+
#define SFF_ITEM_VAL_COMPOSE_SIZE 64
/* Consumer of decoded module EEPROM fields */
struct sff_output {
/* Called once per decoded field, name may repeat */
- void (*field_cb)(const char *name, const char *value, void *arg);
+ rte_eth_module_eeprom_field_cb field_cb;
void *arg;
};
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v2 4/4] test: add ethdev module EEPROM decoding tests
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
` (2 preceding siblings ...)
2026-09-28 8:47 ` [PATCH v2 3/4] ethdev: add API to decode module EEPROM Roman Khromenok
@ 2026-09-28 8:47 ` Roman Khromenok
2026-09-28 18:11 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Stephen Hemminger
4 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-28 8:47 UTC (permalink / raw)
To: dev; +Cc: Thomas Monjalon, Andrew Rybchenko
Add unit tests for rte_eth_module_eeprom_parse().
The tests decode SFF-8079, SFF-8472 and SFF-8636 EEPROM images
built in the test, check the identification and diagnostic fields,
and check that the SFF-8472 diagnostics are skipped when page A2h
is not complete.
Invalid parameters, too short data and unknown module types
must be rejected without invoking the callback.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
app/test/meson.build | 1 +
app/test/test_ethdev_module_eeprom.c | 285 +++++++++++++++++++++++++++
2 files changed, 286 insertions(+)
create mode 100644 app/test/test_ethdev_module_eeprom.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 9dcdb069e8..c6a808d63d 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -74,6 +74,7 @@ source_file_deps = {
'test_errno.c': [],
'test_ethdev_api.c': ['ethdev'],
'test_ethdev_link.c': ['ethdev'],
+ 'test_ethdev_module_eeprom.c': ['ethdev'],
'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],
'test_event_dma_adapter.c': ['dmadev', 'eventdev', 'bus_vdev'],
'test_event_eth_rx_adapter.c': ['ethdev', 'eventdev', 'bus_vdev'],
diff --git a/app/test/test_ethdev_module_eeprom.c b/app/test/test_ethdev_module_eeprom.c
new file mode 100644
index 0000000000..8a09b870da
--- /dev/null
+++ b/app/test/test_ethdev_module_eeprom.c
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Roman Khromenok
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+
+#define MAX_FIELDS 128
+#define MAX_NAME_LEN 64
+#define MAX_VALUE_LEN 128
+
+struct decoded_fields {
+ unsigned int count;
+ struct {
+ char name[MAX_NAME_LEN];
+ char value[MAX_VALUE_LEN];
+ } field[MAX_FIELDS];
+};
+
+static struct decoded_fields fields;
+
+static void
+collect_field(const char *name, const char *value, void *arg)
+{
+ struct decoded_fields *f = arg;
+
+ if (f->count >= MAX_FIELDS)
+ return;
+ strlcpy(f->field[f->count].name, name, MAX_NAME_LEN);
+ strlcpy(f->field[f->count].value, value, MAX_VALUE_LEN);
+ f->count++;
+}
+
+static const char *
+find_field(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < fields.count; i++)
+ if (strcmp(fields.field[i].name, name) == 0)
+ return fields.field[i].value;
+ return NULL;
+}
+
+static int
+parse(uint32_t type, const uint8_t *data, uint32_t length)
+{
+ memset(&fields, 0, sizeof(fields));
+ return rte_eth_module_eeprom_parse(type, data, length, collect_field, &fields);
+}
+
+#define CHECK_FIELD(name, expected) do { \
+ const char *value = find_field(name); \
+ TEST_ASSERT_NOT_NULL(value, "Field \"%s\" not found", name); \
+ TEST_ASSERT(strcmp(value, expected) == 0, \
+ "Field \"%s\": expected \"%s\", got \"%s\"", name, expected, value); \
+} while (0)
+
+#define CHECK_NO_FIELD(name) \
+ TEST_ASSERT_NULL(find_field(name), "Unexpected field \"%s\"", name)
+
+static void
+put_u16(uint8_t *data, unsigned int offset, uint16_t value)
+{
+ data[offset] = value >> 8;
+ data[offset + 1] = value & 0xff;
+}
+
+/* SFP+ 10GBASE-LR module, SFF-8472 layout: page A0h then page A2h */
+static void
+fill_sfp(uint8_t *data)
+{
+ uint8_t *a2 = data + RTE_ETH_MODULE_SFF_8079_LEN;
+
+ memset(data, 0, RTE_ETH_MODULE_SFF_8472_LEN);
+ data[0] = 0x03; /* identifier: SFP */
+ data[1] = 0x04; /* extended identifier */
+ data[2] = 0x07; /* connector: LC */
+ data[3] = 0x20; /* 10G Base-LR */
+ data[11] = 0x06; /* encoding: 64B/66B */
+ data[12] = 103; /* nominal bit rate, units of 100 MBd */
+ memcpy(&data[20], "STARRY ", 16);
+ data[37] = 0x00;
+ data[38] = 0x1b;
+ data[39] = 0x21;
+ memcpy(&data[40], "SFP-10G-LR-20 ", 16);
+ memcpy(&data[56], "A ", 4);
+ memcpy(&data[68], "2024082600001 ", 16);
+ memcpy(&data[84], "240902 ", 8);
+ /* diagnostics implemented, internally calibrated, average RX power */
+ data[92] = 0x68;
+
+ put_u16(a2, 96, 0x2940); /* temperature: 41.25 C */
+ put_u16(a2, 98, 33205); /* voltage: 3.3205 V */
+ put_u16(a2, 100, 19790); /* TX bias: 39.580 mA */
+ put_u16(a2, 102, 7754); /* TX power: 0.7754 mW */
+ put_u16(a2, 104, 6724); /* RX power: 0.6724 mW */
+}
+
+/* QSFP28 module, SFF-8636 layout: lower page then upper page 00h */
+static void
+fill_qsfp(uint8_t *data)
+{
+ memset(data, 0, RTE_ETH_MODULE_SFF_8636_LEN);
+ data[0] = 0x11; /* identifier: QSFP28 */
+ put_u16(data, 22, 0x3040); /* temperature: 48.25 C */
+ put_u16(data, 26, 32638); /* voltage: 3.2638 V */
+ put_u16(data, 34, 1); /* channel 1 RX power: 0.0001 mW */
+ put_u16(data, 42, 21525); /* channel 1 TX bias: 43.050 mA */
+ put_u16(data, 50, 12763); /* channel 1 TX power: 1.2763 mW */
+
+ data[128] = 0x11; /* identifier: QSFP28 */
+ data[130] = 0x07; /* connector: LC */
+ memcpy(&data[148], "FINISAR CORP. ", 16);
+ data[165] = 0x00;
+ data[166] = 0x90;
+ data[167] = 0x65;
+ memcpy(&data[168], "FTLC1157RGPL6-FB", 16);
+ memcpy(&data[184], "A0", 2);
+ memcpy(&data[196], "X24A15P ", 16);
+ memcpy(&data[212], "190826 ", 8);
+ data[220] = 0x08; /* average RX power */
+}
+
+static int
+check_sfp_identity(void)
+{
+ CHECK_FIELD("Identifier", "0x03 (SFP)");
+ CHECK_FIELD("Connector", "0x07 (LC)");
+ CHECK_FIELD("Vendor name", "STARRY");
+ CHECK_FIELD("Vendor OUI", "00:1b:21");
+ CHECK_FIELD("Vendor PN", "SFP-10G-LR-20");
+ CHECK_FIELD("Vendor rev", "A");
+ CHECK_FIELD("Vendor SN", "2024082600001");
+ CHECK_FIELD("Date code", "240902");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8079(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+
+ fill_sfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8079, data, RTE_ETH_MODULE_SFF_8079_LEN),
+ "Failed to parse SFF-8079 data");
+ TEST_ASSERT_SUCCESS(check_sfp_identity(), "Wrong SFF-8079 base information");
+ CHECK_NO_FIELD("Optical diagnostics support");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8472(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+
+ fill_sfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8472, data, sizeof(data)),
+ "Failed to parse SFF-8472 data");
+ TEST_ASSERT_SUCCESS(check_sfp_identity(), "Wrong SFF-8472 base information");
+ CHECK_FIELD("Optical diagnostics support", "Yes");
+ CHECK_FIELD("Laser bias current", "39.580 mA");
+ CHECK_FIELD("Laser output power", "0.7754 mW / -1.10 dBm");
+ CHECK_FIELD("Receiver signal average optical power", "0.6724 mW / -1.72 dBm");
+ CHECK_FIELD("Module temperature", "41.25 degrees C / 106.25 degrees F");
+ CHECK_FIELD("Module voltage", "3.3205 V");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8472_short(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+ const uint32_t lengths[] = {
+ RTE_ETH_MODULE_SFF_8079_LEN,
+ RTE_ETH_MODULE_SFF_8472_LEN - 1,
+ };
+ unsigned int i;
+
+ fill_sfp(data);
+ for (i = 0; i < RTE_DIM(lengths); i++) {
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8472, data, lengths[i]),
+ "Failed to parse SFF-8472 data of %u bytes", lengths[i]);
+ TEST_ASSERT_SUCCESS(check_sfp_identity(),
+ "Wrong SFF-8472 base information from %u bytes", lengths[i]);
+ /* page A2h is not complete, diagnostics must be skipped */
+ CHECK_NO_FIELD("Optical diagnostics support");
+ CHECK_NO_FIELD("Laser bias current");
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_qsfp_8636(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8636_LEN];
+
+ fill_qsfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8636, data, sizeof(data)),
+ "Failed to parse SFF-8636 data");
+ CHECK_FIELD("Identifier", "0x11 (QSFP28)");
+ CHECK_FIELD("Connector", "0x07 (LC)");
+ CHECK_FIELD("Vendor name", "FINISAR CORP.");
+ CHECK_FIELD("Vendor OUI", "00:90:65");
+ CHECK_FIELD("Vendor PN", "FTLC1157RGPL6-FB");
+ CHECK_FIELD("Vendor rev", "A0");
+ CHECK_FIELD("Vendor SN", "X24A15P");
+ CHECK_FIELD("Module temperature", "48.25 degrees C / 118.85 degrees F");
+ CHECK_FIELD("Module voltage", "3.2638 V");
+ /* page 03h is not provided */
+ CHECK_FIELD("Alarm/warning flags implemented", "No");
+ CHECK_FIELD("Laser tx bias current (Channel 1)", "43.050 mA");
+ CHECK_FIELD("Transmit avg optical power (Channel 1)", "1.2763 mW / 1.06 dBm");
+ CHECK_FIELD("Rcvr signal avg optical power(Channel 1)", "0.0001 mW / -40.00 dBm");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_invalid(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8636_MAX_LEN] = {0};
+ const uint32_t types[] = {
+ RTE_ETH_MODULE_SFF_8079,
+ RTE_ETH_MODULE_SFF_8472,
+ RTE_ETH_MODULE_SFF_8436,
+ RTE_ETH_MODULE_SFF_8636,
+ };
+ unsigned int i;
+ int ret;
+
+ /* all supported types require at least one full page */
+ for (i = 0; i < RTE_DIM(types); i++) {
+ ret = parse(types[i], data, 255);
+ TEST_ASSERT_EQUAL(ret, -EINVAL,
+ "Type %u with 255 bytes: expected %d, got %d", types[i], -EINVAL, ret);
+ TEST_ASSERT_EQUAL(fields.count, 0,
+ "Type %u with 255 bytes: callback called %u times",
+ types[i], fields.count);
+ }
+
+ ret = parse(0, data, sizeof(data));
+ TEST_ASSERT_EQUAL(ret, -ENOTSUP, "Type 0: expected %d, got %d", -ENOTSUP, ret);
+ ret = parse(RTE_ETH_MODULE_SFF_8436 + 1, data, sizeof(data));
+ TEST_ASSERT_EQUAL(ret, -ENOTSUP, "Unknown type: expected %d, got %d", -ENOTSUP, ret);
+ TEST_ASSERT_EQUAL(fields.count, 0, "Unknown type: callback called %u times",
+ fields.count);
+
+ ret = rte_eth_module_eeprom_parse(RTE_ETH_MODULE_SFF_8079, NULL,
+ RTE_ETH_MODULE_SFF_8079_LEN, collect_field, &fields);
+ TEST_ASSERT_EQUAL(ret, -EINVAL, "NULL data: expected %d, got %d", -EINVAL, ret);
+ ret = rte_eth_module_eeprom_parse(RTE_ETH_MODULE_SFF_8079, data,
+ RTE_ETH_MODULE_SFF_8079_LEN, NULL, NULL);
+ TEST_ASSERT_EQUAL(ret, -EINVAL, "NULL callback: expected %d, got %d", -EINVAL, ret);
+
+ return TEST_SUCCESS;
+}
+
+static struct unit_test_suite module_eeprom_testsuite = {
+ .suite_name = "ethdev module EEPROM decoding",
+ .setup = NULL,
+ .teardown = NULL,
+ .unit_test_cases = {
+ TEST_CASE(test_module_eeprom_sfp_8079),
+ TEST_CASE(test_module_eeprom_sfp_8472),
+ TEST_CASE(test_module_eeprom_sfp_8472_short),
+ TEST_CASE(test_module_eeprom_qsfp_8636),
+ TEST_CASE(test_module_eeprom_invalid),
+ TEST_CASES_END()
+ }
+};
+
+static int
+test_module_eeprom(void)
+{
+ return unit_test_suite_runner(&module_eeprom_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_module_eeprom, NOHUGE_OK, ASAN_OK, test_module_eeprom);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] ethdev: add API to decode module EEPROM
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
` (3 preceding siblings ...)
2026-09-28 8:47 ` [PATCH v2 4/4] test: add ethdev module EEPROM decoding tests Roman Khromenok
@ 2026-09-28 18:11 ` Stephen Hemminger
4 siblings, 0 replies; 19+ messages in thread
From: Stephen Hemminger @ 2026-09-28 18:11 UTC (permalink / raw)
To: Roman Khromenok; +Cc: dev, Thomas Monjalon, Andrew Rybchenko
On Mon, 28 Sep 2026 10:47:25 +0200
Roman Khromenok <roma55592@yandex.ru> wrote:
> Since 22.07, ethdev contains SFF-8079, SFF-8472 and SFF-8636 decoders
> for plugin module EEPROM (SFP, QSFP), ported from ethtool.
> They are reachable only through the telemetry command
> /ethdev/module_eeprom, which reads the EEPROM of a DPDK port
> and returns the result as a telemetry dictionary.
>
> An application which needs to show transceiver information
> (vendor, part number, serial number, optical power) in its own
> interface cannot reuse this code and has to duplicate it.
> It is also common to have some ports managed by DPDK and others
> by the Linux kernel; the kernel returns the EEPROM through the ethtool
> interface with the same module types and layout, but there is no way
> to decode it with DPDK.
>
> This series exposes the decoders through a small experimental function:
>
> int rte_eth_module_eeprom_parse(uint32_t type,
> const uint8_t *data, uint32_t length,
> rte_eth_module_eeprom_field_cb cb, void *arg);
>
> Each decoded field is reported to the callback as a pair of strings,
> the same names and values as in the telemetry output.
> The function does not access any device and does not require
> EAL initialization.
>
> Patch 1 makes the decoders write to a callback instead of
> the telemetry dictionary; the telemetry output is unchanged.
> Patch 2 adds the buffer length checks needed by a public API:
> currently SFF-8079 and SFF-8472 decoders do not receive the length.
> Patch 3 adds the API with documentation and release notes.
> Patch 4 adds unit tests.
>
> v2:
> - patch 2: do not read the SFF-8636 alarm and warning thresholds
> from page 03h when only 256 bytes are available (out of bounds read
> found by ASan in the new unit test).
>
> Roman Khromenok (4):
> ethdev: decouple SFF module EEPROM decoders from telemetry
> ethdev: check module EEPROM length before decoding
> ethdev: add API to decode module EEPROM
> test: add ethdev module EEPROM decoding tests
>
> .mailmap | 1 +
> app/test/meson.build | 1 +
> app/test/test_ethdev_module_eeprom.c | 285 ++++++++++++++++++++++++
> doc/guides/prog_guide/ethdev/ethdev.rst | 44 ++++
> doc/guides/rel_notes/release_26_11.rst | 7 +
> lib/ethdev/rte_ethdev.c | 20 ++
> lib/ethdev/rte_ethdev.h | 50 +++++
> lib/ethdev/sff_8079.c | 20 +-
> lib/ethdev/sff_8472.c | 2 +-
> lib/ethdev/sff_8636.c | 74 +++---
> lib/ethdev/sff_common.c | 14 +-
> lib/ethdev/sff_common.h | 14 +-
> lib/ethdev/sff_telemetry.c | 94 +++++---
> lib/ethdev/sff_telemetry.h | 25 ++-
> 14 files changed, 552 insertions(+), 99 deletions(-)
> create mode 100644 app/test/test_ethdev_module_eeprom.c
>
AI review had good point that bugfix should be first patch and cc to stable
Review: [PATCH v2 0/4] ethdev: module EEPROM decoding API
Each commit builds with -Dwerror=true, and ethdev_module_eeprom
passes.
Patch 2 is a real bug fix, not just preparation. Before this series,
sff_8636_dom_parse() reads the page 03h thresholds (0x200-0x247)
unconditionally, while the telemetry handler allocates exactly
minfo.eeprom_len. Several in-tree drivers report less than 640
bytes for QSFP modules: i40e reports QSFP+ as SFF-8436 with 256,
bnxt reports flat memory QSFP28 with 256, xsc reports 256 or 512.
/ethdev/module_eeprom on those ports reads past the end of the heap
buffer. This has been present since 22.07.
Please make the fix the first patch, standalone against the current
rte_tel_data code, so it can go to stable:
Fixes: c42754fd581a ("ethdev: support SFF-8636 module telemetry")
Cc: stable@dpdk.org
The commit message should say what it fixes (heap over-read in the
telemetry handler) rather than describe it as preparation. The
sff_output refactor and the new API then go on top.
Patch 1/4 ethdev: decouple SFF module EEPROM decoders from telemetry
Info
- struct sff_output, and after patch 2 sff_decode_module_eeprom(),
are no longer telemetry specific but still live in
sff_telemetry.[ch]. Every decoder still calls
ssf_add_dict_string(), a misspelled telemetry name for what is now
a one line callback dispatch. This patch already touches every
signature; consider renaming it (e.g. sff_output_field()) and
moving the generic parts to sff_common.h.
Patch 2/4 ethdev: check module EEPROM length before decoding
Warning
- Missing Fixes: and Cc: stable@dpdk.org, and it depends on the
sff_output refactor in patch 1, so it cannot be backported as is.
See summary above.
Patch 3/4 ethdev: add API to decode module EEPROM
Warning
- A2_OFFSET_TO_RXPWRx() in sff_8472.c casts the byte buffer to
const uint32_t * and befloattoh() dereferences it. The RXPWR
calibration offsets are 4-byte aligned relative to data, so this
only worked because the telemetry buffer comes from calloc(). The
new API accepts any const uint8_t * from the application; a
misaligned buffer makes this an unaligned load (UB, UBSan reports
it, traps on strict alignment targets). Reached when the module
sets the externally calibrated bit (byte 92, bit 4). Read with
memcpy() into a uint32_t, then rte_be_to_cpu_32().
Info
- The field names and value strings become the de facto API. The
test in patch 4 already pins "Rcvr signal avg optical
power(Channel 1)", missing space included, so fixing that later
breaks applications that match on names. Either clean up the
names before exposing them, or state in the doxygen that names
and value formats are for display only and may change.
Applications wanting numeric values (RX power, temperature) must
parse "0.6724 mW / -1.72 dBm"; worth considering while the API is
still experimental. A void callback also gives the caller no way
to stop early.
- The doxygen documents the SFF-8472 length rule but not the
SFF-8636 one: thresholds and alarm flags are decoded only when
length == RTE_ETH_MODULE_SFF_8636_MAX_LEN exactly. With an
application supplied buffer, an oversized length silently drops
them. Use >= and document it next to the SFF-8472 note.
- ethdev.rst and the release note say Linux ethtool data uses the
same types and layout. That holds for the ETHTOOL_GMODULEINFO /
ETHTOOL_GMODULEEEPROM ioctls. The netlink MODULE_EEPROM_GET
interface is page addressed and carries no module type, so its
data must be reassembled first. Name the ioctl.
- "Plugin module" in the doc heading and release note; the usual
term is "pluggable module".
Patch 4/4 test: add ethdev module EEPROM decoding tests
Info
- No case covers SFF-8636 with 640 bytes and paging present (byte 2
bit 2 clear), which is the path patch 2 changes. Add one checking
"Alarm/warning flags implemented" is "Yes" and a threshold field
is decoded.
- The test is registered as ethdev_module_eeprom; nearly all fast
tests use the _autotest suffix.
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v3 0/6] ethdev: add API to decode module EEPROM
2026-09-27 11:20 [PATCH 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
` (4 preceding siblings ...)
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
@ 2026-09-29 7:07 ` Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 1/6] ethdev: fix out-of-bounds read of SFF-8636 thresholds Roman Khromenok
` (6 more replies)
5 siblings, 7 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-29 7:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Thomas Monjalon, Andrew Rybchenko
Since 22.07, ethdev contains SFF-8079, SFF-8472 and SFF-8636 decoders
for pluggable module EEPROM (SFP, QSFP), ported from ethtool.
They are reachable only through the telemetry command
/ethdev/module_eeprom, which reads the EEPROM of a DPDK port
and returns the result as a telemetry dictionary.
An application which needs to show transceiver information
(vendor, part number, serial number, optical power) in its own
interface cannot reuse this code and has to duplicate it.
It is also common to have some ports managed by DPDK and others
by the Linux kernel; the kernel returns the EEPROM through the ethtool
ioctls with the same module types and layout, but there is no way
to decode it with DPDK.
This series exposes the decoders through a small experimental function:
int rte_eth_module_eeprom_parse(uint32_t type,
const uint8_t *data, uint32_t length,
rte_eth_module_eeprom_field_cb cb, void *arg);
Each decoded field is reported to the callback as a pair of strings,
the same names and values as in the telemetry output.
The function does not access any device and does not require
EAL initialization.
Patch 1 fixes an out-of-bounds read in the telemetry command
and is intended for stable.
Patches 2 to 4 prepare the decoders to be called by applications.
Patch 5 adds the API, patch 6 adds unit tests.
v3:
- move the SFF-8636 thresholds fix to a standalone first patch
against the current code, with Fixes and Cc stable
- rename ssf_add_dict_string() to sff_output_field() and move
the generic declarations from sff_telemetry.h to sff_common.h
- decode the SFF-8636 thresholds when the length is at least 640 bytes,
not only exactly 640
- new patch to avoid the unaligned float access in the SFF-8472
calibration
- document that field names and value formats are for display only,
document the SFF-8636 length rule, name the ethtool ioctls
and the netlink difference, use "pluggable module"
- tests: add SFF-8636 with page 03h and unaligned SFF-8472 with
external calibration, rename to ethdev_module_eeprom_autotest
Not changed in v3: the callback keeps returning void. Decoding one
module is a pass over a buffer of at most 640 bytes reporting about
a hundred fields, so stopping early does not seem useful.
A numeric API for diagnostic values could be added later
on top of the same decoders if needed.
v2:
- do not read the SFF-8636 alarm and warning thresholds from page 03h
when only 256 bytes are available (out of bounds read found by ASan
in the new unit test)
Roman Khromenok (6):
ethdev: fix out-of-bounds read of SFF-8636 thresholds
ethdev: decouple SFF module EEPROM decoders from telemetry
ethdev: check module EEPROM length before decoding
ethdev: avoid unaligned access in SFF-8472 decoder
ethdev: add API to decode module EEPROM
test: add ethdev module EEPROM decoding tests
.mailmap | 1 +
app/test/meson.build | 1 +
app/test/test_ethdev_module_eeprom.c | 334 ++++++++++++++++++++++++
doc/guides/prog_guide/ethdev/ethdev.rst | 50 ++++
doc/guides/rel_notes/release_26_11.rst | 8 +
lib/ethdev/rte_ethdev.c | 21 ++
lib/ethdev/rte_ethdev.h | 57 ++++
lib/ethdev/sff_8079.c | 204 +++++++--------
lib/ethdev/sff_8472.c | 32 ++-
lib/ethdev/sff_8636.c | 280 ++++++++++----------
lib/ethdev/sff_common.c | 103 +++++---
lib/ethdev/sff_common.h | 45 +++-
lib/ethdev/sff_telemetry.c | 81 +++---
lib/ethdev/sff_telemetry.h | 14 -
14 files changed, 876 insertions(+), 355 deletions(-)
create mode 100644 app/test/test_ethdev_module_eeprom.c
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v3 1/6] ethdev: fix out-of-bounds read of SFF-8636 thresholds
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
@ 2026-09-29 7:07 ` Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 2/6] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
` (5 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-29 7:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Thomas Monjalon, Andrew Rybchenko, stable
The SFF-8636 decoder reads the alarm and warning thresholds
from page 03h (offsets 0x200 to 0x247) unconditionally,
but they are printed only if the EEPROM data is 640 bytes long.
The telemetry command /ethdev/module_eeprom allocates exactly
the length reported by the driver, and several drivers report
256 bytes for QSFP modules, for example i40e for SFF-8436
and bnxt for flat memory QSFP28.
On such ports, the command reads past the end of the heap buffer.
Read the thresholds only when they are going to be printed.
Fixes: c42754fd581a ("ethdev: support SFF-8636 module telemetry")
Cc: stable@dpdk.org
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
v3: new patch, split from the length checks to be backported
.mailmap | 1 +
lib/ethdev/sff_8636.c | 52 ++++++++++++++++++++++---------------------
2 files changed, 28 insertions(+), 25 deletions(-)
diff --git a/.mailmap b/.mailmap
index 57f7a9f87a..45d4e92fbd 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1445,6 +1445,7 @@ Romain Delhomel <romain.delhomel@6wind.com>
Roman Dementiev <roman.dementiev@intel.com>
Roman Fridlyand <roman.fridlyand@intel.com>
Roman Kapl <rka@sysgo.com>
+Roman Khromenok <roma55592@yandex.ru>
Roman Korynkevych <romanx.korynkevych@intel.com>
Roman Storozhenko <roman.storozhenko@intel.com>
Roman Zhukov <roman.zhukov@arknetworks.am> <roman.zhukov@oktetlabs.ru>
diff --git a/lib/ethdev/sff_8636.c b/lib/ethdev/sff_8636.c
index 6b65f47efe..9dfff41a3b 100644
--- a/lib/ethdev/sff_8636.c
+++ b/lib/ethdev/sff_8636.c
@@ -600,34 +600,36 @@ static void sff_8636_dom_parse(const uint8_t *data, struct sff_diags *sd)
{
int i = 0;
- /* Monitoring Thresholds for Alarms and Warnings */
sd->sfp_voltage[SFF_MCURR] = SFF_OFFSET_TO_U16(SFF_8636_VCC_CURR);
- sd->sfp_voltage[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HALRM);
- sd->sfp_voltage[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LALRM);
- sd->sfp_voltage[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HWARN);
- sd->sfp_voltage[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LWARN);
-
sd->sfp_temp[SFF_MCURR] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_CURR);
- sd->sfp_temp[SFF_HALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HALRM);
- sd->sfp_temp[SFF_LALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LALRM);
- sd->sfp_temp[SFF_HWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HWARN);
- sd->sfp_temp[SFF_LWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LWARN);
-
- sd->bias_cur[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HALRM);
- sd->bias_cur[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LALRM);
- sd->bias_cur[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HWARN);
- sd->bias_cur[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LWARN);
-
- sd->tx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HALRM);
- sd->tx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LALRM);
- sd->tx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HWARN);
- sd->tx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LWARN);
-
- sd->rx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HALRM);
- sd->rx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LALRM);
- sd->rx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HWARN);
- sd->rx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LWARN);
+ /* Monitoring Thresholds for Alarms and Warnings are in page 03h */
+ if (sd->supports_alarms) {
+ sd->sfp_voltage[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HALRM);
+ sd->sfp_voltage[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LALRM);
+ sd->sfp_voltage[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_HWARN);
+ sd->sfp_voltage[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_VCC_LWARN);
+
+ sd->sfp_temp[SFF_HALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HALRM);
+ sd->sfp_temp[SFF_LALRM] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LALRM);
+ sd->sfp_temp[SFF_HWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_HWARN);
+ sd->sfp_temp[SFF_LWARN] = SFF_8636_OFFSET_TO_TEMP(SFF_8636_TEMP_LWARN);
+
+ sd->bias_cur[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HALRM);
+ sd->bias_cur[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LALRM);
+ sd->bias_cur[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_HWARN);
+ sd->bias_cur[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_BIAS_LWARN);
+
+ sd->tx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HALRM);
+ sd->tx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LALRM);
+ sd->tx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_HWARN);
+ sd->tx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_TX_PWR_LWARN);
+
+ sd->rx_power[SFF_HALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HALRM);
+ sd->rx_power[SFF_LALRM] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LALRM);
+ sd->rx_power[SFF_HWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_HWARN);
+ sd->rx_power[SFF_LWARN] = SFF_OFFSET_TO_U16(SFF_8636_RX_PWR_LWARN);
+ }
/* Channel Specific Data */
for (i = 0; i < SFF_MAX_CHANNEL_NUM; i++) {
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v3 2/6] ethdev: decouple SFF module EEPROM decoders from telemetry
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 1/6] ethdev: fix out-of-bounds read of SFF-8636 thresholds Roman Khromenok
@ 2026-09-29 7:07 ` Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 3/6] ethdev: check module EEPROM length before decoding Roman Khromenok
` (4 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-29 7:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Thomas Monjalon, Andrew Rybchenko
The SFF-8079, SFF-8472 and SFF-8636 decoders write their results
directly into a telemetry dictionary, so the decoding logic cannot
be reused outside of the telemetry command.
Introduce an internal output descriptor with a per-field callback
and pass it to the decoders instead of the telemetry data.
The telemetry handler becomes one of the callback consumers,
its output is unchanged.
As the field reporting function is no longer telemetry specific,
rename ssf_add_dict_string() to sff_output_field()
and move it with the decoder declarations to sff_common.h.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
v3: rename ssf_add_dict_string() to sff_output_field(),
move generic declarations to sff_common.h
lib/ethdev/sff_8079.c | 204 ++++++++++++++++-----------------
lib/ethdev/sff_8472.c | 20 ++--
lib/ethdev/sff_8636.c | 226 ++++++++++++++++++-------------------
lib/ethdev/sff_common.c | 72 ++++++------
lib/ethdev/sff_common.h | 37 ++++--
lib/ethdev/sff_telemetry.c | 68 +++++------
lib/ethdev/sff_telemetry.h | 14 ---
7 files changed, 327 insertions(+), 314 deletions(-)
diff --git a/lib/ethdev/sff_8079.c b/lib/ethdev/sff_8079.c
index 91ad25ffcd..9632a26af3 100644
--- a/lib/ethdev/sff_8079.c
+++ b/lib/ethdev/sff_8079.c
@@ -7,12 +7,12 @@
#include "sff_common.h"
-static void sff_8079_show_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_identifier(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_identifier(data, 0, d);
}
-static void sff_8079_show_ext_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_ext_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -29,15 +29,15 @@ static void sff_8079_show_ext_identifier(const uint8_t *data, struct rte_tel_dat
strlcat(val_string, tmp, sizeof(val_string));
} else
strlcat(val_string, " (unknown)", sizeof(val_string));
- ssf_add_dict_string(d, "Extended identifier", val_string);
+ sff_output_field(d, "Extended identifier", val_string);
}
-static void sff_8079_show_connector(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_connector(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_connector(data, 2, d);
}
-static void sff_8079_show_transceiver(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_transceiver(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Transceiver type";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -45,194 +45,194 @@ static void sff_8079_show_transceiver(const uint8_t *data, struct rte_tel_data *
snprintf(val_string, sizeof(val_string),
"0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x",
data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[36]);
- ssf_add_dict_string(d, "Transceiver codes", val_string);
+ sff_output_field(d, "Transceiver codes", val_string);
/* 10G Ethernet Compliance Codes */
if (data[3] & (1 << 7))
- ssf_add_dict_string(d, "10G Ethernet transceiver type",
+ sff_output_field(d, "10G Ethernet transceiver type",
"10G Ethernet: 10G Base-ER [SFF-8472 rev10.4 onwards]");
if (data[3] & (1 << 6))
- ssf_add_dict_string(d, name, "10G Ethernet: 10G Base-LRM");
+ sff_output_field(d, name, "10G Ethernet: 10G Base-LRM");
if (data[3] & (1 << 5))
- ssf_add_dict_string(d, name, "10G Ethernet: 10G Base-LR");
+ sff_output_field(d, name, "10G Ethernet: 10G Base-LR");
if (data[3] & (1 << 4))
- ssf_add_dict_string(d, name, "10G Ethernet: 10G Base-SR");
+ sff_output_field(d, name, "10G Ethernet: 10G Base-SR");
/* Infiniband Compliance Codes */
if (data[3] & (1 << 3))
- ssf_add_dict_string(d, name, "Infiniband: 1X SX");
+ sff_output_field(d, name, "Infiniband: 1X SX");
if (data[3] & (1 << 2))
- ssf_add_dict_string(d, name, "Infiniband: 1X LX");
+ sff_output_field(d, name, "Infiniband: 1X LX");
if (data[3] & (1 << 1))
- ssf_add_dict_string(d, name, "Infiniband: 1X Copper Active");
+ sff_output_field(d, name, "Infiniband: 1X Copper Active");
if (data[3] & (1 << 0))
- ssf_add_dict_string(d, name, "Infiniband: 1X Copper Passive");
+ sff_output_field(d, name, "Infiniband: 1X Copper Passive");
/* ESCON Compliance Codes */
if (data[4] & (1 << 7))
- ssf_add_dict_string(d, name, "ESCON: ESCON MMF, 1310nm LED");
+ sff_output_field(d, name, "ESCON: ESCON MMF, 1310nm LED");
if (data[4] & (1 << 6))
- ssf_add_dict_string(d, name, "ESCON: ESCON SMF, 1310nm Laser");
+ sff_output_field(d, name, "ESCON: ESCON SMF, 1310nm Laser");
/* SONET Compliance Codes */
if (data[4] & (1 << 5))
- ssf_add_dict_string(d, name, "SONET: OC-192, short reach");
+ sff_output_field(d, name, "SONET: OC-192, short reach");
if (data[4] & (1 << 4))
- ssf_add_dict_string(d, name, "SONET: SONET reach specifier bit 1");
+ sff_output_field(d, name, "SONET: SONET reach specifier bit 1");
if (data[4] & (1 << 3))
- ssf_add_dict_string(d, name, "SONET: SONET reach specifier bit 2");
+ sff_output_field(d, name, "SONET: SONET reach specifier bit 2");
if (data[4] & (1 << 2))
- ssf_add_dict_string(d, name, "SONET: OC-48, long reach");
+ sff_output_field(d, name, "SONET: OC-48, long reach");
if (data[4] & (1 << 1))
- ssf_add_dict_string(d, name, "SONET: OC-48, intermediate reach");
+ sff_output_field(d, name, "SONET: OC-48, intermediate reach");
if (data[4] & (1 << 0))
- ssf_add_dict_string(d, name, "SONET: OC-48, short reach");
+ sff_output_field(d, name, "SONET: OC-48, short reach");
if (data[5] & (1 << 6))
- ssf_add_dict_string(d, name, "SONET: OC-12, single mode, long reach");
+ sff_output_field(d, name, "SONET: OC-12, single mode, long reach");
if (data[5] & (1 << 5))
- ssf_add_dict_string(d, name, "SONET: OC-12, single mode, inter. reach");
+ sff_output_field(d, name, "SONET: OC-12, single mode, inter. reach");
if (data[5] & (1 << 4))
- ssf_add_dict_string(d, name, "SONET: OC-12, short reach");
+ sff_output_field(d, name, "SONET: OC-12, short reach");
if (data[5] & (1 << 2))
- ssf_add_dict_string(d, name, "SONET: OC-3, single mode, long reach");
+ sff_output_field(d, name, "SONET: OC-3, single mode, long reach");
if (data[5] & (1 << 1))
- ssf_add_dict_string(d, name, "SONET: OC-3, single mode, inter. reach");
+ sff_output_field(d, name, "SONET: OC-3, single mode, inter. reach");
if (data[5] & (1 << 0))
- ssf_add_dict_string(d, name, "SONET: OC-3, short reach");
+ sff_output_field(d, name, "SONET: OC-3, short reach");
/* Ethernet Compliance Codes */
if (data[6] & (1 << 7))
- ssf_add_dict_string(d, name, "Ethernet: BASE-PX");
+ sff_output_field(d, name, "Ethernet: BASE-PX");
if (data[6] & (1 << 6))
- ssf_add_dict_string(d, name, "Ethernet: BASE-BX10");
+ sff_output_field(d, name, "Ethernet: BASE-BX10");
if (data[6] & (1 << 5))
- ssf_add_dict_string(d, name, "Ethernet: 100BASE-FX");
+ sff_output_field(d, name, "Ethernet: 100BASE-FX");
if (data[6] & (1 << 4))
- ssf_add_dict_string(d, name, "Ethernet: 100BASE-LX/LX10");
+ sff_output_field(d, name, "Ethernet: 100BASE-LX/LX10");
if (data[6] & (1 << 3))
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-T");
+ sff_output_field(d, name, "Ethernet: 1000BASE-T");
if (data[6] & (1 << 2))
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-CX");
+ sff_output_field(d, name, "Ethernet: 1000BASE-CX");
if (data[6] & (1 << 1))
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-LX");
+ sff_output_field(d, name, "Ethernet: 1000BASE-LX");
if (data[6] & (1 << 0))
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-SX");
+ sff_output_field(d, name, "Ethernet: 1000BASE-SX");
/* Fibre Channel link length */
if (data[7] & (1 << 7))
- ssf_add_dict_string(d, name, "FC: very long distance (V)");
+ sff_output_field(d, name, "FC: very long distance (V)");
if (data[7] & (1 << 6))
- ssf_add_dict_string(d, name, "FC: short distance (S)");
+ sff_output_field(d, name, "FC: short distance (S)");
if (data[7] & (1 << 5))
- ssf_add_dict_string(d, name, "FC: intermediate distance (I)");
+ sff_output_field(d, name, "FC: intermediate distance (I)");
if (data[7] & (1 << 4))
- ssf_add_dict_string(d, name, "FC: long distance (L)");
+ sff_output_field(d, name, "FC: long distance (L)");
if (data[7] & (1 << 3))
- ssf_add_dict_string(d, name, "FC: medium distance (M)");
+ sff_output_field(d, name, "FC: medium distance (M)");
/* Fibre Channel transmitter technology */
if (data[7] & (1 << 2))
- ssf_add_dict_string(d, name, "FC: Shortwave laser, linear Rx (SA)");
+ sff_output_field(d, name, "FC: Shortwave laser, linear Rx (SA)");
if (data[7] & (1 << 1))
- ssf_add_dict_string(d, name, "FC: Longwave laser (LC)");
+ sff_output_field(d, name, "FC: Longwave laser (LC)");
if (data[7] & (1 << 0))
- ssf_add_dict_string(d, name, "FC: Electrical inter-enclosure (EL)");
+ sff_output_field(d, name, "FC: Electrical inter-enclosure (EL)");
if (data[8] & (1 << 7))
- ssf_add_dict_string(d, name, "FC: Electrical intra-enclosure (EL)");
+ sff_output_field(d, name, "FC: Electrical intra-enclosure (EL)");
if (data[8] & (1 << 6))
- ssf_add_dict_string(d, name, "FC: Shortwave laser w/o OFC (SN)");
+ sff_output_field(d, name, "FC: Shortwave laser w/o OFC (SN)");
if (data[8] & (1 << 5))
- ssf_add_dict_string(d, name, "FC: Shortwave laser with OFC (SL)");
+ sff_output_field(d, name, "FC: Shortwave laser with OFC (SL)");
if (data[8] & (1 << 4))
- ssf_add_dict_string(d, name, "FC: Longwave laser (LL)");
+ sff_output_field(d, name, "FC: Longwave laser (LL)");
if (data[8] & (1 << 3))
- ssf_add_dict_string(d, name, "Active Cable");
+ sff_output_field(d, name, "Active Cable");
if (data[8] & (1 << 2))
- ssf_add_dict_string(d, name, "Passive Cable");
+ sff_output_field(d, name, "Passive Cable");
if (data[8] & (1 << 1))
- ssf_add_dict_string(d, name, "FC: Copper FC-BaseT");
+ sff_output_field(d, name, "FC: Copper FC-BaseT");
/* Fibre Channel transmission media */
if (data[9] & (1 << 7))
- ssf_add_dict_string(d, name, "FC: Twin Axial Pair (TW)");
+ sff_output_field(d, name, "FC: Twin Axial Pair (TW)");
if (data[9] & (1 << 6))
- ssf_add_dict_string(d, name, "FC: Twisted Pair (TP)");
+ sff_output_field(d, name, "FC: Twisted Pair (TP)");
if (data[9] & (1 << 5))
- ssf_add_dict_string(d, name, "FC: Miniature Coax (MI)");
+ sff_output_field(d, name, "FC: Miniature Coax (MI)");
if (data[9] & (1 << 4))
- ssf_add_dict_string(d, name, "FC: Video Coax (TV)");
+ sff_output_field(d, name, "FC: Video Coax (TV)");
if (data[9] & (1 << 3))
- ssf_add_dict_string(d, name, "FC: Multimode, 62.5um (M6)");
+ sff_output_field(d, name, "FC: Multimode, 62.5um (M6)");
if (data[9] & (1 << 2))
- ssf_add_dict_string(d, name, "FC: Multimode, 50um (M5)");
+ sff_output_field(d, name, "FC: Multimode, 50um (M5)");
if (data[9] & (1 << 0))
- ssf_add_dict_string(d, name, "FC: Single Mode (SM)");
+ sff_output_field(d, name, "FC: Single Mode (SM)");
/* Fibre Channel speed */
if (data[10] & (1 << 7))
- ssf_add_dict_string(d, name, "FC: 1200 MBytes/sec");
+ sff_output_field(d, name, "FC: 1200 MBytes/sec");
if (data[10] & (1 << 6))
- ssf_add_dict_string(d, name, "FC: 800 MBytes/sec");
+ sff_output_field(d, name, "FC: 800 MBytes/sec");
if (data[10] & (1 << 4))
- ssf_add_dict_string(d, name, "FC: 400 MBytes/sec");
+ sff_output_field(d, name, "FC: 400 MBytes/sec");
if (data[10] & (1 << 2))
- ssf_add_dict_string(d, name, "FC: 200 MBytes/sec");
+ sff_output_field(d, name, "FC: 200 MBytes/sec");
if (data[10] & (1 << 0))
- ssf_add_dict_string(d, name, "FC: 100 MBytes/sec");
+ sff_output_field(d, name, "FC: 100 MBytes/sec");
/* Extended Specification Compliance Codes from SFF-8024 */
switch (data[36]) {
case 0x1:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
break;
case 0x2:
- ssf_add_dict_string(d, name, "Extended: 100G Base-SR4 or 25GBase-SR");
+ sff_output_field(d, name, "Extended: 100G Base-SR4 or 25GBase-SR");
break;
case 0x3:
- ssf_add_dict_string(d, name, "Extended: 100G Base-LR4 or 25GBase-LR");
+ sff_output_field(d, name, "Extended: 100G Base-LR4 or 25GBase-LR");
break;
case 0x4:
- ssf_add_dict_string(d, name, "Extended: 100G Base-ER4 or 25GBase-ER");
+ sff_output_field(d, name, "Extended: 100G Base-ER4 or 25GBase-ER");
break;
case 0x8:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
break;
case 0xb:
- ssf_add_dict_string(d, name, "Extended: 100G Base-CR4 or 25G Base-CR CA-L");
+ sff_output_field(d, name, "Extended: 100G Base-CR4 or 25G Base-CR CA-L");
break;
case 0xc:
- ssf_add_dict_string(d, name, "Extended: 25G Base-CR CA-S");
+ sff_output_field(d, name, "Extended: 25G Base-CR CA-S");
break;
case 0xd:
- ssf_add_dict_string(d, name, "Extended: 25G Base-CR CA-N");
+ sff_output_field(d, name, "Extended: 25G Base-CR CA-N");
break;
case 0x16:
- ssf_add_dict_string(d, name, "Extended: 10Gbase-T with SFI electrical interface");
+ sff_output_field(d, name, "Extended: 10Gbase-T with SFI electrical interface");
break;
case 0x18:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
break;
case 0x19:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
break;
case 0x1c:
- ssf_add_dict_string(d, name, "Extended: 10Gbase-T Short Reach");
+ sff_output_field(d, name, "Extended: 10Gbase-T Short Reach");
break;
default:
break;
}
}
-static void sff_8079_show_encoding(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_encoding(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_encoding(data, 11, RTE_ETH_MODULE_SFF_8472, d);
}
-static void sff_8079_show_rate_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_rate_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -259,17 +259,17 @@ static void sff_8079_show_rate_identifier(const uint8_t *data, struct rte_tel_da
strlcat(val_string, " (reserved or unknown)", sizeof(val_string));
break;
}
- ssf_add_dict_string(d, "Rate identifier", val_string);
+ sff_output_field(d, "Rate identifier", val_string);
}
-static void sff_8079_show_oui(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_oui(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_oui(data, 37, d);
}
static void
sff_8079_show_wavelength_or_copper_compliance(const uint8_t *data,
- struct rte_tel_data *d)
+ struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -287,7 +287,7 @@ sff_8079_show_wavelength_or_copper_compliance(const uint8_t *data,
break;
}
strlcat(val_string, " [SFF-8472 rev10.4 only]", sizeof(val_string));
- ssf_add_dict_string(d, "Passive Cu cmplnce.", val_string);
+ sff_output_field(d, "Passive Cu cmplnce.", val_string);
} else if (data[8] & (1 << 3)) {
snprintf(val_string, sizeof(val_string), "0x%02x", data[60]);
switch (data[60]) {
@@ -305,50 +305,50 @@ sff_8079_show_wavelength_or_copper_compliance(const uint8_t *data,
break;
}
strlcat(val_string, " [SFF-8472 rev10.4 only]", sizeof(val_string));
- ssf_add_dict_string(d, "Active Cu cmplnce.", val_string);
+ sff_output_field(d, "Active Cu cmplnce.", val_string);
} else {
snprintf(val_string, sizeof(val_string), "%unm", (data[60] << 8) | data[61]);
- ssf_add_dict_string(d, "Laser wavelength", val_string);
+ sff_output_field(d, "Laser wavelength", val_string);
}
}
-static void sff_8079_show_options(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8079_show_options(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Option";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
snprintf(val_string, sizeof(val_string), "0x%02x 0x%02x", data[64], data[65]);
- ssf_add_dict_string(d, "Option values", val_string);
+ sff_output_field(d, "Option values", val_string);
if (data[65] & (1 << 1))
- ssf_add_dict_string(d, name, "RX_LOS implemented");
+ sff_output_field(d, name, "RX_LOS implemented");
if (data[65] & (1 << 2))
- ssf_add_dict_string(d, name, "RX_LOS implemented, inverted");
+ sff_output_field(d, name, "RX_LOS implemented, inverted");
if (data[65] & (1 << 3))
- ssf_add_dict_string(d, name, "TX_FAULT implemented");
+ sff_output_field(d, name, "TX_FAULT implemented");
if (data[65] & (1 << 4))
- ssf_add_dict_string(d, name, "TX_DISABLE implemented");
+ sff_output_field(d, name, "TX_DISABLE implemented");
if (data[65] & (1 << 5))
- ssf_add_dict_string(d, name, "RATE_SELECT implemented");
+ sff_output_field(d, name, "RATE_SELECT implemented");
if (data[65] & (1 << 6))
- ssf_add_dict_string(d, name, "Tunable transmitter technology");
+ sff_output_field(d, name, "Tunable transmitter technology");
if (data[65] & (1 << 7))
- ssf_add_dict_string(d, name, "Receiver decision threshold implemented");
+ sff_output_field(d, name, "Receiver decision threshold implemented");
if (data[64] & (1 << 0))
- ssf_add_dict_string(d, name, "Linear receiver output implemented");
+ sff_output_field(d, name, "Linear receiver output implemented");
if (data[64] & (1 << 1))
- ssf_add_dict_string(d, name, "Power level 2 requirement");
+ sff_output_field(d, name, "Power level 2 requirement");
if (data[64] & (1 << 2))
- ssf_add_dict_string(d, name, "Cooled transceiver implemented");
+ sff_output_field(d, name, "Cooled transceiver implemented");
if (data[64] & (1 << 3))
- ssf_add_dict_string(d, name, "Retimer or CDR implemented");
+ sff_output_field(d, name, "Retimer or CDR implemented");
if (data[64] & (1 << 4))
- ssf_add_dict_string(d, name, "Paging implemented");
+ sff_output_field(d, name, "Paging implemented");
if (data[64] & (1 << 5))
- ssf_add_dict_string(d, name, "Power level 3 requirement");
+ sff_output_field(d, name, "Power level 3 requirement");
}
-void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d)
+void sff_8079_show_all(const uint8_t *data, struct sff_output *d)
{
sff_8079_show_identifier(data, d);
if (((data[0] == 0x02) || (data[0] == 0x03)) && (data[1] == 0x04)) {
@@ -372,7 +372,7 @@ void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d)
sff_8079_show_encoding(data, d);
snprintf(val_string, sizeof(val_string), "%uMBd", br_nom);
- ssf_add_dict_string(d, "BR, Nominal", val_string);
+ sff_output_field(d, "BR, Nominal", val_string);
sff_8079_show_rate_identifier(data, d);
sff_show_value_with_unit(data, 14,
@@ -391,9 +391,9 @@ void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d)
sff_8079_show_options(data, d);
snprintf(val_string, sizeof(val_string), "%u%%", br_max);
- ssf_add_dict_string(d, "BR margin, max", val_string);
+ sff_output_field(d, "BR margin, max", val_string);
snprintf(val_string, sizeof(val_string), "%u%%", br_min);
- ssf_add_dict_string(d, "BR margin, min", val_string);
+ sff_output_field(d, "BR margin, min", val_string);
sff_show_ascii(data, 68, 83, "Vendor SN", d);
sff_show_ascii(data, 84, 91, "Date code", d);
diff --git a/lib/ethdev/sff_8472.c b/lib/ethdev/sff_8472.c
index 97f231854c..f58eba9d2d 100644
--- a/lib/ethdev/sff_8472.c
+++ b/lib/ethdev/sff_8472.c
@@ -231,7 +231,7 @@ static void sff_8472_parse_eeprom(const uint8_t *data, struct sff_diags *sd)
sff_8472_calibration(data, sd);
}
-void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d)
+void sff_8472_show_all(const uint8_t *data, struct sff_output *d)
{
struct sff_diags sd = {0};
const char *rx_power_string = NULL;
@@ -241,16 +241,16 @@ void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d)
sff_8472_parse_eeprom(data, &sd);
if (!sd.supports_dom) {
- ssf_add_dict_string(d, "Optical diagnostics support", "No");
+ sff_output_field(d, "Optical diagnostics support", "No");
return;
}
- ssf_add_dict_string(d, "Optical diagnostics support", "Yes");
+ sff_output_field(d, "Optical diagnostics support", "Yes");
SFF_SPRINT_BIAS(val_string, sd.bias_cur[SFF_MCURR]);
- ssf_add_dict_string(d, "Laser bias current", val_string);
+ sff_output_field(d, "Laser bias current", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.tx_power[SFF_MCURR]);
- ssf_add_dict_string(d, "Laser output power", val_string);
+ sff_output_field(d, "Laser output power", val_string);
if (!sd.rx_power_type)
rx_power_string = "Receiver signal OMA";
@@ -258,20 +258,20 @@ void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d)
rx_power_string = "Receiver signal average optical power";
SFF_SPRINT_xX_PWR(val_string, sd.rx_power[SFF_MCURR]);
- ssf_add_dict_string(d, rx_power_string, val_string);
+ sff_output_field(d, rx_power_string, val_string);
SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_MCURR]);
- ssf_add_dict_string(d, "Module temperature", val_string);
+ sff_output_field(d, "Module temperature", val_string);
SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_MCURR]);
- ssf_add_dict_string(d, "Module voltage", val_string);
+ sff_output_field(d, "Module voltage", val_string);
- ssf_add_dict_string(d, "Alarm/warning flags implemented",
+ sff_output_field(d, "Alarm/warning flags implemented",
(sd.supports_alarms ? "Yes" : "No"));
if (sd.supports_alarms) {
for (i = 0; sff_8472_aw_flags[i].str; ++i) {
- ssf_add_dict_string(d, sff_8472_aw_flags[i].str,
+ sff_output_field(d, sff_8472_aw_flags[i].str,
data[SFF_A2_BASE + sff_8472_aw_flags[i].offset]
& sff_8472_aw_flags[i].value ? "On" : "Off");
}
diff --git a/lib/ethdev/sff_8636.c b/lib/ethdev/sff_8636.c
index 9dfff41a3b..bbd00cc0ca 100644
--- a/lib/ethdev/sff_8636.c
+++ b/lib/ethdev/sff_8636.c
@@ -164,42 +164,42 @@ static struct sff_8636_aw_flags {
{ NULL, 0, 0 },
};
-static void sff_8636_show_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_identifier(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_identifier(data, SFF_8636_ID_OFFSET, d);
}
-static void sff_8636_show_ext_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_ext_identifier(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Extended identifier description";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
snprintf(val_string, sizeof(val_string), "0x%02x", data[SFF_8636_EXT_ID_OFFSET]);
- ssf_add_dict_string(d, "Extended identifier", val_string);
+ sff_output_field(d, "Extended identifier", val_string);
switch (data[SFF_8636_EXT_ID_OFFSET] & SFF_8636_EXT_ID_PWR_CLASS_MASK) {
case SFF_8636_EXT_ID_PWR_CLASS_1:
- ssf_add_dict_string(d, name, "1.5W max. Power consumption");
+ sff_output_field(d, name, "1.5W max. Power consumption");
break;
case SFF_8636_EXT_ID_PWR_CLASS_2:
- ssf_add_dict_string(d, name, "2.0W max. Power consumption");
+ sff_output_field(d, name, "2.0W max. Power consumption");
break;
case SFF_8636_EXT_ID_PWR_CLASS_3:
- ssf_add_dict_string(d, name, "2.5W max. Power consumption");
+ sff_output_field(d, name, "2.5W max. Power consumption");
break;
case SFF_8636_EXT_ID_PWR_CLASS_4:
- ssf_add_dict_string(d, name, "3.5W max. Power consumption");
+ sff_output_field(d, name, "3.5W max. Power consumption");
break;
}
if (data[SFF_8636_EXT_ID_OFFSET] & SFF_8636_EXT_ID_CDR_TX_MASK)
- ssf_add_dict_string(d, name, "CDR present in TX");
+ sff_output_field(d, name, "CDR present in TX");
else
- ssf_add_dict_string(d, name, "No CDR in TX");
+ sff_output_field(d, name, "No CDR in TX");
if (data[SFF_8636_EXT_ID_OFFSET] & SFF_8636_EXT_ID_CDR_RX_MASK)
- ssf_add_dict_string(d, name, "CDR present in RX");
+ sff_output_field(d, name, "CDR present in RX");
else
- ssf_add_dict_string(d, name, "No CDR in RX");
+ sff_output_field(d, name, "No CDR in RX");
switch (data[SFF_8636_EXT_ID_OFFSET] & SFF_8636_EXT_ID_EPWR_CLASS_MASK) {
case SFF_8636_EXT_ID_PWR_CLASS_LEGACY:
@@ -221,15 +221,15 @@ static void sff_8636_show_ext_identifier(const uint8_t *data, struct rte_tel_dat
else
strlcat(val_string, "High Power Class (> 3.5 W) not enabled", sizeof(val_string));
- ssf_add_dict_string(d, name, val_string);
+ sff_output_field(d, name, val_string);
}
-static void sff_8636_show_connector(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_connector(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_connector(data, SFF_8636_CTOR_OFFSET, d);
}
-static void sff_8636_show_transceiver(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_transceiver(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Transceiver type";
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -243,222 +243,222 @@ static void sff_8636_show_transceiver(const uint8_t *data, struct rte_tel_data *
data[SFF_8636_FC_TECH_OFFSET],
data[SFF_8636_FC_TRANS_MEDIA_OFFSET],
data[SFF_8636_FC_SPEED_OFFSET]);
- ssf_add_dict_string(d, "Transceiver codes", val_string);
+ sff_output_field(d, "Transceiver codes", val_string);
/* 10G/40G Ethernet Compliance Codes */
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_10G_LRM)
- ssf_add_dict_string(d, name, "10G Ethernet: 10G Base-LRM");
+ sff_output_field(d, name, "10G Ethernet: 10G Base-LRM");
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_10G_LR)
- ssf_add_dict_string(d, name, "10G Ethernet: 10G Base-LR");
+ sff_output_field(d, name, "10G Ethernet: 10G Base-LR");
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_10G_SR)
- ssf_add_dict_string(d, name, "10G Ethernet: 10G Base-SR");
+ sff_output_field(d, name, "10G Ethernet: 10G Base-SR");
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_40G_CR4)
- ssf_add_dict_string(d, name, "40G Ethernet: 40G Base-CR4");
+ sff_output_field(d, name, "40G Ethernet: 40G Base-CR4");
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_40G_SR4)
- ssf_add_dict_string(d, name, "40G Ethernet: 40G Base-SR4");
+ sff_output_field(d, name, "40G Ethernet: 40G Base-SR4");
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_40G_LR4)
- ssf_add_dict_string(d, name, "40G Ethernet: 40G Base-LR4");
+ sff_output_field(d, name, "40G Ethernet: 40G Base-LR4");
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_40G_ACTIVE)
- ssf_add_dict_string(d, name, "40G Ethernet: 40G Active Cable (XLPPI)");
+ sff_output_field(d, name, "40G Ethernet: 40G Active Cable (XLPPI)");
/* Extended Specification Compliance Codes from SFF-8024 */
if (data[SFF_8636_ETHERNET_COMP_OFFSET] & SFF_8636_ETHERNET_RSRVD) {
switch (data[SFF_8636_OPTION_1_OFFSET]) {
case SFF_8636_ETHERNET_UNSPECIFIED:
- ssf_add_dict_string(d, name, "(reserved or unknown)");
+ sff_output_field(d, name, "(reserved or unknown)");
break;
case SFF_8636_ETHERNET_100G_AOC:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"100G Ethernet: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
break;
case SFF_8636_ETHERNET_100G_SR4:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"100G Ethernet: 100G Base-SR4 or 25GBase-SR");
break;
case SFF_8636_ETHERNET_100G_LR4:
- ssf_add_dict_string(d, name, "100G Ethernet: 100G Base-LR4");
+ sff_output_field(d, name, "100G Ethernet: 100G Base-LR4");
break;
case SFF_8636_ETHERNET_100G_ER4:
- ssf_add_dict_string(d, name, "100G Ethernet: 100G Base-ER4");
+ sff_output_field(d, name, "100G Ethernet: 100G Base-ER4");
break;
case SFF_8636_ETHERNET_100G_SR10:
- ssf_add_dict_string(d, name, "100G Ethernet: 100G Base-SR10");
+ sff_output_field(d, name, "100G Ethernet: 100G Base-SR10");
break;
case SFF_8636_ETHERNET_100G_CWDM4_FEC:
- ssf_add_dict_string(d, name, "100G Ethernet: 100G CWDM4 MSA with FEC");
+ sff_output_field(d, name, "100G Ethernet: 100G CWDM4 MSA with FEC");
break;
case SFF_8636_ETHERNET_100G_PSM4:
- ssf_add_dict_string(d, name, "100G Ethernet: 100G PSM4 Parallel SMF");
+ sff_output_field(d, name, "100G Ethernet: 100G PSM4 Parallel SMF");
break;
case SFF_8636_ETHERNET_100G_ACC:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"100G Ethernet: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
break;
case SFF_8636_ETHERNET_100G_CWDM4_NO_FEC:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"100G Ethernet: 100G CWDM4 MSA without FEC");
break;
case SFF_8636_ETHERNET_100G_RSVD1:
- ssf_add_dict_string(d, name, "(reserved or unknown)");
+ sff_output_field(d, name, "(reserved or unknown)");
break;
case SFF_8636_ETHERNET_100G_CR4:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"100G Ethernet: 100G Base-CR4 or 25G Base-CR CA-L");
break;
case SFF_8636_ETHERNET_25G_CR_CA_S:
- ssf_add_dict_string(d, name, "25G Ethernet: 25G Base-CR CA-S");
+ sff_output_field(d, name, "25G Ethernet: 25G Base-CR CA-S");
break;
case SFF_8636_ETHERNET_25G_CR_CA_N:
- ssf_add_dict_string(d, name, "25G Ethernet: 25G Base-CR CA-N");
+ sff_output_field(d, name, "25G Ethernet: 25G Base-CR CA-N");
break;
case SFF_8636_ETHERNET_40G_ER4:
- ssf_add_dict_string(d, name, "40G Ethernet: 40G Base-ER4");
+ sff_output_field(d, name, "40G Ethernet: 40G Base-ER4");
break;
case SFF_8636_ETHERNET_4X10_SR:
- ssf_add_dict_string(d, name, "4x10G Ethernet: 10G Base-SR");
+ sff_output_field(d, name, "4x10G Ethernet: 10G Base-SR");
break;
case SFF_8636_ETHERNET_40G_PSM4:
- ssf_add_dict_string(d, name, "40G Ethernet: 40G PSM4 Parallel SMF");
+ sff_output_field(d, name, "40G Ethernet: 40G PSM4 Parallel SMF");
break;
case SFF_8636_ETHERNET_G959_P1I1_2D1:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"Ethernet: G959.1 profile P1I1-2D1 (10709 MBd, 2km, 1310nm SM)");
break;
case SFF_8636_ETHERNET_G959_P1S1_2D2:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"Ethernet: G959.1 profile P1S1-2D2 (10709 MBd, 40km, 1550nm SM)");
break;
case SFF_8636_ETHERNET_G959_P1L1_2D2:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"Ethernet: G959.1 profile P1L1-2D2 (10709 MBd, 80km, 1550nm SM)");
break;
case SFF_8636_ETHERNET_10GT_SFI:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"10G Ethernet: 10G Base-T with SFI electrical interface");
break;
case SFF_8636_ETHERNET_100G_CLR4:
- ssf_add_dict_string(d, name, "100G Ethernet: 100G CLR4");
+ sff_output_field(d, name, "100G Ethernet: 100G CLR4");
break;
case SFF_8636_ETHERNET_100G_AOC2:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"100G Ethernet: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
break;
case SFF_8636_ETHERNET_100G_ACC2:
- ssf_add_dict_string(d, name,
+ sff_output_field(d, name,
"100G Ethernet: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
break;
default:
- ssf_add_dict_string(d, name, "(reserved or unknown)");
+ sff_output_field(d, name, "(reserved or unknown)");
break;
}
}
/* SONET Compliance Codes */
if (data[SFF_8636_SONET_COMP_OFFSET] & SFF_8636_SONET_40G_OTN)
- ssf_add_dict_string(d, name, "40G OTN (OTU3B/OTU3C)");
+ sff_output_field(d, name, "40G OTN (OTU3B/OTU3C)");
if (data[SFF_8636_SONET_COMP_OFFSET] & SFF_8636_SONET_OC48_LR)
- ssf_add_dict_string(d, name, "SONET: OC-48, long reach");
+ sff_output_field(d, name, "SONET: OC-48, long reach");
if (data[SFF_8636_SONET_COMP_OFFSET] & SFF_8636_SONET_OC48_IR)
- ssf_add_dict_string(d, name, "SONET: OC-48, intermediate reach");
+ sff_output_field(d, name, "SONET: OC-48, intermediate reach");
if (data[SFF_8636_SONET_COMP_OFFSET] & SFF_8636_SONET_OC48_SR)
- ssf_add_dict_string(d, name, "SONET: OC-48, short reach");
+ sff_output_field(d, name, "SONET: OC-48, short reach");
/* SAS/SATA Compliance Codes */
if (data[SFF_8636_SAS_COMP_OFFSET] & SFF_8636_SAS_6G)
- ssf_add_dict_string(d, name, "SAS 6.0G");
+ sff_output_field(d, name, "SAS 6.0G");
if (data[SFF_8636_SAS_COMP_OFFSET] & SFF_8636_SAS_3G)
- ssf_add_dict_string(d, name, "SAS 3.0G");
+ sff_output_field(d, name, "SAS 3.0G");
/* Ethernet Compliance Codes */
if (data[SFF_8636_GIGE_COMP_OFFSET] & SFF_8636_GIGE_1000_BASE_T)
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-T");
+ sff_output_field(d, name, "Ethernet: 1000BASE-T");
if (data[SFF_8636_GIGE_COMP_OFFSET] & SFF_8636_GIGE_1000_BASE_CX)
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-CX");
+ sff_output_field(d, name, "Ethernet: 1000BASE-CX");
if (data[SFF_8636_GIGE_COMP_OFFSET] & SFF_8636_GIGE_1000_BASE_LX)
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-LX");
+ sff_output_field(d, name, "Ethernet: 1000BASE-LX");
if (data[SFF_8636_GIGE_COMP_OFFSET] & SFF_8636_GIGE_1000_BASE_SX)
- ssf_add_dict_string(d, name, "Ethernet: 1000BASE-SX");
+ sff_output_field(d, name, "Ethernet: 1000BASE-SX");
/* Fibre Channel link length */
if (data[SFF_8636_FC_LEN_OFFSET] & SFF_8636_FC_LEN_VERY_LONG)
- ssf_add_dict_string(d, name, "FC: very long distance (V)");
+ sff_output_field(d, name, "FC: very long distance (V)");
if (data[SFF_8636_FC_LEN_OFFSET] & SFF_8636_FC_LEN_SHORT)
- ssf_add_dict_string(d, name, "FC: short distance (S)");
+ sff_output_field(d, name, "FC: short distance (S)");
if (data[SFF_8636_FC_LEN_OFFSET] & SFF_8636_FC_LEN_INT)
- ssf_add_dict_string(d, name, "FC: intermediate distance (I)");
+ sff_output_field(d, name, "FC: intermediate distance (I)");
if (data[SFF_8636_FC_LEN_OFFSET] & SFF_8636_FC_LEN_LONG)
- ssf_add_dict_string(d, name, "FC: long distance (L)");
+ sff_output_field(d, name, "FC: long distance (L)");
if (data[SFF_8636_FC_LEN_OFFSET] & SFF_8636_FC_LEN_MED)
- ssf_add_dict_string(d, name, "FC: medium distance (M)");
+ sff_output_field(d, name, "FC: medium distance (M)");
/* Fibre Channel transmitter technology */
if (data[SFF_8636_FC_LEN_OFFSET] & SFF_8636_FC_TECH_LONG_LC)
- ssf_add_dict_string(d, name, "FC: Longwave laser (LC)");
+ sff_output_field(d, name, "FC: Longwave laser (LC)");
if (data[SFF_8636_FC_LEN_OFFSET] & SFF_8636_FC_TECH_ELEC_INTER)
- ssf_add_dict_string(d, name, "FC: Electrical inter-enclosure (EL)");
+ sff_output_field(d, name, "FC: Electrical inter-enclosure (EL)");
if (data[SFF_8636_FC_TECH_OFFSET] & SFF_8636_FC_TECH_ELEC_INTRA)
- ssf_add_dict_string(d, name, "FC: Electrical intra-enclosure (EL)");
+ sff_output_field(d, name, "FC: Electrical intra-enclosure (EL)");
if (data[SFF_8636_FC_TECH_OFFSET] & SFF_8636_FC_TECH_SHORT_WO_OFC)
- ssf_add_dict_string(d, name, "FC: Shortwave laser w/o OFC (SN)");
+ sff_output_field(d, name, "FC: Shortwave laser w/o OFC (SN)");
if (data[SFF_8636_FC_TECH_OFFSET] & SFF_8636_FC_TECH_SHORT_W_OFC)
- ssf_add_dict_string(d, name, "FC: Shortwave laser with OFC (SL)");
+ sff_output_field(d, name, "FC: Shortwave laser with OFC (SL)");
if (data[SFF_8636_FC_TECH_OFFSET] & SFF_8636_FC_TECH_LONG_LL)
- ssf_add_dict_string(d, name, "FC: Longwave laser (LL)");
+ sff_output_field(d, name, "FC: Longwave laser (LL)");
/* Fibre Channel transmission media */
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_TW)
- ssf_add_dict_string(d, name, "FC: Twin Axial Pair (TW)");
+ sff_output_field(d, name, "FC: Twin Axial Pair (TW)");
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_TP)
- ssf_add_dict_string(d, name, "FC: Twisted Pair (TP)");
+ sff_output_field(d, name, "FC: Twisted Pair (TP)");
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_MI)
- ssf_add_dict_string(d, name, "FC: Miniature Coax (MI)");
+ sff_output_field(d, name, "FC: Miniature Coax (MI)");
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_TV)
- ssf_add_dict_string(d, name, "FC: Video Coax (TV)");
+ sff_output_field(d, name, "FC: Video Coax (TV)");
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_M6)
- ssf_add_dict_string(d, name, "FC: Multimode, 62.5m (M6)");
+ sff_output_field(d, name, "FC: Multimode, 62.5m (M6)");
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_M5)
- ssf_add_dict_string(d, name, "FC: Multimode, 50m (M5)");
+ sff_output_field(d, name, "FC: Multimode, 50m (M5)");
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_OM3)
- ssf_add_dict_string(d, name, "FC: Multimode, 50um (OM3)");
+ sff_output_field(d, name, "FC: Multimode, 50um (OM3)");
if (data[SFF_8636_FC_TRANS_MEDIA_OFFSET] & SFF_8636_FC_TRANS_MEDIA_SM)
- ssf_add_dict_string(d, name, "FC: Single Mode (SM)");
+ sff_output_field(d, name, "FC: Single Mode (SM)");
/* Fibre Channel speed */
if (data[SFF_8636_FC_SPEED_OFFSET] & SFF_8636_FC_SPEED_1200_MBPS)
- ssf_add_dict_string(d, name, "FC: 1200 MBytes/sec");
+ sff_output_field(d, name, "FC: 1200 MBytes/sec");
if (data[SFF_8636_FC_SPEED_OFFSET] & SFF_8636_FC_SPEED_800_MBPS)
- ssf_add_dict_string(d, name, "FC: 800 MBytes/sec");
+ sff_output_field(d, name, "FC: 800 MBytes/sec");
if (data[SFF_8636_FC_SPEED_OFFSET] & SFF_8636_FC_SPEED_1600_MBPS)
- ssf_add_dict_string(d, name, "FC: 1600 MBytes/sec");
+ sff_output_field(d, name, "FC: 1600 MBytes/sec");
if (data[SFF_8636_FC_SPEED_OFFSET] & SFF_8636_FC_SPEED_400_MBPS)
- ssf_add_dict_string(d, name, "FC: 400 MBytes/sec");
+ sff_output_field(d, name, "FC: 400 MBytes/sec");
if (data[SFF_8636_FC_SPEED_OFFSET] & SFF_8636_FC_SPEED_200_MBPS)
- ssf_add_dict_string(d, name, "FC: 200 MBytes/sec");
+ sff_output_field(d, name, "FC: 200 MBytes/sec");
if (data[SFF_8636_FC_SPEED_OFFSET] & SFF_8636_FC_SPEED_100_MBPS)
- ssf_add_dict_string(d, name, "FC: 100 MBytes/sec");
+ sff_output_field(d, name, "FC: 100 MBytes/sec");
}
-static void sff_8636_show_encoding(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_encoding(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_encoding(data, SFF_8636_ENCODING_OFFSET,
RTE_ETH_MODULE_SFF_8636, d);
}
-static void sff_8636_show_rate_identifier(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_rate_identifier(const uint8_t *data, struct sff_output *d)
{
char val_string[20];
snprintf(val_string, sizeof(val_string), "0x%02x", data[SFF_8636_EXT_RS_OFFSET]);
- ssf_add_dict_string(d, "Rate identifier", val_string);
+ sff_output_field(d, "Rate identifier", val_string);
}
-static void sff_8636_show_oui(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_oui(const uint8_t *data, struct sff_output *d)
{
sff_8024_show_oui(data, SFF_8636_VENDOR_OUI_OFFSET, d);
}
static void sff_8636_show_wavelength_or_copper_compliance(const uint8_t *data,
- struct rte_tel_data *d)
+ struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
snprintf(val_string, sizeof(val_string), "0x%02x",
@@ -522,69 +522,69 @@ static void sff_8636_show_wavelength_or_copper_compliance(const uint8_t *data,
sizeof(val_string));
break;
}
- ssf_add_dict_string(d, "Transmitter technology", val_string);
+ sff_output_field(d, "Transmitter technology", val_string);
if ((data[SFF_8636_DEVICE_TECH_OFFSET] & SFF_8636_TRANS_TECH_MASK)
>= SFF_8636_TRANS_COPPER_PAS_UNEQUAL) {
snprintf(val_string, sizeof(val_string), "%udb",
data[SFF_8636_WAVELEN_HIGH_BYTE_OFFSET]);
- ssf_add_dict_string(d, "Attenuation at 2.5GHz", val_string);
+ sff_output_field(d, "Attenuation at 2.5GHz", val_string);
snprintf(val_string, sizeof(val_string), "%udb",
data[SFF_8636_WAVELEN_HIGH_BYTE_OFFSET]);
- ssf_add_dict_string(d, "Attenuation at 5.0GHz", val_string);
+ sff_output_field(d, "Attenuation at 5.0GHz", val_string);
snprintf(val_string, sizeof(val_string), "%udb",
data[SFF_8636_WAVELEN_HIGH_BYTE_OFFSET]);
- ssf_add_dict_string(d, "Attenuation at 7.0GHz", val_string);
+ sff_output_field(d, "Attenuation at 7.0GHz", val_string);
snprintf(val_string, sizeof(val_string), "%udb",
data[SFF_8636_WAVELEN_HIGH_BYTE_OFFSET]);
- ssf_add_dict_string(d, "Attenuation at 12.9GHz", val_string);
+ sff_output_field(d, "Attenuation at 12.9GHz", val_string);
} else {
snprintf(val_string, sizeof(val_string), "%.3lfnm",
(((data[SFF_8636_WAVELEN_HIGH_BYTE_OFFSET] << 8) |
data[SFF_8636_WAVELEN_LOW_BYTE_OFFSET])*0.05));
- ssf_add_dict_string(d, "Laser wavelength", val_string);
+ sff_output_field(d, "Laser wavelength", val_string);
snprintf(val_string, sizeof(val_string), "%.3lfnm",
(((data[SFF_8636_WAVE_TOL_HIGH_BYTE_OFFSET] << 8) |
data[SFF_8636_WAVE_TOL_LOW_BYTE_OFFSET])*0.005));
- ssf_add_dict_string(d, "Laser wavelength tolerance", val_string);
+ sff_output_field(d, "Laser wavelength tolerance", val_string);
}
}
-static void sff_8636_show_revision_compliance(const uint8_t *data, struct rte_tel_data *d)
+static void sff_8636_show_revision_compliance(const uint8_t *data, struct sff_output *d)
{
static const char *name = "Revision Compliance";
switch (data[SFF_8636_REV_COMPLIANCE_OFFSET]) {
case SFF_8636_REV_UNSPECIFIED:
- ssf_add_dict_string(d, name, "Revision not specified");
+ sff_output_field(d, name, "Revision not specified");
break;
case SFF_8636_REV_8436_48:
- ssf_add_dict_string(d, name, "SFF-8436 Rev 4.8 or earlier");
+ sff_output_field(d, name, "SFF-8436 Rev 4.8 or earlier");
break;
case SFF_8636_REV_8436_8636:
- ssf_add_dict_string(d, name, "SFF-8436 Rev 4.8 or earlier");
+ sff_output_field(d, name, "SFF-8436 Rev 4.8 or earlier");
break;
case SFF_8636_REV_8636_13:
- ssf_add_dict_string(d, name, "SFF-8636 Rev 1.3 or earlier");
+ sff_output_field(d, name, "SFF-8636 Rev 1.3 or earlier");
break;
case SFF_8636_REV_8636_14:
- ssf_add_dict_string(d, name, "SFF-8636 Rev 1.4");
+ sff_output_field(d, name, "SFF-8636 Rev 1.4");
break;
case SFF_8636_REV_8636_15:
- ssf_add_dict_string(d, name, "SFF-8636 Rev 1.5");
+ sff_output_field(d, name, "SFF-8636 Rev 1.5");
break;
case SFF_8636_REV_8636_20:
- ssf_add_dict_string(d, name, "SFF-8636 Rev 2.0");
+ sff_output_field(d, name, "SFF-8636 Rev 2.0");
break;
case SFF_8636_REV_8636_27:
- ssf_add_dict_string(d, name, "SFF-8636 Rev 2.5/2.6/2.7");
+ sff_output_field(d, name, "SFF-8636 Rev 2.5/2.6/2.7");
break;
default:
- ssf_add_dict_string(d, name, "Unallocated");
+ sff_output_field(d, name, "Unallocated");
break;
}
}
@@ -640,7 +640,7 @@ static void sff_8636_dom_parse(const uint8_t *data, struct sff_diags *sd)
}
-static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d)
+static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d)
{
struct sff_diags sd = {0};
const char *rx_power_string = NULL;
@@ -671,10 +671,10 @@ static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct r
sff_8636_dom_parse(data, &sd);
SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_MCURR]);
- ssf_add_dict_string(d, "Module temperature", val_string);
+ sff_output_field(d, "Module temperature", val_string);
SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_MCURR]);
- ssf_add_dict_string(d, "Module voltage", val_string);
+ sff_output_field(d, "Module voltage", val_string);
/*
* SFF-8636/8436 spec is not clear whether RX power/ TX bias
@@ -685,21 +685,21 @@ static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct r
(sd.sfp_temp[SFF_MCURR] == (int16_t)0xFFFF))
return;
- ssf_add_dict_string(d, "Alarm/warning flags implemented",
+ sff_output_field(d, "Alarm/warning flags implemented",
(sd.supports_alarms ? "Yes" : "No"));
for (i = 0; i < SFF_MAX_CHANNEL_NUM; i++) {
snprintf(power_string, SFF_MAX_DESC_SIZE, "%s (Channel %d)",
"Laser tx bias current", i+1);
SFF_SPRINT_BIAS(val_string, sd.scd[i].bias_cur);
- ssf_add_dict_string(d, power_string, val_string);
+ sff_output_field(d, power_string, val_string);
}
for (i = 0; i < SFF_MAX_CHANNEL_NUM; i++) {
snprintf(power_string, SFF_MAX_DESC_SIZE, "%s (Channel %d)",
"Transmit avg optical power", i+1);
SFF_SPRINT_xX_PWR(val_string, sd.scd[i].tx_power);
- ssf_add_dict_string(d, power_string, val_string);
+ sff_output_field(d, power_string, val_string);
}
if (!sd.rx_power_type)
@@ -711,12 +711,12 @@ static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct r
snprintf(power_string, SFF_MAX_DESC_SIZE, "%s(Channel %d)",
rx_power_string, i+1);
SFF_SPRINT_xX_PWR(val_string, sd.scd[i].rx_power);
- ssf_add_dict_string(d, power_string, val_string);
+ sff_output_field(d, power_string, val_string);
}
if (sd.supports_alarms) {
for (i = 0; sff_8636_aw_flags[i].str; ++i) {
- ssf_add_dict_string(d, sff_8636_aw_flags[i].str,
+ sff_output_field(d, sff_8636_aw_flags[i].str,
data[sff_8636_aw_flags[i].offset]
& sff_8636_aw_flags[i].value ? "On" : "Off");
}
@@ -725,7 +725,7 @@ static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct r
}
}
-void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d)
+void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d)
{
sff_8636_show_identifier(data, d);
if ((data[SFF_8636_ID_OFFSET] == SFF_8024_ID_QSFP) ||
diff --git a/lib/ethdev/sff_common.c b/lib/ethdev/sff_common.c
index bd425274e3..1b48c0f6ea 100644
--- a/lib/ethdev/sff_common.c
+++ b/lib/ethdev/sff_common.c
@@ -8,6 +8,12 @@
#include "sff_common.h"
+void sff_output_field(struct sff_output *d, const char *name_str,
+ const char *value_str)
+{
+ d->field_cb(name_str, value_str, d->arg);
+}
+
double sff_convert_mw_to_dbm(double mw)
{
return (10. * log10(mw / 1000.)) + 30.;
@@ -15,17 +21,17 @@ double sff_convert_mw_to_dbm(double mw)
void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
const char *name, unsigned int mult,
- const char *unit, struct rte_tel_data *d)
+ const char *unit, struct sff_output *d)
{
unsigned int val = data[reg];
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
snprintf(val_string, sizeof(val_string), "%u%s", val * mult, unit);
- ssf_add_dict_string(d, name, val_string);
+ sff_output_field(d, name, val_string);
}
void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
- unsigned int last_reg, const char *name, struct rte_tel_data *d)
+ unsigned int last_reg, const char *name, struct sff_output *d)
{
unsigned int reg, val;
char tmp[3];
@@ -44,19 +50,19 @@ void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
strlcat(val_string, "_", sizeof(val_string));
}
}
- ssf_add_dict_string(d, name, val_string);
+ sff_output_field(d, name, val_string);
}
-void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *d)
+void sff_8024_show_oui(const uint8_t *data, int id_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
snprintf(val_string, sizeof(val_string), "%02x:%02x:%02x",
data[id_offset], data[(id_offset) + 1], data[(id_offset) + 2]);
- ssf_add_dict_string(d, "Vendor OUI", val_string);
+ sff_output_field(d, "Vendor OUI", val_string);
}
-void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel_data *d)
+void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -142,10 +148,10 @@ void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel
strlcat(val_string, " (reserved or unknown)", sizeof(val_string));
break;
}
- ssf_add_dict_string(d, "Identifier", val_string);
+ sff_output_field(d, "Identifier", val_string);
}
-void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_tel_data *d)
+void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -213,11 +219,11 @@ void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_te
strlcat(val_string, " (reserved or unknown)", sizeof(val_string));
break;
}
- ssf_add_dict_string(d, "Connector", val_string);
+ sff_output_field(d, "Connector", val_string);
}
void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
- int sff_type, struct rte_tel_data *d)
+ int sff_type, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
@@ -265,55 +271,55 @@ void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
strlcat(val_string, " (reserved or unknown)", sizeof(val_string));
break;
}
- ssf_add_dict_string(d, "Encoding", val_string);
+ sff_output_field(d, "Encoding", val_string);
}
-void sff_show_thresholds(struct sff_diags sd, struct rte_tel_data *d)
+void sff_show_thresholds(struct sff_diags sd, struct sff_output *d)
{
char val_string[SFF_ITEM_VAL_COMPOSE_SIZE];
SFF_SPRINT_BIAS(val_string, sd.bias_cur[SFF_HALRM]);
- ssf_add_dict_string(d, "Laser bias current high alarm threshold", val_string);
+ sff_output_field(d, "Laser bias current high alarm threshold", val_string);
SFF_SPRINT_BIAS(val_string, sd.bias_cur[SFF_LALRM]);
- ssf_add_dict_string(d, "Laser bias current low alarm threshold", val_string);
+ sff_output_field(d, "Laser bias current low alarm threshold", val_string);
SFF_SPRINT_BIAS(val_string, sd.bias_cur[SFF_HWARN]);
- ssf_add_dict_string(d, "Laser bias current high warning threshold", val_string);
+ sff_output_field(d, "Laser bias current high warning threshold", val_string);
SFF_SPRINT_BIAS(val_string, sd.bias_cur[SFF_LWARN]);
- ssf_add_dict_string(d, "Laser bias current low warning threshold", val_string);
+ sff_output_field(d, "Laser bias current low warning threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.tx_power[SFF_HALRM]);
- ssf_add_dict_string(d, "Laser output power high alarm threshold", val_string);
+ sff_output_field(d, "Laser output power high alarm threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.tx_power[SFF_LALRM]);
- ssf_add_dict_string(d, "Laser output power low alarm threshold", val_string);
+ sff_output_field(d, "Laser output power low alarm threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.tx_power[SFF_HWARN]);
- ssf_add_dict_string(d, "Laser output power high warning threshold", val_string);
+ sff_output_field(d, "Laser output power high warning threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.tx_power[SFF_LWARN]);
- ssf_add_dict_string(d, "Laser output power low warning threshold", val_string);
+ sff_output_field(d, "Laser output power low warning threshold", val_string);
SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_HALRM]);
- ssf_add_dict_string(d, "Module temperature high alarm threshold", val_string);
+ sff_output_field(d, "Module temperature high alarm threshold", val_string);
SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_LALRM]);
- ssf_add_dict_string(d, "Module temperature low alarm threshold", val_string);
+ sff_output_field(d, "Module temperature low alarm threshold", val_string);
SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_HWARN]);
- ssf_add_dict_string(d, "Module temperature high warning threshold", val_string);
+ sff_output_field(d, "Module temperature high warning threshold", val_string);
SFF_SPRINT_TEMP(val_string, sd.sfp_temp[SFF_LWARN]);
- ssf_add_dict_string(d, "Module temperature low warning threshold", val_string);
+ sff_output_field(d, "Module temperature low warning threshold", val_string);
SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_HALRM]);
- ssf_add_dict_string(d, "Module voltage high alarm threshold", val_string);
+ sff_output_field(d, "Module voltage high alarm threshold", val_string);
SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_LALRM]);
- ssf_add_dict_string(d, "Module voltage low alarm threshold", val_string);
+ sff_output_field(d, "Module voltage low alarm threshold", val_string);
SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_HWARN]);
- ssf_add_dict_string(d, "Module voltage high warning threshold", val_string);
+ sff_output_field(d, "Module voltage high warning threshold", val_string);
SFF_SPRINT_VCC(val_string, sd.sfp_voltage[SFF_LWARN]);
- ssf_add_dict_string(d, "Module voltage low alarm threshold", val_string);
+ sff_output_field(d, "Module voltage low alarm threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.rx_power[SFF_HALRM]);
- ssf_add_dict_string(d, "Laser rx power high alarm threshold", val_string);
+ sff_output_field(d, "Laser rx power high alarm threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.rx_power[SFF_LALRM]);
- ssf_add_dict_string(d, "Laser rx power low alarm threshold", val_string);
+ sff_output_field(d, "Laser rx power low alarm threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.rx_power[SFF_HWARN]);
- ssf_add_dict_string(d, "Laser rx power high warning threshold", val_string);
+ sff_output_field(d, "Laser rx power high warning threshold", val_string);
SFF_SPRINT_xX_PWR(val_string, sd.rx_power[SFF_LWARN]);
- ssf_add_dict_string(d, "Laser rx power low warning threshold", val_string);
+ sff_output_field(d, "Laser rx power low warning threshold", val_string);
}
diff --git a/lib/ethdev/sff_common.h b/lib/ethdev/sff_common.h
index 2e42cbe8be..06f5a35e8e 100644
--- a/lib/ethdev/sff_common.h
+++ b/lib/ethdev/sff_common.h
@@ -11,7 +11,28 @@
#include <stdio.h>
#include "rte_ethdev.h"
-#include "sff_telemetry.h"
+
+#define SFF_ITEM_VAL_COMPOSE_SIZE 64
+
+/* Consumer of decoded module EEPROM fields */
+struct sff_output {
+ /* Called once per decoded field, name may repeat */
+ void (*field_cb)(const char *name, const char *value, void *arg);
+ void *arg;
+};
+
+/* Report one decoded field */
+void sff_output_field(struct sff_output *d, const char *name_str,
+ const char *value_str);
+
+/* SFF-8079 Optics diagnostics */
+void sff_8079_show_all(const uint8_t *data, struct sff_output *d);
+
+/* SFF-8472 Optics diagnostics */
+void sff_8472_show_all(const uint8_t *data, struct sff_output *d);
+
+/* SFF-8636 Optics diagnostics */
+void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d);
#define SFF_8024_ID_OFFSET 0x00
#define SFF_8024_ID_UNKNOWN 0x00
@@ -158,15 +179,15 @@ struct sff_diags {
double sff_convert_mw_to_dbm(double mw);
void sff_show_value_with_unit(const uint8_t *data, unsigned int reg,
const char *name, unsigned int mult,
- const char *unit, struct rte_tel_data *d);
+ const char *unit, struct sff_output *d);
void sff_show_ascii(const uint8_t *data, unsigned int first_reg,
- unsigned int last_reg, const char *name, struct rte_tel_data *d);
-void sff_show_thresholds(struct sff_diags sd, struct rte_tel_data *d);
+ unsigned int last_reg, const char *name, struct sff_output *d);
+void sff_show_thresholds(struct sff_diags sd, struct sff_output *d);
-void sff_8024_show_oui(const uint8_t *data, int id_offset, struct rte_tel_data *d);
-void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct rte_tel_data *d);
-void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct rte_tel_data *d);
+void sff_8024_show_oui(const uint8_t *data, int id_offset, struct sff_output *d);
+void sff_8024_show_identifier(const uint8_t *data, int id_offset, struct sff_output *d);
+void sff_8024_show_connector(const uint8_t *data, int ctor_offset, struct sff_output *d);
void sff_8024_show_encoding(const uint8_t *data, int encoding_offset,
- int sff_type, struct rte_tel_data *d);
+ int sff_type, struct sff_output *d);
#endif /* _SFF_COMMON_H_ */
diff --git a/lib/ethdev/sff_telemetry.c b/lib/ethdev/sff_telemetry.c
index b3f239d967..06a8122367 100644
--- a/lib/ethdev/sff_telemetry.c
+++ b/lib/ethdev/sff_telemetry.c
@@ -8,12 +8,42 @@
#include "rte_ethdev.h"
#include <rte_common.h>
+#include "sff_common.h"
#include "sff_telemetry.h"
#include <telemetry_data.h>
+static void
+sff_tel_add_field(const char *name_str, const char *value_str, void *arg)
+{
+ struct rte_tel_data *d = arg;
+ struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+ if (d->type != TEL_DICT)
+ return;
+ if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES) {
+ RTE_ETHDEV_LOG_LINE(ERR, "data_len has exceeded the maximum number of inserts");
+ return;
+ }
+
+ e->type = RTE_TEL_STRING_VAL;
+ /* append different values for same keys */
+ if (d->data_len > 0) {
+ struct tel_dict_entry *previous = &d->data.dict[d->data_len - 1];
+ if (strcmp(previous->name, name_str) == 0) {
+ strlcat(previous->value.sval, "; ", RTE_TEL_MAX_STRING_LEN);
+ strlcat(previous->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
+ return;
+ }
+ }
+ strlcpy(e->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
+ strlcpy(e->name, name_str, RTE_TEL_MAX_STRING_LEN);
+ d->data_len++;
+}
+
static void
sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
{
+ struct sff_output out = { .field_cb = sff_tel_add_field, .arg = d };
struct rte_eth_dev_module_info minfo;
struct rte_dev_eeprom_info einfo;
int ret;
@@ -73,15 +103,15 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
switch (minfo.type) {
/* parsing module EEPROM data base on different module type */
case RTE_ETH_MODULE_SFF_8079:
- sff_8079_show_all(einfo.data, d);
+ sff_8079_show_all(einfo.data, &out);
break;
case RTE_ETH_MODULE_SFF_8472:
- sff_8079_show_all(einfo.data, d);
- sff_8472_show_all(einfo.data, d);
+ sff_8079_show_all(einfo.data, &out);
+ sff_8472_show_all(einfo.data, &out);
break;
case RTE_ETH_MODULE_SFF_8436:
case RTE_ETH_MODULE_SFF_8636:
- sff_8636_show_all(einfo.data, einfo.length, d);
+ sff_8636_show_all(einfo.data, einfo.length, &out);
break;
default:
RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
@@ -91,36 +121,6 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
free(einfo.data);
}
-void
-ssf_add_dict_string(struct rte_tel_data *d, const char *name_str, const char *value_str)
-{
- struct tel_dict_entry *e = &d->data.dict[d->data_len];
-
- if (d->type != TEL_DICT)
- return;
- if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES) {
- RTE_ETHDEV_LOG_LINE(ERR, "data_len has exceeded the maximum number of inserts");
- return;
- }
-
- e->type = RTE_TEL_STRING_VAL;
- /* append different values for same keys */
- if (d->data_len > 0) {
- struct tel_dict_entry *previous = &d->data.dict[d->data_len - 1];
- if (strcmp(previous->name, name_str) == 0) {
- strlcat(previous->value.sval, "; ", RTE_TEL_MAX_STRING_LEN);
- strlcat(previous->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
- goto end;
- }
- }
- strlcpy(e->value.sval, value_str, RTE_TEL_MAX_STRING_LEN);
- strlcpy(e->name, name_str, RTE_TEL_MAX_STRING_LEN);
- d->data_len++;
-
-end:
- return;
-}
-
int
eth_dev_handle_port_module_eeprom(const char *cmd __rte_unused, const char *params,
struct rte_tel_data *d)
diff --git a/lib/ethdev/sff_telemetry.h b/lib/ethdev/sff_telemetry.h
index 81c1fb0ffb..8c10a88cf6 100644
--- a/lib/ethdev/sff_telemetry.h
+++ b/lib/ethdev/sff_telemetry.h
@@ -7,22 +7,8 @@
#include <rte_telemetry.h>
-#define SFF_ITEM_VAL_COMPOSE_SIZE 64
-
-/* SFF-8079 Optics diagnostics */
-void sff_8079_show_all(const uint8_t *data, struct rte_tel_data *d);
-
-/* SFF-8472 Optics diagnostics */
-void sff_8472_show_all(const uint8_t *data, struct rte_tel_data *d);
-
-/* SFF-8636 Optics diagnostics */
-void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct rte_tel_data *d);
-
int eth_dev_handle_port_module_eeprom(const char *cmd __rte_unused,
const char *params,
struct rte_tel_data *d);
-void ssf_add_dict_string(struct rte_tel_data *d, const char *name_str,
- const char *value_str);
-
#endif /* _ETHDEV_SFF_TELEMETRY_H_ */
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v3 3/6] ethdev: check module EEPROM length before decoding
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 1/6] ethdev: fix out-of-bounds read of SFF-8636 thresholds Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 2/6] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
@ 2026-09-29 7:07 ` Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 4/6] ethdev: avoid unaligned access in SFF-8472 decoder Roman Khromenok
` (3 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-29 7:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Thomas Monjalon, Andrew Rybchenko
The SFF-8079 and SFF-8472 decoders do not receive the buffer length
and assume it is large enough for the module type.
This holds for the telemetry command, which allocates the length
reported by the driver, but not for other callers.
Move the type dispatch into a common internal function which checks
the minimal length for each type before decoding:
- SFF-8079 and SFF-8436/8636 require at least 256 bytes,
- SFF-8472 requires 256 bytes for the base information,
and the diagnostics (page A2h) are decoded only if 512 bytes
are available.
Also decode the SFF-8636 thresholds and alarm flags when the data
is longer than 640 bytes, not only when it is exactly 640 bytes.
This is a preparation for exposing the decoders to applications.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
v3: SFF-8636 fix moved to patch 1, decode thresholds for length >= 640
lib/ethdev/sff_8636.c | 2 +-
lib/ethdev/sff_common.c | 31 +++++++++++++++++++++++++++++++
lib/ethdev/sff_common.h | 8 ++++++++
lib/ethdev/sff_telemetry.c | 21 +++++----------------
4 files changed, 45 insertions(+), 17 deletions(-)
diff --git a/lib/ethdev/sff_8636.c b/lib/ethdev/sff_8636.c
index bbd00cc0ca..40e5404caf 100644
--- a/lib/ethdev/sff_8636.c
+++ b/lib/ethdev/sff_8636.c
@@ -656,7 +656,7 @@ static void sff_8636_show_dom(const uint8_t *data, uint32_t eeprom_len, struct s
* If pagging support exists, then supports_alarms is marked as 1
*/
- if (eeprom_len == RTE_ETH_MODULE_SFF_8636_MAX_LEN) {
+ if (eeprom_len >= RTE_ETH_MODULE_SFF_8636_MAX_LEN) {
if (!(data[SFF_8636_STATUS_2_OFFSET] &
SFF_8636_STATUS_PAGE_3_PRESENT)) {
sd.supports_alarms = 1;
diff --git a/lib/ethdev/sff_common.c b/lib/ethdev/sff_common.c
index 1b48c0f6ea..a36bf83026 100644
--- a/lib/ethdev/sff_common.c
+++ b/lib/ethdev/sff_common.c
@@ -4,6 +4,7 @@
* common utilities for SFF-8436/8636 and SFF-8472/8079
*/
+#include <errno.h>
#include <math.h>
#include "sff_common.h"
@@ -14,6 +15,36 @@ void sff_output_field(struct sff_output *d, const char *name_str,
d->field_cb(name_str, value_str, d->arg);
}
+int sff_decode_module_eeprom(uint32_t type, const uint8_t *data, uint32_t length,
+ struct sff_output *d)
+{
+ switch (type) {
+ case RTE_ETH_MODULE_SFF_8079:
+ if (length < RTE_ETH_MODULE_SFF_8079_LEN)
+ return -EINVAL;
+ sff_8079_show_all(data, d);
+ break;
+ case RTE_ETH_MODULE_SFF_8472:
+ if (length < RTE_ETH_MODULE_SFF_8079_LEN)
+ return -EINVAL;
+ sff_8079_show_all(data, d);
+ /* diagnostics are in the second page (A2h) */
+ if (length >= RTE_ETH_MODULE_SFF_8472_LEN)
+ sff_8472_show_all(data, d);
+ break;
+ case RTE_ETH_MODULE_SFF_8436:
+ case RTE_ETH_MODULE_SFF_8636:
+ if (length < RTE_ETH_MODULE_SFF_8636_LEN)
+ return -EINVAL;
+ sff_8636_show_all(data, length, d);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+
+ return 0;
+}
+
double sff_convert_mw_to_dbm(double mw)
{
return (10. * log10(mw / 1000.)) + 30.;
diff --git a/lib/ethdev/sff_common.h b/lib/ethdev/sff_common.h
index 06f5a35e8e..6435f4d6f5 100644
--- a/lib/ethdev/sff_common.h
+++ b/lib/ethdev/sff_common.h
@@ -34,6 +34,14 @@ void sff_8472_show_all(const uint8_t *data, struct sff_output *d);
/* SFF-8636 Optics diagnostics */
void sff_8636_show_all(const uint8_t *data, uint32_t eeprom_len, struct sff_output *d);
+/*
+ * Decode module EEPROM of the given type (RTE_ETH_MODULE_SFF_*).
+ * Returns 0 on success, -EINVAL if the data is too short for the type,
+ * -ENOTSUP if the type is unknown.
+ */
+int sff_decode_module_eeprom(uint32_t type, const uint8_t *data, uint32_t length,
+ struct sff_output *d);
+
#define SFF_8024_ID_OFFSET 0x00
#define SFF_8024_ID_UNKNOWN 0x00
#define SFF_8024_ID_GBIC 0x01
diff --git a/lib/ethdev/sff_telemetry.c b/lib/ethdev/sff_telemetry.c
index 06a8122367..46533a9fc5 100644
--- a/lib/ethdev/sff_telemetry.c
+++ b/lib/ethdev/sff_telemetry.c
@@ -100,23 +100,12 @@ sff_port_module_eeprom_parse(uint16_t port_id, struct rte_tel_data *d)
return;
}
- switch (minfo.type) {
- /* parsing module EEPROM data base on different module type */
- case RTE_ETH_MODULE_SFF_8079:
- sff_8079_show_all(einfo.data, &out);
- break;
- case RTE_ETH_MODULE_SFF_8472:
- sff_8079_show_all(einfo.data, &out);
- sff_8472_show_all(einfo.data, &out);
- break;
- case RTE_ETH_MODULE_SFF_8436:
- case RTE_ETH_MODULE_SFF_8636:
- sff_8636_show_all(einfo.data, einfo.length, &out);
- break;
- default:
+ ret = sff_decode_module_eeprom(minfo.type, einfo.data, einfo.length, &out);
+ if (ret == -ENOTSUP)
RTE_ETHDEV_LOG_LINE(NOTICE, "Unsupported module type: %u", minfo.type);
- break;
- }
+ else if (ret != 0)
+ RTE_ETHDEV_LOG_LINE(ERR, "Port %u module EEPROM is too short: %u bytes",
+ port_id, einfo.length);
free(einfo.data);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v3 4/6] ethdev: avoid unaligned access in SFF-8472 decoder
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
` (2 preceding siblings ...)
2026-09-29 7:07 ` [PATCH v3 3/6] ethdev: check module EEPROM length before decoding Roman Khromenok
@ 2026-09-29 7:07 ` Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 5/6] ethdev: add API to decode module EEPROM Roman Khromenok
` (2 subsequent siblings)
6 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-29 7:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Thomas Monjalon, Andrew Rybchenko
The external calibration coefficients of the RX power are big-endian
floats in page A2h. They are read by casting the byte buffer
to a 32-bit pointer, which is an unaligned access if the buffer
is not 4-byte aligned.
The telemetry command always decodes a buffer from calloc(),
but an application may pass any buffer once the decoders are exposed.
Read the value with memcpy() and convert it with rte_be_to_cpu_32().
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
v3: new patch
lib/ethdev/sff_8472.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/lib/ethdev/sff_8472.c b/lib/ethdev/sff_8472.c
index f58eba9d2d..cdb6ef1f7e 100644
--- a/lib/ethdev/sff_8472.c
+++ b/lib/ethdev/sff_8472.c
@@ -4,6 +4,9 @@
*/
#include <stdio.h>
+#include <string.h>
+
+#include <rte_byteorder.h>
#include "sff_common.h"
@@ -126,7 +129,7 @@ static struct sff_8472_aw_flags {
/* RXPWR(x) are IEEE-754 floating point numbers in big-endian format */
#define A2_OFFSET_TO_RXPWRx(offset) \
- (befloattoh((const uint32_t *)(data + SFF_A2_BASE + (offset))))
+ (befloattoh(data + SFF_A2_BASE + (offset)))
/*
* 2-byte internal temperature conversions:
@@ -169,14 +172,17 @@ static void sff_8472_dom_parse(const uint8_t *data, struct sff_diags *sd)
}
/* Converts to a float from a big-endian 4-byte source buffer. */
-static float befloattoh(const uint32_t *source)
+static float befloattoh(const uint8_t *source)
{
union {
uint32_t src;
float dst;
} converter;
+ rte_be32_t be;
- converter.src = ntohl(*source);
+ /* the source may be unaligned */
+ memcpy(&be, source, sizeof(be));
+ converter.src = rte_be_to_cpu_32(be);
return converter.dst;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v3 5/6] ethdev: add API to decode module EEPROM
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
` (3 preceding siblings ...)
2026-09-29 7:07 ` [PATCH v3 4/6] ethdev: avoid unaligned access in SFF-8472 decoder Roman Khromenok
@ 2026-09-29 7:07 ` Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 6/6] test: add ethdev module EEPROM decoding tests Roman Khromenok
2026-09-29 16:02 ` [PATCH v3 0/6] ethdev: add API to decode module EEPROM Stephen Hemminger
6 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-29 7:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Thomas Monjalon, Andrew Rybchenko
The SFF module EEPROM decoders are available only through
the telemetry command, which reads the EEPROM of a DPDK port
and returns the result as a telemetry dictionary.
An application cannot use them for its own output,
nor for EEPROM data obtained from other sources,
for example ports managed by the Linux kernel.
Add an experimental function which decodes a module EEPROM buffer
of the given type and reports each decoded field through
a user callback. The field names and value formats are the ones
of the telemetry output; they are documented as intended for display.
The function does not access any device and can be used
without EAL initialization.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
v3: document display-only names, SFF-8636 length rule,
ethtool ioctls vs netlink, "pluggable module"
doc/guides/prog_guide/ethdev/ethdev.rst | 50 ++++++++++++++++++++++
doc/guides/rel_notes/release_26_11.rst | 8 ++++
lib/ethdev/rte_ethdev.c | 21 +++++++++
lib/ethdev/rte_ethdev.h | 57 +++++++++++++++++++++++++
lib/ethdev/sff_common.h | 2 +-
5 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/doc/guides/prog_guide/ethdev/ethdev.rst b/doc/guides/prog_guide/ethdev/ethdev.rst
index 6521fe14a5..ff2ab69b0f 100644
--- a/doc/guides/prog_guide/ethdev/ethdev.rst
+++ b/doc/guides/prog_guide/ethdev/ethdev.rst
@@ -802,3 +802,53 @@ three events are available:
The application should close the port.
Query the error handling mode supported by the PMD using ``rte_eth_dev_info_get()``.
+
+
+Pluggable Module EEPROM
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Pluggable transceiver modules (SFP, QSFP) store their identification
+and diagnostic data in an EEPROM with a layout defined by the SFF specifications.
+Use ``rte_eth_dev_get_module_info()`` to get the module type and the EEPROM size,
+then ``rte_eth_dev_get_module_eeprom()`` to read the raw EEPROM data.
+
+The raw data can be decoded with ``rte_eth_module_eeprom_parse()``.
+It supports the SFF-8079, SFF-8472, SFF-8436 and SFF-8636 layouts,
+and reports each decoded field, such as the vendor name, the serial number
+or the measured optical power, through an application callback
+as a pair of human readable strings.
+The field names and value formats are intended for display
+and may change in future releases:
+
+.. code-block:: c
+
+ static void
+ print_field(const char *name, const char *value, void *arg)
+ {
+ RTE_SET_USED(arg);
+ printf("%s: %s\n", name, value);
+ }
+
+ struct rte_eth_dev_module_info info;
+ struct rte_dev_eeprom_info eeprom = {0};
+
+ if (rte_eth_dev_get_module_info(port_id, &info) == 0) {
+ eeprom.data = malloc(info.eeprom_len);
+ eeprom.length = info.eeprom_len;
+ if (eeprom.data != NULL &&
+ rte_eth_dev_get_module_eeprom(port_id, &eeprom) == 0)
+ rte_eth_module_eeprom_parse(info.type, eeprom.data, eeprom.length,
+ print_field, NULL);
+ free(eeprom.data);
+ }
+
+The decoding function does not access the device,
+so it can also decode EEPROM data obtained from other sources.
+For ports managed by the Linux kernel, the ethtool ioctls
+``ETHTOOL_GMODULEINFO`` and ``ETHTOOL_GMODULEEEPROM``
+return the same module types and data layout.
+The ethtool netlink request ``ETHTOOL_MSG_MODULE_EEPROM_GET``
+is page addressed and does not report the module type,
+so its data has to be assembled into this layout first.
+The same decoding is available through the telemetry command
+``/ethdev/module_eeprom``.
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index dec96ccbc7..d87f1e0362 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -64,6 +64,14 @@ New Features
Added ``rte_vlan_insert_tpid()`` to the net library.
+* **Added module EEPROM decoding API to ethdev.**
+
+ Added the experimental ``rte_eth_module_eeprom_parse()`` function
+ to decode pluggable module EEPROM data according to the SFF specifications.
+ It can decode data read with ``rte_eth_dev_get_module_eeprom()``
+ or obtained from any other source with the same layout,
+ such as the Linux ethtool ``ETHTOOL_GMODULEEEPROM`` ioctl.
+
* **Updated AF_XDP driver.**
* Changed the default device plugin endpoint path used when
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index 7c57a9bb8f..af26e30d46 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -35,6 +35,7 @@
#include "ethdev_profile.h"
#include "ethdev_private.h"
#include "ethdev_trace.h"
+#include "sff_common.h"
#include "sff_telemetry.h"
#define ETH_XSTATS_ITER_NUM 0x100
@@ -7000,6 +7001,26 @@ rte_eth_dev_get_module_eeprom(uint16_t port_id,
return ret;
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_module_eeprom_parse, 26.11)
+int
+rte_eth_module_eeprom_parse(uint32_t type, const uint8_t *data, uint32_t length,
+ rte_eth_module_eeprom_field_cb cb, void *arg)
+{
+ struct sff_output out = { .field_cb = cb, .arg = arg };
+
+ if (data == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR, "Cannot parse module EEPROM from NULL data");
+ return -EINVAL;
+ }
+
+ if (cb == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR, "Cannot parse module EEPROM with NULL callback");
+ return -EINVAL;
+ }
+
+ return sff_decode_module_eeprom(type, data, length, &out);
+}
+
RTE_EXPORT_SYMBOL(rte_eth_dev_get_dcb_info)
int
rte_eth_dev_get_dcb_info(uint16_t port_id,
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index ae43420cf6..1924483689 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -5233,6 +5233,63 @@ int
rte_eth_dev_get_module_eeprom(uint16_t port_id, struct rte_dev_eeprom_info *info)
__rte_warn_unused_result;
+/**
+ * Callback invoked by rte_eth_module_eeprom_parse() for each decoded field.
+ *
+ * @param name
+ * Field name, for example "Vendor name" or "Laser bias current".
+ * The same name may be reported more than once,
+ * for example when a module complies with several transceiver types.
+ * @param value
+ * Field value as a human readable string, including the unit if any.
+ * @param arg
+ * The opaque argument passed to rte_eth_module_eeprom_parse().
+ *
+ * @note The field names and the value formats are intended for display.
+ * They follow the ethtool output and may change in future releases,
+ * so they should not be parsed or matched by applications.
+ */
+typedef void (*rte_eth_module_eeprom_field_cb)(const char *name,
+ const char *value, void *arg);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Decode pluggable module EEPROM data according to the SFF specifications.
+ *
+ * The data may come from rte_eth_dev_get_module_eeprom()
+ * or from any other source using the same layout,
+ * for example the Linux ethtool ioctls ETHTOOL_GMODULEINFO
+ * and ETHTOOL_GMODULEEEPROM.
+ * The function does not access any device
+ * and can be called without EAL initialization.
+ *
+ * @param type
+ * Module type, one of RTE_ETH_MODULE_SFF_*,
+ * as reported by rte_eth_dev_get_module_info().
+ * @param data
+ * Module EEPROM data starting at offset 0.
+ * @param length
+ * Length of the data in bytes.
+ * SFF-8472 diagnostics are decoded only if at least
+ * RTE_ETH_MODULE_SFF_8472_LEN bytes are provided.
+ * SFF-8436 and SFF-8636 thresholds and alarm flags are decoded
+ * only if at least RTE_ETH_MODULE_SFF_8636_MAX_LEN bytes are provided.
+ * @param cb
+ * Callback invoked for each decoded field.
+ * @param arg
+ * Opaque argument passed to the callback.
+ * @return
+ * - (0) if successful.
+ * - (-EINVAL) if bad parameter or data is too short for the module type.
+ * - (-ENOTSUP) if the module type is not supported.
+ */
+__rte_experimental
+int
+rte_eth_module_eeprom_parse(uint32_t type, const uint8_t *data, uint32_t length,
+ rte_eth_module_eeprom_field_cb cb, void *arg);
+
/**
* Set the list of multicast addresses to filter on an Ethernet device.
*
diff --git a/lib/ethdev/sff_common.h b/lib/ethdev/sff_common.h
index 6435f4d6f5..d346e1b378 100644
--- a/lib/ethdev/sff_common.h
+++ b/lib/ethdev/sff_common.h
@@ -17,7 +17,7 @@
/* Consumer of decoded module EEPROM fields */
struct sff_output {
/* Called once per decoded field, name may repeat */
- void (*field_cb)(const char *name, const char *value, void *arg);
+ rte_eth_module_eeprom_field_cb field_cb;
void *arg;
};
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v3 6/6] test: add ethdev module EEPROM decoding tests
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
` (4 preceding siblings ...)
2026-09-29 7:07 ` [PATCH v3 5/6] ethdev: add API to decode module EEPROM Roman Khromenok
@ 2026-09-29 7:07 ` Roman Khromenok
2026-09-29 16:02 ` [PATCH v3 0/6] ethdev: add API to decode module EEPROM Stephen Hemminger
6 siblings, 0 replies; 19+ messages in thread
From: Roman Khromenok @ 2026-09-29 7:07 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Thomas Monjalon, Andrew Rybchenko
Add unit tests for rte_eth_module_eeprom_parse().
The tests decode SFF-8079, SFF-8472 and SFF-8636 EEPROM images
built in the test and check the identification and diagnostic fields.
They also check that:
- the SFF-8472 diagnostics are skipped when page A2h is not complete,
- the SFF-8472 external calibration is applied when the data
is not 4-byte aligned,
- the SFF-8636 thresholds are decoded only when page 03h is provided.
Invalid parameters, too short data and unknown module types
must be rejected without invoking the callback.
Signed-off-by: Roman Khromenok <roma55592@yandex.ru>
---
v3: add SFF-8636 page 03h and unaligned external calibration cases,
rename test to ethdev_module_eeprom_autotest
app/test/meson.build | 1 +
app/test/test_ethdev_module_eeprom.c | 334 +++++++++++++++++++++++++++
2 files changed, 335 insertions(+)
create mode 100644 app/test/test_ethdev_module_eeprom.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 9dcdb069e8..c6a808d63d 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -74,6 +74,7 @@ source_file_deps = {
'test_errno.c': [],
'test_ethdev_api.c': ['ethdev'],
'test_ethdev_link.c': ['ethdev'],
+ 'test_ethdev_module_eeprom.c': ['ethdev'],
'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],
'test_event_dma_adapter.c': ['dmadev', 'eventdev', 'bus_vdev'],
'test_event_eth_rx_adapter.c': ['ethdev', 'eventdev', 'bus_vdev'],
diff --git a/app/test/test_ethdev_module_eeprom.c b/app/test/test_ethdev_module_eeprom.c
new file mode 100644
index 0000000000..d76bdc0060
--- /dev/null
+++ b/app/test/test_ethdev_module_eeprom.c
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Roman Khromenok
+ */
+
+#include <errno.h>
+#include <stdalign.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+
+#define MAX_FIELDS 128
+#define MAX_NAME_LEN 64
+#define MAX_VALUE_LEN 128
+
+struct decoded_fields {
+ unsigned int count;
+ struct {
+ char name[MAX_NAME_LEN];
+ char value[MAX_VALUE_LEN];
+ } field[MAX_FIELDS];
+};
+
+static struct decoded_fields fields;
+
+static void
+collect_field(const char *name, const char *value, void *arg)
+{
+ struct decoded_fields *f = arg;
+
+ if (f->count >= MAX_FIELDS)
+ return;
+ strlcpy(f->field[f->count].name, name, MAX_NAME_LEN);
+ strlcpy(f->field[f->count].value, value, MAX_VALUE_LEN);
+ f->count++;
+}
+
+static const char *
+find_field(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < fields.count; i++)
+ if (strcmp(fields.field[i].name, name) == 0)
+ return fields.field[i].value;
+ return NULL;
+}
+
+static int
+parse(uint32_t type, const uint8_t *data, uint32_t length)
+{
+ memset(&fields, 0, sizeof(fields));
+ return rte_eth_module_eeprom_parse(type, data, length, collect_field, &fields);
+}
+
+#define CHECK_FIELD(name, expected) do { \
+ const char *value = find_field(name); \
+ TEST_ASSERT_NOT_NULL(value, "Field \"%s\" not found", name); \
+ TEST_ASSERT(strcmp(value, expected) == 0, \
+ "Field \"%s\": expected \"%s\", got \"%s\"", name, expected, value); \
+} while (0)
+
+#define CHECK_NO_FIELD(name) \
+ TEST_ASSERT_NULL(find_field(name), "Unexpected field \"%s\"", name)
+
+static void
+put_u16(uint8_t *data, unsigned int offset, uint16_t value)
+{
+ data[offset] = value >> 8;
+ data[offset + 1] = value & 0xff;
+}
+
+/* SFP+ 10GBASE-LR module, SFF-8472 layout: page A0h then page A2h */
+static void
+fill_sfp(uint8_t *data)
+{
+ uint8_t *a2 = data + RTE_ETH_MODULE_SFF_8079_LEN;
+
+ memset(data, 0, RTE_ETH_MODULE_SFF_8472_LEN);
+ data[0] = 0x03; /* identifier: SFP */
+ data[1] = 0x04; /* extended identifier */
+ data[2] = 0x07; /* connector: LC */
+ data[3] = 0x20; /* 10G Base-LR */
+ data[11] = 0x06; /* encoding: 64B/66B */
+ data[12] = 103; /* nominal bit rate, units of 100 MBd */
+ memcpy(&data[20], "STARRY ", 16);
+ data[37] = 0x00;
+ data[38] = 0x1b;
+ data[39] = 0x21;
+ memcpy(&data[40], "SFP-10G-LR-20 ", 16);
+ memcpy(&data[56], "A ", 4);
+ memcpy(&data[68], "2024082600001 ", 16);
+ memcpy(&data[84], "240902 ", 8);
+ /* diagnostics implemented, internally calibrated, average RX power */
+ data[92] = 0x68;
+
+ put_u16(a2, 96, 0x2940); /* temperature: 41.25 C */
+ put_u16(a2, 98, 33205); /* voltage: 3.3205 V */
+ put_u16(a2, 100, 19790); /* TX bias: 39.580 mA */
+ put_u16(a2, 102, 7754); /* TX power: 0.7754 mW */
+ put_u16(a2, 104, 6724); /* RX power: 0.6724 mW */
+}
+
+/* QSFP28 module, SFF-8636 layout: lower page then upper page 00h */
+static void
+fill_qsfp(uint8_t *data)
+{
+ memset(data, 0, RTE_ETH_MODULE_SFF_8636_LEN);
+ data[0] = 0x11; /* identifier: QSFP28 */
+ put_u16(data, 22, 0x3040); /* temperature: 48.25 C */
+ put_u16(data, 26, 32638); /* voltage: 3.2638 V */
+ put_u16(data, 34, 1); /* channel 1 RX power: 0.0001 mW */
+ put_u16(data, 42, 21525); /* channel 1 TX bias: 43.050 mA */
+ put_u16(data, 50, 12763); /* channel 1 TX power: 1.2763 mW */
+
+ data[128] = 0x11; /* identifier: QSFP28 */
+ data[130] = 0x07; /* connector: LC */
+ memcpy(&data[148], "FINISAR CORP. ", 16);
+ data[165] = 0x00;
+ data[166] = 0x90;
+ data[167] = 0x65;
+ memcpy(&data[168], "FTLC1157RGPL6-FB", 16);
+ memcpy(&data[184], "A0", 2);
+ memcpy(&data[196], "X24A15P ", 16);
+ memcpy(&data[212], "190826 ", 8);
+ data[220] = 0x08; /* average RX power */
+}
+
+static int
+check_sfp_identity(void)
+{
+ CHECK_FIELD("Identifier", "0x03 (SFP)");
+ CHECK_FIELD("Connector", "0x07 (LC)");
+ CHECK_FIELD("Vendor name", "STARRY");
+ CHECK_FIELD("Vendor OUI", "00:1b:21");
+ CHECK_FIELD("Vendor PN", "SFP-10G-LR-20");
+ CHECK_FIELD("Vendor rev", "A");
+ CHECK_FIELD("Vendor SN", "2024082600001");
+ CHECK_FIELD("Date code", "240902");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8079(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+
+ fill_sfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8079, data, RTE_ETH_MODULE_SFF_8079_LEN),
+ "Failed to parse SFF-8079 data");
+ TEST_ASSERT_SUCCESS(check_sfp_identity(), "Wrong SFF-8079 base information");
+ CHECK_NO_FIELD("Optical diagnostics support");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8472(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+
+ fill_sfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8472, data, sizeof(data)),
+ "Failed to parse SFF-8472 data");
+ TEST_ASSERT_SUCCESS(check_sfp_identity(), "Wrong SFF-8472 base information");
+ CHECK_FIELD("Optical diagnostics support", "Yes");
+ CHECK_FIELD("Laser bias current", "39.580 mA");
+ CHECK_FIELD("Laser output power", "0.7754 mW / -1.10 dBm");
+ CHECK_FIELD("Receiver signal average optical power", "0.6724 mW / -1.72 dBm");
+ CHECK_FIELD("Module temperature", "41.25 degrees C / 106.25 degrees F");
+ CHECK_FIELD("Module voltage", "3.3205 V");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8472_short(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8472_LEN];
+ const uint32_t lengths[] = {
+ RTE_ETH_MODULE_SFF_8079_LEN,
+ RTE_ETH_MODULE_SFF_8472_LEN - 1,
+ };
+ unsigned int i;
+
+ fill_sfp(data);
+ for (i = 0; i < RTE_DIM(lengths); i++) {
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8472, data, lengths[i]),
+ "Failed to parse SFF-8472 data of %u bytes", lengths[i]);
+ TEST_ASSERT_SUCCESS(check_sfp_identity(),
+ "Wrong SFF-8472 base information from %u bytes", lengths[i]);
+ /* page A2h is not complete, diagnostics must be skipped */
+ CHECK_NO_FIELD("Optical diagnostics support");
+ CHECK_NO_FIELD("Laser bias current");
+ }
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_qsfp_8636(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8636_LEN];
+
+ fill_qsfp(data);
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8636, data, sizeof(data)),
+ "Failed to parse SFF-8636 data");
+ CHECK_FIELD("Identifier", "0x11 (QSFP28)");
+ CHECK_FIELD("Connector", "0x07 (LC)");
+ CHECK_FIELD("Vendor name", "FINISAR CORP.");
+ CHECK_FIELD("Vendor OUI", "00:90:65");
+ CHECK_FIELD("Vendor PN", "FTLC1157RGPL6-FB");
+ CHECK_FIELD("Vendor rev", "A0");
+ CHECK_FIELD("Vendor SN", "X24A15P");
+ CHECK_FIELD("Module temperature", "48.25 degrees C / 118.85 degrees F");
+ CHECK_FIELD("Module voltage", "3.2638 V");
+ /* page 03h is not provided */
+ CHECK_FIELD("Alarm/warning flags implemented", "No");
+ CHECK_FIELD("Laser tx bias current (Channel 1)", "43.050 mA");
+ CHECK_FIELD("Transmit avg optical power (Channel 1)", "1.2763 mW / 1.06 dBm");
+ CHECK_FIELD("Rcvr signal avg optical power(Channel 1)", "0.0001 mW / -40.00 dBm");
+ CHECK_NO_FIELD("Module temperature high alarm threshold");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_qsfp_8636_thresholds(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8636_MAX_LEN];
+
+ memset(data, 0, sizeof(data));
+ /* paging is supported (flat memory bit is clear), page 03h is at 0x200 */
+ fill_qsfp(data);
+ put_u16(data, 0x200, 0x5000); /* temperature high alarm: 80 C */
+
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8636, data, sizeof(data)),
+ "Failed to parse SFF-8636 data with page 03h");
+ CHECK_FIELD("Vendor name", "FINISAR CORP.");
+ CHECK_FIELD("Alarm/warning flags implemented", "Yes");
+ CHECK_FIELD("Module temperature high alarm threshold",
+ "80.00 degrees C / 176.00 degrees F");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_sfp_8472_ext_cal_unaligned(void)
+{
+ /* the SFF-8472 calibration floats must be readable from any address */
+ uint8_t alignas(8) buf[RTE_ETH_MODULE_SFF_8472_LEN + 1];
+ uint8_t *data = buf + 1;
+ uint8_t *a2 = data + RTE_ETH_MODULE_SFF_8079_LEN;
+ const uint8_t two[4] = { 0x40, 0x00, 0x00, 0x00 }; /* 2.0f big endian */
+
+ fill_sfp(data);
+ /* diagnostics implemented, externally calibrated, average RX power */
+ data[92] = 0x58;
+ /* unit slopes, zero offsets, RX power multiplied by RX_PWR(1) */
+ a2[76] = 1; /* TX bias slope */
+ a2[80] = 1; /* TX power slope */
+ a2[84] = 1; /* temperature slope */
+ a2[88] = 1; /* voltage slope */
+ memcpy(&a2[68], two, sizeof(two));
+
+ TEST_ASSERT_SUCCESS(parse(RTE_ETH_MODULE_SFF_8472, data, RTE_ETH_MODULE_SFF_8472_LEN),
+ "Failed to parse externally calibrated SFF-8472 data");
+ CHECK_FIELD("Laser bias current", "39.580 mA");
+ CHECK_FIELD("Receiver signal average optical power", "1.3448 mW / 1.29 dBm");
+ return TEST_SUCCESS;
+}
+
+static int
+test_module_eeprom_invalid(void)
+{
+ uint8_t data[RTE_ETH_MODULE_SFF_8636_MAX_LEN] = {0};
+ const uint32_t types[] = {
+ RTE_ETH_MODULE_SFF_8079,
+ RTE_ETH_MODULE_SFF_8472,
+ RTE_ETH_MODULE_SFF_8436,
+ RTE_ETH_MODULE_SFF_8636,
+ };
+ unsigned int i;
+ int ret;
+
+ /* all supported types require at least one full page */
+ for (i = 0; i < RTE_DIM(types); i++) {
+ ret = parse(types[i], data, 255);
+ TEST_ASSERT_EQUAL(ret, -EINVAL,
+ "Type %u with 255 bytes: expected %d, got %d", types[i], -EINVAL, ret);
+ TEST_ASSERT_EQUAL(fields.count, 0,
+ "Type %u with 255 bytes: callback called %u times",
+ types[i], fields.count);
+ }
+
+ ret = parse(0, data, sizeof(data));
+ TEST_ASSERT_EQUAL(ret, -ENOTSUP, "Type 0: expected %d, got %d", -ENOTSUP, ret);
+ ret = parse(RTE_ETH_MODULE_SFF_8436 + 1, data, sizeof(data));
+ TEST_ASSERT_EQUAL(ret, -ENOTSUP, "Unknown type: expected %d, got %d", -ENOTSUP, ret);
+ TEST_ASSERT_EQUAL(fields.count, 0, "Unknown type: callback called %u times",
+ fields.count);
+
+ ret = rte_eth_module_eeprom_parse(RTE_ETH_MODULE_SFF_8079, NULL,
+ RTE_ETH_MODULE_SFF_8079_LEN, collect_field, &fields);
+ TEST_ASSERT_EQUAL(ret, -EINVAL, "NULL data: expected %d, got %d", -EINVAL, ret);
+ ret = rte_eth_module_eeprom_parse(RTE_ETH_MODULE_SFF_8079, data,
+ RTE_ETH_MODULE_SFF_8079_LEN, NULL, NULL);
+ TEST_ASSERT_EQUAL(ret, -EINVAL, "NULL callback: expected %d, got %d", -EINVAL, ret);
+
+ return TEST_SUCCESS;
+}
+
+static struct unit_test_suite module_eeprom_testsuite = {
+ .suite_name = "ethdev module EEPROM decoding",
+ .setup = NULL,
+ .teardown = NULL,
+ .unit_test_cases = {
+ TEST_CASE(test_module_eeprom_sfp_8079),
+ TEST_CASE(test_module_eeprom_sfp_8472),
+ TEST_CASE(test_module_eeprom_sfp_8472_short),
+ TEST_CASE(test_module_eeprom_sfp_8472_ext_cal_unaligned),
+ TEST_CASE(test_module_eeprom_qsfp_8636),
+ TEST_CASE(test_module_eeprom_qsfp_8636_thresholds),
+ TEST_CASE(test_module_eeprom_invalid),
+ TEST_CASES_END()
+ }
+};
+
+static int
+test_module_eeprom(void)
+{
+ return unit_test_suite_runner(&module_eeprom_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_module_eeprom_autotest, NOHUGE_OK, ASAN_OK, test_module_eeprom);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v3 0/6] ethdev: add API to decode module EEPROM
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
` (5 preceding siblings ...)
2026-09-29 7:07 ` [PATCH v3 6/6] test: add ethdev module EEPROM decoding tests Roman Khromenok
@ 2026-09-29 16:02 ` Stephen Hemminger
6 siblings, 0 replies; 19+ messages in thread
From: Stephen Hemminger @ 2026-09-29 16:02 UTC (permalink / raw)
To: Roman Khromenok; +Cc: dev, Thomas Monjalon, Andrew Rybchenko
On Tue, 29 Sep 2026 09:07:28 +0200
Roman Khromenok <roma55592@yandex.ru> wrote:
> Since 22.07, ethdev contains SFF-8079, SFF-8472 and SFF-8636 decoders
> for pluggable module EEPROM (SFP, QSFP), ported from ethtool.
> They are reachable only through the telemetry command
> /ethdev/module_eeprom, which reads the EEPROM of a DPDK port
> and returns the result as a telemetry dictionary.
Applied to net-next with one addition. sff_telemetry.h and rte_telemetry.h
are include in rte_ethdev.c but no longer used.
AI also saw something you should look at:
Pre-existing, not for this series: sff_8472_calibration() applies
SFF-8472 external RX power calibration as
RX_PWR(0) + x * (RX_PWR(1) + RX_PWR(2) + RX_PWR(3))
The spec formula is
RX_PWR(4)*x^4 + RX_PWR(3)*x^3 + RX_PWR(2)*x^2 + RX_PWR(1)*x
+ RX_PWR(0)
SFF_A2_CAL_RXPWR4 is defined but never used. The code was carried
over from ethtool's sfpdiag.c, which has the same formula. The new
test only sets RX_PWR(1), so it does not hit this. A separate fix
would carry:
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-09-29 16:02 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-27 11:20 [PATCH 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-27 11:20 ` [PATCH 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
2026-09-27 11:20 ` [PATCH 2/4] ethdev: check module EEPROM length before decoding Roman Khromenok
2026-09-27 11:20 ` [PATCH 3/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-27 11:20 ` [PATCH 4/4] test: add ethdev module EEPROM decoding tests Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 1/4] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 2/4] ethdev: check module EEPROM length before decoding Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 3/4] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-28 8:47 ` [PATCH v2 4/4] test: add ethdev module EEPROM decoding tests Roman Khromenok
2026-09-28 18:11 ` [PATCH v2 0/4] ethdev: add API to decode module EEPROM Stephen Hemminger
2026-09-29 7:07 ` [PATCH v3 0/6] " Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 1/6] ethdev: fix out-of-bounds read of SFF-8636 thresholds Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 2/6] ethdev: decouple SFF module EEPROM decoders from telemetry Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 3/6] ethdev: check module EEPROM length before decoding Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 4/6] ethdev: avoid unaligned access in SFF-8472 decoder Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 5/6] ethdev: add API to decode module EEPROM Roman Khromenok
2026-09-29 7:07 ` [PATCH v3 6/6] test: add ethdev module EEPROM decoding tests Roman Khromenok
2026-09-29 16:02 ` [PATCH v3 0/6] ethdev: add API to decode module EEPROM Stephen Hemminger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox