tools: ynl: use the common YAML loading and validation code

Adapt the common object hierarchy in code gen and CLI.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2023-01-30 18:33:44 -08:00
parent 3aacf82813
commit 30a5c6c810
2 changed files with 148 additions and 238 deletions

View file

@ -1,13 +1,14 @@
# SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: BSD-3-Clause
import functools import functools
import jsonschema
import os import os
import random import random
import socket import socket
import struct import struct
import yaml import yaml
from .nlspec import SpecFamily
# #
# Generic Netlink code which should really be in some library, but I can't quickly find one. # Generic Netlink code which should really be in some library, but I can't quickly find one.
# #
@ -158,8 +159,8 @@ class NlMsg:
# We don't have the ability to parse nests yet, so only do global # We don't have the ability to parse nests yet, so only do global
if 'miss-type' in self.extack and 'miss-nest' not in self.extack: if 'miss-type' in self.extack and 'miss-nest' not in self.extack:
miss_type = self.extack['miss-type'] miss_type = self.extack['miss-type']
if len(attr_space.attr_list) > miss_type: if miss_type in attr_space.attrs_by_val:
spec = attr_space.attr_list[miss_type] spec = attr_space.attrs_by_val[miss_type]
desc = spec['name'] desc = spec['name']
if 'doc' in spec: if 'doc' in spec:
desc += f" ({spec['doc']})" desc += f" ({spec['doc']})"
@ -289,100 +290,31 @@ class GenlFamily:
# #
class YnlAttrSpace: class YnlFamily(SpecFamily):
def __init__(self, family, yaml):
self.yaml = yaml
self.attrs = dict()
self.name = self.yaml['name']
self.subspace_of = self.yaml['subset-of'] if 'subspace-of' in self.yaml else None
val = 0
max_val = 0
for elem in self.yaml['attributes']:
if 'value' in elem:
val = elem['value']
else:
elem['value'] = val
if val > max_val:
max_val = val
val += 1
self.attrs[elem['name']] = elem
self.attr_list = [None] * (max_val + 1)
for elem in self.yaml['attributes']:
self.attr_list[elem['value']] = elem
def __getitem__(self, key):
return self.attrs[key]
def __contains__(self, key):
return key in self.yaml
def __iter__(self):
yield from self.attrs
def items(self):
return self.attrs.items()
class YnlFamily:
def __init__(self, def_path, schema=None): def __init__(self, def_path, schema=None):
super().__init__(def_path, schema)
self.include_raw = False self.include_raw = False
with open(def_path, "r") as stream:
self.yaml = yaml.safe_load(stream)
if schema:
with open(schema, "r") as stream:
schema = yaml.safe_load(stream)
jsonschema.validate(self.yaml, schema)
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC)
self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1)
self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1)
self._ops = dict()
self._spaces = dict()
self._types = dict() self._types = dict()
for elem in self.yaml['attribute-sets']:
self._spaces[elem['name']] = YnlAttrSpace(self, elem)
for elem in self.yaml['definitions']: for elem in self.yaml['definitions']:
self._types[elem['name']] = elem self._types[elem['name']] = elem
async_separation = 'async-prefix' in self.yaml['operations']
self.async_msg_ids = set() self.async_msg_ids = set()
self.async_msg_queue = [] self.async_msg_queue = []
val = 0
max_val = 0
for elem in self.yaml['operations']['list']:
if not (async_separation and ('notify' in elem or 'event' in elem)):
if 'value' in elem:
val = elem['value']
else:
elem['value'] = val
val += 1
max_val = max(val, max_val)
if 'notify' in elem or 'event' in elem: for msg in self.msgs.values():
self.async_msg_ids.add(elem['value']) if msg.is_async:
self.async_msg_ids.add(msg.value)
self._ops[elem['name']] = elem for op_name, op in self.ops.items():
bound_f = functools.partial(self._op, op_name)
op_name = elem['name'].replace('-', '_') setattr(self, op.ident_name, bound_f)
bound_f = functools.partial(self._op, elem['name'])
setattr(self, op_name, bound_f)
self._op_array = [None] * max_val
for _, op in self._ops.items():
self._op_array[op['value']] = op
if 'notify' in op:
op['attribute-set'] = self._ops[op['notify']]['attribute-set']
self.family = GenlFamily(self.yaml['name']) self.family = GenlFamily(self.yaml['name'])
@ -395,8 +327,8 @@ class YnlFamily:
self.family.genl_family['mcast'][mcast_name]) self.family.genl_family['mcast'][mcast_name])
def _add_attr(self, space, name, value): def _add_attr(self, space, name, value):
attr = self._spaces[space][name] attr = self.attr_sets[space][name]
nl_type = attr['value'] nl_type = attr.value
if attr["type"] == 'nest': if attr["type"] == 'nest':
nl_type |= Netlink.NLA_F_NESTED nl_type |= Netlink.NLA_F_NESTED
attr_payload = b'' attr_payload = b''
@ -430,10 +362,10 @@ class YnlFamily:
rsp[attr_spec['name']] = value rsp[attr_spec['name']] = value
def _decode(self, attrs, space): def _decode(self, attrs, space):
attr_space = self._spaces[space] attr_space = self.attr_sets[space]
rsp = dict() rsp = dict()
for attr in attrs: for attr in attrs:
attr_spec = attr_space.attr_list[attr.type] attr_spec = attr_space.attrs_by_val[attr.type]
if attr_spec["type"] == 'nest': if attr_spec["type"] == 'nest':
subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'])
rsp[attr_spec['name']] = subdict rsp[attr_spec['name']] = subdict
@ -457,9 +389,9 @@ class YnlFamily:
if self.include_raw: if self.include_raw:
msg['nlmsg'] = nl_msg msg['nlmsg'] = nl_msg
msg['genlmsg'] = genl_msg msg['genlmsg'] = genl_msg
op = self._op_array[genl_msg.genl_cmd] op = self.msgs_by_value[genl_msg.genl_cmd]
msg['name'] = op['name'] msg['name'] = op['name']
msg['msg'] = self._decode(genl_msg.raw_attrs, op['attribute-set']) msg['msg'] = self._decode(genl_msg.raw_attrs, op.attr_set.name)
self.async_msg_queue.append(msg) self.async_msg_queue.append(msg)
def check_ntf(self): def check_ntf(self):
@ -487,16 +419,16 @@ class YnlFamily:
self.handle_ntf(nl_msg, gm) self.handle_ntf(nl_msg, gm)
def _op(self, method, vals, dump=False): def _op(self, method, vals, dump=False):
op = self._ops[method] op = self.ops[method]
nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK
if dump: if dump:
nl_flags |= Netlink.NLM_F_DUMP nl_flags |= Netlink.NLM_F_DUMP
req_seq = random.randint(1024, 65535) req_seq = random.randint(1024, 65535)
msg = _genl_msg(self.family.family_id, nl_flags, op['value'], 1, req_seq) msg = _genl_msg(self.family.family_id, nl_flags, op.value, 1, req_seq)
for name, value in vals.items(): for name, value in vals.items():
msg += self._add_attr(op['attribute-set'], name, value) msg += self._add_attr(op.attr_set.name, name, value)
msg = _genl_msg_finalize(msg) msg = _genl_msg_finalize(msg)
self.sock.send(msg, 0) self.sock.send(msg, 0)
@ -505,7 +437,7 @@ class YnlFamily:
rsp = [] rsp = []
while not done: while not done:
reply = self.sock.recv(128 * 1024) reply = self.sock.recv(128 * 1024)
nms = NlMsgs(reply, attr_space=self._spaces[op['attribute-set']]) nms = NlMsgs(reply, attr_space=op.attr_set)
for nl_msg in nms: for nl_msg in nms:
if nl_msg.error: if nl_msg.error:
print("Netlink error:", os.strerror(-nl_msg.error)) print("Netlink error:", os.strerror(-nl_msg.error))
@ -517,7 +449,7 @@ class YnlFamily:
gm = GenlMsg(nl_msg) gm = GenlMsg(nl_msg)
# Check if this is a reply to our request # Check if this is a reply to our request
if nl_msg.nl_seq != req_seq or gm.genl_cmd != op['value']: if nl_msg.nl_seq != req_seq or gm.genl_cmd != op.value:
if gm.genl_cmd in self.async_msg_ids: if gm.genl_cmd in self.async_msg_ids:
self.handle_ntf(nl_msg, gm) self.handle_ntf(nl_msg, gm)
continue continue
@ -525,7 +457,7 @@ class YnlFamily:
print('Unexpected message: ' + repr(gm)) print('Unexpected message: ' + repr(gm))
continue continue
rsp.append(self._decode(gm.raw_attrs, op['attribute-set'])) rsp.append(self._decode(gm.raw_attrs, op.attr_set.name))
if not rsp: if not rsp:
return None return None

View file

@ -2,10 +2,11 @@
import argparse import argparse
import collections import collections
import jsonschema
import os import os
import yaml import yaml
from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation
def c_upper(name): def c_upper(name):
return name.upper().replace('-', '_') return name.upper().replace('-', '_')
@ -28,12 +29,12 @@ class BaseNlLib:
"ynl_cb_array, NLMSG_MIN_TYPE)" "ynl_cb_array, NLMSG_MIN_TYPE)"
class Type: class Type(SpecAttr):
def __init__(self, family, attr_set, attr): def __init__(self, family, attr_set, attr, value):
self.family = family super().__init__(family, attr_set, attr, value)
self.attr = attr self.attr = attr
self.value = attr['value'] self.attr_set = attr_set
self.name = c_lower(attr['name'])
self.type = attr['type'] self.type = attr['type']
self.checks = attr.get('checks', {}) self.checks = attr.get('checks', {})
@ -46,17 +47,17 @@ class Type:
else: else:
self.nested_render_name = f"{family.name}_{c_lower(self.nested_attrs)}" self.nested_render_name = f"{family.name}_{c_lower(self.nested_attrs)}"
self.enum_name = f"{attr_set.name_prefix}{self.name}"
self.enum_name = c_upper(self.enum_name)
self.c_name = c_lower(self.name) self.c_name = c_lower(self.name)
if self.c_name in _C_KW: if self.c_name in _C_KW:
self.c_name += '_' self.c_name += '_'
def __getitem__(self, key): # Added by resolve():
return self.attr[key] self.enum_name = None
delattr(self, "enum_name")
def __contains__(self, key): def resolve(self):
return key in self.attr self.enum_name = f"{self.attr_set.name_prefix}{self.name}"
self.enum_name = c_upper(self.enum_name)
def is_multi_val(self): def is_multi_val(self):
return None return None
@ -214,24 +215,34 @@ class TypePad(Type):
class TypeScalar(Type): class TypeScalar(Type):
def __init__(self, family, attr_set, attr): def __init__(self, family, attr_set, attr, value):
super().__init__(family, attr_set, attr) super().__init__(family, attr_set, attr, value)
self.is_bitfield = False
if 'enum' in self.attr:
self.is_bitfield = family.consts[self.attr['enum']]['type'] == 'flags'
if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']:
self.is_bitfield = True
if 'enum' in self.attr and not self.is_bitfield:
self.type_name = f"enum {family.name}_{c_lower(self.attr['enum'])}"
else:
self.type_name = '__' + self.type
self.byte_order_comment = '' self.byte_order_comment = ''
if 'byte-order' in attr: if 'byte-order' in attr:
self.byte_order_comment = f" /* {attr['byte-order']} */" self.byte_order_comment = f" /* {attr['byte-order']} */"
# Added by resolve():
self.is_bitfield = None
delattr(self, "is_bitfield")
self.type_name = None
delattr(self, "type_name")
def resolve(self):
self.resolve_up(super())
if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']:
self.is_bitfield = True
elif 'enum' in self.attr:
self.is_bitfield = self.family.consts[self.attr['enum']]['type'] == 'flags'
else:
self.is_bitfield = False
if 'enum' in self.attr and not self.is_bitfield:
self.type_name = f"enum {self.family.name}_{c_lower(self.attr['enum'])}"
else:
self.type_name = '__' + self.type
def _mnl_type(self): def _mnl_type(self):
t = self.type t = self.type
# mnl does not have a helper for signed types # mnl does not have a helper for signed types
@ -648,14 +659,11 @@ class EnumSet:
return mask return mask
class AttrSet: class AttrSet(SpecAttrSet):
def __init__(self, family, yaml): def __init__(self, family, yaml):
self.yaml = yaml super().__init__(family, yaml)
self.attrs = dict() if self.subset_of is None:
self.name = self.yaml['name']
if 'subset-of' not in yaml:
self.subset_of = None
if 'name-prefix' in yaml: if 'name-prefix' in yaml:
pfx = yaml['name-prefix'] pfx = yaml['name-prefix']
elif self.name == family.name: elif self.name == family.name:
@ -665,83 +673,68 @@ class AttrSet:
self.name_prefix = c_upper(pfx) self.name_prefix = c_upper(pfx)
self.max_name = c_upper(self.yaml.get('attr-max-name', f"{self.name_prefix}max")) self.max_name = c_upper(self.yaml.get('attr-max-name', f"{self.name_prefix}max"))
else: else:
self.subset_of = self.yaml['subset-of']
self.name_prefix = family.attr_sets[self.subset_of].name_prefix self.name_prefix = family.attr_sets[self.subset_of].name_prefix
self.max_name = family.attr_sets[self.subset_of].max_name self.max_name = family.attr_sets[self.subset_of].max_name
# Added by resolve:
self.c_name = None
delattr(self, "c_name")
def resolve(self):
self.c_name = c_lower(self.name) self.c_name = c_lower(self.name)
if self.c_name in _C_KW: if self.c_name in _C_KW:
self.c_name += '_' self.c_name += '_'
if self.c_name == family.c_name: if self.c_name == self.family.c_name:
self.c_name = '' self.c_name = ''
val = 0 def new_attr(self, elem, value):
for elem in self.yaml['attributes']: if 'multi-attr' in elem and elem['multi-attr']:
if 'value' in elem: return TypeMultiAttr(self.family, self, elem, value)
val = elem['value'] elif elem['type'] in scalars:
else: return TypeScalar(self.family, self, elem, value)
elem['value'] = val elif elem['type'] == 'unused':
val += 1 return TypeUnused(self.family, self, elem, value)
elif elem['type'] == 'pad':
if 'multi-attr' in elem and elem['multi-attr']: return TypePad(self.family, self, elem, value)
attr = TypeMultiAttr(family, self, elem) elif elem['type'] == 'flag':
elif elem['type'] in scalars: return TypeFlag(self.family, self, elem, value)
attr = TypeScalar(family, self, elem) elif elem['type'] == 'string':
elif elem['type'] == 'unused': return TypeString(self.family, self, elem, value)
attr = TypeUnused(family, self, elem) elif elem['type'] == 'binary':
elif elem['type'] == 'pad': return TypeBinary(self.family, self, elem, value)
attr = TypePad(family, self, elem) elif elem['type'] == 'nest':
elif elem['type'] == 'flag': return TypeNest(self.family, self, elem, value)
attr = TypeFlag(family, self, elem) elif elem['type'] == 'array-nest':
elif elem['type'] == 'string': return TypeArrayNest(self.family, self, elem, value)
attr = TypeString(family, self, elem) elif elem['type'] == 'nest-type-value':
elif elem['type'] == 'binary': return TypeNestTypeValue(self.family, self, elem, value)
attr = TypeBinary(family, self, elem)
elif elem['type'] == 'nest':
attr = TypeNest(family, self, elem)
elif elem['type'] == 'array-nest':
attr = TypeArrayNest(family, self, elem)
elif elem['type'] == 'nest-type-value':
attr = TypeNestTypeValue(family, self, elem)
else:
raise Exception(f"No typed class for type {elem['type']}")
self.attrs[elem['name']] = attr
def __getitem__(self, key):
return self.attrs[key]
def __contains__(self, key):
return key in self.yaml
def __iter__(self):
yield from self.attrs
def items(self):
return self.attrs.items()
class Operation:
def __init__(self, family, yaml, value):
self.yaml = yaml
self.value = value
self.name = self.yaml['name']
self.render_name = family.name + '_' + c_lower(self.name)
self.is_async = 'notify' in yaml or 'event' in yaml
if not self.is_async:
self.enum_name = family.op_prefix + c_upper(self.name)
else: else:
self.enum_name = family.async_op_prefix + c_upper(self.name) raise Exception(f"No typed class for type {elem['type']}")
class Operation(SpecOperation):
def __init__(self, family, yaml, req_value, rsp_value):
super().__init__(family, yaml, req_value, rsp_value)
if req_value != rsp_value:
raise Exception("Directional messages not supported by codegen")
self.render_name = family.name + '_' + c_lower(self.name)
self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \ self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \
('dump' in yaml and 'request' in yaml['dump']) ('dump' in yaml and 'request' in yaml['dump'])
def __getitem__(self, key): # Added by resolve:
return self.yaml[key] self.enum_name = None
delattr(self, "enum_name")
def __contains__(self, key): def resolve(self):
return key in self.yaml self.resolve_up(super())
if not self.is_async:
self.enum_name = self.family.op_prefix + c_upper(self.name)
else:
self.enum_name = self.family.async_op_prefix + c_upper(self.name)
def add_notification(self, op): def add_notification(self, op):
if 'notify' not in self.yaml: if 'notify' not in self.yaml:
@ -751,21 +744,23 @@ class Operation:
self.yaml['notify']['cmds'].append(op) self.yaml['notify']['cmds'].append(op)
class Family: class Family(SpecFamily):
def __init__(self, file_name): def __init__(self, file_name):
with open(file_name, "r") as stream: # Added by resolve:
self.yaml = yaml.safe_load(stream) self.c_name = None
delattr(self, "c_name")
self.op_prefix = None
delattr(self, "op_prefix")
self.async_op_prefix = None
delattr(self, "async_op_prefix")
self.mcgrps = None
delattr(self, "mcgrps")
self.consts = None
delattr(self, "consts")
self.hooks = None
delattr(self, "hooks")
self.proto = self.yaml.get('protocol', 'genetlink') super().__init__(file_name)
with open(os.path.dirname(os.path.dirname(file_name)) +
f'/{self.proto}.yaml', "r") as stream:
schema = yaml.safe_load(stream)
jsonschema.validate(self.yaml, schema)
if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}:
raise Exception("Codegen only supported for genetlink")
self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME')) self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME'))
self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION')) self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION'))
@ -773,12 +768,18 @@ class Family:
if 'definitions' not in self.yaml: if 'definitions' not in self.yaml:
self.yaml['definitions'] = [] self.yaml['definitions'] = []
self.name = self.yaml['name']
self.c_name = c_lower(self.name)
if 'uapi-header' in self.yaml: if 'uapi-header' in self.yaml:
self.uapi_header = self.yaml['uapi-header'] self.uapi_header = self.yaml['uapi-header']
else: else:
self.uapi_header = f"linux/{self.name}.h" self.uapi_header = f"linux/{self.name}.h"
def resolve(self):
self.resolve_up(super())
if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}:
raise Exception("Codegen only supported for genetlink")
self.c_name = c_lower(self.name)
if 'name-prefix' in self.yaml['operations']: if 'name-prefix' in self.yaml['operations']:
self.op_prefix = c_upper(self.yaml['operations']['name-prefix']) self.op_prefix = c_upper(self.yaml['operations']['name-prefix'])
else: else:
@ -791,12 +792,6 @@ class Family:
self.mcgrps = self.yaml.get('mcast-groups', {'list': []}) self.mcgrps = self.yaml.get('mcast-groups', {'list': []})
self.consts = dict() self.consts = dict()
# list of all operations
self.msg_list = []
# dict of operations which have their own message type (have attributes)
self.ops = collections.OrderedDict()
self.attr_sets = dict()
self.attr_sets_list = []
self.hooks = dict() self.hooks = dict()
for when in ['pre', 'post']: for when in ['pre', 'post']:
@ -824,11 +819,11 @@ class Family:
if self.kernel_policy == 'global': if self.kernel_policy == 'global':
self._load_global_policy() self._load_global_policy()
def __getitem__(self, key): def new_attr_set(self, elem):
return self.yaml[key] return AttrSet(self, elem)
def get(self, key, default=None): def new_operation(self, elem, req_value, rsp_value):
return self.yaml.get(key, default) return Operation(self, elem, req_value, rsp_value)
# Fake a 'do' equivalent of all events, so that we can render their response parsing # Fake a 'do' equivalent of all events, so that we can render their response parsing
def _mock_up_events(self): def _mock_up_events(self):
@ -847,27 +842,10 @@ class Family:
else: else:
self.consts[elem['name']] = elem self.consts[elem['name']] = elem
for elem in self.yaml['attribute-sets']:
attr_set = AttrSet(self, elem)
self.attr_sets[elem['name']] = attr_set
self.attr_sets_list.append((elem['name'], attr_set), )
ntf = [] ntf = []
val = 0 for msg in self.msgs.values():
for elem in self.yaml['operations']['list']: if 'notify' in msg:
if 'value' in elem: ntf.append(msg)
val = elem['value']
op = Operation(self, elem, val)
val += 1
self.msg_list.append(op)
if 'notify' in elem:
ntf.append(op)
continue
if 'attribute-set' not in elem:
continue
self.ops[elem['name']] = op
for n in ntf: for n in ntf:
self.ops[n['notify']].add_notification(n) self.ops[n['notify']].add_notification(n)
@ -2033,7 +2011,7 @@ def render_uapi(family, cw):
max_by_define = family.get('max-by-define', False) max_by_define = family.get('max-by-define', False)
for _, attr_set in family.attr_sets_list: for _, attr_set in family.attr_sets.items():
if attr_set.subset_of: if attr_set.subset_of:
continue continue
@ -2044,9 +2022,9 @@ def render_uapi(family, cw):
uapi_enum_start(family, cw, attr_set.yaml, 'enum-name') uapi_enum_start(family, cw, attr_set.yaml, 'enum-name')
for _, attr in attr_set.items(): for _, attr in attr_set.items():
suffix = ',' suffix = ','
if attr['value'] != val: if attr.value != val:
suffix = f" = {attr['value']}," suffix = f" = {attr.value},"
val = attr['value'] val = attr.value
val += 1 val += 1
cw.p(attr.enum_name + suffix) cw.p(attr.enum_name + suffix)
cw.nl() cw.nl()
@ -2066,7 +2044,7 @@ def render_uapi(family, cw):
max_value = f"({cnt_name} - 1)" max_value = f"({cnt_name} - 1)"
uapi_enum_start(family, cw, family['operations'], 'enum-name') uapi_enum_start(family, cw, family['operations'], 'enum-name')
for op in family.msg_list: for op in family.msgs.values():
if separate_ntf and ('notify' in op or 'event' in op): if separate_ntf and ('notify' in op or 'event' in op):
continue continue
@ -2085,7 +2063,7 @@ def render_uapi(family, cw):
if separate_ntf: if separate_ntf:
uapi_enum_start(family, cw, family['operations'], enum_name='async-enum') uapi_enum_start(family, cw, family['operations'], enum_name='async-enum')
for op in family.msg_list: for op in family.msgs.values():
if separate_ntf and not ('notify' in op or 'event' in op): if separate_ntf and not ('notify' in op or 'event' in op):
continue continue