Re: [PATCH RFC 02/20] ctlv: Add the UAPI wire format and family API
From: Yeoreum Yun
Date: Mon Oct 26 2026 - 14:56:39 EST
Hi,
> From: Jiri Pirko <jiri@xxxxxxxxxx>
>
> Define CTLV's common TLV transport, ioctl, event records, identifiers
> and limits. Add the in-kernel API used by family implementations and
> generated code.
>
> Assisted-by: Cursor: GPT-5.6 Sol
> Assisted-by: Cursor: Claude Opus 5.5
> Signed-off-by: Jiri Pirko <jiri@xxxxxxxxxx>
> ---
> .../userspace-api/ioctl/ioctl-number.rst | 2 +
> MAINTAINERS | 2 +
> include/linux/ctlv.h | 55 +++
> include/linux/ctlv_family.h | 316 ++++++++++++++++++
> include/uapi/linux/ctlv.h | 183 ++++++++++
> 5 files changed, 558 insertions(+)
> create mode 100644 include/linux/ctlv.h
> create mode 100644 include/linux/ctlv_family.h
> create mode 100644 include/uapi/linux/ctlv.h
>
> diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
> index 2fc53093752d..e545669c773c 100644
> --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
> +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
> @@ -386,6 +386,8 @@ Code Seq# Include File Comments
> <mailto:linux-hyperv@xxxxxxxxxxxxxxx>
> 0xBA 00-0F uapi/linux/liveupdate.h Pasha Tatashin
> <mailto:pasha.tatashin@xxxxxxxxxx>
> +0xBB 00-0F uapi/linux/ctlv.h CTLV
> + <mailto:jiri@xxxxxxxxxxx>
> 0xC0 00-0F linux/usb/iowarrior.h
> 0xCA 00-0F uapi/misc/cxl.h Dead since 6.15
> 0xCA 10-2F uapi/misc/ocxl.h
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a8508fbae37d..8ab0d6a7a2fd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7006,6 +7006,8 @@ M: Jiri Pirko <jiri@xxxxxxxxxxx>
> L: linux-kernel@xxxxxxxxxxxxxxx
> S: Maintained
> F: Documentation/ctlv/
> +F: include/linux/ctlv*.h
> +F: include/uapi/linux/ctlv*.h
> F: tools/ctlv/
>
> CTU CAN FD DRIVER
> diff --git a/include/linux/ctlv.h b/include/linux/ctlv.h
> new file mode 100644
> index 000000000000..addb893f4b8d
> --- /dev/null
> +++ b/include/linux/ctlv.h
> @@ -0,0 +1,55 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
> + *
> + * CTLV family API.
> + */
> +#ifndef _LINUX_CTLV_H
> +#define _LINUX_CTLV_H
> +
> +#include <linux/types.h>
> +
> +#include <uapi/linux/ctlv.h>
> +
> +struct ctlv_device;
> +struct ctlv_family;
> +struct ctlv_msg;
> +struct ctlv_op_ctx;
> +struct ctlv_schema_tree;
> +
> +/**
> + * struct ctlv_cursor - One attr position in one message.
> + * @msg: the message this position is in, core-private bookkeeping
> + * @attr: the attr itself, or NULL when a lookup found nothing
> + * @parent: reading, the nest containing @attr, NULL at the root
> + * @up: writing, cursor of the nest containing @attr, NULL at the root. The
> + * chain of them is the open-nest stack
> + * @start: writing, where @attr begins in the message. An append reaches what
> + * it lengthens through the buffer rather than through @attr, that buffer
> + * being the core's own
> + * @tree: schema tree the node belongs to, which is a fill's own tree for an
> + * attr that arrived through one
> + * @node: index of the node within @tree
> + * @depth: nesting levels above this position, the root being zero
> + * @index_nest_scope: schema scope used to read members of the physical nest
> + * @index_scope: core-private schema scope containing this attr
> + *
> + * Fixed-size, so family code can put a child cursor on the stack. The members
> + * belong to the core: a family reaches a message through what was generated
> + * for the op it answers. One direction at a time, a helper of the other
> + * direction refusing the cursor.
> + */
> +struct ctlv_cursor {
> + struct ctlv_msg *msg;
> + const struct ctlv_attr *attr;
> + const struct ctlv_attr *parent;
> + struct ctlv_cursor *up;
> + size_t start;
> + const struct ctlv_schema_tree *tree;
> + u32 node;
> + u32 depth;
> + u32 index_nest_scope;
> + u32 index_scope;
> +};
> +
> +#endif /* _LINUX_CTLV_H */
> diff --git a/include/linux/ctlv_family.h b/include/linux/ctlv_family.h
> new file mode 100644
> index 000000000000..1ae4d3a536f7
> --- /dev/null
> +++ b/include/linux/ctlv_family.h
> @@ -0,0 +1,316 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
> + *
> + * CTLV family metadata and generated-code support.
> + */
> +#ifndef _LINUX_CTLV_FAMILY_H
> +#define _LINUX_CTLV_FAMILY_H
> +
> +#include <linux/ctlv.h>
> +#include <linux/gfp_types.h>
> +#include <linux/kobject.h>
> +#include <linux/types.h>
> +
> +#include <uapi/linux/ctlv.h>
> +
> +/* op-schema-get needs one level to describe the declared schema. */
> +#define CTLV_MAX_DECLARED_NEST_DEPTH (CTLV_MAX_NEST_DEPTH - 1)
> +
> +struct ctlv_node_schema {
> + u64 attr_id;
> + u8 kind;
> + u8 unit;
> + s8 scale;
> + bool reenters;
> + u32 max_occurrences;
> + u32 first_child;
> + u32 children_count;
> + u32 required_children_count;
> + union {
> + struct {
> + u64 min;
> + u64 max;
> + } range;
> + u64 flags_mask;
> + struct {
> + u32 min_len;
> + u32 max_len;
> + } length;
> + struct {
> + u64 max_len;
> + u32 alignment;
> + u16 backings;
> + u8 direction;
> + } blob;
> + };
> +};
> +
> +#define CTLV_NODE_SCHEMA_UNLIMITED 0
> +
> +#define __CTLV_NODE_SCHEMA(_attr, _kind, _occurrences, ...) \
> + { .attr_id = (_attr), .kind = (_kind), \
> + .max_occurrences = (_occurrences), __VA_ARGS__ }
> +
> +#define __CTLV_NODE_SCHEMA_CHILDREN(_first, _count, _required) \
> + .first_child = (_first), .children_count = (_count), \
> + .required_children_count = (_required)
> +
> +#define CTLV_NODE_SCHEMA_ROOT(_first, _count, _required) \
> + __CTLV_NODE_SCHEMA(CTLV_ATTR_ROOT, CTLV_SCHEMA_NEST, 1, \
> + __CTLV_NODE_SCHEMA_CHILDREN(_first, _count, _required))
> +
> +#define CTLV_NODE_SCHEMA_NEST(_attr, _occurrences, _first, _count, _required) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_NEST, _occurrences, \
> + __CTLV_NODE_SCHEMA_CHILDREN(_first, _count, _required))
> +
> +#define CTLV_NODE_SCHEMA_NEST_REENTERS(_attr, _occurrences, _first, _count, _required) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_NEST, _occurrences, \
> + .reenters = true, \
> + __CTLV_NODE_SCHEMA_CHILDREN(_first, _count, _required))
> +
> +#define CTLV_NODE_SCHEMA_UINT(_attr, _occurrences, _min, _max) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_UINT, _occurrences, \
> + .range = { .min = (_min), .max = (_max) })
> +
> +#define CTLV_NODE_SCHEMA_UINT_UNIT(_attr, _occurrences, _min, _max, _unit, _scale) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_UINT, _occurrences, \
> + .range = { .min = (_min), .max = (_max) }, \
> + .unit = (_unit), .scale = (_scale))
> +
> +#define CTLV_NODE_SCHEMA_SINT(_attr, _occurrences, _min, _max) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_SINT, _occurrences, \
> + .range = { .min = (_min), .max = (_max) })
> +
> +#define CTLV_NODE_SCHEMA_SINT_UNIT(_attr, _occurrences, _min, _max, _unit, _scale) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_SINT, _occurrences, \
> + .range = { .min = (_min), .max = (_max) }, \
> + .unit = (_unit), .scale = (_scale))
> +
> +#define CTLV_NODE_SCHEMA_ENUM(_attr, _occurrences, _max) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_ENUM, _occurrences, \
> + .range = { .min = 1, .max = (_max) })
> +
> +#define CTLV_NODE_SCHEMA_FLAGS(_attr, _occurrences, _mask) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_FLAGS, _occurrences, \
> + .flags_mask = (_mask))
> +
> +#define CTLV_NODE_SCHEMA_BOOL(_attr, _occurrences) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_BOOL, _occurrences)
> +
> +#define CTLV_NODE_SCHEMA_STRING(_attr, _occurrences, _min, _max) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_STRING, _occurrences, \
> + .length = { .min_len = (_min), .max_len = (_max) })
> +
> +#define CTLV_NODE_SCHEMA_BINARY(_attr, _occurrences, _min, _max) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_BINARY, _occurrences, \
> + .length = { .min_len = (_min), .max_len = (_max) })
> +
> +#define CTLV_NODE_SCHEMA_BLOB(_attr, _occurrences, _max, _alignment, _backings, _dir) \
> + __CTLV_NODE_SCHEMA(_attr, CTLV_SCHEMA_BLOB, _occurrences, \
> + .blob = { .max_len = (_max), \
> + .alignment = (_alignment), \
> + .backings = (_backings), \
> + .direction = (_dir) })
> +
> +struct ctlv_stub_schema {
> + u16 family_id;
> + u32 set_id;
> + u32 node;
> + bool optional;
> +};
> +
> +#define __CTLV_STUB_SCHEMA(_family, _set, _node, _optional) \
> + { .family_id = (_family), .set_id = (_set), \
> + .node = (_node), .optional = (_optional) }
> +
> +#define CTLV_STUB_SCHEMA(_family, _set, _node) \
> + __CTLV_STUB_SCHEMA(_family, _set, _node, false)
> +
> +#define CTLV_STUB_SCHEMA_OPTIONAL(_family, _set, _node) \
> + __CTLV_STUB_SCHEMA(_family, _set, _node, true)
> +
> +struct ctlv_schema_tree {
> + const struct ctlv_node_schema *node_schemas;
> + u32 node_schemas_count;
> + const struct ctlv_stub_schema *stub_schemas;
> + u32 stub_schemas_count;
> +};
> +
> +#define CTLV_SCHEMA_TREE(_name) { \
> + .node_schemas = _name##_node_schemas, \
> + .node_schemas_count = ARRAY_SIZE(_name##_node_schemas), \
> +}
> +
> +#define CTLV_SCHEMA_TREE_STUBS(_name) { \
> + .node_schemas = _name##_node_schemas, \
> + .node_schemas_count = ARRAY_SIZE(_name##_node_schemas), \
> + .stub_schemas = _name##_stub_schemas, \
> + .stub_schemas_count = ARRAY_SIZE(_name##_stub_schemas), \
> +}
> +
> +#define CTLV_SCHEMA_TREE_NONE() { }
> +
> +struct ctlv_stub_fill_schema {
> + u16 family_id;
> + u32 set_id;
> + struct ctlv_schema_tree members;
> +};
> +
> +#define CTLV_STUB_FILL_SCHEMA(_family, _set, _name) { \
> + .family_id = (_family), \
> + .set_id = (_set), \
> + .members = CTLV_SCHEMA_TREE(_name##_fill), \
> +}
> +
> +struct ctlv_op_sizes {
> + u32 request_max_len;
> + u32 action_reply_max_len;
> + u32 query_reply_min_len;
> + u32 query_reply_suggested_len;
> + u32 event_record_max_len;
> +};
> +
> +struct ctlv_op_schema {
> + u64 op_id;
> + u8 type;
> + u8 fd_access;
> + u8 availability;
> + bool optional;
> + bool pre_post;
> + struct ctlv_schema_tree request;
> + struct ctlv_schema_tree reply;
> + struct ctlv_op_sizes sizes;
> +};
> +
> +#define __CTLV_OP_SCHEMA(_op, _type, _availability, ...) \
> + { .op_id = (_op), .type = (_type), \
> + .availability = (_availability), __VA_ARGS__ }
> +
> +#define CTLV_OP_SCHEMA_ACTION(_op, _access, _availability, _request_len, _reply_len, \
> + _request, _reply, ...) \
> + __CTLV_OP_SCHEMA(_op, CTLV_OP_TYPE_ACTION, _availability, \
> + .fd_access = (_access), \
> + .request = _request, .reply = _reply, \
> + .sizes = { \
> + .request_max_len = (_request_len), \
> + .action_reply_max_len = (_reply_len), \
> + }, \
> + __VA_ARGS__)
> +
> +#define CTLV_OP_SCHEMA_QUERY(_op, _access, _availability, _request_len, _reply_min, \
> + _reply_suggested, _request, _reply, ...) \
> + __CTLV_OP_SCHEMA(_op, CTLV_OP_TYPE_QUERY, _availability, \
> + .fd_access = (_access), \
> + .request = _request, .reply = _reply, \
> + .sizes = { \
> + .request_max_len = (_request_len), \
> + .query_reply_min_len = (_reply_min), \
> + .query_reply_suggested_len = \
> + (_reply_suggested), \
> + }, \
> + __VA_ARGS__)
> +
> +#define CTLV_OP_SCHEMA_EVENT(_op, _record_len, _reply, ...) \
> + __CTLV_OP_SCHEMA(_op, CTLV_OP_TYPE_EVENT, \
> + CTLV_OP_AVAILABILITY_FIXED, \
> + .reply = _reply, \
> + .sizes = { \
> + .event_record_max_len = (_record_len), \
> + }, \
> + __VA_ARGS__)
> +
> +struct ctlv_family_schema {
> + const char *name;
> + u16 family_id;
> + const struct ctlv_op_schema *ops;
> + u32 ops_count;
> + const struct ctlv_stub_fill_schema *stub_fill_schemas;
> + u32 stub_fill_schemas_count;
> +};
> +
> +/* Leave half of a uevent environment to the device model. */
> +#define CTLV_UEVENT_MAX_LEN (UEVENT_BUFFER_SIZE / 2)
> +#define CTLV_UEVENT_MAX_VARS (UEVENT_NUM_ENVP / 2)
> +
> +struct ctlv_input_ref {
> + const struct ctlv_schema_tree *tree;
> + u64 attr_id;
> + u32 node;
> + u32 slot;
> + u8 kind;
> +};
> +
> +typedef int (*ctlv_action_fn)(void *priv, struct ctlv_op_ctx *ctx,
> + const struct ctlv_cursor *request,
> + struct ctlv_cursor *reply);
> +typedef int (*ctlv_query_fn)(void *priv, struct ctlv_op_ctx *ctx,
> + const struct ctlv_cursor *request,
> + struct ctlv_cursor *reply);
> +typedef int (*ctlv_op_pre_fn)(void *priv, struct ctlv_op_ctx *ctx);
> +typedef void (*ctlv_op_post_fn)(void *priv, struct ctlv_op_ctx *ctx);
> +
> +struct ctlv_op {
> + union {
> + ctlv_action_fn action;
> + ctlv_query_fn query;
> + };
> + ctlv_op_pre_fn pre;
> + ctlv_op_post_fn post;
> +};
> +
> +#define CTLV_OP_ACTION(_name) { .action = _name##_action }
> +#define CTLV_OP_QUERY(_name) { .query = _name##_query }
> +#define CTLV_OP_ACTION_PRE_POST(_name) \
> + { .action = _name##_action, .pre = _name##_pre, \
> + .post = _name##_post }
> +#define CTLV_OP_QUERY_PRE_POST(_name) \
> + { .query = _name##_query, .pre = _name##_pre, \
> + .post = _name##_post }
> +
> +typedef size_t (*ctlv_family_priv_size_fn)(void);
> +typedef const void *(*ctlv_family_pre_init_fn)(const void *init_info);
> +typedef int (*ctlv_family_init_fn)(struct ctlv_device *ctlvdev,
> + const void *init_info, void *priv);
> +typedef int (*ctlv_family_post_init_fn)(struct ctlv_device *ctlvdev,
> + void *priv);
> +typedef void (*ctlv_family_pre_fini_fn)(struct ctlv_device *ctlvdev,
> + void *priv);
> +typedef void (*ctlv_family_fini_fn)(struct ctlv_device *ctlvdev, void *priv);
> +
> +typedef int (*ctlv_stub_fill_fn)(void *priv, struct ctlv_op_ctx *ctx,
> + const struct ctlv_cursor *request,
> + struct ctlv_cursor *nest);
> +
> +struct ctlv_stub_ops {
> + u16 family_id;
> + u32 set_id;
> + ctlv_stub_fill_fn fill;
> +};
> +
> +#define CTLV_STUB_OPS(_family, _set, _name) { \
> + .family_id = (_family), \
> + .set_id = (_set), \
> + .fill = _name##_fill, \
> +}
> +
> +struct ctlv_family {
> + const struct ctlv_family_schema *schema;
> + const struct ctlv_family *parent;
> + const struct ctlv_op *ops;
> + u32 ops_count;
> + ctlv_op_pre_fn pre;
> + ctlv_op_post_fn post;
> + ctlv_family_priv_size_fn priv_size;
> + ctlv_family_pre_init_fn pre_init;
> + ctlv_family_init_fn init;
> + ctlv_family_post_init_fn post_init;
> + ctlv_family_pre_fini_fn pre_fini;
> + ctlv_family_fini_fn fini;
> + const struct ctlv_stub_ops *stub_ops;
> + u32 stub_ops_count;
> +};
> +
> +extern const struct ctlv_family ctlv_family;
> +
> +#endif /* _LINUX_CTLV_FAMILY_H */
> diff --git a/include/uapi/linux/ctlv.h b/include/uapi/linux/ctlv.h
> new file mode 100644
> index 000000000000..6bc0d1a296e6
> --- /dev/null
> +++ b/include/uapi/linux/ctlv.h
> @@ -0,0 +1,183 @@
> +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
> + *
> + * CTLV transport UAPI: the TLV wire format, the op-execution ioctl, the event
> + * record header and transport limits. Schema-free; a family's constants
> + * are generated into <linux/<family>_gen.h>, the root family's into
> + * <linux/ctlv_gen.h>.
> + */
> +#ifndef _UAPI_LINUX_CTLV_H
> +#define _UAPI_LINUX_CTLV_H
> +
> +#include <linux/ioctl.h>
> +#include <linux/types.h>
> +
> +/**
> + * struct ctlv_attr - Attr header.
> + * @len: this header plus its payload, excluding this attr's trailing padding
> + * @flags: CTLV_ATTR_F_* bits
> + * @attr_id: complete attr ID, or CTLV_ATTR_ROOT for a message root
> + *
> + * Attrs are aligned to CTLV_ALIGNTO, which is why @attr_id is __aligned_u64: a
> + * 32-bit ABI would otherwise align this structure to four bytes. A nested
> + * payload is the concatenation of CTLV_ALIGN(child.len) bytes per child, so
> + * child padding counts inside the containing attr's @len while a leaf's own
> + * padding does not. Scalar payloads are native-endian. Within each request
> + * root or nest, occurrences of one @attr_id are contiguous; different IDs
> + * otherwise have no required order.
> + */
> +struct ctlv_attr {
> + __u32 len;
> + __u32 flags;
> + __aligned_u64 attr_id;
> +};
> +
> +#define CTLV_ALIGNTO 8
> +#define CTLV_ALIGN(_len) (((_len) + CTLV_ALIGNTO - 1) & ~(CTLV_ALIGNTO - 1))
> +
> +/* Set exactly on root and nested attrs, clear on scalar attrs. */
> +#define CTLV_ATTR_F_NESTED (1U << 0)
> +/* Query fragments: set on the root and each nest logically open at the end. */
> +#define CTLV_ATTR_F_FRAG_MORE (1U << 1)
> +/*
> + * A request attr, never the root, that a device whose schema does not declare
> + * its ID skips whole instead of refusing the message. An ID that schema does
> + * declare is held to it wherever it appears, so this asks to be forgiven what
> + * this kernel has never heard of and nothing else.
> + */
> +#define CTLV_ATTR_F_IGNORE_IF_UNKNOWN (1U << 2)
> +
> +/* Root attr: carries CTLV_ATTR_F_NESTED, valid only at a message start. */
> +#define CTLV_ATTR_ROOT 0ULL
> +
> +/* Complete attr: family in bits 63..48, group in 47..32, attr in 31..0. */
> +#define CTLV_ATTR(_family, _group, _attr) \
> + (((__u64)(_family) << 48) | \
> + ((__u64)(_group) << 32) | \
> + (__u64)(_attr))
> +
> +#define CTLV_ATTR_FAMILY(_attr) ((__u16)((_attr) >> 48))
> +#define CTLV_ATTR_GROUP(_attr) ((__u16)((_attr) >> 32))
> +#define CTLV_ATTR_NUMBER(_attr) ((__u32)(_attr))
> +
> +/* Complete op: family in bits 63..48, zero in 47..32, op in 31..0. */
> +#define CTLV_OP(_family, _op) \
> + (((__u64)(_family) << 48) | (__u64)(_op))
> +
> +#define CTLV_OP_FAMILY(_op) ((__u16)((_op) >> 48))
> +#define CTLV_OP_NUMBER(_op) ((__u32)(_op))
> +
> +/**
> + * enum ctlv_unit - CTLV-defined unit of an integer attr payload.
> + * @CTLV_UNIT_NONE: dimensionless
> + * @CTLV_UNIT_SECONDS: seconds
> + * @CTLV_UNIT_HERTZ: hertz
> + * @CTLV_UNIT_BYTES: bytes
> + * @CTLV_UNIT_CELSIUS: degrees Celsius
> + *
> + * A payload is value * 10^scale of this unit, from the decimal scale the attr
> + * declares alongside. Changing either is an ABI break.
> + */
> +enum ctlv_unit {
> + CTLV_UNIT_NONE = 0,
> + CTLV_UNIT_SECONDS,
> + CTLV_UNIT_HERTZ,
> + CTLV_UNIT_BYTES,
> + CTLV_UNIT_CELSIUS,
> +};
> +
> +/**
> + * enum ctlv_schema_kind - CTLV-defined form of one attr payload.
> + * @CTLV_SCHEMA_UINT: unsigned integer payload bounded by a range
> + * @CTLV_SCHEMA_SINT: signed integer payload bounded by a range
> + * @CTLV_SCHEMA_BOOL: one payload byte holding zero or one
> + * @CTLV_SCHEMA_STRING: NUL-terminated string payload bounded by length
> + * @CTLV_SCHEMA_BINARY: opaque payload bounded by length
> + * @CTLV_SCHEMA_ENUM: unsigned integer payload restricted to a range
> + * @CTLV_SCHEMA_FLAGS: unsigned integer payload restricted to a mask
> + * @CTLV_SCHEMA_NEST: nested payload described by further nodes
> + * @CTLV_SCHEMA_BLOB: blob descriptor nest
> + *
> + * Exactly one form applies to an attr. These are the values op-schema-get
> + * reports, and the set is append-only.
> + */
> +enum ctlv_schema_kind {
> + CTLV_SCHEMA_UINT,
> + CTLV_SCHEMA_SINT,
> + CTLV_SCHEMA_BOOL,
> + CTLV_SCHEMA_STRING,
> + CTLV_SCHEMA_BINARY,
> + CTLV_SCHEMA_ENUM,
> + CTLV_SCHEMA_FLAGS,
> + CTLV_SCHEMA_NEST,
> + CTLV_SCHEMA_BLOB,
> +};
> +
> +/**
> + * struct ctlv_op_exec - Op-execution ioctl argument.
> + * @op_id: complete op ID; zero is invalid
> + * @request: struct ctlv_attr __user *, the mandatory complete request root
> + * @reply: struct ctlv_attr __user *, the success reply or the error
> + * information
> + * @request_len: readable bytes beginning at @request, at least one root header;
> + * the transport copy boundary, which may exceed the root's own length
> + * @reply_len: writable bytes beginning at @reply; input only, never
> + * overwritten by the kernel
> + * @generation: query only; zero on a first fragment, the previously returned
> + * value on continuation, and the query generation on output. Actions must
> + * set zero.
> + * @cursor: query only; zero starts a query and a nonzero value resumes it. On
> + * output it is the next cursor when the root carries
> + * CTLV_ATTR_F_FRAG_MORE and zero when no further fragment exists. Actions
> + * must set zero.
> + * @reserved: must be zero
> + *
> + * No size field: the ioctl number encodes the structure size and @reserved
> + * covers compatible growth. @request and @reply are fixed-width
> + * representations of userspace pointers so that the compat layout is identical.
> + */
> +struct ctlv_op_exec {
> + __aligned_u64 op_id;
> + __aligned_u64 request;
> + __aligned_u64 reply;
> + __u32 request_len;
> + __u32 reply_len;
> + __aligned_u64 generation;
> + __aligned_u64 cursor;
> + __aligned_u64 reserved[2];
> +};
> +
> +#define CTLV_IOC_MAGIC 0xBB
> +
> +#define CTLV_IOC_OP_EXEC _IOWR(CTLV_IOC_MAGIC, 0x00, struct ctlv_op_exec)
> +
> +/**
> + * struct ctlv_event_hdr - Header of one queued event record.
> + * @length: complete record length, this header plus the aligned root
> + * @flags: must be zero
> + * @op_id: complete op ID of the event op that produced the record
> + * @sequence: per open file description sequence number, assigned to every
> + * eligible enqueue attempt including dropped ones
> + *
> + * Exactly one root attr follows the header, and one read() returns one record.
> + */
> +struct ctlv_event_hdr {
> + __u32 length;
> + __u32 flags;
> + __aligned_u64 op_id;
> + __aligned_u64 sequence;
> +};
> +
> +/* Implementation limits, not wire-field maxima: raising them is compatible. */
> +
> +/* Request roots, action replies, query reply fragments and event roots. */
> +#define CTLV_MAX_INLINE_MESSAGE_LEN 0x100000 /* 1 MiB */
> +/* Includes the error information root. */
> +#define CTLV_MAX_ERROR_LEN 0x4000 /* 16 KiB */
> +/* The root attr is at depth zero. */
> +#define CTLV_MAX_NEST_DEPTH 32
JFYI, when I tested with CTLV_MAX_NEST_DEPTH as 32 and try to run
ctlv_msg_test_as_deep_as_the_core_allows() message_kunit.c test,
It experiences the *real stack overflow* on FVP
(lesser value (e.g. 16) is fine):
[ 3.834419] ok 22 ctlv_msg_test_the_bit_where_it_means_nothing
[ 3.836499] ok 23 ctlv_msg_test_a_bit_no_version_declares
[ 3.837852] Insufficient stack space to handle exception!
[ 3.837875] ESR: 0x0000000096000047 -- DABT (current EL)
[ 3.837916] FAR: 0xffff800084efffe0
[ 3.837953] Task stack: [0xffff800084f00000..0xffff800084f04000]
[ 3.838001] IRQ stack: [0xffff800083d50000..0xffff800083d54000]
[ 3.838049] Overflow stack: [0xffff00087b7c3150..0xffff00087b7c4150]
[ 3.838114] CPU: 7 UID: 0 PID: 257 Comm: kunit_try_catch Tainted: G N
** replaying previous printk message **
[ 3.838114] CPU: 7 UID: 0 PID: 257 Comm: kunit_try_catch Tainted: G N 7.3.0-rc4+ #1216 PREEMPTLAZY
[ 3.838219] Tainted: [N]=TEST
[ 3.838249] Hardware name: , BIOS
[ 3.838279] pstate: 21400009 (nzCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
[ 3.838360] pc : __kmalloc_flags_noprof+0x10/0x540
[ 3.838433] lr : __alloc_empty_sheaf+0x48/0x88
[ 3.838501] sp : ffff800084efffa0
[ 3.838534] x29: ffff800084f00030 x28: 0000000000000400 x27: 0000000000000400
[ 3.838657] x26: ffff000803d721c0 x25: 0000000000000000 x24: 0000000000400cc0
[ 3.838780] x23: ffff00087b7e3398 x22: 16e0800080564b98 x21: ffff0008000a0900
[ 3.838908] x20: 0000000000000000 x19: ffff000800013600 x18: 0000000000000000
[ 3.839029] x17: 0000000000000001 x16: 0000000000000002 x15: ffffffff00000001
[ 3.839150] x14: 0000001c000001c0 x13: ffffffff00000001 x12: ffff000800013500
[ 3.839275] x11: ffff00080370a800 x10: 0000000000000060 x9 : ffff8000805627d0
[ 3.839401] x8 : 0000000000000000 x7 : ffffffff00000001 x6 : 0000001d000001d0
[ 3.839521] x5 : ffff800080567b68 x4 : 00000000ffffffff x3 : 0000000000000004
[ 3.839640] x2 : 0000000000000dc0 x1 : 0000000000000008 x0 : 0000000000000080
[ 3.839761] Kernel panic - not syncing: kernel stack overflow
[ 3.841533] CPU: 7 UID: 0 PID: 257 Comm: kunit_try_catch Tainted: G N 7.3.0-rc4+ #1216 PREEMPTLAZY
[ 3.841783] Tainted: [N]=TEST
[ 3.841877] Hardware name: , BIOS
[ 3.841980] Call trace:
[ 3.842065] show_stack+0x20/0x40 (C)
[ 3.842246] __dump_stack+0x28/0x48
[ 3.842427] dump_stack_lvl+0x28/0xb0
[ 3.842609] dump_stack+0x18/0x34
[ 3.842788] vpanic+0x208/0x5b8
[ 3.842944] panic+0x54/0x60
[ 3.843096] nmi_panic+0xc0/0xc8
[ 3.843273] panic_bad_stack+0x10c/0x128
[ 3.843466] handle_bad_stack+0x34/0x38
[ 3.843622] __bad_stack+0x98/0xa0
[ 3.843773] __kmalloc_flags_noprof+0x10/0x540 (P)
[ 3.843950] __pcs_replace_empty_main+0x2ac/0x3e8
[ 3.844139] __kmalloc_node_track_caller_noprof+0x440/0x520
[ 3.844328] krealloc_node_align_noprof+0x178/0x390
[ 3.844509] kvrealloc_node_align_noprof+0x98/0x130
[ 3.844690] ctlv_schema_index_scope_add+0xb0/0x3f8
[ 3.844882] ctlv_schema_check_indexed_nest+0x1e8/0x958
(repeat ctlv_schema_check_attr()/ctlv_schema_check_indexed_nest())
[ 3.856919] ctlv_schema_check_attr+0x3dc/0x538
[ 3.857105] ctlv_schema_check_indexed_nest+0x788/0x958
[ 3.857301] ctlv_schema_validate+0x88/0x180
[ 3.857482] ctlv_msg_test_as_deep_as_the_core_allows+0x11c/0x1e8
[ 3.857698] kunit_try_run_case+0x64/0x188
[ 3.857878] kunit_generic_run_threadfn_adapter+0x30/0x68
[ 3.858086] kthread+0x10c/0x140
[ 3.858245] ret_from_fork+0x10/0x20
[ 3.858398] SMP: stopping secondary CPUs
[ 3.858630] Kernel Offset: disabled
[ 3.858720] CPU features: 0x0,00000000,00b0802e,9f190bc3,2affe72f
[ 3.858872] Memory Limit: none
[ 3.858976] ---[ end Kernel panic - not syncing: kernel stack overflow ]---
[...]
I think it might be good to configurable according to architecture
unless it's not good to reduce the CTLV_MAX_NEST_DEPTH number
otherwise, increasing stack size when CTLV is enabled seems unresonable...
--
Sincerely,
Yeoreum Yun