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