From: Dan Williams <djbw@kernel.org>
To: linux-coco@lists.linux.dev
Cc: linux-pci@vger.kernel.org, driver-core@lists.linux.dev,
ankita@nvidia.com, Alistair Francis <alistair.francis@wdc.com>,
Lukas Wunner <lukas@wunner.de>,
Donald Hunter <donald.hunter@gmail.com>,
Jakub Kicinski <kuba@kernel.org>
Subject: [PATCH 03/15] tools: ynl: Teach ynl_gen_c to validate and dump 'blob' attributes
Date: Sun, 5 Jul 2026 15:08:07 -0700 [thread overview]
Message-ID: <20260705220819.2472765-4-djbw@kernel.org> (raw)
In-Reply-To: <20260705220819.2472765-1-djbw@kernel.org>
Now that blobs are recognized by pyynl and coalesced over successive
netlink messages, do the same for the generated C. Recall that blobs
support large objects for protocols like SPDM.
With blobs the generator can assume that the length of the blob will be
conveyed in the response messages before the blob data message(s). Update
the C generation to:
* Mark when a given operation may have blobs (objects that need more than
one message to fill) with a new ynl_more_cb_t in the dump state.
* Use @more() determine when to continue filling a partially received
blob, and check if the end of the message stream is reached without
receiving all of its data.
* Pre-allocate the blob and parse successive messages into that shared
object.
This proposal is the follow up to a discussion [1] where an alternative
solution of adding an @offset attribute to make each chunk standalone was
discarded. That approach commits userspace to inefficient attribute
reassembly where it needs to retrieve a series of structures with offset
properties and then reassemble them into the full blob.
Assisted-by: Codex:GPT-5.5
[codex: helped with typing out the code generation]
Cc: Alistair Francis <alistair.francis@wdc.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Donald Hunter <donald.hunter@gmail.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Link: http://lore.kernel.org/20260318170014.6650d2bf@kernel.org [1]
Signed-off-by: Dan Williams <djbw@kernel.org>
---
tools/net/ynl/lib/ynl-priv.h | 4 ++
tools/net/ynl/lib/ynl.h | 14 ++++++
tools/net/ynl/lib/ynl.c | 33 +++++++++++++
tools/net/ynl/pyynl/ynl_gen_c.py | 80 +++++++++++++++++++++++++++++---
4 files changed, 124 insertions(+), 7 deletions(-)
diff --git a/tools/net/ynl/lib/ynl-priv.h b/tools/net/ynl/lib/ynl-priv.h
index ced7dce44efb..dba98cf78204 100644
--- a/tools/net/ynl/lib/ynl-priv.h
+++ b/tools/net/ynl/lib/ynl-priv.h
@@ -41,6 +41,9 @@ enum ynl_parse_result {
typedef int (*ynl_parse_cb_t)(const struct nlmsghdr *nlh,
struct ynl_parse_arg *yarg);
+typedef bool (*ynl_more_cb_t)(void *data);
+
+struct ynl_blob *ynl_blob_alloc(unsigned int len);
struct ynl_policy_attr {
enum ynl_policy_type type:8;
@@ -123,6 +126,7 @@ struct ynl_dump_state {
struct ynl_dump_list_type *last;
size_t alloc_sz;
ynl_parse_cb_t cb;
+ ynl_more_cb_t more;
__u32 rsp_cmd;
};
diff --git a/tools/net/ynl/lib/ynl.h b/tools/net/ynl/lib/ynl.h
index db7c0591a63f..9c50b068ee83 100644
--- a/tools/net/ynl/lib/ynl.h
+++ b/tools/net/ynl/lib/ynl.h
@@ -100,6 +100,20 @@ struct ynl_string {
char str[];
};
+/**
+ * struct ynl_blob - variable-length blob attribute
+ * @len: length of the blob data
+ * @data: value of the blob attribute
+ *
+ * Parsed binary value. This struct is used for variable-length blob values
+ * that may be built from one or more attributes spanning multiple netlink
+ * messages.
+ */
+struct ynl_blob {
+ unsigned int len;
+ unsigned char data[];
+};
+
struct ynl_sock *
ynl_sock_create(const struct ynl_family *yf, struct ynl_error *e);
void ynl_sock_destroy(struct ynl_sock *ys);
diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c
index 2bcd781111d7..933ee0ad2c51 100644
--- a/tools/net/ynl/lib/ynl.c
+++ b/tools/net/ynl/lib/ynl.c
@@ -1003,6 +1003,27 @@ int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
return err;
}
+struct ynl_blob *ynl_blob_alloc(unsigned int len)
+{
+ size_t size = sizeof(struct ynl_blob) + len;
+ struct ynl_blob *blob;
+
+ if (size < len)
+ return NULL;
+ blob = malloc(size);
+ if (blob)
+ blob->len = len;
+ return blob;
+}
+
+/* continue filling a blob attribute? */
+static bool ynl_check_more(struct ynl_dump_state *ds)
+{
+ struct ynl_dump_list_type *prev = ds->last;
+
+ return prev && ds->more && ds->more(&prev->data);
+}
+
static int
ynl_dump_trampoline(const struct nlmsghdr *nlh, struct ynl_parse_arg *data)
{
@@ -1015,6 +1036,13 @@ ynl_dump_trampoline(const struct nlmsghdr *nlh, struct ynl_parse_arg *data)
if (ret)
return ret < 0 ? YNL_PARSE_CB_ERROR : YNL_PARSE_CB_OK;
+ if (ynl_check_more(ds)) {
+ yarg = ds->yarg;
+ yarg.data = &ds->last->data;
+
+ return ds->cb(nlh, &yarg);
+ }
+
obj = calloc(1, ds->alloc_sz);
if (!obj)
return YNL_PARSE_CB_ERROR;
@@ -1059,6 +1087,11 @@ int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
goto err_close_list;
} while (err > 0);
+ if (ynl_check_more(yds)) {
+ yerr(ys, YNL_ERROR_ATTR_MISSING, "Dump ended with an incomplete blob");
+ goto err_close_list;
+ }
+
yds->first = ynl_dump_end(yds);
return 0;
diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py
index cdc3646f2642..f0387004b7cc 100755
--- a/tools/net/ynl/pyynl/ynl_gen_c.py
+++ b/tools/net/ynl/pyynl/ynl_gen_c.py
@@ -727,14 +727,20 @@ class TypeMultiAttr(Type):
def is_multi_val(self):
return True
+ def is_blob(self):
+ return bool(self.bloblen)
+
def presence_type(self):
- return 'count'
+ return 'len' if self.is_blob() else 'count'
def _complex_member_type(self, ri):
if 'type' not in self.attr or self.attr['type'] == 'nest':
return self.nested_struct_type
- if self.attr['type'] == 'binary' and 'struct' in self.attr:
- return None # use arg_member()
+ if self.attr['type'] == 'binary':
+ if 'struct' in self.attr:
+ return None # use arg_member()
+ if self.is_blob():
+ return 'struct ynl_blob'
if self.attr['type'] == 'string':
return 'struct ynl_string *'
if self.attr['type'] in scalars:
@@ -747,9 +753,12 @@ class TypeMultiAttr(Type):
raise Exception(f"Sub-type {self.attr['type']} not supported yet")
def arg_member(self, ri):
- if self.type == 'binary' and 'struct' in self.attr:
- return [f'struct {c_lower(self.attr["struct"])} *{self.c_name}',
- f'unsigned int n_{self.c_name}']
+ if self.type == 'binary':
+ if 'struct' in self.attr:
+ return [f'struct {c_lower(self.attr["struct"])} *{self.c_name}',
+ f'unsigned int n_{self.c_name}']
+ if self.is_blob():
+ return [f'struct ynl_blob *{self.c_name}']
return super().arg_member(ri)
def free_needs_iter(self):
@@ -784,6 +793,24 @@ class TypeMultiAttr(Type):
return self.base_type._attr_typol()
def _attr_get(self, ri, var):
+ if self.is_blob():
+ length = self.attr_set[self.bloblen]
+ return [
+ f"if (!{var}->{self.c_name})",
+ f"{var}->{self.c_name} = ynl_blob_alloc({var}->{length.c_name});",
+ f"if (!{var}->{self.c_name})",
+ "return YNL_PARSE_CB_ERROR;",
+ f"if ({var}->_len.{self.c_name} > {var}->{self.c_name}->len || " +
+ f"len > {var}->{self.c_name}->len - {var}->_len.{self.c_name})",
+ f'return ynl_error_parse(yarg, "blob data exceeds length '
+ f'({self.attr_set.name}.{self.name})");',
+ f"memcpy({var}->{self.c_name}->data + {var}->_len.{self.c_name}, " +
+ "ynl_attr_data(attr), len);",
+ f"{var}->_len.{self.c_name} += len;"], \
+ ['if (ynl_attr_validate(yarg, attr))', 'return YNL_PARSE_CB_ERROR;',
+ 'len = ynl_attr_data_len(attr);'], \
+ ['unsigned int len;']
+
return f'n_{self.c_name}++;', None, None
def attr_put(self, ri, var):
@@ -791,6 +818,10 @@ class TypeMultiAttr(Type):
put_type = self.type
ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
ri.cw.p(f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);")
+ elif self.attr['type'] == 'binary' and self.is_blob():
+ ri.cw.p(f"if ({var}->{self.c_name})")
+ ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, " +
+ f"{var}->{self.c_name}->data, {var}->{self.c_name}->len);")
elif self.attr['type'] == 'binary' and 'struct' in self.attr:
ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}[i], sizeof(struct {c_lower(self.attr['struct'])}));")
@@ -805,6 +836,10 @@ class TypeMultiAttr(Type):
raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet")
def _setter_lines(self, ri, member, presence):
+ if self.is_blob():
+ return [f"{presence} = {self.c_name}->len;",
+ f"{member} = malloc(sizeof(*{member}) + {presence});",
+ f"memcpy({member}, {self.c_name}, sizeof(*{member}) + {presence});"]
return [f"{member} = {self.c_name};",
f"{presence} = n_{self.c_name};"]
@@ -2164,7 +2199,8 @@ def _multi_parse(ri, struct, init_lines, local_vars):
else:
raise Exception(f'Not supported sub-type {aspec["sub-type"]}')
if 'multi-attr' in aspec:
- multi_attrs.add(arg)
+ if not aspec.bloblen:
+ multi_attrs.add(arg)
needs_parg |= 'nested-attributes' in aspec
needs_parg |= 'sub-message' in aspec
@@ -2388,6 +2424,33 @@ def parse_rsp_msg(ri, deref=False):
ri.cw.nl()
+def _blob_attrs(ri):
+ return [attr for _, attr in ri.struct['reply'].member_list() if attr.bloblen]
+
+
+def print_rsp_more(ri):
+ blob_attrs = _blob_attrs(ri)
+ if not blob_attrs:
+ return
+
+ local_vars = [f'{type_name(ri, "reply", deref=True)} *rsp = data;']
+
+ ri.cw.write_func_prot('static bool',
+ f'{op_prefix(ri, "reply", deref=True)}_more',
+ ['void *data'], suffix='')
+ ri.cw.block_start()
+ ri.cw.write_func_lvar(local_vars)
+
+ for attr in blob_attrs:
+ ri.cw.p(f"if (rsp->{attr.c_name} && " +
+ f"rsp->_len.{attr.c_name} < rsp->{attr.c_name}->len)")
+ ri.cw.p('return true;')
+
+ ri.cw.p('return false;')
+ ri.cw.block_end()
+ ri.cw.nl()
+
+
def print_req(ri):
ret_ok = '0'
ret_err = '-1'
@@ -2482,6 +2545,8 @@ def print_dump(ri):
ri.cw.p("yds.yarg.data = NULL;")
ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});")
ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;")
+ if _blob_attrs(ri):
+ ri.cw.p(f"yds.more = {op_prefix(ri, 'reply', deref=True)}_more;")
if ri.op.value is not None:
ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};')
else:
@@ -3731,6 +3796,7 @@ def main():
parse_rsp_msg(ri, deref=True)
print_req_free(ri)
print_dump_type_free(ri)
+ print_rsp_more(ri)
print_dump(ri)
cw.nl()
--
2.54.0
next prev parent reply other threads:[~2026-07-05 22:08 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
2026-07-05 22:08 ` [PATCH 01/15] netlink: specs: Introduce multi-message blobs for SPDM Dan Williams
2026-07-05 22:08 ` [PATCH 02/15] tools: ynl: Teach pyynl to handle blobs Dan Williams
2026-07-05 22:08 ` Dan Williams [this message]
2026-07-05 22:08 ` [PATCH 04/15] device core: Introduce "device evidence" over netlink Dan Williams
2026-07-05 22:08 ` [PATCH 05/15] device core: Add "device evidence" 'validate' command Dan Williams
2026-07-05 22:08 ` [PATCH 06/15] PCI/TSM: Add device evidence support Dan Williams
2026-07-05 22:08 ` [PATCH 07/15] modules: Document the global async_probe parameter Dan Williams
2026-07-05 22:08 ` [PATCH 08/15] device core: Initial device trust infrastructure Dan Williams
2026-07-06 13:45 ` Jason Gunthorpe
2026-07-05 22:08 ` [PATCH 09/15] PCI, device core: Move "untrusted" concept to DEVICE_TRUST_ADVERSARY Dan Williams
2026-07-06 13:49 ` Jason Gunthorpe
2026-07-05 22:08 ` [PATCH 10/15] PCI/TSM: Add device interface security LOCKED support Dan Williams
2026-07-05 22:08 ` [PATCH 11/15] PCI/TSM: Add device interface security RUN support Dan Williams
2026-07-05 22:08 ` [PATCH 12/15] PCI/TSM: Add device interface security DMA enable/disable Dan Williams
2026-07-05 22:08 ` [PATCH 13/15] PCI, device core: Add private memory access for DEVICE_TRUST_TCB Dan Williams
2026-07-06 12:42 ` Aneesh Kumar K.V
2026-07-05 22:08 ` [PATCH 14/15] PCI/TSM: Create MMIO descriptors via TDISP Report Dan Williams
2026-07-05 22:08 ` [PATCH 15/15] PCI/TSM: Add relative MMIO offset support? Dan Williams
2026-07-06 12:51 ` [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Jason Gunthorpe
2026-07-06 20:55 ` Dan Williams (nvidia)
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=20260705220819.2472765-4-djbw@kernel.org \
--to=djbw@kernel.org \
--cc=alistair.francis@wdc.com \
--cc=ankita@nvidia.com \
--cc=donald.hunter@gmail.com \
--cc=driver-core@lists.linux.dev \
--cc=kuba@kernel.org \
--cc=linux-coco@lists.linux.dev \
--cc=linux-pci@vger.kernel.org \
--cc=lukas@wunner.de \
/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