xref: /openbmc/linux/tools/net/ynl/lib/nlspec.py (revision 7cf93538)
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