13aacf828SJakub Kicinski# SPDX-License-Identifier: BSD-3-Clause 23aacf828SJakub Kicinski 33aacf828SJakub Kicinskiimport collections 45c6674f6SJakub Kicinskiimport importlib 53aacf828SJakub Kicinskiimport os 63aacf828SJakub Kicinskiimport yaml 73aacf828SJakub Kicinski 83aacf828SJakub Kicinski 95c6674f6SJakub Kicinski# To be loaded dynamically as needed 105c6674f6SJakub Kicinskijsonschema = None 115c6674f6SJakub Kicinski 125c6674f6SJakub Kicinski 133aacf828SJakub Kicinskiclass SpecElement: 143aacf828SJakub Kicinski """Netlink spec element. 153aacf828SJakub Kicinski 163aacf828SJakub Kicinski Abstract element of the Netlink spec. Implements the dictionary interface 173aacf828SJakub Kicinski for access to the raw spec. Supports iterative resolution of dependencies 183aacf828SJakub Kicinski across elements and class inheritance levels. The elements of the spec 193aacf828SJakub Kicinski may refer to each other, and although loops should be very rare, having 203aacf828SJakub Kicinski to maintain correct ordering of instantiation is painful, so the resolve() 213aacf828SJakub Kicinski method should be used to perform parts of init which require access to 223aacf828SJakub Kicinski other parts of the spec. 233aacf828SJakub Kicinski 243aacf828SJakub Kicinski Attributes: 253aacf828SJakub Kicinski yaml raw spec as loaded from the spec file 263aacf828SJakub Kicinski family back reference to the full family 273aacf828SJakub Kicinski 283aacf828SJakub Kicinski name name of the entity as listed in the spec (optional) 293aacf828SJakub Kicinski ident_name name which can be safely used as identifier in code (optional) 303aacf828SJakub Kicinski """ 313aacf828SJakub Kicinski def __init__(self, family, yaml): 323aacf828SJakub Kicinski self.yaml = yaml 333aacf828SJakub Kicinski self.family = family 343aacf828SJakub Kicinski 353aacf828SJakub Kicinski if 'name' in self.yaml: 363aacf828SJakub Kicinski self.name = self.yaml['name'] 373aacf828SJakub Kicinski self.ident_name = self.name.replace('-', '_') 383aacf828SJakub Kicinski 393aacf828SJakub Kicinski self._super_resolved = False 403aacf828SJakub Kicinski family.add_unresolved(self) 413aacf828SJakub Kicinski 423aacf828SJakub Kicinski def __getitem__(self, key): 433aacf828SJakub Kicinski return self.yaml[key] 443aacf828SJakub Kicinski 453aacf828SJakub Kicinski def __contains__(self, key): 463aacf828SJakub Kicinski return key in self.yaml 473aacf828SJakub Kicinski 483aacf828SJakub Kicinski def get(self, key, default=None): 493aacf828SJakub Kicinski return self.yaml.get(key, default) 503aacf828SJakub Kicinski 513aacf828SJakub Kicinski def resolve_up(self, up): 523aacf828SJakub Kicinski if not self._super_resolved: 533aacf828SJakub Kicinski up.resolve() 543aacf828SJakub Kicinski self._super_resolved = True 553aacf828SJakub Kicinski 563aacf828SJakub Kicinski def resolve(self): 573aacf828SJakub Kicinski pass 583aacf828SJakub Kicinski 593aacf828SJakub Kicinski 603aacf828SJakub Kicinskiclass SpecAttr(SpecElement): 613aacf828SJakub Kicinski """ Single Netlink atttribute type 623aacf828SJakub Kicinski 633aacf828SJakub Kicinski Represents a single attribute type within an attr space. 643aacf828SJakub Kicinski 653aacf828SJakub Kicinski Attributes: 663aacf828SJakub Kicinski value numerical ID when serialized 673aacf828SJakub Kicinski attr_set Attribute Set containing this attr 683aacf828SJakub Kicinski """ 693aacf828SJakub Kicinski def __init__(self, family, attr_set, yaml, value): 703aacf828SJakub Kicinski super().__init__(family, yaml) 713aacf828SJakub Kicinski 723aacf828SJakub Kicinski self.value = value 733aacf828SJakub Kicinski self.attr_set = attr_set 743aacf828SJakub Kicinski self.is_multi = yaml.get('multi-attr', False) 753aacf828SJakub Kicinski 763aacf828SJakub Kicinski 773aacf828SJakub Kicinskiclass SpecAttrSet(SpecElement): 783aacf828SJakub Kicinski """ Netlink Attribute Set class. 793aacf828SJakub Kicinski 803aacf828SJakub Kicinski Represents a ID space of attributes within Netlink. 813aacf828SJakub Kicinski 823aacf828SJakub Kicinski Note that unlike other elements, which expose contents of the raw spec 833aacf828SJakub Kicinski via the dictionary interface Attribute Set exposes attributes by name. 843aacf828SJakub Kicinski 853aacf828SJakub Kicinski Attributes: 863aacf828SJakub Kicinski attrs ordered dict of all attributes (indexed by name) 873aacf828SJakub Kicinski attrs_by_val ordered dict of all attributes (indexed by value) 883aacf828SJakub Kicinski subset_of parent set if this is a subset, otherwise None 893aacf828SJakub Kicinski """ 903aacf828SJakub Kicinski def __init__(self, family, yaml): 913aacf828SJakub Kicinski super().__init__(family, yaml) 923aacf828SJakub Kicinski 933aacf828SJakub Kicinski self.subset_of = self.yaml.get('subset-of', None) 943aacf828SJakub Kicinski 953aacf828SJakub Kicinski self.attrs = collections.OrderedDict() 963aacf828SJakub Kicinski self.attrs_by_val = collections.OrderedDict() 973aacf828SJakub Kicinski 98*7cf93538SJakub Kicinski if self.subset_of is None: 993aacf828SJakub Kicinski val = 0 1003aacf828SJakub Kicinski for elem in self.yaml['attributes']: 1013aacf828SJakub Kicinski if 'value' in elem: 1023aacf828SJakub Kicinski val = elem['value'] 1033aacf828SJakub Kicinski 1043aacf828SJakub Kicinski attr = self.new_attr(elem, val) 1053aacf828SJakub Kicinski self.attrs[attr.name] = attr 1063aacf828SJakub Kicinski self.attrs_by_val[attr.value] = attr 1073aacf828SJakub Kicinski val += 1 108*7cf93538SJakub Kicinski else: 109*7cf93538SJakub Kicinski real_set = family.attr_sets[self.subset_of] 110*7cf93538SJakub Kicinski for elem in self.yaml['attributes']: 111*7cf93538SJakub Kicinski attr = real_set[elem['name']] 112*7cf93538SJakub Kicinski self.attrs[attr.name] = attr 113*7cf93538SJakub Kicinski self.attrs_by_val[attr.value] = attr 1143aacf828SJakub Kicinski 1153aacf828SJakub Kicinski def new_attr(self, elem, value): 1163aacf828SJakub Kicinski return SpecAttr(self.family, self, elem, value) 1173aacf828SJakub Kicinski 1183aacf828SJakub Kicinski def __getitem__(self, key): 1193aacf828SJakub Kicinski return self.attrs[key] 1203aacf828SJakub Kicinski 1213aacf828SJakub Kicinski def __contains__(self, key): 1223aacf828SJakub Kicinski return key in self.attrs 1233aacf828SJakub Kicinski 1243aacf828SJakub Kicinski def __iter__(self): 1253aacf828SJakub Kicinski yield from self.attrs 1263aacf828SJakub Kicinski 1273aacf828SJakub Kicinski def items(self): 1283aacf828SJakub Kicinski return self.attrs.items() 1293aacf828SJakub Kicinski 1303aacf828SJakub Kicinski 1313aacf828SJakub Kicinskiclass SpecOperation(SpecElement): 1323aacf828SJakub Kicinski """Netlink Operation 1333aacf828SJakub Kicinski 1343aacf828SJakub Kicinski Information about a single Netlink operation. 1353aacf828SJakub Kicinski 1363aacf828SJakub Kicinski Attributes: 1373aacf828SJakub Kicinski value numerical ID when serialized, None if req/rsp values differ 1383aacf828SJakub Kicinski 1393aacf828SJakub Kicinski req_value numerical ID when serialized, user -> kernel 1403aacf828SJakub Kicinski rsp_value numerical ID when serialized, user <- kernel 1413aacf828SJakub Kicinski is_call bool, whether the operation is a call 1423aacf828SJakub Kicinski is_async bool, whether the operation is a notification 1433aacf828SJakub Kicinski is_resv bool, whether the operation does not exist (it's just a reserved ID) 1443aacf828SJakub Kicinski attr_set attribute set name 1453aacf828SJakub Kicinski 1463aacf828SJakub Kicinski yaml raw spec as loaded from the spec file 1473aacf828SJakub Kicinski """ 1483aacf828SJakub Kicinski def __init__(self, family, yaml, req_value, rsp_value): 1493aacf828SJakub Kicinski super().__init__(family, yaml) 1503aacf828SJakub Kicinski 1513aacf828SJakub Kicinski self.value = req_value if req_value == rsp_value else None 1523aacf828SJakub Kicinski self.req_value = req_value 1533aacf828SJakub Kicinski self.rsp_value = rsp_value 1543aacf828SJakub Kicinski 1553aacf828SJakub Kicinski self.is_call = 'do' in yaml or 'dump' in yaml 1563aacf828SJakub Kicinski self.is_async = 'notify' in yaml or 'event' in yaml 1573aacf828SJakub Kicinski self.is_resv = not self.is_async and not self.is_call 1583aacf828SJakub Kicinski 1593aacf828SJakub Kicinski # Added by resolve: 1603aacf828SJakub Kicinski self.attr_set = None 1613aacf828SJakub Kicinski delattr(self, "attr_set") 1623aacf828SJakub Kicinski 1633aacf828SJakub Kicinski def resolve(self): 1643aacf828SJakub Kicinski self.resolve_up(super()) 1653aacf828SJakub Kicinski 1663aacf828SJakub Kicinski if 'attribute-set' in self.yaml: 1673aacf828SJakub Kicinski attr_set_name = self.yaml['attribute-set'] 1683aacf828SJakub Kicinski elif 'notify' in self.yaml: 1693aacf828SJakub Kicinski msg = self.family.msgs[self.yaml['notify']] 1703aacf828SJakub Kicinski attr_set_name = msg['attribute-set'] 1713aacf828SJakub Kicinski elif self.is_resv: 1723aacf828SJakub Kicinski attr_set_name = '' 1733aacf828SJakub Kicinski else: 1743aacf828SJakub Kicinski raise Exception(f"Can't resolve attribute set for op '{self.name}'") 1753aacf828SJakub Kicinski if attr_set_name: 1763aacf828SJakub Kicinski self.attr_set = self.family.attr_sets[attr_set_name] 1773aacf828SJakub Kicinski 1783aacf828SJakub Kicinski 1793aacf828SJakub Kicinskiclass SpecFamily(SpecElement): 1803aacf828SJakub Kicinski """ Netlink Family Spec class. 1813aacf828SJakub Kicinski 1823aacf828SJakub Kicinski Netlink family information loaded from a spec (e.g. in YAML). 1833aacf828SJakub Kicinski Takes care of unfolding implicit information which can be skipped 1843aacf828SJakub Kicinski in the spec itself for brevity. 1853aacf828SJakub Kicinski 1863aacf828SJakub Kicinski The class can be used like a dictionary to access the raw spec 1873aacf828SJakub Kicinski elements but that's usually a bad idea. 1883aacf828SJakub Kicinski 1893aacf828SJakub Kicinski Attributes: 1903aacf828SJakub Kicinski proto protocol type (e.g. genetlink) 1913aacf828SJakub Kicinski 1923aacf828SJakub Kicinski attr_sets dict of attribute sets 1933aacf828SJakub Kicinski msgs dict of all messages (index by name) 1943aacf828SJakub Kicinski msgs_by_value dict of all messages (indexed by name) 1953aacf828SJakub Kicinski ops dict of all valid requests / responses 1963aacf828SJakub Kicinski """ 1973aacf828SJakub Kicinski def __init__(self, spec_path, schema_path=None): 1983aacf828SJakub Kicinski with open(spec_path, "r") as stream: 1993aacf828SJakub Kicinski spec = yaml.safe_load(stream) 2003aacf828SJakub Kicinski 2013aacf828SJakub Kicinski self._resolution_list = [] 2023aacf828SJakub Kicinski 2033aacf828SJakub Kicinski super().__init__(self, spec) 2043aacf828SJakub Kicinski 2053aacf828SJakub Kicinski self.proto = self.yaml.get('protocol', 'genetlink') 2063aacf828SJakub Kicinski 2073aacf828SJakub Kicinski if schema_path is None: 2083aacf828SJakub Kicinski schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' 2093aacf828SJakub Kicinski if schema_path: 2105c6674f6SJakub Kicinski global jsonschema 2115c6674f6SJakub Kicinski 2123aacf828SJakub Kicinski with open(schema_path, "r") as stream: 2133aacf828SJakub Kicinski schema = yaml.safe_load(stream) 2143aacf828SJakub Kicinski 2155c6674f6SJakub Kicinski if jsonschema is None: 2165c6674f6SJakub Kicinski jsonschema = importlib.import_module("jsonschema") 2175c6674f6SJakub Kicinski 2183aacf828SJakub Kicinski jsonschema.validate(self.yaml, schema) 2193aacf828SJakub Kicinski 2203aacf828SJakub Kicinski self.attr_sets = collections.OrderedDict() 2213aacf828SJakub Kicinski self.msgs = collections.OrderedDict() 2223aacf828SJakub Kicinski self.req_by_value = collections.OrderedDict() 2233aacf828SJakub Kicinski self.rsp_by_value = collections.OrderedDict() 2243aacf828SJakub Kicinski self.ops = collections.OrderedDict() 2253aacf828SJakub Kicinski 2263aacf828SJakub Kicinski last_exception = None 2273aacf828SJakub Kicinski while len(self._resolution_list) > 0: 2283aacf828SJakub Kicinski resolved = [] 2293aacf828SJakub Kicinski unresolved = self._resolution_list 2303aacf828SJakub Kicinski self._resolution_list = [] 2313aacf828SJakub Kicinski 2323aacf828SJakub Kicinski for elem in unresolved: 2333aacf828SJakub Kicinski try: 2343aacf828SJakub Kicinski elem.resolve() 2353aacf828SJakub Kicinski except (KeyError, AttributeError) as e: 2363aacf828SJakub Kicinski self._resolution_list.append(elem) 2373aacf828SJakub Kicinski last_exception = e 2383aacf828SJakub Kicinski continue 2393aacf828SJakub Kicinski 2403aacf828SJakub Kicinski resolved.append(elem) 2413aacf828SJakub Kicinski 2423aacf828SJakub Kicinski if len(resolved) == 0: 243b9d3a3e4SJakub Kicinski raise last_exception 2443aacf828SJakub Kicinski 2453aacf828SJakub Kicinski def new_attr_set(self, elem): 2463aacf828SJakub Kicinski return SpecAttrSet(self, elem) 2473aacf828SJakub Kicinski 2483aacf828SJakub Kicinski def new_operation(self, elem, req_val, rsp_val): 2493aacf828SJakub Kicinski return SpecOperation(self, elem, req_val, rsp_val) 2503aacf828SJakub Kicinski 2513aacf828SJakub Kicinski def add_unresolved(self, elem): 2523aacf828SJakub Kicinski self._resolution_list.append(elem) 2533aacf828SJakub Kicinski 2543aacf828SJakub Kicinski def _dictify_ops_unified(self): 2553aacf828SJakub Kicinski val = 0 2563aacf828SJakub Kicinski for elem in self.yaml['operations']['list']: 2573aacf828SJakub Kicinski if 'value' in elem: 2583aacf828SJakub Kicinski val = elem['value'] 2593aacf828SJakub Kicinski 2603aacf828SJakub Kicinski op = self.new_operation(elem, val, val) 2613aacf828SJakub Kicinski val += 1 2623aacf828SJakub Kicinski 2633aacf828SJakub Kicinski self.msgs[op.name] = op 2643aacf828SJakub Kicinski 2653aacf828SJakub Kicinski def _dictify_ops_directional(self): 2663aacf828SJakub Kicinski req_val = rsp_val = 0 2673aacf828SJakub Kicinski for elem in self.yaml['operations']['list']: 2683aacf828SJakub Kicinski if 'notify' in elem: 2693aacf828SJakub Kicinski if 'value' in elem: 2703aacf828SJakub Kicinski rsp_val = elem['value'] 2713aacf828SJakub Kicinski req_val_next = req_val 2723aacf828SJakub Kicinski rsp_val_next = rsp_val + 1 2733aacf828SJakub Kicinski req_val = None 2743aacf828SJakub Kicinski elif 'do' in elem or 'dump' in elem: 2753aacf828SJakub Kicinski mode = elem['do'] if 'do' in elem else elem['dump'] 2763aacf828SJakub Kicinski 2773aacf828SJakub Kicinski v = mode.get('request', {}).get('value', None) 2783aacf828SJakub Kicinski if v: 2793aacf828SJakub Kicinski req_val = v 2803aacf828SJakub Kicinski v = mode.get('reply', {}).get('value', None) 2813aacf828SJakub Kicinski if v: 2823aacf828SJakub Kicinski rsp_val = v 2833aacf828SJakub Kicinski 2843aacf828SJakub Kicinski rsp_inc = 1 if 'reply' in mode else 0 2853aacf828SJakub Kicinski req_val_next = req_val + 1 2863aacf828SJakub Kicinski rsp_val_next = rsp_val + rsp_inc 2873aacf828SJakub Kicinski else: 2883aacf828SJakub Kicinski raise Exception("Can't parse directional ops") 2893aacf828SJakub Kicinski 2903aacf828SJakub Kicinski op = self.new_operation(elem, req_val, rsp_val) 2913aacf828SJakub Kicinski req_val = req_val_next 2923aacf828SJakub Kicinski rsp_val = rsp_val_next 2933aacf828SJakub Kicinski 2943aacf828SJakub Kicinski self.msgs[op.name] = op 2953aacf828SJakub Kicinski 2963aacf828SJakub Kicinski def resolve(self): 2973aacf828SJakub Kicinski self.resolve_up(super()) 2983aacf828SJakub Kicinski 2993aacf828SJakub Kicinski for elem in self.yaml['attribute-sets']: 3003aacf828SJakub Kicinski attr_set = self.new_attr_set(elem) 3013aacf828SJakub Kicinski self.attr_sets[elem['name']] = attr_set 3023aacf828SJakub Kicinski 3033aacf828SJakub Kicinski msg_id_model = self.yaml['operations'].get('enum-model', 'unified') 3043aacf828SJakub Kicinski if msg_id_model == 'unified': 3053aacf828SJakub Kicinski self._dictify_ops_unified() 3063aacf828SJakub Kicinski elif msg_id_model == 'directional': 3073aacf828SJakub Kicinski self._dictify_ops_directional() 3083aacf828SJakub Kicinski 3093aacf828SJakub Kicinski for op in self.msgs.values(): 3103aacf828SJakub Kicinski if op.req_value is not None: 3113aacf828SJakub Kicinski self.req_by_value[op.req_value] = op 3123aacf828SJakub Kicinski if op.rsp_value is not None: 3133aacf828SJakub Kicinski self.rsp_by_value[op.rsp_value] = op 3143aacf828SJakub Kicinski if not op.is_async and 'attribute-set' in op: 3153aacf828SJakub Kicinski self.ops[op.name] = op 316