diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 44491463935a4c..2969b9ca7bdf98 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -165,7 +165,11 @@ jobs: -smp 2 \ -nographic \ -no-reboot \ - -append '${{ env.QEMU_APPEND }} rust_example.my_i32=123321 rust_example.my_str=🦀mod rust_example_2.my_i32=234432' \ + -append '${{ env.QEMU_APPEND }} \ + rust_example.my_i32=123321 \ + rust_example.my_str=🦀mod \ + rust_example_2.my_i32=234432 \ + rust_example_2.my_array=1,2,3' \ | sed s:$'\r'$:: \ | tee qemu-stdout.log @@ -191,6 +195,11 @@ jobs: grep '] \[3] my_str: 🦀mod$' qemu-stdout.log grep '] \[4] my_str: default str val$' qemu-stdout.log + grep '] my_array: \[0, 1]$' qemu-stdout.log + grep '] \[2] my_array: \[1, 2, 3]$' qemu-stdout.log + grep '] \[3] my_array: \[0, 1]$' qemu-stdout.log + grep '] \[4] my_array: \[1, 2, 3]$' qemu-stdout.log + grep '] \[3] Rust Example (exit)$' qemu-stdout.log grep '] \[4] Rust Example (exit)$' qemu-stdout.log diff --git a/.github/workflows/qemu-init.sh b/.github/workflows/qemu-init.sh index 666630c1798f26..7cbb1c4441cd30 100755 --- a/.github/workflows/qemu-init.sh +++ b/.github/workflows/qemu-init.sh @@ -1,7 +1,7 @@ #!/bin/sh busybox insmod rust_example_3.ko my_i32=345543 my_str=🦀mod -busybox insmod rust_example_4.ko my_i32=456654 my_usize=84 +busybox insmod rust_example_4.ko my_i32=456654 my_usize=84 my_array=1,2,3 busybox rmmod rust_example_3.ko busybox rmmod rust_example_4.ko diff --git a/drivers/char/rust_example.rs b/drivers/char/rust_example.rs index 1761f734d23371..9524d3c9170e13 100644 --- a/drivers/char/rust_example.rs +++ b/drivers/char/rust_example.rs @@ -43,6 +43,11 @@ module! { permissions: 0o644, description: b"Example of usize", }, + my_array: ArrayParam { + default: [0, 1], + permissions: 0, + description: b"Example of array", + }, }, } @@ -79,6 +84,7 @@ impl KernelModule for RustExample { core::str::from_utf8(my_str.read(&lock))? ); println!(" my_usize: {}", my_usize.read(&lock)); + println!(" my_array: {:?}", my_array.read()); } // Test mutexes. diff --git a/drivers/char/rust_example_2.rs b/drivers/char/rust_example_2.rs index 3f0630b286476a..9be3618a41c662 100644 --- a/drivers/char/rust_example_2.rs +++ b/drivers/char/rust_example_2.rs @@ -33,6 +33,11 @@ module! { permissions: 0o644, description: b"Example of usize", }, + my_array: ArrayParam { + default: [0, 1], + permissions: 0, + description: b"Example of array", + }, }, } @@ -54,6 +59,7 @@ impl KernelModule for RustExample2 { core::str::from_utf8(my_str.read(&lock))? ); println!("[2] my_usize: {}", my_usize.read(&lock)); + println!("[2] my_array: {:?}", my_array.read()); } // Including this large variable on the stack will trigger diff --git a/drivers/char/rust_example_3.rs b/drivers/char/rust_example_3.rs index 5fa88b3c969382..a5d03b4f8a24cf 100644 --- a/drivers/char/rust_example_3.rs +++ b/drivers/char/rust_example_3.rs @@ -33,6 +33,11 @@ module! { permissions: 0o644, description: b"Example of usize", }, + my_array: ArrayParam { + default: [0, 1], + permissions: 0, + description: b"Example of array", + }, }, } @@ -54,6 +59,7 @@ impl KernelModule for RustExample3 { core::str::from_utf8(my_str.read(&lock))? ); println!("[3] my_usize: {}", my_usize.read(&lock)); + println!("[3] my_array: {:?}", my_array.read()); } // Including this large variable on the stack will trigger diff --git a/drivers/char/rust_example_4.rs b/drivers/char/rust_example_4.rs index 9b7230d01d1905..6090dfc8600f0a 100644 --- a/drivers/char/rust_example_4.rs +++ b/drivers/char/rust_example_4.rs @@ -33,6 +33,11 @@ module! { permissions: 0o644, description: b"Example of usize", }, + my_array: ArrayParam { + default: [0, 1], + permissions: 0, + description: b"Example of array", + }, }, } @@ -54,6 +59,7 @@ impl KernelModule for RustExample4 { core::str::from_utf8(my_str.read(&lock))? ); println!("[4] my_usize: {}", my_usize.read(&lock)); + println!("[4] my_array: {:?}", my_array.read()); } // Including this large variable on the stack will trigger diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 95da8c33d2caf7..49cd6bbe20dae8 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -12,7 +12,7 @@ //! do so first instead of bypassing this crate. #![no_std] -#![feature(allocator_api, alloc_error_handler)] +#![feature(allocator_api, alloc_error_handler, const_fn, const_mut_refs)] #![deny(clippy::complexity)] #![deny(clippy::correctness)] #![deny(clippy::perf)] @@ -36,7 +36,10 @@ pub mod chrdev; mod error; pub mod file_operations; pub mod miscdev; + +#[doc(hidden)] pub mod module_param; + pub mod prelude; pub mod printk; pub mod random; diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs index 776458773c0821..328dc0bbf26959 100644 --- a/rust/kernel/module_param.rs +++ b/rust/kernel/module_param.rs @@ -15,6 +15,13 @@ use core::fmt::Write; /// /// [`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 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. See [`StringParam`] or [`ArrayParam`] for examples. + 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 @@ -27,7 +34,13 @@ pub trait ModuleParam: core::fmt::Display + core::marker::Sized { /// `arg == None` indicates that the parameter was passed without an /// argument. If `NOARG_ALLOWED` is set to `false` then `arg` is guaranteed /// to always be `Some(_)`. - fn try_from_param_arg(arg: Option<&[u8]>) -> Option; + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option; + + /// Get the current value of the parameter for use in the kernel module. + /// + /// This function should not be used directly. Instead use the wrapper + /// `read` which will be generated by [`module::module`]. + fn value(&self) -> &Self::Value; /// Set the module parameter from a string. /// @@ -161,17 +174,23 @@ impl_parse_int!(usize); macro_rules! impl_module_param { ($ty:ident) => { impl ModuleParam for $ty { + type Value = $ty; const NOARG_ALLOWED: bool = false; - fn try_from_param_arg(arg: Option<&[u8]>) -> Option { + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option { let bytes = arg?; let utf8 = core::str::from_utf8(bytes).ok()?; <$ty as crate::module_param::ParseInt>::from_str(utf8) } + + fn value(&self) -> &Self::Value { + self + } } }; } +#[doc(hidden)] #[macro_export] /// Generate a static [`kernel_param_ops`](../../../include/linux/moduleparam.h) struct. /// @@ -184,27 +203,27 @@ macro_rules! impl_module_param { /// ); /// ``` macro_rules! make_param_ops { - ($ops:ident, $ty:ident) => { - make_param_ops!( + ($ops:ident, $ty:ty) => { + $crate::make_param_ops!( #[doc=""] $ops, $ty ); }; - ($(#[$meta:meta])* $ops:ident, $ty:ident) => { + ($(#[$meta:meta])* $ops:ident, $ty:ty) => { $(#[$meta])* /// /// Static [`kernel_param_ops`](../../../include/linux/moduleparam.h) /// struct generated by [`make_param_ops`]. - pub static $ops: crate::bindings::kernel_param_ops = crate::bindings::kernel_param_ops { - flags: if <$ty as crate::module_param::ModuleParam>::NOARG_ALLOWED { - crate::bindings::KERNEL_PARAM_OPS_FL_NOARG + pub static $ops: $crate::bindings::kernel_param_ops = $crate::bindings::kernel_param_ops { + flags: if <$ty as $crate::module_param::ModuleParam>::NOARG_ALLOWED { + $crate::bindings::KERNEL_PARAM_OPS_FL_NOARG } else { 0 }, - set: Some(<$ty as crate::module_param::ModuleParam>::set_param), - get: Some(<$ty as crate::module_param::ModuleParam>::get_param), - free: Some(<$ty as crate::module_param::ModuleParam>::free), + set: Some(<$ty as $crate::module_param::ModuleParam>::set_param), + get: Some(<$ty as $crate::module_param::ModuleParam>::get_param), + free: Some(<$ty as $crate::module_param::ModuleParam>::free), }; }; } @@ -282,9 +301,10 @@ make_param_ops!( ); impl ModuleParam for bool { + type Value = bool; const NOARG_ALLOWED: bool = true; - fn try_from_param_arg(arg: Option<&[u8]>) -> Option { + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option { match arg { None => Some(true), Some(b"y") | Some(b"Y") | Some(b"1") | Some(b"true") => Some(true), @@ -292,6 +312,10 @@ impl ModuleParam for bool { _ => None, } } + + fn value(&self) -> &Self::Value { + self + } } make_param_ops!( @@ -300,3 +324,156 @@ make_param_ops!( 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 cast + // the first `self.used` elements to `T`. + unsafe { + &*(&self.values[0..self.used] as *const [core::mem::MaybeUninit] 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] = core::mem::MaybeUninit::new(val); + self.used += 1; + } + } + + /// Create an instance of `ArrayParam` initialized with `vals`. + /// + /// This function is only meant to be used in the [`module::module`] macro. + pub const fn create(vals: &[T]) -> Self { + let mut result = ArrayParam::new(); + let mut i = 0; + while i < vals.len() { + result.push(vals[i]); + i += 1; + } + result + } +} + +impl core::fmt::Display for ArrayParam { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for val in self.values() { + write!(f, "{},", val)?; + } + Ok(()) + } +} + +impl ModuleParam + for ArrayParam +{ + type Value = [T]; + const NOARG_ALLOWED: bool = false; + + fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option { + arg.and_then(|args| { + let mut result = Self::new(); + for arg in args.split(|b| *b == 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 [`module::module`] macro, not handled directly. Instead use 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 borrowed + /// 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 module is + /// unloaded. + Owned(alloc::vec::Vec), +} + +impl StringParam { + fn bytes(&self) -> &[u8] { + match self { + StringParam::Ref(bytes) => *bytes, + StringParam::Owned(vec) => &vec[..], + } + } +} + +impl core::fmt::Display for StringParam { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let bytes = self.bytes(); + match core::str::from_utf8(bytes) { + Ok(utf8) => write!(f, "{}", utf8), + Err(_) => write!(f, "{:?}", bytes), + } + } +} + +impl ModuleParam for StringParam { + type Value = [u8]; + const NOARG_ALLOWED: bool = 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 = unsafe { crate::bindings::slab_is_available() }; + arg.map(|arg| { + if slab_available { + let mut vec = alloc::vec::Vec::new(); + vec.extend_from_slice(arg); + StringParam::Owned(vec) + } else { + 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/module.rs b/rust/module.rs index a7925f3ac1961d..7ff3c3880f23aa 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -11,14 +11,36 @@ use proc_macro::{token_stream, Delimiter, Group, TokenStream, TokenTree}; -fn expect_ident(it: &mut token_stream::IntoIter) -> String { - if let TokenTree::Ident(ident) = it.next().unwrap() { - ident.to_string() +fn try_ident(it: &mut token_stream::IntoIter) -> Option { + if let Some(TokenTree::Ident(ident)) = it.next() { + Some(ident.to_string()) + } else { + None + } +} + +fn try_literal(it: &mut token_stream::IntoIter) -> Option { + if let Some(TokenTree::Literal(literal)) = it.next() { + Some(literal.to_string()) } else { - panic!("Expected Ident"); + None } } +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 + } + }) +} + +fn expect_ident(it: &mut token_stream::IntoIter) -> String { + try_ident(it).expect("Expected Ident") +} + fn expect_punct(it: &mut token_stream::IntoIter) -> char { if let TokenTree::Punct(punct) = it.next().unwrap() { punct.as_char() @@ -28,11 +50,7 @@ fn expect_punct(it: &mut token_stream::IntoIter) -> char { } fn expect_literal(it: &mut token_stream::IntoIter) -> String { - if let TokenTree::Literal(literal) = it.next().unwrap() { - literal.to_string() - } else { - panic!("Expected Literal"); - } + try_literal(it).expect("Expected Literal") } fn expect_group(it: &mut token_stream::IntoIter) -> Group { @@ -43,6 +61,39 @@ fn expect_group(it: &mut token_stream::IntoIter) -> Group { } } +fn expect_byte_string(it: &mut token_stream::IntoIter) -> String { + try_byte_string(it).expect("Expected byte string") +} + +#[derive(Debug, 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 = expect_ident(it); + assert_eq!(expect_punct(it), ','); + let max_length_str = expect_literal(it); + let max_length = 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) = it.next().unwrap() { + match ident.to_string().as_ref() { + "ArrayParam" => expect_array_fields(it), + _ => ParamType::Ident(ident.to_string()), + } + } else { + panic!("Expected Param Type") + } +} + fn expect_end(it: &mut token_stream::IntoIter) { if it.next().is_some() { panic!("Expected end"); @@ -74,12 +125,11 @@ fn get_group(it: &mut token_stream::IntoIter, expected_name: &str) -> Group { } fn get_byte_string(it: &mut token_stream::IntoIter, expected_name: &str) -> String { - let byte_string = get_literal(it, expected_name); - - assert!(byte_string.starts_with("b\"")); - assert!(byte_string.ends_with('\"')); - - byte_string[2..byte_string.len() - 1].to_string() + assert_eq!(expect_ident(it), expected_name); + assert_eq!(expect_punct(it), ':'); + let byte_string = expect_byte_string(it); + assert_eq!(expect_punct(it), ','); + byte_string } fn __build_modinfo_string_base( @@ -177,6 +227,91 @@ fn permissions_are_readonly(perms: &str) -> bool { } } +fn param_ops_path(param_type: &str) -> &'static str { + match param_type { + "bool" => "kernel::module_param::PARAM_OPS_BOOL", + "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", + "str" => "kernel::module_param::PARAM_OPS_STR", + t => panic!("Unrecognized type {}", t), + } +} + +fn try_simple_param_val( + param_type: &str, +) -> Box Option> { + match param_type { + "bool" => Box::new(|param_it| try_ident(param_it)), + "str" => Box::new(|param_it| { + try_byte_string(param_it) + .map(|s| format!("kernel::module_param::StringParam::Ref(b\"{}\")", s)) + }), + _ => Box::new(|param_it| try_literal(param_it)), + } +} + +fn get_default(param_type: &ParamType, param_it: &mut token_stream::IntoIter) -> String { + let try_param_val = match param_type { + ParamType::Ident(ref param_type) + | ParamType::Array { + vals: ref param_type, + max_length: _, + } => try_simple_param_val(param_type), + }; + assert_eq!(expect_ident(param_it), "default"); + assert_eq!(expect_punct(param_it), ':'); + let default = match param_type { + ParamType::Ident(_) => try_param_val(param_it).expect("Expected default param value"), + ParamType::Array { + vals: _, + max_length: _, + } => { + let group = expect_group(param_it); + assert_eq!(group.delimiter(), Delimiter::Bracket); + let mut default_vals = Vec::new(); + let mut it = group.stream().into_iter(); + + while let Some(default_val) = try_param_val(&mut it) { + default_vals.push(default_val); + match it.next() { + Some(TokenTree::Punct(punct)) => assert_eq!(punct.as_char(), ','), + None => break, + _ => panic!("Expected ',' or end of array default values"), + } + } + + let mut default_array = "kernel::module_param::ArrayParam::create(&[".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 = vals, + max_length = max_length + ) +} + /// Declares a kernel module. /// /// The `type` argument should be a type which implements the [`KernelModule`] @@ -242,6 +377,8 @@ fn permissions_are_readonly(perms: &str) -> bool { /// - `usize`: No equivalent C param type. /// - `str`: Corresponds to C `charp` param type. Reading returns a byte /// slice. +/// - `ArrayParam`: Corresponds to C parameters created using +/// `module_param_array`. An array of `T`'s of length at **most** `N`. /// /// `invbool` is unsupported: it was only ever used in a few modules. /// Consider using a `bool` and inverting the logic instead. @@ -264,6 +401,8 @@ pub fn module(ts: TokenStream) -> TokenStream { let mut params_modinfo = String::new(); + let mut array_types_to_generate = Vec::new(); + loop { let param_name = match it.next() { Some(TokenTree::Ident(ident)) => ident.to_string(), @@ -272,38 +411,35 @@ pub fn module(ts: TokenStream) -> TokenStream { }; assert_eq!(expect_punct(&mut it), ':'); - let param_type = expect_ident(&mut it); + let param_type = expect_type(&mut it); let group = expect_group(&mut it); assert_eq!(expect_punct(&mut it), ','); assert_eq!(group.delimiter(), Delimiter::Brace); let mut param_it = group.stream().into_iter(); - let param_default = match param_type.as_ref() { - "bool" => get_ident(&mut param_it, "default"), - "str" => get_byte_string(&mut param_it, "default"), - _ => get_literal(&mut param_it, "default"), - }; + let param_default = get_default(¶m_type, &mut param_it); let param_permissions = get_literal(&mut param_it, "permissions"); let param_description = get_byte_string(&mut param_it, "description"); expect_end(&mut param_it); // TODO: more primitive types - // TODO: other kinds: arrays, unsafes, etc. - let (param_kernel_type, ops) = match param_type.as_ref() { - "bool" => ("bool", "kernel::module_param::PARAM_OPS_BOOL"), - "i8" => ("i8", "kernel::module_param::PARAM_OPS_I8"), - "u8" => ("u8", "kernel::module_param::PARAM_OPS_U8"), - "i16" => ("i16", "kernel::module_param::PARAM_OPS_I16"), - "u16" => ("u16", "kernel::module_param::PARAM_OPS_U16"), - "i32" => ("i32", "kernel::module_param::PARAM_OPS_I32"), - "u32" => ("u32", "kernel::module_param::PARAM_OPS_U32"), - "i64" => ("i64", "kernel::module_param::PARAM_OPS_I64"), - "u64" => ("u64", "kernel::module_param::PARAM_OPS_U64"), - "isize" => ("isize", "kernel::module_param::PARAM_OPS_ISIZE"), - "usize" => ("usize", "kernel::module_param::PARAM_OPS_USIZE"), - "str" => ("charp", "kernel::bindings::param_ops_charp"), - t => panic!("Unrecognized type {}", t), + // TODO: other kinds: unsafes, etc. + let (param_kernel_type, ops): (String, _) = match param_type { + ParamType::Ident(ref param_type) => ( + param_type.to_string(), + param_ops_path(¶m_type).to_string(), + ), + ParamType::Array { + ref vals, + max_length, + } => { + array_types_to_generate.push((vals.clone(), max_length)); + ( + format!("__rust_array_param_{}_{}", vals, max_length), + generated_array_ops_name(vals, max_length), + ) + } }; params_modinfo.push_str(&build_modinfo_string_param( @@ -318,68 +454,44 @@ pub fn module(ts: TokenStream) -> TokenStream { ¶m_name, ¶m_description, )); - let param_type_internal = match param_type.as_ref() { - "str" => "*mut kernel::c_types::c_char", - _ => ¶m_type, - }; - let param_default = match param_type.as_ref() { - "str" => format!( - "b\"{}\0\" as *const _ as *mut kernel::c_types::c_char", - param_default + let param_type_internal = match param_type { + ParamType::Ident(ref param_type) => match param_type.as_ref() { + "str" => "kernel::module_param::StringParam".to_string(), + other => other.to_string(), + }, + ParamType::Array { + ref vals, + max_length, + } => format!( + "kernel::module_param::ArrayParam<{vals}, {max_length}>", + vals = vals, + max_length = max_length ), - _ => param_default, }; - let read_func = match (param_type.as_ref(), permissions_are_readonly(¶m_permissions)) { - ("str", false) => format!( + let read_func = if permissions_are_readonly(¶m_permissions) { + format!( " - fn read<'lck>(&self, lock: &'lck kernel::KParamGuard) -> &'lck [u8] {{ - // SAFETY: The pointer is provided either in `param_default` when building the module, - // or by the kernel through `param_set_charp`. Both will be valid C strings. - // Parameters are locked by `KParamGuard`. - unsafe {{ - kernel::c_types::c_string_bytes(__{name}_{param_name}_value) - }} - }} - ", - name = name, - param_name = param_name, - ), - ("str", true) => format!( - " - fn read(&self) -> &[u8] {{ - // SAFETY: The pointer is provided either in `param_default` when building the module, - // or by the kernel through `param_set_charp`. Both will be valid C strings. - // Parameters do not need to be locked because they are read only or sysfs is not enabled. - unsafe {{ - kernel::c_types::c_string_bytes(__{name}_{param_name}_value) - }} - }} - ", - name = name, - param_name = param_name, - ), - (_, false) => format!( - " - // SAFETY: Parameters are locked by `KParamGuard`. - fn read<'lck>(&self, lock: &'lck kernel::KParamGuard) -> &'lck {param_type_internal} {{ - unsafe {{ &__{name}_{param_name}_value }} + fn read(&self) -> &<{param_type_internal} as kernel::module_param::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_param::ModuleParam>::value(&__{name}_{param_name}_value) }} }} ", name = name, param_name = param_name, param_type_internal = param_type_internal, - ), - (_, true) => format!( + ) + } else { + format!( " - // SAFETY: Parameters do not need to be locked because they are read only or sysfs is not enabled. - fn read(&self) -> &{param_type_internal} {{ - unsafe {{ &__{name}_{param_name}_value }} + fn read<'lck>(&self, lock: &'lck kernel::KParamGuard) -> &'lck <{param_type_internal} as kernel::module_param::ModuleParam>::Value {{ + // SAFETY: Parameters are locked by `KParamGuard`. + unsafe {{ <{param_type_internal} as kernel::module_param::ModuleParam>::value(&__{name}_{param_name}_value) }} }} ", name = name, param_name = param_name, param_type_internal = param_type_internal, - ), + ) }; let kparam = format!( " @@ -448,6 +560,23 @@ pub fn module(ts: TokenStream) -> TokenStream { ); } + let mut generated_array_types = String::new(); + + for (vals, max_length) in array_types_to_generate { + let ops_name = generated_array_ops_name(&vals, max_length); + generated_array_types.push_str(&format!( + " + kernel::make_param_ops!( + {ops_name}, + kernel::module_param::ArrayParam<{vals}, {{ {max_length} }}> + ); + ", + ops_name = ops_name, + vals = vals, + max_length = max_length, + )); + } + let file = std::env::var("RUST_MODFILE").unwrap(); format!( @@ -532,6 +661,8 @@ pub fn module(ts: TokenStream) -> TokenStream { {file} {params_modinfo} + + {generated_array_types} ", type_ = type_, name = name, @@ -540,6 +671,7 @@ pub fn module(ts: TokenStream) -> TokenStream { license = &build_modinfo_string(&name, "license", &license), file = &build_modinfo_string_only_builtin(&name, "file", &file), params_modinfo = params_modinfo, + generated_array_types = generated_array_types, initcall_section = ".initcall6.init" ).parse().unwrap() }