From nobody Thu Feb 12 01:27:28 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C7925C77B7F for ; Wed, 3 May 2023 09:07:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229995AbjECJHx (ORCPT ); Wed, 3 May 2023 05:07:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:45818 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229911AbjECJHh (ORCPT ); Wed, 3 May 2023 05:07:37 -0400 Received: from mail-wr1-x42f.google.com (mail-wr1-x42f.google.com [IPv6:2a00:1450:4864:20::42f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6D3201FE3 for ; Wed, 3 May 2023 02:07:23 -0700 (PDT) Received: by mail-wr1-x42f.google.com with SMTP id ffacd0b85a97d-2fc3f1d6f8cso2976472f8f.3 for ; Wed, 03 May 2023 02:07:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=metaspace-dk.20221208.gappssmtp.com; s=20221208; t=1683104841; x=1685696841; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=gSA28PAasqbQ6gihh6v56FFurfwvVr3++GQNYiXLBOM=; b=vQsE9+nWwjoc0CqWeJtb/ZfNGHy0rUvS1Y6KMIM/8XIALq5wndo94M0A9dNDG/sgx3 TntjBxyhGd3sPHq81BA5d+rCrZqPK+YW4IthsZPI1CtTP6bLLkexUwvgl31L4ic67Qf3 3a/12tbFt4/FvWOnftXK8qByjLSFlbpkmQMkf5C8I3hc6vlYpAnFzNFcDxdS2MRw4IEx wPbbwhJsNepi1C2Pis2b+onQe5ZIJJr4481QrdlPAHTxbbNDusKuXuoh7FCB5nas1ipJ GJZUZOXkGrlNsq2sAq9Ubuo5revBkkMADIqKLVPTnZWg7liUy4NgrQWnKD4vuzI/VlLI s34Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1683104841; x=1685696841; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=gSA28PAasqbQ6gihh6v56FFurfwvVr3++GQNYiXLBOM=; b=gubZbuZWBV2BcI1ZfV/2i/NNXAjhxlKHPxxydid1/9yvDoxvQ23FhL8kLh9xqpjmqh +evqKAjDStriPfD2YQyIAocLq96s4Lu2mMZmaEJolo1l4/stN9juV482uWMix+3sEIAC wdsZhTQ/Lx/zduXdjXs1QuiUHBEU0Gad079WtN3/gqwocQANouajj6PL26f9nCsi7ecK 2BQaNgISUaW76Vzaig3qYd+phGxEAi4LyqVZPhpLrNac4tmHXB5J3b3BBzi03NVxOUML 5MSCsGIAS6XgbMaAV04y9/dAQpFOzYR+w5fjo4jroRGIoiwylBq/kL4vXlkSBZ8hKAyY r1XA== X-Gm-Message-State: AC+VfDwezXsJCCFxbU0Z9vwV1r9J7jmKgS2NbWruyO3nFDdOHZWKT8Oi 9qad3/yNRQVLfGq0C3iT0j2Fhg== X-Google-Smtp-Source: ACHHUZ7Ksbe7Ooyt5nBUxHytKU58iAJRK1L03foLc8Hcc6DMHl/WjWoe8IHgUJ0g8IzAAH00aNw/ZQ== X-Received: by 2002:adf:f68c:0:b0:2f5:6430:35c with SMTP id v12-20020adff68c000000b002f56430035cmr13820637wrp.26.1683104841285; Wed, 03 May 2023 02:07:21 -0700 (PDT) Received: from localhost ([147.161.155.99]) by smtp.gmail.com with ESMTPSA id q11-20020a5d574b000000b003049d7b9f4csm19166645wrw.32.2023.05.03.02.07.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 03 May 2023 02:07:21 -0700 (PDT) From: Andreas Hindborg To: Jens Axboe , Christoph Hellwig , Keith Busch , Damien Le Moal , Hannes Reinecke , lsf-pc@lists.linux-foundation.org, rust-for-linux@vger.kernel.org, linux-block@vger.kernel.org Cc: Andreas Hindborg , Matthew Wilcox , Miguel Ojeda , Alex Gaynor , Wedson Almeida Filho , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , linux-kernel@vger.kernel.org (open list), gost.dev@samsung.com Subject: [RFC PATCH 05/11] RUST: add `module_params` macro Date: Wed, 3 May 2023 11:07:02 +0200 Message-Id: <20230503090708.2524310-6-nmi@metaspace.dk> X-Mailer: git-send-email 2.40.0 In-Reply-To: <20230503090708.2524310-1-nmi@metaspace.dk> References: <20230503090708.2524310-1-nmi@metaspace.dk> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: Andreas Hindborg This patch includes changes required for Rust kernel modules to utilize mod= ule parameters. Imported with minor changes from `rust` branch [1], see original code for attributions. [1] https://github.com/Rust-for-Linux/linux/tree/bc22545f38d74473cfef3e9fd6= 5432733435b79f Cc: adamrk --- rust/kernel/lib.rs | 3 + rust/kernel/module_param.rs | 501 ++++++++++++++++++++++++++++++++++++ rust/macros/helpers.rs | 46 +++- rust/macros/module.rs | 402 +++++++++++++++++++++++++++-- 4 files changed, 920 insertions(+), 32 deletions(-) create mode 100644 rust/kernel/module_param.rs diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index cd798d12d97c..a0bd0b0e2aef 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -22,6 +22,7 @@ #![feature(pin_macro)] #![feature(receiver_trait)] #![feature(unsize)] +#![feature(const_mut_refs)] =20 // Ensure conditional compilation based on the kernel configuration works; // otherwise we may silently break things like initcall handling. @@ -39,6 +40,8 @@ mod build_assert; pub mod error; pub mod init; pub mod ioctl; +#[doc(hidden)] +pub mod module_param; pub mod pages; pub mod prelude; pub mod print; diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs new file mode 100644 index 000000000000..539decbdcd48 --- /dev/null +++ b/rust/kernel/module_param.rs @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Types for module parameters. +//! +//! C header: [`include/linux/moduleparam.h`](../../../include/linux/modul= eparam.h) + +use crate::error::{code::*, from_result}; +use crate::str::{CStr, Formatter}; +use core::fmt::Write; + +/// Types that can be used for module parameters. +/// +/// Note that displaying the type in `sysfs` will fail if +/// [`alloc::string::ToString::to_string`] (as implemented through the +/// [`core::fmt::Display`] trait) writes more than [`PAGE_SIZE`] +/// bytes (including an additional null terminator). +/// +/// [`PAGE_SIZE`]: `crate::PAGE_SIZE` +pub trait ModuleParam: core::fmt::Display + core::marker::Sized { + /// The `ModuleParam` will be used by the kernel module through this t= ype. + /// + /// This may differ from `Self` if, for example, `Self` needs to track + /// ownership without exposing it or allocate extra space for other po= ssible + /// parameter values. See [`StringParam`] or [`ArrayParam`] for exampl= es. + type Value: ?Sized; + + /// Whether the parameter is allowed to be set without an argument. + /// + /// Setting this to `true` allows the parameter to be passed without an + /// argument (e.g. just `module.param` instead of `module.param=3Dfoo`= ). + const NOARG_ALLOWED: bool; + + /// Convert a parameter argument into the parameter value. + /// + /// `None` should be returned when parsing of the argument fails. + /// `arg =3D=3D None` indicates that the parameter was passed without = an + /// argument. If `NOARG_ALLOWED` is set to `false` then `arg` is guara= nteed + /// to always be `Some(_)`. + /// + /// Parameters passed at boot time will be set before [`kmalloc`] is + /// available (even if the module is loaded at a later time). However,= in + /// this case, the argument buffer will be valid for the entire lifeti= me of + /// the kernel. So implementations of this method which need to alloca= te + /// should first check that the allocator is available (with + /// [`crate::bindings::slab_is_available`]) and when it is not availab= le + /// provide an alternative implementation which doesn't allocate. In c= ases + /// where the allocator is not available it is safe to save references= to + /// `arg` in `Self`, but in other cases a copy should be made. + /// + /// [`kmalloc`]: ../../../include/linux/slab.h + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option; + + /// Get the current value of the parameter for use in the kernel modul= e. + /// + /// This function should not be used directly. Instead use the wrapper + /// `read` which will be generated by [`macros::module`]. + fn value(&self) -> &Self::Value; + + /// Set the module parameter from a string. + /// + /// Used to set the parameter value when loading the module or when set + /// through `sysfs`. + /// + /// # Safety + /// + /// If `val` is non-null then it must point to a valid null-terminated + /// string. The `arg` field of `param` must be an instance of `Self`. + unsafe extern "C" fn set_param( + val: *const core::ffi::c_char, + param: *const crate::bindings::kernel_param, + ) -> core::ffi::c_int { + let arg =3D if val.is_null() { + None + } else { + Some(unsafe { CStr::from_char_ptr(val).as_bytes() }) + }; + match Self::try_from_param_arg(arg) { + Some(new_value) =3D> { + let old_value =3D unsafe { (*param).__bindgen_anon_1.arg a= s *mut Self }; + let _ =3D unsafe { core::ptr::replace(old_value, new_value= ) }; + 0 + } + None =3D> EINVAL.to_errno(), + } + } + + /// Write a string representation of the current parameter value to `b= uf`. + /// + /// Used for displaying the current parameter value in `sysfs`. + /// + /// # Safety + /// + /// `buf` must be a buffer of length at least `kernel::PAGE_SIZE` that= is + /// writeable. The `arg` field of `param` must be an instance of `Self= `. + unsafe extern "C" fn get_param( + buf: *mut core::ffi::c_char, + param: *const crate::bindings::kernel_param, + ) -> core::ffi::c_int { + from_result(|| { + // SAFETY: The C contracts guarantees that the buffer is at le= ast `PAGE_SIZE` bytes. + let mut f =3D unsafe { Formatter::from_buffer(buf.cast(), crat= e::PAGE_SIZE as usize) }; + unsafe { write!(f, "{}\0", *((*param).__bindgen_anon_1.arg as = *mut Self)) }?; + Ok(f.bytes_written().try_into()?) + }) + } + + /// Drop the parameter. + /// + /// Called when unloading a module. + /// + /// # Safety + /// + /// The `arg` field of `param` must be an instance of `Self`. + unsafe extern "C" fn free(arg: *mut core::ffi::c_void) { + unsafe { core::ptr::drop_in_place(arg as *mut Self) }; + } +} + +/// Trait for parsing integers. +/// +/// Strings beginning with `0x`, `0o`, or `0b` are parsed as hex, octal, or +/// binary respectively. Strings beginning with `0` otherwise are parsed as +/// octal. Anything else is parsed as decimal. A leading `+` or `-` is also +/// permitted. Any string parsed by [`kstrtol()`] or [`kstrtoul()`] will be +/// successfully parsed. +/// +/// [`kstrtol()`]: https://www.kernel.org/doc/html/latest/core-api/kernel-= api.html#c.kstrtol +/// [`kstrtoul()`]: https://www.kernel.org/doc/html/latest/core-api/kernel= -api.html#c.kstrtoul +trait ParseInt: Sized { + fn from_str_radix(src: &str, radix: u32) -> Result; + fn checked_neg(self) -> Option; + + fn from_str_unsigned(src: &str) -> Result { + let (radix, digits) =3D if let Some(n) =3D src.strip_prefix("0x") { + (16, n) + } else if let Some(n) =3D src.strip_prefix("0X") { + (16, n) + } else if let Some(n) =3D src.strip_prefix("0o") { + (8, n) + } else if let Some(n) =3D src.strip_prefix("0O") { + (8, n) + } else if let Some(n) =3D src.strip_prefix("0b") { + (2, n) + } else if let Some(n) =3D src.strip_prefix("0B") { + (2, n) + } else if src.starts_with('0') { + (8, src) + } else { + (10, src) + }; + Self::from_str_radix(digits, radix) + } + + fn from_str(src: &str) -> Option { + match src.bytes().next() { + None =3D> None, + Some(b'-') =3D> Self::from_str_unsigned(&src[1..]).ok()?.check= ed_neg(), + Some(b'+') =3D> Some(Self::from_str_unsigned(&src[1..]).ok()?), + Some(_) =3D> Some(Self::from_str_unsigned(src).ok()?), + } + } +} + +macro_rules! impl_parse_int { + ($ty:ident) =3D> { + impl ParseInt for $ty { + fn from_str_radix(src: &str, radix: u32) -> Result { + $ty::from_str_radix(src, radix) + } + + fn checked_neg(self) -> Option { + self.checked_neg() + } + } + }; +} + +impl_parse_int!(i8); +impl_parse_int!(u8); +impl_parse_int!(i16); +impl_parse_int!(u16); +impl_parse_int!(i32); +impl_parse_int!(u32); +impl_parse_int!(i64); +impl_parse_int!(u64); +impl_parse_int!(isize); +impl_parse_int!(usize); + +macro_rules! impl_module_param { + ($ty:ident) =3D> { + impl ModuleParam for $ty { + type Value =3D $ty; + + const NOARG_ALLOWED: bool =3D false; + + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option { + let bytes =3D arg?; + let utf8 =3D core::str::from_utf8(bytes).ok()?; + <$ty as crate::module_param::ParseInt>::from_str(utf8) + } + + #[inline(always)] + fn value(&self) -> &Self::Value { + self + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +/// Generate a static [`kernel_param_ops`](../../../include/linux/modulepa= ram.h) struct. +/// +/// # Examples +/// +/// ```ignore +/// make_param_ops!( +/// /// Documentation for new param ops. +/// PARAM_OPS_MYTYPE, // Name for the static. +/// MyType // A type which implements [`ModuleParam`]. +/// ); +/// ``` +macro_rules! make_param_ops { + ($ops:ident, $ty:ty) =3D> { + $crate::make_param_ops!( + #[doc=3D""] + $ops, + $ty + ); + }; + ($(#[$meta:meta])* $ops:ident, $ty:ty) =3D> { + $(#[$meta])* + /// + /// Static [`kernel_param_ops`](../../../include/linux/moduleparam= .h) + /// struct generated by [`make_param_ops`]. + pub static $ops: $crate::bindings::kernel_param_ops =3D $crate::bi= ndings::kernel_param_ops { + flags: if <$ty as $crate::module_param::ModuleParam>::NOARG_AL= LOWED { + $crate::bindings::KERNEL_PARAM_OPS_FL_NOARG + } else { + 0 + }, + set: Some(<$ty as $crate::module_param::ModuleParam>::set_para= m), + get: Some(<$ty as $crate::module_param::ModuleParam>::get_para= m), + free: Some(<$ty as $crate::module_param::ModuleParam>::free), + }; + }; +} + +impl_module_param!(i8); +impl_module_param!(u8); +impl_module_param!(i16); +impl_module_param!(u16); +impl_module_param!(i32); +impl_module_param!(u32); +impl_module_param!(i64); +impl_module_param!(u64); +impl_module_param!(isize); +impl_module_param!(usize); + +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`i8`]. + PARAM_OPS_I8, + i8 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`u8`]. + PARAM_OPS_U8, + u8 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`i16`]. + PARAM_OPS_I16, + i16 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`u16`]. + PARAM_OPS_U16, + u16 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`i32`]. + PARAM_OPS_I32, + i32 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`u32`]. + PARAM_OPS_U32, + u32 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`i64`]. + PARAM_OPS_I64, + i64 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`u64`]. + PARAM_OPS_U64, + u64 +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`isize`]. + PARAM_OPS_ISIZE, + isize +); +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`usize`]. + PARAM_OPS_USIZE, + usize +); + +impl ModuleParam for bool { + type Value =3D bool; + + const NOARG_ALLOWED: bool =3D true; + + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option { + match arg { + None =3D> Some(true), + Some(b"y") | Some(b"Y") | Some(b"1") | Some(b"true") =3D> Some= (true), + Some(b"n") | Some(b"N") | Some(b"0") | Some(b"false") =3D> Som= e(false), + _ =3D> None, + } + } + + #[inline(always)] + fn value(&self) -> &Self::Value { + self + } +} + +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`bool`]. + PARAM_OPS_BOOL, + bool +); + +/// An array of at __most__ `N` values. +/// +/// # Invariant +/// +/// The first `self.used` elements of `self.values` are initialized. +pub struct ArrayParam { + values: [core::mem::MaybeUninit; N], + used: usize, +} + +impl ArrayParam { + fn values(&self) -> &[T] { + // SAFETY: The invariant maintained by `ArrayParam` allows us to c= ast + // the first `self.used` elements to `T`. + unsafe { + &*(&self.values[0..self.used] as *const [core::mem::MaybeUnini= t] as *const [T]) + } + } +} + +impl ArrayParam { + const fn new() -> Self { + // INVARIANT: The first `self.used` elements of `self.values` are + // initialized. + ArrayParam { + values: [core::mem::MaybeUninit::uninit(); N], + used: 0, + } + } + + const fn push(&mut self, val: T) { + if self.used < N { + // INVARIANT: The first `self.used` elements of `self.values` = are + // initialized. + self.values[self.used] =3D core::mem::MaybeUninit::new(val); + self.used +=3D 1; + } + } + + /// Create an instance of `ArrayParam` initialized with `vals`. + /// + /// This function is only meant to be used in the [`module::module`] m= acro. + pub const fn create(vals: &[T]) -> Self { + let mut result =3D ArrayParam::new(); + let mut i =3D 0; + while i < vals.len() { + result.push(vals[i]); + i +=3D 1; + } + result + } +} + +impl core::fmt::Display for ArrayPa= ram { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for val in self.values() { + write!(f, "{},", val)?; + } + Ok(()) + } +} + +impl ModulePar= am + for ArrayParam +{ + type Value =3D [T]; + + const NOARG_ALLOWED: bool =3D false; + + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option { + arg.and_then(|args| { + let mut result =3D Self::new(); + for arg in args.split(|b| *b =3D=3D b',') { + result.push(T::try_from_param_arg(Some(arg))?); + } + Some(result) + }) + } + + fn value(&self) -> &Self::Value { + self.values() + } +} + +/// A C-style string parameter. +/// +/// The Rust version of the [`charp`] parameter. This type is meant to be +/// used by the [`macros::module`] macro, not handled directly. Instead us= e the +/// `read` method generated by that macro. +/// +/// [`charp`]: ../../../include/linux/moduleparam.h +pub enum StringParam { + /// A borrowed parameter value. + /// + /// Either the default value (which is static in the module) or borrow= ed + /// from the original argument buffer used to set the value. + Ref(&'static [u8]), + + /// A value that was allocated when the parameter was set. + /// + /// The value needs to be freed when the parameter is reset or the mod= ule is + /// unloaded. + Owned(alloc::vec::Vec), +} + +impl StringParam { + fn bytes(&self) -> &[u8] { + match self { + StringParam::Ref(bytes) =3D> *bytes, + StringParam::Owned(vec) =3D> &vec[..], + } + } +} + +impl core::fmt::Display for StringParam { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let bytes =3D self.bytes(); + match core::str::from_utf8(bytes) { + Ok(utf8) =3D> write!(f, "{}", utf8), + Err(_) =3D> write!(f, "{:?}", bytes), + } + } +} + +impl ModuleParam for StringParam { + type Value =3D [u8]; + + const NOARG_ALLOWED: bool =3D false; + + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option { + // SAFETY: It is always safe to call [`slab_is_available`](../../.= ./include/linux/slab.h). + let slab_available =3D unsafe { crate::bindings::slab_is_available= () }; + arg.and_then(|arg| { + if slab_available { + let mut vec =3D alloc::vec::Vec::new(); + vec.try_extend_from_slice(arg).ok()?; + Some(StringParam::Owned(vec)) + } else { + Some(StringParam::Ref(arg)) + } + }) + } + + fn value(&self) -> &Self::Value { + self.bytes() + } +} + +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux= /moduleparam.h) + /// for [`StringParam`]. + PARAM_OPS_STR, + StringParam +); diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs index b2bdd4d8c958..8b5f9bc414d7 100644 --- a/rust/macros/helpers.rs +++ b/rust/macros/helpers.rs @@ -18,6 +18,16 @@ pub(crate) fn try_literal(it: &mut token_stream::IntoIte= r) -> Option { } } =20 +pub(crate) fn try_byte_string(it: &mut token_stream::IntoIter) -> Option { + try_literal(it).and_then(|byte_string| { + if byte_string.starts_with("b\"") && byte_string.ends_with('\"') { + Some(byte_string[2..byte_string.len() - 1].to_string()) + } else { + None + } + }) +} + pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option { try_literal(it).and_then(|string| { if string.starts_with('\"') && string.ends_with('\"') { @@ -46,14 +56,8 @@ pub(crate) fn expect_punct(it: &mut token_stream::IntoIt= er) -> char { } } =20 -pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String { - try_string(it).expect("Expected string") -} - -pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> Stri= ng { - let string =3D try_string(it).expect("Expected string"); - assert!(string.is_ascii(), "Expected ASCII string"); - string +pub(crate) fn expect_literal(it: &mut token_stream::IntoIter) -> String { + try_literal(it).expect("Expected Literal") } =20 pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group { @@ -64,8 +68,34 @@ pub(crate) fn expect_group(it: &mut token_stream::IntoIt= er) -> Group { } } =20 +pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String { + try_string(it).expect("Expected string") +} + +pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> Stri= ng { + let string =3D try_string(it).expect("Expected string"); + assert!(string.is_ascii(), "Expected ASCII string"); + string +} + pub(crate) fn expect_end(it: &mut token_stream::IntoIter) { if it.next().is_some() { panic!("Expected end"); } } + +pub(crate) fn get_literal(it: &mut token_stream::IntoIter, expected_name: = &str) -> String { + assert_eq!(expect_ident(it), expected_name); + assert_eq!(expect_punct(it), ':'); + let literal =3D expect_literal(it); + assert_eq!(expect_punct(it), ','); + literal +} + +pub(crate) fn get_string(it: &mut token_stream::IntoIter, expected_name: &= str) -> String { + assert_eq!(expect_ident(it), expected_name); + assert_eq!(expect_punct(it), ':'); + let string =3D expect_string(it); + assert_eq!(expect_punct(it), ','); + string +} diff --git a/rust/macros/module.rs b/rust/macros/module.rs index fb1244f8c2e6..172631e7fb05 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -1,25 +1,40 @@ // SPDX-License-Identifier: GPL-2.0 =20 -use crate::helpers::*; -use proc_macro::{token_stream, Delimiter, Literal, TokenStream, TokenTree}; +use proc_macro::{token_stream, Delimiter, Group, Literal, TokenStream, Tok= enTree}; use std::fmt::Write; =20 -fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec { - let group =3D expect_group(it); - assert_eq!(group.delimiter(), Delimiter::Bracket); - let mut values =3D Vec::new(); - let mut it =3D group.stream().into_iter(); - - while let Some(val) =3D try_string(&mut it) { - assert!(val.is_ascii(), "Expected ASCII string"); - values.push(val); - match it.next() { - Some(TokenTree::Punct(punct)) =3D> assert_eq!(punct.as_char(),= ','), - None =3D> break, - _ =3D> panic!("Expected ',' or end of array"), +use crate::helpers::*; + +#[derive(Clone, PartialEq)] +enum ParamType { + Ident(String), + Array { vals: String, max_length: usize }, +} + +fn expect_array_fields(it: &mut token_stream::IntoIter) -> ParamType { + assert_eq!(expect_punct(it), '<'); + let vals =3D expect_ident(it); + assert_eq!(expect_punct(it), ','); + let max_length_str =3D expect_literal(it); + let max_length =3D max_length_str + .parse::() + .expect("Expected usize length"); + assert_eq!(expect_punct(it), '>'); + ParamType::Array { vals, max_length } +} + +fn expect_type(it: &mut token_stream::IntoIter) -> ParamType { + if let TokenTree::Ident(ident) =3D it + .next() + .expect("Reached end of token stream for param type") + { + match ident.to_string().as_ref() { + "ArrayParam" =3D> expect_array_fields(it), + _ =3D> ParamType::Ident(ident.to_string()), } + } else { + panic!("Expected Param Type") } - values } =20 struct ModInfoBuilder<'a> { @@ -87,6 +102,113 @@ impl<'a> ModInfoBuilder<'a> { self.emit_only_builtin(field, content); self.emit_only_loadable(field, content); } + + fn emit_param(&mut self, field: &str, param: &str, content: &str) { + let content =3D format!("{param}:{content}", param =3D param, cont= ent =3D content); + self.emit(field, &content); + } +} + +fn permissions_are_readonly(perms: &str) -> bool { + let (radix, digits) =3D if let Some(n) =3D perms.strip_prefix("0x") { + (16, n) + } else if let Some(n) =3D perms.strip_prefix("0o") { + (8, n) + } else if let Some(n) =3D perms.strip_prefix("0b") { + (2, n) + } else { + (10, perms) + }; + match u32::from_str_radix(digits, radix) { + Ok(perms) =3D> perms & 0o222 =3D=3D 0, + Err(_) =3D> false, + } +} + +fn param_ops_path(param_type: &str) -> &'static str { + match param_type { + "bool" =3D> "kernel::module_param::PARAM_OPS_BOOL", + "i8" =3D> "kernel::module_param::PARAM_OPS_I8", + "u8" =3D> "kernel::module_param::PARAM_OPS_U8", + "i16" =3D> "kernel::module_param::PARAM_OPS_I16", + "u16" =3D> "kernel::module_param::PARAM_OPS_U16", + "i32" =3D> "kernel::module_param::PARAM_OPS_I32", + "u32" =3D> "kernel::module_param::PARAM_OPS_U32", + "i64" =3D> "kernel::module_param::PARAM_OPS_I64", + "u64" =3D> "kernel::module_param::PARAM_OPS_U64", + "isize" =3D> "kernel::module_param::PARAM_OPS_ISIZE", + "usize" =3D> "kernel::module_param::PARAM_OPS_USIZE", + "str" =3D> "kernel::module_param::PARAM_OPS_STR", + t =3D> panic!("Unrecognized type {}", t), + } +} + +#[allow(clippy::type_complexity)] +fn try_simple_param_val( + param_type: &str, +) -> Box Option> { + match param_type { + "bool" =3D> Box::new(try_ident), + "str" =3D> Box::new(|param_it| { + try_byte_string(param_it) + .map(|s| format!("kernel::module_param::StringParam::Ref(b= \"{}\")", s)) + }), + _ =3D> Box::new(try_literal), + } +} + +fn get_default(param_type: &ParamType, param_it: &mut token_stream::IntoIt= er) -> String { + let try_param_val =3D match param_type { + ParamType::Ident(ref param_type) + | ParamType::Array { + vals: ref param_type, + max_length: _, + } =3D> try_simple_param_val(param_type), + }; + assert_eq!(expect_ident(param_it), "default"); + assert_eq!(expect_punct(param_it), ':'); + let default =3D match param_type { + ParamType::Ident(_) =3D> try_param_val(param_it).expect("Expected = default param value"), + ParamType::Array { + vals: _, + max_length: _, + } =3D> { + let group =3D expect_group(param_it); + assert_eq!(group.delimiter(), Delimiter::Bracket); + let mut default_vals =3D Vec::new(); + let mut it =3D group.stream().into_iter(); + + while let Some(default_val) =3D try_param_val(&mut it) { + default_vals.push(default_val); + match it.next() { + Some(TokenTree::Punct(punct)) =3D> assert_eq!(punct.as= _char(), ','), + None =3D> break, + _ =3D> panic!("Expected ',' or end of array default va= lues"), + } + } + + let mut default_array =3D "kernel::module_param::ArrayParam::c= reate(&[".to_string(); + default_array.push_str( + &default_vals + .iter() + .map(|val| val.to_string()) + .collect::>() + .join(","), + ); + default_array.push_str("])"); + default_array + } + }; + assert_eq!(expect_punct(param_it), ','); + default +} + +fn generated_array_ops_name(vals: &str, max_length: usize) -> String { + format!( + "__generated_array_ops_{vals}_{max_length}", + vals =3D vals, + max_length =3D max_length + ) } =20 #[derive(Debug, Default)] @@ -96,15 +218,24 @@ struct ModuleInfo { name: String, author: Option, description: Option, - alias: Option>, + alias: Option, + params: Option, } =20 impl ModuleInfo { fn parse(it: &mut token_stream::IntoIter) -> Self { let mut info =3D ModuleInfo::default(); =20 - const EXPECTED_KEYS: &[&str] =3D - &["type", "name", "author", "description", "license", "alias"]; + const EXPECTED_KEYS: &[&str] =3D &[ + "type", + "name", + "author", + "description", + "license", + "alias", + "alias_rtnl_link", + "params", + ]; const REQUIRED_KEYS: &[&str] =3D &["type", "name", "license"]; let mut seen_keys =3D Vec::new(); =20 @@ -130,7 +261,11 @@ impl ModuleInfo { "author" =3D> info.author =3D Some(expect_string(it)), "description" =3D> info.description =3D Some(expect_string= (it)), "license" =3D> info.license =3D expect_string_ascii(it), - "alias" =3D> info.alias =3D Some(expect_string_array(it)), + "alias" =3D> info.alias =3D Some(expect_string_ascii(it)), + "alias_rtnl_link" =3D> { + info.alias =3D Some(format!("rtnl-link-{}", expect_str= ing_ascii(it))) + } + "params" =3D> info.params =3D Some(expect_group(it)), _ =3D> panic!( "Unknown key \"{}\". Valid keys are: {:?}.", key, EXPECTED_KEYS @@ -181,10 +316,8 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { modinfo.emit("description", &description); } modinfo.emit("license", &info.license); - if let Some(aliases) =3D info.alias { - for alias in aliases { - modinfo.emit("alias", &alias); - } + if let Some(alias) =3D info.alias { + modinfo.emit("alias", &alias); } =20 // Built-in modules also export the `file` modinfo string. @@ -192,6 +325,195 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE= environmental variable"); modinfo.emit_only_builtin("file", &file); =20 + let mut array_types_to_generate =3D Vec::new(); + if let Some(params) =3D info.params { + assert_eq!(params.delimiter(), Delimiter::Brace); + + let mut it =3D params.stream().into_iter(); + + loop { + let param_name =3D match it.next() { + Some(TokenTree::Ident(ident)) =3D> ident.to_string(), + Some(_) =3D> panic!("Expected Ident or end"), + None =3D> break, + }; + + assert_eq!(expect_punct(&mut it), ':'); + let param_type =3D expect_type(&mut it); + let group =3D expect_group(&mut it); + assert_eq!(expect_punct(&mut it), ','); + + assert_eq!(group.delimiter(), Delimiter::Brace); + + let mut param_it =3D group.stream().into_iter(); + let param_default =3D get_default(¶m_type, &mut param_it); + let param_permissions =3D get_literal(&mut param_it, "permissi= ons"); + let param_description =3D get_string(&mut param_it, "descripti= on"); + expect_end(&mut param_it); + + // TODO: More primitive types. + // TODO: Other kinds: unsafes, etc. + let (param_kernel_type, ops): (String, _) =3D match param_type= { + ParamType::Ident(ref param_type) =3D> ( + param_type.to_string(), + param_ops_path(param_type).to_string(), + ), + ParamType::Array { + ref vals, + max_length, + } =3D> { + array_types_to_generate.push((vals.clone(), max_length= )); + ( + format!("__rust_array_param_{}_{}", vals, max_leng= th), + generated_array_ops_name(vals, max_length), + ) + } + }; + + modinfo.emit_param("parmtype", ¶m_name, ¶m_kernel_type= ); + modinfo.emit_param("parm", ¶m_name, ¶m_description); + let param_type_internal =3D match param_type { + ParamType::Ident(ref param_type) =3D> match param_type.as_= ref() { + "str" =3D> "kernel::module_param::StringParam".to_stri= ng(), + other =3D> other.to_string(), + }, + ParamType::Array { + ref vals, + max_length, + } =3D> format!( + "kernel::module_param::ArrayParam<{vals}, {max_length}= >", + vals =3D vals, + max_length =3D max_length + ), + }; + let read_func =3D if permissions_are_readonly(¶m_permissio= ns) { + format!( + " + fn read(&self) + -> &<{param_type_internal} as kernel::module_p= aram::ModuleParam>::Value {{ + // SAFETY: Parameters do not need to be locked= because they are + // read only or sysfs is not enabled. + unsafe {{ + <{param_type_internal} as kernel::module_p= aram::ModuleParam>::value( + &__{name}_{param_name}_value + ) + }} + }} + ", + name =3D info.name, + param_name =3D param_name, + param_type_internal =3D param_type_internal, + ) + } else { + format!( + " + fn read<'lck>(&self, lock: &'lck kernel::KParamGua= rd) + -> &'lck <{param_type_internal} as kernel::mod= ule_param::ModuleParam>::Value {{ + // SAFETY: Parameters are locked by `KParamGua= rd`. + unsafe {{ + <{param_type_internal} as kernel::module_p= aram::ModuleParam>::value( + &__{name}_{param_name}_value + ) + }} + }} + ", + name =3D info.name, + param_name =3D param_name, + param_type_internal =3D param_type_internal, + ) + }; + let kparam =3D format!( + " + kernel::bindings::kernel_param__bindgen_ty_1 {{ + arg: unsafe {{ &__{name}_{param_name}_value }} + as *const _ as *mut core::ffi::c_void, + }}, + ", + name =3D info.name, + param_name =3D param_name, + ); + write!( + modinfo.buffer, + " + static mut __{name}_{param_name}_value: {param_type_intern= al} =3D {param_default}; + + struct __{name}_{param_name}; + + impl __{name}_{param_name} {{ {read_func} }} + + const {param_name}: __{name}_{param_name} =3D __{name}_{pa= ram_name}; + + // Note: the C macro that generates the static structs for= the `__param` section + // asks for them to be `aligned(sizeof(void *))`. However,= that was put in place + // in 2003 in commit 38d5b085d2a0 (\"[PATCH] Fix over-alig= nment problem on x86-64\") + // to undo GCC over-alignment of static structs of >32 byt= es. It seems that is + // not the case anymore, so we simplify to a transparent r= epresentation here + // in the expectation that it is not needed anymore. + // TODO: Revisit this to confirm the above comment and rem= ove it if it happened. + #[repr(transparent)] + struct __{name}_{param_name}_RacyKernelParam(kernel::bindi= ngs::kernel_param); + + unsafe impl Sync for __{name}_{param_name}_RacyKernelParam= {{ + }} + + #[cfg(not(MODULE))] + const __{name}_{param_name}_name: *const core::ffi::c_char= =3D + b\"{name}.{param_name}\\0\" as *const _ as *const core= ::ffi::c_char; + + #[cfg(MODULE)] + const __{name}_{param_name}_name: *const core::ffi::c_char= =3D + b\"{param_name}\\0\" as *const _ as *const core::ffi::= c_char; + + #[link_section =3D \"__param\"] + #[used] + static __{name}_{param_name}_struct: __{name}_{param_name}= _RacyKernelParam =3D + __{name}_{param_name}_RacyKernelParam(kernel::bindings= ::kernel_param {{ + name: __{name}_{param_name}_name, + // SAFETY: `__this_module` is constructed by the k= ernel at load time + // and will not be freed until the module is unloa= ded. + #[cfg(MODULE)] + mod_: unsafe {{ &kernel::bindings::__this_module a= s *const _ as *mut _ }}, + #[cfg(not(MODULE))] + mod_: core::ptr::null_mut(), + ops: unsafe {{ &{ops} }} as *const kernel::binding= s::kernel_param_ops, + perm: {permissions}, + level: -1, + flags: 0, + __bindgen_anon_1: {kparam} + }}); + ", + name =3D info.name, + param_type_internal =3D param_type_internal, + read_func =3D read_func, + param_default =3D param_default, + param_name =3D param_name, + ops =3D ops, + permissions =3D param_permissions, + kparam =3D kparam, + ) + .unwrap(); + } + } + + let mut generated_array_types =3D String::new(); + + for (vals, max_length) in array_types_to_generate { + let ops_name =3D generated_array_ops_name(&vals, max_length); + write!( + generated_array_types, + " + kernel::make_param_ops!( + {ops_name}, + kernel::module_param::ArrayParam<{vals}, {{ {max_lengt= h} }}> + ); + ", + ops_name =3D ops_name, + vals =3D vals, + max_length =3D max_length, + ) + .unwrap(); + } + format!( " /// The module name. @@ -291,12 +613,44 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { }} =20 {modinfo} + + {generated_array_types} ", type_ =3D info.type_, name =3D info.name, modinfo =3D modinfo.buffer, + generated_array_types =3D generated_array_types, initcall_section =3D ".initcall6.init" ) .parse() .expect("Error parsing formatted string into token stream.") } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_permissions_are_readonly() { + assert!(permissions_are_readonly("0b000000000")); + assert!(permissions_are_readonly("0o000")); + assert!(permissions_are_readonly("000")); + assert!(permissions_are_readonly("0x000")); + + assert!(!permissions_are_readonly("0b111111111")); + assert!(!permissions_are_readonly("0o777")); + assert!(!permissions_are_readonly("511")); + assert!(!permissions_are_readonly("0x1ff")); + + assert!(permissions_are_readonly("0o014")); + assert!(permissions_are_readonly("0o015")); + + assert!(!permissions_are_readonly("0o214")); + assert!(!permissions_are_readonly("0o024")); + assert!(!permissions_are_readonly("0o012")); + + assert!(!permissions_are_readonly("0o315")); + assert!(!permissions_are_readonly("0o065")); + assert!(!permissions_are_readonly("0o017")); + } +} --=20 2.40.0