Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.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
Expand All @@ -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"
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
busybox insmod rust_example_4.ko my_i32=456654 my_usize=84
busybox rmmod rust_example_3.ko
busybox rmmod rust_example_4.ko

Expand Down
6 changes: 6 additions & 0 deletions drivers/char/rust_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
}

Expand Down Expand Up @@ -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
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 @@ -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",
},
},
}

Expand All @@ -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
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 @@ -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",
},
},
}

Expand All @@ -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
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 @@ -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",
},
},
}

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion rust/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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);' > $@

Expand Down
28 changes: 28 additions & 0 deletions rust/kernel/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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(())
}
}
}
5 changes: 5 additions & 0 deletions rust/kernel/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ mod allocator;
#[doc(hidden)]
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;
Expand All @@ -48,6 +50,9 @@ 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.
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`].
Expand Down
198 changes: 198 additions & 0 deletions rust/kernel/module_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// 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 `to_string` returns
/// more than `kernel::PAGE_SIZE` bytes (including an additional null terminator).
pub trait ModuleParam: core::fmt::Display + core::marker::Sized {
/// 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;

/// `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>;

/// # 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(),
}
}

/// # 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);
}
}

/// 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.
trait ParseInt: Sized {
fn from_str_radix(src: &str, radix: u32) -> Result<Self, core::num::ParseIntError>;
fn checked_neg(self) -> Option<Self>;

fn from_str_unsigned(src: &str) -> Result<Self, core::num::ParseIntError> {
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<Self> {
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<Self, core::num::ParseIntError> {
$ty::from_str_radix(src, radix)
}
fn checked_neg(self) -> Option<Self> {
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<Self> {
let bytes = arg?;
let utf8 = core::str::from_utf8(bytes).ok()?;
<$ty as crate::module_param::ParseInt>::from_str(utf8)
}
}
};
}

macro_rules! make_param_ops {
($ops:ident, $ty:ident) => {
/// Generated 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!(PARAM_OPS_I8, i8);
make_param_ops!(PARAM_OPS_U8, u8);
make_param_ops!(PARAM_OPS_I16, i16);
make_param_ops!(PARAM_OPS_U16, u16);
make_param_ops!(PARAM_OPS_I32, i32);
make_param_ops!(PARAM_OPS_U32, u32);
make_param_ops!(PARAM_OPS_I64, i64);
make_param_ops!(PARAM_OPS_U64, u64);
make_param_ops!(PARAM_OPS_ISIZE, isize);
make_param_ops!(PARAM_OPS_USIZE, usize);

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

fn try_from_param_arg(arg: Option<&[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,
}
}
}

make_param_ops!(PARAM_OPS_BOOL, bool);
Loading