summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiguel Ojeda <ojeda@users.noreply.github.com>2020-09-20 01:53:25 +0200
committerGitHub <noreply@github.com>2020-09-20 01:53:25 +0200
commit58ffd762f26eb282e84aaa49a647d379c029b9f6 (patch)
treec447bc24bc994c2f6a6ae81611bd93183f08a3f9
parent33c80fea1445f217bc048aada96e77bab857cd1c (diff)
parent60567cfb44249250ce3f86b8131a07b37c7ef55b (diff)
Merge pull request #7 from Rust-for-Linux/rust-params
Parameter support
-rw-r--r--Cargo.lock5
-rw-r--r--Cargo.toml1
-rw-r--r--drivers/char/rust_example/Cargo.toml2
-rw-r--r--drivers/char/rust_example/src/lib.rs29
-rw-r--r--rust/kernel/Cargo.toml1
-rw-r--r--rust/kernel/build.rs2
-rw-r--r--rust/kernel/src/lib.rs125
-rw-r--r--rust/kernel/src/prelude.rs3
-rw-r--r--rust/module/Cargo.toml12
-rw-r--r--rust/module/src/lib.rs355
-rw-r--r--scripts/Makefile.lib5
11 files changed, 407 insertions, 133 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 414ef254f79e..c358a6a4fbdc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -149,6 +149,7 @@ version = "0.1.0"
dependencies = [
"bindgen",
"bitflags",
+ "module",
"shlex 0.1.1",
]
@@ -196,6 +197,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
+name = "module"
+version = "0.1.0"
+
+[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 26c9c7516ea0..914306648fe8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,7 @@
[workspace]
members = [
"rust/shlex",
+ "rust/module",
"rust/kernel",
"drivers/char/rust_example",
]
diff --git a/drivers/char/rust_example/Cargo.toml b/drivers/char/rust_example/Cargo.toml
index 500476526018..27232fd06347 100644
--- a/drivers/char/rust_example/Cargo.toml
+++ b/drivers/char/rust_example/Cargo.toml
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
# No need for details like `authors` here -- that goes in the modinfo
-# via the `kernel_module!` macro
+# via the `module!` macro
[package]
name = "rust_example"
version = "0.1.0"
diff --git a/drivers/char/rust_example/src/lib.rs b/drivers/char/rust_example/src/lib.rs
index 56562b840d9c..e8405f12ce44 100644
--- a/drivers/char/rust_example/src/lib.rs
+++ b/drivers/char/rust_example/src/lib.rs
@@ -5,6 +5,26 @@
use kernel::prelude::*;
+module!{
+ type: RustExample,
+ name: b"rust_example",
+ author: b"Rust for Linux Contributors",
+ description: b"An example kernel module written in Rust",
+ license: b"GPL v2",
+ params: {
+ my_bool: bool {
+ default: true,
+ permissions: 0,
+ description: b"Example of bool",
+ },
+ my_i32: i32 {
+ default: 42,
+ permissions: 0o644,
+ description: b"Example of i32",
+ },
+ },
+}
+
struct RustExample {
message: String,
}
@@ -13,6 +33,9 @@ impl KernelModule for RustExample {
fn init() -> KernelResult<Self> {
println!("Rust Example (init)");
println!("Am I built-in? {}", !cfg!(MODULE));
+ println!("Parameters:");
+ println!(" my_bool: {}", my_bool.read());
+ println!(" my_i32: {}", my_i32.read());
Ok(RustExample {
message: "on the heap!".to_owned(),
})
@@ -26,9 +49,3 @@ impl Drop for RustExample {
}
}
-kernel_module!(
- RustExample,
- author: b"Rust for Linux Contributors",
- description: b"An example kernel module written in Rust",
- license: b"GPL v2"
-);
diff --git a/rust/kernel/Cargo.toml b/rust/kernel/Cargo.toml
index d7f6b5aa375d..fca60a4635ca 100644
--- a/rust/kernel/Cargo.toml
+++ b/rust/kernel/Cargo.toml
@@ -9,6 +9,7 @@ publish = false
[dependencies]
bitflags = "1"
+module = { path = "../module" }
[build-dependencies]
bindgen = "0.54"
diff --git a/rust/kernel/build.rs b/rust/kernel/build.rs
index b9085a6367b4..5a3794fdc995 100644
--- a/rust/kernel/build.rs
+++ b/rust/kernel/build.rs
@@ -47,6 +47,8 @@ const INCLUDED_VARS: &[&str] = &[
"SEEK_CUR",
"SEEK_END",
"O_NONBLOCK",
+ "param_ops_bool",
+ "param_ops_int",
];
const OPAQUE_TYPES: &[&str] = &[
// These need to be opaque because they're both packed and aligned, which rustc
diff --git a/rust/kernel/src/lib.rs b/rust/kernel/src/lib.rs
index 688c7590602d..7880928de74d 100644
--- a/rust/kernel/src/lib.rs
+++ b/rust/kernel/src/lib.rs
@@ -25,131 +25,6 @@ pub mod user_ptr;
pub use crate::error::{Error, KernelResult};
pub use crate::types::{CStr, Mode};
-/// Declares the entrypoint for a kernel module. The first argument should be a type which
-/// implements the [`KernelModule`] trait. Also accepts various forms of kernel metadata.
-///
-/// Example:
-/// ```rust,no_run
-/// use kernel::prelude::*;
-///
-/// struct MyKernelModule;
-/// impl KernelModule for MyKernelModule {
-/// fn init() -> KernelResult<Self> {
-/// Ok(MyKernelModule)
-/// }
-/// }
-///
-/// kernel_module!(
-/// MyKernelModule,
-/// author: b"Rust for Linux Contributors",
-/// description: b"My very own kernel module!",
-/// license: b"GPL"
-/// );
-#[macro_export]
-macro_rules! kernel_module {
- ($module:ty, $($name:ident : $value:expr),*) => {
- static mut __MOD: Option<$module> = None;
-
- // Built-in modules are initialized through an initcall pointer
- //
- // TODO: should we compile a C file on the fly to avoid duplication?
- #[cfg(not(MODULE))]
- #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
- #[link_section = ".initcall6.init"]
- #[used]
- pub static __initcall: extern "C" fn() -> $crate::c_types::c_int = init_module;
-
- #[cfg(not(MODULE))]
- #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
- global_asm!(
- r#".section ".initcall6.init", "a"
- __initcall:
- .long init_module - .
- .previous
- "#
- );
-
- // TODO: pass the kernel module name here to generate a unique,
- // helpful symbol name (the name would also useful for the `modinfo`
- // issue below).
- #[no_mangle]
- pub extern "C" fn init_module() -> $crate::c_types::c_int {
- match <$module as $crate::KernelModule>::init() {
- Ok(m) => {
- unsafe {
- __MOD = Some(m);
- }
- return 0;
- }
- Err(e) => {
- return e.to_kernel_errno();
- }
- }
- }
-
- #[no_mangle]
- pub extern "C" fn cleanup_module() {
- unsafe {
- // Invokes drop() on __MOD, which should be used for cleanup.
- __MOD = None;
- }
- }
-
- $(
- $crate::kernel_module!(@attribute $name, $value);
- )*
- };
-
- // TODO: The modinfo attributes below depend on the compiler placing
- // the variables in order in the .modinfo section, so that you end up
- // with b"key=value\0" in order in the section. This is a reasonably
- // standard trick in C, but I'm not sure that rustc guarantees it.
- //
- // Ideally we'd be able to use concat_bytes! + stringify_bytes! +
- // some way of turning a string literal (or at least a string
- // literal token) into a bytes literal, and get a single static
- // [u8; * N] with the whole thing, but those don't really exist yet.
- // Most of the alternatives (e.g. .as_bytes() as a const fn) give
- // you a pointer, not an array, which isn't right.
-
- // TODO: `modules.builtin.modinfo` etc. is missing the prefix (module name)
- (@attribute author, $value:expr) => {
- #[link_section = ".modinfo"]
- #[used]
- pub static AUTHOR_KEY: [u8; 7] = *b"author=";
- #[link_section = ".modinfo"]
- #[used]
- pub static AUTHOR_VALUE: [u8; $value.len()] = *$value;
- #[link_section = ".modinfo"]
- #[used]
- pub static AUTHOR_NUL: [u8; 1] = *b"\0";
- };
-
- (@attribute description, $value:expr) => {
- #[link_section = ".modinfo"]
- #[used]
- pub static DESCRIPTION_KEY: [u8; 12] = *b"description=";
- #[link_section = ".modinfo"]
- #[used]
- pub static DESCRIPTION_VALUE: [u8; $value.len()] = *$value;
- #[link_section = ".modinfo"]
- #[used]
- pub static DESCRIPTION_NUL: [u8; 1] = *b"\0";
- };
-
- (@attribute license, $value:expr) => {
- #[link_section = ".modinfo"]
- #[used]
- pub static LICENSE_KEY: [u8; 8] = *b"license=";
- #[link_section = ".modinfo"]
- #[used]
- pub static LICENSE_VALUE: [u8; $value.len()] = *$value;
- #[link_section = ".modinfo"]
- #[used]
- pub static LICENSE_NUL: [u8; 1] = *b"\0";
- };
-}
-
/// 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/src/prelude.rs b/rust/kernel/src/prelude.rs
index d88baa99c902..75a5017488a4 100644
--- a/rust/kernel/src/prelude.rs
+++ b/rust/kernel/src/prelude.rs
@@ -7,8 +7,9 @@ pub use alloc::{
borrow::ToOwned,
};
+pub use module::module;
+
pub use super::{
- kernel_module,
println,
KernelResult,
KernelModule,
diff --git a/rust/module/Cargo.toml b/rust/module/Cargo.toml
new file mode 100644
index 000000000000..c61e6d2c192c
--- /dev/null
+++ b/rust/module/Cargo.toml
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+
+[package]
+name = "module"
+version = "0.1.0"
+authors = ["Rust for Linux Contributors"]
+edition = "2018"
+publish = false
+
+[lib]
+proc-macro = true
+
diff --git a/rust/module/src/lib.rs b/rust/module/src/lib.rs
new file mode 100644
index 000000000000..82dac6780b9c
--- /dev/null
+++ b/rust/module/src/lib.rs
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Implements the `module!` macro magic
+
+extern crate proc_macro;
+
+use proc_macro::{TokenStream, TokenTree, Group, Delimiter, token_stream};
+
+fn expect_ident(it: &mut token_stream::IntoIter) -> String {
+ if let TokenTree::Ident(ident) = it.next().unwrap() {
+ ident.to_string()
+ } else {
+ panic!("Expected Ident");
+ }
+}
+
+fn expect_punct(it: &mut token_stream::IntoIter) -> char {
+ if let TokenTree::Punct(punct) = it.next().unwrap() {
+ punct.as_char()
+ } else {
+ panic!("Expected Punct");
+ }
+}
+
+fn expect_literal(it: &mut token_stream::IntoIter) -> String {
+ if let TokenTree::Literal(literal) = it.next().unwrap() {
+ literal.to_string()
+ } else {
+ panic!("Expected Literal");
+ }
+}
+
+fn expect_group(it: &mut token_stream::IntoIter) -> Group {
+ if let TokenTree::Group(group) = it.next().unwrap() {
+ group
+ } else {
+ panic!("Expected Group");
+ }
+}
+
+fn expect_end(it: &mut token_stream::IntoIter) {
+ if let None = it.next() {
+ } else {
+ panic!("Expected end");
+ }
+}
+
+fn get_ident(it: &mut token_stream::IntoIter, expected_name: &str) -> String {
+ assert_eq!(expect_ident(it), expected_name);
+ assert_eq!(expect_punct(it), ':');
+ let ident = expect_ident(it);
+ assert_eq!(expect_punct(it), ',');
+ ident
+}
+
+fn get_literal(it: &mut token_stream::IntoIter, expected_name: &str) -> String {
+ assert_eq!(expect_ident(it), expected_name);
+ assert_eq!(expect_punct(it), ':');
+ let literal = expect_literal(it);
+ assert_eq!(expect_punct(it), ',');
+ literal
+}
+
+fn get_group(it: &mut token_stream::IntoIter, expected_name: &str) -> Group {
+ assert_eq!(expect_ident(it), expected_name);
+ assert_eq!(expect_punct(it), ':');
+ let group = expect_group(it);
+ assert_eq!(expect_punct(it), ',');
+ group
+}
+
+fn get_byte_string(it: &mut token_stream::IntoIter, expected_name: &str) -> String {
+ let byte_string = get_literal(it, expected_name);
+
+ assert!(byte_string.starts_with("b\""));
+ assert!(byte_string.ends_with("\""));
+
+ byte_string[2..byte_string.len() - 1].to_string()
+}
+
+fn __build_modinfo_string_base(module: &str, field: &str, content: &str, variable: &str, builtin: bool) -> String {
+ let string = if builtin {
+ // Built-in modules prefix their modinfo strings by `module.`
+ format!("{module}.{field}={content}", module=module, field=field, content=content)
+ } else {
+ // Loadable modules' modinfo strings go as-is
+ format!("{field}={content}", field=field, content=content)
+ };
+
+ format!(
+ "
+ {cfg}
+ #[link_section = \".modinfo\"]
+ #[used]
+ pub static {variable}: [u8; {length}] = *b\"{string}\\0\";
+ ",
+ cfg = if builtin { "#[cfg(not(MODULE))]" } else { "#[cfg(MODULE)]" },
+ variable = variable,
+ length = string.len() + 1,
+ string = string,
+ )
+}
+
+fn __build_modinfo_string_variable(module: &str, field: &str) -> String {
+ format!("__{module}_{field}", module=module, field=field)
+}
+
+fn build_modinfo_string_only_builtin(module: &str, field: &str, content: &str) -> String {
+ __build_modinfo_string_base(module, field, content, &__build_modinfo_string_variable(module, field), true)
+}
+
+fn build_modinfo_string_only_loadable(module: &str, field: &str, content: &str) -> String {
+ __build_modinfo_string_base(module, field, content, &__build_modinfo_string_variable(module, field), false)
+}
+
+fn build_modinfo_string(module: &str, field: &str, content: &str) -> String {
+ build_modinfo_string_only_builtin(module, field, content)
+ + &build_modinfo_string_only_loadable(module, field, content)
+}
+
+fn build_modinfo_string_param(module: &str, field: &str, param: &str, content: &str) -> String {
+ let variable = format!("__{module}_{field}_{param}", module=module, field=field, param=param);
+ let content = format!("{param}:{content}", param=param, content=content);
+ __build_modinfo_string_base(module, field, &content, &variable, true)
+ + &__build_modinfo_string_base(module, field, &content, &variable, false)
+}
+
+/// Declares a kernel module.
+///
+/// The `type` argument should be a type which implements the [`KernelModule`] trait.
+/// Also accepts various forms of kernel metadata.
+///
+/// Example:
+/// ```rust,no_run
+/// use kernel::prelude::*;
+///
+/// module!{
+/// type: MyKernelModule,
+/// name: b"my_kernel_module",
+/// author: b"Rust for Linux Contributors",
+/// description: b"My very own kernel module!",
+/// license: b"GPL v2",
+/// params: {},
+/// }
+///
+/// struct MyKernelModule;
+///
+/// impl KernelModule for MyKernelModule {
+/// fn init() -> KernelResult<Self> {
+/// Ok(MyKernelModule)
+/// }
+/// }
+/// ```
+#[proc_macro]
+pub fn module(ts: TokenStream) -> TokenStream {
+ let mut it = ts.into_iter();
+
+ let type_ = get_ident(&mut it, "type");
+ let name = get_byte_string(&mut it, "name");
+ let author = get_byte_string(&mut it, "author");
+ let description = get_byte_string(&mut it, "description");
+ let license = get_byte_string(&mut it, "license");
+ let params = get_group(&mut it, "params");
+
+ expect_end(&mut it);
+
+ assert_eq!(params.delimiter(), Delimiter::Brace);
+
+ let mut it = params.stream().into_iter();
+
+ let mut params_modinfo = String::new();
+
+ loop {
+ let param_name = match it.next() {
+ Some(TokenTree::Ident(ident)) => ident.to_string(),
+ Some(_) => panic!("Expected Ident or end"),
+ None => break,
+ };
+
+ assert_eq!(expect_punct(&mut it), ':');
+ let param_type = expect_ident(&mut it);
+ let group = expect_group(&mut it);
+ assert_eq!(expect_punct(&mut it), ',');
+
+ assert_eq!(group.delimiter(), Delimiter::Brace);
+
+ let mut param_it = group.stream().into_iter();
+ let param_default = if param_type == "bool" {
+ get_ident(&mut param_it, "default")
+ } else {
+ get_literal(&mut param_it, "default")
+ };
+ let param_permissions = get_literal(&mut param_it, "permissions");
+ let param_description = get_byte_string(&mut param_it, "description");
+ expect_end(&mut param_it);
+
+ // TODO: more primitive types
+ // TODO: other kinds: arrays, unsafes, etc.
+ let param_kernel_type = match param_type.as_ref() {
+ "bool" => "bool",
+ "i32" => "int",
+ t => panic!("Unrecognized type {}", t),
+ };
+
+ params_modinfo.push_str(&build_modinfo_string_param(&name, "parmtype", &param_name, &param_kernel_type));
+ params_modinfo.push_str(&build_modinfo_string_param(&name, "parm", &param_name, &param_description));
+ params_modinfo.push_str(
+ &format!(
+ "
+ static mut __{name}_{param_name}_value: {param_type} = {param_default};
+
+ struct __{name}_{param_name};
+
+ impl __{name}_{param_name} {{
+ fn read(&self) -> {param_type} {{
+ unsafe {{ __{name}_{param_name}_value }}
+ }}
+ }}
+
+ const {param_name}: __{name}_{param_name} = __{name}_{param_name};
+
+ // Note: the C macro that generates the static structs for the `__param` section
+ // asks for them to be `aligned(sizeof(void *))`. However, that was put in place
+ // in 2003 in commit 38d5b085d2 (\"[PATCH] Fix over-alignment problem on x86-64\")
+ // to undo GCC over-alignment of static structs of >32 bytes. It seems that is
+ // not the case anymore, so we simplify to a transparent representation here
+ // in the expectation that it is not needed anymore.
+ // TODO: revisit this to confirm the above comment and remove it if it happened
+ #[repr(transparent)]
+ struct __{name}_{param_name}_RacyKernelParam(kernel::bindings::kernel_param);
+
+ unsafe impl Sync for __{name}_{param_name}_RacyKernelParam {{
+ }}
+
+ #[cfg(not(MODULE))]
+ const __{name}_{param_name}_name: *const kernel::c_types::c_char = b\"{name}.{param_name}\\0\" as *const _ as *const kernel::c_types::c_char;
+
+ #[cfg(MODULE)]
+ const __{name}_{param_name}_name: *const kernel::c_types::c_char = b\"{param_name}\\0\" as *const _ as *const kernel::c_types::c_char;
+
+ #[link_section = \"__param\"]
+ #[used]
+ static __{name}_{param_name}_struct: __{name}_{param_name}_RacyKernelParam = __{name}_{param_name}_RacyKernelParam(kernel::bindings::kernel_param {{
+ name: __{name}_{param_name}_name,
+ // TODO: `THIS_MODULE`
+ mod_: core::ptr::null_mut(),
+ ops: unsafe {{ &kernel::bindings::param_ops_{param_kernel_type} }} as *const kernel::bindings::kernel_param_ops,
+ perm: {permissions},
+ level: -1,
+ flags: 0,
+ __bindgen_anon_1: kernel::bindings::kernel_param__bindgen_ty_1 {{
+ arg: unsafe {{ &__{name}_{param_name}_value }} as *const _ as *mut kernel::c_types::c_void,
+ }},
+ }});
+ ",
+ name = name,
+ param_type = param_type,
+ param_kernel_type = param_kernel_type,
+ param_default = param_default,
+ param_name = param_name,
+ permissions = param_permissions,
+ )
+ );
+ }
+
+ let file = std::env::var("RUST_MODFILE").unwrap();
+
+ format!(
+ "
+ static mut __MOD: Option<{type_}> = None;
+
+ // Loadable modules need to export the `{{init,cleanup}}_module` identifiers
+ #[cfg(MODULE)]
+ #[no_mangle]
+ pub extern \"C\" fn init_module() -> kernel::c_types::c_int {{
+ __init()
+ }}
+
+ #[cfg(MODULE)]
+ #[no_mangle]
+ pub extern \"C\" fn cleanup_module() {{
+ __exit()
+ }}
+
+ // Built-in modules are initialized through an initcall pointer
+ // and the identifiers need to be unique
+ #[cfg(not(MODULE))]
+ #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
+ #[link_section = \"{initcall_section}\"]
+ #[used]
+ pub static __{name}_initcall: extern \"C\" fn() -> kernel::c_types::c_int = __{name}_init;
+
+ #[cfg(not(MODULE))]
+ #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
+ global_asm!(
+ r#\".section \"{initcall_section}\", \"a\"
+ __{name}_initcall:
+ .long __{name}_init - .
+ .previous
+ \"#
+ );
+
+ #[cfg(not(MODULE))]
+ #[no_mangle]
+ pub extern \"C\" fn __{name}_init() -> kernel::c_types::c_int {{
+ __init()
+ }}
+
+ #[cfg(not(MODULE))]
+ #[no_mangle]
+ pub extern \"C\" fn __{name}_exit() {{
+ __exit()
+ }}
+
+ fn __init() -> kernel::c_types::c_int {{
+ match <{type_} as KernelModule>::init() {{
+ Ok(m) => {{
+ unsafe {{
+ __MOD = Some(m);
+ }}
+ return 0;
+ }}
+ Err(e) => {{
+ return e.to_kernel_errno();
+ }}
+ }}
+ }}
+
+ fn __exit() {{
+ unsafe {{
+ // Invokes `drop()` on `__MOD`, which should be used for cleanup.
+ __MOD = None;
+ }}
+ }}
+
+ {author}
+ {description}
+ {license}
+
+ // Built-in modules also export the `file` modinfo string
+ {file}
+
+ {params_modinfo}
+ ",
+ type_ = type_,
+ name = name,
+ author = &build_modinfo_string(&name, "author", &author),
+ description = &build_modinfo_string(&name, "description", &description),
+ license = &build_modinfo_string(&name, "license", &license),
+ file = &build_modinfo_string_only_builtin(&name, "file", &file),
+ params_modinfo = params_modinfo,
+ initcall_section = ".initcall6.init"
+ ).parse().unwrap()
+}
+
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index a74898ff131a..daf920fa9551 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -237,6 +237,11 @@ rustc_flags = $(_rustc_flags) $(modkern_rustcflags) $(rustc_cfg_flags)
RUSTFLAGS = $(rustc_flags)
export RUSTFLAGS
+# For the `module!` macro
+# TODO: should be `$(modfile)`, but it is not correct for us
+RUST_MODFILE = $(obj)/$(notdir $(obj))
+export RUST_MODFILE
+
cargo_flags = $(_cargo_flags) $(modkern_cargoflags)
a_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \