Skip to content

Rust string param #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
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