1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 3# 4# pylint: disable=too-many-locals, too-many-branches, too-many-statements 5# pylint: disable=too-many-return-statements 6 7""" YNL ethtool utility """ 8 9import argparse 10import pathlib 11import pprint 12import sys 13import re 14 15# pylint: disable=no-name-in-module,wrong-import-position 16sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix()) 17# pylint: disable=import-error 18from lib import YnlFamily 19 20 21def args_to_req(ynl, op_name, args, req): 22 """ 23 Verify and convert command-line arguments to the ynl-compatible request. 24 """ 25 valid_attrs = ynl.operation_do_attributes(op_name) 26 valid_attrs.remove('header') # not user-provided 27 28 if len(args) == 0: 29 print(f'no attributes, expected: {valid_attrs}') 30 sys.exit(1) 31 32 i = 0 33 while i < len(args): 34 attr = args[i] 35 if i + 1 >= len(args): 36 print(f'expected value for \'{attr}\'') 37 sys.exit(1) 38 39 if attr not in valid_attrs: 40 print(f'invalid attribute \'{attr}\', expected: {valid_attrs}') 41 sys.exit(1) 42 43 val = args[i+1] 44 i += 2 45 46 req[attr] = val 47 48def print_field(reply, *desc): 49 """ 50 Pretty-print a set of fields from the reply. desc specifies the 51 fields and the optional type (bool/yn). 52 """ 53 if not reply: 54 return 55 56 if len(desc) == 0: 57 print_field(reply, *zip(reply.keys(), reply.keys())) 58 return 59 60 for spec in desc: 61 try: 62 field, name, tp = spec 63 except ValueError: 64 field, name = spec 65 tp = 'int' 66 67 value = reply.get(field, None) 68 if tp == 'yn': 69 value = 'yes' if value else 'no' 70 elif tp == 'bool' or isinstance(value, bool): 71 value = 'on' if value else 'off' 72 else: 73 value = 'n/a' if value is None else value 74 75 print(f'{name}: {value}') 76 77def print_speed(name, value): 78 """ 79 Print out the speed-like strings from the value dict. 80 """ 81 speed_re = re.compile(r'[0-9]+base[^/]+/.+') 82 speed = [ k for k, v in value.items() if v and speed_re.match(k) ] 83 print(f'{name}: {" ".join(speed)}') 84 85def do_set(ynl, args, op_name): 86 """ 87 Prepare request header, parse arguments and do a set operation. 88 """ 89 req = { 90 'header': { 91 'dev-name': args.device, 92 }, 93 } 94 95 args_to_req(ynl, op_name, args.args, req) 96 ynl.do(op_name, req) 97 98def do_get(ynl, args, op_name, extra=None): 99 """ 100 Prepare request header and get info for a specific device using doit. 101 """ 102 extra = extra or {} 103 req = {'header': {'dev-name': args.device}} 104 req['header'].update(extra.pop('header', {})) 105 req.update(extra) 106 107 reply = ynl.do(op_name, req) 108 if not reply: 109 return {} 110 111 if args.json: 112 pprint.PrettyPrinter().pprint(reply) 113 sys.exit(0) 114 reply.pop('header', None) 115 return reply 116 117def bits_to_dict(attr): 118 """ 119 Convert ynl-formatted bitmask to a dict of bit=value. 120 """ 121 ret = {} 122 if 'bits' not in attr: 123 return {} 124 if 'bit' not in attr['bits']: 125 return {} 126 for bit in attr['bits']['bit']: 127 if bit['name'] == '': 128 continue 129 name = bit['name'] 130 value = bit.get('value', False) 131 ret[name] = value 132 return ret 133 134def main(): 135 """ YNL ethtool utility """ 136 137 parser = argparse.ArgumentParser(description='ethtool wannabe') 138 parser.add_argument('--json', action=argparse.BooleanOptionalAction) 139 parser.add_argument('--show-priv-flags', action=argparse.BooleanOptionalAction) 140 parser.add_argument('--set-priv-flags', action=argparse.BooleanOptionalAction) 141 parser.add_argument('--show-eee', action=argparse.BooleanOptionalAction) 142 parser.add_argument('--set-eee', action=argparse.BooleanOptionalAction) 143 parser.add_argument('-a', '--show-pause', action=argparse.BooleanOptionalAction) 144 parser.add_argument('-A', '--set-pause', action=argparse.BooleanOptionalAction) 145 parser.add_argument('-c', '--show-coalesce', action=argparse.BooleanOptionalAction) 146 parser.add_argument('-C', '--set-coalesce', action=argparse.BooleanOptionalAction) 147 parser.add_argument('-g', '--show-ring', action=argparse.BooleanOptionalAction) 148 parser.add_argument('-G', '--set-ring', action=argparse.BooleanOptionalAction) 149 parser.add_argument('-k', '--show-features', action=argparse.BooleanOptionalAction) 150 parser.add_argument('-K', '--set-features', action=argparse.BooleanOptionalAction) 151 parser.add_argument('-l', '--show-channels', action=argparse.BooleanOptionalAction) 152 parser.add_argument('-L', '--set-channels', action=argparse.BooleanOptionalAction) 153 parser.add_argument('-T', '--show-time-stamping', action=argparse.BooleanOptionalAction) 154 parser.add_argument('-S', '--statistics', action=argparse.BooleanOptionalAction) 155 # TODO: --show-tunnels tunnel-info-get 156 # TODO: --show-module module-get 157 # TODO: --get-plca-cfg plca-get 158 # TODO: --get-plca-status plca-get-status 159 # TODO: --show-mm mm-get 160 # TODO: --show-fec fec-get 161 # TODO: --dump-module-eerpom module-eeprom-get 162 # TODO: pse-get 163 # TODO: rss-get 164 parser.add_argument('device', metavar='device', type=str) 165 parser.add_argument('args', metavar='args', type=str, nargs='*') 166 167 dbg_group = parser.add_argument_group('Debug options') 168 dbg_group.add_argument('--dbg-small-recv', default=0, const=4000, 169 action='store', nargs='?', type=int, metavar='INT', 170 help="Length of buffers used for recv()") 171 172 args = parser.parse_args() 173 174 ynl = YnlFamily(family='ethtool', recv_size=args.dbg_small_recv) 175 if args.dbg_small_recv: 176 ynl.set_recv_dbg(True) 177 178 if args.set_priv_flags: 179 # TODO: parse the bitmask 180 print("not implemented") 181 return 182 183 if args.set_eee: 184 do_set(ynl, args, 'eee-set') 185 return 186 187 if args.set_pause: 188 do_set(ynl, args, 'pause-set') 189 return 190 191 if args.set_coalesce: 192 do_set(ynl, args, 'coalesce-set') 193 return 194 195 if args.set_features: 196 # TODO: parse the bitmask 197 print("not implemented") 198 return 199 200 if args.set_channels: 201 do_set(ynl, args, 'channels-set') 202 return 203 204 if args.set_ring: 205 do_set(ynl, args, 'rings-set') 206 return 207 208 if args.show_priv_flags: 209 flags = bits_to_dict(do_get(ynl, args, 'privflags-get')['flags']) 210 print_field(flags) 211 return 212 213 if args.show_eee: 214 eee = do_get(ynl, args, 'eee-get') 215 ours = bits_to_dict(eee['modes-ours']) 216 peer = bits_to_dict(eee['modes-peer']) 217 218 if 'enabled' in eee: 219 status = 'enabled' if eee['enabled'] else 'disabled' 220 if 'active' in eee and eee['active']: 221 status = status + ' - active' 222 else: 223 status = status + ' - inactive' 224 else: 225 status = 'not supported' 226 227 print(f'EEE status: {status}') 228 print_field(eee, ('tx-lpi-timer', 'Tx LPI')) 229 print_speed('Advertised EEE link modes', ours) 230 print_speed('Link partner advertised EEE link modes', peer) 231 232 return 233 234 if args.show_pause: 235 print_field(do_get(ynl, args, 'pause-get'), 236 ('autoneg', 'Autonegotiate', 'bool'), 237 ('rx', 'RX', 'bool'), 238 ('tx', 'TX', 'bool')) 239 return 240 241 if args.show_coalesce: 242 print_field(do_get(ynl, args, 'coalesce-get')) 243 return 244 245 if args.show_features: 246 reply = do_get(ynl, args, 'features-get') 247 available = bits_to_dict(reply['hw']) 248 requested = bits_to_dict(reply['wanted']).keys() 249 active = bits_to_dict(reply['active']).keys() 250 never_changed = bits_to_dict(reply['nochange']).keys() 251 252 for f in sorted(available): 253 value = "off" 254 if f in active: 255 value = "on" 256 257 fixed = "" 258 if f not in available or f in never_changed: 259 fixed = " [fixed]" 260 261 req = "" 262 if f in requested: 263 if f in active: 264 req = " [requested on]" 265 else: 266 req = " [requested off]" 267 268 print(f'{f}: {value}{fixed}{req}') 269 270 return 271 272 if args.show_channels: 273 reply = do_get(ynl, args, 'channels-get') 274 print(f'Channel parameters for {args.device}:') 275 276 print('Pre-set maximums:') 277 print_field(reply, 278 ('rx-max', 'RX'), 279 ('tx-max', 'TX'), 280 ('other-max', 'Other'), 281 ('combined-max', 'Combined')) 282 283 print('Current hardware settings:') 284 print_field(reply, 285 ('rx-count', 'RX'), 286 ('tx-count', 'TX'), 287 ('other-count', 'Other'), 288 ('combined-count', 'Combined')) 289 290 return 291 292 if args.show_ring: 293 reply = do_get(ynl, args, 'channels-get') 294 295 print(f'Ring parameters for {args.device}:') 296 297 print('Pre-set maximums:') 298 print_field(reply, 299 ('rx-max', 'RX'), 300 ('rx-mini-max', 'RX Mini'), 301 ('rx-jumbo-max', 'RX Jumbo'), 302 ('tx-max', 'TX')) 303 304 print('Current hardware settings:') 305 print_field(reply, 306 ('rx', 'RX'), 307 ('rx-mini', 'RX Mini'), 308 ('rx-jumbo', 'RX Jumbo'), 309 ('tx', 'TX')) 310 311 print_field(reply, 312 ('rx-buf-len', 'RX Buf Len'), 313 ('cqe-size', 'CQE Size'), 314 ('tx-push', 'TX Push', 'bool')) 315 316 return 317 318 if args.statistics: 319 print('NIC statistics:') 320 321 # TODO: pass id? 322 strset = do_get(ynl, args, 'strset-get') 323 pprint.PrettyPrinter().pprint(strset) 324 325 req = { 326 'groups': { 327 'size': 1, 328 'bits': { 329 'bit': 330 # TODO: support passing the bitmask 331 #[ 332 #{ 'name': 'eth-phy', 'value': True }, 333 { 'name': 'eth-mac', 'value': True }, 334 #{ 'name': 'eth-ctrl', 'value': True }, 335 #{ 'name': 'rmon', 'value': True }, 336 #], 337 }, 338 }, 339 } 340 341 rsp = do_get(ynl, args, 'stats-get', req) 342 pprint.PrettyPrinter().pprint(rsp) 343 return 344 345 if args.show_time_stamping: 346 req = { 347 'header': { 348 'flags': 'stats', 349 }, 350 } 351 352 tsinfo = do_get(ynl, args, 'tsinfo-get', req) 353 354 print(f'Time stamping parameters for {args.device}:') 355 356 print('Capabilities:') 357 _ = [print(f'\t{v}') for v in bits_to_dict(tsinfo['timestamping'])] 358 359 print(f'PTP Hardware Clock: {tsinfo.get("phc-index", "none")}') 360 361 if 'tx-types' in tsinfo: 362 print('Hardware Transmit Timestamp Modes:') 363 _ = [print(f'\t{v}') for v in bits_to_dict(tsinfo['tx-types'])] 364 else: 365 print('Hardware Transmit Timestamp Modes: none') 366 367 if 'rx-filters' in tsinfo: 368 print('Hardware Receive Filter Modes:') 369 _ = [print(f'\t{v}') for v in bits_to_dict(tsinfo['rx-filters'])] 370 else: 371 print('Hardware Receive Filter Modes: none') 372 373 if 'stats' in tsinfo and tsinfo['stats']: 374 print('Statistics:') 375 _ = [print(f'\t{k}: {v}') for k, v in tsinfo['stats'].items()] 376 377 return 378 379 print(f'Settings for {args.device}:') 380 linkmodes = do_get(ynl, args, 'linkmodes-get') 381 ours = bits_to_dict(linkmodes['ours']) 382 383 supported_ports = ('TP', 'AUI', 'BNC', 'MII', 'FIBRE', 'Backplane') 384 ports = [ p for p in supported_ports if ours.get(p, False)] 385 print(f'Supported ports: [ {" ".join(ports)} ]') 386 387 print_speed('Supported link modes', ours) 388 389 print_field(ours, ('Pause', 'Supported pause frame use', 'yn')) 390 print_field(ours, ('Autoneg', 'Supports auto-negotiation', 'yn')) 391 392 supported_fec = ('None', 'PS', 'BASER', 'LLRS') 393 fec = [ p for p in supported_fec if ours.get(p, False)] 394 fec_str = " ".join(fec) 395 if len(fec) == 0: 396 fec_str = "Not reported" 397 398 print(f'Supported FEC modes: {fec_str}') 399 400 speed = 'Unknown!' 401 if linkmodes['speed'] > 0 and linkmodes['speed'] < 0xffffffff: 402 speed = f'{linkmodes["speed"]}Mb/s' 403 print(f'Speed: {speed}') 404 405 duplex_modes = { 406 0: 'Half', 407 1: 'Full', 408 } 409 duplex = duplex_modes.get(linkmodes["duplex"], None) 410 if not duplex: 411 duplex = f'Unknown! ({linkmodes["duplex"]})' 412 print(f'Duplex: {duplex}') 413 414 autoneg = "off" 415 if linkmodes.get("autoneg", 0) != 0: 416 autoneg = "on" 417 print(f'Auto-negotiation: {autoneg}') 418 419 ports = { 420 0: 'Twisted Pair', 421 1: 'AUI', 422 2: 'MII', 423 3: 'FIBRE', 424 4: 'BNC', 425 5: 'Directly Attached Copper', 426 0xef: 'None', 427 } 428 linkinfo = do_get(ynl, args, 'linkinfo-get') 429 print(f'Port: {ports.get(linkinfo["port"], "Other")}') 430 431 print_field(linkinfo, ('phyaddr', 'PHYAD')) 432 433 transceiver = { 434 0: 'Internal', 435 1: 'External', 436 } 437 print(f'Transceiver: {transceiver.get(linkinfo["transceiver"], "Unknown")}') 438 439 mdix_ctrl = { 440 1: 'off', 441 2: 'on', 442 } 443 mdix = mdix_ctrl.get(linkinfo['tp-mdix-ctrl'], None) 444 if mdix: 445 mdix = mdix + ' (forced)' 446 else: 447 mdix = mdix_ctrl.get(linkinfo['tp-mdix'], 'Unknown (auto)') 448 print(f'MDI-X: {mdix}') 449 450 debug = do_get(ynl, args, 'debug-get') 451 msgmask = bits_to_dict(debug.get("msgmask", [])).keys() 452 print(f'Current message level: {" ".join(msgmask)}') 453 454 linkstate = do_get(ynl, args, 'linkstate-get') 455 detected_states = { 456 0: 'no', 457 1: 'yes', 458 } 459 # TODO: wol-get 460 detected = detected_states.get(linkstate['link'], 'unknown') 461 print(f'Link detected: {detected}') 462 463if __name__ == '__main__': 464 main() 465