Linux userland API discussions
 help / color / mirror / Atom feed
From: Jiri Pirko <jiri@resnulli.us>
To: linux-coco@lists.linux.dev
Cc: linux-kernel@vger.kernel.org, linux-api@vger.kernel.org,
	x86@kernel.org, kvm@vger.kernel.org, linux-doc@vger.kernel.org,
	jgg@ziepe.ca, david.quigley@intel.com, lukas@wunner.de,
	ksinyuk@kernel.org, rodrigo.vivi@intel.com,
	cedric.xing@intel.com, sameo@meta.com, drdeeglaze@gmail.com,
	James.Bottomley@HansenPartnership.com, aik@amd.com,
	jpiotrowski@nvidia.com, qinkun@google.com, pgonda@google.com,
	jic23@kernel.org, alistair.francis@wdc.com, ankita@nvidia.com,
	suzuki.poulose@arm.com, kas@kernel.org,
	rick.p.edgecombe@intel.com, elena.reshetova@intel.com,
	dave.hansen@linux.intel.com,
	sathyanarayanan.kuppuswamy@linux.intel.com, peter.fang@intel.com
Subject: [PATCH RFC 13/20] Documentation: ctlv: Describe how to write a family
Date: Thu, 15 Oct 2026 16:50:46 +0200	[thread overview]
Message-ID: <20261015145053.3664509-14-jiri@resnulli.us> (raw)
In-Reply-To: <20261015145053.3664509-1-jiri@resnulli.us>

From: Jiri Pirko <jiri@nvidia.com>

Describe the YAML language, generated family descriptors, lifecycle,
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 <jiri@nvidia.com>
---
 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::
+
+    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
+    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
+
+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:
+              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
+        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,
+                                     const struct ctlv_cursor *request,
+                                     struct ctlv_cursor *reply)
+    {
+            u64 index = ctlv_example_item_index_get(request);
+
+            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
+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_event_lost()`` allocates nothing and does not sleep. For each subscribed
+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
+------------------
+
+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``
+    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
@@ -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 b308e3a5f0b6..983437acd388 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7006,6 +7006,7 @@ M:	Jiri Pirko <jiri@resnulli.us>
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/ctlv/
+F:	Documentation/driver-api/ctlv.rst
 F:	Documentation/userspace-api/ctlv.rst
 F:	drivers/ctlv/
 F:	include/linux/ctlv*.h
-- 
2.56.0


  parent reply	other threads:[~2026-10-15 14:51 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-15 14:50 [PATCH RFC 00/20] attestation: Add a common subsystem and TDX support Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 01/20] ctlv: Add the schema language and generator Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 02/20] ctlv: Add the UAPI wire format and family API Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 03/20] ctlv: Add the root family specification and generated files Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 04/20] Documentation: ctlv: Add userspace ABI Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 05/20] ctlv: Prepare message input handling Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 06/20] ctlv: Prepare blob descriptor validation Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 07/20] ctlv: Prepare schema validation Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 08/20] ctlv: Prepare message output handling Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 09/20] ctlv: Prepare VA blob handling Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 10/20] ctlv: Prepare event queue support Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 11/20] ctlv: Prepare runtime introspection Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 12/20] ctlv: Add device and operation dispatch Jiri Pirko
2026-10-15 14:50 ` Jiri Pirko [this message]
2026-10-15 14:50 ` [PATCH RFC 14/20] attestation: Add the base family specification and generated files Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 15/20] attestation: Prepare provider and instance core Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 16/20] attestation: Prepare measurement register journal Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 17/20] attestation: Add CTLV operation handling Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 18/20] tdx-guest: Expose the native attestation service API Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 19/20] attestation: Add the TDX family specification and generated files Jiri Pirko
2026-10-15 14:50 ` [PATCH RFC 20/20] attestation: Add the TDX family implementation Jiri Pirko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261015145053.3664509-14-jiri@resnulli.us \
    --to=jiri@resnulli.us \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=aik@amd.com \
    --cc=alistair.francis@wdc.com \
    --cc=ankita@nvidia.com \
    --cc=cedric.xing@intel.com \
    --cc=dave.hansen@linux.intel.com \
    --cc=david.quigley@intel.com \
    --cc=drdeeglaze@gmail.com \
    --cc=elena.reshetova@intel.com \
    --cc=jgg@ziepe.ca \
    --cc=jic23@kernel.org \
    --cc=jpiotrowski@nvidia.com \
    --cc=kas@kernel.org \
    --cc=ksinyuk@kernel.org \
    --cc=kvm@vger.kernel.org \
    --cc=linux-api@vger.kernel.org \
    --cc=linux-coco@lists.linux.dev \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lukas@wunner.de \
    --cc=peter.fang@intel.com \
    --cc=pgonda@google.com \
    --cc=qinkun@google.com \
    --cc=rick.p.edgecombe@intel.com \
    --cc=rodrigo.vivi@intel.com \
    --cc=sameo@meta.com \
    --cc=sathyanarayanan.kuppuswamy@linux.intel.com \
    --cc=suzuki.poulose@arm.com \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox