Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/net/ynl/pyynl/lib/nlspec.py
29274 views
1
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2
3
import collections
4
import importlib
5
import os
6
import yaml
7
8
9
# To be loaded dynamically as needed
10
jsonschema = None
11
12
13
class 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
60
class 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
100
class 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
149
class 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
188
class 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
245
class 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
269
class 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
294
class 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
316
class 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
334
class 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
386
class 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
406
class 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
618
619