Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reading/writing module EEPROM data by page and offset #3008

Merged
merged 19 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 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 @@ -12793,6 +12796,61 @@ 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] <port_name> <page> <offset> <size>
Read SFP EEPROM data
Options:
--no-format Display non formatted data.
--wire-addr Wire address of sff8472.
--help Show this
```

```
admin@sonic:~$ sfputil read-eeprom Ethernet0 0 100 2
00000064 4a 44 |..|

admin@sonic:~$ sfputil read-eeprom Ethernet0 0 0 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 Ethernet0 0 100 2 --no-format
4a44
```

# SFP Utilities write command

- Write SFP EEPROM data

```
admin@sonic:~$ sfputil write-eeprom --help
Usage: sfputil write-eeprom [OPTIONS] <port> <page> <offset> <data>

Write SFP EEPROM data

Options:
--wire-addr Wire address of sff8472.
--verify Verify the data by reading back.
--help Show this message and exit.
```

```
admin@sonic:~$ sfputil write-eeprom Ethernet0 0 100 4a44

admin@sonic:~$ sfputil write-eeprom Etherent0 0 100 4a44 --verify
Error: Write data failed! Write: 4a44, read: 0000.
Junchao-Mellanox marked this conversation as resolved.
Show resolved Hide resolved
```

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
260 changes: 240 additions & 20 deletions sfputil/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@
PAGE_OFFSET = 128

SFF8472_A0_SIZE = 256
MAX_EEPROM_PAGE = 255
MAX_EEPROM_OFFSET = 255
MIN_OFFSET_FOR_PAGE0 = 0
MIN_OFFSET_FOR_OTHER_PAGE = 128
Junchao-Mellanox marked this conversation as resolved.
Show resolved Hide resolved
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 +803,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. This function is designed for SFF8472 only.
Junchao-Mellanox marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1564,5 +1603,186 @@ def target(port_name, target):
click.echo("Target Mode set failed!")
sys.exit(EXIT_FAIL)


# 'read-eeprom' subcommand
@cli.command()
@click.argument('port_name', metavar='<port_name>', required=True)
@click.argument('page', metavar='<page>', type=click.INT, required=True)
@click.argument('offset', metavar='<offset>', type=click.INT, required=True)
@click.argument('size', metavar='<size>', type=click.INT, 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_name, page, offset, size, no_format, wire_addr):
"""Read SFP EEPROM data"""
Junchao-Mellanox marked this conversation as resolved.
Show resolved Hide resolved
try:
if platform_sfputil.is_logical_port(port_name) == 0:
click.echo("Error: invalid port {}".format(port_name))
print_all_valid_port_values()
sys.exit(ERROR_INVALID_PORT)

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

physical_port = logical_port_to_physical_port_index(port_name)
sfp = platform_chassis.get_sfp(physical_port)
if not sfp.get_presence():
click.echo("{}: SFP EEPROM not detected\n".format(port_name))
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.argument('port_name', metavar='<port_name>', required=True)
@click.argument('page', metavar='<page>', type=click.INT, required=True)
@click.argument('offset', metavar='<offset>', type=click.INT, required=True)
@click.argument('data', metavar='<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")
Junchao-Mellanox marked this conversation as resolved.
Show resolved Hide resolved
def write_eeprom(port_name, page, offset, data, wire_addr, verify):
"""Write SFP EEPROM data"""
try:
if platform_sfputil.is_logical_port(port_name) == 0:
click.echo("Error: invalid port {}".format(port_name))
print_all_valid_port_values()
sys.exit(ERROR_INVALID_PORT)

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

physical_port = logical_port_to_physical_port_index(port_name)
sfp = platform_chassis.get_sfp(physical_port)
if not sfp.get_presence():
click.echo("{}: SFP EEPROM not detected\n".format(port_name))
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
"""
max_page = 0 if api.is_flat_memory() else MAX_EEPROM_PAGE
if max_page == 0 and page != 0:
raise ValueError(f'Invalid page number {page}, only page 0 is supported')

if page < 0 or page > max_page:
raise ValueError(f'Invalid page number {page}, valid range: [0, {max_page}]')

if page == 0:
if offset < MIN_OFFSET_FOR_PAGE0 or offset > MAX_EEPROM_OFFSET:
raise ValueError(f'Invalid offset {offset} for page 0, valid range: [0, 255]')
else:
if offset < MIN_OFFSET_FOR_OTHER_PAGE or offset > MAX_EEPROM_OFFSET:
raise ValueError(f'Invalid offset {offset} for page {page}, valid range: [128, 255]')

if size <= 0 or 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 < 0 or offset > max_offset:
raise ValueError(f'Invalid offset {offset} for wire address {wire_addr}, valid range: [0, {max_offset}]')
if size <= 0 or 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 page < 0 or page > MAX_EEPROM_PAGE:
raise ValueError(f'Invalid page number {page} for wire address {wire_addr}, valid range: [0, 255]')
if offset < 0 or offset > MAX_OFFSET_FOR_A2H:
raise ValueError(f'Invalid offset {offset} for wire address {wire_addr}, valid range: [0, 255]')
if size <= 0 or 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
Loading