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