> From d1c572829e09459e315fa07c2dbb9bb8a45bd359 Mon Sep 17 00:00:00 2001 > From: Jiri Pirko > Date: Thu, 15 Oct 2026 16:50:46 +0200 > Subject: [PATCH 13/20] Documentation: ctlv: Describe how to write a family > > Describe the YAML language, generated family descriptors, lifecycle, The commit message is accurate in spirit, but note that the document does not describe the ABI snapshot file (`Documentation/ctlv/abi/.yaml`) at all, and does not describe kernel-doc/API reference; it is a how-to only. See below. > operations, events, blobs, inheritance and compatible extension rules. > > Assisted-by: Cursor: GPT-5.6 Sol > Assisted-by: Cursor: Claude Opus 5.5 > Signed-off-by: Jiri Pirko > --- > Documentation/driver-api/ctlv.rst | 422 +++++++++++++++++++++++++++++ > Documentation/driver-api/index.rst | 1 + > MAINTAINERS | 1 + > 3 files changed, 424 insertions(+) > create mode 100644 Documentation/driver-api/ctlv.rst > > diff --git a/Documentation/driver-api/ctlv.rst b/Documentation/driver-api/ctlv.rst > new file mode 100644 > index 000000000000..461e2977a483 > --- /dev/null > +++ b/Documentation/driver-api/ctlv.rst > @@ -0,0 +1,422 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +===================== > +Writing a CTLV family > +===================== > + > +:Author: Jiri Pirko > + > +A CTLV family is a YAML specification plus the callbacks named by its generated > +header. CTLV validates requests before a callback sees them. The wire ABI is > +:doc:`/userspace-api/ctlv`; family specifications are under > +``Documentation/ctlv/specs``. > + > +The code here is a small invented family, ``ctlv-example``. > + > +Add the specification, registry entry, and output manifest entry in one change:: Incomplete: patch 14 (the first real family) has to add a fourth file, `Documentation/ctlv/abi/ctlv-attestation.yaml`, and also bump `Documentation/ctlv/abi/families.registry.yaml` and `include/uapi/linux/ctlv_families_gen.h`. 'make -C `tools/ctlv` abi-check' (part of 'all') fails without the snapshot. The list of files to add in one change is missing the ABI snapshot, and 'make abi-update' is never mentioned in the document. > + > + Documentation/ctlv/specs/ctlv-example.yaml > + Documentation/ctlv/families.yaml > + Documentation/ctlv/generated-files.yaml > + > +``families.yaml`` is append-only. Its array index is the family ID and is part > +of every generated attribute and operation ID. Never reorder, remove, rename, > +or reuse an entry. ``Documentation/ctlv/abi/families.registry.yaml`` is the > +checked-in registry ABI. The output manifest selects the generated UAPI, > +kernel and userspace paths and generation modes. > + > +Generate and verify them with:: > + > + make -C tools/ctlv regen The text says 'Generate and verify them with regen / all'. 'all' is validate + abi-check; it does not verify generated outputs. The verifying target is 'check-regen'. Please mention it (and 'abi-update', 'lint') or fix the sentence. > + make -C tools/ctlv all > + > +Never edit a generated output. Change the YAML or generator and regenerate it. > +Optional RST reference pages are written to > +``Documentation/output/ctlv/families`` by:: > + > + make -C tools/ctlv docs There is no 'docs' target in `tools/ctlv/Makefile` at HEAD (targets: all validate abi-check abi-update lint regen check-regen clean), nor any code in `tools/ctlv/ctlvgen` that produces RST pages or `Documentation/output/ctlv/families` (grep finds neither). This sentence documents a non-existent feature; 'make -C `tools/ctlv` docs' will fail with 'No rule to make target'. Drop it or add the target. > + > +Write the specification > +======================= > + > +.. code-block:: yaml > + > + # SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) > + %YAML 1.2 > + --- > + family: > + name: ctlv-example > + inherits: ctlv > + post-init: true > + pre-fini: true > + > + attr-groups: > + - name: item > + attrs: > + - name: index > + type: uint > + max: 4095 > + - name: label > + type: string > + max-length: 63 > + - name: data > + blob: Nit: the sample spec is never validated. Since 'make -C `tools/ctlv` validate' globs `Documentation/ctlv/specs/*.yaml`, the example cannot simply be dropped there, but nothing guards it against rot (the bugs below are exactly that). Consider a literalinclude of a real, validated spec (e.g. the attestation one) instead of an invented family. > + direction: input > + allowed-backings: [ va ] > + max-length: 1048576 > + alignment: 8 > + - name: list > + attrs: > + - name: item > + attrs: item-reply > + max-occurrences: unlimited > + > + attr-sets: > + - name: item-request > + attrs: item.index > + optional-attrs: > + - item.label > + - item.data > + - name: item-reply > + attrs: item.index > + optional-attrs: item.label > + - name: items-reply > + optional-attrs: list.item > + > + ops: > + - name: item-set > + type: action > + optional: true > + request: > + attrs: item-request > + - name: items-dump > + type: query > + availability: dynamic > + pre-post: true 'pre-post' is a spec key only handled in `compat.py` and the schema; please make sure it is documented in the prose below ('Hooks and availability' mentions 'pre-post: true' only in passing). > + reply: > + attrs: items-reply > + suggested-reply-length: 4096 > + - name: item-changed > + type: event > + reply: > + attrs: item-reply > + > +An attribute has one payload form: ``type``, ``enum``, ``flags``, ``attrs``, or > +``blob``. An attr set names one message level. ``attrs`` are required and > +``optional-attrs`` are optional. Its bounds and membership are ABI. > + > +Inheritance and extension points > +================================ > + > +A descendant implements only the operations it declares. Inherited operations > +keep the callbacks and operation hooks of their declaring family. Every module > +therefore depends on the module containing its parent implementation. > + > +A parent reserves an extension point with an empty stub set: > + > +.. code-block:: yaml > + > + attr-groups: > + - name: device > + attrs: > + - name: details > + attrs: details-extension > + > + attr-sets: > + - name: details-extension > + stub: true > + - name: device-reply > + optional-attrs: device.details > + > +A child fills that same set: > + > +.. code-block:: yaml > + > + family: > + name: ctlv-example-vendor > + inherits: ctlv-example > + pre-init: true > + > + attr-groups: > + - name: vendor > + attrs: > + - name: serial > + type: uint > + > + attr-sets: > + - name: details-extension > + optional-attrs: vendor.serial > + > +The parent implementation opens the physical nest and delegates its members: > + > +.. code-block:: c > + > + if (ctlv_example_device_details_start(reply, &details)) { > + ret = ctlv_stub_fill(ctx, request_details, &details); > + ctlv_nest_end(&details); > + if (ret) > + return ret; > + } > + > +The generated child callback fills it: > + > +.. code-block:: c > + > + int ctlv_example_vendor_details_extension_fill( > + void *priv, struct ctlv_op_ctx *ctx, > + const struct ctlv_cursor *request, > + struct ctlv_cursor *nest) > + { > + ctlv_example_vendor_vendor_serial_put(nest, > + example_serial(priv)); > + return 0; > + } > + > +An unfilled stub contributes no nest or message length. Neither side names the > +family on the other side. > + > +Create and destroy a device > +=========================== > + > +``ctlv_create()`` takes the leaf family descriptor and leaf initialization > +information: > + > +.. code-block:: c > + > + struct ctlv_example_vendor_init_info info = { > + .dev = dev, > + }; > + struct ctlv_device *ctlvdev; > + > + ctlvdev = ctlv_create(dev, &ctlv_example_vendor_ctlv_family, &info); > + if (IS_ERR(ctlvdev)) > + return PTR_ERR(ctlvdev); > + > +A family with ``pre-init: true`` supplies initialization information to its > +direct parent. A typical provider descriptor is static and immutable: > + > +.. code-block:: c > + > + static const struct ctlv_example_provider example_provider = { > + .name = "vendor-example", > + .ops = &example_provider_ops, > + }; > + > + const void *ctlv_example_vendor_pre_init(const void *init_info) > + { > + return &example_provider; > + } > + > +The leaf's per-device ``init_info`` still reaches its own ``init`` callback; it > +is not forwarded as the parent's descriptor. ``pre_init`` runs leaf to root and > +may return an ``ERR_PTR()``. It must not acquire resources or return automatic > +storage. > + > +Each family supplies ``priv_size``, ``init``, and ``fini``. The YAML flags add > +``pre_init``, ``post_init``, and ``pre_fini`` callbacks. Creation order is:: > + > + leaf -> root pre_init > + root -> leaf init > + freeze published operations; start event admission > + leaf -> root post_init > + register the character device > + > +Select optional operations and their initial dynamic state in ``init``. A > +``post_init`` callback may publish the device to kernel consumers because all > +family privs and event emission are then available. > + > +Normal destruction is ordered as follows:: > + > + withdraw userspace access; drain admitted operation callbacks > + root -> leaf pre_fini > + stop and drain event contexts; queue DEVICE_REMOVED > + leaf -> root fini > + > +A family published in ``post_init`` withdraws and drains its consumers in > +``pre_fini``. Provider state and events remain available until all > +``pre_fini`` callbacks finish. ``ctlv_destroy()`` consumes the reference from > +``ctlv_create()``; open file descriptions remain only to drain queued records > +and otherwise fail with ``-ENODEV``. > + > +If ``init`` fails, that callback releases its partial resources and CTLV calls > +``fini`` only for initialized ancestors. A failure after initialization uses > +the normal ``pre_fini`` and ``fini`` ordering. > + > +CTLV allocates one zeroed, cache-line-aligned priv per family. Direct neighbors > +are available without naming their types globally: > + > +.. code-block:: c > + > + parent = ctlv_priv_parent(priv); > + child = ctlv_priv_child(priv); > + > +During ``init``, the child is not initialized yet. During ``pre_fini`` it still > +is; during ``fini`` it has already been finalized. Allocate a separately > +aligned object when cache-line alignment is insufficient. > + > +Implement operations > +==================== > + > +Action > +------ > + > +Generated getters read only validated placements and generated serializers > +write only declared replies: > + > +.. code-block:: c > + > + int ctlv_example_item_set_action(void *priv, struct ctlv_op_ctx *ctx, Wrong accessor name in the example below: the generated getters embed the operation name. The attestation header (patch 14) has e.g. `ctlv_attestation_mr_get_mr_id_get()` i.e. `___`. By that scheme the call must be `ctlv_example_item_set_item_index_get(request)`, not `ctlv_example_item_index_get(request)` (the put helpers, in contrast, are `___put`, which the document uses correctly). A reader copying the example gets an implicit-declaration error. > + const struct ctlv_cursor *request, > + struct ctlv_cursor *reply) > + { > + u64 index = ctlv_example_item_index_get(request); See above: no such symbol is ever generated. The same applies to the `_present()` and `_for_each()` remark that follows, which are also op-qualified. > + > + if (item_busy(priv, index)) > + return ctlv_op_error(ctx, -EBUSY, "item is busy"); > + return item_set(priv, index); > + } > + > +Use ``_present()`` for optional attributes and ``_for_each()`` for repeated > +ones. ``ctlv_op_error()`` preserves the errno and adds a short diagnostic when > +the caller supplied enough reply space. > + > +Query > +----- > + > +.. code-block:: c > + > + int ctlv_example_items_dump_query(void *priv, struct ctlv_op_ctx *ctx, > + const struct ctlv_cursor *request, > + struct ctlv_cursor *reply) > + { > + struct ctlv_example *example = priv; > + u64 generation = READ_ONCE(example->generation); > + u64 cursor = ctlv_op_query_cursor(ctx); > + struct ctlv_cursor item; > + u64 i = cursor ? cursor - 1 : 0; > + > + if (cursor && ctlv_op_query_generation(ctx) != generation) > + return -ESTALE; > + for (; i < example->items_count; i++) { > + if (!ctlv_example_list_item_start(reply, &item)) > + return ctlv_op_query_more(ctx, generation, i + 1); > + ctlv_example_item_index_put(&item, i); > + ctlv_nest_end(&item); > + } > + return ctlv_op_query_done(ctx, generation); > + } > + > +A continuation cursor is nonzero and advances. Every continuing fragment > +completes a logical leaf. Call ``ctlv_op_query_more()`` while cursors for open > +continuation nests are alive. A generation mismatch returns ``-ESTALE``. > + > +Hooks and availability > +====================== > + > +The leaf family's hooks wrap each effective non-core action or query. An > +operation with ``pre-post: true`` has its declaring family's pair inside them:: > + > + family pre -> operation pre -> availability check -> callback > + -> operation post -> family post > + > +Each successful pre hook has one post hook. A failing pre hook undoes its own > +work. Hooks may sleep and must not retain ``ctx``. Each hook and callback gets > +the priv of the family implementing it. > + > +Optional and dynamic operations are separate choices: > + > +.. code-block:: c > + > + /* During init: publish this optional operation on this device. */ > + ctlv_example_item_set_provide(ctlvdev); > + > + /* At runtime: temporarily refuse a published dynamic operation. */ > + ctlv_example_items_dump_disable(ctlvdev); > + ctlv_example_items_dump_enable(ctlvdev); > + > +Declare the latter with ``availability: dynamic``. An unpublished optional > +operation has no schema entry. A disabled operation remains discoverable and > +returns ``-EOPNOTSUPP``; a state change emits ``op-changed``. > + > +Blobs > +===== > + > +A blob attribute describes caller-owned memory. Resolve it only in the callback > +that received its request and release every successful resolve before return: > + > +.. code-block:: c > + > + blob = ctlv_example_item_data_resolve(ctx, request); > + if (IS_ERR(blob)) > + return PTR_ERR(blob); > + > + ret = ctlv_blob_read(blob, 0, buf, ctlv_blob_len(blob)); > + put_ret = ctlv_blob_put(blob); > + return ret ?: put_ret; > + > +The schema fixes direction, backing, length, and alignment. Resolution holds > +the backing for the ioctl. > + > +Events > +====== > + > +.. code-block:: c > + > + ctx = ctlv_example_item_changed_create(ctlvdev, &event, GFP_KERNEL); > + if (IS_ERR(ctx)) > + return PTR_ERR(ctx); > + ctlv_example_item_index_put(event, index); > + return ctlv_event_emit(ctx); > + > +Exactly one of ``ctlv_event_emit()`` and ``ctlv_event_abort()`` ends the context. The Line is >80 columns while the rest of the paragraph is wrapped; reflow. > +GFP argument covers subscriber copies; atomic producers use ``GFP_NOWAIT`` or > +``GFP_ATOMIC``. Event APIs are not called from interrupt context, and hooks do > +not run around events. > + > +If an event occurred but allocation failed, commit the state change and call > +``ctlv_event_lost()``. It neither allocates nor sleeps. Do not use it for an > +aborted event or for per-subscriber drops already tracked by > +``ctlv_event_emit()``. > + > +If hardware changed before an event context could be allocated, the family > +must commit its invalidation and explicitly report the missing notification: > + > +.. code-block:: c > + > + invalidate_stale_state(priv); > + priv->generation++; > + > + ctx = ctlv_example_thing_happened_create(ctlvdev, &event, GFP_ATOMIC); > + if (IS_ERR(ctx)) { > + if (PTR_ERR(ctx) == -ENOMEM) > + ctlv_event_lost(ctlvdev, > + CTLV_EXAMPLE_OP_THING_HAPPENED); > + return; > + } > + ctlv_example_generation_put(event, priv->generation); > + ctlv_op_emit(ctx); `ctlv_op_emit()` does not exist anywhere in include/ or `drivers/ctlv/` (grep: zero hits); the API is `ctlv_event_emit()`. The same wrong name appears again twice in the prose after this example ('already accounted for by `ctlv_op_emit()`'). Also the example returns void yet earlier calls `ctlv_event_emit()` with a return value that is silently dropped here, and uses a 'return;' in a function whose signature is not shown. > + > +``ctlv_event_lost()`` allocates nothing and does not sleep. For each subscribed This paragraph is largely a repeat of what was already said three paragraphs earlier ('If an event occurred but allocation failed ... neither allocates nor sleeps. Do not use it for an aborted event or for per-subscriber drops'). 'allocates nothing and does not sleep', 'not called from interrupt context' and 'do not call it for an aborted event' each appear twice. Merge into one paragraph; every sentence should have a reason to exist. > +file description it advances the event sequence and aggregates the loss into > +the preallocated ``EVENT_OVERFLOW`` record. The caller keeps the device alive > +and serializes the report with normal emission of that event. Like event > +emission, it is not called from interrupt context. It is only for an event that > +actually occurred but could not be constructed: do not call it for an aborted > +event or for copy-allocation and full-queue drops already accounted for by > +``ctlv_op_emit()``. Reports after event shutdown or the terminal > +``DEVICE_REMOVED`` record are ignored. > + > +Reporting an error Under 'Events' this is a sub-heading of the wrong level/location: 'Reporting an error' (operations) is nested under the Events section with `---` and thus reads as an event topic; it belongs under 'Implement operations'. Also the claim 'writes error information into the caller's reply buffer, naming the reason and the offset' is not backed by any cross-reference to the userspace ABI document, and conflicts with the earlier statement that `ctlv_op_error()` adds a message; it is unclear which mechanism applies to a family returning a plain errno. > +------------------ > + > +A callback returns a negative errno, and that is what the ioctl returns. Where > +CTLV itself refuses a message it also writes error information into the > +caller's reply buffer, naming the reason and the offset of the offending > +attribute. A family implementation does not build that; it adds its own errno > +and its reply. > + > +Configuration > +============= > + > +``CONFIG_CTLV`` A one-entry 'Configuration' section that says 'CTLV. A family implementation selects it.' is content-free (it restates the symbol name). Either document the Kconfig symbols that matter (`CONFIG_CTLV`, uevent/sysfs knobs, if any) or remove the section. > + CTLV. A family implementation selects it. > diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst > index 6601a258690f..e85a7a982bfc 100644 > --- a/Documentation/driver-api/index.rst > +++ b/Documentation/driver-api/index.rst OK. But the document contains no kernel-doc ('.. kernel-doc::') references for `include/linux/ctlv.h` / `ctlv_family.h`, although those headers carry kernel-doc comments. As a driver-api document this should include the API reference or say where it is. > @@ -37,6 +37,7 @@ of interest to most developers working on device drivers. > > early-userspace/index > connector > + ctlv > device-io > devfreq > dma-buf > diff --git a/MAINTAINERS b/MAINTAINERS > index ba32f4ccb1a4..0b565a07f55e 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7008,6 +7008,7 @@ M: Jiri Pirko > L: linux-kernel@vger.kernel.org > S: Maintained > F: Documentation/ctlv/ > +F: Documentation/driver-api/ctlv.rst Minor: the MAINTAINERS entry uses M: jiri@resnulli.us while this patch (and patch 14's ATTESTATION SUBSYSTEM entry) is authored/signed as jiri@nvidia.com. Use one address consistently; the From:/S-o-b: identity should match a MAINTAINERS entry the author can be reached at. > F: Documentation/userspace-api/ctlv.rst > F: drivers/ctlv/ > F: include/linux/ctlv*.h > -- > 2.55.0 > Summary: - The doc describes things that do not exist: 'make -C `tools/ctlv` docs' (no such target, no generator code), `ctlv_op_emit()` (real name `ctlv_event_emit()`, wrong three times). - The generated getter naming in the examples is wrong (getters carry the op name, e.g. `ctlv_attestation_mr_get_mr_id_get`), so the copy-paste examples do not compile. - The "add in one change" file list omits the mandatory ABI snapshot (`Documentation/ctlv/abi/.yaml`) and the abi-update/check-regen workflow, and 'make all' is claimed to verify generated files (it does not). - Redundant `event_lost` paragraphs, misplaced 'Reporting an error' heading, empty 'Configuration' section, no kernel-doc API reference, mixed maintainer e-mail addresses.