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 07/20] ctlv: Prepare schema validation
Date: Thu, 15 Oct 2026 16:50:40 +0200	[thread overview]
Message-ID: <20261015145053.3664509-8-jiri@resnulli.us> (raw)
In-Reply-To: <20261015145053.3664509-1-jiri@resnulli.us>

From: Jiri Pirko <jiri@nvidia.com>

Add generated-schema walking, message validation and request indexing.
Use the descriptor layer for blob placement checks.

Keep CONFIG_CTLV unwired until dispatch completes the module.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
 drivers/ctlv/Makefile |    2 +-
 drivers/ctlv/schema.c | 1047 +++++++++++++++++++++++++++++++++++++++++
 drivers/ctlv/schema.h |   50 ++
 3 files changed, 1098 insertions(+), 1 deletion(-)
 create mode 100644 drivers/ctlv/schema.c
 create mode 100644 drivers/ctlv/schema.h

diff --git a/drivers/ctlv/Makefile b/drivers/ctlv/Makefile
index 5edc0b4b973f..3fd656835b8a 100644
--- a/drivers/ctlv/Makefile
+++ b/drivers/ctlv/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_CTLV) += ctlv.o
 
-ctlv-y += blob_desc.o message.o
+ctlv-y += blob_desc.o message.o schema.o
diff --git a/drivers/ctlv/schema.c b/drivers/ctlv/schema.c
new file mode 100644
index 000000000000..b74f9a358bc3
--- /dev/null
+++ b/drivers/ctlv/schema.c
@@ -0,0 +1,1047 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
+ *
+ * Generated metadata remains owner-local; effective schemas walk the chain.
+ */
+#include <linux/align.h>
+#include <linux/bits.h>
+#include <linux/ctlv.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <uapi/linux/ctlv.h>
+#include <uapi/linux/ctlv_gen.h>
+
+#include "blob_desc.h"
+#include "internal.h"
+#include "message.h"
+#include "schema.h"
+
+/* Every 64-bit payload, and every blob descriptor member, is this long. */
+#define CTLV_SCHEMA_SCALAR_ATTR_LEN (sizeof(struct ctlv_attr) + sizeof(u64))
+
+/*
+ * Where composing a tree's maximum length gives up, which keeps the check on a
+ * composed length from being passed by a total that wrapped.
+ */
+#define CTLV_SCHEMA_MAX_COMPOSED_LEN CTLV_MAX_EVENT_QUEUE_LEN
+
+const struct ctlv_family *ctlv_family_ancestor(const struct ctlv_family *family, u32 up)
+{
+	while (up-- && family)
+		family = family->parent;
+	return family;
+}
+
+u32 ctlv_family_levels(const struct ctlv_family *family)
+{
+	const struct ctlv_family *ancestor;
+	u32 levels = 0;
+
+	for (ancestor = family; ancestor; ancestor = ancestor->parent)
+		levels++;
+	return levels;
+}
+
+void ctlv_schema_cursor_init(struct ctlv_schema_cursor *cursor, const struct ctlv_family *family)
+{
+	cursor->target = family;
+	cursor->level = ctlv_family_levels(family);
+	cursor->family = NULL;
+	cursor->index = 0;
+}
+
+const struct ctlv_op_schema *ctlv_schema_next_op(struct ctlv_schema_cursor *cursor,
+						 const struct ctlv_family **family)
+{
+	while (cursor->level) {
+		const struct ctlv_family_schema *schema;
+
+		if (!cursor->family)
+			cursor->family = ctlv_family_ancestor(cursor->target, cursor->level - 1);
+		schema = cursor->family->schema;
+		if (cursor->index < schema->ops_count) {
+			*family = cursor->family;
+			return &schema->ops[cursor->index++];
+		}
+		cursor->level--;
+		cursor->family = NULL;
+		cursor->index = 0;
+	}
+	return NULL;
+}
+
+bool ctlv_schema_member(const struct ctlv_family *family, const struct ctlv_schema_tree *tree,
+			u32 node, u64 attr_id, struct ctlv_schema_pos *member)
+{
+	const struct ctlv_node_schema *nest;
+	u32 index;
+
+	if (!tree->node_schemas || node >= tree->node_schemas_count)
+		return false;
+	nest = &tree->node_schemas[node];
+
+	for (index = 0; index < nest->children_count; index++) {
+		u32 child = nest->first_child + index;
+
+		if (tree->node_schemas[child].attr_id == attr_id) {
+			*member = (struct ctlv_schema_pos){
+				.tree = tree,
+				.node = child,
+			};
+			return true;
+		}
+	}
+	for (index = 0; index < tree->stub_schemas_count; index++) {
+		const struct ctlv_stub_schema *stub_schema = &tree->stub_schemas[index];
+		const struct ctlv_stub_fill_schema *fill_schema;
+
+		if (stub_schema->node != node)
+			continue;
+		fill_schema = ctlv_schema_fill(family, stub_schema->family_id, stub_schema->set_id);
+		/* Filled-stub members are children of the fill schema's root. */
+		if (fill_schema &&
+		    ctlv_schema_member(family, &fill_schema->members, 0, attr_id, member))
+			return true;
+	}
+	return false;
+}
+
+const struct ctlv_stub_fill_schema *ctlv_schema_fill(const struct ctlv_family *family,
+						     u16 family_id, u32 set_id)
+{
+	const struct ctlv_family *owner;
+	u32 index;
+
+	for (owner = family; owner; owner = owner->parent) {
+		const struct ctlv_family_schema *schema = owner->schema;
+
+		for (index = 0; index < schema->stub_fill_schemas_count; index++)
+			if (schema->stub_fill_schemas[index].family_id == family_id &&
+			    schema->stub_fill_schemas[index].set_id == set_id)
+				return &schema->stub_fill_schemas[index];
+	}
+	return NULL;
+}
+
+/*
+ * The same arithmetic the generator did, over the tree it emitted plus this
+ * family's stub fill schemas, since a fill makes an extension point longer.
+ */
+
+struct ctlv_schema_length_walk {
+	const struct ctlv_family *family;
+	bool bad;
+};
+
+static u64 ctlv_schema_children_max_len(struct ctlv_schema_length_walk *walk,
+					const struct ctlv_schema_tree *tree, u32 index, u32 depth);
+
+static u64 ctlv_schema_payload_max_len(struct ctlv_schema_length_walk *walk,
+				       const struct ctlv_schema_tree *tree, u32 index, u32 depth)
+{
+	const struct ctlv_node_schema *node = &tree->node_schemas[index];
+
+	switch (node->kind) {
+	case CTLV_SCHEMA_UINT:
+	case CTLV_SCHEMA_SINT:
+	case CTLV_SCHEMA_ENUM:
+	case CTLV_SCHEMA_FLAGS:
+		return sizeof(u64);
+	case CTLV_SCHEMA_BOOL:
+		return 1;
+	case CTLV_SCHEMA_STRING:
+		/*
+		 * One terminating NUL, which the declared maximum excludes.
+		 * Widened first so that a maximum at U32_MAX cannot wrap.
+		 */
+		return (u64)node->length.max_len + 1;
+	case CTLV_SCHEMA_BINARY:
+		return node->length.max_len;
+	case CTLV_SCHEMA_BLOB:
+		/*
+		 * Type, length and an address; anything but a bare VA is one
+		 * member wider, an fd with an optional offset.
+		 */
+		return (node->blob.backings == BIT(CTLV_BLOB_TYPE_VA) ? 3 : 4) *
+		       ALIGN(CTLV_SCHEMA_SCALAR_ATTR_LEN, CTLV_ALIGNTO);
+	default:
+		return ctlv_schema_children_max_len(walk, tree, index, depth);
+	}
+}
+
+static u64 ctlv_schema_attr_max_len(struct ctlv_schema_length_walk *walk,
+				    const struct ctlv_schema_tree *tree, u32 index, u32 depth)
+{
+	return sizeof(struct ctlv_attr) + ctlv_schema_payload_max_len(walk, tree, index, depth);
+}
+
+static u64 ctlv_schema_children_max_len(struct ctlv_schema_length_walk *walk,
+					const struct ctlv_schema_tree *tree, u32 index, u32 depth)
+{
+	const struct ctlv_node_schema *node = &tree->node_schemas[index];
+	u64 total = 0;
+	u32 i;
+
+	if (depth > CTLV_MAX_DECLARED_NEST_DEPTH) {
+		walk->bad = true;
+		return 0;
+	}
+	for (i = 0; i < node->children_count; i++) {
+		u32 child = node->first_child + i;
+		u64 each, all;
+
+		/*
+		 * Neither an unlimited placement nor one that re-enters has a
+		 * maximum, and only a query reply may hold either.
+		 */
+		if (tree->node_schemas[child].reenters ||
+		    !tree->node_schemas[child].max_occurrences) {
+			walk->bad = true;
+			return 0;
+		}
+		each = ALIGN(ctlv_schema_attr_max_len(walk, tree, child, depth + 1), CTLV_ALIGNTO);
+		if (check_mul_overflow((u64)tree->node_schemas[child].max_occurrences, each,
+				       &all) ||
+		    check_add_overflow(total, all, &total) ||
+		    total > CTLV_SCHEMA_MAX_COMPOSED_LEN) {
+			walk->bad = true;
+			return 0;
+		}
+	}
+	for (i = 0; i < tree->stub_schemas_count; i++) {
+		const struct ctlv_stub_schema *stub_schema = &tree->stub_schemas[i];
+		const struct ctlv_stub_fill_schema *fill_schema;
+
+		if (stub_schema->node != index)
+			continue;
+		fill_schema =
+			ctlv_schema_fill(walk->family, stub_schema->family_id, stub_schema->set_id);
+		if (!fill_schema)
+			continue;
+		if (check_add_overflow(total,
+				       ctlv_schema_children_max_len(walk, &fill_schema->members, 0,
+								    depth),
+				       &total) ||
+		    total > CTLV_SCHEMA_MAX_COMPOSED_LEN) {
+			walk->bad = true;
+			return 0;
+		}
+	}
+	return total;
+}
+
+/* Buffer length that the largest indivisible leaf and its nests need. */
+static u64 ctlv_schema_min_len(struct ctlv_schema_length_walk *walk,
+			       const struct ctlv_schema_tree *tree, u32 index, u32 above, u32 depth)
+{
+	const struct ctlv_node_schema *node = &tree->node_schemas[index];
+	bool has_children = false;
+	u64 deepest = 0;
+	u32 i;
+
+	if (depth > CTLV_MAX_DECLARED_NEST_DEPTH) {
+		walk->bad = true;
+		return 0;
+	}
+	for (i = 0; i < node->children_count; i++) {
+		u32 child = node->first_child + i;
+
+		has_children = true;
+		deepest = max(deepest,
+			      ctlv_schema_min_len(walk, tree, child,
+						  above + sizeof(struct ctlv_attr), depth + 1));
+	}
+	for (i = 0; i < tree->stub_schemas_count; i++) {
+		const struct ctlv_stub_schema *stub_schema = &tree->stub_schemas[i];
+		const struct ctlv_stub_fill_schema *fill_schema;
+		const struct ctlv_node_schema *root;
+		u32 member;
+
+		if (stub_schema->node != index)
+			continue;
+		fill_schema =
+			ctlv_schema_fill(walk->family, stub_schema->family_id, stub_schema->set_id);
+		if (!fill_schema)
+			continue;
+		root = &fill_schema->members.node_schemas[0];
+		for (member = 0; member < root->children_count; member++) {
+			has_children = true;
+			deepest = max(deepest, ctlv_schema_min_len(walk, &fill_schema->members,
+								   root->first_child + member,
+								   above + sizeof(struct ctlv_attr),
+								   depth + 1));
+		}
+	}
+	if (has_children)
+		return deepest;
+	/* An empty root is a message of its own header alone. */
+	if (!index)
+		return above + sizeof(struct ctlv_attr);
+	return above + ALIGN(ctlv_schema_attr_max_len(walk, tree, index, depth), CTLV_ALIGNTO);
+}
+
+static u64 ctlv_schema_root_max_len(struct ctlv_schema_length_walk *walk,
+				    const struct ctlv_schema_tree *tree)
+{
+	return sizeof(struct ctlv_attr) + ctlv_schema_children_max_len(walk, tree, 0, 0);
+}
+
+/* Size one deepest legal leaf and every nest above it. */
+static u64 ctlv_schema_reentering_min_len(struct ctlv_schema_length_walk *walk,
+					  const struct ctlv_schema_tree *tree)
+{
+	u64 leaf = 0;
+	u32 index;
+
+	for (index = 1; index < tree->node_schemas_count; index++) {
+		if (tree->node_schemas[index].kind == CTLV_SCHEMA_NEST)
+			continue;
+		leaf = max(leaf,
+			   ALIGN(ctlv_schema_attr_max_len(walk, tree, index, 0), CTLV_ALIGNTO));
+	}
+	return (CTLV_MAX_NEST_DEPTH + 1) * sizeof(struct ctlv_attr) + leaf;
+}
+
+static bool ctlv_schema_tree_reenters(const struct ctlv_schema_tree *tree)
+{
+	u32 index;
+
+	for (index = 0; index < tree->node_schemas_count; index++)
+		if (tree->node_schemas[index].reenters)
+			return true;
+	return false;
+}
+
+int ctlv_schema_op_sizes(const struct ctlv_family *family, const struct ctlv_op_schema *op,
+			 struct ctlv_op_sizes *sizes)
+{
+	struct ctlv_schema_length_walk walk = { .family = family };
+	u64 request = 0, reply = 0, record = 0;
+
+	if (op->request.node_schemas)
+		request = ctlv_schema_root_max_len(&walk, &op->request);
+
+	switch (op->type) {
+	case CTLV_OP_TYPE_ACTION:
+		if (op->reply.node_schemas)
+			reply = ctlv_schema_root_max_len(&walk, &op->reply);
+		break;
+	case CTLV_OP_TYPE_QUERY:
+		if (ctlv_schema_tree_reenters(&op->reply))
+			reply = ctlv_schema_reentering_min_len(&walk, &op->reply);
+		else
+			reply = ctlv_schema_min_len(&walk, &op->reply, 0, 0, 0);
+		break;
+	case CTLV_OP_TYPE_EVENT:
+		record = sizeof(struct ctlv_event_hdr) +
+			 ALIGN(ctlv_schema_root_max_len(&walk, &op->reply), CTLV_ALIGNTO);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (walk.bad || request > CTLV_MAX_INLINE_MESSAGE_LEN ||
+	    reply > CTLV_MAX_INLINE_MESSAGE_LEN || record > CTLV_MAX_EVENT_QUEUE_LEN)
+		return -EINVAL;
+
+	*sizes = (struct ctlv_op_sizes){ .request_max_len = request };
+	switch (op->type) {
+	case CTLV_OP_TYPE_ACTION:
+		sizes->action_reply_max_len = reply;
+		break;
+	case CTLV_OP_TYPE_QUERY:
+		sizes->query_reply_min_len = reply;
+		/*
+		 * What the specification asked for, unless a filled extension
+		 * point made one fragment longer than that.
+		 */
+		sizes->query_reply_suggested_len =
+			max_t(u32, op->sizes.query_reply_suggested_len, reply);
+		break;
+	default:
+		sizes->event_record_max_len = record;
+		break;
+	}
+	return 0;
+}
+
+/*
+ * Only a completely valid request reaches family code; nothing is repaired or
+ * normalized. The first failure is the one recorded, and the walk stops there.
+ */
+
+struct ctlv_schema_validation {
+	struct ctlv_msg *msg;
+	struct ctlv_error *error;
+};
+
+void ctlv_message_index_free(struct ctlv_msg *msg)
+{
+	struct ctlv_message_input_index *index;
+
+	if (!msg || !msg->index)
+		return;
+	index = msg->index;
+	kvfree(index->slots);
+	kvfree(index->scopes);
+	kvfree(index->nests);
+	kfree(index);
+	msg->index = NULL;
+}
+
+static void *ctlv_schema_index_grow(void *items, u32 *capacity, u32 needed, size_t item_size)
+{
+	u32 grown = max_t(u32, *capacity, 16);
+	size_t bytes;
+	void *new;
+
+	if (needed <= *capacity)
+		return items;
+	while (grown < needed) {
+		if (grown > U32_MAX / 2)
+			return ERR_PTR(-EOVERFLOW);
+		grown *= 2;
+	}
+	if (check_mul_overflow((size_t)grown, item_size, &bytes))
+		return ERR_PTR(-EOVERFLOW);
+	new = kvrealloc(items, bytes, GFP_KERNEL_ACCOUNT);
+	if (!new)
+		return ERR_PTR(-ENOMEM);
+	*capacity = grown;
+	return new;
+}
+
+static int ctlv_schema_index_slots_add(struct ctlv_message_input_index *index, u32 count,
+				       u32 *first)
+{
+	struct ctlv_message_input_slot *slots;
+	u32 needed;
+	u32 i;
+
+	if (check_add_overflow(index->slots_count, count, &needed))
+		return -EOVERFLOW;
+	slots = ctlv_schema_index_grow(index->slots, &index->slots_capacity, needed,
+				       sizeof(*slots));
+	if (IS_ERR(slots))
+		return PTR_ERR(slots);
+	index->slots = slots;
+	*first = index->slots_count;
+	for (i = index->slots_count; i < needed; i++)
+		index->slots[i] = (struct ctlv_message_input_slot){
+			.excess_offset = CTLV_MESSAGE_INPUT_INDEX_NONE,
+			.first_nest = CTLV_MESSAGE_INPUT_INDEX_NONE,
+			.last_nest = CTLV_MESSAGE_INPUT_INDEX_NONE,
+		};
+	index->slots_count = needed;
+	return 0;
+}
+
+static int ctlv_schema_index_scope_add(struct ctlv_schema_validation *v, u32 nest_index,
+				       const struct ctlv_schema_tree *tree, u32 node, bool optional)
+{
+	struct ctlv_message_input_index *index = v->msg->index;
+	const struct ctlv_node_schema *parent = &tree->node_schemas[node];
+	struct ctlv_message_input_scope *scopes;
+	u32 needed;
+	u32 first_slot;
+	u32 scope_index;
+	u32 i;
+	int ret;
+
+	if (check_add_overflow(index->scopes_count, 1U, &needed))
+		return -EOVERFLOW;
+	scopes = ctlv_schema_index_grow(index->scopes, &index->scopes_capacity, needed,
+					sizeof(*scopes));
+	if (IS_ERR(scopes))
+		return PTR_ERR(scopes);
+	index->scopes = scopes;
+	ret = ctlv_schema_index_slots_add(index, parent->children_count, &first_slot);
+	if (ret)
+		return ret;
+	scope_index = index->scopes_count++;
+	index->scopes[scope_index] = (struct ctlv_message_input_scope){
+		.tree = tree,
+		.nest = nest_index,
+		.node = node,
+		.first_slot = first_slot,
+		.slots_count = parent->children_count,
+		.optional = optional,
+	};
+	index->nests[nest_index].scopes_count++;
+
+	for (i = 0; i < tree->stub_schemas_count; i++) {
+		const struct ctlv_stub_schema *stub_schema = &tree->stub_schemas[i];
+		const struct ctlv_stub_fill_schema *fill_schema;
+
+		if (stub_schema->node != node)
+			continue;
+		fill_schema = ctlv_schema_fill(v->msg->family, stub_schema->family_id,
+					       stub_schema->set_id);
+		if (!fill_schema)
+			continue;
+		ret = ctlv_schema_index_scope_add(v, nest_index, &fill_schema->members, 0,
+						  optional || stub_schema->optional);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int ctlv_schema_index_nest_add(struct ctlv_schema_validation *v,
+				      const struct ctlv_schema_tree *tree, u32 node,
+				      const struct ctlv_attr *nest_attr, u32 *nest_index)
+{
+	struct ctlv_message_input_index *index = v->msg->index;
+	struct ctlv_message_input_nest *nests;
+	u32 needed;
+	u32 added;
+	int ret;
+
+	if (check_add_overflow(index->nests_count, 1U, &needed))
+		return -EOVERFLOW;
+	nests = ctlv_schema_index_grow(index->nests, &index->nests_capacity, needed,
+				       sizeof(*nests));
+	if (IS_ERR(nests))
+		return PTR_ERR(nests);
+	index->nests = nests;
+	added = index->nests_count++;
+	index->nests[added] = (struct ctlv_message_input_nest){
+		.offset = ctlv_message_offset(v->msg, nest_attr),
+		.first_scope = index->scopes_count,
+		.next = CTLV_MESSAGE_INPUT_INDEX_NONE,
+	};
+	ret = ctlv_schema_index_scope_add(v, added, tree, node, false);
+	if (ret)
+		return ret;
+	*nest_index = added;
+	return 0;
+}
+
+/* A request is never a fragment: only a reply of a query is ever incomplete. */
+#define CTLV_SCHEMA_ATTR_F_REQUEST (CTLV_ATTR_F_NESTED | CTLV_ATTR_F_IGNORE_IF_UNKNOWN)
+#define CTLV_SCHEMA_ATTR_F_REPLY CTLV_ATTR_F_NESTED
+
+/*
+ * Record the first failure and refuse the message. @attr is NULL when what is
+ * wrong with the nest is something it does not carry.
+ */
+static int ctlv_schema_bad(struct ctlv_schema_validation *v, u32 reason,
+			   const struct ctlv_attr *nest, const struct ctlv_attr *attr)
+{
+	*v->error = (struct ctlv_error){
+		.reason = reason,
+		.bad_attr_offset = ctlv_message_offset(v->msg, attr ?: nest),
+		.nest_offset = ctlv_message_offset(v->msg, nest),
+	};
+	return -EINVAL;
+}
+
+/* The one failure that names an attr the message does not contain. */
+static int ctlv_schema_bad_missing(struct ctlv_schema_validation *v, const struct ctlv_attr *nest,
+				   u64 attr_id)
+{
+	ctlv_schema_bad(v, CTLV_VALIDATION_REASON_MISSING_ATTR, nest, NULL);
+	v->error->missing_attr_id = attr_id;
+	return -EINVAL;
+}
+
+static struct ctlv_message_input_slot *ctlv_schema_index_slot(struct ctlv_schema_validation *v,
+							      u32 nest_index,
+							      const struct ctlv_schema_pos *pos)
+{
+	struct ctlv_message_input_index *index = v->msg->index;
+	const struct ctlv_message_input_nest *nest;
+	u32 i;
+
+	if (WARN_ON_ONCE(nest_index >= index->nests_count))
+		return NULL;
+	nest = &index->nests[nest_index];
+	for (i = 0; i < nest->scopes_count; i++) {
+		const struct ctlv_message_input_scope *scope =
+			&index->scopes[nest->first_scope + i];
+		const struct ctlv_node_schema *parent;
+		u32 slot;
+
+		if (scope->tree != pos->tree)
+			continue;
+		parent = &scope->tree->node_schemas[scope->node];
+		if (pos->node < parent->first_child ||
+		    pos->node >= parent->first_child + scope->slots_count)
+			continue;
+		slot = pos->node - parent->first_child;
+		return &index->slots[scope->first_slot + slot];
+	}
+	WARN_ON_ONCE(1);
+	return NULL;
+}
+
+static int ctlv_schema_index_record(struct ctlv_schema_validation *v, u32 nest_index,
+				    const struct ctlv_schema_pos *pos, const struct ctlv_attr *attr,
+				    bool *seen)
+{
+	struct ctlv_message_input_slot *slot = ctlv_schema_index_slot(v, nest_index, pos);
+	const struct ctlv_node_schema *schema = &pos->tree->node_schemas[pos->node];
+	u32 offset = ctlv_message_offset(v->msg, attr);
+
+	if (!slot)
+		return -EINVAL;
+	*seen = slot->count;
+	if (!slot->count)
+		slot->first_offset = offset;
+	slot->count++;
+	if (schema->max_occurrences && slot->count > schema->max_occurrences &&
+	    slot->excess_offset == CTLV_MESSAGE_INPUT_INDEX_NONE)
+		slot->excess_offset = offset;
+	slot->end_offset = offset + ALIGN((size_t)attr->len, CTLV_ALIGNTO);
+	return 0;
+}
+
+static void ctlv_schema_index_nest_link(struct ctlv_message_input_index *index,
+					struct ctlv_message_input_slot *slot, u32 nest_index)
+{
+	if (slot->first_nest == CTLV_MESSAGE_INPUT_INDEX_NONE)
+		slot->first_nest = nest_index;
+	else
+		index->nests[slot->last_nest].next = nest_index;
+	slot->last_nest = nest_index;
+}
+
+static int ctlv_schema_index_required(struct ctlv_schema_validation *v, u32 nest_index,
+				      const struct ctlv_attr *nest_attr)
+{
+	const struct ctlv_message_input_index *index = v->msg->index;
+	const struct ctlv_message_input_nest *nest = &index->nests[nest_index];
+	u32 i;
+
+	for (i = 0; i < nest->scopes_count; i++) {
+		const struct ctlv_message_input_scope *scope =
+			&index->scopes[nest->first_scope + i];
+		const struct ctlv_node_schema *parent = &scope->tree->node_schemas[scope->node];
+		u32 slot;
+
+		for (slot = 0; slot < scope->slots_count; slot++) {
+			const struct ctlv_message_input_slot *indexed;
+			const struct ctlv_node_schema *child =
+				&scope->tree->node_schemas[parent->first_child + slot];
+			const struct ctlv_attr *excess;
+
+			indexed = &index->slots[scope->first_slot + slot];
+			if (!indexed->count && slot < parent->required_children_count &&
+			    !scope->optional)
+				return ctlv_schema_bad_missing(v, nest_attr, child->attr_id);
+			if (!child->max_occurrences || indexed->count <= child->max_occurrences)
+				continue;
+			if (WARN_ON_ONCE(indexed->excess_offset == CTLV_MESSAGE_INPUT_INDEX_NONE))
+				return -EINVAL;
+			excess = (const void *)((const u8 *)v->msg->root + indexed->excess_offset);
+			return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_TOO_MANY_OCCURRENCES,
+					       nest_attr, excess);
+		}
+	}
+	return 0;
+}
+
+/* The bits an attr of this message may carry, whichever way it travelled. */
+static u32 ctlv_schema_flags_allowed(const struct ctlv_schema_validation *v)
+{
+	return v->msg->request ? CTLV_SCHEMA_ATTR_F_REQUEST : CTLV_SCHEMA_ATTR_F_REPLY;
+}
+
+static u64 ctlv_schema_scalar_value(const struct ctlv_attr *attr)
+{
+	/* Aligned by the wire format: the root began an allocation. */
+	return *(const u64 *)ctlv_message_attr_payload(attr);
+}
+
+bool ctlv_schema_value_ok(const struct ctlv_node_schema *node, u64 value)
+{
+	switch (node->kind) {
+	case CTLV_SCHEMA_SINT:
+		return (s64)value >= (s64)node->range.min && (s64)value <= (s64)node->range.max;
+	case CTLV_SCHEMA_FLAGS:
+		/* A bit no family declared is one nothing can be told about. */
+		return !(value & ~node->flags_mask);
+	case CTLV_SCHEMA_BOOL:
+		return value <= 1;
+	default:
+		/*
+		 * An enum's values are numbered from one in declaration order
+		 * and never skipped, so its range bounds it like an integer.
+		 */
+		return value >= node->range.min && value <= node->range.max;
+	}
+}
+
+bool ctlv_schema_length_ok(const struct ctlv_node_schema *node, u32 len)
+{
+	return len >= node->length.min_len && len <= node->length.max_len;
+}
+
+static int ctlv_schema_check_integer(struct ctlv_schema_validation *v,
+				     const struct ctlv_node_schema *node,
+				     const struct ctlv_attr *nest, const struct ctlv_attr *attr)
+{
+	u64 value;
+
+	if (ctlv_message_attr_payload_len(attr) != sizeof(value))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_BAD_LENGTH, nest, attr);
+	value = ctlv_schema_scalar_value(attr);
+	if (ctlv_schema_value_ok(node, value))
+		return 0;
+	if (node->kind == CTLV_SCHEMA_FLAGS)
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_UNKNOWN_FLAG, nest, attr);
+	return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_VALUE_OUT_OF_RANGE, nest, attr);
+}
+
+static int ctlv_schema_check_bool(struct ctlv_schema_validation *v,
+				  const struct ctlv_node_schema *node, const struct ctlv_attr *nest,
+				  const struct ctlv_attr *attr)
+{
+	const u8 *value = ctlv_message_attr_payload(attr);
+
+	if (ctlv_message_attr_payload_len(attr) != sizeof(*value))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_BAD_LENGTH, nest, attr);
+	if (!ctlv_schema_value_ok(node, *value))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_VALUE_OUT_OF_RANGE, nest, attr);
+	return 0;
+}
+
+static int ctlv_schema_check_string(struct ctlv_schema_validation *v,
+				    const struct ctlv_node_schema *node,
+				    const struct ctlv_attr *nest, const struct ctlv_attr *attr)
+{
+	u32 len = ctlv_message_attr_payload_len(attr);
+	const char *value = ctlv_message_attr_payload(attr);
+
+	/*
+	 * One terminating NUL and no other, so a family may hand the payload
+	 * to anything that takes a C string.
+	 */
+	if (!len || value[len - 1] || strnlen(value, len) != len - 1)
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_BAD_STRING, nest, attr);
+	if (!ctlv_schema_length_ok(node, len - 1))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_BAD_LENGTH, nest, attr);
+	return 0;
+}
+
+static int ctlv_schema_check_binary(struct ctlv_schema_validation *v,
+				    const struct ctlv_node_schema *node,
+				    const struct ctlv_attr *nest, const struct ctlv_attr *attr)
+{
+	if (!ctlv_schema_length_ok(node, ctlv_message_attr_payload_len(attr)))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_BAD_LENGTH, nest, attr);
+	return 0;
+}
+
+/*
+ * The one shape the tree cannot describe: a nest of core-owned attrs
+ * whose members depend on its type. Nothing here acquires anything.
+ */
+static int ctlv_schema_check_blob(struct ctlv_schema_validation *v,
+				  const struct ctlv_node_schema *node, const struct ctlv_attr *blob)
+{
+	const struct ctlv_attr *bad = NULL;
+	struct ctlv_blob_desc desc;
+	u32 reason;
+
+	reason = ctlv_blob_desc_read(blob, &desc, &bad);
+	if (!reason)
+		reason = ctlv_blob_desc_check(&desc, node);
+	if (reason)
+		return ctlv_schema_bad(v, reason, blob, bad);
+	return 0;
+}
+
+static int ctlv_schema_check_nest(struct ctlv_schema_validation *v,
+				  const struct ctlv_schema_tree *tree, u32 node,
+				  const struct ctlv_attr *nest, u32 depth);
+static int ctlv_schema_check_indexed_nest(struct ctlv_schema_validation *v,
+					  const struct ctlv_schema_tree *tree, u32 node,
+					  const struct ctlv_attr *nest, u32 depth, u32 *nest_index);
+
+static int ctlv_schema_check_attr(struct ctlv_schema_validation *v,
+				  const struct ctlv_schema_pos *pos, const struct ctlv_attr *nest,
+				  const struct ctlv_attr *attr, u32 depth, u32 *nest_index)
+{
+	const struct ctlv_node_schema *node = &pos->tree->node_schemas[pos->node];
+	bool nested = node->kind == CTLV_SCHEMA_NEST || node->kind == CTLV_SCHEMA_BLOB;
+
+	if (attr->flags & ~ctlv_schema_flags_allowed(v))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_UNKNOWN_FLAG, nest, attr);
+	/* Whether an attr is a nest is the schema's to say, not the sender's. */
+	if (!(attr->flags & CTLV_ATTR_F_NESTED) != !nested)
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_MALFORMED_MESSAGE, nest, attr);
+
+	switch (node->kind) {
+	case CTLV_SCHEMA_BOOL:
+		return ctlv_schema_check_bool(v, node, nest, attr);
+	case CTLV_SCHEMA_STRING:
+		return ctlv_schema_check_string(v, node, nest, attr);
+	case CTLV_SCHEMA_BINARY:
+		return ctlv_schema_check_binary(v, node, nest, attr);
+	case CTLV_SCHEMA_BLOB:
+		return ctlv_schema_check_blob(v, node, attr);
+	case CTLV_SCHEMA_NEST:
+		if (v->msg->request)
+			return ctlv_schema_check_indexed_nest(v, pos->tree, pos->node, attr,
+							      depth + 1, nest_index);
+		return ctlv_schema_check_nest(v, pos->tree, pos->node, attr, depth + 1);
+	default:
+		return ctlv_schema_check_integer(v, node, nest, attr);
+	}
+}
+
+/* How many times one member occurs, and where the one too many is. */
+static u32 ctlv_schema_occurrences(const struct ctlv_attr *nest, u64 attr_id, u32 max,
+				   const struct ctlv_attr **excess)
+{
+	const struct ctlv_attr *child = NULL;
+	u32 count = 0;
+
+	while (ctlv_message_next(nest, &child) == CTLV_MESSAGE_STEP_ATTR) {
+		if (child->attr_id != attr_id)
+			continue;
+		count++;
+		if (max && count == max + 1)
+			*excess = child;
+	}
+	return count;
+}
+
+/*
+ * An optional stub makes everything it was filled with optional too: what a
+ * descendant added cannot be something an ancestor's caller had to send.
+ */
+static int ctlv_schema_check_members(struct ctlv_schema_validation *v,
+				     const struct ctlv_schema_tree *tree, u32 node,
+				     const struct ctlv_attr *nest, bool optional)
+{
+	const struct ctlv_node_schema *parent = &tree->node_schemas[node];
+	u32 index;
+
+	for (index = 0; index < parent->children_count; index++) {
+		const struct ctlv_attr *excess = NULL;
+		const struct ctlv_node_schema *child;
+		u32 count;
+
+		child = &tree->node_schemas[parent->first_child + index];
+		count = ctlv_schema_occurrences(nest, child->attr_id, child->max_occurrences,
+						&excess);
+		if (!count) {
+			if (index < parent->required_children_count && !optional)
+				return ctlv_schema_bad_missing(v, nest, child->attr_id);
+			continue;
+		}
+		/* An unlimited placement, which only a reply may hold. */
+		if (child->max_occurrences && count > child->max_occurrences)
+			return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_TOO_MANY_OCCURRENCES, nest,
+					       excess);
+	}
+
+	for (index = 0; index < tree->stub_schemas_count; index++) {
+		const struct ctlv_stub_schema *stub_schema = &tree->stub_schemas[index];
+		const struct ctlv_stub_fill_schema *fill_schema;
+		int ret;
+
+		if (stub_schema->node != node)
+			continue;
+		fill_schema = ctlv_schema_fill(v->msg->family, stub_schema->family_id,
+					       stub_schema->set_id);
+		if (!fill_schema)
+			continue;
+		ret = ctlv_schema_check_members(v, &fill_schema->members, 0, nest,
+						optional || stub_schema->optional);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/* Whether one tree places one attr ID anywhere, filled stubs included. */
+static bool ctlv_schema_tree_declares(const struct ctlv_family *family,
+				      const struct ctlv_schema_tree *tree, u64 attr_id)
+{
+	u32 index;
+
+	for (index = 0; index < tree->node_schemas_count; index++)
+		if (tree->node_schemas[index].attr_id == attr_id)
+			return true;
+	for (index = 0; index < tree->stub_schemas_count; index++) {
+		const struct ctlv_stub_schema *stub_schema = &tree->stub_schemas[index];
+		const struct ctlv_stub_fill_schema *fill_schema;
+
+		fill_schema = ctlv_schema_fill(family, stub_schema->family_id, stub_schema->set_id);
+		if (fill_schema &&
+		    ctlv_schema_tree_declares(family, &fill_schema->members, attr_id))
+			return true;
+	}
+	return false;
+}
+
+/* Search every attr ID known anywhere in the effective schema. */
+static bool ctlv_schema_declares(const struct ctlv_family *family, u64 attr_id)
+{
+	const struct ctlv_family *owner;
+	struct ctlv_schema_cursor cursor;
+	const struct ctlv_op_schema *op;
+
+	ctlv_schema_cursor_init(&cursor, family);
+	while ((op = ctlv_schema_next_op(&cursor, &owner)))
+		if (ctlv_schema_tree_declares(family, &op->request, attr_id) ||
+		    ctlv_schema_tree_declares(family, &op->reply, attr_id))
+			return true;
+	return false;
+}
+
+/*
+ * Unknown IDs remain reserved unless explicitly ignorable; ignored unknown
+ * nests are opaque.
+ */
+static int ctlv_schema_check_unknown(struct ctlv_schema_validation *v, const struct ctlv_attr *nest,
+				     const struct ctlv_attr *attr)
+{
+	/* Which refuses the bit itself on a reply, where it means nothing. */
+	if (attr->flags & ~ctlv_schema_flags_allowed(v))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_UNKNOWN_FLAG, nest, attr);
+	if (!(attr->flags & CTLV_ATTR_F_IGNORE_IF_UNKNOWN) ||
+	    ctlv_schema_declares(v->msg->family, attr->attr_id))
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_UNKNOWN_ATTR, nest, attr);
+	return 0;
+}
+
+static int ctlv_schema_check_indexed_nest(struct ctlv_schema_validation *v,
+					  const struct ctlv_schema_tree *tree, u32 node,
+					  const struct ctlv_attr *nest, u32 depth, u32 *nest_index)
+{
+	struct ctlv_message_input_index *index = v->msg->index;
+	const struct ctlv_attr *child = NULL;
+	const struct ctlv_attr *split = NULL;
+	u64 previous = 0;
+	bool previous_valid = false;
+	u32 added;
+	enum ctlv_message_step step;
+	int ret;
+
+	if (depth > CTLV_MAX_NEST_DEPTH)
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_NESTING_TOO_DEEP, nest, NULL);
+	ret = ctlv_schema_index_nest_add(v, tree, node, nest, &added);
+	if (ret)
+		return ret;
+
+	while ((step = ctlv_message_next(nest, &child)) == CTLV_MESSAGE_STEP_ATTR) {
+		struct ctlv_schema_pos pos;
+		bool seen;
+
+		if (!ctlv_schema_member(v->msg->family, tree, node, child->attr_id, &pos)) {
+			previous_valid = false;
+			continue;
+		}
+		ret = ctlv_schema_index_record(v, added, &pos, child, &seen);
+		if (ret)
+			return ret;
+		if (seen && (!previous_valid || previous != child->attr_id) && !split)
+			split = child;
+		previous = child->attr_id;
+		previous_valid = true;
+	}
+	ret = ctlv_schema_index_required(v, added, nest);
+	if (ret)
+		return ret;
+	if (step == CTLV_MESSAGE_STEP_BAD)
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_MALFORMED_MESSAGE, nest, NULL);
+
+	child = NULL;
+	while (ctlv_message_next(nest, &child) == CTLV_MESSAGE_STEP_ATTR) {
+		struct ctlv_schema_pos pos;
+		struct ctlv_message_input_slot *slot;
+		u32 child_nest = CTLV_MESSAGE_INPUT_INDEX_NONE;
+
+		if (!ctlv_schema_member(v->msg->family, tree, node, child->attr_id, &pos)) {
+			ret = ctlv_schema_check_unknown(v, nest, child);
+			if (ret)
+				return ret;
+			continue;
+		}
+		if (child == split)
+			return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_NONCONTIGUOUS_ATTR, nest,
+					       child);
+		ret = ctlv_schema_check_attr(v, &pos, nest, child, depth, &child_nest);
+		if (ret)
+			return ret;
+		if (child_nest != CTLV_MESSAGE_INPUT_INDEX_NONE) {
+			slot = ctlv_schema_index_slot(v, added, &pos);
+			if (!slot)
+				return -EINVAL;
+			ctlv_schema_index_nest_link(index, slot, child_nest);
+		}
+	}
+	*nest_index = added;
+	return 0;
+}
+
+/*
+ * Required members first, then every attr in the order the nest carries them,
+ * so that what is reported is the first thing a caller has to fix.
+ */
+static int ctlv_schema_check_nest(struct ctlv_schema_validation *v,
+				  const struct ctlv_schema_tree *tree, u32 node,
+				  const struct ctlv_attr *nest, u32 depth)
+{
+	const struct ctlv_attr *child = NULL;
+	enum ctlv_message_step step;
+	int ret;
+
+	if (depth > CTLV_MAX_NEST_DEPTH)
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_NESTING_TOO_DEEP, nest, NULL);
+
+	ret = ctlv_schema_check_members(v, tree, node, nest, false);
+	if (ret)
+		return ret;
+
+	while ((step = ctlv_message_next(nest, &child)) == CTLV_MESSAGE_STEP_ATTR) {
+		struct ctlv_schema_pos pos;
+
+		if (!ctlv_schema_member(v->msg->family, tree, node, child->attr_id, &pos)) {
+			ret = ctlv_schema_check_unknown(v, nest, child);
+			if (ret)
+				return ret;
+			/* Skipped whole: the step past it checked its length. */
+			continue;
+		}
+		ret = ctlv_schema_check_attr(v, &pos, nest, child, depth, NULL);
+		if (ret)
+			return ret;
+	}
+	if (step == CTLV_MESSAGE_STEP_BAD)
+		return ctlv_schema_bad(v, CTLV_VALIDATION_REASON_MALFORMED_MESSAGE, nest, NULL);
+	return 0;
+}
+
+int ctlv_schema_validate(struct ctlv_msg *msg, struct ctlv_error *error)
+{
+	struct ctlv_schema_validation validate = { .msg = msg, .error = error };
+	u32 root_index;
+	int ret;
+
+	if (!msg->request)
+		return ctlv_schema_check_nest(&validate, msg->tree, 0, msg->root, 0);
+	if (WARN_ON_ONCE(msg->index))
+		return -EINVAL;
+	msg->index = kzalloc_obj(*msg->index, GFP_KERNEL_ACCOUNT);
+	if (!msg->index)
+		return -ENOMEM;
+	ret = ctlv_schema_check_indexed_nest(&validate, msg->tree, 0, msg->root, 0, &root_index);
+	if (ret || WARN_ON_ONCE(root_index)) {
+		ctlv_message_index_free(msg);
+		return ret ?: -EINVAL;
+	}
+	return 0;
+}
diff --git a/drivers/ctlv/schema.h b/drivers/ctlv/schema.h
new file mode 100644
index 000000000000..4552a392da60
--- /dev/null
+++ b/drivers/ctlv/schema.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
+ *
+ * Private generated-schema walkers.
+ */
+#ifndef _CTLV_SCHEMA_H
+#define _CTLV_SCHEMA_H
+
+#include <linux/ctlv_family.h>
+#include <linux/types.h>
+
+struct ctlv_schema_cursor {
+	const struct ctlv_family *target;
+	const struct ctlv_family *family;
+	u32 level;
+	u32 index;
+};
+
+struct ctlv_schema_pos {
+	const struct ctlv_schema_tree *tree;
+	u32 node;
+};
+
+const struct ctlv_family *
+ctlv_family_ancestor(const struct ctlv_family *family, u32 up);
+u32 ctlv_family_levels(const struct ctlv_family *family);
+void ctlv_schema_cursor_init(struct ctlv_schema_cursor *cursor,
+			     const struct ctlv_family *family);
+const struct ctlv_op_schema *
+ctlv_schema_next_op(struct ctlv_schema_cursor *cursor,
+		    const struct ctlv_family **family);
+const struct ctlv_stub_fill_schema *
+ctlv_schema_fill(const struct ctlv_family *family, u16 family_id,
+		 u32 set_id);
+int ctlv_schema_op_sizes(const struct ctlv_family *family,
+			 const struct ctlv_op_schema *op,
+			 struct ctlv_op_sizes *sizes);
+bool ctlv_schema_member(const struct ctlv_family *family,
+			const struct ctlv_schema_tree *tree, u32 node,
+			u64 attr_id, struct ctlv_schema_pos *member);
+bool ctlv_schema_value_ok(const struct ctlv_node_schema *node, u64 value);
+bool ctlv_schema_length_ok(const struct ctlv_node_schema *node, u32 len);
+
+struct ctlv_error;
+struct ctlv_msg;
+
+int ctlv_schema_validate(struct ctlv_msg *msg, struct ctlv_error *error);
+
+#endif /* _CTLV_SCHEMA_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 ` Jiri Pirko [this message]
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 ` [PATCH RFC 13/20] Documentation: ctlv: Describe how to write a family Jiri Pirko
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-8-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