Message ID | 20250109-module-params-v3-v4-4-c208bcfbe11f@kernel.org (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | rust: extend `module!` macro with integer parameter support | expand |
On Thu, Jan 09, 2025 at 11:54:59AM +0100, Andreas Hindborg wrote: > This patch includes changes required for Rust kernel modules to utilize > module parameters. This code implements read only support for integer > types without `sysfs` support. I know you want to keep this simple for now, but will you have to go and touch all users of this when you do add the sysfs support later? sysfs wants the mode of the file to be set here, so how do you think of that happening? And don't you need that for your null block driver? Also, what about all the other "types" of module parameters that are currently able to be done, like call-back, hardware control, and unsafe? Are we just not going to do that for rust code (no objection from me, just wanting to be sure.) thanks, greg k-h
"Greg KH" <gregkh@linuxfoundation.org> writes: > On Thu, Jan 09, 2025 at 11:54:59AM +0100, Andreas Hindborg wrote: >> This patch includes changes required for Rust kernel modules to utilize >> module parameters. This code implements read only support for integer >> types without `sysfs` support. > > I know you want to keep this simple for now, but will you have to go and > touch all users of this when you do add the sysfs support later? sysfs > wants the mode of the file to be set here, so how do you think of that > happening? We would add the required fields to the `module!` macro as optional fields. No need to touch everyone. Leaving out the sysfs file permission field would cause the parameter to not show up in sysfs. > And don't you need that for your null block driver? Yes I need it eventually. > Also, what about all the other "types" of module parameters that are > currently able to be done, like call-back, hardware control, and unsafe? > Are we just not going to do that for rust code (no objection from me, > just wanting to be sure.) Someone told me "no dead code", so I would defer those features to when we have a user. We have blueprints for strings and arrays based on Adams earlier work. I don't imagine any rust code relying on hw param and the use in the kernel seems to be very limited. Not sure about cb params. While it might be nice for the user to be able to pass a callback to decode/sanitize , the same effect could be achieved by putting the logic elsewhere. I don't have an idea of how to implement this in Rust at the moment, but I am sure we can come up with a solution if we need to. Best regards, Andreas Hindborg
On Thu, Jan 09, 2025 at 02:03:39PM +0100, Andreas Hindborg wrote: > "Greg KH" <gregkh@linuxfoundation.org> writes: > > > On Thu, Jan 09, 2025 at 11:54:59AM +0100, Andreas Hindborg wrote: > >> This patch includes changes required for Rust kernel modules to utilize > >> module parameters. This code implements read only support for integer > >> types without `sysfs` support. > > > > I know you want to keep this simple for now, but will you have to go and > > touch all users of this when you do add the sysfs support later? sysfs > > wants the mode of the file to be set here, so how do you think of that > > happening? > > We would add the required fields to the `module!` macro as optional > fields. No need to touch everyone. Leaving out the sysfs file permission > field would cause the parameter to not show up in sysfs. Ok, that sounds reasonable, thanks! greg k-h
On 1/9/25 11:54, Andreas Hindborg wrote: > This patch includes changes required for Rust kernel modules to utilize > module parameters. This code implements read only support for integer > types without `sysfs` support. > > Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org> > --- > rust/kernel/lib.rs | 1 + > rust/kernel/module_param.rs | 225 +++++++++++++++++++++++++++++++++++++++++++ > rust/macros/helpers.rs | 14 +++ > rust/macros/lib.rs | 31 ++++++ > rust/macros/module.rs | 188 ++++++++++++++++++++++++++++++++---- > samples/rust/rust_minimal.rs | 10 ++ > 6 files changed, 451 insertions(+), 18 deletions(-) > > diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs > index 4253c9a7fe7df935dc714bc3036d299bd80054a0..707ec81411c7aecccdd14e02cbf011642bc02e07 100644 > --- a/rust/kernel/lib.rs > +++ b/rust/kernel/lib.rs > @@ -46,6 +46,7 @@ > pub mod kunit; > pub mod list; > pub mod miscdevice; > +pub mod module_param; > #[cfg(CONFIG_NET)] > pub mod net; > pub mod once_lite; > diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs > new file mode 100644 > index 0000000000000000000000000000000000000000..6cb64096090a37c4f9c4ce73e23b7ac049e32d20 > --- /dev/null > +++ b/rust/kernel/module_param.rs > @@ -0,0 +1,225 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! Types for module parameters. > +//! > +//! C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) > + > +use crate::prelude::*; > +use crate::str::BStr; > + > +/// Newtype to make `bindings::kernel_param` [`Sync`]. > +#[repr(transparent)] > +#[doc(hidden)] > +pub struct RacyKernelParam(pub ::kernel::bindings::kernel_param); > + > +// SAFETY: C kernel handles serializing access to this type. We never access > +// from Rust module. > +unsafe impl Sync for RacyKernelParam {} > + > +/// Types that can be used for module parameters. > +/// > +/// Note that displaying the type in `sysfs` will fail if > +/// [`Display`](core::fmt::Display) implementation would write more than > +/// [`PAGE_SIZE`] - 1 bytes. > +/// > +/// [`PAGE_SIZE`]: `bindings::PAGE_SIZE` > +pub trait ModuleParam: Sized { > + /// The [`ModuleParam`] will be used by the kernel module through this type. > + /// > + /// This may differ from `Self` if, for example, `Self` needs to track > + /// ownership without exposing it or allocate extra space for other possible > + /// parameter values. > + // This is required to support string parameters in the future. > + type Value: ?Sized; > + > + /// Parse a parameter argument into the parameter value. > + /// > + /// `Err(_)` should be returned when parsing of the argument fails. > + /// > + /// 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 lifetime of > + /// the kernel. So implementations of this method which need to allocate > + /// should first check that the allocator is available (with > + /// [`crate::bindings::slab_is_available`]) and when it is not available > + /// provide an alternative implementation which doesn't allocate. In cases > + /// 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`]: srctree/include/linux/slab.h > + fn try_from_param_arg(arg: &'static [u8]) -> Result<Self>; > +} > + > +/// Set the module parameter from a string. > +/// > +/// Used to set the parameter value at kernel initialization, when loading > +/// the module or when set through `sysfs`. > +/// > +/// `param.arg` is a pointer to `*mut T` as set up by the [`module!`] > +/// macro. > +/// > +/// See `struct kernel_param_ops.set`. > +/// > +/// # 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 `T`. > +/// > +/// # Invariants > +/// > +/// Currently, we only support read-only parameters that are not readable > +/// from `sysfs`. Thus, this function is only called at kernel > +/// initialization time, or at module load time, and we have exclusive > +/// access to the parameter for the duration of the function. > +/// > +/// [`module!`]: macros::module > +unsafe extern "C" fn set_param<T>( > + val: *const core::ffi::c_char, > + param: *const crate::bindings::kernel_param, > +) -> core::ffi::c_int > +where > + T: ModuleParam, > +{ > + // NOTE: If we start supporting arguments without values, val _is_ allowed > + // to be null here. > + if val.is_null() { > + crate::pr_warn_once!("Null pointer passed to `module_param::set_param`"); > + return crate::error::code::EINVAL.to_errno(); > + } > + > + // SAFETY: By function safety requirement, val is non-null and > + // null-terminated. By C API contract, `val` is live and valid for reads > + // for the duration of this function. > + let arg = unsafe { CStr::from_char_ptr(val).as_bytes() }; > + > + crate::error::from_result(|| { > + let new_value = T::try_from_param_arg(arg)?; > + > + // SAFETY: `param` is guaranteed to be valid by C API contract > + // and `arg` is guaranteed to point to an instance of `T`. > + let old_value = unsafe { (*param).__bindgen_anon_1.arg as *mut T }; > + > + // SAFETY: `old_value` is valid for writes, as we have exclusive > + // access. `old_value` is pointing to an initialized static, an Typo: an -> and > + // so it is properly initialized. > + unsafe { core::ptr::replace(old_value, new_value) }; > + Ok(0) > + }) > +} > + > +/// Drop the parameter. > +/// > +/// Called when unloading a module. > +/// > +/// # Safety > +/// > +/// The `arg` field of `param` must be an initialized instance of `T`. > +unsafe extern "C" fn free<T>(arg: *mut core::ffi::c_void) > +where > + T: ModuleParam, > +{ > + // SAFETY: By function safety requirement, `arg` is an initialized > + // instance of `T`. By C API contract, `arg` will not be used after > + // this function returns. > + unsafe { core::ptr::drop_in_place(arg as *mut T) }; > +} > + > +macro_rules! impl_int_module_param { > + ($ty:ident) => { > + impl ModuleParam for $ty { > + type Value = $ty; > + > + fn try_from_param_arg(arg: &'static [u8]) -> Result<Self> { > + let bstr = BStr::from_bytes(arg); > + <$ty as crate::str::parse_int::ParseInt>::from_str(bstr) > + } > + } > + }; > +} > + > +impl_int_module_param!(i8); > +impl_int_module_param!(u8); > +impl_int_module_param!(i16); > +impl_int_module_param!(u16); > +impl_int_module_param!(i32); > +impl_int_module_param!(u32); > +impl_int_module_param!(i64); > +impl_int_module_param!(u64); > +impl_int_module_param!(isize); > +impl_int_module_param!(usize); > + > +/// A wrapper for kernel parameters. > +/// > +/// This type is instantiated by the [`module!`] macro when module parameters are > +/// defined. You should never need to instantiate this type directly. > +#[repr(transparent)] > +pub struct ModuleParamAccess<T> { > + data: core::cell::UnsafeCell<T>, > +} > + > +// SAFETY: We only create shared references to the contents of this container, > +// so if `T` is `Sync`, so is `ModuleParamAccess`. > +unsafe impl<T: Sync> Sync for ModuleParamAccess<T> {} > + > +impl<T> ModuleParamAccess<T> { > + #[doc(hidden)] > + pub const fn new(value: T) -> Self { > + Self { > + data: core::cell::UnsafeCell::new(value), > + } > + } > + > + /// Get a shared reference to the parameter value. > + // Note: When sysfs access to parameters are enabled, we have to pass in a > + // held lock guard here. > + pub fn get(&self) -> &T { > + // SAFETY: As we only support read only parameters with no sysfs > + // exposure, the kernel will not touch the parameter data after module > + // initialization. > + unsafe { &*self.data.get() } > + } > + > + /// Get a mutable pointer to the parameter value. > + pub const fn as_mut_ptr(&self) -> *mut T { > + self.data.get() > + } > +} > + > +#[doc(hidden)] > +#[macro_export] > +/// Generate a static [`kernel_param_ops`](srctree/include/linux/moduleparam.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) => { > + /// > + /// Static [`kernel_param_ops`](srctree/include/linux/moduleparam.h) > + /// struct generated by `make_param_ops` > + #[doc = concat!("for [`", stringify!($ty), "`].")] > + pub static $ops: $crate::bindings::kernel_param_ops = $crate::bindings::kernel_param_ops { > + flags: 0, > + set: Some(set_param::<$ty>), > + get: None, > + free: Some(free::<$ty>), > + }; > + }; > +} > + > +make_param_ops!(PARAM_OPS_I8, i8); > +make_param_ops!(PARAM_OPS_U8, u8); > +make_param_ops!(PARAM_OPS_I16, i16); > +make_param_ops!(PARAM_OPS_U16, u16); > +make_param_ops!(PARAM_OPS_I32, i32); > +make_param_ops!(PARAM_OPS_U32, u32); > +make_param_ops!(PARAM_OPS_I64, i64); > +make_param_ops!(PARAM_OPS_U64, u64); > +make_param_ops!(PARAM_OPS_ISIZE, isize); > +make_param_ops!(PARAM_OPS_USIZE, usize); > diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs > index 563dcd2b7ace5e8322d0fddb409571cca2dd31ea..5efed578e3d5d851b460f83b29a4a95e2f106e64 100644 > --- a/rust/macros/helpers.rs > +++ b/rust/macros/helpers.rs > @@ -107,6 +107,20 @@ pub(crate) struct Generics { > pub(crate) ty_generics: Vec<TokenTree>, > } > > +/// Parse a token stream of the form `expected_name: "value",` and return the > +/// string in the position of "value". > +/// > +/// # Panics > +/// > +/// - On parse error. > +pub(crate) fn expect_string_field(it: &mut token_stream::IntoIter, expected_name: &str) -> String { > + assert_eq!(expect_ident(it), expected_name); > + assert_eq!(expect_punct(it), ':'); > + let string = expect_string(it); > + assert_eq!(expect_punct(it), ','); > + string > +} > + > /// Parses the given `TokenStream` into `Generics` and the rest. > /// > /// The generics are not present in the rest, but a where clause might remain. > diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs > index 4ab94e44adfe3206faad159e81417ea41a35815b..0fe581fbc53266bf22fe07f5c35495affbe0d67d 100644 > --- a/rust/macros/lib.rs > +++ b/rust/macros/lib.rs > @@ -24,6 +24,30 @@ > /// The `type` argument should be a type which implements the [`Module`] > /// trait. Also accepts various forms of kernel metadata. > /// > +/// The `params` field describe module parameters. Each entry has the form > +/// > +/// ```ignore > +/// parameter_name: type { > +/// default: default_value, > +/// description: "Description", > +/// } > +/// ``` > +/// > +/// `type` may be one of > +/// > +/// - [`i8`] > +/// - [`u8`] > +/// - [`i8`] > +/// - [`u8`] > +/// - [`i16`] > +/// - [`u16`] > +/// - [`i32`] > +/// - [`u32`] > +/// - [`i64`] > +/// - [`u64`] > +/// - [`isize`] > +/// - [`usize`] > +/// > /// C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) > /// > /// [`Module`]: ../kernel/trait.Module.html > @@ -40,6 +64,12 @@ > /// description: "My very own kernel module!", > /// license: "GPL", > /// alias: ["alternate_module_name"], > +/// params: { > +/// my_parameter: i64 { > +/// default: 1, > +/// description: "This parameter has a default of 1", > +/// }, > +/// }, > /// } > /// > /// struct MyModule(i32); > @@ -48,6 +78,7 @@ > /// fn init(_module: &'static ThisModule) -> Result<Self> { > /// let foo: i32 = 42; > /// pr_info!("I contain: {}\n", foo); > +/// pr_info!("i32 param is: {}\n", module_parameters::my_parameter.read()); > /// Ok(Self(foo)) > /// } > /// } > diff --git a/rust/macros/module.rs b/rust/macros/module.rs > index 2587f41b0d3929af7ceac5f42b4711f70b4f8749..627279cf318b14b50128045bce980f9d60b9c6d0 100644 > --- a/rust/macros/module.rs > +++ b/rust/macros/module.rs > @@ -26,6 +26,7 @@ struct ModInfoBuilder<'a> { > module: &'a str, > counter: usize, > buffer: String, > + param_buffer: String, > } > > impl<'a> ModInfoBuilder<'a> { > @@ -34,10 +35,11 @@ fn new(module: &'a str) -> Self { > module, > counter: 0, > buffer: String::new(), > + param_buffer: String::new(), > } > } > > - fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { > + fn emit_base(&mut self, field: &str, content: &str, builtin: bool, param: bool) { > let string = if builtin { > // Built-in modules prefix their modinfo strings by `module.`. > format!( > @@ -51,8 +53,14 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { > format!("{field}={content}\0", field = field, content = content) > }; > > + let buffer = if param { > + &mut self.param_buffer > + } else { > + &mut self.buffer > + }; > + > write!( > - &mut self.buffer, > + buffer, > " > {cfg} > #[doc(hidden)] > @@ -75,20 +83,113 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { > self.counter += 1; > } > > - fn emit_only_builtin(&mut self, field: &str, content: &str) { > - self.emit_base(field, content, true) > + fn emit_only_builtin(&mut self, field: &str, content: &str, param: bool) { > + self.emit_base(field, content, true, param) > } > > - fn emit_only_loadable(&mut self, field: &str, content: &str) { > - self.emit_base(field, content, false) > + fn emit_only_loadable(&mut self, field: &str, content: &str, param: bool) { > + self.emit_base(field, content, false, param) > } > > fn emit(&mut self, field: &str, content: &str) { > - self.emit_only_builtin(field, content); > - self.emit_only_loadable(field, content); > + self.emit_internal(field, content, false); > + } > + > + fn emit_internal(&mut self, field: &str, content: &str, param: bool) { > + self.emit_only_builtin(field, content, param); > + self.emit_only_loadable(field, content, param); > + } > + > + fn emit_param(&mut self, field: &str, param: &str, content: &str) { > + let content = format!("{param}:{content}", param = param, content = content); > + self.emit_internal(field, &content, true); > + } > + > + fn emit_params(&mut self, info: &ModuleInfo) { > + let Some(params) = &info.params else { > + return; > + }; > + > + for param in params { > + let ops = param_ops_path(¶m.ptype); > + > + // Note: The spelling of these fields is dictated by the user space > + // tool `modinfo`. > + self.emit_param("parmtype", ¶m.name, ¶m.ptype); > + self.emit_param("parm", ¶m.name, ¶m.description); > + > + write!( > + self.param_buffer, > + " > + pub(crate) static {param_name}: > + ::kernel::module_param::ModuleParamAccess<{param_type}> = > + ::kernel::module_param::ModuleParamAccess::new({param_default}); > + > + #[link_section = \"__param\"] > + #[used] > + static __{module_name}_{param_name}_struct: > + ::kernel::module_param::RacyKernelParam = > + ::kernel::module_param::RacyKernelParam(::kernel::bindings::kernel_param {{ > + name: if cfg!(MODULE) {{ > + c\"{module_name}.{param_name}\" > + }} else {{ > + c\"{param_name}\" > + }}.as_ptr(), This should be the other way around? Running 'insmod rust_minimal.ko test_parameter=2' reports for me that the parameter is unknown while specifying 'rust_minimal.test_parameter=2' works. The prefix should be used only if the module is built-in so the parameter can be distinguished on the kernel command line. > + // SAFETY: `__this_module` is constructed by the kernel at load time > + // and will not be freed until the module is unloaded. > + #[cfg(MODULE)] > + mod_: unsafe {{ > + (&::kernel::bindings::__this_module > + as *const ::kernel::bindings::module) > + .cast_mut() > + }}, > + #[cfg(not(MODULE))] > + mod_: ::core::ptr::null_mut(), > + ops: &{ops} as *const ::kernel::bindings::kernel_param_ops, > + perm: 0, // Will not appear in sysfs > + level: -1, > + flags: 0, > + __bindgen_anon_1: > + ::kernel::bindings::kernel_param__bindgen_ty_1 {{ > + arg: {param_name}.as_mut_ptr().cast() > + }}, > + }}); > + ", > + module_name = info.name, > + param_type = param.ptype, > + param_default = param.default, > + param_name = param.name, > + ops = ops, > + ) > + .unwrap(); > + } > + } > +} > + > +fn param_ops_path(param_type: &str) -> &'static str { > + match param_type { > + "i8" => "::kernel::module_param::PARAM_OPS_I8", > + "u8" => "::kernel::module_param::PARAM_OPS_U8", > + "i16" => "::kernel::module_param::PARAM_OPS_I16", > + "u16" => "::kernel::module_param::PARAM_OPS_U16", > + "i32" => "::kernel::module_param::PARAM_OPS_I32", > + "u32" => "::kernel::module_param::PARAM_OPS_U32", > + "i64" => "::kernel::module_param::PARAM_OPS_I64", > + "u64" => "::kernel::module_param::PARAM_OPS_U64", > + "isize" => "::kernel::module_param::PARAM_OPS_ISIZE", > + "usize" => "::kernel::module_param::PARAM_OPS_USIZE", > + t => panic!("Unsupported parameter type {}", t), > } > } > > +fn expect_param_default(param_it: &mut token_stream::IntoIter) -> String { > + assert_eq!(expect_ident(param_it), "default"); > + assert_eq!(expect_punct(param_it), ':'); > + let default = try_literal(param_it).expect("Expected default param value"); > + assert_eq!(expect_punct(param_it), ','); > + default > +} > + > #[derive(Debug, Default)] > struct ModuleInfo { > type_: String, > @@ -98,6 +199,50 @@ struct ModuleInfo { > description: Option<String>, > alias: Option<Vec<String>>, > firmware: Option<Vec<String>>, > + params: Option<Vec<Parameter>>, > +} > + > +#[derive(Debug)] > +struct Parameter { > + name: String, > + ptype: String, > + default: String, > + description: String, > +} > + > +fn expect_params(it: &mut token_stream::IntoIter) -> Vec<Parameter> { > + let params = expect_group(it); > + assert_eq!(params.delimiter(), Delimiter::Brace); > + let mut it = params.stream().into_iter(); > + let mut parsed = Vec::new(); > + > + loop { > + let param_name = match it.next() { > + Some(TokenTree::Ident(ident)) => ident.to_string(), > + Some(_) => panic!("Expected Ident or end"), > + None => break, > + }; > + > + assert_eq!(expect_punct(&mut it), ':'); > + let param_type = expect_ident(&mut it); > + let group = expect_group(&mut it); > + assert_eq!(group.delimiter(), Delimiter::Brace); > + assert_eq!(expect_punct(&mut it), ','); > + > + let mut param_it = group.stream().into_iter(); > + let param_default = expect_param_default(&mut param_it); > + let param_description = expect_string_field(&mut param_it, "description"); > + expect_end(&mut param_it); > + > + parsed.push(Parameter { > + name: param_name, > + ptype: param_type, > + default: param_default, > + description: param_description, > + }) > + } > + > + parsed > } > > impl ModuleInfo { > @@ -112,6 +257,7 @@ fn parse(it: &mut token_stream::IntoIter) -> Self { > "license", > "alias", > "firmware", > + "params", > ]; > const REQUIRED_KEYS: &[&str] = &["type", "name", "license"]; > let mut seen_keys = Vec::new(); > @@ -140,6 +286,7 @@ fn parse(it: &mut token_stream::IntoIter) -> Self { > "license" => info.license = expect_string_ascii(it), > "alias" => info.alias = Some(expect_string_array(it)), > "firmware" => info.firmware = Some(expect_string_array(it)), > + "params" => info.params = Some(expect_params(it)), > _ => panic!( > "Unknown key \"{}\". Valid keys are: {:?}.", > key, EXPECTED_KEYS > @@ -183,28 +330,30 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { > let info = ModuleInfo::parse(&mut it); > > let mut modinfo = ModInfoBuilder::new(info.name.as_ref()); > - if let Some(author) = info.author { > - modinfo.emit("author", &author); > + if let Some(author) = &info.author { > + modinfo.emit("author", author); > } > - if let Some(description) = info.description { > - modinfo.emit("description", &description); > + if let Some(description) = &info.description { > + modinfo.emit("description", description); > } > modinfo.emit("license", &info.license); > - if let Some(aliases) = info.alias { > + if let Some(aliases) = &info.alias { > for alias in aliases { > - modinfo.emit("alias", &alias); > + modinfo.emit("alias", alias); > } > } > - if let Some(firmware) = info.firmware { > + if let Some(firmware) = &info.firmware { > for fw in firmware { > - modinfo.emit("firmware", &fw); > + modinfo.emit("firmware", fw); > } > } > > // Built-in modules also export the `file` modinfo string. > let file = > std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable"); > - modinfo.emit_only_builtin("file", &file); > + modinfo.emit_only_builtin("file", &file, false); > + > + modinfo.emit_params(&info); > > format!( > " > @@ -358,14 +507,17 @@ unsafe fn __exit() {{ > __MOD.assume_init_drop(); > }} > }} > - > {modinfo} > }} > }} > + mod module_parameters {{ > + {params} > + }} > ", > type_ = info.type_, > name = info.name, > modinfo = modinfo.buffer, > + params = modinfo.param_buffer, > initcall_section = ".initcall6.init" > ) > .parse() > diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs > index 4aaf117bf8e3c0cc77e188b1ad0708e3650a6654..d999a77c6eb9a0db9acf5fa03d4feca5e6525fc8 100644 > --- a/samples/rust/rust_minimal.rs > +++ b/samples/rust/rust_minimal.rs > @@ -10,6 +10,12 @@ > author: "Rust for Linux Contributors", > description: "Rust minimal sample", > license: "GPL", > + params: { > + test_parameter: i64 { > + default: 1, > + description: "This parameter has a default of 1", > + }, > + }, > } > > struct RustMinimal { > @@ -20,6 +26,10 @@ impl kernel::Module for RustMinimal { > fn init(_module: &'static ThisModule) -> Result<Self> { > pr_info!("Rust minimal sample (init)\n"); > pr_info!("Am I built-in? {}\n", !cfg!(MODULE)); > + pr_info!( > + "My parameter: {}\n", > + *module_parameters::test_parameter.get() > + ); > > let mut numbers = KVec::new(); > numbers.push(72, GFP_KERNEL)?; >
"Petr Pavlu" <petr.pavlu@suse.com> writes: > On 1/9/25 11:54, Andreas Hindborg wrote: >> This patch includes changes required for Rust kernel modules to utilize >> module parameters. This code implements read only support for integer >> types without `sysfs` support. >> [cut] >> + >> + // SAFETY: `old_value` is valid for writes, as we have exclusive >> + // access. `old_value` is pointing to an initialized static, an > > Typo: an -> and Thanks. [cut] >> + >> + fn emit_params(&mut self, info: &ModuleInfo) { >> + let Some(params) = &info.params else { >> + return; >> + }; >> + >> + for param in params { >> + let ops = param_ops_path(¶m.ptype); >> + >> + // Note: The spelling of these fields is dictated by the user space >> + // tool `modinfo`. >> + self.emit_param("parmtype", ¶m.name, ¶m.ptype); >> + self.emit_param("parm", ¶m.name, ¶m.description); >> + >> + write!( >> + self.param_buffer, >> + " >> + pub(crate) static {param_name}: >> + ::kernel::module_param::ModuleParamAccess<{param_type}> = >> + ::kernel::module_param::ModuleParamAccess::new({param_default}); >> + >> + #[link_section = \"__param\"] >> + #[used] >> + static __{module_name}_{param_name}_struct: >> + ::kernel::module_param::RacyKernelParam = >> + ::kernel::module_param::RacyKernelParam(::kernel::bindings::kernel_param {{ >> + name: if cfg!(MODULE) {{ >> + c\"{module_name}.{param_name}\" >> + }} else {{ >> + c\"{param_name}\" >> + }}.as_ptr(), > > This should be the other way around? Running > 'insmod rust_minimal.ko test_parameter=2' reports for me that the > parameter is unknown while specifying 'rust_minimal.test_parameter=2' > works. The prefix should be used only if the module is built-in so the > parameter can be distinguished on the kernel command line. Thanks for testing! This actually gave me quite a headache when I was using the series elsewhere. I forgot to resend the series with the fix, sorry. Best regards, Andreas Hindborg
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 4253c9a7fe7df935dc714bc3036d299bd80054a0..707ec81411c7aecccdd14e02cbf011642bc02e07 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -46,6 +46,7 @@ pub mod kunit; pub mod list; pub mod miscdevice; +pub mod module_param; #[cfg(CONFIG_NET)] pub mod net; pub mod once_lite; diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs new file mode 100644 index 0000000000000000000000000000000000000000..6cb64096090a37c4f9c4ce73e23b7ac049e32d20 --- /dev/null +++ b/rust/kernel/module_param.rs @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Types for module parameters. +//! +//! C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) + +use crate::prelude::*; +use crate::str::BStr; + +/// Newtype to make `bindings::kernel_param` [`Sync`]. +#[repr(transparent)] +#[doc(hidden)] +pub struct RacyKernelParam(pub ::kernel::bindings::kernel_param); + +// SAFETY: C kernel handles serializing access to this type. We never access +// from Rust module. +unsafe impl Sync for RacyKernelParam {} + +/// Types that can be used for module parameters. +/// +/// Note that displaying the type in `sysfs` will fail if +/// [`Display`](core::fmt::Display) implementation would write more than +/// [`PAGE_SIZE`] - 1 bytes. +/// +/// [`PAGE_SIZE`]: `bindings::PAGE_SIZE` +pub trait ModuleParam: Sized { + /// The [`ModuleParam`] will be used by the kernel module through this type. + /// + /// This may differ from `Self` if, for example, `Self` needs to track + /// ownership without exposing it or allocate extra space for other possible + /// parameter values. + // This is required to support string parameters in the future. + type Value: ?Sized; + + /// Parse a parameter argument into the parameter value. + /// + /// `Err(_)` should be returned when parsing of the argument fails. + /// + /// 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 lifetime of + /// the kernel. So implementations of this method which need to allocate + /// should first check that the allocator is available (with + /// [`crate::bindings::slab_is_available`]) and when it is not available + /// provide an alternative implementation which doesn't allocate. In cases + /// 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`]: srctree/include/linux/slab.h + fn try_from_param_arg(arg: &'static [u8]) -> Result<Self>; +} + +/// Set the module parameter from a string. +/// +/// Used to set the parameter value at kernel initialization, when loading +/// the module or when set through `sysfs`. +/// +/// `param.arg` is a pointer to `*mut T` as set up by the [`module!`] +/// macro. +/// +/// See `struct kernel_param_ops.set`. +/// +/// # 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 `T`. +/// +/// # Invariants +/// +/// Currently, we only support read-only parameters that are not readable +/// from `sysfs`. Thus, this function is only called at kernel +/// initialization time, or at module load time, and we have exclusive +/// access to the parameter for the duration of the function. +/// +/// [`module!`]: macros::module +unsafe extern "C" fn set_param<T>( + val: *const core::ffi::c_char, + param: *const crate::bindings::kernel_param, +) -> core::ffi::c_int +where + T: ModuleParam, +{ + // NOTE: If we start supporting arguments without values, val _is_ allowed + // to be null here. + if val.is_null() { + crate::pr_warn_once!("Null pointer passed to `module_param::set_param`"); + return crate::error::code::EINVAL.to_errno(); + } + + // SAFETY: By function safety requirement, val is non-null and + // null-terminated. By C API contract, `val` is live and valid for reads + // for the duration of this function. + let arg = unsafe { CStr::from_char_ptr(val).as_bytes() }; + + crate::error::from_result(|| { + let new_value = T::try_from_param_arg(arg)?; + + // SAFETY: `param` is guaranteed to be valid by C API contract + // and `arg` is guaranteed to point to an instance of `T`. + let old_value = unsafe { (*param).__bindgen_anon_1.arg as *mut T }; + + // SAFETY: `old_value` is valid for writes, as we have exclusive + // access. `old_value` is pointing to an initialized static, an + // so it is properly initialized. + unsafe { core::ptr::replace(old_value, new_value) }; + Ok(0) + }) +} + +/// Drop the parameter. +/// +/// Called when unloading a module. +/// +/// # Safety +/// +/// The `arg` field of `param` must be an initialized instance of `T`. +unsafe extern "C" fn free<T>(arg: *mut core::ffi::c_void) +where + T: ModuleParam, +{ + // SAFETY: By function safety requirement, `arg` is an initialized + // instance of `T`. By C API contract, `arg` will not be used after + // this function returns. + unsafe { core::ptr::drop_in_place(arg as *mut T) }; +} + +macro_rules! impl_int_module_param { + ($ty:ident) => { + impl ModuleParam for $ty { + type Value = $ty; + + fn try_from_param_arg(arg: &'static [u8]) -> Result<Self> { + let bstr = BStr::from_bytes(arg); + <$ty as crate::str::parse_int::ParseInt>::from_str(bstr) + } + } + }; +} + +impl_int_module_param!(i8); +impl_int_module_param!(u8); +impl_int_module_param!(i16); +impl_int_module_param!(u16); +impl_int_module_param!(i32); +impl_int_module_param!(u32); +impl_int_module_param!(i64); +impl_int_module_param!(u64); +impl_int_module_param!(isize); +impl_int_module_param!(usize); + +/// A wrapper for kernel parameters. +/// +/// This type is instantiated by the [`module!`] macro when module parameters are +/// defined. You should never need to instantiate this type directly. +#[repr(transparent)] +pub struct ModuleParamAccess<T> { + data: core::cell::UnsafeCell<T>, +} + +// SAFETY: We only create shared references to the contents of this container, +// so if `T` is `Sync`, so is `ModuleParamAccess`. +unsafe impl<T: Sync> Sync for ModuleParamAccess<T> {} + +impl<T> ModuleParamAccess<T> { + #[doc(hidden)] + pub const fn new(value: T) -> Self { + Self { + data: core::cell::UnsafeCell::new(value), + } + } + + /// Get a shared reference to the parameter value. + // Note: When sysfs access to parameters are enabled, we have to pass in a + // held lock guard here. + pub fn get(&self) -> &T { + // SAFETY: As we only support read only parameters with no sysfs + // exposure, the kernel will not touch the parameter data after module + // initialization. + unsafe { &*self.data.get() } + } + + /// Get a mutable pointer to the parameter value. + pub const fn as_mut_ptr(&self) -> *mut T { + self.data.get() + } +} + +#[doc(hidden)] +#[macro_export] +/// Generate a static [`kernel_param_ops`](srctree/include/linux/moduleparam.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) => { + /// + /// Static [`kernel_param_ops`](srctree/include/linux/moduleparam.h) + /// struct generated by `make_param_ops` + #[doc = concat!("for [`", stringify!($ty), "`].")] + pub static $ops: $crate::bindings::kernel_param_ops = $crate::bindings::kernel_param_ops { + flags: 0, + set: Some(set_param::<$ty>), + get: None, + free: Some(free::<$ty>), + }; + }; +} + +make_param_ops!(PARAM_OPS_I8, i8); +make_param_ops!(PARAM_OPS_U8, u8); +make_param_ops!(PARAM_OPS_I16, i16); +make_param_ops!(PARAM_OPS_U16, u16); +make_param_ops!(PARAM_OPS_I32, i32); +make_param_ops!(PARAM_OPS_U32, u32); +make_param_ops!(PARAM_OPS_I64, i64); +make_param_ops!(PARAM_OPS_U64, u64); +make_param_ops!(PARAM_OPS_ISIZE, isize); +make_param_ops!(PARAM_OPS_USIZE, usize); diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs index 563dcd2b7ace5e8322d0fddb409571cca2dd31ea..5efed578e3d5d851b460f83b29a4a95e2f106e64 100644 --- a/rust/macros/helpers.rs +++ b/rust/macros/helpers.rs @@ -107,6 +107,20 @@ pub(crate) struct Generics { pub(crate) ty_generics: Vec<TokenTree>, } +/// Parse a token stream of the form `expected_name: "value",` and return the +/// string in the position of "value". +/// +/// # Panics +/// +/// - On parse error. +pub(crate) fn expect_string_field(it: &mut token_stream::IntoIter, expected_name: &str) -> String { + assert_eq!(expect_ident(it), expected_name); + assert_eq!(expect_punct(it), ':'); + let string = expect_string(it); + assert_eq!(expect_punct(it), ','); + string +} + /// Parses the given `TokenStream` into `Generics` and the rest. /// /// The generics are not present in the rest, but a where clause might remain. diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 4ab94e44adfe3206faad159e81417ea41a35815b..0fe581fbc53266bf22fe07f5c35495affbe0d67d 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -24,6 +24,30 @@ /// The `type` argument should be a type which implements the [`Module`] /// trait. Also accepts various forms of kernel metadata. /// +/// The `params` field describe module parameters. Each entry has the form +/// +/// ```ignore +/// parameter_name: type { +/// default: default_value, +/// description: "Description", +/// } +/// ``` +/// +/// `type` may be one of +/// +/// - [`i8`] +/// - [`u8`] +/// - [`i8`] +/// - [`u8`] +/// - [`i16`] +/// - [`u16`] +/// - [`i32`] +/// - [`u32`] +/// - [`i64`] +/// - [`u64`] +/// - [`isize`] +/// - [`usize`] +/// /// C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) /// /// [`Module`]: ../kernel/trait.Module.html @@ -40,6 +64,12 @@ /// description: "My very own kernel module!", /// license: "GPL", /// alias: ["alternate_module_name"], +/// params: { +/// my_parameter: i64 { +/// default: 1, +/// description: "This parameter has a default of 1", +/// }, +/// }, /// } /// /// struct MyModule(i32); @@ -48,6 +78,7 @@ /// fn init(_module: &'static ThisModule) -> Result<Self> { /// let foo: i32 = 42; /// pr_info!("I contain: {}\n", foo); +/// pr_info!("i32 param is: {}\n", module_parameters::my_parameter.read()); /// Ok(Self(foo)) /// } /// } diff --git a/rust/macros/module.rs b/rust/macros/module.rs index 2587f41b0d3929af7ceac5f42b4711f70b4f8749..627279cf318b14b50128045bce980f9d60b9c6d0 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -26,6 +26,7 @@ struct ModInfoBuilder<'a> { module: &'a str, counter: usize, buffer: String, + param_buffer: String, } impl<'a> ModInfoBuilder<'a> { @@ -34,10 +35,11 @@ fn new(module: &'a str) -> Self { module, counter: 0, buffer: String::new(), + param_buffer: String::new(), } } - fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { + fn emit_base(&mut self, field: &str, content: &str, builtin: bool, param: bool) { let string = if builtin { // Built-in modules prefix their modinfo strings by `module.`. format!( @@ -51,8 +53,14 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { format!("{field}={content}\0", field = field, content = content) }; + let buffer = if param { + &mut self.param_buffer + } else { + &mut self.buffer + }; + write!( - &mut self.buffer, + buffer, " {cfg} #[doc(hidden)] @@ -75,20 +83,113 @@ fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { self.counter += 1; } - fn emit_only_builtin(&mut self, field: &str, content: &str) { - self.emit_base(field, content, true) + fn emit_only_builtin(&mut self, field: &str, content: &str, param: bool) { + self.emit_base(field, content, true, param) } - fn emit_only_loadable(&mut self, field: &str, content: &str) { - self.emit_base(field, content, false) + fn emit_only_loadable(&mut self, field: &str, content: &str, param: bool) { + self.emit_base(field, content, false, param) } fn emit(&mut self, field: &str, content: &str) { - self.emit_only_builtin(field, content); - self.emit_only_loadable(field, content); + self.emit_internal(field, content, false); + } + + fn emit_internal(&mut self, field: &str, content: &str, param: bool) { + self.emit_only_builtin(field, content, param); + self.emit_only_loadable(field, content, param); + } + + fn emit_param(&mut self, field: &str, param: &str, content: &str) { + let content = format!("{param}:{content}", param = param, content = content); + self.emit_internal(field, &content, true); + } + + fn emit_params(&mut self, info: &ModuleInfo) { + let Some(params) = &info.params else { + return; + }; + + for param in params { + let ops = param_ops_path(¶m.ptype); + + // Note: The spelling of these fields is dictated by the user space + // tool `modinfo`. + self.emit_param("parmtype", ¶m.name, ¶m.ptype); + self.emit_param("parm", ¶m.name, ¶m.description); + + write!( + self.param_buffer, + " + pub(crate) static {param_name}: + ::kernel::module_param::ModuleParamAccess<{param_type}> = + ::kernel::module_param::ModuleParamAccess::new({param_default}); + + #[link_section = \"__param\"] + #[used] + static __{module_name}_{param_name}_struct: + ::kernel::module_param::RacyKernelParam = + ::kernel::module_param::RacyKernelParam(::kernel::bindings::kernel_param {{ + name: if cfg!(MODULE) {{ + c\"{module_name}.{param_name}\" + }} else {{ + c\"{param_name}\" + }}.as_ptr(), + // SAFETY: `__this_module` is constructed by the kernel at load time + // and will not be freed until the module is unloaded. + #[cfg(MODULE)] + mod_: unsafe {{ + (&::kernel::bindings::__this_module + as *const ::kernel::bindings::module) + .cast_mut() + }}, + #[cfg(not(MODULE))] + mod_: ::core::ptr::null_mut(), + ops: &{ops} as *const ::kernel::bindings::kernel_param_ops, + perm: 0, // Will not appear in sysfs + level: -1, + flags: 0, + __bindgen_anon_1: + ::kernel::bindings::kernel_param__bindgen_ty_1 {{ + arg: {param_name}.as_mut_ptr().cast() + }}, + }}); + ", + module_name = info.name, + param_type = param.ptype, + param_default = param.default, + param_name = param.name, + ops = ops, + ) + .unwrap(); + } + } +} + +fn param_ops_path(param_type: &str) -> &'static str { + match param_type { + "i8" => "::kernel::module_param::PARAM_OPS_I8", + "u8" => "::kernel::module_param::PARAM_OPS_U8", + "i16" => "::kernel::module_param::PARAM_OPS_I16", + "u16" => "::kernel::module_param::PARAM_OPS_U16", + "i32" => "::kernel::module_param::PARAM_OPS_I32", + "u32" => "::kernel::module_param::PARAM_OPS_U32", + "i64" => "::kernel::module_param::PARAM_OPS_I64", + "u64" => "::kernel::module_param::PARAM_OPS_U64", + "isize" => "::kernel::module_param::PARAM_OPS_ISIZE", + "usize" => "::kernel::module_param::PARAM_OPS_USIZE", + t => panic!("Unsupported parameter type {}", t), } } +fn expect_param_default(param_it: &mut token_stream::IntoIter) -> String { + assert_eq!(expect_ident(param_it), "default"); + assert_eq!(expect_punct(param_it), ':'); + let default = try_literal(param_it).expect("Expected default param value"); + assert_eq!(expect_punct(param_it), ','); + default +} + #[derive(Debug, Default)] struct ModuleInfo { type_: String, @@ -98,6 +199,50 @@ struct ModuleInfo { description: Option<String>, alias: Option<Vec<String>>, firmware: Option<Vec<String>>, + params: Option<Vec<Parameter>>, +} + +#[derive(Debug)] +struct Parameter { + name: String, + ptype: String, + default: String, + description: String, +} + +fn expect_params(it: &mut token_stream::IntoIter) -> Vec<Parameter> { + let params = expect_group(it); + assert_eq!(params.delimiter(), Delimiter::Brace); + let mut it = params.stream().into_iter(); + let mut parsed = Vec::new(); + + loop { + let param_name = match it.next() { + Some(TokenTree::Ident(ident)) => ident.to_string(), + Some(_) => panic!("Expected Ident or end"), + None => break, + }; + + assert_eq!(expect_punct(&mut it), ':'); + let param_type = expect_ident(&mut it); + let group = expect_group(&mut it); + assert_eq!(group.delimiter(), Delimiter::Brace); + assert_eq!(expect_punct(&mut it), ','); + + let mut param_it = group.stream().into_iter(); + let param_default = expect_param_default(&mut param_it); + let param_description = expect_string_field(&mut param_it, "description"); + expect_end(&mut param_it); + + parsed.push(Parameter { + name: param_name, + ptype: param_type, + default: param_default, + description: param_description, + }) + } + + parsed } impl ModuleInfo { @@ -112,6 +257,7 @@ fn parse(it: &mut token_stream::IntoIter) -> Self { "license", "alias", "firmware", + "params", ]; const REQUIRED_KEYS: &[&str] = &["type", "name", "license"]; let mut seen_keys = Vec::new(); @@ -140,6 +286,7 @@ fn parse(it: &mut token_stream::IntoIter) -> Self { "license" => info.license = expect_string_ascii(it), "alias" => info.alias = Some(expect_string_array(it)), "firmware" => info.firmware = Some(expect_string_array(it)), + "params" => info.params = Some(expect_params(it)), _ => panic!( "Unknown key \"{}\". Valid keys are: {:?}.", key, EXPECTED_KEYS @@ -183,28 +330,30 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { let info = ModuleInfo::parse(&mut it); let mut modinfo = ModInfoBuilder::new(info.name.as_ref()); - if let Some(author) = info.author { - modinfo.emit("author", &author); + if let Some(author) = &info.author { + modinfo.emit("author", author); } - if let Some(description) = info.description { - modinfo.emit("description", &description); + if let Some(description) = &info.description { + modinfo.emit("description", description); } modinfo.emit("license", &info.license); - if let Some(aliases) = info.alias { + if let Some(aliases) = &info.alias { for alias in aliases { - modinfo.emit("alias", &alias); + modinfo.emit("alias", alias); } } - if let Some(firmware) = info.firmware { + if let Some(firmware) = &info.firmware { for fw in firmware { - modinfo.emit("firmware", &fw); + modinfo.emit("firmware", fw); } } // Built-in modules also export the `file` modinfo string. let file = std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable"); - modinfo.emit_only_builtin("file", &file); + modinfo.emit_only_builtin("file", &file, false); + + modinfo.emit_params(&info); format!( " @@ -358,14 +507,17 @@ unsafe fn __exit() {{ __MOD.assume_init_drop(); }} }} - {modinfo} }} }} + mod module_parameters {{ + {params} + }} ", type_ = info.type_, name = info.name, modinfo = modinfo.buffer, + params = modinfo.param_buffer, initcall_section = ".initcall6.init" ) .parse() diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs index 4aaf117bf8e3c0cc77e188b1ad0708e3650a6654..d999a77c6eb9a0db9acf5fa03d4feca5e6525fc8 100644 --- a/samples/rust/rust_minimal.rs +++ b/samples/rust/rust_minimal.rs @@ -10,6 +10,12 @@ author: "Rust for Linux Contributors", description: "Rust minimal sample", license: "GPL", + params: { + test_parameter: i64 { + default: 1, + description: "This parameter has a default of 1", + }, + }, } struct RustMinimal { @@ -20,6 +26,10 @@ impl kernel::Module for RustMinimal { fn init(_module: &'static ThisModule) -> Result<Self> { pr_info!("Rust minimal sample (init)\n"); pr_info!("Am I built-in? {}\n", !cfg!(MODULE)); + pr_info!( + "My parameter: {}\n", + *module_parameters::test_parameter.get() + ); let mut numbers = KVec::new(); numbers.push(72, GFP_KERNEL)?;
This patch includes changes required for Rust kernel modules to utilize module parameters. This code implements read only support for integer types without `sysfs` support. Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org> --- rust/kernel/lib.rs | 1 + rust/kernel/module_param.rs | 225 +++++++++++++++++++++++++++++++++++++++++++ rust/macros/helpers.rs | 14 +++ rust/macros/lib.rs | 31 ++++++ rust/macros/module.rs | 188 ++++++++++++++++++++++++++++++++---- samples/rust/rust_minimal.rs | 10 ++ 6 files changed, 451 insertions(+), 18 deletions(-)