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 05/15] ctlv: Prepare message input handling
Date: Fri, 13 Nov 2026 16:55:04 +0100 [thread overview]
Message-ID: <20261113155515.2510194-6-jiri@resnulli.us> (raw)
In-Reply-To: <20261113155515.2510194-1-jiri@resnulli.us>
From: Jiri Pirko <jiri@nvidia.com>
Add TLV walking, request indexing and validated input accessors for
generated family readers, and CTLV_ASSERT() for the core's invariants
that no error path depends on, checked only with CONFIG_CTLV_DEBUG.
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:
- made ctlv_attr_next() end the walk on an attr overrunning its scope
- made CTLV a symbol its users select, with no prompt or help
- used void pointer arithmetic for attr addresses
- scoped the rebind loop variable to its loop
- cleared the binary attr length once
- dropped the comment ending message.c
- added CTLV_ASSERT(), checked only with CONFIG_CTLV_DEBUG
- said TLV, type, record and field instead of attr
- said record instead of nest, the root being one
- kept the length an output blob needs with the error
- separated the steps of the longer functions
- numbered every enum member
- took the UAPI headers from include/uapi/ctlv/
- moved to lib/ctlv/
---
MAINTAINERS | 1 +
include/linux/ctlv_family.h | 135 +++++++++++++
lib/ctlv/Kconfig | 13 ++
lib/ctlv/Makefile | 4 +
lib/ctlv/internal.h | 74 +++++++
lib/ctlv/message.c | 393 ++++++++++++++++++++++++++++++++++++
lib/ctlv/message.h | 91 +++++++++
7 files changed, 711 insertions(+)
create mode 100644 lib/ctlv/Kconfig
create mode 100644 lib/ctlv/Makefile
create mode 100644 lib/ctlv/internal.h
create mode 100644 lib/ctlv/message.c
create mode 100644 lib/ctlv/message.h
diff --git a/MAINTAINERS b/MAINTAINERS
index e6748a4ad282..29bdb4de915d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7191,6 +7191,7 @@ F: Documentation/ctlv/
F: Documentation/userspace-api/ctlv.rst
F: include/linux/ctlv*.h
F: include/uapi/ctlv/
+F: lib/ctlv/
F: tools/ctlv/
CTU CAN FD DRIVER
diff --git a/include/linux/ctlv_family.h b/include/linux/ctlv_family.h
index 360eb6e30974..ae8ed69df5ea 100644
--- a/include/linux/ctlv_family.h
+++ b/include/linux/ctlv_family.h
@@ -428,4 +428,139 @@ extern const struct ctlv_family ctlv_family;
u32 ctlv_enum_ext_max(const struct ctlv_family *family, u16 family_id, u32 ext_id);
+struct ctlv_cursor *
+ctlv_input_field(struct ctlv_cursor *cursor, const struct ctlv_cursor *record,
+ struct ctlv_input_ref ref);
+bool ctlv_input_cursor(const struct ctlv_cursor *cursor,
+ struct ctlv_input_ref ref);
+void ctlv_cursor_next(struct ctlv_cursor *cursor);
+bool ctlv_cursor_ok(const struct ctlv_cursor *cursor);
+u64 ctlv_cursor_u64(const struct ctlv_cursor *cursor);
+s64 ctlv_cursor_s64(const struct ctlv_cursor *cursor);
+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);
+
+#define CTLV_IN_REF(_name, _type, _kind) \
+static inline struct ctlv_input_ref _name##_ref(void) \
+{ \
+ return (struct ctlv_input_ref){ \
+ .type = (_type), \
+ .kind = (_kind), \
+ }; \
+}
+
+#define CTLV_IN_FOR_EACH(_name, _cursor, _record) \
+ for (struct ctlv_cursor *__ctlv_cursor = \
+ ctlv_input_field(_cursor, _record, _name##_ref()); \
+ ctlv_cursor_ok(__ctlv_cursor); \
+ ctlv_cursor_next(__ctlv_cursor))
+
+#define CTLV_IN_HAS(_name) \
+static inline bool _name##_present(const struct ctlv_cursor *record) \
+{ \
+ struct ctlv_cursor cursor; \
+ \
+ ctlv_input_field(&cursor, record, _name##_ref()); \
+ return ctlv_cursor_ok(&cursor); \
+}
+
+#define CTLV_IN_U64(_name) \
+static inline u64 _name##_get(const struct ctlv_cursor *record) \
+{ \
+ struct ctlv_cursor cursor; \
+ \
+ ctlv_input_field(&cursor, record, _name##_ref()); \
+ return ctlv_cursor_u64(&cursor); \
+}
+
+#define CTLV_IN_S64(_name) \
+static inline s64 _name##_get(const struct ctlv_cursor *record) \
+{ \
+ struct ctlv_cursor cursor; \
+ \
+ ctlv_input_field(&cursor, record, _name##_ref()); \
+ return ctlv_cursor_s64(&cursor); \
+}
+
+#define CTLV_IN_BOOL(_name) \
+static inline bool _name##_get(const struct ctlv_cursor *record) \
+{ \
+ struct ctlv_cursor cursor; \
+ \
+ ctlv_input_field(&cursor, record, _name##_ref()); \
+ return ctlv_cursor_bool(&cursor); \
+}
+
+#define CTLV_IN_STRING(_name) \
+static inline const char * \
+_name##_get(const struct ctlv_cursor *record) \
+{ \
+ struct ctlv_cursor cursor; \
+ \
+ ctlv_input_field(&cursor, record, _name##_ref()); \
+ return ctlv_cursor_string(&cursor); \
+}
+
+#define CTLV_IN_BINARY(_name) \
+static inline const void * \
+_name##_get(const struct ctlv_cursor *record, u32 *len) \
+{ \
+ struct ctlv_cursor cursor; \
+ \
+ ctlv_input_field(&cursor, record, _name##_ref()); \
+ return ctlv_cursor_binary(&cursor, len); \
+}
+
+#define CTLV_IN_RECORD(_name) \
+static inline void _name##_get(struct ctlv_cursor *inner, \
+ const struct ctlv_cursor *record) \
+{ \
+ ctlv_input_field(inner, record, _name##_ref()); \
+}
+
+#define CTLV_IN_EACH_U64(_name) \
+static inline u64 _name##_get(const struct ctlv_cursor *cursor) \
+{ \
+ if (!ctlv_input_cursor(cursor, _name##_ref())) \
+ return 0; \
+ return ctlv_cursor_u64(cursor); \
+}
+
+#define CTLV_IN_EACH_S64(_name) \
+static inline s64 _name##_get(const struct ctlv_cursor *cursor) \
+{ \
+ if (!ctlv_input_cursor(cursor, _name##_ref())) \
+ return 0; \
+ return ctlv_cursor_s64(cursor); \
+}
+
+#define CTLV_IN_EACH_BOOL(_name) \
+static inline bool _name##_get(const struct ctlv_cursor *cursor) \
+{ \
+ if (!ctlv_input_cursor(cursor, _name##_ref())) \
+ return false; \
+ return ctlv_cursor_bool(cursor); \
+}
+
+#define CTLV_IN_EACH_STRING(_name) \
+static inline const char * \
+_name##_get(const struct ctlv_cursor *cursor) \
+{ \
+ if (!ctlv_input_cursor(cursor, _name##_ref())) \
+ return NULL; \
+ return ctlv_cursor_string(cursor); \
+}
+
+#define CTLV_IN_EACH_BINARY(_name) \
+static inline const void * \
+_name##_get(const struct ctlv_cursor *cursor, u32 *len) \
+{ \
+ if (!ctlv_input_cursor(cursor, _name##_ref())) { \
+ *len = 0; \
+ return NULL; \
+ } \
+ return ctlv_cursor_binary(cursor, len); \
+}
+
#endif /* _LINUX_CTLV_FAMILY_H */
diff --git a/lib/ctlv/Kconfig b/lib/ctlv/Kconfig
new file mode 100644
index 000000000000..465663bb7efa
--- /dev/null
+++ b/lib/ctlv/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config CTLV
+ tristate
+
+config CTLV_DEBUG
+ bool "CTLV core self-checks"
+ depends on CTLV
+ help
+ Checks the CTLV core's own bookkeeping where no error path depends
+ on the result, and warns once when it is wrong. The checks that do
+ guard an error path are made either way.
+
+ If you are unsure, say N.
diff --git a/lib/ctlv/Makefile b/lib/ctlv/Makefile
new file mode 100644
index 000000000000..4f0fe41bc9e1
--- /dev/null
+++ b/lib/ctlv/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_CTLV) += ctlv.o
+
+ctlv-y += message.o
diff --git a/lib/ctlv/internal.h b/lib/ctlv/internal.h
new file mode 100644
index 000000000000..90d07d2638d3
--- /dev/null
+++ b/lib/ctlv/internal.h
@@ -0,0 +1,74 @@
+/* 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/bug.h>
+#include <linux/ctlv_family.h>
+#include <linux/types.h>
+
+#include <uapi/ctlv/ctlv.h>
+
+#include "message.h"
+
+/* Without CONFIG_CTLV_DEBUG @cond is not evaluated, so nothing may rely on it. */
+#ifdef CONFIG_CTLV_DEBUG
+#define CTLV_ASSERT(cond) ((void)WARN_ON_ONCE(!(cond)))
+#else
+#define CTLV_ASSERT(cond) BUILD_BUG_ON_INVALID(cond)
+#endif
+
+#define CTLV_ERROR_MESSAGE_LEN 256
+
+/* 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;
+ struct ctlv_op_sizes sizes;
+ u32 state_index;
+ bool absent;
+};
+
+struct ctlv_error {
+ u32 reason;
+ char message[CTLV_ERROR_MESSAGE_LEN];
+ u32 bad_tlv_offset;
+ u32 record_offset;
+ u64 missing_type;
+ u64 expected_generation;
+ u64 current_generation;
+ u64 required_length;
+};
+
+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/lib/ctlv/message.c b/lib/ctlv/message.c
new file mode 100644
index 000000000000..8d672e42cbe3
--- /dev/null
+++ b/lib/ctlv/message.c
@@ -0,0 +1,393 @@
+// 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>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <uapi/ctlv/ctlv.h>
+#include <uapi/ctlv/ctlv_gen.h>
+
+#include "internal.h"
+#include "message.h"
+
+enum ctlv_message_step ctlv_message_next(const struct ctlv_hdr *record,
+ const struct ctlv_hdr **child)
+{
+ const u8 *end = (const u8 *)record + record->len;
+ const u8 *next;
+ size_t room;
+
+ if (record->len < sizeof(*record))
+ return CTLV_MESSAGE_STEP_BAD;
+ if (*child)
+ next = (const u8 *)*child + ALIGN((size_t)(*child)->len, CTLV_ALIGNTO);
+ else
+ next = (const u8 *)(record + 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_hdr *)next;
+ /*
+ * Length before alignment, so that aligning a length this record 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_TLV;
+}
+
+u32 ctlv_message_offset(const struct ctlv_msg *msg, const struct ctlv_hdr *hdr)
+{
+ return (const u8 *)hdr - (const u8 *)msg->root;
+}
+
+void ctlv_message_root(struct ctlv_cursor *cursor, struct ctlv_msg *msg)
+{
+ *cursor = (struct ctlv_cursor){
+ .msg = msg,
+ .hdr = msg->root,
+ .tree = msg->tree,
+ .index_record_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
+ .index_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
+ };
+ if (msg->request && msg->index)
+ cursor->index_record_scope = msg->index->records[0].first_scope;
+}
+
+static bool ctlv_message_record_index_ok(const struct ctlv_cursor *record)
+{
+ const struct ctlv_message_input_index *index = record->msg->index;
+ const struct ctlv_message_input_record *indexed;
+ const struct ctlv_message_input_scope *scope;
+ const struct ctlv_hdr *hdr;
+
+ if (WARN_ON_ONCE(!record->msg->request || !index ||
+ record->index_record_scope >= index->scopes_count))
+ return false;
+ scope = &index->scopes[record->index_record_scope];
+ if (WARN_ON_ONCE(scope->record >= index->records_count))
+ return false;
+ indexed = &index->records[scope->record];
+ hdr = (const void *)record->msg->root + indexed->offset;
+ if (WARN_ON_ONCE(hdr != record->hdr))
+ return false;
+ return !WARN_ON_ONCE(scope->tree != record->tree || scope->node != record->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);
+}
+
+/* A reader names a field its record lacks only by a family's mistake. */
+static bool ctlv_message_input_slot(const struct ctlv_cursor *record, struct ctlv_input_ref ref,
+ u32 *slot)
+{
+ const struct ctlv_schema_tree *tree = record->tree;
+ const struct ctlv_node_schema *parent;
+ const struct ctlv_node_schema *node;
+
+ if (WARN_ON_ONCE(!tree || !tree->node_schemas || record->node >= tree->node_schemas_count))
+ return false;
+ parent = &tree->node_schemas[record->node];
+ if (WARN_ON_ONCE(parent->first_child > tree->node_schemas_count ||
+ parent->children_count > tree->node_schemas_count - parent->first_child))
+ return false;
+ for (*slot = 0; *slot < parent->children_count; (*slot)++) {
+ node = &tree->node_schemas[parent->first_child + *slot];
+ if (node->type == ref.type)
+ return !WARN_ON_ONCE(node->kind != ref.kind);
+ }
+ WARN_ON_ONCE(1);
+ return false;
+}
+
+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_record *record;
+ 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->hdr)
+ return slot;
+
+ if (WARN_ON_ONCE(cursor->hdr->type != node->type))
+ return NULL;
+ if (WARN_ON_ONCE(scope->record >= index->records_count))
+ return NULL;
+ record = &index->records[scope->record];
+ if (WARN_ON_ONCE(cursor->parent != (const void *)cursor->msg->root + record->offset))
+ return NULL;
+ offset = ctlv_message_offset(cursor->msg, cursor->hdr);
+ if (WARN_ON_ONCE(offset < slot->first_offset || offset >= slot->end_offset ||
+ offset + ALIGN((size_t)cursor->hdr->len, CTLV_ALIGNTO) >
+ slot->end_offset))
+ return NULL;
+
+ if (node->kind == CTLV_SCHEMA_RECORD) {
+ const struct ctlv_message_input_scope *record_scope;
+
+ if (WARN_ON_ONCE(cursor->index_record_scope >= index->scopes_count))
+ return NULL;
+ record_scope = &index->scopes[cursor->index_record_scope];
+ if (WARN_ON_ONCE(record_scope->record >= index->records_count ||
+ index->records[record_scope->record].offset != offset ||
+ record_scope->tree != cursor->tree ||
+ record_scope->node != cursor->node))
+ return NULL;
+ } else if (WARN_ON_ONCE(cursor->index_record_scope != CTLV_MESSAGE_INPUT_INDEX_NONE)) {
+ return NULL;
+ }
+ return slot;
+}
+
+bool ctlv_message_input_rebind(struct ctlv_cursor *record, const struct ctlv_schema_tree *tree,
+ u32 node)
+{
+ const struct ctlv_message_input_scope *bound_scope;
+ const struct ctlv_message_input_record *indexed;
+
+ if (WARN_ON_ONCE(!record->hdr || !record->msg || !record->msg->index))
+ return false;
+ if (!ctlv_message_record_index_ok(record))
+ return false;
+ bound_scope = &record->msg->index->scopes[record->index_record_scope];
+ indexed = &record->msg->index->records[bound_scope->record];
+ for (u32 i = 0; i < indexed->scopes_count; i++) {
+ u32 scope_index = indexed->first_scope + i;
+ const struct ctlv_message_input_scope *scope =
+ &record->msg->index->scopes[scope_index];
+
+ if (scope->tree != tree || scope->node != node)
+ continue;
+ record->tree = tree;
+ record->node = node;
+ record->index_record_scope = scope_index;
+ return true;
+ }
+ return false;
+}
+
+struct ctlv_cursor *ctlv_input_field(struct ctlv_cursor *cursor, const struct ctlv_cursor *record,
+ 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;
+ u32 slot;
+
+ *cursor = (struct ctlv_cursor){
+ .index_record_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
+ .index_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
+ };
+
+ if (WARN_ON_ONCE(!record->hdr || !record->msg))
+ return cursor;
+ if (!ctlv_message_record_index_ok(record))
+ return cursor;
+
+ index = record->msg->index;
+ if (WARN_ON_ONCE(record->index_record_scope >= index->scopes_count))
+ return cursor;
+ scope = &index->scopes[record->index_record_scope];
+ if (!ctlv_message_input_scope_ok(index, scope))
+ return cursor;
+ if (!ctlv_message_input_slot(record, ref, &slot) ||
+ WARN_ON_ONCE(slot >= scope->slots_count))
+ return cursor;
+
+ indexed = &index->slots[scope->first_slot + slot];
+ *cursor = (struct ctlv_cursor){
+ .msg = record->msg,
+ .parent = record->hdr,
+ .hdr = indexed->count ?
+ (const void *)record->msg->root + indexed->first_offset : NULL,
+ .tree = record->tree,
+ .node = record->tree->node_schemas[record->node].first_child + slot,
+ .depth = record->depth + 1,
+ .index_record_scope = CTLV_MESSAGE_INPUT_INDEX_NONE,
+ .index_scope = record->index_record_scope,
+ };
+
+ if (indexed->count && ref.kind == CTLV_SCHEMA_RECORD) {
+ if (WARN_ON_ONCE(indexed->first_record >= index->records_count))
+ return cursor;
+ cursor->index_record_scope = index->records[indexed->first_record].first_scope;
+ }
+ return cursor;
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_input_field, "CTLV");
+
+bool ctlv_input_cursor(const struct ctlv_cursor *cursor, struct ctlv_input_ref ref)
+{
+ const struct ctlv_node_schema *node;
+
+ if (!ctlv_message_cursor_index(cursor))
+ return false;
+ node = &cursor->tree->node_schemas[cursor->node];
+ return !WARN_ON_ONCE(node->type != ref.type || node->kind != ref.kind);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_input_cursor, "CTLV");
+
+void ctlv_cursor_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_hdr *next;
+ u32 next_record;
+ u32 next_offset;
+
+ slot = ctlv_message_cursor_index(cursor);
+ if (!slot || WARN_ON_ONCE(!cursor->hdr))
+ return;
+
+ next = (const void *)cursor->hdr +
+ ALIGN((size_t)cursor->hdr->len, CTLV_ALIGNTO);
+ next_offset = ctlv_message_offset(cursor->msg, next);
+ if (next_offset >= slot->end_offset) {
+ CTLV_ASSERT(next_offset == slot->end_offset);
+ cursor->hdr = NULL;
+ cursor->index_record_scope = CTLV_MESSAGE_INPUT_INDEX_NONE;
+ return;
+ }
+
+ cursor->hdr = next;
+ node = &cursor->tree->node_schemas[cursor->node];
+ if (node->kind == CTLV_SCHEMA_RECORD) {
+ if (WARN_ON_ONCE(cursor->index_record_scope >= cursor->msg->index->scopes_count))
+ return;
+ scope = &cursor->msg->index->scopes[cursor->index_record_scope];
+ if (WARN_ON_ONCE(scope->record >= cursor->msg->index->records_count))
+ return;
+ next_record = cursor->msg->index->records[scope->record].next;
+ if (WARN_ON_ONCE(next_record == CTLV_MESSAGE_INPUT_INDEX_NONE ||
+ next_record >= cursor->msg->index->records_count))
+ return;
+ cursor->index_record_scope = cursor->msg->index->records[next_record].first_scope;
+ }
+ CTLV_ASSERT(ctlv_message_cursor_index(cursor));
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_cursor_next, "CTLV");
+
+bool ctlv_cursor_ok(const struct ctlv_cursor *cursor)
+{
+ return cursor && cursor->hdr && ctlv_message_cursor_index(cursor);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_cursor_ok, "CTLV");
+
+/* The payload, checked against the schema 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->hdr))
+ return NULL;
+ node = &cursor->tree->node_schemas[cursor->node];
+ if (WARN_ON_ONCE(!(kinds & BIT(node->kind))))
+ return NULL;
+ *len = ctlv_message_hdr_payload_len(cursor->hdr);
+ return ctlv_message_hdr_payload(cursor->hdr);
+}
+
+/* A length disagreeing with a validated schema 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_cursor_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_cursor_u64, "CTLV");
+
+s64 ctlv_cursor_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_cursor_s64, "CTLV");
+
+bool ctlv_cursor_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_cursor_bool, "CTLV");
+
+const char *ctlv_cursor_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_cursor_string, "CTLV");
+
+const void *ctlv_cursor_binary(const struct ctlv_cursor *cursor, u32 *len)
+{
+ *len = 0;
+ return ctlv_message_cursor_payload(cursor, BIT(CTLV_SCHEMA_BINARY), len);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_cursor_binary, "CTLV");
diff --git a/lib/ctlv/message.h b/lib/ctlv/message.h
new file mode 100644
index 000000000000..c177021be88a
--- /dev/null
+++ b/lib/ctlv/message.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
+ *
+ * Private message representation and record walking.
+ */
+#ifndef _CTLV_MESSAGE_H
+#define _CTLV_MESSAGE_H
+
+#include <linux/ctlv_family.h>
+#include <linux/types.h>
+
+#include <uapi/ctlv/ctlv.h>
+
+#define CTLV_MESSAGE_INPUT_INDEX_NONE U32_MAX
+
+/* A field's type 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_record;
+ u32 last_record;
+};
+
+struct ctlv_message_input_scope {
+ const struct ctlv_schema_tree *tree;
+ u32 record;
+ u32 node;
+ u32 first_slot;
+ u32 slots_count;
+ bool optional;
+};
+
+struct ctlv_message_input_record {
+ u32 offset;
+ u32 first_scope;
+ u32 scopes_count;
+ u32 next;
+};
+
+struct ctlv_message_input_index {
+ struct ctlv_message_input_record *records;
+ struct ctlv_message_input_scope *scopes;
+ struct ctlv_message_input_slot *slots;
+ u32 records_count;
+ u32 records_capacity;
+ u32 scopes_count;
+ u32 scopes_capacity;
+ u32 slots_count;
+ u32 slots_capacity;
+};
+
+struct ctlv_msg {
+ struct ctlv_hdr *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_TLV = 0,
+ CTLV_MESSAGE_STEP_END = 1,
+ CTLV_MESSAGE_STEP_BAD = 2,
+};
+
+enum ctlv_message_step ctlv_message_next(const struct ctlv_hdr *record,
+ const struct ctlv_hdr **child);
+u32 ctlv_message_offset(const struct ctlv_msg *msg,
+ const struct ctlv_hdr *hdr);
+void ctlv_message_root(struct ctlv_cursor *cursor, struct ctlv_msg *msg);
+bool ctlv_message_input_rebind(struct ctlv_cursor *record,
+ const struct ctlv_schema_tree *tree, u32 node);
+
+static inline u32 ctlv_message_hdr_payload_len(const struct ctlv_hdr *hdr)
+{
+ return hdr->len - sizeof(*hdr);
+}
+
+static inline const void *
+ctlv_message_hdr_payload(const struct ctlv_hdr *hdr)
+{
+ return hdr + 1;
+}
+
+#endif /* _CTLV_MESSAGE_H */
--
2.56.0
next prev 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 ` Jiri Pirko [this message]
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 ` [PATCH RFC v2 08/15] ctlv: Prepare message output handling Jiri Pirko
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-6-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
all inboxes | Powered by JetHome®