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.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 doit(ynl, args, op_name): 88 """ 89 Prepare request header, parse arguments and doit. 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 dumpit(ynl, args, op_name, extra=None): 101 """ 102 Prepare request header, parse arguments and dumpit (filtering out the 103 devices we're not interested in). 104 """ 105 extra = extra or {} 106 reply = ynl.dump(op_name, { 'header': {} } | extra) 107 if not reply: 108 return {} 109 110 for msg in reply: 111 if msg['header']['dev-name'] == args.device: 112 if args.json: 113 pprint.PrettyPrinter().pprint(msg) 114 sys.exit(0) 115 msg.pop('header', None) 116 return msg 117 118 print(f"Not supported for device {args.device}") 119 sys.exit(1) 120 121def bits_to_dict(attr): 122 """ 123 Convert ynl-formatted bitmask to a dict of bit=value. 124 """ 125 ret = {} 126 if 'bits' not in attr: 127 return {} 128 if 'bit' not in attr['bits']: 129 return {} 130 for bit in attr['bits']['bit']: 131 if bit['name'] == '': 132 continue 133 name = bit['name'] 134 value = bit.get('value', False) 135 ret[name] = value 136 return ret 137 138def main(): 139 """ YNL ethtool utility """ 140 141 parser = argparse.ArgumentParser(description='ethtool wannabe') 142 parser.add_argument('--json', action=argparse.BooleanOptionalAction) 143 parser.add_argument('--show-priv-flags', action=argparse.BooleanOptionalAction) 144 parser.add_argument('--set-priv-flags', action=argparse.BooleanOptionalAction) 145 parser.add_argument('--show-eee', action=argparse.BooleanOptionalAction) 146 parser.add_argument('--set-eee', action=argparse.BooleanOptionalAction) 147 parser.add_argument('-a', '--show-pause', action=argparse.BooleanOptionalAction) 148 parser.add_argument('-A', '--set-pause', action=argparse.BooleanOptionalAction) 149 parser.add_argument('-c', '--show-coalesce', action=argparse.BooleanOptionalAction) 150 parser.add_argument('-C', '--set-coalesce', action=argparse.BooleanOptionalAction) 151 parser.add_argument('-g', '--show-ring', action=argparse.BooleanOptionalAction) 152 parser.add_argument('-G', '--set-ring', action=argparse.BooleanOptionalAction) 153 parser.add_argument('-k', '--show-features', action=argparse.BooleanOptionalAction) 154 parser.add_argument('-K', '--set-features', action=argparse.BooleanOptionalAction) 155 parser.add_argument('-l', '--show-channels', action=argparse.BooleanOptionalAction) 156 parser.add_argument('-L', '--set-channels', action=argparse.BooleanOptionalAction) 157 parser.add_argument('-T', '--show-time-stamping', action=argparse.BooleanOptionalAction) 158 parser.add_argument('-S', '--statistics', action=argparse.BooleanOptionalAction) 159 # TODO: --show-tunnels tunnel-info-get 160 # TODO: --show-module module-get 161 # TODO: --get-plca-cfg plca-get 162 # TODO: --get-plca-status plca-get-status 163 # TODO: --show-mm mm-get 164 # TODO: --show-fec fec-get 165 # TODO: --dump-module-eerpom module-eeprom-get 166 # TODO: pse-get 167 # TODO: rss-get 168 parser.add_argument('device', metavar='device', type=str) 169 parser.add_argument('args', metavar='args', type=str, nargs='*') 170 171 args = parser.parse_args() 172 173 spec = os.path.join(spec_dir(), 'ethtool.yaml') 174 schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml') 175 176 ynl = YnlFamily(spec, schema) 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 doit(ynl, args, 'eee-set') 185 return 186 187 if args.set_pause: 188 doit(ynl, args, 'pause-set') 189 return 190 191 if args.set_coalesce: 192 doit(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 doit(ynl, args, 'channels-set') 202 return 203 204 if args.set_ring: 205 doit(ynl, args, 'rings-set') 206 return 207 208 if args.show_priv_flags: 209 flags = bits_to_dict(dumpit(ynl, args, 'privflags-get')['flags']) 210 print_field(flags) 211 return 212 213 if args.show_eee: 214 eee = dumpit(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(dumpit(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(dumpit(ynl, args, 'coalesce-get')) 243 return 244 245 if args.show_features: 246 reply = dumpit(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 = dumpit(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 = dumpit(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 = dumpit(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 = dumpit(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 = dumpit(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 = dumpit(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 = dumpit(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 = dumpit(ynl, args, 'debug-get') 451 msgmask = bits_to_dict(debug.get("msgmask", [])).keys() 452 print(f'Current message level: {" ".join(msgmask)}') 453 454 linkstate = dumpit(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