Skip to content
7 changes: 6 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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_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 rust_example_2.my_array=1,2,3' | tee qemu-stdout.log

# Check
- run: grep -F '] Rust Example (init)' qemu-stdout.log
Expand All @@ -179,6 +179,11 @@ jobs:
- run: "grep '\\] \\[3\\] my_str: 🦀mod\\s*$' qemu-stdout.log"
- run: "grep '\\] \\[4\\] my_str: default str val\\s*$' qemu-stdout.log"

- run: "grep -F '] my_array: [0, 1]' qemu-stdout.log"
- run: "grep -F '] [2] my_array: [1, 2, 3]' qemu-stdout.log"
- run: "grep -F '] [3] my_array: [0, 1]' qemu-stdout.log"
- run: "grep -F '] [4] my_array: [1, 2, 3]' qemu-stdout.log"

- run: grep -F '] [3] Rust Example (exit)' qemu-stdout.log
- run: grep -F '] [4] Rust Example (exit)' qemu-stdout.log

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/qemu-init.sh
Original file line number Diff line number Diff line change
@@ -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

Expand Down
8 changes: 7 additions & 1 deletion drivers/char/rust_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use kernel::{chrdev, cstr, file_operations::FileOperations, miscdev};
module! {
type: RustExample,
name: b"rust_example",
author: b"Rust for Linux Contributors",
author: b"Rust for Linux Contributors-",
description: b"An example kernel module written in Rust",
license: b"GPL v2",
params: {
Expand All @@ -38,6 +38,11 @@ module! {
permissions: 0o644,
description: b"Example of usize",
},
my_array: [i32; 3] {
default: [0, 1],
permissions: 0,
description: b"Example of array",
},
},
}

Expand Down Expand Up @@ -72,6 +77,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());
}

// Including this large variable on the stack will trigger
Expand Down
6 changes: 6 additions & 0 deletions drivers/char/rust_example_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ module! {
permissions: 0o644,
description: b"Example of usize",
},
my_array: [i32; 3] {
default: [0, 1],
permissions: 0,
description: b"Example of array",
},
},
}

Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions drivers/char/rust_example_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ module! {
permissions: 0o644,
description: b"Example of usize",
},
my_array: [i32; 3] {
default: [0, 1],
permissions: 0,
description: b"Example of array",
},
},
}

Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions drivers/char/rust_example_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ module! {
permissions: 0o644,
description: b"Example of usize",
},
my_array: [i32; 3] {
default: [0, 1],
permissions: 0,
description: b"Example of array",
},
},
}

Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion rust/kernel/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
//! 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,
maybe_uninit_ref
)]
#![deny(clippy::complexity)]
#![deny(clippy::correctness)]
#![deny(clippy::perf)]
Expand Down
194 changes: 183 additions & 11 deletions rust/kernel/module_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Self>;
fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self>;

/// 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.
///
Expand Down Expand Up @@ -161,13 +174,18 @@ 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<Self> {
fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
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
}
}
};
}
Expand All @@ -184,27 +202,27 @@ macro_rules! impl_module_param {
/// );
/// ```
macro_rules! make_param_ops {
($ops:ident, $ty:ident) => {
($ops:ident, $ty:ty) => {
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),
};
};
}
Expand Down Expand Up @@ -282,16 +300,21 @@ make_param_ops!(
);

impl ModuleParam for bool {
type Value = bool;
const NOARG_ALLOWED: bool = true;

fn try_from_param_arg(arg: Option<&[u8]>) -> Option<Self> {
fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
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,
}
}

fn value(&self) -> &Self::Value {
self
}
}

make_param_ops!(
Expand All @@ -300,3 +323,152 @@ make_param_ops!(
PARAM_OPS_BOOL,
bool
);

/// An array of at __most__ `N` values.
pub struct ArrayParam<T, const N: usize> {
values: [core::mem::MaybeUninit<T>; N],
used: usize,
}

impl<T, const N: usize> ArrayParam<T, { N }> {
fn values(&self) -> &[T] {
// SAFETY: `self` can only be generated and modified by the methods
// [`new`], and [`push`]. These maintain the invariant that the first
// `self.used` elements of `self.values` are initialized.
unsafe {
&*(&self.values[0..self.used] as *const [core::mem::MaybeUninit<T>] as *const [T])
}
}
}

impl<T: Copy, const N: usize> ArrayParam<T, { N }> {
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<T: core::fmt::Display, const N: usize> core::fmt::Display for ArrayParam<T, { N }> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for val in self.values() {
write!(f, "{},", val)?;
}
Ok(())
}
}

impl<T: Copy + core::fmt::Display + ModuleParam, const N: usize> ModuleParam
for ArrayParam<T, { N }>
{
type Value = [T];
const NOARG_ALLOWED: bool = false;

fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
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<u8>),
}

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<Self> {
// Safety: It is always safe to call `slab_is_available`.
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
);
Loading