Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'tools-ynl-more-docs-and-basic-ethtool-support'

Jakub Kicinski says:

====================
tools: ynl: more docs and basic ethtool support

I got discouraged from supporting ethtool in specs, because
generating the user space C code seems a little tricky.
The messages are ID'ed in a "directional" way (to and from
kernel are separate ID "spaces"). There is value, however,
in having the spec and being able to for example use it
in Python.

After paying off some technical debt - add a partial
ethtool spec. Partial because the header for ethtool is almost
a 1000 LoC, so converting in one sitting is tough. But adding
new commands should be trivial now.

Last but not least I add more docs, I realized that I've been
sending a similar "instructions" email to people working on
new families. It's now intro-specs.rst.
====================

Link: https://lore.kernel.org/r/20230131023354.1732677-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+1115 -262
+1 -3
Documentation/netlink/genetlink.yaml
··· 188 188 to a single enum. 189 189 "directional" has the messages sent to the kernel and from the kernel 190 190 enumerated separately. 191 - "notify-split" has the notifications and request-response types in 192 - different enums. 193 - enum: [ unified, directional, notify-split ] 191 + enum: [ unified ] 194 192 name-prefix: 195 193 description: | 196 194 Prefix for the C enum name of the command. The name is formed by concatenating
+392
Documentation/netlink/specs/ethtool.yaml
··· 1 + name: ethtool 2 + 3 + protocol: genetlink-legacy 4 + 5 + doc: Partial family for Ethtool Netlink. 6 + 7 + attribute-sets: 8 + - 9 + name: header 10 + attributes: 11 + - 12 + name: dev-index 13 + type: u32 14 + value: 1 15 + - 16 + name: dev-name 17 + type: string 18 + - 19 + name: flags 20 + type: u32 21 + 22 + - 23 + name: bitset-bit 24 + attributes: 25 + - 26 + name: index 27 + type: u32 28 + value: 1 29 + - 30 + name: name 31 + type: string 32 + - 33 + name: value 34 + type: flag 35 + - 36 + name: bitset-bits 37 + attributes: 38 + - 39 + name: bit 40 + type: nest 41 + nested-attributes: bitset-bit 42 + value: 1 43 + - 44 + name: bitset 45 + attributes: 46 + - 47 + name: nomask 48 + type: flag 49 + value: 1 50 + - 51 + name: size 52 + type: u32 53 + - 54 + name: bits 55 + type: nest 56 + nested-attributes: bitset-bits 57 + 58 + - 59 + name: string 60 + attributes: 61 + - 62 + name: index 63 + type: u32 64 + value: 1 65 + - 66 + name: value 67 + type: string 68 + - 69 + name: strings 70 + attributes: 71 + - 72 + name: string 73 + type: nest 74 + value: 1 75 + multi-attr: true 76 + nested-attributes: string 77 + - 78 + name: stringset 79 + attributes: 80 + - 81 + name: id 82 + type: u32 83 + value: 1 84 + - 85 + name: count 86 + type: u32 87 + - 88 + name: strings 89 + type: nest 90 + multi-attr: true 91 + nested-attributes: strings 92 + - 93 + name: stringsets 94 + attributes: 95 + - 96 + name: stringset 97 + type: nest 98 + multi-attr: true 99 + value: 1 100 + nested-attributes: stringset 101 + - 102 + name: strset 103 + attributes: 104 + - 105 + name: header 106 + value: 1 107 + type: nest 108 + nested-attributes: header 109 + - 110 + name: stringsets 111 + type: nest 112 + nested-attributes: stringsets 113 + - 114 + name: counts-only 115 + type: flag 116 + 117 + - 118 + name: privflags 119 + attributes: 120 + - 121 + name: header 122 + value: 1 123 + type: nest 124 + nested-attributes: header 125 + - 126 + name: flags 127 + type: nest 128 + nested-attributes: bitset 129 + 130 + - 131 + name: rings 132 + attributes: 133 + - 134 + name: header 135 + value: 1 136 + type: nest 137 + nested-attributes: header 138 + - 139 + name: rx-max 140 + type: u32 141 + - 142 + name: rx-mini-max 143 + type: u32 144 + - 145 + name: rx-jumbo-max 146 + type: u32 147 + - 148 + name: tx-max 149 + type: u32 150 + - 151 + name: rx 152 + type: u32 153 + - 154 + name: rx-mini 155 + type: u32 156 + - 157 + name: rx-jumbo 158 + type: u32 159 + - 160 + name: tx 161 + type: u32 162 + - 163 + name: rx-buf-len 164 + type: u32 165 + - 166 + name: tcp-data-split 167 + type: u8 168 + - 169 + name: cqe-size 170 + type: u32 171 + - 172 + name: tx-push 173 + type: u8 174 + 175 + - 176 + name: mm-stat 177 + attributes: 178 + - 179 + name: pad 180 + value: 1 181 + type: pad 182 + - 183 + name: reassembly-errors 184 + type: u64 185 + - 186 + name: smd-errors 187 + type: u64 188 + - 189 + name: reassembly-ok 190 + type: u64 191 + - 192 + name: rx-frag-count 193 + type: u64 194 + - 195 + name: tx-frag-count 196 + type: u64 197 + - 198 + name: hold-count 199 + type: u64 200 + - 201 + name: mm 202 + attributes: 203 + - 204 + name: header 205 + value: 1 206 + type: nest 207 + nested-attributes: header 208 + - 209 + name: pmac-enabled 210 + type: u8 211 + - 212 + name: tx-enabled 213 + type: u8 214 + - 215 + name: tx-active 216 + type: u8 217 + - 218 + name: tx-min-frag-size 219 + type: u32 220 + - 221 + name: tx-min-frag-size 222 + type: u32 223 + - 224 + name: verify-enabled 225 + type: u8 226 + - 227 + name: verify-status 228 + type: u8 229 + - 230 + name: verify-time 231 + type: u32 232 + - 233 + name: max-verify-time 234 + type: u32 235 + - 236 + name: stats 237 + type: nest 238 + nested-attributes: mm-stat 239 + 240 + operations: 241 + enum-model: directional 242 + list: 243 + - 244 + name: strset-get 245 + doc: Get string set from the kernel. 246 + 247 + attribute-set: strset 248 + 249 + do: &strset-get-op 250 + request: 251 + value: 1 252 + attributes: 253 + - header 254 + - stringsets 255 + - counts-only 256 + reply: 257 + value: 1 258 + attributes: 259 + - header 260 + - stringsets 261 + dump: *strset-get-op 262 + 263 + # TODO: fill in the requests in between 264 + 265 + - 266 + name: privflags-get 267 + doc: Get device private flags. 268 + 269 + attribute-set: privflags 270 + 271 + do: &privflag-get-op 272 + request: 273 + value: 13 274 + attributes: 275 + - header 276 + reply: 277 + value: 14 278 + attributes: 279 + - header 280 + - flags 281 + dump: *privflag-get-op 282 + - 283 + name: privflags-set 284 + doc: Set device private flags. 285 + 286 + attribute-set: privflags 287 + 288 + do: 289 + request: 290 + attributes: 291 + - header 292 + - flags 293 + - 294 + name: privflags-ntf 295 + doc: Notification for change in device private flags. 296 + notify: privflags-get 297 + 298 + - 299 + name: rings-get 300 + doc: Get ring params. 301 + 302 + attribute-set: rings 303 + 304 + do: &ring-get-op 305 + request: 306 + attributes: 307 + - header 308 + reply: 309 + attributes: 310 + - header 311 + - rx-max 312 + - rx-mini-max 313 + - rx-jumbo-max 314 + - tx-max 315 + - rx 316 + - rx-mini 317 + - rx-jumbo 318 + - tx 319 + - rx-buf-len 320 + - tcp-data-split 321 + - cqe-size 322 + - tx-push 323 + dump: *ring-get-op 324 + - 325 + name: rings-set 326 + doc: Set ring params. 327 + 328 + attribute-set: rings 329 + 330 + do: 331 + request: 332 + attributes: 333 + - header 334 + - rx 335 + - rx-mini 336 + - rx-jumbo 337 + - tx 338 + - rx-buf-len 339 + - tcp-data-split 340 + - cqe-size 341 + - tx-push 342 + - 343 + name: rings-ntf 344 + doc: Notification for change in ring params. 345 + notify: rings-get 346 + 347 + # TODO: fill in the requests in between 348 + 349 + - 350 + name: mm-get 351 + doc: Get MAC Merge configuration and state 352 + 353 + attribute-set: mm 354 + 355 + do: &mm-get-op 356 + request: 357 + value: 42 358 + attributes: 359 + - header 360 + reply: 361 + value: 42 362 + attributes: 363 + - header 364 + - pmac-enabled 365 + - tx-enabled 366 + - tx-active 367 + - tx-min-frag-size 368 + - rx-min-frag-size 369 + - verify-enabled 370 + - verify-time 371 + - max-verify-time 372 + - stats 373 + dump: *mm-get-op 374 + - 375 + name: mm-set 376 + doc: Set MAC Merge configuration 377 + 378 + attribute-set: mm 379 + 380 + do: 381 + request: 382 + attributes: 383 + - header 384 + - verify-enabled 385 + - verify-time 386 + - tx-enabled 387 + - pmac-enabled 388 + - tx-min-frag-size 389 + - 390 + name: mm-ntf 391 + doc: Notification for change in MAC Merge configuration. 392 + notify: mm-get
+1
Documentation/userspace-api/netlink/index.rst
··· 10 10 :maxdepth: 2 11 11 12 12 intro 13 + intro-specs 13 14 specs 14 15 c-code-gen 15 16 genetlink-legacy
+80
Documentation/userspace-api/netlink/intro-specs.rst
··· 1 + .. SPDX-License-Identifier: BSD-3-Clause 2 + 3 + ===================================== 4 + Using Netlink protocol specifications 5 + ===================================== 6 + 7 + This document is a quick starting guide for using Netlink protocol 8 + specifications. For more detailed description of the specs see :doc:`specs`. 9 + 10 + Simple CLI 11 + ========== 12 + 13 + Kernel comes with a simple CLI tool which should be useful when 14 + developing Netlink related code. The tool is implemented in Python 15 + and can use a YAML specification to issue Netlink requests 16 + to the kernel. Only Generic Netlink is supported. 17 + 18 + The tool is located at ``tools/net/ynl/cli.py``. It accepts 19 + a handul of arguments, the most important ones are: 20 + 21 + - ``--spec`` - point to the spec file 22 + - ``--do $name`` / ``--dump $name`` - issue request ``$name`` 23 + - ``--json $attrs`` - provide attributes for the request 24 + - ``--subscribe $group`` - receive notifications from ``$group`` 25 + 26 + YAML specs can be found under ``Documentation/netlink/specs/``. 27 + 28 + Example use:: 29 + 30 + $ ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/ethtool.yaml \ 31 + --do rings-get \ 32 + --json '{"header":{"dev-index": 18}}' 33 + {'header': {'dev-index': 18, 'dev-name': 'eni1np1'}, 34 + 'rx': 0, 35 + 'rx-jumbo': 0, 36 + 'rx-jumbo-max': 4096, 37 + 'rx-max': 4096, 38 + 'rx-mini': 0, 39 + 'rx-mini-max': 4096, 40 + 'tx': 0, 41 + 'tx-max': 4096, 42 + 'tx-push': 0} 43 + 44 + The input arguments are parsed as JSON, while the output is only 45 + Python-pretty-printed. This is because some Netlink types can't 46 + be expressed as JSON directly. If such attributes are needed in 47 + the input some hacking of the script will be necessary. 48 + 49 + The spec and Netlink internals are factored out as a standalone 50 + library - it should be easy to write Python tools / tests reusing 51 + code from ``cli.py``. 52 + 53 + Generating kernel code 54 + ====================== 55 + 56 + ``tools/net/ynl/ynl-regen.sh`` scans the kernel tree in search of 57 + auto-generated files which need to be updated. Using this tool is the easiest 58 + way to generate / update auto-generated code. 59 + 60 + By default code is re-generated only if spec is newer than the source, 61 + to force regeneration use ``-f``. 62 + 63 + ``ynl-regen.sh`` searches for ``YNL-GEN`` in the contents of files 64 + (note that it only scans files in the git index, that is only files 65 + tracked by git!) For instance the ``fou_nl.c`` kernel source contains:: 66 + 67 + /* Documentation/netlink/specs/fou.yaml */ 68 + /* YNL-GEN kernel source */ 69 + 70 + ``ynl-regen.sh`` will find this marker and replace the file with 71 + kernel source based on fou.yaml. 72 + 73 + The simplest way to generate a new file based on a spec is to add 74 + the two marker lines like above to a file, add that file to git, 75 + and run the regeneration tool. Grep the tree for ``YNL-GEN`` 76 + to see other examples. 77 + 78 + The code generation itself is performed by ``tools/net/ynl/ynl-gen-c.py`` 79 + but it takes a few arguments so calling it directly for each file 80 + quickly becomes tedious.
+3
Documentation/userspace-api/netlink/specs.rst
··· 21 21 22 22 YAML specifications can be found under ``Documentation/netlink/specs/`` 23 23 24 + This document describes details of the schema. 25 + See :doc:`intro-specs` for a practical starting guide. 26 + 24 27 Compatibility levels 25 28 ==================== 26 29
+7
tools/net/ynl/lib/__init__.py
··· 1 + # SPDX-License-Identifier: BSD-3-Clause 2 + 3 + from .nlspec import SpecAttr, SpecAttrSet, SpecFamily, SpecOperation 4 + from .ynl import YnlFamily 5 + 6 + __all__ = ["SpecAttr", "SpecAttrSet", "SpecFamily", "SpecOperation", 7 + "YnlFamily"]
+310
tools/net/ynl/lib/nlspec.py
··· 1 + # SPDX-License-Identifier: BSD-3-Clause 2 + 3 + import collections 4 + import importlib 5 + import os 6 + import traceback 7 + import yaml 8 + 9 + 10 + # To be loaded dynamically as needed 11 + jsonschema = None 12 + 13 + 14 + class SpecElement: 15 + """Netlink spec element. 16 + 17 + Abstract element of the Netlink spec. Implements the dictionary interface 18 + for access to the raw spec. Supports iterative resolution of dependencies 19 + across elements and class inheritance levels. The elements of the spec 20 + may refer to each other, and although loops should be very rare, having 21 + to maintain correct ordering of instantiation is painful, so the resolve() 22 + method should be used to perform parts of init which require access to 23 + other parts of the spec. 24 + 25 + Attributes: 26 + yaml raw spec as loaded from the spec file 27 + family back reference to the full family 28 + 29 + name name of the entity as listed in the spec (optional) 30 + ident_name name which can be safely used as identifier in code (optional) 31 + """ 32 + def __init__(self, family, yaml): 33 + self.yaml = yaml 34 + self.family = family 35 + 36 + if 'name' in self.yaml: 37 + self.name = self.yaml['name'] 38 + self.ident_name = self.name.replace('-', '_') 39 + 40 + self._super_resolved = False 41 + family.add_unresolved(self) 42 + 43 + def __getitem__(self, key): 44 + return self.yaml[key] 45 + 46 + def __contains__(self, key): 47 + return key in self.yaml 48 + 49 + def get(self, key, default=None): 50 + return self.yaml.get(key, default) 51 + 52 + def resolve_up(self, up): 53 + if not self._super_resolved: 54 + up.resolve() 55 + self._super_resolved = True 56 + 57 + def resolve(self): 58 + pass 59 + 60 + 61 + class SpecAttr(SpecElement): 62 + """ Single Netlink atttribute type 63 + 64 + Represents a single attribute type within an attr space. 65 + 66 + Attributes: 67 + value numerical ID when serialized 68 + attr_set Attribute Set containing this attr 69 + """ 70 + def __init__(self, family, attr_set, yaml, value): 71 + super().__init__(family, yaml) 72 + 73 + self.value = value 74 + self.attr_set = attr_set 75 + self.is_multi = yaml.get('multi-attr', False) 76 + 77 + 78 + class SpecAttrSet(SpecElement): 79 + """ Netlink Attribute Set class. 80 + 81 + Represents a ID space of attributes within Netlink. 82 + 83 + Note that unlike other elements, which expose contents of the raw spec 84 + via the dictionary interface Attribute Set exposes attributes by name. 85 + 86 + Attributes: 87 + attrs ordered dict of all attributes (indexed by name) 88 + attrs_by_val ordered dict of all attributes (indexed by value) 89 + subset_of parent set if this is a subset, otherwise None 90 + """ 91 + def __init__(self, family, yaml): 92 + super().__init__(family, yaml) 93 + 94 + self.subset_of = self.yaml.get('subset-of', None) 95 + 96 + self.attrs = collections.OrderedDict() 97 + self.attrs_by_val = collections.OrderedDict() 98 + 99 + val = 0 100 + for elem in self.yaml['attributes']: 101 + if 'value' in elem: 102 + val = elem['value'] 103 + 104 + attr = self.new_attr(elem, val) 105 + self.attrs[attr.name] = attr 106 + self.attrs_by_val[attr.value] = attr 107 + val += 1 108 + 109 + def new_attr(self, elem, value): 110 + return SpecAttr(self.family, self, elem, value) 111 + 112 + def __getitem__(self, key): 113 + return self.attrs[key] 114 + 115 + def __contains__(self, key): 116 + return key in self.attrs 117 + 118 + def __iter__(self): 119 + yield from self.attrs 120 + 121 + def items(self): 122 + return self.attrs.items() 123 + 124 + 125 + class SpecOperation(SpecElement): 126 + """Netlink Operation 127 + 128 + Information about a single Netlink operation. 129 + 130 + Attributes: 131 + value numerical ID when serialized, None if req/rsp values differ 132 + 133 + req_value numerical ID when serialized, user -> kernel 134 + rsp_value numerical ID when serialized, user <- kernel 135 + is_call bool, whether the operation is a call 136 + is_async bool, whether the operation is a notification 137 + is_resv bool, whether the operation does not exist (it's just a reserved ID) 138 + attr_set attribute set name 139 + 140 + yaml raw spec as loaded from the spec file 141 + """ 142 + def __init__(self, family, yaml, req_value, rsp_value): 143 + super().__init__(family, yaml) 144 + 145 + self.value = req_value if req_value == rsp_value else None 146 + self.req_value = req_value 147 + self.rsp_value = rsp_value 148 + 149 + self.is_call = 'do' in yaml or 'dump' in yaml 150 + self.is_async = 'notify' in yaml or 'event' in yaml 151 + self.is_resv = not self.is_async and not self.is_call 152 + 153 + # Added by resolve: 154 + self.attr_set = None 155 + delattr(self, "attr_set") 156 + 157 + def resolve(self): 158 + self.resolve_up(super()) 159 + 160 + if 'attribute-set' in self.yaml: 161 + attr_set_name = self.yaml['attribute-set'] 162 + elif 'notify' in self.yaml: 163 + msg = self.family.msgs[self.yaml['notify']] 164 + attr_set_name = msg['attribute-set'] 165 + elif self.is_resv: 166 + attr_set_name = '' 167 + else: 168 + raise Exception(f"Can't resolve attribute set for op '{self.name}'") 169 + if attr_set_name: 170 + self.attr_set = self.family.attr_sets[attr_set_name] 171 + 172 + 173 + class SpecFamily(SpecElement): 174 + """ Netlink Family Spec class. 175 + 176 + Netlink family information loaded from a spec (e.g. in YAML). 177 + Takes care of unfolding implicit information which can be skipped 178 + in the spec itself for brevity. 179 + 180 + The class can be used like a dictionary to access the raw spec 181 + elements but that's usually a bad idea. 182 + 183 + Attributes: 184 + proto protocol type (e.g. genetlink) 185 + 186 + attr_sets dict of attribute sets 187 + msgs dict of all messages (index by name) 188 + msgs_by_value dict of all messages (indexed by name) 189 + ops dict of all valid requests / responses 190 + """ 191 + def __init__(self, spec_path, schema_path=None): 192 + with open(spec_path, "r") as stream: 193 + spec = yaml.safe_load(stream) 194 + 195 + self._resolution_list = [] 196 + 197 + super().__init__(self, spec) 198 + 199 + self.proto = self.yaml.get('protocol', 'genetlink') 200 + 201 + if schema_path is None: 202 + schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' 203 + if schema_path: 204 + global jsonschema 205 + 206 + with open(schema_path, "r") as stream: 207 + schema = yaml.safe_load(stream) 208 + 209 + if jsonschema is None: 210 + jsonschema = importlib.import_module("jsonschema") 211 + 212 + jsonschema.validate(self.yaml, schema) 213 + 214 + self.attr_sets = collections.OrderedDict() 215 + self.msgs = collections.OrderedDict() 216 + self.req_by_value = collections.OrderedDict() 217 + self.rsp_by_value = collections.OrderedDict() 218 + self.ops = collections.OrderedDict() 219 + 220 + last_exception = None 221 + while len(self._resolution_list) > 0: 222 + resolved = [] 223 + unresolved = self._resolution_list 224 + self._resolution_list = [] 225 + 226 + for elem in unresolved: 227 + try: 228 + elem.resolve() 229 + except (KeyError, AttributeError) as e: 230 + self._resolution_list.append(elem) 231 + last_exception = e 232 + continue 233 + 234 + resolved.append(elem) 235 + 236 + if len(resolved) == 0: 237 + traceback.print_exception(last_exception) 238 + raise Exception("Could not resolve any spec element, infinite loop?") 239 + 240 + def new_attr_set(self, elem): 241 + return SpecAttrSet(self, elem) 242 + 243 + def new_operation(self, elem, req_val, rsp_val): 244 + return SpecOperation(self, elem, req_val, rsp_val) 245 + 246 + def add_unresolved(self, elem): 247 + self._resolution_list.append(elem) 248 + 249 + def _dictify_ops_unified(self): 250 + val = 0 251 + for elem in self.yaml['operations']['list']: 252 + if 'value' in elem: 253 + val = elem['value'] 254 + 255 + op = self.new_operation(elem, val, val) 256 + val += 1 257 + 258 + self.msgs[op.name] = op 259 + 260 + def _dictify_ops_directional(self): 261 + req_val = rsp_val = 0 262 + for elem in self.yaml['operations']['list']: 263 + if 'notify' in elem: 264 + if 'value' in elem: 265 + rsp_val = elem['value'] 266 + req_val_next = req_val 267 + rsp_val_next = rsp_val + 1 268 + req_val = None 269 + elif 'do' in elem or 'dump' in elem: 270 + mode = elem['do'] if 'do' in elem else elem['dump'] 271 + 272 + v = mode.get('request', {}).get('value', None) 273 + if v: 274 + req_val = v 275 + v = mode.get('reply', {}).get('value', None) 276 + if v: 277 + rsp_val = v 278 + 279 + rsp_inc = 1 if 'reply' in mode else 0 280 + req_val_next = req_val + 1 281 + rsp_val_next = rsp_val + rsp_inc 282 + else: 283 + raise Exception("Can't parse directional ops") 284 + 285 + op = self.new_operation(elem, req_val, rsp_val) 286 + req_val = req_val_next 287 + rsp_val = rsp_val_next 288 + 289 + self.msgs[op.name] = op 290 + 291 + def resolve(self): 292 + self.resolve_up(super()) 293 + 294 + for elem in self.yaml['attribute-sets']: 295 + attr_set = self.new_attr_set(elem) 296 + self.attr_sets[elem['name']] = attr_set 297 + 298 + msg_id_model = self.yaml['operations'].get('enum-model', 'unified') 299 + if msg_id_model == 'unified': 300 + self._dictify_ops_unified() 301 + elif msg_id_model == 'directional': 302 + self._dictify_ops_directional() 303 + 304 + for op in self.msgs.values(): 305 + if op.req_value is not None: 306 + self.req_by_value[op.req_value] = op 307 + if op.rsp_value is not None: 308 + self.rsp_by_value[op.rsp_value] = op 309 + if not op.is_async and 'attribute-set' in op: 310 + self.ops[op.name] = op
+11 -6
tools/net/ynl/samples/cli.py tools/net/ynl/cli.py
··· 1 - #!/usr/bin/env python 1 + #!/usr/bin/env python3 2 2 # SPDX-License-Identifier: BSD-3-Clause 3 3 4 4 import argparse ··· 6 6 import pprint 7 7 import time 8 8 9 - from ynl import YnlFamily 9 + from lib import YnlFamily 10 10 11 11 12 12 def main(): 13 13 parser = argparse.ArgumentParser(description='YNL CLI sample') 14 14 parser.add_argument('--spec', dest='spec', type=str, required=True) 15 15 parser.add_argument('--schema', dest='schema', type=str) 16 + parser.add_argument('--no-schema', action='store_true') 16 17 parser.add_argument('--json', dest='json_text', type=str) 17 18 parser.add_argument('--do', dest='do', type=str) 18 19 parser.add_argument('--dump', dest='dump', type=str) 19 20 parser.add_argument('--sleep', dest='sleep', type=int) 20 21 parser.add_argument('--subscribe', dest='ntf', type=str) 21 22 args = parser.parse_args() 23 + 24 + if args.no_schema: 25 + args.schema = '' 22 26 23 27 attrs = {} 24 28 if args.json_text: ··· 36 32 if args.sleep: 37 33 time.sleep(args.sleep) 38 34 39 - if args.do or args.dump: 40 - method = getattr(ynl, args.do if args.do else args.dump) 41 - 42 - reply = method(attrs, dump=bool(args.dump)) 35 + if args.do: 36 + reply = ynl.do(args.do, attrs) 37 + pprint.PrettyPrinter().pprint(reply) 38 + if args.dump: 39 + reply = ynl.dump(args.dump, attrs) 43 40 pprint.PrettyPrinter().pprint(reply) 44 41 45 42 if args.ntf:
+93 -99
tools/net/ynl/samples/ynl.py tools/net/ynl/lib/ynl.py
··· 1 1 # SPDX-License-Identifier: BSD-3-Clause 2 2 3 3 import functools 4 - import jsonschema 5 4 import os 6 5 import random 7 6 import socket 8 7 import struct 9 8 import yaml 9 + 10 + from .nlspec import SpecFamily 10 11 11 12 # 12 13 # Generic Netlink code which should really be in some library, but I can't quickly find one. ··· 74 73 self.payload_len = self._len 75 74 self.full_len = (self.payload_len + 3) & ~3 76 75 self.raw = raw[offset + 4:offset + self.payload_len] 76 + 77 + def as_u8(self): 78 + return struct.unpack("B", self.raw)[0] 77 79 78 80 def as_u16(self): 79 81 return struct.unpack("H", self.raw)[0] ··· 162 158 # We don't have the ability to parse nests yet, so only do global 163 159 if 'miss-type' in self.extack and 'miss-nest' not in self.extack: 164 160 miss_type = self.extack['miss-type'] 165 - if len(attr_space.attr_list) > miss_type: 166 - spec = attr_space.attr_list[miss_type] 161 + if miss_type in attr_space.attrs_by_val: 162 + spec = attr_space.attrs_by_val[miss_type] 167 163 desc = spec['name'] 168 164 if 'doc' in spec: 169 165 desc += f" ({spec['doc']})" ··· 293 289 # 294 290 295 291 296 - class YnlAttrSpace: 297 - def __init__(self, family, yaml): 298 - self.yaml = yaml 299 - 300 - self.attrs = dict() 301 - self.name = self.yaml['name'] 302 - self.subspace_of = self.yaml['subset-of'] if 'subspace-of' in self.yaml else None 303 - 304 - val = 0 305 - max_val = 0 306 - for elem in self.yaml['attributes']: 307 - if 'value' in elem: 308 - val = elem['value'] 309 - else: 310 - elem['value'] = val 311 - if val > max_val: 312 - max_val = val 313 - val += 1 314 - 315 - self.attrs[elem['name']] = elem 316 - 317 - self.attr_list = [None] * (max_val + 1) 318 - for elem in self.yaml['attributes']: 319 - self.attr_list[elem['value']] = elem 320 - 321 - def __getitem__(self, key): 322 - return self.attrs[key] 323 - 324 - def __contains__(self, key): 325 - return key in self.yaml 326 - 327 - def __iter__(self): 328 - yield from self.attrs 329 - 330 - def items(self): 331 - return self.attrs.items() 332 - 333 - 334 - class YnlFamily: 292 + class YnlFamily(SpecFamily): 335 293 def __init__(self, def_path, schema=None): 294 + super().__init__(def_path, schema) 295 + 336 296 self.include_raw = False 337 - 338 - with open(def_path, "r") as stream: 339 - self.yaml = yaml.safe_load(stream) 340 - 341 - if schema: 342 - with open(schema, "r") as stream: 343 - schema = yaml.safe_load(stream) 344 - 345 - jsonschema.validate(self.yaml, schema) 346 297 347 298 self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, Netlink.NETLINK_GENERIC) 348 299 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) 349 300 self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) 350 301 351 - self._ops = dict() 352 - self._spaces = dict() 353 302 self._types = dict() 354 303 355 - for elem in self.yaml['attribute-sets']: 356 - self._spaces[elem['name']] = YnlAttrSpace(self, elem) 357 - 358 - for elem in self.yaml['definitions']: 304 + for elem in self.yaml.get('definitions', []): 359 305 self._types[elem['name']] = elem 360 306 361 - async_separation = 'async-prefix' in self.yaml['operations'] 362 307 self.async_msg_ids = set() 363 308 self.async_msg_queue = [] 364 - val = 0 365 - max_val = 0 366 - for elem in self.yaml['operations']['list']: 367 - if not (async_separation and ('notify' in elem or 'event' in elem)): 368 - if 'value' in elem: 369 - val = elem['value'] 370 - else: 371 - elem['value'] = val 372 - val += 1 373 - max_val = max(val, max_val) 374 309 375 - if 'notify' in elem or 'event' in elem: 376 - self.async_msg_ids.add(elem['value']) 310 + for msg in self.msgs.values(): 311 + if msg.is_async: 312 + self.async_msg_ids.add(msg.rsp_value) 377 313 378 - self._ops[elem['name']] = elem 379 - 380 - op_name = elem['name'].replace('-', '_') 381 - 382 - bound_f = functools.partial(self._op, elem['name']) 383 - setattr(self, op_name, bound_f) 384 - 385 - self._op_array = [None] * max_val 386 - for _, op in self._ops.items(): 387 - self._op_array[op['value']] = op 388 - if 'notify' in op: 389 - op['attribute-set'] = self._ops[op['notify']]['attribute-set'] 314 + for op_name, op in self.ops.items(): 315 + bound_f = functools.partial(self._op, op_name) 316 + setattr(self, op.ident_name, bound_f) 390 317 391 318 self.family = GenlFamily(self.yaml['name']) 392 319 ··· 330 395 self.family.genl_family['mcast'][mcast_name]) 331 396 332 397 def _add_attr(self, space, name, value): 333 - attr = self._spaces[space][name] 334 - nl_type = attr['value'] 398 + attr = self.attr_sets[space][name] 399 + nl_type = attr.value 335 400 if attr["type"] == 'nest': 336 401 nl_type |= Netlink.NLA_F_NESTED 337 402 attr_payload = b'' 338 403 for subname, subvalue in value.items(): 339 404 attr_payload += self._add_attr(attr['nested-attributes'], subname, subvalue) 405 + elif attr["type"] == 'flag': 406 + attr_payload = b'' 340 407 elif attr["type"] == 'u32': 341 408 attr_payload = struct.pack("I", int(value)) 342 409 elif attr["type"] == 'string': ··· 367 430 rsp[attr_spec['name']] = value 368 431 369 432 def _decode(self, attrs, space): 370 - attr_space = self._spaces[space] 433 + attr_space = self.attr_sets[space] 371 434 rsp = dict() 372 435 for attr in attrs: 373 - attr_spec = attr_space.attr_list[attr.type] 436 + attr_spec = attr_space.attrs_by_val[attr.type] 374 437 if attr_spec["type"] == 'nest': 375 438 subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes']) 376 - rsp[attr_spec['name']] = subdict 439 + decoded = subdict 440 + elif attr_spec['type'] == 'u8': 441 + decoded = attr.as_u8() 377 442 elif attr_spec['type'] == 'u32': 378 - rsp[attr_spec['name']] = attr.as_u32() 443 + decoded = attr.as_u32() 379 444 elif attr_spec['type'] == 'u64': 380 - rsp[attr_spec['name']] = attr.as_u64() 445 + decoded = attr.as_u64() 381 446 elif attr_spec["type"] == 'string': 382 - rsp[attr_spec['name']] = attr.as_strz() 447 + decoded = attr.as_strz() 383 448 elif attr_spec["type"] == 'binary': 384 - rsp[attr_spec['name']] = attr.as_bin() 449 + decoded = attr.as_bin() 450 + elif attr_spec["type"] == 'flag': 451 + decoded = True 385 452 else: 386 453 raise Exception(f'Unknown {attr.type} {attr_spec["name"]} {attr_spec["type"]}') 454 + 455 + if not attr_spec.is_multi: 456 + rsp[attr_spec['name']] = decoded 457 + elif attr_spec.name in rsp: 458 + rsp[attr_spec.name].append(decoded) 459 + else: 460 + rsp[attr_spec.name] = [decoded] 387 461 388 462 if 'enum' in attr_spec: 389 463 self._decode_enum(rsp, attr_spec) 390 464 return rsp 465 + 466 + def _decode_extack_path(self, attrs, attr_set, offset, target): 467 + for attr in attrs: 468 + attr_spec = attr_set.attrs_by_val[attr.type] 469 + if offset > target: 470 + break 471 + if offset == target: 472 + return '.' + attr_spec.name 473 + 474 + if offset + attr.full_len <= target: 475 + offset += attr.full_len 476 + continue 477 + if attr_spec['type'] != 'nest': 478 + raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") 479 + offset += 4 480 + subpath = self._decode_extack_path(NlAttrs(attr.raw), 481 + self.attr_sets[attr_spec['nested-attributes']], 482 + offset, target) 483 + if subpath is None: 484 + return None 485 + return '.' + attr_spec.name + subpath 486 + 487 + return None 488 + 489 + def _decode_extack(self, request, attr_space, extack): 490 + if 'bad-attr-offs' not in extack: 491 + return 492 + 493 + genl_req = GenlMsg(NlMsg(request, 0, attr_space=attr_space)) 494 + path = self._decode_extack_path(genl_req.raw_attrs, attr_space, 495 + 20, extack['bad-attr-offs']) 496 + if path: 497 + del extack['bad-attr-offs'] 498 + extack['bad-attr'] = path 391 499 392 500 def handle_ntf(self, nl_msg, genl_msg): 393 501 msg = dict() 394 502 if self.include_raw: 395 503 msg['nlmsg'] = nl_msg 396 504 msg['genlmsg'] = genl_msg 397 - op = self._op_array[genl_msg.genl_cmd] 505 + op = self.rsp_by_value[genl_msg.genl_cmd] 398 506 msg['name'] = op['name'] 399 - msg['msg'] = self._decode(genl_msg.raw_attrs, op['attribute-set']) 507 + msg['msg'] = self._decode(genl_msg.raw_attrs, op.attr_set.name) 400 508 self.async_msg_queue.append(msg) 401 509 402 510 def check_ntf(self): ··· 469 487 self.handle_ntf(nl_msg, gm) 470 488 471 489 def _op(self, method, vals, dump=False): 472 - op = self._ops[method] 490 + op = self.ops[method] 473 491 474 492 nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK 475 493 if dump: 476 494 nl_flags |= Netlink.NLM_F_DUMP 477 495 478 496 req_seq = random.randint(1024, 65535) 479 - msg = _genl_msg(self.family.family_id, nl_flags, op['value'], 1, req_seq) 497 + msg = _genl_msg(self.family.family_id, nl_flags, op.req_value, 1, req_seq) 480 498 for name, value in vals.items(): 481 - msg += self._add_attr(op['attribute-set'], name, value) 499 + msg += self._add_attr(op.attr_set.name, name, value) 482 500 msg = _genl_msg_finalize(msg) 483 501 484 502 self.sock.send(msg, 0) ··· 487 505 rsp = [] 488 506 while not done: 489 507 reply = self.sock.recv(128 * 1024) 490 - nms = NlMsgs(reply, attr_space=self._spaces[op['attribute-set']]) 508 + nms = NlMsgs(reply, attr_space=op.attr_set) 491 509 for nl_msg in nms: 510 + if nl_msg.extack: 511 + self._decode_extack(msg, op.attr_set, nl_msg.extack) 512 + 492 513 if nl_msg.error: 493 514 print("Netlink error:", os.strerror(-nl_msg.error)) 494 515 print(nl_msg) 495 516 return 496 517 if nl_msg.done: 518 + if nl_msg.extack: 519 + print("Netlink warning:") 520 + print(nl_msg) 497 521 done = True 498 522 break 499 523 500 524 gm = GenlMsg(nl_msg) 501 525 # Check if this is a reply to our request 502 - if nl_msg.nl_seq != req_seq or gm.genl_cmd != op['value']: 526 + if nl_msg.nl_seq != req_seq or gm.genl_cmd != op.rsp_value: 503 527 if gm.genl_cmd in self.async_msg_ids: 504 528 self.handle_ntf(nl_msg, gm) 505 529 continue ··· 513 525 print('Unexpected message: ' + repr(gm)) 514 526 continue 515 527 516 - rsp.append(self._decode(gm.raw_attrs, op['attribute-set'])) 528 + rsp.append(self._decode(gm.raw_attrs, op.attr_set.name)) 517 529 518 530 if not rsp: 519 531 return None 520 532 if not dump and len(rsp) == 1: 521 533 return rsp[0] 522 534 return rsp 535 + 536 + def do(self, method, vals): 537 + return self._op(method, vals) 538 + 539 + def dump(self, method, vals): 540 + return self._op(method, vals, dump=True)
+126 -148
tools/net/ynl/ynl-gen-c.py
··· 1 - #!/usr/bin/env python 1 + #!/usr/bin/env python3 2 2 3 3 import argparse 4 4 import collections 5 - import jsonschema 6 5 import os 7 6 import yaml 7 + 8 + from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation 8 9 9 10 10 11 def c_upper(name): ··· 29 28 "ynl_cb_array, NLMSG_MIN_TYPE)" 30 29 31 30 32 - class Type: 33 - def __init__(self, family, attr_set, attr): 34 - self.family = family 31 + class Type(SpecAttr): 32 + def __init__(self, family, attr_set, attr, value): 33 + super().__init__(family, attr_set, attr, value) 34 + 35 35 self.attr = attr 36 - self.value = attr['value'] 37 - self.name = c_lower(attr['name']) 36 + self.attr_set = attr_set 38 37 self.type = attr['type'] 39 38 self.checks = attr.get('checks', {}) 40 39 ··· 47 46 else: 48 47 self.nested_render_name = f"{family.name}_{c_lower(self.nested_attrs)}" 49 48 50 - self.enum_name = f"{attr_set.name_prefix}{self.name}" 51 - self.enum_name = c_upper(self.enum_name) 52 49 self.c_name = c_lower(self.name) 53 50 if self.c_name in _C_KW: 54 51 self.c_name += '_' 55 52 56 - def __getitem__(self, key): 57 - return self.attr[key] 53 + # Added by resolve(): 54 + self.enum_name = None 55 + delattr(self, "enum_name") 58 56 59 - def __contains__(self, key): 60 - return key in self.attr 57 + def resolve(self): 58 + self.enum_name = f"{self.attr_set.name_prefix}{self.name}" 59 + self.enum_name = c_upper(self.enum_name) 61 60 62 61 def is_multi_val(self): 63 62 return None ··· 215 214 216 215 217 216 class TypeScalar(Type): 218 - def __init__(self, family, attr_set, attr): 219 - super().__init__(family, attr_set, attr) 220 - 221 - self.is_bitfield = False 222 - if 'enum' in self.attr: 223 - self.is_bitfield = family.consts[self.attr['enum']]['type'] == 'flags' 224 - if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']: 225 - self.is_bitfield = True 226 - 227 - if 'enum' in self.attr and not self.is_bitfield: 228 - self.type_name = f"enum {family.name}_{c_lower(self.attr['enum'])}" 229 - else: 230 - self.type_name = '__' + self.type 217 + def __init__(self, family, attr_set, attr, value): 218 + super().__init__(family, attr_set, attr, value) 231 219 232 220 self.byte_order_comment = '' 233 221 if 'byte-order' in attr: 234 222 self.byte_order_comment = f" /* {attr['byte-order']} */" 223 + 224 + # Added by resolve(): 225 + self.is_bitfield = None 226 + delattr(self, "is_bitfield") 227 + self.type_name = None 228 + delattr(self, "type_name") 229 + 230 + def resolve(self): 231 + self.resolve_up(super()) 232 + 233 + if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']: 234 + self.is_bitfield = True 235 + elif 'enum' in self.attr: 236 + self.is_bitfield = self.family.consts[self.attr['enum']]['type'] == 'flags' 237 + else: 238 + self.is_bitfield = False 239 + 240 + if 'enum' in self.attr and not self.is_bitfield: 241 + self.type_name = f"enum {self.family.name}_{c_lower(self.attr['enum'])}" 242 + else: 243 + self.type_name = '__' + self.type 235 244 236 245 def _mnl_type(self): 237 246 t = self.type ··· 659 648 return mask 660 649 661 650 662 - class AttrSet: 651 + class AttrSet(SpecAttrSet): 663 652 def __init__(self, family, yaml): 664 - self.yaml = yaml 653 + super().__init__(family, yaml) 665 654 666 - self.attrs = dict() 667 - self.name = self.yaml['name'] 668 - if 'subset-of' not in yaml: 669 - self.subset_of = None 655 + if self.subset_of is None: 670 656 if 'name-prefix' in yaml: 671 657 pfx = yaml['name-prefix'] 672 658 elif self.name == family.name: ··· 673 665 self.name_prefix = c_upper(pfx) 674 666 self.max_name = c_upper(self.yaml.get('attr-max-name', f"{self.name_prefix}max")) 675 667 else: 676 - self.subset_of = self.yaml['subset-of'] 677 668 self.name_prefix = family.attr_sets[self.subset_of].name_prefix 678 669 self.max_name = family.attr_sets[self.subset_of].max_name 679 670 671 + # Added by resolve: 672 + self.c_name = None 673 + delattr(self, "c_name") 674 + 675 + def resolve(self): 680 676 self.c_name = c_lower(self.name) 681 677 if self.c_name in _C_KW: 682 678 self.c_name += '_' 683 - if self.c_name == family.c_name: 679 + if self.c_name == self.family.c_name: 684 680 self.c_name = '' 685 681 686 - val = 0 687 - for elem in self.yaml['attributes']: 688 - if 'value' in elem: 689 - val = elem['value'] 690 - else: 691 - elem['value'] = val 692 - val += 1 693 - 694 - if 'multi-attr' in elem and elem['multi-attr']: 695 - attr = TypeMultiAttr(family, self, elem) 696 - elif elem['type'] in scalars: 697 - attr = TypeScalar(family, self, elem) 698 - elif elem['type'] == 'unused': 699 - attr = TypeUnused(family, self, elem) 700 - elif elem['type'] == 'pad': 701 - attr = TypePad(family, self, elem) 702 - elif elem['type'] == 'flag': 703 - attr = TypeFlag(family, self, elem) 704 - elif elem['type'] == 'string': 705 - attr = TypeString(family, self, elem) 706 - elif elem['type'] == 'binary': 707 - attr = TypeBinary(family, self, elem) 708 - elif elem['type'] == 'nest': 709 - attr = TypeNest(family, self, elem) 710 - elif elem['type'] == 'array-nest': 711 - attr = TypeArrayNest(family, self, elem) 712 - elif elem['type'] == 'nest-type-value': 713 - attr = TypeNestTypeValue(family, self, elem) 714 - else: 715 - raise Exception(f"No typed class for type {elem['type']}") 716 - 717 - self.attrs[elem['name']] = attr 718 - 719 - def __getitem__(self, key): 720 - return self.attrs[key] 721 - 722 - def __contains__(self, key): 723 - return key in self.yaml 724 - 725 - def __iter__(self): 726 - yield from self.attrs 727 - 728 - def items(self): 729 - return self.attrs.items() 730 - 731 - 732 - class Operation: 733 - def __init__(self, family, yaml, value): 734 - self.yaml = yaml 735 - self.value = value 736 - 737 - self.name = self.yaml['name'] 738 - self.render_name = family.name + '_' + c_lower(self.name) 739 - self.is_async = 'notify' in yaml or 'event' in yaml 740 - if not self.is_async: 741 - self.enum_name = family.op_prefix + c_upper(self.name) 682 + def new_attr(self, elem, value): 683 + if 'multi-attr' in elem and elem['multi-attr']: 684 + return TypeMultiAttr(self.family, self, elem, value) 685 + elif elem['type'] in scalars: 686 + return TypeScalar(self.family, self, elem, value) 687 + elif elem['type'] == 'unused': 688 + return TypeUnused(self.family, self, elem, value) 689 + elif elem['type'] == 'pad': 690 + return TypePad(self.family, self, elem, value) 691 + elif elem['type'] == 'flag': 692 + return TypeFlag(self.family, self, elem, value) 693 + elif elem['type'] == 'string': 694 + return TypeString(self.family, self, elem, value) 695 + elif elem['type'] == 'binary': 696 + return TypeBinary(self.family, self, elem, value) 697 + elif elem['type'] == 'nest': 698 + return TypeNest(self.family, self, elem, value) 699 + elif elem['type'] == 'array-nest': 700 + return TypeArrayNest(self.family, self, elem, value) 701 + elif elem['type'] == 'nest-type-value': 702 + return TypeNestTypeValue(self.family, self, elem, value) 742 703 else: 743 - self.enum_name = family.async_op_prefix + c_upper(self.name) 704 + raise Exception(f"No typed class for type {elem['type']}") 705 + 706 + 707 + class Operation(SpecOperation): 708 + def __init__(self, family, yaml, req_value, rsp_value): 709 + super().__init__(family, yaml, req_value, rsp_value) 710 + 711 + if req_value != rsp_value: 712 + raise Exception("Directional messages not supported by codegen") 713 + 714 + self.render_name = family.name + '_' + c_lower(self.name) 744 715 745 716 self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \ 746 717 ('dump' in yaml and 'request' in yaml['dump']) 747 718 748 - def __getitem__(self, key): 749 - return self.yaml[key] 719 + # Added by resolve: 720 + self.enum_name = None 721 + delattr(self, "enum_name") 750 722 751 - def __contains__(self, key): 752 - return key in self.yaml 723 + def resolve(self): 724 + self.resolve_up(super()) 725 + 726 + if not self.is_async: 727 + self.enum_name = self.family.op_prefix + c_upper(self.name) 728 + else: 729 + self.enum_name = self.family.async_op_prefix + c_upper(self.name) 753 730 754 731 def add_notification(self, op): 755 732 if 'notify' not in self.yaml: ··· 744 751 self.yaml['notify']['cmds'].append(op) 745 752 746 753 747 - class Family: 754 + class Family(SpecFamily): 748 755 def __init__(self, file_name): 749 - with open(file_name, "r") as stream: 750 - self.yaml = yaml.safe_load(stream) 756 + # Added by resolve: 757 + self.c_name = None 758 + delattr(self, "c_name") 759 + self.op_prefix = None 760 + delattr(self, "op_prefix") 761 + self.async_op_prefix = None 762 + delattr(self, "async_op_prefix") 763 + self.mcgrps = None 764 + delattr(self, "mcgrps") 765 + self.consts = None 766 + delattr(self, "consts") 767 + self.hooks = None 768 + delattr(self, "hooks") 751 769 752 - self.proto = self.yaml.get('protocol', 'genetlink') 753 - 754 - with open(os.path.dirname(os.path.dirname(file_name)) + 755 - f'/{self.proto}.yaml', "r") as stream: 756 - schema = yaml.safe_load(stream) 757 - 758 - jsonschema.validate(self.yaml, schema) 759 - 760 - if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}: 761 - raise Exception("Codegen only supported for genetlink") 770 + super().__init__(file_name) 762 771 763 772 self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME')) 764 773 self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION')) ··· 768 773 if 'definitions' not in self.yaml: 769 774 self.yaml['definitions'] = [] 770 775 771 - self.name = self.yaml['name'] 772 - self.c_name = c_lower(self.name) 773 776 if 'uapi-header' in self.yaml: 774 777 self.uapi_header = self.yaml['uapi-header'] 775 778 else: 776 779 self.uapi_header = f"linux/{self.name}.h" 780 + 781 + def resolve(self): 782 + self.resolve_up(super()) 783 + 784 + if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}: 785 + raise Exception("Codegen only supported for genetlink") 786 + 787 + self.c_name = c_lower(self.name) 777 788 if 'name-prefix' in self.yaml['operations']: 778 789 self.op_prefix = c_upper(self.yaml['operations']['name-prefix']) 779 790 else: ··· 792 791 self.mcgrps = self.yaml.get('mcast-groups', {'list': []}) 793 792 794 793 self.consts = dict() 795 - # list of all operations 796 - self.msg_list = [] 797 - # dict of operations which have their own message type (have attributes) 798 - self.ops = collections.OrderedDict() 799 - self.attr_sets = dict() 800 - self.attr_sets_list = [] 801 794 802 795 self.hooks = dict() 803 796 for when in ['pre', 'post']: ··· 819 824 if self.kernel_policy == 'global': 820 825 self._load_global_policy() 821 826 822 - def __getitem__(self, key): 823 - return self.yaml[key] 827 + def new_attr_set(self, elem): 828 + return AttrSet(self, elem) 824 829 825 - def get(self, key, default=None): 826 - return self.yaml.get(key, default) 830 + def new_operation(self, elem, req_value, rsp_value): 831 + return Operation(self, elem, req_value, rsp_value) 827 832 828 833 # Fake a 'do' equivalent of all events, so that we can render their response parsing 829 834 def _mock_up_events(self): ··· 842 847 else: 843 848 self.consts[elem['name']] = elem 844 849 845 - for elem in self.yaml['attribute-sets']: 846 - attr_set = AttrSet(self, elem) 847 - self.attr_sets[elem['name']] = attr_set 848 - self.attr_sets_list.append((elem['name'], attr_set), ) 849 - 850 850 ntf = [] 851 - val = 0 852 - for elem in self.yaml['operations']['list']: 853 - if 'value' in elem: 854 - val = elem['value'] 855 - 856 - op = Operation(self, elem, val) 857 - val += 1 858 - 859 - self.msg_list.append(op) 860 - if 'notify' in elem: 861 - ntf.append(op) 862 - continue 863 - if 'attribute-set' not in elem: 864 - continue 865 - self.ops[elem['name']] = op 851 + for msg in self.msgs.values(): 852 + if 'notify' in msg: 853 + ntf.append(msg) 866 854 for n in ntf: 867 855 self.ops[n['notify']].add_notification(n) 868 856 ··· 911 933 if attr_set_name != op['attribute-set']: 912 934 raise Exception('For a global policy all ops must use the same set') 913 935 914 - for op_mode in {'do', 'dump'}: 936 + for op_mode in ['do', 'dump']: 915 937 if op_mode in op: 916 938 global_set.update(op[op_mode].get('request', [])) 917 939 ··· 2011 2033 2012 2034 max_by_define = family.get('max-by-define', False) 2013 2035 2014 - for _, attr_set in family.attr_sets_list: 2036 + for _, attr_set in family.attr_sets.items(): 2015 2037 if attr_set.subset_of: 2016 2038 continue 2017 2039 ··· 2022 2044 uapi_enum_start(family, cw, attr_set.yaml, 'enum-name') 2023 2045 for _, attr in attr_set.items(): 2024 2046 suffix = ',' 2025 - if attr['value'] != val: 2026 - suffix = f" = {attr['value']}," 2027 - val = attr['value'] 2047 + if attr.value != val: 2048 + suffix = f" = {attr.value}," 2049 + val = attr.value 2028 2050 val += 1 2029 2051 cw.p(attr.enum_name + suffix) 2030 2052 cw.nl() ··· 2044 2066 max_value = f"({cnt_name} - 1)" 2045 2067 2046 2068 uapi_enum_start(family, cw, family['operations'], 'enum-name') 2047 - for op in family.msg_list: 2069 + for op in family.msgs.values(): 2048 2070 if separate_ntf and ('notify' in op or 'event' in op): 2049 2071 continue 2050 2072 ··· 2063 2085 2064 2086 if separate_ntf: 2065 2087 uapi_enum_start(family, cw, family['operations'], enum_name='async-enum') 2066 - for op in family.msg_list: 2088 + for op in family.msgs.values(): 2067 2089 if separate_ntf and not ('notify' in op or 'event' in op): 2068 2090 continue 2069 2091 ··· 2222 2244 2223 2245 for op_name, op in parsed.ops.items(): 2224 2246 if parsed.kernel_policy in {'per-op', 'split'}: 2225 - for op_mode in {'do', 'dump'}: 2247 + for op_mode in ['do', 'dump']: 2226 2248 if op_mode in op and 'request' in op[op_mode]: 2227 2249 cw.p(f"/* {op.enum_name} - {op_mode} */") 2228 2250 ri = RenderInfo(cw, parsed, args.mode, op, op_name, op_mode)