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