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