ethtool: Add 'pages on|off' option for module EEPROM hex dump
Subsystems (2)
v2 cover ethtool: Add 'pages on|off' option for module EEPROM hex dump
The existing 'ethtool -m' command can dump module EEPROM data either as a parsed pretty-print or as a raw/hex dump. The pretty-printer reads multiple pages per module type, but decodes only specific fields from them.
In practice, both outputs are often needed together for offline debugging:
the pretty-print to identify the module type and decoded fields, and the hex dump for fields the pretty-printer does not decode. However, there is no single command that provides the raw hex dump of all relevant pages, organized by page boundary. This is especially problematic for CMIS transceivers where the existing hex dump does not provide Upper Pages beyond Page 02h, let alone banked pages.
This series adds 'pages on|off' sub-option to 'hex on|off' for 'ethtool -m':
$ ethtool -m hex on pages on
That produces a hex dump organized by page, matching exactly the pages read by the pretty-printer for each module type.
Each page is preceded by a header identifying the page number and, for banked modules, the bank number. For SFP, where the two memory regions are separate I2C addresses rather than pages, the header shows the I2C address instead.
In JSON context (--json), each function emits a "pages" array with one object per page, containing page/bank or I2C address, offset, length and data. Consumers that need a stable, machine-parseable form of the hex dump can use this output instead of the text format.
SFF-8636 Output examples (values zeroed to omit vendor-specific identifiers):
$ ethtool -m swp61 hex on pages on Page: 0x0
Offset Values ------ ------ 0x0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Page: 0x0
Offset Values ------ ------ 0x0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
$ ethtool -j -m swp61 hex on pages on
[ {
"pages": [ {
"bank": 0,
"page": 0,
"offset": 0,
"length": 128,
"i2c_address": 80,
"data": ["0","0",...,"0"]
},{
"bank": 0,
"page": 0,
"offset": 128,
"length": 128,
"i2c_address": 80,
"data": ["0","0",...,"0"]
} ]
} ]v2:
* Add JSON output support: each per-type function emits a "pages" array with one object per page containing bank, page, offset, length, i2c_address and data.
* The shared module_dump_eeprom_hex() helper takes a struct module_eeprom_dump with print_bank / print_i2c flags selecting which header fields are printed
* In JSON context all fields are emitted unconditionally.
* Document the JSON output format in the man page.
Danielle Ratson (5):
module-common: Add module_dump_eeprom_hex() helper
sfpid: Refactor sff8079_show_all_nl() to separate page retrieval from
display
module: Add per-type EEPROM page hex dump functions
netlink: module-eeprom: Add 'hex on pages on' option for
page-organized hex dump
ethtool: Add man page and bash completion for 'pages on|off'
cmis.c | 70 +++++++++++++++++++++++++++---- cmis.h | 2 +- ethtool.8.in | 18 ++++++++ ethtool.c | 1 + internal.h | 4 +- module-common.c | 38 +++++++++++++++++ module-common.h | 12 ++++++ netlink/module-eeprom.c | 43 +++++++++++++++---- qsfp.c | 67 +++++++++++++++++++++++++---- sfpid.c | 79 +++++++++++++++++++++++++++-------- shell-completion/bash/ethtool | 2 + 11 files changed, 294 insertions(+), 42 deletions(-)
Signature · 1 line
2.51.0
PATCH v2 1/5 module-common: Add module_dump_eeprom_hex() helper
Add module_dump_eeprom_hex() as the shared building block for the per-type EEPROM page hex dump functions added in the next patch. It prints a per-page header followed by a hex dump; the header format is selected by print_i2c / print_bank flags in struct module_eeprom_dump.
In JSON context, the flags are ignored and all metadata fields are emitted as one object inside the enclosing "pages" array.
--- module-common.c | 38 ++++++++++++++++++++++++++++++++++++++ module-common.h | 12 ++++++++++++ 2 files changed, 50 insertions(+)
module-common.c +38 -0
@@ -656,3 +656,41 @@ void module_show_dom_mod_lvl_monitors(const struct sff_diags *sd)PRINT_VCC_ALL ("Module voltage" ,"module_voltage_measurement" , sd->sfp_voltage[MCURR ]); } + +/* Print one EEPROM memory block with a descriptive header followed by hex +* dump. The header is selected by the dump->print_i2c and dump->print_bank +* flags: "Page: 0xN" is always printed, an I2C-addressed block prints +* "I2C Address: 0xN" and a banked page prints "Bank: 0xN". +* +* In JSON context, all fields (bank, page, offset, length, i2c_address) are +* emitted unconditionally as a JSON object inside the enclosing "pages" +* array. +*/ +void module_dump_eeprom_hex (const struct module_eeprom_dump *dump) +{ +u32 i; + +if (is_json_context ()) { +open_json_object (NULL ); +print_uint (PRINT_JSON ,"bank" ,"%u" , dump->bank); +print_uint (PRINT_JSON ,"page" ,"%u" , dump->page); +print_uint (PRINT_JSON ,"offset" ,"%u" , dump->offset); +print_uint (PRINT_JSON ,"length" ,"%u" , dump->length); +print_uint (PRINT_JSON ,"i2c_address" ,"%u" , dump->i2c_address); +open_json_array ("data" ,"" ); +for (i =0 ; i < dump->length; i++) +print_hex (PRINT_JSON ,NULL ,"%02x" , dump->data[i]); +close_json_array ("" ); +close_json_object (); +return ; + } + +if (dump->print_i2c) +printf ("I2C Address: 0x%02x\n" , dump->i2c_address); +if (dump->print_bank) +printf ("Bank: 0x%x\n" , dump->bank); +printf ("Page: 0x%x\n\n" , dump->page); + +dump_hex (stdout, dump->data, dump->length, dump->offset); +printf ("\n" ); +}
module-common.h +12 -0
@@ -261,6 +261,17 @@ struct module_aw_chan {__u8 adver_value;/* Supported if (offset & value) != 0. */ }; +struct module_eeprom_dump { +u32 offset; +u32 length; +u8 page; +u8 bank; +u8 i2c_address; +bool print_bank; +bool print_i2c; +const u8 *data; +}; +extern const struct module_aw_mod module_aw_mod_flags[];extern const struct module_aw_chan module_aw_chan_flags[];
@@ -283,5 +294,6 @@ void module_show_identifier(const __u8 *id, int id_offset);void module_show_connector (const __u8 *id,int ctor_offset);void module_show_mit_compliance (u16 value);void module_show_dom_mod_lvl_monitors (const struct sff_diags *sd); +void module_dump_eeprom_hex (const struct module_eeprom_dump *dump);#endif /* MODULE_COMMON_H__ */
Signature · 1 line
2.51.0
PATCH v2 2/5 sfpid: Refactor sff8079_show_all_nl() to separate page retrieval from display
In reply to Danielle RatsonRestructure sff8079_show_all_nl() to first retrieve all the necessary pages (A0h and optionally A2h) and then pretty-print them, matching the pattern already used in cmis_show_all_nl() and sff8636_show_all_nl().
This prepares for the next patch which extends the function with a hex dump mode.
Note that if reading A2h fails, the function now returns an error without producing partial output, matching the behavior of the other parsers.
--- sfpid.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-)
sfpid.c +16 -13
@@ -500,6 +500,7 @@ static int sff8079_get_eeprom_page(struct cmd_context *ctx, u8 i2c_address,int sff8079_show_all_nl (struct cmd_context *ctx) { +bool a2h_present;u8 *buf;int ret;
@@ -516,24 +517,26 @@ int sff8079_show_all_nl(struct cmd_context *ctx)if (ret) goto out; +/* Check if A2h page is present */ + a2h_present = buf[92 ] & (1 <<6 ); + +if (a2h_present) { +/* Read A2h page */ + ret =sff8079_get_eeprom_page (ctx,SFF8079_I2C_ADDRESS_HIGH , + buf +ETH_MODULE_SFF_8079_LEN ); +if (ret) { +fprintf (stderr,"Failed to read Page A2h\n" ); + goto out; + } + } +new_json_obj (ctx->json);open_json_object (NULL );sff8079_show_all_common (buf); -/* Finish if A2h page is not present */ -if (!(buf[92 ] & (1 <<6 ))) - goto out_json; - -/* Read A2h page */ - ret =sff8079_get_eeprom_page (ctx,SFF8079_I2C_ADDRESS_HIGH , - buf +ETH_MODULE_SFF_8079_LEN ); -if (ret) { -fprintf (stderr,"Failed to read Page A2h.\n" ); - goto out_json; - } +if (a2h_present) +sff8472_show_all (buf); -sff8472_show_all (buf); -out_json:close_json_object ();delete_json_obj (); out:
Signature · 1 line
2.51.0
PATCH v2 3/5 module: Add per-type EEPROM page hex dump functions
In reply to Danielle RatsonExtend sff8079_show_all_nl(), cmis_show_all_nl() and sff8636_show_all_nl() with a bool dump_pages parameter to support hex dump output alongside their existing pretty-print output. Each function first retrieves all the necessary pages and then either pretty-prints them or performs a hex dump according to the argument.
The hex dump and pretty-print logic is extracted into dedicated static functions for each parser type:
- cmis_hex_dump() / cmis_pretty_print()
- sff8636_hex_dump() / sff8636_pretty_print()
- sff8079_hex_dump() / sff8079_pretty_print()
Each function dumps the same pages as the corresponding pretty-printer:
- CMIS: uses cmis_memory_map_init_pages() to read the lower memory and relevant upper pages across all supported banks, using generic iteration over the memory map. Each block is labeled with "Bank: 0xN" followed by "Page: 0xM".
- SFF-8636: uses sff8636_memory_map_init_pages() to read the lower memory and relevant upper pages, using generic iteration over the memory map. Each block is labeled "Page: 0xN".
- SFF-8079: uses sff8079_get_eeprom_page() to read A0h lower and optional A2h lower. Each block is labeled with "I2C Address: 0xN" followed by "Page: 0xM".
In JSON context (--json), each function emits a "pages" array with one object per page containing bank, page, offset, length, i2c_address and data. Consumers that need a stable, machine-parseable form of the hex dump can use this output instead of the text format.
--- cmis.c | 70 ++++++++++++++++++++++++++++++++++++----- cmis.h | 2 +- internal.h | 4 +-- netlink/module-eeprom.c | 6 ++-- qsfp.c | 67 ++++++++++++++++++++++++++++++++++----- sfpid.c | 60 +++++++++++++++++++++++++++++------ 6 files changed, 179 insertions(+), 30 deletions(-)
cmis.c +63 -7
@@ -1022,6 +1022,64 @@ void cmis_show_all_ioctl(const __u8 *id)cmis_show_all_common (&map); } +static void cmis_hex_dump (struct cmd_context *ctx, +const struct cmis_memory_map *map) +{ +struct module_eeprom_dump dump = { + .length =CMIS_PAGE_SIZE , + .i2c_address =CMIS_I2C_ADDRESS , + .print_bank = true, + }; +u8 bank, page; + +new_json_obj (ctx->json); +if (is_json_context ()) { +open_json_object (NULL ); +open_json_array ("pages" ,"" ); + } + + dump.data = map->lower_memory; +module_dump_eeprom_hex (&dump); + +for (bank =0 ; bank <CMIS_MAX_BANKS ; bank++) { +for (page =0 ; page <CMIS_MAX_PAGES ; page++) { +const __u8 *buf = map->upper_memory[bank][page]; + +if (!buf) +continue ; + +/* Upper memory starts at one page size into +* the buffer, since pages are accessed at +* offset between page size and twice the +* page size. +*/ + dump.offset =CMIS_PAGE_SIZE ; + dump.page = page; + dump.bank = bank; + dump.data = buf +CMIS_PAGE_SIZE ; +module_dump_eeprom_hex (&dump); + } + } + +if (is_json_context ()) { +close_json_array ("" ); +close_json_object (); + } +delete_json_obj (); +} + +static void cmis_pretty_print (struct cmd_context *ctx, +const struct cmis_memory_map *map) +{ +new_json_obj (ctx->json); +open_json_object (NULL ); + +cmis_show_all_common (map); + +close_json_object (); +delete_json_obj (); +} +static void cmis_request_init (struct ethtool_module_eeprom *request,u8 bank,u8 page,u32 offset) {
@@ -1117,21 +1175,19 @@ cmis_memory_map_init_pages(struct cmd_context *ctx,return 0 ; } -int cmis_show_all_nl (struct cmd_context *ctx) +int cmis_show_all_nl (struct cmd_context *ctx,bool dump_pages) {struct cmis_memory_map map = {};int ret; -new_json_obj (ctx->json); -open_json_object (NULL ); - ret =cmis_memory_map_init_pages (ctx, &map);if (ret <0 )return ret; -cmis_show_all_common (&map); -close_json_object (); -delete_json_obj (); +if (dump_pages) +cmis_hex_dump (ctx, &map); +else +cmis_pretty_print (ctx, &map);return 0 ; }
cmis.h +1 -1
@@ -201,6 +201,6 @@void cmis_show_all_ioctl (const __u8 *id); -int cmis_show_all_nl (struct cmd_context *ctx); +int cmis_show_all_nl (struct cmd_context *ctx,bool dump_pages);#endif /* CMIS_H__ */
internal.h +2 -2
@@ -381,14 +381,14 @@ int rxclass_rule_del(struct cmd_context *ctx, __u32 loc);/* Module EEPROM parsing code */ void sff8079_show_all_ioctl (const __u8 *id); -int sff8079_show_all_nl (struct cmd_context *ctx); +int sff8079_show_all_nl (struct cmd_context *ctx,bool dump_pages);/* Optics diagnostics */ void sff8472_show_all (const __u8 *id);/* QSFP Optics diagnostics */ void sff8636_show_all_ioctl (const __u8 *id,__u32 eeprom_len); -int sff8636_show_all_nl (struct cmd_context *ctx); +int sff8636_show_all_nl (struct cmd_context *ctx,bool dump_pages);/* FUJITSU Extended Socket network device */ int fjes_dump_regs (struct ethtool_drvinfo *info,struct ethtool_regs *regs);
netlink/module-eeprom.c +3 -3
@@ -228,18 +228,18 @@ static int eeprom_parse(struct cmd_context *ctx)case MODULE_ID_GBIC :case MODULE_ID_SOLDERED_MODULE :case MODULE_ID_SFP : -return sff8079_show_all_nl (ctx); +return sff8079_show_all_nl (ctx, false);case MODULE_ID_QSFP :case MODULE_ID_QSFP28 :case MODULE_ID_QSFP_PLUS : -return sff8636_show_all_nl (ctx); +return sff8636_show_all_nl (ctx, false);case MODULE_ID_QSFP_DD :case MODULE_ID_OSFP :case MODULE_ID_DSFP :case MODULE_ID_QSFP_PLUS_CMIS :case MODULE_ID_SFP_DD_CMIS :case MODULE_ID_SFP_PLUS_CMIS : -return cmis_show_all_nl (ctx); +return cmis_show_all_nl (ctx, false);#endif default :/* If we cannot recognize the memory map, default to dumping
qsfp.c +60 -7
90 lines not on this page · raw message
sfpid.c +50 -10
72 lines not on this page · raw message
Signature · 1 line
2.51.0
PATCH v2 4/5 netlink: module-eeprom: Add 'hex on pages on' option for page-organized hex dump
In reply to Danielle RatsonAdd 'pages on|off' sub-option to 'hex on|off' for 'ethtool -m'. When 'hex on pages on' is specified, eeprom_parse() dispatches to the appropriate per-type function with dump_pages=true, producing a hex dump of all relevant EEPROM pages with a per-page header showing the page number and optional bank number.
eeprom_parse() is extended with a bool dump_pages parameter to select between pretty-print and hex dump output, avoiding duplication of the module-type dispatch logic.
Unlike plain 'hex on', 'hex on pages on' does not fall back to the ioctl path, as page selection is handled internally by the per-type functions.
Unknown module types fall back to dumping the lower 128 bytes.
'pages' and 'hex' are both parsed as independent flat options, since the parameter parser does not support context-dependent argument recognition. The requirement for 'hex on' when using 'pages on' is enforced by an explicit validation check.
SFF-8636 Output example (values zeroed to omit vendor-specific identifiers):
$ ethtool -m swp61 hex on pages on Page: 0x0
Offset Values ------ ------ 0x0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Page: 0x0
Offset Values ------ ------ 0x0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x00f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Note: 'Page: 0x0' appears twice in the output. The first block is the lower memory (bytes 0x00-0x7f), which is always accessible. The second block is upper memory page 00h (bytes 0x80-0xff). Both carry the same page label but are distinguished by their offset range in the hex dump.
--- ethtool.c | 1 + netlink/module-eeprom.c | 43 ++++++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 7 deletions(-)
ethtool.c +1 -0
@@ -6165,6 +6165,7 @@ static const struct option args[] = { .help = "Query/Decode Module EEPROM information and optical diagnostics if available", .xhelp =" [ raw on|off ]\n" " [ hex on|off ]\n" +" [ pages on|off ]\n" " [ offset N ]\n" " [ length N ]\n" " [ page N ]\n"
netlink/module-eeprom.c +36 -7
97 lines not on this page · raw message
Signature · 1 line
2.51.0
PATCH v2 5/5 ethtool: Add man page and bash completion for 'pages on|off'
In reply to Danielle RatsonAdd 'pages on|off' to the bash completion for ethtool -m and the ethtool man page.
The man page documents that 'pages on' requires 'hex on', describes the per-page header format, and notes that it cannot be combined with offset, length, page, bank or i2c parameters.
--- ethtool.8.in | 18 ++++++++++++++++++ shell-completion/bash/ethtool | 2 ++ 2 files changed, 20 insertions(+)
ethtool.8.in +18 -0
@@ -403,6 +403,7 @@ ethtool \- query or control network driver and hardware settings .I devname .B2 raw on off .B2 hex on off +.B2 pages on off .BN offset .BN length .BN page
@@ -1431,6 +1432,23 @@ allowed. In such a case and .I length parameters are treated relatively to EEPROM page boundaries. +When +.B hex on pages on +is specified, dumps all relevant EEPROM pages in hex, organized by page. +Each page is preceded by a header identifying the page number. For banked pages, +the bank number is also shown, and for SFP modules, the I2C address is also +shown, as different parts of the EEPROM memory map are accessible via different +I2C addresses. The set of pages dumped matches what the pretty-printer reads for +each module type. This option cannot be combined with +.I offset, length, page, bank +or +.I i2c +parameters. When combined with +.BI \-\-json , +the output is a JSON object with a "pages" array. Each element is an +object with the fields "bank", "page", "offset", "length", "i2c_address" +and "data", emitted unconditionally regardless of module type. "data" is +an array of lowercase hex strings, one per byte. .TP .B \-\-show\-priv\-flags Queries the specified network device for its private flags. The
shell-completion/bash/ethtool +2 -0
@@ -671,11 +671,13 @@ _ethtool_module_info() [hex]=1 [length]=1 [offset]=1 + [pages]=1 [raw]=1 ) case "$prev" in hex|\ + pages|\ raw) COMPREPLY=( $( compgen -W 'on off' -- "$cur" ) ) return ;;
Signature · 1 line
2.51.0
Thanks for adding json support.
Quoted 9 lines
$ ethtool -j -m swp61 hex on pages on
[ {
"pages": [ {
"bank": 0,
"page": 0,
"offset": 0,
"length": 128,
"i2c_address": 80,
"data": ["0","0",...,"0"]What is the value of length? It seems to be something you can determine from data itself. Is there a legitimate case for length != len(data)?
Andrew
Quoted 21 lines
-----Original Message-----
From: Andrew Lunn <[email protected]>
Sent: Tuesday, 19 May 2026 17:10
To: Danielle Ratson <[email protected]>
Cc: [email protected]; [email protected]; Ido Schimmel <[email protected]>; Petr Machata <[email protected]>
Subject: Re: [PATCH ethtool-next v2 0/5] ethtool: Add 'pages on|off' option for module EEPROM hex dumpThanks for adding json support.
Quoted 9 lines
$ ethtool -j -m swp61 hex on pages on [ { "pages": [ { "bank": 0, "page": 0, "offset": 0, "length": 128, "i2c_address": 80, "data": ["0","0",...,"0"]
Quoted 4 lines
What is the value of length? It seems to be something you can determine from data itself. Is there a legitimate case for length != len(data)?
Andrew
Right, will fix, thanks!