ids_parser.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. #!/usr/bin/env python3
  2. import re
  3. import sys
  4. from pyparsing import (Word, White, Literal, Regex,
  5. LineEnd, SkipTo,
  6. ZeroOrMore, OneOrMore, Combine, Optional, Suppress,
  7. stringEnd, pythonStyleComment)
  8. EOL = LineEnd().suppress()
  9. NUM1 = Word('0123456789abcdefABCDEF', exact=1)
  10. NUM2 = Word('0123456789abcdefABCDEF', exact=2)
  11. NUM3 = Word('0123456789abcdefABCDEF', exact=3)
  12. NUM4 = Word('0123456789abcdefABCDEF', exact=4)
  13. NUM6 = Word('0123456789abcdefABCDEF', exact=6)
  14. TAB = White('\t', exact=1).suppress()
  15. COMMENTLINE = pythonStyleComment + EOL
  16. EMPTYLINE = LineEnd()
  17. text_eol = lambda name: Regex(r'[^\n]+')(name) + EOL
  18. def klass_grammar():
  19. klass_line = Literal('C ').suppress() + NUM2('klass') + text_eol('text')
  20. subclass_line = TAB + NUM2('subclass') + text_eol('text')
  21. protocol_line = TAB + TAB + NUM2('protocol') + text_eol('name')
  22. subclass = (subclass_line('SUBCLASS') -
  23. ZeroOrMore(protocol_line('PROTOCOLS*')
  24. ^ COMMENTLINE.suppress()))
  25. klass = (klass_line('KLASS') -
  26. ZeroOrMore(subclass('SUBCLASSES*')
  27. ^ COMMENTLINE.suppress()))
  28. return klass
  29. def usb_ids_grammar():
  30. vendor_line = NUM4('vendor') + text_eol('text')
  31. device_line = TAB + NUM4('device') + text_eol('text')
  32. vendor = (vendor_line('VENDOR') +
  33. ZeroOrMore(device_line('VENDOR_DEV*') ^ COMMENTLINE.suppress()))
  34. klass = klass_grammar()
  35. other_line = (Literal('AT ') ^ Literal('HID ') ^ Literal('R ')
  36. ^ Literal('PHY ') ^ Literal('BIAS ') ^ Literal('HUT ')
  37. ^ Literal('L ') ^ Literal('VT ') ^ Literal('HCC ')) + text_eol('text')
  38. other_group = (other_line - ZeroOrMore(TAB + text_eol('text')))
  39. commentgroup = OneOrMore(COMMENTLINE).suppress() ^ EMPTYLINE.suppress()
  40. grammar = OneOrMore(vendor('VENDORS*') ^ klass('CLASSES*')
  41. ^ other_group.suppress() ^ commentgroup) + stringEnd()
  42. grammar.parseWithTabs()
  43. return grammar
  44. def pci_ids_grammar():
  45. vendor_line = NUM4('vendor') + text_eol('text')
  46. device_line = TAB + NUM4('device') + text_eol('text')
  47. subvendor_line = TAB + TAB + NUM4('a') + White(' ') + NUM4('b') + text_eol('name')
  48. device = (device_line('DEVICE') +
  49. ZeroOrMore(subvendor_line('SUBVENDORS*') ^ COMMENTLINE.suppress()))
  50. vendor = (vendor_line('VENDOR') +
  51. ZeroOrMore(device('DEVICES*') ^ COMMENTLINE.suppress()))
  52. klass = klass_grammar()
  53. commentgroup = OneOrMore(COMMENTLINE).suppress() ^ EMPTYLINE.suppress()
  54. grammar = OneOrMore(vendor('VENDORS*') ^ klass('CLASSES*')
  55. ^ commentgroup) + stringEnd()
  56. grammar.parseWithTabs()
  57. return grammar
  58. def sdio_ids_grammar():
  59. vendor_line = NUM4('vendor') + text_eol('text')
  60. device_line = TAB + NUM4('device') + text_eol('text')
  61. vendor = (vendor_line('VENDOR') +
  62. ZeroOrMore(device_line('DEVICES*') ^ COMMENTLINE.suppress()))
  63. klass = klass_grammar()
  64. commentgroup = OneOrMore(COMMENTLINE).suppress() ^ EMPTYLINE.suppress()
  65. grammar = OneOrMore(vendor('VENDORS*') ^ klass('CLASSES*') ^ commentgroup) + stringEnd()
  66. grammar.parseWithTabs()
  67. return grammar
  68. def oui_grammar(type):
  69. prefix_line = (Combine(NUM2 - Suppress('-') - NUM2 - Suppress('-') - NUM2)('prefix')
  70. - Literal('(hex)') - text_eol('text'))
  71. if type == 'small':
  72. vendor_line = (NUM3('start') - '000-' - NUM3('end') - 'FFF'
  73. - Literal('(base 16)') - text_eol('text2'))
  74. elif type == 'medium':
  75. vendor_line = (NUM1('start') - '00000-' - NUM1('end') - 'FFFFF'
  76. - Literal('(base 16)') - text_eol('text2'))
  77. else:
  78. assert type == 'large'
  79. vendor_line = (NUM6('start')
  80. - Literal('(base 16)') - text_eol('text2'))
  81. extra_line = TAB - TAB - TAB - TAB - SkipTo(EOL)
  82. vendor = prefix_line + vendor_line + ZeroOrMore(extra_line) + Optional(EMPTYLINE)
  83. grammar = (Literal('OUI') + text_eol('header')
  84. + text_eol('header') + text_eol('header') + EMPTYLINE
  85. + OneOrMore(vendor('VENDORS*')) + stringEnd())
  86. grammar.parseWithTabs()
  87. return grammar
  88. def header(file, *sources):
  89. print('''\
  90. # This file is part of systemd.
  91. #
  92. # Data imported from:{}{}'''.format(' ' if len(sources) == 1 else '\n# ',
  93. '\n# '.join(sources)),
  94. file=file)
  95. def add_item(items, key, value):
  96. if key in items:
  97. print(f'Ignoring duplicate entry: {key} = "{items[key]}", "{value}"')
  98. else:
  99. items[key] = value
  100. def usb_vendor_model(p):
  101. items = {}
  102. for vendor_group in p.VENDORS:
  103. vendor = vendor_group.VENDOR.vendor.upper()
  104. text = vendor_group.VENDOR.text.strip()
  105. add_item(items, (vendor,), text)
  106. for vendor_dev in vendor_group.VENDOR_DEV:
  107. device = vendor_dev.device.upper()
  108. text = vendor_dev.text.strip()
  109. add_item(items, (vendor, device), text)
  110. with open('20-usb-vendor-model.hwdb', 'wt') as out:
  111. header(out, 'http://www.linux-usb.org/usb.ids')
  112. for key in sorted(items):
  113. if len(key) == 1:
  114. p, n = 'usb:v{}*', 'VENDOR'
  115. else:
  116. p, n = 'usb:v{}p{}*', 'MODEL',
  117. print('', p.format(*key),
  118. f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out)
  119. print(f'Wrote {out.name}')
  120. def usb_classes(p):
  121. items = {}
  122. for klass_group in p.CLASSES:
  123. klass = klass_group.KLASS.klass.upper()
  124. text = klass_group.KLASS.text.strip()
  125. if klass != '00' and not re.match(r'(\?|None|Unused)\s*$', text):
  126. add_item(items, (klass,), text)
  127. for subclass_group in klass_group.SUBCLASSES:
  128. subclass = subclass_group.subclass.upper()
  129. text = subclass_group.text.strip()
  130. if subclass != '00' and not re.match(r'(\?|None|Unused)\s*$', text):
  131. add_item(items, (klass, subclass), text)
  132. for protocol_group in subclass_group.PROTOCOLS:
  133. protocol = protocol_group.protocol.upper()
  134. text = protocol_group.name.strip()
  135. if klass != '00' and not re.match(r'(\?|None|Unused)\s*$', text):
  136. add_item(items, (klass, subclass, protocol), text)
  137. with open('20-usb-classes.hwdb', 'wt') as out:
  138. header(out, 'http://www.linux-usb.org/usb.ids')
  139. for key in sorted(items):
  140. if len(key) == 1:
  141. p, n = 'usb:v*p*d*dc{}*', 'CLASS'
  142. elif len(key) == 2:
  143. p, n = 'usb:v*p*d*dc{}dsc{}*', 'SUBCLASS'
  144. else:
  145. p, n = 'usb:v*p*d*dc{}dsc{}dp{}*', 'PROTOCOL'
  146. print('', p.format(*key),
  147. f' ID_USB_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out)
  148. print(f'Wrote {out.name}')
  149. def pci_vendor_model(p):
  150. items = {}
  151. for vendor_group in p.VENDORS:
  152. vendor = vendor_group.VENDOR.vendor.upper()
  153. text = vendor_group.VENDOR.text.strip()
  154. add_item(items, (vendor,), text)
  155. for device_group in vendor_group.DEVICES:
  156. device = device_group.device.upper()
  157. text = device_group.text.strip()
  158. add_item(items, (vendor, device), text)
  159. for subvendor_group in device_group.SUBVENDORS:
  160. sub_vendor = subvendor_group.a.upper()
  161. sub_model = subvendor_group.b.upper()
  162. sub_text = subvendor_group.name.strip()
  163. if sub_text.startswith(text):
  164. sub_text = sub_text[len(text):].lstrip()
  165. if sub_text:
  166. sub_text = f' ({sub_text})'
  167. add_item(items, (vendor, device, sub_vendor, sub_model), text + sub_text)
  168. with open('20-pci-vendor-model.hwdb', 'wt') as out:
  169. header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids')
  170. for key in sorted(items):
  171. if len(key) == 1:
  172. p, n = 'pci:v0000{}*', 'VENDOR'
  173. elif len(key) == 2:
  174. p, n = 'pci:v0000{}d0000{}*', 'MODEL'
  175. else:
  176. p, n = 'pci:v0000{}d0000{}sv0000{}sd0000{}*', 'MODEL'
  177. print('', p.format(*key),
  178. f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out)
  179. print(f'Wrote {out.name}')
  180. def pci_classes(p):
  181. items = {}
  182. for klass_group in p.CLASSES:
  183. klass = klass_group.KLASS.klass.upper()
  184. text = klass_group.KLASS.text.strip()
  185. add_item(items, (klass,), text)
  186. for subclass_group in klass_group.SUBCLASSES:
  187. subclass = subclass_group.subclass.upper()
  188. text = subclass_group.text.strip()
  189. add_item(items, (klass, subclass), text)
  190. for protocol_group in subclass_group.PROTOCOLS:
  191. protocol = protocol_group.protocol.upper()
  192. text = protocol_group.name.strip()
  193. add_item(items, (klass, subclass, protocol), text)
  194. with open('20-pci-classes.hwdb', 'wt') as out:
  195. header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids')
  196. for key in sorted(items):
  197. if len(key) == 1:
  198. p, n = 'pci:v*d*sv*sd*bc{}*', 'CLASS'
  199. elif len(key) == 2:
  200. p, n = 'pci:v*d*sv*sd*bc{}sc{}*', 'SUBCLASS'
  201. else:
  202. p, n = 'pci:v*d*sv*sd*bc{}sc{}i{}*', 'INTERFACE'
  203. print('', p.format(*key),
  204. f' ID_PCI_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out)
  205. print(f'Wrote {out.name}')
  206. def sdio_vendor_model(p):
  207. items = {}
  208. for vendor_group in p.VENDORS:
  209. vendor = vendor_group.VENDOR.vendor.upper()
  210. text = vendor_group.VENDOR.text.strip()
  211. add_item(items, (vendor,), text)
  212. for device_group in vendor_group.DEVICES:
  213. device = device_group.device.upper()
  214. text = device_group.text.strip()
  215. add_item(items, (vendor, device), text)
  216. with open('20-sdio-vendor-model.hwdb', 'wt') as out:
  217. header(out, 'hwdb/sdio.ids')
  218. for key in sorted(items):
  219. if len(key) == 1:
  220. p, n = 'sdio:c*v{}*', 'VENDOR'
  221. else:
  222. p, n = 'sdio:c*v{}d{}*', 'MODEL'
  223. print('', p.format(*key),
  224. f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out)
  225. print(f'Wrote {out.name}')
  226. def sdio_classes(p):
  227. items = {}
  228. for klass_group in p.CLASSES:
  229. klass = klass_group.KLASS.klass.upper()
  230. text = klass_group.KLASS.text.strip()
  231. add_item(items, klass, text)
  232. with open('20-sdio-classes.hwdb', 'wt') as out:
  233. header(out, 'hwdb/sdio.ids')
  234. for klass in sorted(items):
  235. print(f'',
  236. f'sdio:c{klass}v*d*',
  237. f' ID_SDIO_CLASS_FROM_DATABASE={items[klass]}', sep='\n', file=out)
  238. print(f'Wrote {out.name}')
  239. # MAC Address Block Large/Medium/Small
  240. # Large MA-L 24/24 bit (OUI)
  241. # Medium MA-M 28/20 bit (OUI prefix owned by IEEE)
  242. # Small MA-S 36/12 bit (OUI prefix owned by IEEE)
  243. def oui(p1, p2, p3):
  244. prefixes = set()
  245. items = {}
  246. for p, check in ((p1, False), (p2, False), (p3, True)):
  247. for vendor_group in p.VENDORS:
  248. prefix = vendor_group.prefix.upper()
  249. if check:
  250. if prefix in prefixes:
  251. continue
  252. else:
  253. prefixes.add(prefix)
  254. start = vendor_group.start.upper()
  255. end = vendor_group.end.upper()
  256. if end and start != end:
  257. print(f'{prefix:} {start} != {end}', file=sys.stderr)
  258. text = vendor_group.text.strip()
  259. key = prefix + start if end else prefix
  260. add_item(items, key, text)
  261. with open('20-OUI.hwdb', 'wt') as out:
  262. header(out,
  263. 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-L&format=txt',
  264. 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-M&format=txt',
  265. 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-S&format=txt')
  266. for pattern in sorted(items):
  267. print(f'',
  268. f'OUI:{pattern}*',
  269. f' ID_OUI_FROM_DATABASE={items[pattern]}', sep='\n', file=out)
  270. print(f'Wrote {out.name}')
  271. if __name__ == '__main__':
  272. args = sys.argv[1:]
  273. if not args or 'usb' in args:
  274. p = usb_ids_grammar().parseFile(open('usb.ids', errors='replace'))
  275. usb_vendor_model(p)
  276. usb_classes(p)
  277. if not args or 'pci' in args:
  278. p = pci_ids_grammar().parseFile(open('pci.ids', errors='replace'))
  279. pci_vendor_model(p)
  280. pci_classes(p)
  281. if not args or 'sdio' in args:
  282. p = pci_ids_grammar().parseFile(open('sdio.ids', errors='replace'))
  283. sdio_vendor_model(p)
  284. sdio_classes(p)
  285. if not args or 'oui' in args:
  286. p = oui_grammar('small').parseFile(open('ma-small.txt'))
  287. p2 = oui_grammar('medium').parseFile(open('ma-medium.txt'))
  288. p3 = oui_grammar('large').parseFile(open('ma-large.txt'))
  289. oui(p, p2, p3)