> From 79f3cfcd00bc7b1d41f2ac357ca599b98a03f06d Mon Sep 17 00:00:00 2001
> From: Jiri Pirko <jiri@nvidia.com>
> Date: Thu, 15 Oct 2026 16:50:38 +0200
> Subject: [PATCH 05/20] ctlv: Prepare message input handling
>
> Add wire walking, request indexing and validated input accessors for
> generated family readers.

Commit message accuracy: "generated family readers" ok, but "request indexing" is not added here. This patch only defines the index structures (`message.h`) and consumes a prebuilt index; nothing builds it (that is `schema.c`, patch 7), and `ctlv_message_index_free()` is declared in `message.h` with no definition anywhere until then.

>
> Keep CONFIG_CTLV unwired until dispatch completes the module.
>
> Assisted-by: Cursor: GPT-5.6 Sol

So `drivers/ctlv/Kconfig` and the Makefile are dead files: they are reachable from neither `drivers/Kconfig` nor `drivers/Makefile` until patch 11. Net effect: `message.c` (~400 lines of index-walking code with lots of invariants) is not compiled by any bisect step between here and patch 11, and the series cannot even be build-tested one patch at a time. Please wire it up (default n, or depends on BROKEN) in the first patch that has a compilable core, or let each Prepare patch build with an obj-y stub; at minimum the commit message should say that none of this has been compiled in isolation.

> Assisted-by: Cursor: Claude Opus 5.5
> Signed-off-by: Jiri Pirko <jiri@nvidia.com>
> ---
>  MAINTAINERS                 |   1 +
>  drivers/ctlv/Kconfig        |  12 ++
>  drivers/ctlv/Makefile       |   4 +
>  drivers/ctlv/internal.h     |  66 ++++++
>  drivers/ctlv/message.c      | 403 ++++++++++++++++++++++++++++++++++++
>  drivers/ctlv/message.h      |  92 ++++++++
>  include/linux/ctlv_family.h | 138 ++++++++++++
>  7 files changed, 716 insertions(+)
>  create mode 100644 drivers/ctlv/Kconfig
>  create mode 100644 drivers/ctlv/Makefile
>  create mode 100644 drivers/ctlv/internal.h
>  create mode 100644 drivers/ctlv/message.c
>  create mode 100644 drivers/ctlv/message.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 05469282b991..ba32f4ccb1a4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7009,6 +7009,7 @@ L:	linux-kernel@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/ctlv/
>  F:	Documentation/userspace-api/ctlv.rst
> +F:	drivers/ctlv/
>  F:	include/linux/ctlv*.h
>  F:	include/uapi/linux/ctlv*.h
>  F:	tools/ctlv/
> diff --git a/drivers/ctlv/Kconfig b/drivers/ctlv/Kconfig
> new file mode 100644
> index 000000000000..aed3243c55fd
> --- /dev/null
> +++ b/drivers/ctlv/Kconfig
> @@ -0,0 +1,12 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +menuconfig CTLV

menuconfig with no sub-options (and none at HEAD either): this should be a plain "config". Also no "depends on" (e.g. MMU if VA blobs pin user pages) and the help text describes character device lifetime, event queues, blobs and introspection that do not exist in this patch. Makefile uses SPDX "GPL-2.0" while Kconfig uses "GPL-2.0-only"; pick the -only form.

> +	tristate "CTLV character device TLV UAPI"
> +	help
> +	  CTLV provides character device UAPIs described by a checked-in YAML
> +	  specification. It owns the character device lifetime, the strict TLV
> +	  message format, request validation, reply serialization, the large
> +	  payloads a request describes rather than carries, per-open event
> +	  queues and runtime schema introspection, so families only implement
> +	  semantics.
> +
> +	  If you don't know what to do here, say N.
> diff --git a/drivers/ctlv/Makefile b/drivers/ctlv/Makefile
> new file mode 100644
> index 000000000000..4f0fe41bc9e1
> --- /dev/null
> +++ b/drivers/ctlv/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0
> +obj-$(CONFIG_CTLV) += ctlv.o
> +
> +ctlv-y += message.o
> diff --git a/drivers/ctlv/internal.h b/drivers/ctlv/internal.h
> new file mode 100644
> index 000000000000..7d2b43857299
> --- /dev/null
> +++ b/drivers/ctlv/internal.h
> @@ -0,0 +1,66 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
> + *
> + * CTLV core internals.
> + */
> +#ifndef _CTLV_INTERNAL_H
> +#define _CTLV_INTERNAL_H
> +
> +#include <linux/ctlv_family.h>
> +#include <linux/types.h>
> +
> +#include <uapi/linux/ctlv.h>
> +
> +#include "message.h"
> +
> +#define CTLV_ERROR_MESSAGE_LEN 256

Nothing in this patch uses anything in `internal.h` except as an opaque type: `struct ctlv_op_entry`, `struct ctlv_error`, `struct ctlv_query_fragment`, `CTLV_OP_STATE_NONE`, `CTLV_ERROR_MESSAGE_LEN` and nearly all of `struct ctlv_op_ctx` are used only by `core.c/introspection.c`/etc. 6+ patches later. (`message.c` does not dereference `struct ctlv_op_ctx` at all.) Also the magic 256 is tied to error.message max-length 255 in the spec and `CTLV_ERROR_INFO_MAX_LEN` 432 in the generated header with no `static_assert` or comment linking them; and `<uapi/linux/ctlv.h>` should be reached via `<linux/ctlv.h>`.

> +
> +/* An op that is always enabled has no per-device availability state. */
> +#define CTLV_OP_STATE_NONE U32_MAX
> +
> +struct ctlv_op_entry {
> +	const struct ctlv_op_schema *schema;
> +	const struct ctlv_op *binding;
> +	void *priv;
> +	struct ctlv_op_sizes sizes;
> +	u32 state_index;
> +	bool absent;
> +};
> +
> +struct ctlv_error {
> +	u32 reason;
> +	char message[CTLV_ERROR_MESSAGE_LEN];
> +	u32 bad_attr_offset;
> +	u32 nest_offset;
> +	u64 missing_attr_id;
> +	u64 expected_generation;
> +	u64 current_generation;
> +};
> +
> +struct ctlv_query_fragment {
> +	u64 generation;
> +	u64 cursor;
> +	u64 reported;
> +	u64 next_cursor;
> +	bool progress;
> +	bool answered;
> +	int result;
> +};
> +
> +struct ctlv_op_ctx {
> +	const struct ctlv_family *family;
> +	const struct ctlv_op_entry *op;
> +
> +	struct ctlv_msg request;
> +	struct ctlv_cursor request_root;
> +
> +	struct ctlv_msg output;
> +	struct ctlv_cursor output_root;
> +	struct ctlv_cursor *deepest;
> +
> +	struct ctlv_query_fragment frag;
> +	struct ctlv_error error;
> +};
> +
> +#endif /* _CTLV_INTERNAL_H */
> diff --git a/drivers/ctlv/message.c b/drivers/ctlv/message.c
> new file mode 100644
> index 000000000000..7e5742ba8f70
> --- /dev/null
> +++ b/drivers/ctlv/message.c
> @@ -0,0 +1,403 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
> + *
> + * Message walking, indexing, and generated-accessor primitives.
> + */
> +#include <linux/align.h>
> +#include <linux/bits.h>
> +#include <linux/bug.h>
> +#include <linux/ctlv.h>
> +#include <linux/err.h>

Unused includes in `message.c`: `<linux/err.h>`, `<linux/errno.h>`, `<linux/string.h>` (no `ERR_PTR`, errno or mem*/str* use), and `<uapi/linux/ctlv_gen.h>` (no generated identifier is referenced; `CTLV_SCHEMA_*` come from `ctlv.h`). `<uapi/linux/ctlv.h>` is redundant with `<linux/ctlv.h>` which wraps it.

> +#include <linux/errno.h>
> +#include <linux/export.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +
> +#include <uapi/linux/ctlv.h>
> +#include <uapi/linux/ctlv_gen.h>
> +
> +#include "internal.h"
> +#include "message.h"
> +
> +enum ctlv_message_step ctlv_message_next(const struct ctlv_attr *nest,
> +					 const struct ctlv_attr **child)
> +{
> +	const u8 *end = (const u8 *)nest + nest->len;

Nit/robustness: end is computed from `nest->len` before `nest->len` is validated two lines below (harmless arithmetic, but reads oddly; reorder). There is no kernel-doc for this function: callers need to know that *child is in/out, is `NULL` to start, and is left untouched on END and BAD. Also note this walker enforces that a child's trailing padding lies inside the parent len (stricter than a plain header check); that rule is the wire format's, so it should be stated in the comment rather than only in `struct ctlv_attr`'s doc.

> +	const u8 *next;
> +	size_t room;
> +
> +	if (nest->len < sizeof(*nest))
> +		return CTLV_MESSAGE_STEP_BAD;
> +	if (*child)
> +		next = (const u8 *)*child + ALIGN((size_t)(*child)->len, CTLV_ALIGNTO);
> +	else
> +		next = (const u8 *)(nest + 1);
> +	if (next >= end)
> +		return next == end ? CTLV_MESSAGE_STEP_END : CTLV_MESSAGE_STEP_BAD;
> +
> +	room = end - next;
> +	if (room < sizeof(**child))
> +		return CTLV_MESSAGE_STEP_BAD;
> +	*child = (const struct ctlv_attr *)next;
> +	/*
> +	 * Length before alignment, so that aligning a length this nest could
> +	 * never hold is not what overflows.
> +	 */
> +	if ((*child)->len < sizeof(**child) || (*child)->len > room)
> +		return CTLV_MESSAGE_STEP_BAD;
> +	if (ALIGN((size_t)(*child)->len, CTLV_ALIGNTO) > room)
> +		return CTLV_MESSAGE_STEP_BAD;
> +	return CTLV_MESSAGE_STEP_ATTR;
> +}
> +
> +u32 ctlv_message_offset(const struct ctlv_msg *msg, const struct ctlv_attr *attr)
> +{
> +	return (const u8 *)attr - (const u8 *)msg->root;
> +}
> +
> +void ctlv_message_root(struct ctlv_cursor *cursor, struct ctlv_msg *msg)
> +{
> +	*cursor = (struct ctlv_cursor){
> +		.msg = msg,
> +		.attr = msg->root,
> +		.tree = msg->tree,
> +		.index_nest_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
> +		.index_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
> +	};
> +	if (msg->request && msg->index)
> +		cursor->index_nest_scope = msg->index->nests[0].first_scope;

No check that `index->nests_count` > 0; every other index access in this file is bounds-checked (WARN) before use, this one is not. Either the invariant (a request index always has the root nest) is documented and assumed everywhere, or this should be checked like the rest.

> +}
> +
> +static bool ctlv_message_nest_index_ok(const struct ctlv_cursor *nest)

General point about this file: the request index is built by the core from a message the core has already validated, and then every accessor re-derives and re-verifies it. `ctlv_attr_ok()` runs `ctlv_message_cursor_index()` (about 15 checks); the following `ctlv_attr_u64()` runs it again; `ctlv_attr_next()` runs it at entry and again via `WARN_ON_ONCE(!...)` at exit; `ctlv_input_cursor()` runs it plus ref-node verification. So one iteration of a for-each loop revalidates the same cursor ~5 times. These checks defend the core against its own bugs; each `WARN_ON_ONCE` is dead code if the core is correct. Gate them behind a debug option (or a single `CTLV_DEBUG` helper) rather than paying for them on every read, and make the invariants (cursor/index/schema agreement) a comment at the top of the file. Also in this function `nest->msg->index` is dereferenced in the initializer before the !`nest->msg` check in the WARN (callers check, but it is fragile).

> +{
> +	const struct ctlv_message_input_index *index = nest->msg->index;
> +	const struct ctlv_message_input_nest *indexed;
> +	const struct ctlv_message_input_scope *scope;
> +	const struct ctlv_attr *attr;
> +
> +	if (WARN_ON_ONCE(!nest->msg->request || !index ||
> +			 nest->index_nest_scope >= index->scopes_count))
> +		return false;
> +	scope = &index->scopes[nest->index_nest_scope];
> +	if (WARN_ON_ONCE(scope->nest >= index->nests_count))
> +		return false;
> +	indexed = &index->nests[scope->nest];
> +	attr = (const void *)((const u8 *)nest->msg->root + indexed->offset);
> +	if (WARN_ON_ONCE(attr != nest->attr))
> +		return false;
> +	return !WARN_ON_ONCE(scope->tree != nest->tree || scope->node != nest->node);
> +}
> +
> +static bool ctlv_message_input_scope_ok(const struct ctlv_message_input_index *index,
> +					const struct ctlv_message_input_scope *scope)
> +{
> +	return !WARN_ON_ONCE(scope->first_slot > index->slots_count ||
> +			     scope->slots_count > index->slots_count - scope->first_slot);
> +}
> +
> +static const struct ctlv_node_schema *ctlv_message_input_ref_node(struct ctlv_input_ref ref)
> +{
> +	const struct ctlv_node_schema *parent;
> +	const struct ctlv_node_schema *node;
> +
> +	if (WARN_ON_ONCE(!ref.tree->node_schemas || ref.node >= ref.tree->node_schemas_count))
> +		return NULL;
> +	parent = &ref.tree->node_schemas[ref.node];
> +	if (WARN_ON_ONCE(ref.slot >= parent->children_count ||
> +			 parent->first_child + ref.slot >= ref.tree->node_schemas_count))
> +		return NULL;
> +	node = &ref.tree->node_schemas[parent->first_child + ref.slot];
> +	if (WARN_ON_ONCE(node->attr_id != ref.attr_id || node->kind != ref.kind))
> +		return NULL;
> +	return node;
> +}
> +
> +static const struct ctlv_message_input_slot *
> +ctlv_message_cursor_index(const struct ctlv_cursor *cursor)
> +{
> +	const struct ctlv_message_input_index *index;
> +	const struct ctlv_message_input_scope *scope;
> +	const struct ctlv_node_schema *parent;
> +	const struct ctlv_node_schema *node;
> +	const struct ctlv_message_input_slot *slot;
> +	const struct ctlv_message_input_nest *nest;
> +	u32 local_slot;
> +	u32 offset;
> +
> +	if (WARN_ON_ONCE(!cursor->msg || !cursor->tree || !cursor->msg->request ||
> +			 !cursor->msg->index))
> +		return NULL;
> +	index = cursor->msg->index;
> +	if (WARN_ON_ONCE(cursor->index_scope >= index->scopes_count))
> +		return NULL;
> +	scope = &index->scopes[cursor->index_scope];
> +	if (!ctlv_message_input_scope_ok(index, scope))
> +		return NULL;
> +	parent = &scope->tree->node_schemas[scope->node];
> +	if (WARN_ON_ONCE(scope->tree != cursor->tree || cursor->node < parent->first_child ||
> +			 cursor->node >= parent->first_child + scope->slots_count))
> +		return NULL;
> +	local_slot = cursor->node - parent->first_child;
> +	node = &cursor->tree->node_schemas[cursor->node];
> +	slot = &index->slots[scope->first_slot + local_slot];
> +	if (!cursor->attr)
> +		return slot;
> +	if (WARN_ON_ONCE(cursor->attr->attr_id != node->attr_id))
> +		return NULL;
> +	if (WARN_ON_ONCE(scope->nest >= index->nests_count))
> +		return NULL;
> +	nest = &index->nests[scope->nest];
> +	if (WARN_ON_ONCE(cursor->parent !=
> +			 (const void *)((const u8 *)cursor->msg->root + nest->offset)))
> +		return NULL;
> +	offset = ctlv_message_offset(cursor->msg, cursor->attr);
> +	if (WARN_ON_ONCE(offset < slot->first_offset || offset >= slot->end_offset ||
> +			 offset + ALIGN((size_t)cursor->attr->len, CTLV_ALIGNTO) >
> +				 slot->end_offset))
> +		return NULL;
> +	if (node->kind == CTLV_SCHEMA_NEST) {
> +		const struct ctlv_message_input_scope *nest_scope;
> +
> +		if (WARN_ON_ONCE(cursor->index_nest_scope >= index->scopes_count))
> +			return NULL;
> +		nest_scope = &index->scopes[cursor->index_nest_scope];
> +		if (WARN_ON_ONCE(nest_scope->nest >= index->nests_count ||
> +				 index->nests[nest_scope->nest].offset != offset ||
> +				 nest_scope->tree != cursor->tree ||
> +				 nest_scope->node != cursor->node))
> +			return NULL;
> +	} else if (WARN_ON_ONCE(cursor->index_nest_scope != CTLV_MESSAGE_INPUT_INDEX_NONE)) {
> +		return NULL;
> +	}
> +	return slot;
> +}
> +
> +bool ctlv_message_input_rebind(struct ctlv_cursor *nest, const struct ctlv_schema_tree *tree,
> +			       u32 node)
> +{
> +	const struct ctlv_message_input_scope *bound_scope;
> +	const struct ctlv_message_input_nest *indexed;
> +	u32 i;
> +
> +	if (WARN_ON_ONCE(!nest->attr || !nest->msg || !nest->msg->index))
> +		return false;
> +	if (!ctlv_message_nest_index_ok(nest))
> +		return false;
> +	bound_scope = &nest->msg->index->scopes[nest->index_nest_scope];
> +	indexed = &nest->msg->index->nests[bound_scope->nest];
> +	for (i = 0; i < indexed->scopes_count; i++) {
> +		u32 scope_index = indexed->first_scope + i;
> +		const struct ctlv_message_input_scope *scope =
> +			&nest->msg->index->scopes[scope_index];
> +
> +		if (scope->tree != tree || scope->node != node)
> +			continue;
> +		nest->tree = tree;
> +		nest->node = node;
> +		nest->index_nest_scope = scope_index;
> +		return true;
> +	}
> +	return false;
> +}
> +
> +struct ctlv_cursor *ctlv_input_attr(struct ctlv_cursor *cursor, const struct ctlv_cursor *nest,
> +				    struct ctlv_input_ref ref)
> +{
> +	const struct ctlv_message_input_index *index;
> +	const struct ctlv_message_input_scope *scope;
> +	const struct ctlv_message_input_slot *indexed;
> +	const struct ctlv_node_schema *schema;
> +
> +	*cursor = (struct ctlv_cursor){
> +		.index_nest_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
> +		.index_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
> +	};
> +	if (WARN_ON_ONCE(!nest->attr || !nest->msg))

User-triggerable WARN: `ctlv_input_attr()` on a cursor whose attr is `NULL` warns. A `NULL`-attr nest cursor is exactly what `ctlv_input_attr()`/`CTLV_IN_NEST` produce when an optional nest is absent from the request (`indexed->count` == 0). A handler that then calls any `_get()`/`_present()` on that nested cursor, which is the natural "optional nest absent => defaults" pattern, hits this WARN with a request chosen by userspace (`panic_on_warn` = DoS, and syzkaller noise otherwise). An absent nest should simply yield an absent child, silently.

Also aliasing: *cursor is overwritten with the empty state before nest is read, so `ctlv_input_attr(&c, &c, ref)` (descend in place) reads a zeroed nest and WARNs. Build the result in a local and assign at the end, or document that cursor != nest is required.

> +		return cursor;
> +	if (!ctlv_message_nest_index_ok(nest))
> +		return cursor;
> +	index = nest->msg->index;
> +	if (WARN_ON_ONCE(nest->index_nest_scope >= index->scopes_count))
> +		return cursor;
> +	scope = &index->scopes[nest->index_nest_scope];
> +	if (!ctlv_message_input_scope_ok(index, scope))
> +		return cursor;
> +	if (WARN_ON_ONCE(scope->tree != ref.tree || scope->node != ref.node ||
> +			 nest->tree != ref.tree || nest->node != ref.node ||
> +			 ref.slot >= scope->slots_count))
> +		return cursor;
> +	schema = ctlv_message_input_ref_node(ref);
> +	if (!schema)
> +		return cursor;
> +	indexed = &index->slots[scope->first_slot + ref.slot];
> +	*cursor = (struct ctlv_cursor){
> +		.msg = nest->msg,
> +		.parent = nest->attr,
> +		.attr = indexed->count ? (const void *)((const u8 *)nest->msg->root +
> +							indexed->first_offset) :
> +					 NULL,
> +		.tree = ref.tree,
> +		.node = ref.tree->node_schemas[ref.node].first_child + ref.slot,
> +		.depth = nest->depth + 1,
> +		.index_nest_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
> +		.index_scope = nest->index_nest_scope,
> +	};
> +	if (indexed->count && schema->kind == CTLV_SCHEMA_NEST) {
> +		if (WARN_ON_ONCE(indexed->first_nest >= index->nests_count))
> +			return cursor;
> +		cursor->index_nest_scope = index->nests[indexed->first_nest].first_scope;
> +	}
> +	return cursor;
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_input_attr, "CTLV");
> +
> +bool ctlv_input_cursor(const struct ctlv_cursor *cursor, struct ctlv_input_ref ref)
> +{
> +	const struct ctlv_message_input_scope *scope;
> +	const struct ctlv_node_schema *schema;
> +
> +	if (!ctlv_message_cursor_index(cursor))
> +		return false;
> +	schema = ctlv_message_input_ref_node(ref);
> +	if (!schema)
> +		return false;
> +	scope = &cursor->msg->index->scopes[cursor->index_scope];
> +	return !WARN_ON_ONCE(scope->tree != ref.tree ||
> +			     scope->node != ref.node ||
> +			     cursor->tree != ref.tree ||
> +			     cursor->node !=
> +				     ref.tree->node_schemas[ref.node].first_child +
> +					     ref.slot);
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_input_cursor, "CTLV");
> +
> +void ctlv_attr_next(struct ctlv_cursor *cursor)
> +{
> +	const struct ctlv_message_input_slot *slot;
> +	const struct ctlv_message_input_scope *scope;
> +	const struct ctlv_node_schema *node;
> +	const struct ctlv_attr *next;
> +	u32 next_nest;
> +	u32 next_offset;
> +
> +	slot = ctlv_message_cursor_index(cursor);
> +	if (!slot || WARN_ON_ONCE(!cursor->attr))
> +		return;
> +	next = (const void *)((const u8 *)cursor->attr +
> +			      ALIGN((size_t)cursor->attr->len, CTLV_ALIGNTO));
> +	next_offset = ctlv_message_offset(cursor->msg, next);
> +	if (next_offset == slot->end_offset) {
> +		cursor->attr = NULL;
> +		cursor->index_nest_scope = CTLV_MESSAGE_INPUT_INDEX_NONE;
> +		return;
> +	}
> +	if (WARN_ON_ONCE(next_offset > slot->end_offset))
> +		return;

Failure path hangs: this and other early returns in `ctlv_attr_next()` leave `cursor->attr` unchanged. The cursor then still passes `ctlv_attr_ok()`, so `CTLV_IN_FOR_EACH` / for (`ctlv_input_attr(...)`; `ctlv_attr_ok()`; `ctlv_attr_next()`) loops spin forever in the kernel (soft lockup) rather than terminating after the WARN. Every error return in this function must clear `cursor->attr` (and `index_nest_scope`) so the loop ends. The same applies to the `scopes_count`/`nests_count`/`next_nest` WARNs below, which return after `cursor->attr` was advanced but with a stale `index_nest_scope`; there `ctlv_attr_ok()` happens to fail via the revalidation, but that is by luck.

> +	cursor->attr = next;
> +	node = &cursor->tree->node_schemas[cursor->node];
> +	if (node->kind == CTLV_SCHEMA_NEST) {
> +		if (WARN_ON_ONCE(cursor->index_nest_scope >= cursor->msg->index->scopes_count))
> +			return;
> +		scope = &cursor->msg->index->scopes[cursor->index_nest_scope];
> +		if (WARN_ON_ONCE(scope->nest >= cursor->msg->index->nests_count))
> +			return;
> +		next_nest = cursor->msg->index->nests[scope->nest].next;
> +		if (WARN_ON_ONCE(next_nest == CTLV_MESSAGE_INPUT_INDEX_NONE ||
> +				 next_nest >= cursor->msg->index->nests_count))
> +			return;
> +		cursor->index_nest_scope = cursor->msg->index->nests[next_nest].first_scope;
> +	}
> +	WARN_ON_ONCE(!ctlv_message_cursor_index(cursor));
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_attr_next, "CTLV");
> +
> +bool ctlv_attr_ok(const struct ctlv_cursor *cursor)

The `cursor &&` test is inconsistent: every other accessor dereferences the cursor unconditionally, and `ctlv_input_attr()` always returns non-`NULL`. Drop it or justify it.

> +{
> +	return cursor && cursor->attr && ctlv_message_cursor_index(cursor);
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_attr_ok, "CTLV");
> +
> +/* The payload, checked against the kinds a reader of this C type accepts. */
> +static const void *ctlv_message_cursor_payload(const struct ctlv_cursor *cursor, u32 kinds,
> +					       u32 *len)
> +{
> +	const struct ctlv_node_schema *node;
> +
> +	if (!ctlv_message_cursor_index(cursor) || WARN_ON_ONCE(!cursor->attr))
> +		return NULL;
> +	node = &cursor->tree->node_schemas[cursor->node];
> +	if (WARN_ON_ONCE(!(kinds & BIT(node->kind))))
> +		return NULL;
> +	*len = ctlv_message_attr_payload_len(cursor->attr);
> +	return ctlv_message_attr_payload(cursor->attr);
> +}
> +
> +/* A length disagreeing with a validated kind is this core's mistake. */
> +static const void *ctlv_message_cursor_fixed(const struct ctlv_cursor *cursor, u32 kinds,
> +					     u32 expected)
> +{
> +	const void *payload;
> +	u32 len;
> +
> +	payload = ctlv_message_cursor_payload(cursor, kinds, &len);
> +	if (!payload || WARN_ON_ONCE(len != expected))
> +		return NULL;
> +	return payload;
> +}
> +
> +u64 ctlv_attr_u64(const struct ctlv_cursor *cursor)
> +{
> +	const u64 *value = ctlv_message_cursor_fixed(cursor,
> +						     BIT(CTLV_SCHEMA_UINT) | BIT(CTLV_SCHEMA_ENUM) |
> +							     BIT(CTLV_SCHEMA_FLAGS),
> +						     sizeof(u64));
> +
> +	return value ? *value : 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_attr_u64, "CTLV");
> +
> +s64 ctlv_attr_s64(const struct ctlv_cursor *cursor)
> +{
> +	const s64 *value = ctlv_message_cursor_fixed(cursor, BIT(CTLV_SCHEMA_SINT), sizeof(s64));
> +
> +	return value ? *value : 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_attr_s64, "CTLV");
> +
> +bool ctlv_attr_bool(const struct ctlv_cursor *cursor)
> +{
> +	const u8 *value = ctlv_message_cursor_fixed(cursor, BIT(CTLV_SCHEMA_BOOL), sizeof(u8));
> +
> +	return value ? *value : false;
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_attr_bool, "CTLV");
> +
> +const char *ctlv_attr_string(const struct ctlv_cursor *cursor)
> +{
> +	const char *value;
> +	u32 len;
> +
> +	value = ctlv_message_cursor_payload(cursor, BIT(CTLV_SCHEMA_STRING), &len);
> +	if (!value || WARN_ON_ONCE(!len || value[len - 1]))
> +		return NULL;
> +	return value;
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_attr_string, "CTLV");
> +
> +const void *ctlv_attr_binary(const struct ctlv_cursor *cursor, u32 *len)
> +{
> +	const void *value;
> +
> +	*len = 0;
> +	value = ctlv_message_cursor_payload(cursor, BIT(CTLV_SCHEMA_BINARY), len);

Redundant: `ctlv_message_cursor_payload()` only writes *len on its success path and returns a non-`NULL` pointer there, so the `if (!value) *len = 0;` below can never change anything (the initial *len = 0 already covers failure). Keep one of the two.

> +	if (!value)
> +		*len = 0;
> +	return value;
> +}
> +EXPORT_SYMBOL_NS_GPL(ctlv_attr_binary, "CTLV");
> +
> +/*
> + * The room is measured before anything is written, which is what leaves a
> + * refused attr's message exactly as it was, padding included.
> + */

Stray comment: this block (about writing/refusing an attr) is not attached to any code; it is the header of a function that belongs to the output patch (patch 6), left dangling at EOF here. Remove it from this patch.

> diff --git a/drivers/ctlv/message.h b/drivers/ctlv/message.h
> new file mode 100644
> index 000000000000..efd781af9b37
> --- /dev/null
> +++ b/drivers/ctlv/message.h
> @@ -0,0 +1,92 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
> + *
> + * Private message representation and nest walking.
> + */
> +#ifndef _CTLV_MESSAGE_H
> +#define _CTLV_MESSAGE_H
> +
> +#include <linux/ctlv_family.h>
> +#include <linux/types.h>
> +
> +#include <uapi/linux/ctlv.h>
> +
> +#define CTLV_MESSAGE_INPUT_INDEX_NONE U32_MAX
> +
> +/* Attr identity remains in the generated schema, not in this request index. */
> +struct ctlv_message_input_slot {
> +	u32 first_offset;
> +	u32 end_offset;
> +	u32 count;
> +	u32 excess_offset;
> +	u32 first_nest;
> +	u32 last_nest;
> +};
> +
> +struct ctlv_message_input_scope {
> +	const struct ctlv_schema_tree *tree;
> +	u32 nest;
> +	u32 node;
> +	u32 first_slot;
> +	u32 slots_count;
> +	bool optional;
> +};
> +
> +struct ctlv_message_input_nest {
> +	u32 offset;
> +	u32 first_scope;
> +	u32 scopes_count;
> +	u32 next;
> +};
> +
> +struct ctlv_message_input_index {
> +	struct ctlv_message_input_nest *nests;
> +	struct ctlv_message_input_scope *scopes;
> +	struct ctlv_message_input_slot *slots;
> +	u32 nests_count;
> +	u32 nests_capacity;
> +	u32 scopes_count;
> +	u32 scopes_capacity;
> +	u32 slots_count;
> +	u32 slots_capacity;
> +};
> +
> +struct ctlv_msg {

`struct ctlv_msg` has no documentation, and in this patch family, ctx, len and poison are never read or written (only root, tree, index and request are). poison in particular (an int with unexplained semantics) needs a comment. Same for `ctlv_message_input_slot.excess_offset` / `last_nest`, `ctlv_message_input_scope`.optional and the *`_capacity` members: none are used by `message.c`, so a reader of this patch cannot tell what they mean. Also the request/output split encoded as a bool inside one struct means the index pointer is only meaningful for request == true; consider documenting or splitting.

> +	struct ctlv_attr *root;
> +	size_t len;
> +	const struct ctlv_schema_tree *tree;
> +	const struct ctlv_family *family;
> +	struct ctlv_op_ctx *ctx;
> +	struct ctlv_message_input_index *index;
> +	bool request;
> +	int poison;
> +};
> +
> +enum ctlv_message_step {
> +	CTLV_MESSAGE_STEP_ATTR,
> +	CTLV_MESSAGE_STEP_END,
> +	CTLV_MESSAGE_STEP_BAD,
> +};
> +
> +enum ctlv_message_step ctlv_message_next(const struct ctlv_attr *nest,
> +					 const struct ctlv_attr **child);
> +u32 ctlv_message_offset(const struct ctlv_msg *msg,
> +			const struct ctlv_attr *attr);
> +void ctlv_message_root(struct ctlv_cursor *cursor, struct ctlv_msg *msg);
> +void ctlv_message_index_free(struct ctlv_msg *msg);

Declared but not defined in this patch (defined in `schema.c`, patch 7). Move the declaration with its definition.

> +bool ctlv_message_input_rebind(struct ctlv_cursor *nest,
> +			       const struct ctlv_schema_tree *tree, u32 node);
> +
> +static inline u32 ctlv_message_attr_payload_len(const struct ctlv_attr *attr)
> +{
> +	return attr->len - sizeof(*attr);
> +}
> +
> +static inline const void *
> +ctlv_message_attr_payload(const struct ctlv_attr *attr)
> +{
> +	return attr + 1;
> +}
> +
> +#endif /* _CTLV_MESSAGE_H */
> diff --git a/include/linux/ctlv_family.h b/include/linux/ctlv_family.h
> index 1ae4d3a536f7..eae690b9c1d5 100644
> --- a/include/linux/ctlv_family.h
> +++ b/include/linux/ctlv_family.h
> @@ -313,4 +313,142 @@ struct ctlv_family {
>  
>  extern const struct ctlv_family ctlv_family;
>  
> +struct ctlv_cursor *
> +ctlv_input_attr(struct ctlv_cursor *cursor, const struct ctlv_cursor *nest,
> +		struct ctlv_input_ref ref);
> +bool ctlv_input_cursor(const struct ctlv_cursor *cursor,
> +		       struct ctlv_input_ref ref);
> +void ctlv_attr_next(struct ctlv_cursor *cursor);
> +bool ctlv_attr_ok(const struct ctlv_cursor *cursor);
> +u64 ctlv_attr_u64(const struct ctlv_cursor *cursor);
> +s64 ctlv_attr_s64(const struct ctlv_cursor *cursor);
> +bool ctlv_attr_bool(const struct ctlv_cursor *cursor);
> +const char *ctlv_attr_string(const struct ctlv_cursor *cursor);
> +const void *ctlv_attr_binary(const struct ctlv_cursor *cursor, u32 *len);
> +
> +#define CTLV_IN_REF(_name, _tree, _attr_id, _node, _slot, _kind) \
> +static inline struct ctlv_input_ref _name##_ref(void)		\
> +{								\
> +	return (struct ctlv_input_ref){				\
> +		.tree = (_tree),					\
> +		.attr_id = (_attr_id),				\
> +		.node = (_node),					\
> +		.slot = (_slot),					\
> +		.kind = (_kind),					\
> +	};							\
> +}
> +
> +#define CTLV_IN_FOR_EACH(_name, _attr, _nest)			\
> +	for (struct ctlv_cursor *__ctlv_attr =			\
> +		ctlv_input_attr(_attr, _nest, _name##_ref());	\
> +	     ctlv_attr_ok(__ctlv_attr);				\
> +	     ctlv_attr_next(__ctlv_attr))
> +
> +#define CTLV_IN_HAS(_name)					\
> +static inline bool _name##_present(const struct ctlv_cursor *nest) \
> +{								\
> +	struct ctlv_cursor attr;				\
> +								\
> +	ctlv_input_attr(&attr, nest, _name##_ref());		\
> +	return ctlv_attr_ok(&attr);				\
> +}
> +
> +#define CTLV_IN_U64(_name)					\
> +static inline u64 _name##_get(const struct ctlv_cursor *nest)	\
> +{								\
> +	struct ctlv_cursor attr;				\
> +								\
> +	ctlv_input_attr(&attr, nest, _name##_ref());		\
> +	return ctlv_attr_u64(&attr);				\
> +}
> +
> +#define CTLV_IN_S64(_name)					\

Unused API: no generated header in the series (including the attestation and TDX families at HEAD) instantiates `CTLV_IN_S64`, `CTLV_IN_BOOL`, `CTLV_IN_NEST`, `CTLV_IN_EACH_S64`, `CTLV_IN_EACH_BOOL`, `CTLV_IN_EACH_STRING` or `CTLV_IN_EACH_BINARY`, and so `ctlv_attr_s64()`, `ctlv_attr_bool()` (and their `EXPORT_SYMBOL_NS_GPL`) have no user. Speculative generality: add them with the first spec that has such a request attr. Note that `ctlv_family.h` additions also have no kernel-doc at all although the neighbouring API in `ctlv.h` is documented; the semantics (cursor lifetime tied to the request, `NULL`/0 on mismatch after a WARN, string pointing into the request buffer) are non-obvious.

> +static inline s64 _name##_get(const struct ctlv_cursor *nest)	\
> +{								\
> +	struct ctlv_cursor attr;				\
> +								\
> +	ctlv_input_attr(&attr, nest, _name##_ref());		\
> +	return ctlv_attr_s64(&attr);				\
> +}
> +
> +#define CTLV_IN_BOOL(_name)					\
> +static inline bool _name##_get(const struct ctlv_cursor *nest)	\
> +{								\
> +	struct ctlv_cursor attr;				\
> +								\
> +	ctlv_input_attr(&attr, nest, _name##_ref());		\
> +	return ctlv_attr_bool(&attr);				\
> +}
> +
> +#define CTLV_IN_STRING(_name)					\
> +static inline const char *					\
> +_name##_get(const struct ctlv_cursor *nest)			\
> +{								\
> +	struct ctlv_cursor attr;				\
> +								\
> +	ctlv_input_attr(&attr, nest, _name##_ref());		\
> +	return ctlv_attr_string(&attr);				\
> +}
> +
> +#define CTLV_IN_BINARY(_name)					\
> +static inline const void *					\
> +_name##_get(const struct ctlv_cursor *nest, u32 *len)		\
> +{								\
> +	struct ctlv_cursor attr;				\
> +								\
> +	ctlv_input_attr(&attr, nest, _name##_ref());		\
> +	return ctlv_attr_binary(&attr, len);			\
> +}
> +
> +#define CTLV_IN_NEST(_name)					\
> +static inline void _name##_get(struct ctlv_cursor *nested,	\
> +			       const struct ctlv_cursor *nest)	\
> +{								\
> +	ctlv_input_attr(nested, nest, _name##_ref());		\
> +}
> +
> +#define CTLV_IN_EACH_U64(_name)					\
> +static inline u64 _name##_get(const struct ctlv_cursor *attr)	\
> +{								\
> +	if (!ctlv_input_cursor(attr, _name##_ref()))		\
> +		return 0;					\
> +	return ctlv_attr_u64(attr);				\
> +}
> +
> +#define CTLV_IN_EACH_S64(_name)					\
> +static inline s64 _name##_get(const struct ctlv_cursor *attr)	\
> +{								\
> +	if (!ctlv_input_cursor(attr, _name##_ref()))		\
> +		return 0;					\
> +	return ctlv_attr_s64(attr);				\
> +}
> +
> +#define CTLV_IN_EACH_BOOL(_name)				\
> +static inline bool _name##_get(const struct ctlv_cursor *attr)	\
> +{								\
> +	if (!ctlv_input_cursor(attr, _name##_ref()))		\
> +		return false;					\
> +	return ctlv_attr_bool(attr);				\
> +}
> +
> +#define CTLV_IN_EACH_STRING(_name)				\
> +static inline const char *					\
> +_name##_get(const struct ctlv_cursor *attr)			\
> +{								\
> +	if (!ctlv_input_cursor(attr, _name##_ref()))		\
> +		return NULL;					\
> +	return ctlv_attr_string(attr);				\
> +}
> +
> +#define CTLV_IN_EACH_BINARY(_name)				\
> +static inline const void *					\
> +_name##_get(const struct ctlv_cursor *attr, u32 *len)		\
> +{								\
> +	if (!ctlv_input_cursor(attr, _name##_ref())) {		\
> +		*len = 0;					\
> +		return NULL;					\
> +	}							\
> +	return ctlv_attr_binary(attr, len);			\
> +}
> +
>  #endif /* _LINUX_CTLV_FAMILY_H */
> -- 
> 2.55.0
>

Summary of the most serious issues in this patch:
- `ctlv_attr_next()` error paths leave `cursor->attr` unchanged, so for-each loops can spin forever after a WARN.
- `ctlv_input_attr()` WARNs (and aliasing zeroes nest) when handed a `NULL`-attr nest, which is what an absent optional nest produces, so userspace can trigger the WARN.
- Pervasive redundant self-revalidation (about 5x per iteration) of a core-built index through `WARN_ON_ONCE`; dead if the core is correct, costly if not gated.
- Bisectability: Kconfig/Makefile are unreachable, `message.c` is not compiled until patch 11; `ctlv_message_index_free()` declared with no definition; `internal.h` and many struct fields are only used patches later.
- Cleanup: orphan trailing comment, unused includes, redundant *len reset, unused `CTLV_IN_*`/`ctlv_attr_s64`/bool API, menuconfig without children, no kernel-doc.
- Commit message claims request indexing that this patch does not implement.
