[PATCH 2/4] ynl_gen: generate Rust files from yaml files
From: Alice Ryhl <aliceryhl@google.com>
Date: 2026-03-06 15:12:27
Also in:
lkml, rust-for-linux
Subsystem:
networking [general], the rest, yaml netlink (ynl) · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds, Donald Hunter
To generate netlink frames from Rust code easily, generate Rust libraries with methods for generating different netlink messages as appropriate. The new 'rust' type corresponds to a Rust version of the C target 'kernel'. There is no Rust version of the 'uapi' target since Rust code exports its uapi via C headers - choice of language is opaque to userspace. Signed-off-by: Alice Ryhl <aliceryhl@google.com> --- tools/net/ynl/pyynl/ynl_gen_c.py | 132 ++++++++++++++++++++++++++++++++++++++- tools/net/ynl/ynl-regen.sh | 2 +- 2 files changed, 132 insertions(+), 2 deletions(-)
diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py
index 0e1e486c1185..5ddc8f84c4a3 100755
--- a/tools/net/ynl/pyynl/ynl_gen_c.py
+++ b/tools/net/ynl/pyynl/ynl_gen_c.py@@ -19,6 +19,7 @@ import pathlib import os import re import shutil +import subprocess import sys import tempfile import yaml as pyyaml
@@ -1744,6 +1745,19 @@ class CodeWriter: else: self.p('}' + line) + def array_start(self, line=''): + if line: + line = line + ' ' + self.p(line + '[') + self._ind += 1 + + def array_end(self, line=''): + if line and line[0] not in {';', ','}: + line = ' ' + line + self._ind -= 1 + self._nl = False + self.p(']' + line) + def write_doc_line(self, doc, indent=True): words = doc.split() line = ' *'
@@ -3415,10 +3429,119 @@ def find_kernel_root(full_path): return full_path, sub_path[:-1] +def render_rust(family, cw): + cw.p('#![allow(unreachable_pub, clippy::wrong_self_convention)]') + cw.p('use kernel::netlink::{Family, MulticastGroup};') + cw.p('use kernel::prelude::*;') + cw.nl() + + family_upper = c_upper(family.ident_name) + family_name = f'{family_upper}_NL_FAMILY' + mcgrps_name = f'{family_name}_MCGRPS' + + cw.p(f'pub static {family_name}: Family = Family::const_new(') + cw._ind += 1 + cw.p('&crate::THIS_MODULE,') + cw.p(f'kernel::uapi::{family.fam_key},') + cw.p(f'kernel::uapi::{family.ver_key},') + cw.p(f'&{mcgrps_name},') + cw._ind -= 1 + cw.p(');') + cw.nl() + + if family.mcgrps['list']: + cw.array_start(f'static {mcgrps_name}: [MulticastGroup; {len(family.mcgrps["list"])}] = ') + for grp in family.mcgrps['list']: + cw.p(f'MulticastGroup::const_new(c"{grp["name"]}"),') + cw.array_end(';') + cw.nl() + + for idx, (op_name, op) in enumerate(item for item in family.msgs.items() if 'event' in item[1]): + struct_name = op_name.capitalize() + + if 'doc' in op: + doc_lines = op['doc'].strip().split('\n') + for line in doc_lines: + cw.p(f'/// {line.strip()}') + + cw.block_start(f'pub struct {struct_name}') + cw.p('skb: kernel::netlink::GenlMsg,') + cw.block_end() + cw.nl() + + cw.block_start(f'impl {struct_name}') + cw.p('/// Create a new multicast message.') + cw.p('pub fn new(') + cw._ind += 1 + cw.p('size: usize,') + cw.p('portid: u32,') + cw.p('seq: u32,') + cw.p('flags: kernel::alloc::Flags,') + cw._ind -= 1 + cw.block_start(') -> Result<Self, kernel::alloc::AllocError>') + cw.p(f'const {op.enum_name}: u8 = kernel::uapi::{op.enum_name} as u8;') + cw.p('let skb = kernel::netlink::SkBuff::new(size, flags)?;') + cw.p(f'let skb = skb.genlmsg_put(portid, seq, &{family_name}, {op.enum_name})?;') + cw.p('Ok(Self { skb })') + cw.block_end() + cw.nl() + + cw.p('/// Broadcast this message.') + cw.block_start('pub fn multicast(self, portid: u32, flags: kernel::alloc::Flags) -> Result') + cw.p(f'self.skb.multicast(&{family_name}, portid, {idx}, flags)') + cw.block_end() + cw.nl() + + cw.p('/// Check if this message type has listeners.') + cw.block_start('pub fn has_listeners() -> bool') + cw.p(f'{family_name}.has_listeners({idx})') + cw.block_end() + + attr_set_name = op['attribute-set'] + attr_set = family.attr_sets[attr_set_name] + event_attrs = op['event']['attributes'] + + for attr_name in event_attrs: + attr = attr_set[attr_name] + method_name = attr_name.replace('-', '_') + + if attr.type == 'u32': + put_fn = 'put_u32' + arg_str = ', val' + method_args = '(&mut self, val: u32)' + elif attr.type == 'string': + put_fn = 'put_string' + arg_str = ', val' + method_args = '(&mut self, val: &CStr)' + elif attr.type == 'flag': + put_fn = 'put_flag' + arg_str = '' + method_args = '(&mut self)' + else: + put_fn = 'put_u32' + arg_str = ', val' + method_args = f'(&mut self, val: {attr.type})' + + cw.nl() + if 'doc' in attr.yaml: + doc_lines = attr.yaml['doc'].strip().split('\n') + for line in doc_lines: + cw.p(f'/// {line.strip()}') + + cw.block_start(f'pub fn {method_name}{method_args} -> Result') + cw.p(f'const {attr.enum_name}: c_int = kernel::uapi::{attr.enum_name} as c_int;') + cw.p(f'self.skb.{put_fn}({attr.enum_name}{arg_str})') + cw.block_end() + + cw.block_end() + cw.nl() + cw.p(' ') + + def main(): parser = argparse.ArgumentParser(description='Netlink simple parsing generator') parser.add_argument('--mode', dest='mode', type=str, required=True, - choices=('user', 'kernel', 'uapi')) + choices=('user', 'kernel', 'uapi', 'rust')) parser.add_argument('--spec', dest='spec', type=str, required=True) parser.add_argument('--header', dest='header', action='store_true', default=None) parser.add_argument('--source', dest='header', action='store_false')
@@ -3471,6 +3594,13 @@ def main(): render_uapi(parsed, cw) return + if args.mode == 'rust': + render_rust(parsed, cw) + cw.close_out_file() + if args.out_file: + subprocess.run(['rustfmt', '--edition', '2021', args.out_file]) + return + hdr_prot = f"_LINUX_{parsed.c_name.upper()}_GEN_H" if args.header: cw.p('#ifndef ' + hdr_prot)
diff --git a/tools/net/ynl/ynl-regen.sh b/tools/net/ynl/ynl-regen.sh
index d9809276db98..4f5ceb4fe147 100755
--- a/tools/net/ynl/ynl-regen.sh
+++ b/tools/net/ynl/ynl-regen.sh@@ -17,7 +17,7 @@ done KDIR=$(dirname $(dirname $(dirname $(dirname $(realpath $0))))) pushd ${search:-$KDIR} >>/dev/null -files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user\)') +files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user\|rust\)') for f in $files; do # params: 0 1 2 3 # $YAML YNL-GEN kernel $mode
--
2.53.0.473.g4a7958ca14-goog