Skip to content

Commit

Permalink
Support reading/writing module EEPROM data by page and offset (sonic-…
Browse files Browse the repository at this point in the history
…net#3008)

* Support reading/writing module EEPROM data by page and offset

* Fix unit test issue

* Fix unit test issue

* Fix review comment

* Update command ref

* Fix format

* Update main.py

* Fix review comments

* Fix review comment

* Remove un-intended change

* Update Command-Reference.md

* Update Command-Reference.md

* Fix review comments

* Fix review comments

* User click.IntRange to avoid duplicate validation

* Fix review comments

* Update sfputil_test.py
  • Loading branch information
Junchao-Mellanox committed Dec 8, 2023
1 parent 3037959 commit 641c6ad
Show file tree
Hide file tree
Showing 3 changed files with 570 additions and 22 deletions.
75 changes: 75 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@
* [MACsec config command](#macsec-config-command)
* [MACsec show command](#macsec-show-command)
* [MACsec clear command](#macsec-clear-command)
* [SFP Utilities Commands](#sfp-utilities-commands)
* [SFP Utilities read command](#sfp-utilities-read-command)
* [SFP Utilities write command](#sfp-utilities-write-command)
* [Static DNS Commands](#static-dns-commands)
* [Static DNS config command](#static-dns-config-command)
* [Static DNS show command](#static-dns-show-command)
Expand Down Expand Up @@ -12910,6 +12913,78 @@ Clear MACsec counters which is to reset all MACsec counters to ZERO.
Go Back To [Beginning of the document](#) or [Beginning of this section](#macsec-commands)
# SFP Utilities Commands
This sub-section explains the list of commands available for SFP utilities feature.
# SFP Utilities read command
- Read SFP EEPROM data
```
admin@sonic:~$ sfputil read-eeprom --help
Usage: sfputil read-eeprom [OPTIONS]
Read SFP EEPROM data
Options:
-p, --port <logical_port_name> Logical port name [required]
-n, --page <page> EEPROM page number [required]
-o, --offset <offset> EEPROM offset within the page [required]
-s, --size <size> Size of byte to be read [required]
--no-format Display non formatted data
--wire-addr TEXT Wire address of sff8472
--help Show this message and exit.
```
```
admin@sonic:~$ sfputil read-eeprom -p Ethernet0 -n 0 -o 100 -s 2
00000064 4a 44 |..|
admin@sonic:~$ sfputil read-eeprom --port Ethernet0 --page 0 --offset 0 --size 32
00000000 11 08 06 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
admin@sonic:~$ sfputil read-eeprom --port Ethernet0 --page 0 --offset 100 --size 2 --no-format
4a44
```
# SFP Utilities write command
- Write SFP EEPROM data
```
admin@sonic:~$ sfputil write-eeprom --help
Usage: sfputil write-eeprom [OPTIONS]
Write SFP EEPROM data
Options:
-p, --port <logical_port_name> Logical port name [required]
-n, --page <page> EEPROM page number [required]
-o, --offset <offset> EEPROM offset within the page [required]
-d, --data <data> Hex string EEPROM data [required]
--wire-addr TEXT Wire address of sff8472
--verify Verify the data by reading back
--help Show this message and exit.
```
- Write success
```
admin@sonic:~$ sfputil write-eeprom -p Ethernet0 -n 0 -o 100 -d 4a44
admin@sonic:~$ sfputil write-eeprom --port Etherent0 --page 0 --offset 100 --data 0000 --verify
```
- Write fail
```
admin@sonic:~$ sfputil write-eeprom -p Etherent0 -n 0 -o 100 -d 4a44 --verify
Error: Write data failed! Write: 4a44, read: 0000.
```
Go Back To [Beginning of the document](#) or [Beginning of this section](#sfp-utilities-commands)
# Static DNS Commands
This sub-section explains the list of the configuration options available for static DNS feature.
Expand Down
250 changes: 230 additions & 20 deletions sfputil/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
PAGE_OFFSET = 128

SFF8472_A0_SIZE = 256
MAX_EEPROM_PAGE = 255
MAX_EEPROM_OFFSET = 255
MIN_OFFSET_FOR_NON_PAGE0 = 128
MAX_OFFSET_FOR_A0H_UPPER_PAGE = 255
MAX_OFFSET_FOR_A0H_LOWER_PAGE = 127
MAX_OFFSET_FOR_A2H = 255
PAGE_SIZE_FOR_A0H = 256

EEPROM_DUMP_INDENT = ' ' * 8

# TODO: We should share these maps and the formatting functions between sfputil and sfpshow
QSFP_DD_DATA_MAP = {
Expand Down Expand Up @@ -793,33 +802,62 @@ def eeprom_hexdump_sff8636(port, physical_port, page):

return output


def eeprom_dump_general(physical_port, page, overall_offset, size, page_offset, no_format=False):
"""
Dump module EEPROM for given pages in hex format.
Args:
logical_port_name: logical port name
pages: a list of pages to be dumped. The list always include a default page list and the target_page input by
user
target_page: user input page number, optional. target_page is only for display purpose
Returns:
tuple(0, dump string) if success else tuple(error_code, error_message)
"""
sfp = platform_chassis.get_sfp(physical_port)
page_dump = sfp.read_eeprom(overall_offset, size)
if page_dump is None:
return ERROR_NOT_IMPLEMENTED, f'Error: Failed to read EEPROM for page {page:x}h, overall_offset {overall_offset}, page_offset {page_offset}, size {size}!'
if not no_format:
return 0, hexdump(EEPROM_DUMP_INDENT, page_dump, page_offset, start_newline=False)
else:
return 0, ''.join('{:02x}'.format(x) for x in page_dump)


def convert_byte_to_valid_ascii_char(byte):
if byte < 32 or 126 < byte:
return '.'
else:
return chr(byte)

def hexdump(indent, data, mem_address):
ascii_string = ''
result = ''
for byte in data:
ascii_string = ascii_string + convert_byte_to_valid_ascii_char(byte)
byte_string = "{:02x}".format(byte)
if mem_address % 16 == 0:
mem_address_string = "{:08x}".format(mem_address)
result += '\n{}{} '.format(indent, mem_address_string)
result += '{} '.format(byte_string)
elif mem_address % 16 == 15:
result += '{} '.format(byte_string)
result += '|{}|'.format(ascii_string)
ascii_string = ""
elif mem_address % 16 == 8:
result += ' {} '.format(byte_string)
def hexdump(indent, data, mem_address, start_newline=True):
size = len(data)
offset = 0
lines = [''] if start_newline else []
while size > 0:
offset_str = "{}{:08x}".format(indent, mem_address)
if size >= 16:
first_half = ' '.join("{:02x}".format(x) for x in data[offset:offset + 8])
second_half = ' '.join("{:02x}".format(x) for x in data[offset + 8:offset + 16])
ascii_str = ''.join(convert_byte_to_valid_ascii_char(x) for x in data[offset:offset + 16])
lines.append(f'{offset_str} {first_half} {second_half} |{ascii_str}|')
elif size > 8:
first_half = ' '.join("{:02x}".format(x) for x in data[offset:offset + 8])
second_half = ' '.join("{:02x}".format(x) for x in data[offset + 8:offset + size])
padding = ' ' * (16 - size)
ascii_str = ''.join(convert_byte_to_valid_ascii_char(x) for x in data[offset:offset + size])
lines.append(f'{offset_str} {first_half} {second_half}{padding} |{ascii_str}|')
break
else:
result += '{} '.format(byte_string)
mem_address += 1

return result
hex_part = ' '.join("{:02x}".format(x) for x in data[offset:offset + size])
padding = ' ' * (16 - size)
ascii_str = ''.join(convert_byte_to_valid_ascii_char(x) for x in data[offset:offset + size])
lines.append(f'{offset_str} {hex_part} {padding} |{ascii_str}|')
break
size -= 16
offset += 16
mem_address += 16
return '\n'.join(lines)

# 'presence' subcommand
@show.command()
Expand Down Expand Up @@ -1567,5 +1605,177 @@ def target(port_name, target):
click.echo("Target Mode set failed!")
sys.exit(EXIT_FAIL)


# 'read-eeprom' subcommand
@cli.command()
@click.option('-p', '--port', metavar='<logical_port_name>', help="Logical port name", required=True)
@click.option('-n', '--page', metavar='<page>', type=click.IntRange(0, MAX_EEPROM_PAGE), help="EEPROM page number", required=True)
@click.option('-o', '--offset', metavar='<offset>', type=click.IntRange(0, MAX_EEPROM_OFFSET), help="EEPROM offset within the page", required=True)
@click.option('-s', '--size', metavar='<size>', type=click.IntRange(1, MAX_EEPROM_OFFSET + 1), help="Size of byte to be read", required=True)
@click.option('--no-format', is_flag=True, help="Display non formatted data")
@click.option('--wire-addr', help="Wire address of sff8472")
def read_eeprom(port, page, offset, size, no_format, wire_addr):
"""Read SFP EEPROM data
"""
try:
if platform_sfputil.is_logical_port(port) == 0:
click.echo("Error: invalid port {}".format(port))
print_all_valid_port_values()
sys.exit(ERROR_INVALID_PORT)

if is_port_type_rj45(port):
click.echo("This functionality is not applicable for RJ45 port {}.".format(port))
sys.exit(EXIT_FAIL)

physical_port = logical_port_to_physical_port_index(port)
sfp = platform_chassis.get_sfp(physical_port)
if not sfp.get_presence():
click.echo("{}: SFP EEPROM not detected\n".format(port))
sys.exit(EXIT_FAIL)

from sonic_platform_base.sonic_xcvr.api.public import sff8472
api = sfp.get_xcvr_api()
if api is None:
click.echo('Error: SFP EEPROM not detected!')
if not isinstance(api, sff8472.Sff8472Api):
overall_offset = get_overall_offset_general(api, page, offset, size)
else:
overall_offset = get_overall_offset_sff8472(api, page, offset, size, wire_addr)
return_code, output = eeprom_dump_general(physical_port, page, overall_offset, size, offset, no_format)
if return_code != 0:
click.echo("Error: Failed to read EEPROM!")
sys.exit(return_code)
click.echo(output)
except NotImplementedError:
click.echo("This functionality is currently not implemented for this platform")
sys.exit(ERROR_NOT_IMPLEMENTED)
except ValueError as e:
click.echo(f"Error: {e}")
sys.exit(EXIT_FAIL)


# 'write-eeprom' subcommand
@cli.command()
@click.option('-p', '--port', metavar='<logical_port_name>', help="Logical port name", required=True)
@click.option('-n', '--page', metavar='<page>', type=click.IntRange(0, MAX_EEPROM_PAGE), help="EEPROM page number", required=True)
@click.option('-o', '--offset', metavar='<offset>', type=click.IntRange(0, MAX_EEPROM_OFFSET), help="EEPROM offset within the page", required=True)
@click.option('-d', '--data', metavar='<data>', help="Hex string EEPROM data", required=True)
@click.option('--wire-addr', help="Wire address of sff8472")
@click.option('--verify', is_flag=True, help="Verify the data by reading back")
def write_eeprom(port, page, offset, data, wire_addr, verify):
"""Write SFP EEPROM data"""
try:
if platform_sfputil.is_logical_port(port) == 0:
click.echo("Error: invalid port {}".format(port))
print_all_valid_port_values()
sys.exit(ERROR_INVALID_PORT)

if is_port_type_rj45(port):
click.echo("This functionality is not applicable for RJ45 port {}.".format(port))
sys.exit(EXIT_FAIL)

physical_port = logical_port_to_physical_port_index(port)
sfp = platform_chassis.get_sfp(physical_port)
if not sfp.get_presence():
click.echo("{}: SFP EEPROM not detected\n".format(port))
sys.exit(EXIT_FAIL)

try:
bytes = bytearray.fromhex(data)
except ValueError:
click.echo("Error: Data must be a hex string of even length!")
sys.exit(EXIT_FAIL)

from sonic_platform_base.sonic_xcvr.api.public import sff8472
api = sfp.get_xcvr_api()
if api is None:
click.echo('Error: SFP EEPROM not detected!')
sys.exit(EXIT_FAIL)

if not isinstance(api, sff8472.Sff8472Api):
overall_offset = get_overall_offset_general(api, page, offset, len(bytes))
else:
overall_offset = get_overall_offset_sff8472(api, page, offset, len(bytes), wire_addr)
success = sfp.write_eeprom(overall_offset, len(bytes), bytes)
if not success:
click.echo("Error: Failed to write EEPROM!")
sys.exit(ERROR_NOT_IMPLEMENTED)
if verify:
read_data = sfp.read_eeprom(overall_offset, len(bytes))
if read_data != bytes:
click.echo(f"Error: Write data failed! Write: {''.join('{:02x}'.format(x) for x in bytes)}, read: {''.join('{:02x}'.format(x) for x in read_data)}")
sys.exit(EXIT_FAIL)
except NotImplementedError:
click.echo("This functionality is currently not implemented for this platform")
sys.exit(ERROR_NOT_IMPLEMENTED)
except ValueError as e:
click.echo("Error: {}".format(e))
sys.exit(EXIT_FAIL)


def get_overall_offset_general(api, page, offset, size):
"""
Validate input parameter page, offset, size and translate them to overall offset
Args:
api: cable API object
page: module EEPROM page number.
offset: module EEPROM page offset.
size: number bytes of the data to be read/write
Returns:
The overall offset
"""
if api.is_flat_memory():
if page != 0:
raise ValueError(f'Invalid page number {page}, only page 0 is supported')

if page != 0:
if offset < MIN_OFFSET_FOR_NON_PAGE0:
raise ValueError(f'Invalid offset {offset} for page {page}, valid range: [128, 255]')

if size + offset - 1 > MAX_EEPROM_OFFSET:
raise ValueError(f'Invalid size {size}, valid range: [1, {255 - offset + 1}]')

return page * PAGE_SIZE + offset


def get_overall_offset_sff8472(api, page, offset, size, wire_addr):
"""
Validate input parameter page, offset, size, wire_addr and translate them to overall offset
Args:
api: cable API object
page: module EEPROM page number.
offset: module EEPROM page offset.
size: number bytes of the data to be read/write
wire_addr: case-insensitive wire address string. Only valid for sff8472, a0h or a2h.
Returns:
The overall offset
"""
if not wire_addr:
raise ValueError("Invalid wire address for sff8472, must a0h or a2h")

is_active_cable = not api.is_copper()
valid_wire_address = ('a0h', 'a2h') if is_active_cable else ('a0h',)
wire_addr = wire_addr.lower()
if wire_addr not in valid_wire_address:
raise ValueError(f"Invalid wire address {wire_addr} for sff8472, must be {' or '.join(valid_wire_address)}")

if wire_addr == 'a0h':
if page != 0:
raise ValueError(f'Invalid page number {page} for wire address {wire_addr}, only page 0 is supported')
max_offset = MAX_OFFSET_FOR_A0H_UPPER_PAGE if is_active_cable else MAX_OFFSET_FOR_A0H_LOWER_PAGE
if offset > max_offset:
raise ValueError(f'Invalid offset {offset} for wire address {wire_addr}, valid range: [0, {max_offset}]')
if size + offset - 1 > max_offset:
raise ValueError(
f'Invalid size {size} for wire address {wire_addr}, valid range: [1, {max_offset - offset + 1}]')
return offset
else:
if size + offset - 1 > MAX_OFFSET_FOR_A2H:
raise ValueError(f'Invalid size {size} for wire address {wire_addr}, valid range: [1, {255 - offset + 1}]')
return page * PAGE_SIZE + offset + PAGE_SIZE_FOR_A0H


if __name__ == '__main__':
cli()
Loading

0 comments on commit 641c6ad

Please sign in to comment.