diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f0ce044d63d0a8..c825068e5909dd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -153,7 +153,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 @@ -166,6 +166,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 8470bda232b1f9..77b621a440807e 100644 --- a/drivers/char/rust_example.rs +++ b/drivers/char/rust_example.rs @@ -31,6 +31,11 @@ module! { permissions: 0o644, description: b"Example of a string param", }, + my_usize: usize { + default: 42, + permissions: 0o644, + description: b"Example of usize", + }, }, } @@ -63,6 +68,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 496c55cf130554..7e2a8e33738330 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -46,7 +46,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..e5eb0e406dba33 --- /dev/null +++ b/rust/kernel/buffer.rs @@ -0,0 +1,31 @@ +use core::fmt; + +pub struct Buffer<'a> { + slice: &'a mut [u8], + pos: usize, +} + +impl<'a> Buffer<'a> { + pub fn new(slice: &'a mut [u8]) -> Self { + Buffer { + slice, + pos: 0, + } + } + + 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 972c7ad8a1a8c2..d6514cd1400199 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -14,11 +14,13 @@ use core::panic::PanicInfo; mod allocator; pub mod bindings; +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; @@ -32,6 +34,8 @@ pub mod user_ptr; pub use crate::error::{Error, KernelResult}; pub use crate::types::{CStr, Mode}; +pub const PAGE_SIZE: usize = 1 << bindings::PAGE_SHIFT; + /// KernelModule is the top level entrypoint to implementing a kernel module. Your kernel module /// should implement the `init` method on it, which maps to the `module_init` macro in Linux C API. /// You can use this method to do whatever setup or registration your module should do. For any diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs new file mode 100644 index 00000000000000..7a596f30029bdd --- /dev/null +++ b/rust/kernel/module_param.rs @@ -0,0 +1,67 @@ +use core::fmt::Write; + +/// Types that can be used for module parameters. +/// Note that displaying the type in `sysfs` will fail if `to_string` returns +/// more than 4K bytes (including an additional null terminator). +pub trait ModuleParam : core::fmt::Display + core::marker::Sized { + fn try_from_param_arg(arg: &[u8]) -> Option; + + /// # Safety + /// + /// `val` 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 = 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() + } + } + + /// # 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, + } + } + + /// # 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); + } +} + +macro_rules! make_param_ops { + ($ops:ident, $ty:ident) => { + impl ModuleParam for $ty { + fn try_from_param_arg(arg: &[u8]) -> Option { + let utf8 = core::str::from_utf8(arg).ok()?; + utf8.parse::<$ty>().ok() + } + } + + pub static $ops: crate::bindings::kernel_param_ops = crate::bindings::kernel_param_ops { + flags: 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), + }; + } +} + +make_param_ops!(PARAM_OPS_I8, i8); +make_param_ops!(PARAM_OPS_I64, i64); +make_param_ops!(PARAM_OPS_USIZE, usize); +make_param_ops!(PARAM_OPS_ISIZE, isize); diff --git a/rust/module.rs b/rust/module.rs index 97f41b7cbde3a5..6e87414aadb193 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -221,12 +221,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 the param returns a `&[u8]`. /// @@ -277,15 +281,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::bindings::param_ops_bool"), + "i8" => ("i8", "kernel::module_param::PARAM_OPS_I8"), + "u8" => ("u8", "kernel::bindings::param_ops_char"), + "i16" => ("i16", "kernel::bindings::param_ops_short"), + "u16" => ("u16", "kernel::bindings::param_ops_ushort"), + "i32" => ("i32", "kernel::bindings::param_ops_int"), + "u32" => ("u32", "kernel::bindings::param_ops_uint"), + "i64" => ("i64", "kernel::module_param::PARAM_OPS_I64"), + "u64" => ("u64", "kernel::bindings::param_ops_ullong"), + "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), }; @@ -409,7 +417,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, @@ -419,9 +427,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, )