From f823a225c35eb07e31e026a7bea83090dd460832 Mon Sep 17 00:00:00 2001 From: adamrk Date: Mon, 1 Feb 2021 20:41:44 +0100 Subject: [PATCH 1/6] Fixups for `THIS_MODULE` --- rust/module.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rust/module.rs b/rust/module.rs index 81813904b936cc..1cc72aec7ac3f0 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -282,7 +282,10 @@ pub fn module(ts: TokenStream) -> TokenStream { #[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` + // SAFETY: `__this_module` is constructed by the kernel at load time and will not be freed until the module is unloaded. + #[cfg(MODULE)] + mod_: unsafe {{ &kernel::bindings::__this_module as *const _ as *mut _ }}, + #[cfg(not(MODULE))] mod_: core::ptr::null_mut(), ops: unsafe {{ &kernel::bindings::param_ops_{param_kernel_type} }} as *const kernel::bindings::kernel_param_ops, perm: {permissions}, @@ -309,6 +312,7 @@ pub fn module(ts: TokenStream) -> TokenStream { " static mut __MOD: Option<{type_}> = None; + // SAFETY: `__this_module` is constructed by the kernel at load time and will not be freed until the module is unloaded. #[cfg(MODULE)] static THIS_MODULE: kernel::ThisModule = unsafe {{ kernel::ThisModule::from_ptr(&kernel::bindings::__this_module as *const _ as *mut _) }}; #[cfg(not(MODULE))] From e47fac7ea33a2e8b61d400b34690c7e6613472b4 Mon Sep 17 00:00:00 2001 From: adamrk Date: Mon, 1 Feb 2021 20:43:15 +0100 Subject: [PATCH 2/6] Add charp and some prim params --- .github/workflows/ci.yaml | 15 ++-- .github/workflows/qemu-init.sh | 2 +- drivers/char/rust_example.rs | 18 +++- drivers/char/rust_example_2.rs | 18 +++- drivers/char/rust_example_3.rs | 18 +++- drivers/char/rust_example_4.rs | 18 +++- rust/kernel/c_types.rs | 11 +++ rust/kernel/error.rs | 7 ++ rust/kernel/lib.rs | 24 ++++++ rust/module.rs | 151 +++++++++++++++++++++++++++++---- 10 files changed, 248 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b00cfbcf3b7552..c5b7093d6896b6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -153,7 +153,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_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.my_invbool=y rust_example_2.my_i32=234432' | tee qemu-stdout.log # Check - run: grep -F '] Rust Example (init)' qemu-stdout.log @@ -161,10 +161,15 @@ jobs: - run: grep -F '] [3] Rust Example (init)' qemu-stdout.log - run: grep -F '] [4] Rust Example (init)' qemu-stdout.log - - run: "grep -F '] my_i32: 123321' qemu-stdout.log" - - run: "grep -F '] [2] my_i32: 234432' qemu-stdout.log" - - run: "grep -F '] [3] my_i32: 345543' qemu-stdout.log" - - run: "grep -F '] [4] my_i32: 456654' qemu-stdout.log" + - run: "grep -F '] my_i32: 123321' qemu-stdout.log" + - run: "grep -F '] [2] my_i32: 234432' qemu-stdout.log" + - run: "grep -F '] [3] my_i32: 345543' qemu-stdout.log" + - run: "grep -F '] [4] my_i32: 456654' 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" + - run: "grep '\\] \\[4\\] my_str: default str val\\s*$' qemu-stdout.log" - run: grep -F '] [3] Rust Example (exit)' qemu-stdout.log - run: grep -F '] [4] Rust Example (exit)' qemu-stdout.log diff --git a/.github/workflows/qemu-init.sh b/.github/workflows/qemu-init.sh index 5d623426b2a165..a0d547af61e7bd 100755 --- a/.github/workflows/qemu-init.sh +++ b/.github/workflows/qemu-init.sh @@ -1,6 +1,6 @@ #!/bin/sh -busybox insmod rust_example_3.ko my_i32=345543 +busybox insmod rust_example_3.ko my_i32=345543 my_str=🦀mod busybox insmod rust_example_4.ko my_i32=456654 busybox rmmod rust_example_3.ko busybox rmmod rust_example_4.ko diff --git a/drivers/char/rust_example.rs b/drivers/char/rust_example.rs index 6f484d77f82f8d..8470bda232b1f9 100644 --- a/drivers/char/rust_example.rs +++ b/drivers/char/rust_example.rs @@ -26,6 +26,11 @@ module! { permissions: 0o644, description: b"Example of i32", }, + my_str: str { + default: b"default str val", + permissions: 0o644, + description: b"Example of a string param", + }, }, } @@ -49,9 +54,16 @@ impl KernelModule for RustExample { fn init() -> KernelResult { 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()); + { + let lock = THIS_MODULE.kernel_param_lock(); + println!("Parameters:"); + println!(" my_bool: {}", my_bool.read()); + println!(" my_i32: {}", my_i32.read(&lock)); + println!( + " my_str: {}", + core::str::from_utf8(my_str.read(&lock))? + ); + } // Including this large variable on the stack will trigger // stack probing on the supported archs. diff --git a/drivers/char/rust_example_2.rs b/drivers/char/rust_example_2.rs index caa1925d08bc90..9db5d84f681e0a 100644 --- a/drivers/char/rust_example_2.rs +++ b/drivers/char/rust_example_2.rs @@ -23,6 +23,11 @@ module! { permissions: 0o644, description: b"Example of i32", }, + my_str: str { + default: b"default str val", + permissions: 0o644, + description: b"Example of a string param", + }, }, } @@ -34,9 +39,16 @@ impl KernelModule for RustExample2 { fn init() -> KernelResult { println!("[2] Rust Example (init)"); println!("[2] Am I built-in? {}", !cfg!(MODULE)); - println!("[2] Parameters:"); - println!("[2] my_bool: {}", my_bool.read()); - println!("[2] my_i32: {}", my_i32.read()); + { + let lock = THIS_MODULE.kernel_param_lock(); + println!("[2] Parameters:"); + println!("[2] my_bool: {}", my_bool.read()); + println!("[2] my_i32: {}", my_i32.read(&lock)); + println!( + "[2] my_str: {}", + core::str::from_utf8(my_str.read(&lock))? + ); + } // Including this large variable on the stack will trigger // stack probing on the supported archs. diff --git a/drivers/char/rust_example_3.rs b/drivers/char/rust_example_3.rs index fa7f52ac527917..bc7f44d176f00d 100644 --- a/drivers/char/rust_example_3.rs +++ b/drivers/char/rust_example_3.rs @@ -23,6 +23,11 @@ module! { permissions: 0o644, description: b"Example of i32", }, + my_str: str { + default: b"default str val", + permissions: 0o644, + description: b"Example of a string param", + }, }, } @@ -34,9 +39,16 @@ impl KernelModule for RustExample3 { fn init() -> KernelResult { println!("[3] Rust Example (init)"); println!("[3] Am I built-in? {}", !cfg!(MODULE)); - println!("[3] Parameters:"); - println!("[3] my_bool: {}", my_bool.read()); - println!("[3] my_i32: {}", my_i32.read()); + { + let lock = THIS_MODULE.kernel_param_lock(); + println!("[3] Parameters:"); + println!("[3] my_bool: {}", my_bool.read()); + println!("[3] my_i32: {}", my_i32.read(&lock)); + println!( + "[3] my_str: {}", + core::str::from_utf8(my_str.read(&lock))? + ); + } // Including this large variable on the stack will trigger // stack probing on the supported archs. diff --git a/drivers/char/rust_example_4.rs b/drivers/char/rust_example_4.rs index 99b420eebbac35..2ee356690330e6 100644 --- a/drivers/char/rust_example_4.rs +++ b/drivers/char/rust_example_4.rs @@ -23,6 +23,11 @@ module! { permissions: 0o644, description: b"Example of i32", }, + my_str: str { + default: b"default str val", + permissions: 0o644, + description: b"Example of a string param", + }, }, } @@ -34,9 +39,16 @@ impl KernelModule for RustExample4 { fn init() -> KernelResult { println!("[4] Rust Example (init)"); println!("[4] Am I built-in? {}", !cfg!(MODULE)); - println!("[4] Parameters:"); - println!("[4] my_bool: {}", my_bool.read()); - println!("[4] my_i32: {}", my_i32.read()); + { + let lock = THIS_MODULE.kernel_param_lock(); + println!("[4] Parameters:"); + println!("[4] my_bool: {}", my_bool.read()); + println!("[4] my_i32: {}", my_i32.read(&lock)); + println!( + "[4] my_str: {}", + core::str::from_utf8(my_str.read(&lock))? + ); + } // Including this large variable on the stack will trigger // stack probing on the supported archs. diff --git a/rust/kernel/c_types.rs b/rust/kernel/c_types.rs index 0dd32a99117b3d..2f1ad0c73b864e 100644 --- a/rust/kernel/c_types.rs +++ b/rust/kernel/c_types.rs @@ -51,3 +51,14 @@ mod c { } pub use c::*; + +/// Reads string until null byte is reached and returns slice excluding the terminating null. +/// +/// SAFETY: The data from the pointer until the null terminator must be valid for reads and +/// not mutated for all of `'a`. The length of the string must also be less than `isize::MAX`. +/// See the documentation on [`from_raw_parts`](https://doc.rust-lang.org/core/slice/fn.from_raw_parts.html) +/// for further details on safety of converting a pointer to a slice. +pub unsafe fn c_string_bytes<'a>(ptr: *const crate::c_types::c_char) -> &'a [u8] { + let length = crate::bindings::strlen(ptr) as usize; + &core::slice::from_raw_parts(ptr as *const u8, length) +} diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 5599ab066ac0d4..c9a2d43f69cde4 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 use core::num::TryFromIntError; +use core::str::Utf8Error; use alloc::alloc::AllocError; @@ -31,6 +32,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: Utf8Error) -> Error { + Error::EINVAL + } +} + pub type KernelResult = Result; impl From for Error { diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index f1206fef190d8c..19e9662eabc9ec 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -54,6 +54,30 @@ impl ThisModule { pub const unsafe fn from_ptr(ptr: *mut bindings::module) -> ThisModule { ThisModule(ptr) } + + pub fn kernel_param_lock(&self) -> KParamGuard<'_> { + // SAFETY: `kernel_param_lock` will check if the pointer is null and use the built-in mutex + // in that case. + #[cfg(CONFIG_SYSFS)] + unsafe { bindings::kernel_param_lock(self.0) } + + KParamGuard { this_module: self } + } +} + +/// Scoped lock on the kernel parameters of `ThisModule`. Lock will be released +/// when this struct is dropped. +pub struct KParamGuard<'a> { + this_module: &'a ThisModule +} + +#[cfg(CONFIG_SYSFS)] +impl<'a> Drop for KParamGuard<'a> { + fn drop(&mut self) { + // SAFETY: `kernel_param_lock` will check if the pointer is null and use the built-in mutex + // in that case. The existance of `self` guarantees that the lock is held. + unsafe { bindings::kernel_param_unlock(self.this_module.0) } + } } extern "C" { diff --git a/rust/module.rs b/rust/module.rs index 1cc72aec7ac3f0..0be00be3c7d222 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -155,12 +155,28 @@ fn build_modinfo_string_param(module: &str, field: &str, param: &str, content: & + &__build_modinfo_string_base(module, field, &content, &variable, false) } +fn permissions_are_readonly(perms: &str) -> bool { + let (radix, digits) = if let Some(n) = perms.strip_prefix("0x") { + (16, n) + } else if let Some(n) = perms.strip_prefix("0o") { + (8, n) + } else if let Some(n) = perms.strip_prefix("0b") { + (2, n) + } else { + (10, perms) + }; + match u32::from_str_radix(digits, radix) { + Ok(perms) => perms & 0o222 == 0, + Err(_) => 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: +/// ## Example /// ```rust,no_run /// use kernel::prelude::*; /// @@ -170,17 +186,49 @@ fn build_modinfo_string_param(module: &str, field: &str, param: &str, content: & /// author: b"Rust for Linux Contributors", /// description: b"My very own kernel module!", /// license: b"GPL v2", -/// params: {}, +/// params: { +/// my_i32: i32 { +/// default: 42, +/// permissions: 0o000, +/// description: b"Example of i32", +/// }, +/// writeable_i32: i32 { +/// default: 42, +/// permissions: 0o644, +/// description: b"Example of i32", +/// }, +/// }, /// } /// /// struct MyKernelModule; /// /// impl KernelModule for MyKernelModule { /// fn init() -> KernelResult { +/// // If the parameter is writeable, then the kparam lock must be +/// // taken to read the parameter: +/// { +/// let lock = THIS_MODULE.kernel_param_lock(); +/// println!("i32 param is: {}", writeable_i32.read(&lock)); +/// } +/// // If the parameter is read only, it can be read without locking +/// // the kernel parameters: +/// println!("i32 param is: {}", my_i32.read()); /// Ok(MyKernelModule) /// } /// } /// ``` +/// +/// ## Suported Parameter Types +/// +/// - `bool` - Corresponds to C `bool` param type. +/// - `u8` - Corresponds to C `char` param type. +/// - `i16` - Corresponds to C `short` param type. +/// - `u16` - Corresponds to C `ushort` param type. +/// - `i32` - Corresponds to C `int` param type. +/// - `u32` - Corresponds to C `uint` param type. +/// - `u64` - Corresponds to C `ullong` param type. +/// - `str` - Corresponds to C `charp` param type. +/// Reading the param returns a `&[u8]`. #[proc_macro] pub fn module(ts: TokenStream) -> TokenStream { let mut it = ts.into_iter(); @@ -215,10 +263,10 @@ pub fn module(ts: TokenStream) -> TokenStream { 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_default = match param_type.as_ref() { + "bool" => get_ident(&mut param_it, "default"), + "str" => get_byte_string(&mut param_it, "default"), + _ => 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"); @@ -228,7 +276,13 @@ pub fn module(ts: TokenStream) -> TokenStream { // TODO: other kinds: arrays, unsafes, etc. let param_kernel_type = match param_type.as_ref() { "bool" => "bool", + "u8" => "char", + "i16" => "short", + "u16" => "ushort", "i32" => "int", + "u32" => "uint", + "u64" => "ullong", + "str" => "charp", t => panic!("Unrecognized type {}", t), }; @@ -244,18 +298,83 @@ pub fn module(ts: TokenStream) -> TokenStream { ¶m_name, ¶m_description, )); + let param_type_internal = match param_type.as_ref() { + "str" => "*mut kernel::c_types::c_char", + _ => ¶m_type, + }; + let param_default = match param_type.as_ref() { + "str" => format!("b\"{}\0\" as *const _ as *mut kernel::c_types::c_char", param_default), + _ => param_default, + }; + let read_func = match (param_type.as_ref(), permissions_are_readonly(¶m_permissions)) { + ("str", false) => format!( + " + fn read<'lck>(&self, lock: &'lck kernel::KParamGuard) -> &'lck [u8] {{ + // SAFETY: The pointer is provided either in `param_default` when building the module, + // or by the kernel through `param_set_charp`. Both will be valid C strings. + // Parameters are locked by `KParamGuard`. + unsafe {{ + kernel::c_types::c_string_bytes(__{name}_{param_name}_value) + }} + }} + ", + name = name, + param_name = param_name, + ), + ("str", true) => format!( + " + fn read(&self) -> &[u8] {{ + // SAFETY: The pointer is provided either in `param_default` when building the module, + // or by the kernel through `param_set_charp`. Both will be valid C strings. + // Parameters do not need to be locked because they are read only or sysfs is not enabled. + unsafe {{ + kernel::c_types::c_string_bytes(__{name}_{param_name}_value) + }} + }} + ", + name = name, + param_name = param_name, + ), + (_, false) => format!( + " + // SAFETY: Parameters are locked by `KParamGuard`. + fn read<'lck>(&self, lock: &'lck kernel::KParamGuard) -> &'lck {param_type_internal} {{ + unsafe {{ &__{name}_{param_name}_value }} + }} + ", + name = name, + param_name = param_name, + param_type_internal = param_type_internal, + ), + (_, true) => format!( + " + // SAFETY: Parameters do not need to be locked because they are read only or sysfs is not enabled. + fn read(&self) -> &{param_type_internal} {{ + unsafe {{ &__{name}_{param_name}_value }} + }} + ", + name = name, + param_name = param_name, + param_type_internal = param_type_internal, + ), + }; + let kparam = format!( + " + 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_name = param_name, + ); params_modinfo.push_str( &format!( " - static mut __{name}_{param_name}_value: {param_type} = {param_default}; + static mut __{name}_{param_name}_value: {param_type_internal} = {param_default}; struct __{name}_{param_name}; - impl __{name}_{param_name} {{ - fn read(&self) -> {param_type} {{ - unsafe {{ __{name}_{param_name}_value }} - }} - }} + impl __{name}_{param_name} {{ {read_func} }} const {param_name}: __{name}_{param_name} = __{name}_{param_name}; @@ -291,17 +410,17 @@ pub fn module(ts: TokenStream) -> TokenStream { 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, - }}, + __bindgen_anon_1: {kparam} }}); ", name = name, - param_type = param_type, + param_type_internal = param_type_internal, + read_func = read_func, param_kernel_type = param_kernel_type, param_default = param_default, param_name = param_name, permissions = param_permissions, + kparam = kparam, ) ); } From fc0f0186acbfae49dc3abc4a5b2b462662fc48cf Mon Sep 17 00:00:00 2001 From: Adam Bratschi-Kaye Date: Thu, 4 Feb 2021 20:51:37 +0100 Subject: [PATCH 3/6] comment formatting Co-authored-by: Miguel Ojeda --- rust/kernel/c_types.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rust/kernel/c_types.rs b/rust/kernel/c_types.rs index 2f1ad0c73b864e..bc09affce89efb 100644 --- a/rust/kernel/c_types.rs +++ b/rust/kernel/c_types.rs @@ -54,10 +54,14 @@ pub use c::*; /// Reads string until null byte is reached and returns slice excluding the terminating null. /// -/// SAFETY: The data from the pointer until the null terminator must be valid for reads and -/// not mutated for all of `'a`. The length of the string must also be less than `isize::MAX`. -/// See the documentation on [`from_raw_parts`](https://doc.rust-lang.org/core/slice/fn.from_raw_parts.html) -/// for further details on safety of converting a pointer to a slice. +/// # Safety +/// +/// The data from the pointer until the null terminator must be valid for reads +/// and not mutated for all of `'a`. The length of the string must also be less +/// than `isize::MAX`. See the documentation on [`from_raw_parts`] for further +/// details on safety of converting a pointer to a slice. +/// +/// [`from_raw_parts`]: https://doc.rust-lang.org/core/slice/fn.from_raw_parts.html pub unsafe fn c_string_bytes<'a>(ptr: *const crate::c_types::c_char) -> &'a [u8] { let length = crate::bindings::strlen(ptr) as usize; &core::slice::from_raw_parts(ptr as *const u8, length) From db111fc4f50537abf30e91b372bd1bfecbce99c5 Mon Sep 17 00:00:00 2001 From: Adam Bratschi-Kaye Date: Thu, 4 Feb 2021 20:52:23 +0100 Subject: [PATCH 4/6] doc formatting Co-authored-by: Miguel Ojeda --- rust/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/module.rs b/rust/module.rs index 0be00be3c7d222..3185946171371f 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -176,7 +176,7 @@ fn permissions_are_readonly(perms: &str) -> bool { /// The `type` argument should be a type which implements the [`KernelModule`] trait. /// Also accepts various forms of kernel metadata. /// -/// ## Example +/// # Examples /// ```rust,no_run /// use kernel::prelude::*; /// From 3ead8f0131c549e76a43ebe7fa462f49073581c4 Mon Sep 17 00:00:00 2001 From: Adam Bratschi-Kaye Date: Thu, 4 Feb 2021 20:52:35 +0100 Subject: [PATCH 5/6] doc formatting Co-authored-by: Miguel Ojeda --- rust/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/module.rs b/rust/module.rs index 3185946171371f..ed5a226656f4c0 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -218,7 +218,7 @@ fn permissions_are_readonly(perms: &str) -> bool { /// } /// ``` /// -/// ## Suported Parameter Types +/// # Supported Parameter Types /// /// - `bool` - Corresponds to C `bool` param type. /// - `u8` - Corresponds to C `char` param type. From ab0a0f8e5e2f2e094460adc69e46595b76b127e9 Mon Sep 17 00:00:00 2001 From: Adam Bratschi-Kaye Date: Thu, 4 Feb 2021 20:52:57 +0100 Subject: [PATCH 6/6] invbool note Co-authored-by: Miguel Ojeda --- rust/module.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust/module.rs b/rust/module.rs index ed5a226656f4c0..97f41b7cbde3a5 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -229,6 +229,9 @@ fn permissions_are_readonly(perms: &str) -> bool { /// - `u64` - Corresponds to C `ullong` param type. /// - `str` - Corresponds to C `charp` param type. /// Reading the param returns a `&[u8]`. +/// +/// `invbool` is unsupported: it was only ever used in a few modules. +/// Consider using a `bool` inverting the logic instead. #[proc_macro] pub fn module(ts: TokenStream) -> TokenStream { let mut it = ts.into_iter();