1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 3import collections 4import importlib 5import os 6import yaml 7 8 9# To be loaded dynamically as needed 10jsonschema = None 11 12 13class SpecElement: 14 """Netlink spec element. 15 16 Abstract element of the Netlink spec. Implements the dictionary interface 17 for access to the raw spec. Supports iterative resolution of dependencies 18 across elements and class inheritance levels. The elements of the spec 19 may refer to each other, and although loops should be very rare, having 20 to maintain correct ordering of instantiation is painful, so the resolve() 21 method should be used to perform parts of init which require access to 22 other parts of the spec. 23 24 Attributes: 25 yaml raw spec as loaded from the spec file 26 family back reference to the full family 27 28 name name of the entity as listed in the spec (optional) 29 ident_name name which can be safely used as identifier in code (optional) 30 """ 31 def __init__(self, family, yaml): 32 self.yaml = yaml 33 self.family = family 34 35 if 'name' in self.yaml: 36 self.name = self.yaml['name'] 37 self.ident_name = self.name.replace('-', '_') 38 39 self._super_resolved = False 40 family.add_unresolved(self) 41 42 def __getitem__(self, key): 43 return self.yaml[key] 44 45 def __contains__(self, key): 46 return key in self.yaml 47 48 def get(self, key, default=None): 49 return self.yaml.get(key, default) 50 51 def resolve_up(self, up): 52 if not self._super_resolved: 53 up.resolve() 54 self._super_resolved = True 55 56 def resolve(self): 57 pass 58 59 60class SpecEnumEntry(SpecElement): 61 """ Entry within an enum declared in the Netlink spec. 62 63 Attributes: 64 doc documentation string 65 enum_set back reference to the enum 66 value numerical value of this enum (use accessors in most situations!) 67 68 Methods: 69 raw_value raw value, i.e. the id in the enum, unlike user value which is a mask for flags 70 user_value user value, same as raw value for enums, for flags it's the mask 71 """ 72 def __init__(self, enum_set, yaml, prev, value_start): 73 if isinstance(yaml, str): 74 yaml = {'name': yaml} 75 super().__init__(enum_set.family, yaml) 76 77 self.doc = yaml.get('doc', '') 78 self.enum_set = enum_set 79 80 if 'value' in yaml: 81 self.value = yaml['value'] 82 elif prev: 83 self.value = prev.value + 1 84 else: 85 self.value = value_start 86 87 def has_doc(self): 88 return bool(self.doc) 89 90 def raw_value(self): 91 return self.value 92 93 def user_value(self, as_flags=None): 94 if self.enum_set['type'] == 'flags' or as_flags: 95 return 1 << self.value 96 else: 97 return self.value 98 99 100class SpecEnumSet(SpecElement): 101 """ Enum type 102 103 Represents an enumeration (list of numerical constants) 104 as declared in the "definitions" section of the spec. 105 106 Attributes: 107 type enum or flags 108 entries entries by name 109 entries_by_val entries by value 110 Methods: 111 get_mask for flags compute the mask of all defined values 112 """ 113 def __init__(self, family, yaml): 114 super().__init__(family, yaml) 115 116 self.type = yaml['type'] 117 118 prev_entry = None 119 value_start = self.yaml.get('value-start', 0) 120 self.entries = dict() 121 self.entries_by_val = dict() 122 for entry in self.yaml['entries']: 123 e = self.new_entry(entry, prev_entry, value_start) 124 self.entries[e.name] = e 125 self.entries_by_val[e.raw_value()] = e 126 prev_entry = e 127 128 def new_entry(self, entry, prev_entry, value_start): 129 return SpecEnumEntry(self, entry, prev_entry, value_start) 130 131 def has_doc(self): 132 if 'doc' in self.yaml: 133 return True 134 for entry in self.entries.values(): 135 if entry.has_doc(): 136 return True 137 return False 138 139 def get_mask(self, as_flags=None): 140 mask = 0 141 for e in self.entries.values(): 142 mask += e.user_value(as_flags) 143 return mask 144 145 146class SpecAttr(SpecElement): 147 """ Single Netlink atttribute type 148 149 Represents a single attribute type within an attr space. 150 151 Attributes: 152 value numerical ID when serialized 153 attr_set Attribute Set containing this attr 154 is_multi bool, attr may repeat multiple times 155 struct_name string, name of struct definition 156 sub_type string, name of sub type 157 len integer, optional byte length of binary types 158 display_hint string, hint to help choose format specifier 159 when displaying the value 160 """ 161 def __init__(self, family, attr_set, yaml, value): 162 super().__init__(family, yaml) 163 164 self.value = value 165 self.attr_set = attr_set 166 self.is_multi = yaml.get('multi-attr', False) 167 self.struct_name = yaml.get('struct') 168 self.sub_type = yaml.get('sub-type') 169 self.byte_order = yaml.get('byte-order') 170 self.len = yaml.get('len') 171 self.display_hint = yaml.get('display-hint') 172 173 174class SpecAttrSet(SpecElement): 175 """ Netlink Attribute Set class. 176 177 Represents a ID space of attributes within Netlink. 178 179 Note that unlike other elements, which expose contents of the raw spec 180 via the dictionary interface Attribute Set exposes attributes by name. 181 182 Attributes: 183 attrs ordered dict of all attributes (indexed by name) 184 attrs_by_val ordered dict of all attributes (indexed by value) 185 subset_of parent set if this is a subset, otherwise None 186 """ 187 def __init__(self, family, yaml): 188 super().__init__(family, yaml) 189 190 self.subset_of = self.yaml.get('subset-of', None) 191 192 self.attrs = collections.OrderedDict() 193 self.attrs_by_val = collections.OrderedDict() 194 195 if self.subset_of is None: 196 val = 1 197 for elem in self.yaml['attributes']: 198 if 'value' in elem: 199 val = elem['value'] 200 201 attr = self.new_attr(elem, val) 202 self.attrs[attr.name] = attr 203 self.attrs_by_val[attr.value] = attr 204 val += 1 205 else: 206 real_set = family.attr_sets[self.subset_of] 207 for elem in self.yaml['attributes']: 208 attr = real_set[elem['name']] 209 self.attrs[attr.name] = attr 210 self.attrs_by_val[attr.value] = attr 211 212 def new_attr(self, elem, value): 213 return SpecAttr(self.family, self, elem, value) 214 215 def __getitem__(self, key): 216 return self.attrs[key] 217 218 def __contains__(self, key): 219 return key in self.attrs 220 221 def __iter__(self): 222 yield from self.attrs 223 224 def items(self): 225 return self.attrs.items() 226 227 228class SpecStructMember(SpecElement): 229 """Struct member attribute 230 231 Represents a single struct member attribute. 232 233 Attributes: 234 type string, type of the member attribute 235 byte_order string or None for native byte order 236 enum string, name of the enum definition 237 len integer, optional byte length of binary types 238 display_hint string, hint to help choose format specifier 239 when displaying the value 240 """ 241 def __init__(self, family, yaml): 242 super().__init__(family, yaml) 243 self.type = yaml['type'] 244 self.byte_order = yaml.get('byte-order') 245 self.enum = yaml.get('enum') 246 self.len = yaml.get('len') 247 self.display_hint = yaml.get('display-hint') 248 249 250class SpecStruct(SpecElement): 251 """Netlink struct type 252 253 Represents a C struct definition. 254 255 Attributes: 256 members ordered list of struct members 257 """ 258 def __init__(self, family, yaml): 259 super().__init__(family, yaml) 260 261 self.members = [] 262 for member in yaml.get('members', []): 263 self.members.append(self.new_member(family, member)) 264 265 def new_member(self, family, elem): 266 return SpecStructMember(family, elem) 267 268 def __iter__(self): 269 yield from self.members 270 271 def items(self): 272 return self.members.items() 273 274 275class SpecOperation(SpecElement): 276 """Netlink Operation 277 278 Information about a single Netlink operation. 279 280 Attributes: 281 value numerical ID when serialized, None if req/rsp values differ 282 283 req_value numerical ID when serialized, user -> kernel 284 rsp_value numerical ID when serialized, user <- kernel 285 is_call bool, whether the operation is a call 286 is_async bool, whether the operation is a notification 287 is_resv bool, whether the operation does not exist (it's just a reserved ID) 288 attr_set attribute set name 289 fixed_header string, optional name of fixed header struct 290 291 yaml raw spec as loaded from the spec file 292 """ 293 def __init__(self, family, yaml, req_value, rsp_value): 294 super().__init__(family, yaml) 295 296 self.value = req_value if req_value == rsp_value else None 297 self.req_value = req_value 298 self.rsp_value = rsp_value 299 300 self.is_call = 'do' in yaml or 'dump' in yaml 301 self.is_async = 'notify' in yaml or 'event' in yaml 302 self.is_resv = not self.is_async and not self.is_call 303 self.fixed_header = self.yaml.get('fixed-header', family.fixed_header) 304 305 # Added by resolve: 306 self.attr_set = None 307 delattr(self, "attr_set") 308 309 def resolve(self): 310 self.resolve_up(super()) 311 312 if 'attribute-set' in self.yaml: 313 attr_set_name = self.yaml['attribute-set'] 314 elif 'notify' in self.yaml: 315 msg = self.family.msgs[self.yaml['notify']] 316 attr_set_name = msg['attribute-set'] 317 elif self.is_resv: 318 attr_set_name = '' 319 else: 320 raise Exception(f"Can't resolve attribute set for op '{self.name}'") 321 if attr_set_name: 322 self.attr_set = self.family.attr_sets[attr_set_name] 323 324 325class SpecMcastGroup(SpecElement): 326 """Netlink Multicast Group 327 328 Information about a multicast group. 329 330 Value is only used for classic netlink families that use the 331 netlink-raw schema. Genetlink families use dynamic ID allocation 332 where the ids of multicast groups get resolved at runtime. Value 333 will be None for genetlink families. 334 335 Attributes: 336 name name of the mulitcast group 337 value integer id of this multicast group for netlink-raw or None 338 yaml raw spec as loaded from the spec file 339 """ 340 def __init__(self, family, yaml): 341 super().__init__(family, yaml) 342 self.value = self.yaml.get('value') 343 344 345class SpecFamily(SpecElement): 346 """ Netlink Family Spec class. 347 348 Netlink family information loaded from a spec (e.g. in YAML). 349 Takes care of unfolding implicit information which can be skipped 350 in the spec itself for brevity. 351 352 The class can be used like a dictionary to access the raw spec 353 elements but that's usually a bad idea. 354 355 Attributes: 356 proto protocol type (e.g. genetlink) 357 msg_id_model enum-model for operations (unified, directional etc.) 358 license spec license (loaded from an SPDX tag on the spec) 359 360 attr_sets dict of attribute sets 361 msgs dict of all messages (index by name) 362 ops dict of all valid requests / responses 363 ntfs dict of all async events 364 consts dict of all constants/enums 365 fixed_header string, optional name of family default fixed header struct 366 mcast_groups dict of all multicast groups (index by name) 367 """ 368 def __init__(self, spec_path, schema_path=None, exclude_ops=None): 369 with open(spec_path, "r") as stream: 370 prefix = '# SPDX-License-Identifier: ' 371 first = stream.readline().strip() 372 if not first.startswith(prefix): 373 raise Exception('SPDX license tag required in the spec') 374 self.license = first[len(prefix):] 375 376 stream.seek(0) 377 spec = yaml.safe_load(stream) 378 379 self._resolution_list = [] 380 381 super().__init__(self, spec) 382 383 self._exclude_ops = exclude_ops if exclude_ops else [] 384 385 self.proto = self.yaml.get('protocol', 'genetlink') 386 self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') 387 388 if schema_path is None: 389 schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' 390 if schema_path: 391 global jsonschema 392 393 with open(schema_path, "r") as stream: 394 schema = yaml.safe_load(stream) 395 396 if jsonschema is None: 397 jsonschema = importlib.import_module("jsonschema") 398 399 jsonschema.validate(self.yaml, schema) 400 401 self.attr_sets = collections.OrderedDict() 402 self.msgs = collections.OrderedDict() 403 self.req_by_value = collections.OrderedDict() 404 self.rsp_by_value = collections.OrderedDict() 405 self.ops = collections.OrderedDict() 406 self.ntfs = collections.OrderedDict() 407 self.consts = collections.OrderedDict() 408 self.mcast_groups = collections.OrderedDict() 409 410 last_exception = None 411 while len(self._resolution_list) > 0: 412 resolved = [] 413 unresolved = self._resolution_list 414 self._resolution_list = [] 415 416 for elem in unresolved: 417 try: 418 elem.resolve() 419 except (KeyError, AttributeError) as e: 420 self._resolution_list.append(elem) 421 last_exception = e 422 continue 423 424 resolved.append(elem) 425 426 if len(resolved) == 0: 427 raise last_exception 428 429 def new_enum(self, elem): 430 return SpecEnumSet(self, elem) 431 432 def new_attr_set(self, elem): 433 return SpecAttrSet(self, elem) 434 435 def new_struct(self, elem): 436 return SpecStruct(self, elem) 437 438 def new_operation(self, elem, req_val, rsp_val): 439 return SpecOperation(self, elem, req_val, rsp_val) 440 441 def new_mcast_group(self, elem): 442 return SpecMcastGroup(self, elem) 443 444 def add_unresolved(self, elem): 445 self._resolution_list.append(elem) 446 447 def _dictify_ops_unified(self): 448 self.fixed_header = self.yaml['operations'].get('fixed-header') 449 val = 1 450 for elem in self.yaml['operations']['list']: 451 if 'value' in elem: 452 val = elem['value'] 453 454 op = self.new_operation(elem, val, val) 455 val += 1 456 457 self.msgs[op.name] = op 458 459 def _dictify_ops_directional(self): 460 self.fixed_header = self.yaml['operations'].get('fixed-header') 461 req_val = rsp_val = 1 462 for elem in self.yaml['operations']['list']: 463 if 'notify' in elem or 'event' in elem: 464 if 'value' in elem: 465 rsp_val = elem['value'] 466 req_val_next = req_val 467 rsp_val_next = rsp_val + 1 468 req_val = None 469 elif 'do' in elem or 'dump' in elem: 470 mode = elem['do'] if 'do' in elem else elem['dump'] 471 472 v = mode.get('request', {}).get('value', None) 473 if v: 474 req_val = v 475 v = mode.get('reply', {}).get('value', None) 476 if v: 477 rsp_val = v 478 479 rsp_inc = 1 if 'reply' in mode else 0 480 req_val_next = req_val + 1 481 rsp_val_next = rsp_val + rsp_inc 482 else: 483 raise Exception("Can't parse directional ops") 484 485 if req_val == req_val_next: 486 req_val = None 487 if rsp_val == rsp_val_next: 488 rsp_val = None 489 490 skip = False 491 for exclude in self._exclude_ops: 492 skip |= bool(exclude.match(elem['name'])) 493 if not skip: 494 op = self.new_operation(elem, req_val, rsp_val) 495 496 req_val = req_val_next 497 rsp_val = rsp_val_next 498 499 self.msgs[op.name] = op 500 501 def find_operation(self, name): 502 """ 503 For a given operation name, find and return operation spec. 504 """ 505 for op in self.yaml['operations']['list']: 506 if name == op['name']: 507 return op 508 return None 509 510 def resolve(self): 511 self.resolve_up(super()) 512 513 definitions = self.yaml.get('definitions', []) 514 for elem in definitions: 515 if elem['type'] == 'enum' or elem['type'] == 'flags': 516 self.consts[elem['name']] = self.new_enum(elem) 517 elif elem['type'] == 'struct': 518 self.consts[elem['name']] = self.new_struct(elem) 519 else: 520 self.consts[elem['name']] = elem 521 522 for elem in self.yaml['attribute-sets']: 523 attr_set = self.new_attr_set(elem) 524 self.attr_sets[elem['name']] = attr_set 525 526 if self.msg_id_model == 'unified': 527 self._dictify_ops_unified() 528 elif self.msg_id_model == 'directional': 529 self._dictify_ops_directional() 530 531 for op in self.msgs.values(): 532 if op.req_value is not None: 533 self.req_by_value[op.req_value] = op 534 if op.rsp_value is not None: 535 self.rsp_by_value[op.rsp_value] = op 536 if not op.is_async and 'attribute-set' in op: 537 self.ops[op.name] = op 538 elif op.is_async: 539 self.ntfs[op.name] = op 540 541 mcgs = self.yaml.get('mcast-groups') 542 if mcgs: 543 for elem in mcgs['list']: 544 mcg = self.new_mcast_group(elem) 545 self.mcast_groups[elem['name']] = mcg 546