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