Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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 return self.has_entry_doc()
135
136 def has_entry_doc(self):
137 for entry in self.entries.values():
138 if entry.has_doc():
139 return True
140 return False
141
142 def get_mask(self, as_flags=None):
143 mask = 0
144 for e in self.entries.values():
145 mask += e.user_value(as_flags)
146 return mask
147
148
149class SpecAttr(SpecElement):
150 """ Single Netlink attribute type
151
152 Represents a single attribute type within an attr space.
153
154 Attributes:
155 type string, attribute type
156 value numerical ID when serialized
157 attr_set Attribute Set containing this attr
158 is_multi bool, attr may repeat multiple times
159 struct_name string, name of struct definition
160 sub_type string, name of sub type
161 len integer, optional byte length of binary types
162 display_hint string, hint to help choose format specifier
163 when displaying the value
164 sub_message string, name of sub message type
165 selector string, name of attribute used to select
166 sub-message type
167
168 is_auto_scalar bool, attr is a variable-size scalar
169 """
170 def __init__(self, family, attr_set, yaml, value):
171 super().__init__(family, yaml)
172
173 self.type = yaml['type']
174 self.value = value
175 self.attr_set = attr_set
176 self.is_multi = yaml.get('multi-attr', False)
177 self.struct_name = yaml.get('struct')
178 self.sub_type = yaml.get('sub-type')
179 self.byte_order = yaml.get('byte-order')
180 self.len = yaml.get('len')
181 self.display_hint = yaml.get('display-hint')
182 self.sub_message = yaml.get('sub-message')
183 self.selector = yaml.get('selector')
184
185 self.is_auto_scalar = self.type == "sint" or self.type == "uint"
186
187
188class SpecAttrSet(SpecElement):
189 """ Netlink Attribute Set class.
190
191 Represents a ID space of attributes within Netlink.
192
193 Note that unlike other elements, which expose contents of the raw spec
194 via the dictionary interface Attribute Set exposes attributes by name.
195
196 Attributes:
197 attrs ordered dict of all attributes (indexed by name)
198 attrs_by_val ordered dict of all attributes (indexed by value)
199 subset_of parent set if this is a subset, otherwise None
200 """
201 def __init__(self, family, yaml):
202 super().__init__(family, yaml)
203
204 self.subset_of = self.yaml.get('subset-of', None)
205
206 self.attrs = collections.OrderedDict()
207 self.attrs_by_val = collections.OrderedDict()
208
209 if self.subset_of is None:
210 val = 1
211 for elem in self.yaml['attributes']:
212 if 'value' in elem:
213 val = elem['value']
214
215 attr = self.new_attr(elem, val)
216 self.attrs[attr.name] = attr
217 self.attrs_by_val[attr.value] = attr
218 val += 1
219 else:
220 real_set = family.attr_sets[self.subset_of]
221 for elem in self.yaml['attributes']:
222 real_attr = real_set[elem['name']]
223 combined_elem = real_attr.yaml | elem
224 attr = self.new_attr(combined_elem, real_attr.value)
225
226 self.attrs[attr.name] = attr
227 self.attrs_by_val[attr.value] = attr
228
229 def new_attr(self, elem, value):
230 return SpecAttr(self.family, self, elem, value)
231
232 def __getitem__(self, key):
233 return self.attrs[key]
234
235 def __contains__(self, key):
236 return key in self.attrs
237
238 def __iter__(self):
239 yield from self.attrs
240
241 def items(self):
242 return self.attrs.items()
243
244
245class SpecStructMember(SpecElement):
246 """Struct member attribute
247
248 Represents a single struct member attribute.
249
250 Attributes:
251 type string, type of the member attribute
252 byte_order string or None for native byte order
253 enum string, name of the enum definition
254 len integer, optional byte length of binary types
255 display_hint string, hint to help choose format specifier
256 when displaying the value
257 struct string, name of nested struct type
258 """
259 def __init__(self, family, yaml):
260 super().__init__(family, yaml)
261 self.type = yaml['type']
262 self.byte_order = yaml.get('byte-order')
263 self.enum = yaml.get('enum')
264 self.len = yaml.get('len')
265 self.display_hint = yaml.get('display-hint')
266 self.struct = yaml.get('struct')
267
268
269class SpecStruct(SpecElement):
270 """Netlink struct type
271
272 Represents a C struct definition.
273
274 Attributes:
275 members ordered list of struct members
276 """
277 def __init__(self, family, yaml):
278 super().__init__(family, yaml)
279
280 self.members = []
281 for member in yaml.get('members', []):
282 self.members.append(self.new_member(family, member))
283
284 def new_member(self, family, elem):
285 return SpecStructMember(family, elem)
286
287 def __iter__(self):
288 yield from self.members
289
290 def items(self):
291 return self.members.items()
292
293
294class SpecSubMessage(SpecElement):
295 """ Netlink sub-message definition
296
297 Represents a set of sub-message formats for polymorphic nlattrs
298 that contain type-specific sub messages.
299
300 Attributes:
301 name string, name of sub-message definition
302 formats dict of sub-message formats indexed by match value
303 """
304 def __init__(self, family, yaml):
305 super().__init__(family, yaml)
306
307 self.formats = collections.OrderedDict()
308 for elem in self.yaml['formats']:
309 format = self.new_format(family, elem)
310 self.formats[format.value] = format
311
312 def new_format(self, family, format):
313 return SpecSubMessageFormat(family, format)
314
315
316class SpecSubMessageFormat(SpecElement):
317 """ Netlink sub-message format definition
318
319 Represents a single format for a sub-message.
320
321 Attributes:
322 value attribute value to match against type selector
323 fixed_header string, name of fixed header, or None
324 attr_set string, name of attribute set, or None
325 """
326 def __init__(self, family, yaml):
327 super().__init__(family, yaml)
328
329 self.value = yaml.get('value')
330 self.fixed_header = yaml.get('fixed-header')
331 self.attr_set = yaml.get('attribute-set')
332
333
334class SpecOperation(SpecElement):
335 """Netlink Operation
336
337 Information about a single Netlink operation.
338
339 Attributes:
340 value numerical ID when serialized, None if req/rsp values differ
341
342 req_value numerical ID when serialized, user -> kernel
343 rsp_value numerical ID when serialized, user <- kernel
344 modes supported operation modes (do, dump, event etc.)
345 is_call bool, whether the operation is a call
346 is_async bool, whether the operation is a notification
347 is_resv bool, whether the operation does not exist (it's just a reserved ID)
348 attr_set attribute set name
349 fixed_header string, optional name of fixed header struct
350
351 yaml raw spec as loaded from the spec file
352 """
353 def __init__(self, family, yaml, req_value, rsp_value):
354 super().__init__(family, yaml)
355
356 self.value = req_value if req_value == rsp_value else None
357 self.req_value = req_value
358 self.rsp_value = rsp_value
359
360 self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'}
361 self.is_call = 'do' in yaml or 'dump' in yaml
362 self.is_async = 'notify' in yaml or 'event' in yaml
363 self.is_resv = not self.is_async and not self.is_call
364 self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)
365
366 # Added by resolve:
367 self.attr_set = None
368 delattr(self, "attr_set")
369
370 def resolve(self):
371 self.resolve_up(super())
372
373 if 'attribute-set' in self.yaml:
374 attr_set_name = self.yaml['attribute-set']
375 elif 'notify' in self.yaml:
376 msg = self.family.msgs[self.yaml['notify']]
377 attr_set_name = msg['attribute-set']
378 elif self.is_resv:
379 attr_set_name = ''
380 else:
381 raise Exception(f"Can't resolve attribute set for op '{self.name}'")
382 if attr_set_name:
383 self.attr_set = self.family.attr_sets[attr_set_name]
384
385
386class SpecMcastGroup(SpecElement):
387 """Netlink Multicast Group
388
389 Information about a multicast group.
390
391 Value is only used for classic netlink families that use the
392 netlink-raw schema. Genetlink families use dynamic ID allocation
393 where the ids of multicast groups get resolved at runtime. Value
394 will be None for genetlink families.
395
396 Attributes:
397 name name of the mulitcast group
398 value integer id of this multicast group for netlink-raw or None
399 yaml raw spec as loaded from the spec file
400 """
401 def __init__(self, family, yaml):
402 super().__init__(family, yaml)
403 self.value = self.yaml.get('value')
404
405
406class SpecFamily(SpecElement):
407 """ Netlink Family Spec class.
408
409 Netlink family information loaded from a spec (e.g. in YAML).
410 Takes care of unfolding implicit information which can be skipped
411 in the spec itself for brevity.
412
413 The class can be used like a dictionary to access the raw spec
414 elements but that's usually a bad idea.
415
416 Attributes:
417 proto protocol type (e.g. genetlink)
418 msg_id_model enum-model for operations (unified, directional etc.)
419 license spec license (loaded from an SPDX tag on the spec)
420
421 attr_sets dict of attribute sets
422 msgs dict of all messages (index by name)
423 sub_msgs dict of all sub messages (index by name)
424 ops dict of all valid requests / responses
425 ntfs dict of all async events
426 consts dict of all constants/enums
427 fixed_header string, optional name of family default fixed header struct
428 mcast_groups dict of all multicast groups (index by name)
429 kernel_family dict of kernel family attributes
430 """
431 def __init__(self, spec_path, schema_path=None, exclude_ops=None):
432 with open(spec_path, "r") as stream:
433 prefix = '# SPDX-License-Identifier: '
434 first = stream.readline().strip()
435 if not first.startswith(prefix):
436 raise Exception('SPDX license tag required in the spec')
437 self.license = first[len(prefix):]
438
439 stream.seek(0)
440 spec = yaml.safe_load(stream)
441
442 self._resolution_list = []
443
444 super().__init__(self, spec)
445
446 self._exclude_ops = exclude_ops if exclude_ops else []
447
448 self.proto = self.yaml.get('protocol', 'genetlink')
449 self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')
450
451 if schema_path is None:
452 schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'
453 if schema_path:
454 global jsonschema
455
456 with open(schema_path, "r") as stream:
457 schema = yaml.safe_load(stream)
458
459 if jsonschema is None:
460 jsonschema = importlib.import_module("jsonschema")
461
462 jsonschema.validate(self.yaml, schema)
463
464 self.attr_sets = collections.OrderedDict()
465 self.sub_msgs = collections.OrderedDict()
466 self.msgs = collections.OrderedDict()
467 self.req_by_value = collections.OrderedDict()
468 self.rsp_by_value = collections.OrderedDict()
469 self.ops = collections.OrderedDict()
470 self.ntfs = collections.OrderedDict()
471 self.consts = collections.OrderedDict()
472 self.mcast_groups = collections.OrderedDict()
473 self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {}))
474
475 last_exception = None
476 while len(self._resolution_list) > 0:
477 resolved = []
478 unresolved = self._resolution_list
479 self._resolution_list = []
480
481 for elem in unresolved:
482 try:
483 elem.resolve()
484 except (KeyError, AttributeError) as e:
485 self._resolution_list.append(elem)
486 last_exception = e
487 continue
488
489 resolved.append(elem)
490
491 if len(resolved) == 0:
492 raise last_exception
493
494 def new_enum(self, elem):
495 return SpecEnumSet(self, elem)
496
497 def new_attr_set(self, elem):
498 return SpecAttrSet(self, elem)
499
500 def new_struct(self, elem):
501 return SpecStruct(self, elem)
502
503 def new_sub_message(self, elem):
504 return SpecSubMessage(self, elem);
505
506 def new_operation(self, elem, req_val, rsp_val):
507 return SpecOperation(self, elem, req_val, rsp_val)
508
509 def new_mcast_group(self, elem):
510 return SpecMcastGroup(self, elem)
511
512 def add_unresolved(self, elem):
513 self._resolution_list.append(elem)
514
515 def _dictify_ops_unified(self):
516 self.fixed_header = self.yaml['operations'].get('fixed-header')
517 val = 1
518 for elem in self.yaml['operations']['list']:
519 if 'value' in elem:
520 val = elem['value']
521
522 op = self.new_operation(elem, val, val)
523 val += 1
524
525 self.msgs[op.name] = op
526
527 def _dictify_ops_directional(self):
528 self.fixed_header = self.yaml['operations'].get('fixed-header')
529 req_val = rsp_val = 1
530 for elem in self.yaml['operations']['list']:
531 if 'notify' in elem or 'event' in elem:
532 if 'value' in elem:
533 rsp_val = elem['value']
534 req_val_next = req_val
535 rsp_val_next = rsp_val + 1
536 req_val = None
537 elif 'do' in elem or 'dump' in elem:
538 mode = elem['do'] if 'do' in elem else elem['dump']
539
540 v = mode.get('request', {}).get('value', None)
541 if v:
542 req_val = v
543 v = mode.get('reply', {}).get('value', None)
544 if v:
545 rsp_val = v
546
547 rsp_inc = 1 if 'reply' in mode else 0
548 req_val_next = req_val + 1
549 rsp_val_next = rsp_val + rsp_inc
550 else:
551 raise Exception("Can't parse directional ops")
552
553 if req_val == req_val_next:
554 req_val = None
555 if rsp_val == rsp_val_next:
556 rsp_val = None
557
558 skip = False
559 for exclude in self._exclude_ops:
560 skip |= bool(exclude.match(elem['name']))
561 if not skip:
562 op = self.new_operation(elem, req_val, rsp_val)
563
564 req_val = req_val_next
565 rsp_val = rsp_val_next
566
567 self.msgs[op.name] = op
568
569 def find_operation(self, name):
570 """
571 For a given operation name, find and return operation spec.
572 """
573 for op in self.yaml['operations']['list']:
574 if name == op['name']:
575 return op
576 return None
577
578 def resolve(self):
579 self.resolve_up(super())
580
581 definitions = self.yaml.get('definitions', [])
582 for elem in definitions:
583 if elem['type'] == 'enum' or elem['type'] == 'flags':
584 self.consts[elem['name']] = self.new_enum(elem)
585 elif elem['type'] == 'struct':
586 self.consts[elem['name']] = self.new_struct(elem)
587 else:
588 self.consts[elem['name']] = elem
589
590 for elem in self.yaml['attribute-sets']:
591 attr_set = self.new_attr_set(elem)
592 self.attr_sets[elem['name']] = attr_set
593
594 for elem in self.yaml.get('sub-messages', []):
595 sub_message = self.new_sub_message(elem)
596 self.sub_msgs[sub_message.name] = sub_message
597
598 if self.msg_id_model == 'unified':
599 self._dictify_ops_unified()
600 elif self.msg_id_model == 'directional':
601 self._dictify_ops_directional()
602
603 for op in self.msgs.values():
604 if op.req_value is not None:
605 self.req_by_value[op.req_value] = op
606 if op.rsp_value is not None:
607 self.rsp_by_value[op.rsp_value] = op
608 if not op.is_async and 'attribute-set' in op:
609 self.ops[op.name] = op
610 elif op.is_async:
611 self.ntfs[op.name] = op
612
613 mcgs = self.yaml.get('mcast-groups')
614 if mcgs:
615 for elem in mcgs['list']:
616 mcg = self.new_mcast_group(elem)
617 self.mcast_groups[elem['name']] = mcg