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,
	jarkko@kernel.org, zohar@linux.ibm.com, yeoreum.yun@arm.com,
	aneesh.kumar@kernel.org, dave.jiang@intel.com,
	nik.borisov@suse.com, sergii.dmytruk@3mdeb.com
Subject: [PATCH RFC v2 08/15] ctlv: Prepare message output handling
Date: Fri, 13 Nov 2026 16:55:07 +0100	[thread overview]
Message-ID: <20261113155515.2510194-9-jiri@resnulli.us> (raw)
In-Reply-To: <20261113155515.2510194-1-jiri@resnulli.us>

From: Jiri Pirko <jiri@nvidia.com>

Add schema-aware reply construction, generated output accessors,
operation errors and query fragment completion.

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>
---
RFC->RFCv2:
- added ctlv_record_cancel()
- moved the function kernel-doc to the definitions
- made the warning for a reply past its declared length a CTLV_ASSERT()
- said TLV, type, record and field instead of attr
- said record instead of nest, the root being one
- took the UAPI headers from include/uapi/ctlv/
- moved to lib/ctlv/
---
 include/linux/ctlv.h        |  24 ++
 include/linux/ctlv_family.h |  41 ++++
 lib/ctlv/Makefile           |   2 +-
 lib/ctlv/builder.c          | 455 ++++++++++++++++++++++++++++++++++++
 lib/ctlv/builder.h          |  14 ++
 5 files changed, 535 insertions(+), 1 deletion(-)
 create mode 100644 lib/ctlv/builder.c
 create mode 100644 lib/ctlv/builder.h

diff --git a/include/linux/ctlv.h b/include/linux/ctlv.h
index e151e32a0ffd..5b75550de678 100644
--- a/include/linux/ctlv.h
+++ b/include/linux/ctlv.h
@@ -34,6 +34,8 @@ struct ctlv_schema_tree;
  * @depth: record levels above this position, the root being zero
  * @index_record_scope: schema scope used to read the members of @hdr, a record
  * @index_scope: core-private schema scope containing this TLV
+ * @progress: writing, whether the fragment had progressed before this record
+ *	opened
  *
  * Fixed-size, so family code can put a child cursor on the stack.
  * ctlv_record_start() opens at most CTLV_MAX_RECORD_DEPTH levels, which bounds
@@ -52,6 +54,28 @@ struct ctlv_cursor {
 	u32 depth;
 	u32 index_record_scope;
 	u32 index_scope;
+	bool progress;
 };
 
+void ctlv_record_end(struct ctlv_cursor *child);
+void ctlv_record_cancel(struct ctlv_cursor *child);
+
+int ctlv_op_error(struct ctlv_op_ctx *ctx, int error, const char *message);
+__printf(3, 4)
+int ctlv_op_errorf(struct ctlv_op_ctx *ctx, int error, const char *fmt, ...);
+
+/*
+ * The core retains no query state. A continuation repeats the request and
+ * passes back the family's generation and nonzero cursor. It fails with
+ * -ESTALE if the generation changed. Each fragment must complete a logical
+ * leaf and return a cursor different from its input.
+ *
+ * ctlv_op_query_more() marks still-open records, whose cursors must stay live.
+ */
+u64 ctlv_op_query_generation(const struct ctlv_op_ctx *ctx);
+u64 ctlv_op_query_cursor(const struct ctlv_op_ctx *ctx);
+int ctlv_op_query_more(struct ctlv_op_ctx *ctx, u64 generation,
+		       u64 next_cursor);
+int ctlv_op_query_done(struct ctlv_op_ctx *ctx, u64 generation);
+
 #endif /* _LINUX_CTLV_H */
diff --git a/include/linux/ctlv_family.h b/include/linux/ctlv_family.h
index ae8ed69df5ea..4d7d4fb65fec 100644
--- a/include/linux/ctlv_family.h
+++ b/include/linux/ctlv_family.h
@@ -441,6 +441,47 @@ bool ctlv_cursor_bool(const struct ctlv_cursor *cursor);
 const char *ctlv_cursor_string(const struct ctlv_cursor *cursor);
 const void *ctlv_cursor_binary(const struct ctlv_cursor *cursor, u32 *len);
 
+bool ctlv_put_u64(struct ctlv_cursor *record, u64 type, u8 kind, u64 value);
+bool ctlv_put_s64(struct ctlv_cursor *record, u64 type, u8 kind, s64 value);
+bool ctlv_put_bool(struct ctlv_cursor *record, u64 type, u8 kind, bool value);
+bool ctlv_put_string(struct ctlv_cursor *record, u64 type, u8 kind,
+		     const char *value);
+bool ctlv_put_binary(struct ctlv_cursor *record, u64 type, u8 kind,
+		     const void *value, u32 len);
+bool ctlv_record_start(struct ctlv_cursor *record, struct ctlv_cursor *child,
+		       u64 type, u8 kind);
+
+#define __CTLV_DEFINE_PUT(_name, _ctype, _put_fn, _type, _kind)	\
+static inline bool _name##_put(struct ctlv_cursor *record,	\
+			       _ctype value)			\
+{								\
+	return _put_fn(record, (_type), (_kind), value);	\
+}
+
+#define CTLV_PUT_U64(_name, _type, _kind)			\
+	__CTLV_DEFINE_PUT(_name, u64, ctlv_put_u64, _type, _kind)
+#define CTLV_PUT_S64(_name, _type, _kind)			\
+	__CTLV_DEFINE_PUT(_name, s64, ctlv_put_s64, _type, _kind)
+#define CTLV_PUT_BOOL(_name, _type, _kind)			\
+	__CTLV_DEFINE_PUT(_name, bool, ctlv_put_bool, _type, _kind)
+#define CTLV_PUT_STRING(_name, _type, _kind)			\
+	__CTLV_DEFINE_PUT(_name, const char *, ctlv_put_string,	\
+			  _type, _kind)
+
+#define CTLV_PUT_BINARY(_name, _type, _kind)			\
+static inline bool _name##_put(struct ctlv_cursor *record,	\
+			       const void *value, u32 len)	\
+{								\
+	return ctlv_put_binary(record, (_type), (_kind), value, len); \
+}
+
+#define CTLV_RECORD_START(_name, _type, _kind)			\
+static inline bool _name##_start(struct ctlv_cursor *record,	\
+				 struct ctlv_cursor *inner)	\
+{								\
+	return ctlv_record_start(record, inner, (_type), (_kind)); \
+}
+
 #define CTLV_IN_REF(_name, _type, _kind)			\
 static inline struct ctlv_input_ref _name##_ref(void)		\
 {								\
diff --git a/lib/ctlv/Makefile b/lib/ctlv/Makefile
index 3fd656835b8a..e1a22a199ef1 100644
--- a/lib/ctlv/Makefile
+++ b/lib/ctlv/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_CTLV) += ctlv.o
 
-ctlv-y += blob_desc.o message.o schema.o
+ctlv-y += blob_desc.o builder.o message.o schema.o
diff --git a/lib/ctlv/builder.c b/lib/ctlv/builder.c
new file mode 100644
index 000000000000..976e2f8d1aaa
--- /dev/null
+++ b/lib/ctlv/builder.c
@@ -0,0 +1,455 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
+ *
+ * Schema-aware reply construction and operation completion.
+ */
+#include <linux/align.h>
+#include <linux/bug.h>
+#include <linux/ctlv.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/stdarg.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <uapi/ctlv/ctlv.h>
+#include <uapi/ctlv/ctlv_gen.h>
+
+#include "builder.h"
+#include "internal.h"
+#include "message.h"
+#include "schema.h"
+
+static void ctlv_message_output_frag_mark(struct ctlv_op_ctx *ctx);
+
+void ctlv_message_output_init(struct ctlv_msg *msg, struct ctlv_op_ctx *ctx, void *buf, size_t len,
+			      const struct ctlv_schema_tree *tree)
+{
+	*msg = (struct ctlv_msg){
+		.root = buf,
+		.len = len,
+		.tree = tree,
+		.family = ctx->family,
+		.ctx = ctx,
+	};
+	*msg->root = (struct ctlv_hdr){
+		.len = sizeof(*msg->root),
+		.flags = CTLV_HDR_F_RECORD,
+		.type = CTLV_TYPE_ROOT,
+	};
+}
+
+/* The first thing that went wrong is what the op ends up failing with. */
+static bool ctlv_message_output_poison(struct ctlv_cursor *record, int error)
+{
+	if (!record->msg->poison)
+		record->msg->poison = error;
+	return false;
+}
+
+/* A poisoned message is refused silently: whatever poisoned it has warned. */
+static bool ctlv_message_output_ready(struct ctlv_cursor *record)
+{
+	if (WARN_ON_ONCE(!record->msg || !record->hdr))
+		return false;
+	/* Also what refuses a cursor that reads a request. */
+	if (WARN_ON_ONCE(!record->msg->ctx))
+		return false;
+	if (record->msg->poison)
+		return false;
+	if (WARN_ON_ONCE(record->msg->ctx->deepest != record))
+		return ctlv_message_output_poison(record, -EINVAL);
+	return true;
+}
+
+/* What the schema places in this record under one type, as one schema kind. */
+static const struct ctlv_node_schema *ctlv_message_output_member(struct ctlv_cursor *record,
+								 u64 type, u8 kind,
+								 struct ctlv_schema_pos *pos)
+{
+	const struct ctlv_node_schema *node;
+
+	if (!ctlv_message_output_ready(record))
+		return NULL;
+	if (WARN_ON_ONCE(!ctlv_schema_member(record->msg->family, record->tree,
+					     record->node, type, pos))) {
+		ctlv_message_output_poison(record, -EINVAL);
+		return NULL;
+	}
+	node = &pos->tree->node_schemas[pos->node];
+	if (WARN_ON_ONCE(node->kind != kind)) {
+		ctlv_message_output_poison(record, -EINVAL);
+		return NULL;
+	}
+	return node;
+}
+
+/*
+ * A query is offered whatever room its caller had, so running out ends a
+ * fragment. Anything else wrote a message its own schema does not describe.
+ */
+static bool ctlv_message_output_full(struct ctlv_cursor *record)
+{
+	const struct ctlv_op_schema *op = record->msg->ctx->op->schema;
+
+	if (op->type == CTLV_OP_TYPE_QUERY)
+		return false;
+	CTLV_ASSERT(0);
+	return ctlv_message_output_poison(record, -EMSGSIZE);
+}
+
+/* What one cursor stands on, as the buffer this direction may change. */
+static struct ctlv_hdr *ctlv_message_output_hdr(const struct ctlv_cursor *record)
+{
+	return (struct ctlv_hdr *)((u8 *)record->msg->root + record->start);
+}
+
+/* The pad is written rather than left as it was: it belongs to the record. */
+static struct ctlv_hdr *ctlv_message_output_write(struct ctlv_cursor *record, u64 type, u32 flags,
+						  const void *payload, u32 payload_len)
+{
+	u32 len = sizeof(struct ctlv_hdr) + payload_len;
+	size_t step = ALIGN((size_t)len, CTLV_ALIGNTO);
+	struct ctlv_msg *msg = record->msg;
+	struct ctlv_cursor *open;
+	struct ctlv_hdr *hdr;
+	size_t offset;
+
+	offset = record->start + ctlv_message_output_hdr(record)->len;
+	if (offset + step > msg->len) {
+		ctlv_message_output_full(record);
+		return NULL;
+	}
+
+	hdr = (struct ctlv_hdr *)((u8 *)msg->root + offset);
+	*hdr = (struct ctlv_hdr){
+		.len = len,
+		.flags = flags,
+		.type = type,
+	};
+	if (payload_len)
+		memcpy(hdr + 1, payload, payload_len);
+	memset((u8 *)hdr + len, 0, step - len);
+	if (!(flags & CTLV_HDR_F_RECORD))
+		msg->ctx->frag.progress = true;
+
+	/* Every record the TLV landed in is that much longer, the root too. */
+	for (open = record; open; open = open->up)
+		ctlv_message_output_hdr(open)->len += step;
+	return hdr;
+}
+
+/* The 64-bit payload that every integer, enum and flags field carries. */
+static bool ctlv_message_put_scalar(struct ctlv_cursor *record, u64 type, u8 kind, u64 value)
+{
+	const struct ctlv_node_schema *node;
+	struct ctlv_schema_pos pos;
+
+	node = ctlv_message_output_member(record, type, kind, &pos);
+	if (!node)
+		return false;
+	/* A value outside what the field declares is one nothing may be told. */
+	if (WARN_ON_ONCE(!ctlv_schema_value_ok(record->msg->family, node, value)))
+		return ctlv_message_output_poison(record, -EINVAL);
+	return ctlv_message_output_write(record, type, 0, &value, sizeof(value));
+}
+
+bool ctlv_put_u64(struct ctlv_cursor *record, u64 type, u8 kind, u64 value)
+{
+	return ctlv_message_put_scalar(record, type, kind, value);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_put_u64, "CTLV");
+
+bool ctlv_put_s64(struct ctlv_cursor *record, u64 type, u8 kind, s64 value)
+{
+	return ctlv_message_put_scalar(record, type, kind, value);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_put_s64, "CTLV");
+
+bool ctlv_put_bool(struct ctlv_cursor *record, u64 type, u8 kind, bool value)
+{
+	u8 payload = value;
+	struct ctlv_schema_pos pos;
+
+	if (!ctlv_message_output_member(record, type, kind, &pos))
+		return false;
+	return ctlv_message_output_write(record, type, 0, &payload, sizeof(payload));
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_put_bool, "CTLV");
+
+bool ctlv_put_string(struct ctlv_cursor *record, u64 type, u8 kind, const char *value)
+{
+	const struct ctlv_node_schema *node;
+	struct ctlv_schema_pos pos;
+	size_t len;
+
+	node = ctlv_message_output_member(record, type, kind, &pos);
+	if (!node)
+		return false;
+	len = strnlen(value, (size_t)node->length.max_len + 1);
+	if (WARN_ON_ONCE(len > node->length.max_len || !ctlv_schema_length_ok(node, len)))
+		return ctlv_message_output_poison(record, -EINVAL);
+	return ctlv_message_output_write(record, type, 0, value, len + 1);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_put_string, "CTLV");
+
+bool ctlv_put_binary(struct ctlv_cursor *record, u64 type, u8 kind, const void *value, u32 len)
+{
+	const struct ctlv_node_schema *node;
+	struct ctlv_schema_pos pos;
+
+	node = ctlv_message_output_member(record, type, kind, &pos);
+	if (!node)
+		return false;
+	if (WARN_ON_ONCE(!ctlv_schema_length_ok(node, len)))
+		return ctlv_message_output_poison(record, -EINVAL);
+	return ctlv_message_output_write(record, type, 0, value, len);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_put_binary, "CTLV");
+
+bool ctlv_record_start(struct ctlv_cursor *record, struct ctlv_cursor *child, u64 type, u8 kind)
+{
+	struct ctlv_schema_pos pos;
+	struct ctlv_hdr *hdr;
+
+	*child = (struct ctlv_cursor){};
+	if (!ctlv_message_output_member(record, type, kind, &pos))
+		return false;
+	/* Unreachable through any schema the generator would have accepted. */
+	if (WARN_ON_ONCE(record->depth + 1 > CTLV_MAX_RECORD_DEPTH))
+		return ctlv_message_output_poison(record, -EINVAL);
+
+	hdr = ctlv_message_output_write(record, type, CTLV_HDR_F_RECORD, NULL, 0);
+	if (!hdr)
+		return false;
+
+	*child = (struct ctlv_cursor){
+		.msg = record->msg,
+		.hdr = hdr,
+		.up = record,
+		.start = ctlv_message_offset(record->msg, hdr),
+		.tree = pos.tree,
+		.node = pos.node,
+		.depth = record->depth + 1,
+		.progress = record->msg->ctx->frag.progress,
+	};
+	record->msg->ctx->deepest = child;
+	return true;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_record_start, "CTLV");
+
+/* Only the innermost open record of an unpoisoned message can be closed. */
+static struct ctlv_op_ctx *ctlv_message_output_closing(struct ctlv_cursor *child)
+{
+	struct ctlv_op_ctx *ctx;
+
+	if (WARN_ON_ONCE(!child->msg || !child->msg->ctx || !child->up))
+		return NULL;
+	ctx = child->msg->ctx;
+	if (child->msg->poison)
+		return NULL;
+	if (WARN_ON_ONCE(ctx->deepest != child)) {
+		ctlv_message_output_poison(child, -EINVAL);
+		return NULL;
+	}
+	return ctx;
+}
+
+/**
+ * ctlv_record_end - Finish an output record.
+ * @child: record cursor returned by a generated start helper
+ */
+void ctlv_record_end(struct ctlv_cursor *child)
+{
+	struct ctlv_op_ctx *ctx = ctlv_message_output_closing(child);
+
+	if (!ctx)
+		return;
+	if (ctlv_message_output_hdr(child)->len == sizeof(struct ctlv_hdr))
+		ctx->frag.progress = true;
+	ctx->deepest = child->up;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_record_end, "CTLV");
+
+/**
+ * ctlv_record_cancel - Drop an output record and everything written into it.
+ * @child: innermost open record cursor returned by a generated start helper
+ *
+ * For a query that ran out of room partway through a record: the fragment then
+ * ends before it, and the next fragment writes it again.
+ */
+void ctlv_record_cancel(struct ctlv_cursor *child)
+{
+	struct ctlv_op_ctx *ctx = ctlv_message_output_closing(child);
+	struct ctlv_cursor *open;
+	u32 len;
+
+	if (!ctx)
+		return;
+	/* The record lengthened each one holding it by exactly its length. */
+	len = ctlv_message_output_hdr(child)->len;
+	for (open = child->up; open; open = open->up)
+		ctlv_message_output_hdr(open)->len -= len;
+	ctx->frag.progress = child->progress;
+	ctx->deepest = child->up;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_record_cancel, "CTLV");
+
+static bool ctlv_op_error_valid(int error)
+{
+	return !WARN_ON_ONCE(error >= 0 || error < -MAX_ERRNO);
+}
+
+/**
+ * ctlv_op_error - Record an operation error message.
+ * @ctx: operation context
+ * @error: negative error to return
+ * @message: message copied into the operation result
+ *
+ * Return: @error, or -EINVAL if @error is invalid.
+ */
+int ctlv_op_error(struct ctlv_op_ctx *ctx, int error, const char *message)
+{
+	if (!ctlv_op_error_valid(error))
+		return error < 0 && error >= -MAX_ERRNO ? error : -EINVAL;
+	if (!ctx->error.message[0])
+		strscpy(ctx->error.message, message, sizeof(ctx->error.message));
+	return error;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_op_error, "CTLV");
+
+/**
+ * ctlv_op_errorf - Format an operation error message.
+ * @ctx: operation context
+ * @error: negative error to return
+ * @fmt: printf format and arguments
+ *
+ * Return: @error, or -EINVAL if @error is invalid.
+ */
+int ctlv_op_errorf(struct ctlv_op_ctx *ctx, int error, const char *fmt, ...)
+{
+	va_list args;
+
+	if (!ctlv_op_error_valid(error))
+		return error < 0 && error >= -MAX_ERRNO ? error : -EINVAL;
+	if (ctx->error.message[0])
+		return error;
+	va_start(args, fmt);
+	vscnprintf(ctx->error.message, sizeof(ctx->error.message), fmt, args);
+	va_end(args);
+	return error;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_op_errorf, "CTLV");
+
+/* Continuation is a query's, so asking about one anywhere else is a bug. */
+static bool ctlv_op_is_query(const struct ctlv_op_ctx *ctx)
+{
+	return !WARN_ON_ONCE(ctx->op->schema->type != CTLV_OP_TYPE_QUERY);
+}
+
+/**
+ * ctlv_op_query_generation - Get a query continuation generation.
+ * @ctx: query operation context
+ *
+ * Return: generation supplied by the previous fragment, or zero initially.
+ */
+u64 ctlv_op_query_generation(const struct ctlv_op_ctx *ctx)
+{
+	if (!ctlv_op_is_query(ctx))
+		return 0;
+	return ctx->frag.generation;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_op_query_generation, "CTLV");
+
+/**
+ * ctlv_op_query_cursor - Get a query continuation cursor.
+ * @ctx: query operation context
+ *
+ * Return: cursor supplied by the previous fragment, or zero initially.
+ */
+u64 ctlv_op_query_cursor(const struct ctlv_op_ctx *ctx)
+{
+	if (!ctlv_op_is_query(ctx))
+		return 0;
+	return ctx->frag.cursor;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_op_query_cursor, "CTLV");
+
+/*
+ * What both endings check: a query, one ending, and a generation that has not
+ * moved under a continuation, which no fragment can be delivered across.
+ */
+static int ctlv_query_fragment_end(struct ctlv_op_ctx *ctx, u64 generation)
+{
+	if (!ctlv_op_is_query(ctx))
+		return -EINVAL;
+	if (WARN_ON_ONCE(ctx->frag.answered))
+		return -EINVAL;
+	ctx->frag.answered = true;
+	/* Whatever the builder gave up on has warned about it already. */
+	if (ctx->output.poison)
+		return ctx->output.poison;
+	if (ctx->frag.cursor && generation != ctx->frag.generation) {
+		ctx->error.reason = CTLV_VALIDATION_REASON_STALE_GENERATION;
+		ctx->error.expected_generation = ctx->frag.generation;
+		ctx->error.current_generation = generation;
+		return -ESTALE;
+	}
+	ctx->frag.reported = generation;
+	return 0;
+}
+
+/**
+ * ctlv_op_query_more - Complete a query fragment with more data pending.
+ * @ctx: query operation context
+ * @generation: current data generation
+ * @next_cursor: nonzero cursor for the next fragment
+ *
+ * Return: 0, or a negative error.
+ */
+int ctlv_op_query_more(struct ctlv_op_ctx *ctx, u64 generation, u64 next_cursor)
+{
+	int ret = ctlv_query_fragment_end(ctx, generation);
+
+	if (!ret &&
+	    WARN_ON_ONCE(!ctx->frag.progress || !next_cursor || next_cursor == ctx->frag.cursor))
+		ret = -EINVAL;
+	if (!ret) {
+		ctlv_message_output_frag_mark(ctx);
+		ctx->frag.next_cursor = next_cursor;
+	}
+	ctx->frag.result = ret;
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_op_query_more, "CTLV");
+
+/**
+ * ctlv_op_query_done - Complete the last query fragment.
+ * @ctx: query operation context
+ * @generation: current data generation
+ *
+ * Return: 0, or a negative error.
+ */
+int ctlv_op_query_done(struct ctlv_op_ctx *ctx, u64 generation)
+{
+	int ret = ctlv_query_fragment_end(ctx, generation);
+
+	if (!ret && WARN_ON_ONCE(ctx->deepest != &ctx->output_root)) {
+		/* A cursor of a frame that has returned is not followed. */
+		ctx->deepest = &ctx->output_root;
+		ret = -EINVAL;
+	}
+	ctx->frag.result = ret;
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_op_query_done, "CTLV");
+
+static void ctlv_message_output_frag_mark(struct ctlv_op_ctx *ctx)
+{
+	struct ctlv_cursor *open;
+
+	for (open = ctx->deepest; open; open = open->up)
+		ctlv_message_output_hdr(open)->flags |= CTLV_HDR_F_FRAG_MORE;
+	ctx->deepest = &ctx->output_root;
+}
diff --git a/lib/ctlv/builder.h b/lib/ctlv/builder.h
new file mode 100644
index 000000000000..744595ebd88e
--- /dev/null
+++ b/lib/ctlv/builder.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _CTLV_BUILDER_H
+#define _CTLV_BUILDER_H
+
+#include <linux/types.h>
+
+struct ctlv_msg;
+struct ctlv_op_ctx;
+struct ctlv_schema_tree;
+
+void ctlv_message_output_init(struct ctlv_msg *msg, struct ctlv_op_ctx *ctx, void *buf, size_t len,
+			      const struct ctlv_schema_tree *tree);
+
+#endif /* _CTLV_BUILDER_H */
-- 
2.56.0


  parent reply	other threads:[~2026-11-13 15:55 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-13 15:54 [PATCH RFC v2 00/15] ctlv: Add a schema-driven TLV uAPI for character devices Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 01/15] ctlv: Add the schema language and generator Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 02/15] ctlv: Add the UAPI message format and family API Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 03/15] ctlv: Add the root family specification and generated files Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 04/15] Documentation: ctlv: Add userspace ABI Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 05/15] ctlv: Prepare message input handling Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 06/15] ctlv: Prepare blob descriptor validation Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 07/15] ctlv: Prepare schema validation Jiri Pirko
2026-11-13 15:55 ` Jiri Pirko [this message]
2026-11-13 15:55 ` [PATCH RFC v2 09/15] ctlv: Prepare VA blob handling Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 10/15] ctlv: Prepare runtime introspection Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 11/15] ctlv: Add operation dispatch Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 12/15] ctlv: Add character device nodes Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 13/15] Documentation: ctlv: Describe how to write a family Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 14/15] tools/ctlv: Add the userspace library Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 15/15] tools/ctlv: Add ctlvctl 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=20261113155515.2510194-9-jiri@resnulli.us \
    --to=jiri@resnulli.us \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=aik@amd.com \
    --cc=alistair.francis@wdc.com \
    --cc=aneesh.kumar@kernel.org \
    --cc=ankita@nvidia.com \
    --cc=cedric.xing@intel.com \
    --cc=dave.hansen@linux.intel.com \
    --cc=dave.jiang@intel.com \
    --cc=david.quigley@intel.com \
    --cc=drdeeglaze@gmail.com \
    --cc=elena.reshetova@intel.com \
    --cc=jarkko@kernel.org \
    --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=nik.borisov@suse.com \
    --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=sergii.dmytruk@3mdeb.com \
    --cc=suzuki.poulose@arm.com \
    --cc=x86@kernel.org \
    --cc=yeoreum.yun@arm.com \
    --cc=zohar@linux.ibm.com \
    /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