| #!/usr/bin/env python3 | 
 | # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause | 
 |  | 
 | import argparse | 
 | import json | 
 | import pprint | 
 | import sys | 
 | import re | 
 |  | 
 | from lib import YnlFamily | 
 |  | 
 | def args_to_req(ynl, op_name, args, req): | 
 |     """ | 
 |     Verify and convert command-line arguments to the ynl-compatible request. | 
 |     """ | 
 |     valid_attrs = ynl.operation_do_attributes(op_name) | 
 |     valid_attrs.remove('header') # not user-provided | 
 |  | 
 |     if len(args) == 0: | 
 |         print(f'no attributes, expected: {valid_attrs}') | 
 |         sys.exit(1) | 
 |  | 
 |     i = 0 | 
 |     while i < len(args): | 
 |         attr = args[i] | 
 |         if i + 1 >= len(args): | 
 |             print(f'expected value for \'{attr}\'') | 
 |             sys.exit(1) | 
 |  | 
 |         if attr not in valid_attrs: | 
 |             print(f'invalid attribute \'{attr}\', expected: {valid_attrs}') | 
 |             sys.exit(1) | 
 |  | 
 |         val = args[i+1] | 
 |         i += 2 | 
 |  | 
 |         req[attr] = val | 
 |  | 
 | def print_field(reply, *desc): | 
 |     """ | 
 |     Pretty-print a set of fields from the reply. desc specifies the | 
 |     fields and the optional type (bool/yn). | 
 |     """ | 
 |     if len(desc) == 0: | 
 |         return print_field(reply, *zip(reply.keys(), reply.keys())) | 
 |  | 
 |     for spec in desc: | 
 |         try: | 
 |             field, name, tp = spec | 
 |         except: | 
 |             field, name = spec | 
 |             tp = 'int' | 
 |  | 
 |         value = reply.get(field, None) | 
 |         if tp == 'yn': | 
 |             value = 'yes' if value else 'no' | 
 |         elif tp == 'bool' or isinstance(value, bool): | 
 |             value = 'on' if value else 'off' | 
 |         else: | 
 |             value = 'n/a' if value is None else value | 
 |  | 
 |         print(f'{name}: {value}') | 
 |  | 
 | def print_speed(name, value): | 
 |     """ | 
 |     Print out the speed-like strings from the value dict. | 
 |     """ | 
 |     speed_re = re.compile(r'[0-9]+base[^/]+/.+') | 
 |     speed = [ k for k, v in value.items() if v and speed_re.match(k) ] | 
 |     print(f'{name}: {" ".join(speed)}') | 
 |  | 
 | def doit(ynl, args, op_name): | 
 |     """ | 
 |     Prepare request header, parse arguments and doit. | 
 |     """ | 
 |     req = { | 
 |         'header': { | 
 |           'dev-name': args.device, | 
 |         }, | 
 |     } | 
 |  | 
 |     args_to_req(ynl, op_name, args.args, req) | 
 |     ynl.do(op_name, req) | 
 |  | 
 | def dumpit(ynl, args, op_name, extra = {}): | 
 |     """ | 
 |     Prepare request header, parse arguments and dumpit (filtering out the | 
 |     devices we're not interested in). | 
 |     """ | 
 |     reply = ynl.dump(op_name, { 'header': {} } | extra) | 
 |     if not reply: | 
 |         return {} | 
 |  | 
 |     for msg in reply: | 
 |         if msg['header']['dev-name'] == args.device: | 
 |             if args.json: | 
 |                 pprint.PrettyPrinter().pprint(msg) | 
 |                 sys.exit(0) | 
 |             msg.pop('header', None) | 
 |             return msg | 
 |  | 
 |     print(f"Not supported for device {args.device}") | 
 |     sys.exit(1) | 
 |  | 
 | def bits_to_dict(attr): | 
 |     """ | 
 |     Convert ynl-formatted bitmask to a dict of bit=value. | 
 |     """ | 
 |     ret = {} | 
 |     if 'bits' not in attr: | 
 |         return dict() | 
 |     if 'bit' not in attr['bits']: | 
 |         return dict() | 
 |     for bit in attr['bits']['bit']: | 
 |         if bit['name'] == '': | 
 |             continue | 
 |         name = bit['name'] | 
 |         value = bit.get('value', False) | 
 |         ret[name] = value | 
 |     return ret | 
 |  | 
 | def main(): | 
 |     parser = argparse.ArgumentParser(description='ethtool wannabe') | 
 |     parser.add_argument('--json', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('--show-priv-flags', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('--set-priv-flags', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('--show-eee', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('--set-eee', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-a', '--show-pause', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-A', '--set-pause', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-c', '--show-coalesce', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-C', '--set-coalesce', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-g', '--show-ring', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-G', '--set-ring', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-k', '--show-features', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-K', '--set-features', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-l', '--show-channels', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-L', '--set-channels', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-T', '--show-time-stamping', action=argparse.BooleanOptionalAction) | 
 |     parser.add_argument('-S', '--statistics', action=argparse.BooleanOptionalAction) | 
 |     # TODO: --show-tunnels        tunnel-info-get | 
 |     # TODO: --show-module         module-get | 
 |     # TODO: --get-plca-cfg        plca-get | 
 |     # TODO: --get-plca-status     plca-get-status | 
 |     # TODO: --show-mm             mm-get | 
 |     # TODO: --show-fec            fec-get | 
 |     # TODO: --dump-module-eerpom  module-eeprom-get | 
 |     # TODO:                       pse-get | 
 |     # TODO:                       rss-get | 
 |     parser.add_argument('device', metavar='device', type=str) | 
 |     parser.add_argument('args', metavar='args', type=str, nargs='*') | 
 |     global args | 
 |     args = parser.parse_args() | 
 |  | 
 |     spec = '../../../Documentation/netlink/specs/ethtool.yaml' | 
 |     schema = '../../../Documentation/netlink/genetlink-legacy.yaml' | 
 |  | 
 |     ynl = YnlFamily(spec, schema) | 
 |  | 
 |     if args.set_priv_flags: | 
 |         # TODO: parse the bitmask | 
 |         print("not implemented") | 
 |         return | 
 |  | 
 |     if args.set_eee: | 
 |         return doit(ynl, args, 'eee-set') | 
 |  | 
 |     if args.set_pause: | 
 |         return doit(ynl, args, 'pause-set') | 
 |  | 
 |     if args.set_coalesce: | 
 |         return doit(ynl, args, 'coalesce-set') | 
 |  | 
 |     if args.set_features: | 
 |         # TODO: parse the bitmask | 
 |         print("not implemented") | 
 |         return | 
 |  | 
 |     if args.set_channels: | 
 |         return doit(ynl, args, 'channels-set') | 
 |  | 
 |     if args.set_ring: | 
 |         return doit(ynl, args, 'rings-set') | 
 |  | 
 |     if args.show_priv_flags: | 
 |         flags = bits_to_dict(dumpit(ynl, args, 'privflags-get')['flags']) | 
 |         print_field(flags) | 
 |         return | 
 |  | 
 |     if args.show_eee: | 
 |         eee = dumpit(ynl, args, 'eee-get') | 
 |         ours = bits_to_dict(eee['modes-ours']) | 
 |         peer = bits_to_dict(eee['modes-peer']) | 
 |  | 
 |         if 'enabled' in eee: | 
 |             status = 'enabled' if eee['enabled'] else 'disabled' | 
 |             if 'active' in eee and eee['active']: | 
 |                 status = status + ' - active' | 
 |             else: | 
 |                 status = status + ' - inactive' | 
 |         else: | 
 |             status = 'not supported' | 
 |  | 
 |         print(f'EEE status: {status}') | 
 |         print_field(eee, ('tx-lpi-timer', 'Tx LPI')) | 
 |         print_speed('Advertised EEE link modes', ours) | 
 |         print_speed('Link partner advertised EEE link modes', peer) | 
 |  | 
 |         return | 
 |  | 
 |     if args.show_pause: | 
 |         print_field(dumpit(ynl, args, 'pause-get'), | 
 |                 ('autoneg', 'Autonegotiate', 'bool'), | 
 |                 ('rx', 'RX', 'bool'), | 
 |                 ('tx', 'TX', 'bool')) | 
 |         return | 
 |  | 
 |     if args.show_coalesce: | 
 |         print_field(dumpit(ynl, args, 'coalesce-get')) | 
 |         return | 
 |  | 
 |     if args.show_features: | 
 |         reply = dumpit(ynl, args, 'features-get') | 
 |         available = bits_to_dict(reply['hw']) | 
 |         requested = bits_to_dict(reply['wanted']).keys() | 
 |         active = bits_to_dict(reply['active']).keys() | 
 |         never_changed = bits_to_dict(reply['nochange']).keys() | 
 |  | 
 |         for f in sorted(available): | 
 |             value = "off" | 
 |             if f in active: | 
 |                 value = "on" | 
 |  | 
 |             fixed = "" | 
 |             if f not in available or f in never_changed: | 
 |                 fixed = " [fixed]" | 
 |  | 
 |             req = "" | 
 |             if f in requested: | 
 |                 if f in active: | 
 |                     req = " [requested on]" | 
 |                 else: | 
 |                     req = " [requested off]" | 
 |  | 
 |             print(f'{f}: {value}{fixed}{req}') | 
 |  | 
 |         return | 
 |  | 
 |     if args.show_channels: | 
 |         reply = dumpit(ynl, args, 'channels-get') | 
 |         print(f'Channel parameters for {args.device}:') | 
 |  | 
 |         print(f'Pre-set maximums:') | 
 |         print_field(reply, | 
 |             ('rx-max', 'RX'), | 
 |             ('tx-max', 'TX'), | 
 |             ('other-max', 'Other'), | 
 |             ('combined-max', 'Combined')) | 
 |  | 
 |         print(f'Current hardware settings:') | 
 |         print_field(reply, | 
 |             ('rx-count', 'RX'), | 
 |             ('tx-count', 'TX'), | 
 |             ('other-count', 'Other'), | 
 |             ('combined-count', 'Combined')) | 
 |  | 
 |         return | 
 |  | 
 |     if args.show_ring: | 
 |         reply = dumpit(ynl, args, 'channels-get') | 
 |  | 
 |         print(f'Ring parameters for {args.device}:') | 
 |  | 
 |         print(f'Pre-set maximums:') | 
 |         print_field(reply, | 
 |             ('rx-max', 'RX'), | 
 |             ('rx-mini-max', 'RX Mini'), | 
 |             ('rx-jumbo-max', 'RX Jumbo'), | 
 |             ('tx-max', 'TX')) | 
 |  | 
 |         print(f'Current hardware settings:') | 
 |         print_field(reply, | 
 |             ('rx', 'RX'), | 
 |             ('rx-mini', 'RX Mini'), | 
 |             ('rx-jumbo', 'RX Jumbo'), | 
 |             ('tx', 'TX')) | 
 |  | 
 |         print_field(reply, | 
 |             ('rx-buf-len', 'RX Buf Len'), | 
 |             ('cqe-size', 'CQE Size'), | 
 |             ('tx-push', 'TX Push', 'bool')) | 
 |  | 
 |         return | 
 |  | 
 |     if args.statistics: | 
 |         print(f'NIC statistics:') | 
 |  | 
 |         # TODO: pass id? | 
 |         strset = dumpit(ynl, args, 'strset-get') | 
 |         pprint.PrettyPrinter().pprint(strset) | 
 |  | 
 |         req = { | 
 |           'groups': { | 
 |             'size': 1, | 
 |             'bits': { | 
 |               'bit': | 
 |                 # TODO: support passing the bitmask | 
 |                 #[ | 
 |                   #{ 'name': 'eth-phy', 'value': True }, | 
 |                   { 'name': 'eth-mac', 'value': True }, | 
 |                   #{ 'name': 'eth-ctrl', 'value': True }, | 
 |                   #{ 'name': 'rmon', 'value': True }, | 
 |                 #], | 
 |             }, | 
 |           }, | 
 |         } | 
 |  | 
 |         rsp = dumpit(ynl, args, 'stats-get', req) | 
 |         pprint.PrettyPrinter().pprint(rsp) | 
 |         return | 
 |  | 
 |     if args.show_time_stamping: | 
 |         tsinfo = dumpit(ynl, args, 'tsinfo-get') | 
 |  | 
 |         print(f'Time stamping parameters for {args.device}:') | 
 |  | 
 |         print('Capabilities:') | 
 |         [print(f'\t{v}') for v in bits_to_dict(tsinfo['timestamping'])] | 
 |  | 
 |         print(f'PTP Hardware Clock: {tsinfo["phc-index"]}') | 
 |  | 
 |         print('Hardware Transmit Timestamp Modes:') | 
 |         [print(f'\t{v}') for v in bits_to_dict(tsinfo['tx-types'])] | 
 |  | 
 |         print('Hardware Receive Filter Modes:') | 
 |         [print(f'\t{v}') for v in bits_to_dict(tsinfo['rx-filters'])] | 
 |         return | 
 |  | 
 |     print(f'Settings for {args.device}:') | 
 |     linkmodes = dumpit(ynl, args, 'linkmodes-get') | 
 |     ours = bits_to_dict(linkmodes['ours']) | 
 |  | 
 |     supported_ports = ('TP',  'AUI', 'BNC', 'MII', 'FIBRE', 'Backplane') | 
 |     ports = [ p for p in supported_ports if ours.get(p, False)] | 
 |     print(f'Supported ports: [ {" ".join(ports)} ]') | 
 |  | 
 |     print_speed('Supported link modes', ours) | 
 |  | 
 |     print_field(ours, ('Pause', 'Supported pause frame use', 'yn')) | 
 |     print_field(ours, ('Autoneg', 'Supports auto-negotiation', 'yn')) | 
 |  | 
 |     supported_fec = ('None',  'PS', 'BASER', 'LLRS') | 
 |     fec = [ p for p in supported_fec if ours.get(p, False)] | 
 |     fec_str = " ".join(fec) | 
 |     if len(fec) == 0: | 
 |         fec_str = "Not reported" | 
 |  | 
 |     print(f'Supported FEC modes: {fec_str}') | 
 |  | 
 |     speed = 'Unknown!' | 
 |     if linkmodes['speed'] > 0 and linkmodes['speed'] < 0xffffffff: | 
 |         speed = f'{linkmodes["speed"]}Mb/s' | 
 |     print(f'Speed: {speed}') | 
 |  | 
 |     duplex_modes = { | 
 |             0: 'Half', | 
 |             1: 'Full', | 
 |     } | 
 |     duplex = duplex_modes.get(linkmodes["duplex"], None) | 
 |     if not duplex: | 
 |         duplex = f'Unknown! ({linkmodes["duplex"]})' | 
 |     print(f'Duplex: {duplex}') | 
 |  | 
 |     autoneg = "off" | 
 |     if linkmodes.get("autoneg", 0) != 0: | 
 |         autoneg = "on" | 
 |     print(f'Auto-negotiation: {autoneg}') | 
 |  | 
 |     ports = { | 
 |             0: 'Twisted Pair', | 
 |             1: 'AUI', | 
 |             2: 'MII', | 
 |             3: 'FIBRE', | 
 |             4: 'BNC', | 
 |             5: 'Directly Attached Copper', | 
 |             0xef: 'None', | 
 |     } | 
 |     linkinfo = dumpit(ynl, args, 'linkinfo-get') | 
 |     print(f'Port: {ports.get(linkinfo["port"], "Other")}') | 
 |  | 
 |     print_field(linkinfo, ('phyaddr', 'PHYAD')) | 
 |  | 
 |     transceiver = { | 
 |             0: 'Internal', | 
 |             1: 'External', | 
 |     } | 
 |     print(f'Transceiver: {transceiver.get(linkinfo["transceiver"], "Unknown")}') | 
 |  | 
 |     mdix_ctrl = { | 
 |             1: 'off', | 
 |             2: 'on', | 
 |     } | 
 |     mdix = mdix_ctrl.get(linkinfo['tp-mdix-ctrl'], None) | 
 |     if mdix: | 
 |         mdix = mdix + ' (forced)' | 
 |     else: | 
 |         mdix = mdix_ctrl.get(linkinfo['tp-mdix'], 'Unknown (auto)') | 
 |     print(f'MDI-X: {mdix}') | 
 |  | 
 |     debug = dumpit(ynl, args, 'debug-get') | 
 |     msgmask = bits_to_dict(debug.get("msgmask", [])).keys() | 
 |     print(f'Current message level: {" ".join(msgmask)}') | 
 |  | 
 |     linkstate = dumpit(ynl, args, 'linkstate-get') | 
 |     detected_states = { | 
 |             0: 'no', | 
 |             1: 'yes', | 
 |     } | 
 |     # TODO: wol-get | 
 |     detected = detected_states.get(linkstate['link'], 'unknown') | 
 |     print(f'Link detected: {detected}') | 
 |  | 
 | if __name__ == '__main__': | 
 |     main() |