Skip to content
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

Add Rust int param types #87

Merged
merged 10 commits into from
Mar 3, 2021
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)' \
ojeda marked this conversation as resolved.
Show resolved Hide resolved
adamrk marked this conversation as resolved.
Show resolved Hide resolved
| 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;
ojeda marked this conversation as resolved.
Show resolved Hide resolved

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.
adamrk marked this conversation as resolved.
Show resolved Hide resolved
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.
ojeda marked this conversation as resolved.
Show resolved Hide resolved
/// Note that displaying the type in `sysfs` will fail if `to_string` returns
ojeda marked this conversation as resolved.
Show resolved Hide resolved
/// 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
ojeda marked this conversation as resolved.
Show resolved Hide resolved
/// 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>;
ojeda marked this conversation as resolved.
Show resolved Hide resolved

/// # 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`.
ojeda marked this conversation as resolved.
Show resolved Hide resolved
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
ojeda marked this conversation as resolved.
Show resolved Hide resolved
ojeda marked this conversation as resolved.
Show resolved Hide resolved
/// 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.
ojeda marked this conversation as resolved.
Show resolved Hide resolved
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.
ojeda marked this conversation as resolved.
Show resolved Hide resolved
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