ethtool.py 13 KB

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