diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f6810fa8aa695e..809dccf5383ca6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -156,7 +156,7 @@ jobs: # Run - run: ${{ env.BUILD_DIR }}usr/gen_init_cpio .github/workflows/qemu-initramfs.desc > qemu-initramfs.img - - run: qemu-system-${{ env.QEMU_ARCH }} -kernel ${{ env.BUILD_DIR }}${{ env.IMAGE_PATH }} -initrd qemu-initramfs.img -M ${{ env.QEMU_MACHINE }} -cpu ${{ env.QEMU_CPU }} -smp 2 -nographic -no-reboot -append '${{ env.QEMU_APPEND }} rust_example.my_i32=123321 rust_example.my_str=🦀mod rust_example.my_invbool=y rust_example_2.my_i32=234432' | tee qemu-stdout.log + - run: qemu-system-${{ env.QEMU_ARCH }} -kernel ${{ env.BUILD_DIR }}${{ env.IMAGE_PATH }} -initrd qemu-initramfs.img -M ${{ env.QEMU_MACHINE }} -cpu ${{ env.QEMU_CPU }} -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' | tee qemu-stdout.log # Check - run: grep -F '] Rust Example (init)' qemu-stdout.log @@ -169,6 +169,11 @@ jobs: - run: "grep -F '] [3] my_i32: 345543' qemu-stdout.log" - run: "grep -F '] [4] my_i32: 456654' qemu-stdout.log" + - run: "grep -F '] my_usize: 42' qemu-stdout.log" + - run: "grep -F '] [2] my_usize: 42' qemu-stdout.log" + - run: "grep -F '] [3] my_usize: 42' qemu-stdout.log" + - run: "grep -F '] [4] my_usize: 84' qemu-stdout.log" + - run: "grep '\\] my_str: 🦀mod\\s*$' qemu-stdout.log" - run: "grep '\\] \\[2\\] my_str: default str val\\s*$' qemu-stdout.log" - run: "grep '\\] \\[3\\] my_str: 🦀mod\\s*$' qemu-stdout.log" diff --git a/.github/workflows/qemu-init.sh b/.github/workflows/qemu-init.sh index a0d547af61e7bd..666630c1798f26 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 +busybox insmod rust_example_4.ko my_i32=456654 my_usize=84 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 f8233000c7a1c7..91ba6bc353a987 100644 --- a/drivers/char/rust_example.rs +++ b/drivers/char/rust_example.rs @@ -33,6 +33,11 @@ module! { permissions: 0o644, description: b"Example of a string param", }, + my_usize: usize { + default: 42, + permissions: 0o644, + description: b"Example of usize", + }, }, } @@ -66,6 +71,7 @@ impl KernelModule for RustExample { " my_str: {}", core::str::from_utf8(my_str.read(&lock))? ); + println!(" my_usize: {}", my_usize.read(&lock)); } // Including this large variable on the stack will trigger diff --git a/drivers/char/rust_example_2.rs b/drivers/char/rust_example_2.rs index 9db5d84f681e0a..3f0630b286476a 100644 --- a/drivers/char/rust_example_2.rs +++ b/drivers/char/rust_example_2.rs @@ -28,6 +28,11 @@ module! { permissions: 0o644, description: b"Example of a string param", }, + my_usize: usize { + default: 42, + permissions: 0o644, + description: b"Example of usize", + }, }, } @@ -48,6 +53,7 @@ impl KernelModule for RustExample2 { "[2] my_str: {}", core::str::from_utf8(my_str.read(&lock))? ); + println!("[2] my_usize: {}", my_usize.read(&lock)); } // 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 bc7f44d176f00d..5fa88b3c969382 100644 --- a/drivers/char/rust_example_3.rs +++ b/drivers/char/rust_example_3.rs @@ -28,6 +28,11 @@ module! { permissions: 0o644, description: b"Example of a string param", }, + my_usize: usize { + default: 42, + permissions: 0o644, + description: b"Example of usize", + }, }, } @@ -48,6 +53,7 @@ impl KernelModule for RustExample3 { "[3] my_str: {}", core::str::from_utf8(my_str.read(&lock))? ); + println!("[3] my_usize: {}", my_usize.read(&lock)); } // 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 2ee356690330e6..9b7230d01d1905 100644 --- a/drivers/char/rust_example_4.rs +++ b/drivers/char/rust_example_4.rs @@ -28,6 +28,11 @@ module! { permissions: 0o644, description: b"Example of a string param", }, + my_usize: usize { + default: 42, + permissions: 0o644, + description: b"Example of usize", + }, }, } @@ -48,6 +53,7 @@ impl KernelModule for RustExample4 { "[4] my_str: {}", core::str::from_utf8(my_str.read(&lock))? ); + println!("[4] my_usize: {}", my_usize.read(&lock)); } // Including this large variable on the stack will trigger diff --git a/rust/Makefile b/rust/Makefile index 54d2dbcd1a4f67..5be6212c5a9294 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -72,7 +72,7 @@ $(objtree)/rust/bindings_generated.rs: $(srctree)/rust/kernel/bindings_helper.h quiet_cmd_exports = EXPORTS $@ cmd_exports = \ $(NM) -p --defined-only $< \ - | grep -F ' T ' | cut -d ' ' -f 3 | grep -E '^(__rust_|_R)' \ + | grep -E ' (T|R) ' | cut -d ' ' -f 3 | grep -E '^(__rust_|_R)' \ | xargs -n1 -Isymbol \ echo 'EXPORT_SYMBOL$(exports_target_type)(symbol);' > $@ diff --git a/rust/kernel/buffer.rs b/rust/kernel/buffer.rs new file mode 100644 index 00000000000000..60e6a04a37699e --- /dev/null +++ b/rust/kernel/buffer.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Struct for writing to a pre-allocated buffer with the [`write!`] macro. + +use core::fmt; + +/// A pre-allocated buffer that implements [`core::fmt::Write`]. +/// +/// Consequtive writes will append to what has already been written. +/// Writes that don't fit in the buffer will fail. +pub struct Buffer<'a> { + slice: &'a mut [u8], + pos: usize, +} + +impl<'a> Buffer<'a> { + /// Create a new buffer from an existing array. + pub fn new(slice: &'a mut [u8]) -> Self { + Buffer { slice, pos: 0 } + } + + /// Number of bytes that have already been written to the buffer. + /// This will always be less than the length of the original array. + pub fn bytes_written(&self) -> usize { + self.pos + } +} + +impl<'a> fmt::Write for Buffer<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + if s.len() > self.slice.len() - self.pos { + Err(fmt::Error) + } else { + self.slice[self.pos..self.pos + s.len()].copy_from_slice(s.as_bytes()); + self.pos += s.len(); + Ok(()) + } + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index e40a7970422dd0..6ef12992f50a81 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -30,11 +30,13 @@ mod allocator; #[doc(hidden)] pub mod bindings; +pub mod buffer; pub mod c_types; pub mod chrdev; mod error; pub mod file_operations; pub mod miscdev; +pub mod module_param; pub mod prelude; pub mod printk; pub mod random; @@ -48,6 +50,11 @@ pub mod user_ptr; pub use crate::error::{Error, KernelResult}; pub use crate::types::{CStr, Mode}; +/// Page size defined in terms of the `PAGE_SHIFT` macro from C. +/// +/// [`PAGE_SHIFT`]: ../../../include/asm-generic/page.h +pub const PAGE_SIZE: usize = 1 << bindings::PAGE_SHIFT; + /// The top level entrypoint to implementing a kernel module. /// /// For any teardown or cleanup operations, your type may implement [`Drop`]. diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs new file mode 100644 index 00000000000000..776458773c0821 --- /dev/null +++ b/rust/kernel/module_param.rs @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Types for module parameters. +//! +//! C header: [`include/linux/moduleparam.h`](../../../include/linux/moduleparam.h) + +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 { + /// 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=foo`). + const NOARG_ALLOWED: bool; + + /// Convert a parameter argument into the parameter value. + /// + /// `None` should be returned when parsing of the argument fails. + /// `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; + + /// 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 crate::c_types::c_char, + param: *const crate::bindings::kernel_param, + ) -> crate::c_types::c_int { + let arg = if val.is_null() { + None + } else { + Some(crate::c_types::c_string_bytes(val)) + }; + match Self::try_from_param_arg(arg) { + Some(new_value) => { + let old_value = (*param).__bindgen_anon_1.arg as *mut Self; + let _ = core::ptr::replace(old_value, new_value); + 0 + } + None => crate::error::Error::EINVAL.to_kernel_errno(), + } + } + + /// Write a string representation of the current parameter value to `buf`. + /// + /// 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 crate::c_types::c_char, + param: *const crate::bindings::kernel_param, + ) -> crate::c_types::c_int { + let slice = core::slice::from_raw_parts_mut(buf as *mut u8, crate::PAGE_SIZE); + let mut buf = crate::buffer::Buffer::new(slice); + match write!(buf, "{}\0", *((*param).__bindgen_anon_1.arg as *mut Self)) { + Err(_) => crate::error::Error::EINVAL.to_kernel_errno(), + Ok(()) => buf.bytes_written() as crate::c_types::c_int, + } + } + + /// 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 crate::c_types::c_void) { + core::ptr::drop_in_place(arg as *mut Self); + } +} + +/// Trait for parsing integers. +/// +/// Strings begining 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) = if let Some(n) = src.strip_prefix("0x") { + (16, n) + } else if let Some(n) = src.strip_prefix("0X") { + (16, n) + } else if let Some(n) = src.strip_prefix("0o") { + (8, n) + } else if let Some(n) = src.strip_prefix("0O") { + (8, n) + } else if let Some(n) = src.strip_prefix("0b") { + (2, n) + } else if let Some(n) = 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 => None, + Some(b'-') => Self::from_str_unsigned(&src[1..]).ok()?.checked_neg(), + Some(b'+') => Some(Self::from_str_unsigned(&src[1..]).ok()?), + Some(_) => Some(Self::from_str_unsigned(src).ok()?), + } + } +} + +macro_rules! impl_parse_int { + ($ty:ident) => { + 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) => { + impl ModuleParam for $ty { + const NOARG_ALLOWED: bool = false; + + fn try_from_param_arg(arg: Option<&[u8]>) -> Option { + let bytes = arg?; + let utf8 = core::str::from_utf8(bytes).ok()?; + <$ty as crate::module_param::ParseInt>::from_str(utf8) + } + } + }; +} + +#[macro_export] +/// Generate a static [`kernel_param_ops`](../../../include/linux/moduleparam.h) struct. +/// +/// # Example +/// ```rust +/// 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:ident) => { + make_param_ops!( + #[doc=""] + $ops, + $ty + ); + }; + ($(#[$meta:meta])* $ops:ident, $ty:ident) => { + $(#[$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 + } 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), + }; + }; +} + +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 { + const NOARG_ALLOWED: bool = true; + + fn try_from_param_arg(arg: Option<&[u8]>) -> Option { + match arg { + None => Some(true), + Some(b"y") | Some(b"Y") | Some(b"1") | Some(b"true") => Some(true), + Some(b"n") | Some(b"N") | Some(b"0") | Some(b"false") => Some(false), + _ => None, + } + } +} + +make_param_ops!( + /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h) + /// for [`bool`]. + PARAM_OPS_BOOL, + bool +); diff --git a/rust/module.rs b/rust/module.rs index 792f6020d840ae..a7925f3ac1961d 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -230,12 +230,16 @@ fn permissions_are_readonly(perms: &str) -> bool { /// # Supported parameter types /// /// - `bool`: Corresponds to C `bool` param type. +/// - `i8`: No equivalent C param type. /// - `u8`: Corresponds to C `char` param type. /// - `i16`: Corresponds to C `short` param type. /// - `u16`: Corresponds to C `ushort` param type. /// - `i32`: Corresponds to C `int` param type. /// - `u32`: Corresponds to C `uint` param type. +/// - `i64`: No equivalent C param type. /// - `u64`: Corresponds to C `ullong` param type. +/// - `isize`: No equivalent C param type. +/// - `usize`: No equivalent C param type. /// - `str`: Corresponds to C `charp` param type. Reading returns a byte /// slice. /// @@ -286,15 +290,19 @@ pub fn module(ts: TokenStream) -> TokenStream { // TODO: more primitive types // TODO: other kinds: arrays, unsafes, etc. - let param_kernel_type = match param_type.as_ref() { - "bool" => "bool", - "u8" => "char", - "i16" => "short", - "u16" => "ushort", - "i32" => "int", - "u32" => "uint", - "u64" => "ullong", - "str" => "charp", + 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), }; @@ -421,7 +429,7 @@ pub fn module(ts: TokenStream) -> TokenStream { mod_: unsafe {{ &kernel::bindings::__this_module as *const _ as *mut _ }}, #[cfg(not(MODULE))] mod_: core::ptr::null_mut(), - ops: unsafe {{ &kernel::bindings::param_ops_{param_kernel_type} }} as *const kernel::bindings::kernel_param_ops, + ops: unsafe {{ &{ops} }} as *const kernel::bindings::kernel_param_ops, perm: {permissions}, level: -1, flags: 0, @@ -431,9 +439,9 @@ pub fn module(ts: TokenStream) -> TokenStream { name = name, param_type_internal = param_type_internal, read_func = read_func, - param_kernel_type = param_kernel_type, param_default = param_default, param_name = param_name, + ops = ops, permissions = param_permissions, kparam = kparam, )