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 """ 158 def __init__(self, family, attr_set, yaml, value): 159 super().__init__(family, yaml) 160 161 self.value = value 162 self.attr_set = attr_set 163 self.is_multi = yaml.get('multi-attr', False) 164 self.struct_name = yaml.get('struct') 165 self.sub_type = yaml.get('sub-type') 166 self.byte_order = yaml.get('byte-order') 167 168 169class SpecAttrSet(SpecElement): 170 """ Netlink Attribute Set class. 171 172 Represents a ID space of attributes within Netlink. 173 174 Note that unlike other elements, which expose contents of the raw spec 175 via the dictionary interface Attribute Set exposes attributes by name. 176 177 Attributes: 178 attrs ordered dict of all attributes (indexed by name) 179 attrs_by_val ordered dict of all attributes (indexed by value) 180 subset_of parent set if this is a subset, otherwise None 181 """ 182 def __init__(self, family, yaml): 183 super().__init__(family, yaml) 184 185 self.subset_of = self.yaml.get('subset-of', None) 186 187 self.attrs = collections.OrderedDict() 188 self.attrs_by_val = collections.OrderedDict() 189 190 if self.subset_of is None: 191 val = 1 192 for elem in self.yaml['attributes']: 193 if 'value' in elem: 194 val = elem['value'] 195 196 attr = self.new_attr(elem, val) 197 self.attrs[attr.name] = attr 198 self.attrs_by_val[attr.value] = attr 199 val += 1 200 else: 201 real_set = family.attr_sets[self.subset_of] 202 for elem in self.yaml['attributes']: 203 attr = real_set[elem['name']] 204 self.attrs[attr.name] = attr 205 self.attrs_by_val[attr.value] = attr 206 207 def new_attr(self, elem, value): 208 return SpecAttr(self.family, self, elem, value) 209 210 def __getitem__(self, key): 211 return self.attrs[key] 212 213 def __contains__(self, key): 214 return key in self.attrs 215 216 def __iter__(self): 217 yield from self.attrs 218 219 def items(self): 220 return self.attrs.items() 221 222 223class SpecStructMember(SpecElement): 224 """Struct member attribute 225 226 Represents a single struct member attribute. 227 228 Attributes: 229 type string, type of the member attribute 230 """ 231 def __init__(self, family, yaml): 232 super().__init__(family, yaml) 233 self.type = yaml['type'] 234 235 236class SpecStruct(SpecElement): 237 """Netlink struct type 238 239 Represents a C struct definition. 240 241 Attributes: 242 members ordered list of struct members 243 """ 244 def __init__(self, family, yaml): 245 super().__init__(family, yaml) 246 247 self.members = [] 248 for member in yaml.get('members', []): 249 self.members.append(self.new_member(family, member)) 250 251 def new_member(self, family, elem): 252 return SpecStructMember(family, elem) 253 254 def __iter__(self): 255 yield from self.members 256 257 def items(self): 258 return self.members.items() 259 260 261class SpecOperation(SpecElement): 262 """Netlink Operation 263 264 Information about a single Netlink operation. 265 266 Attributes: 267 value numerical ID when serialized, None if req/rsp values differ 268 269 req_value numerical ID when serialized, user -> kernel 270 rsp_value numerical ID when serialized, user <- kernel 271 is_call bool, whether the operation is a call 272 is_async bool, whether the operation is a notification 273 is_resv bool, whether the operation does not exist (it's just a reserved ID) 274 attr_set attribute set name 275 fixed_header string, optional name of fixed header struct 276 277 yaml raw spec as loaded from the spec file 278 """ 279 def __init__(self, family, yaml, req_value, rsp_value): 280 super().__init__(family, yaml) 281 282 self.value = req_value if req_value == rsp_value else None 283 self.req_value = req_value 284 self.rsp_value = rsp_value 285 286 self.is_call = 'do' in yaml or 'dump' in yaml 287 self.is_async = 'notify' in yaml or 'event' in yaml 288 self.is_resv = not self.is_async and not self.is_call 289 self.fixed_header = self.yaml.get('fixed-header', family.fixed_header) 290 291 # Added by resolve: 292 self.attr_set = None 293 delattr(self, "attr_set") 294 295 def resolve(self): 296 self.resolve_up(super()) 297 298 if 'attribute-set' in self.yaml: 299 attr_set_name = self.yaml['attribute-set'] 300 elif 'notify' in self.yaml: 301 msg = self.family.msgs[self.yaml['notify']] 302 attr_set_name = msg['attribute-set'] 303 elif self.is_resv: 304 attr_set_name = '' 305 else: 306 raise Exception(f"Can't resolve attribute set for op '{self.name}'") 307 if attr_set_name: 308 self.attr_set = self.family.attr_sets[attr_set_name] 309 310 311class SpecFamily(SpecElement): 312 """ Netlink Family Spec class. 313 314 Netlink family information loaded from a spec (e.g. in YAML). 315 Takes care of unfolding implicit information which can be skipped 316 in the spec itself for brevity. 317 318 The class can be used like a dictionary to access the raw spec 319 elements but that's usually a bad idea. 320 321 Attributes: 322 proto protocol type (e.g. genetlink) 323 license spec license (loaded from an SPDX tag on the spec) 324 325 attr_sets dict of attribute sets 326 msgs dict of all messages (index by name) 327 msgs_by_value dict of all messages (indexed by name) 328 ops dict of all valid requests / responses 329 consts dict of all constants/enums 330 fixed_header string, optional name of family default fixed header struct 331 """ 332 def __init__(self, spec_path, schema_path=None): 333 with open(spec_path, "r") as stream: 334 prefix = '# SPDX-License-Identifier: ' 335 first = stream.readline().strip() 336 if not first.startswith(prefix): 337 raise Exception('SPDX license tag required in the spec') 338 self.license = first[len(prefix):] 339 340 stream.seek(0) 341 spec = yaml.safe_load(stream) 342 343 self._resolution_list = [] 344 345 super().__init__(self, spec) 346 347 self.proto = self.yaml.get('protocol', 'genetlink') 348 349 if schema_path is None: 350 schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' 351 if schema_path: 352 global jsonschema 353 354 with open(schema_path, "r") as stream: 355 schema = yaml.safe_load(stream) 356 357 if jsonschema is None: 358 jsonschema = importlib.import_module("jsonschema") 359 360 jsonschema.validate(self.yaml, schema) 361 362 self.attr_sets = collections.OrderedDict() 363 self.msgs = collections.OrderedDict() 364 self.req_by_value = collections.OrderedDict() 365 self.rsp_by_value = collections.OrderedDict() 366 self.ops = collections.OrderedDict() 367 self.consts = collections.OrderedDict() 368 369 last_exception = None 370 while len(self._resolution_list) > 0: 371 resolved = [] 372 unresolved = self._resolution_list 373 self._resolution_list = [] 374 375 for elem in unresolved: 376 try: 377 elem.resolve() 378 except (KeyError, AttributeError) as e: 379 self._resolution_list.append(elem) 380 last_exception = e 381 continue 382 383 resolved.append(elem) 384 385 if len(resolved) == 0: 386 raise last_exception 387 388 def new_enum(self, elem): 389 return SpecEnumSet(self, elem) 390 391 def new_attr_set(self, elem): 392 return SpecAttrSet(self, elem) 393 394 def new_struct(self, elem): 395 return SpecStruct(self, elem) 396 397 def new_operation(self, elem, req_val, rsp_val): 398 return SpecOperation(self, elem, req_val, rsp_val) 399 400 def add_unresolved(self, elem): 401 self._resolution_list.append(elem) 402 403 def _dictify_ops_unified(self): 404 self.fixed_header = self.yaml['operations'].get('fixed-header') 405 val = 1 406 for elem in self.yaml['operations']['list']: 407 if 'value' in elem: 408 val = elem['value'] 409 410 op = self.new_operation(elem, val, val) 411 val += 1 412 413 self.msgs[op.name] = op 414 415 def _dictify_ops_directional(self): 416 self.fixed_header = self.yaml['operations'].get('fixed-header') 417 req_val = rsp_val = 1 418 for elem in self.yaml['operations']['list']: 419 if 'notify' in elem: 420 if 'value' in elem: 421 rsp_val = elem['value'] 422 req_val_next = req_val 423 rsp_val_next = rsp_val + 1 424 req_val = None 425 elif 'do' in elem or 'dump' in elem: 426 mode = elem['do'] if 'do' in elem else elem['dump'] 427 428 v = mode.get('request', {}).get('value', None) 429 if v: 430 req_val = v 431 v = mode.get('reply', {}).get('value', None) 432 if v: 433 rsp_val = v 434 435 rsp_inc = 1 if 'reply' in mode else 0 436 req_val_next = req_val + 1 437 rsp_val_next = rsp_val + rsp_inc 438 else: 439 raise Exception("Can't parse directional ops") 440 441 op = self.new_operation(elem, req_val, rsp_val) 442 req_val = req_val_next 443 rsp_val = rsp_val_next 444 445 self.msgs[op.name] = op 446 447 def find_operation(self, name): 448 """ 449 For a given operation name, find and return operation spec. 450 """ 451 for op in self.yaml['operations']['list']: 452 if name == op['name']: 453 return op 454 return None 455 456 def resolve(self): 457 self.resolve_up(super()) 458 459 definitions = self.yaml.get('definitions', []) 460 for elem in definitions: 461 if elem['type'] == 'enum' or elem['type'] == 'flags': 462 self.consts[elem['name']] = self.new_enum(elem) 463 elif elem['type'] == 'struct': 464 self.consts[elem['name']] = self.new_struct(elem) 465 else: 466 self.consts[elem['name']] = elem 467 468 for elem in self.yaml['attribute-sets']: 469 attr_set = self.new_attr_set(elem) 470 self.attr_sets[elem['name']] = attr_set 471 472 msg_id_model = self.yaml['operations'].get('enum-model', 'unified') 473 if msg_id_model == 'unified': 474 self._dictify_ops_unified() 475 elif msg_id_model == 'directional': 476 self._dictify_ops_directional() 477 478 for op in self.msgs.values(): 479 if op.req_value is not None: 480 self.req_by_value[op.req_value] = op 481 if op.rsp_value is not None: 482 self.rsp_by_value[op.rsp_value] = op 483 if not op.is_async and 'attribute-set' in op: 484 self.ops[op.name] = op 485