| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 |
|
| 7 |
|
| 8 |
|
| 9 | import argparse
|
| 10 | import pathlib
|
| 11 | import pprint
|
| 12 | import sys
|
| 13 | import re
|
| 14 |
|
| 15 | |
| 16 | sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix())
|
| 17 | |
| 18 | from lib import YnlFamily
|
| 19 |
|
| 20 |
|
| 21 | def 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') |
| 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 |
|
| 48 | def 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 |
|
| 77 | def 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 |
|
| 85 | def 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 |
|
| 98 | def 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 |
|
| 117 | def 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 |
|
| 134 | def main():
|
| 135 |
|
| 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 | |
| 156 | |
| 157 | |
| 158 | |
| 159 | |
| 160 | |
| 161 | |
| 162 | |
| 163 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 331 | |
| 332 | |
| 333 | { 'name': 'eth-mac', 'value': True },
|
| 334 | |
| 335 | |
| 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 | |
| 460 | detected = detected_states.get(linkstate['link'], 'unknown')
|
| 461 | print(f'Link detected: {detected}')
|
| 462 |
|
| 463 | if __name__ == '__main__':
|
| 464 | main()
|