This introduces Rust abstraction for WMI subsystem via wmi::Driver trait
and module_wmi_driver/wmi_device_table macros. Driver can be probed,
notified on events and removed -- almost everything C-side can do.
This abstraction will be used by subsequent redmi_wmi_rs reference
driver.
Signed-off-by: Gladyshev Ilya <foxido@foxido.dev>
---
MAINTAINERS | 1 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/lib.rs | 2 +
rust/kernel/wmi.rs | 277 ++++++++++++++++++++++++++++++++
4 files changed, 281 insertions(+)
create mode 100644 rust/kernel/wmi.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 765ad2daa218..4909ae8be6e3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -404,6 +404,7 @@ F: Documentation/driver-api/wmi.rst
F: Documentation/wmi/
F: drivers/platform/wmi/
F: include/uapi/linux/wmi.h
+F: rust/kernel/wmi.rs
ACRN HYPERVISOR SERVICE MODULE
M: Fei Li <fei1.li@intel.com>
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a067038b4b42..f9671280c6b5 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -85,6 +85,7 @@
#include <linux/usb.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
+#include <linux/wmi.h>
#include <linux/xarray.h>
#include <trace/events/rust_sample.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index a6eccdba50b5..29bc2b87a103 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -152,6 +152,8 @@
pub mod uaccess;
#[cfg(CONFIG_USB = "y")]
pub mod usb;
+#[cfg(CONFIG_ACPI_WMI)]
+pub mod wmi;
pub mod workqueue;
pub mod xarray;
diff --git a/rust/kernel/wmi.rs b/rust/kernel/wmi.rs
new file mode 100644
index 000000000000..a15258365a2e
--- /dev/null
+++ b/rust/kernel/wmi.rs
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for the WMI devices.
+//!
+//! C header: [`include/linux/wmi.h`](srctree/include/linux/wmi.h).
+
+use crate::{
+ acpi::AcpiObject,
+ device,
+ device_id::{
+ RawDeviceId,
+ RawDeviceIdIndex, //
+ },
+ driver,
+ error::{
+ from_result,
+ to_result,
+ VTABLE_DEFAULT_ERROR, //
+ },
+ prelude::*,
+ types::Opaque, //
+};
+use core::{marker::PhantomData, mem::MaybeUninit, ptr::NonNull};
+use macros::vtable;
+
+/// [`IdTable`](kernel::device_id::IdTable) type for WMI.
+pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>;
+
+/// The WMI driver trait.
+#[vtable]
+pub trait Driver: Send {
+ /// The type holding information about each one of the device ids supported by the driver.
+ type IdInfo: 'static;
+
+ /// The table of device ids supported by the driver.
+ const TABLE: IdTable<Self::IdInfo>;
+
+ /// WMI driver probe.
+ ///
+ /// Called when a new WMI device is bound to this driver.
+ /// Implementers should attempt to initialize the driver here.
+ fn probe(dev: &Device<device::Core>, id_info: &Self::IdInfo) -> impl PinInit<Self, Error>;
+
+ /// WMI device notify.
+ ///
+ /// Called when new WMI event received from bounded device.
+ fn notify(self: Pin<&Self>, _dev: &Device<device::Core>, _event: Option<&AcpiObject>) {
+ build_error!(VTABLE_DEFAULT_ERROR);
+ }
+
+ /// WMI driver remove.
+ fn unbind(self: Pin<&Self>, _dev: &Device<device::Core>) {
+ build_error!(VTABLE_DEFAULT_ERROR);
+ }
+}
+
+/// A WMI device.
+///
+/// This structure represents the Rust abstraction for a C [`struct wmi_device`].
+/// The implementation abstracts the usage of a C [`struct wmi_device`] passed
+/// in from the C side.
+pub struct Device<Cxt: device::DeviceContext = device::Normal> {
+ inner: Opaque<bindings::wmi_device>,
+ _p: PhantomData<Cxt>,
+}
+
+impl<Cxt: device::DeviceContext> Device<Cxt> {
+ fn as_raw(&self) -> *mut bindings::wmi_device {
+ self.inner.get()
+ }
+}
+
+/// An adapter for the registration of WMI drivers.
+pub struct Adapter<T: Driver>(T);
+
+// SAFETY: A call to `unregister` for a given instance of `RegType` is guaranteed to be valid if
+// a preceding call to `register` has been successful.
+unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> {
+ type RegType = bindings::wmi_driver;
+
+ unsafe fn register(
+ wdrv: &Opaque<Self::RegType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ ) -> Result {
+ macro_rules! map_callback {
+ ($flag:ident -> $callback:ident) => {
+ if T::$flag {
+ Some(Self::$callback)
+ } else {
+ None
+ }
+ };
+ }
+
+ // SAFETY: It's safe to set the fields of `struct wmi_driver` on initialization.
+ unsafe {
+ (*wdrv.get()).driver.name = name.as_char_ptr();
+ (*wdrv.get()).driver.probe_type = bindings::probe_type_PROBE_PREFER_ASYNCHRONOUS;
+ (*wdrv.get()).id_table = T::TABLE.as_ptr();
+ (*wdrv.get()).probe = map_callback!(HAS_PROBE -> probe_callback);
+ (*wdrv.get()).notify = map_callback!(HAS_NOTIFY -> notify_callback);
+ (*wdrv.get()).remove = map_callback!(HAS_UNBIND -> remove_callback);
+ (*wdrv.get()).shutdown = None;
+ (*wdrv.get()).no_singleton = true;
+ (*wdrv.get()).no_notify_data = true;
+ }
+
+ // SAFETY: `wdrv` is guaranteed to be a valid `RegType`.
+ to_result(unsafe { bindings::__wmi_driver_register(wdrv.get(), module.0) })
+ }
+
+ unsafe fn unregister(wdrv: &Opaque<Self::RegType>) {
+ // SAFETY: `wdrv` is guaranteed to be a valid `RegType`.
+ unsafe { bindings::wmi_driver_unregister(wdrv.get()) };
+ }
+}
+
+impl<T: Driver + 'static> Adapter<T> {
+ extern "C" fn probe_callback(
+ wdev: *mut bindings::wmi_device,
+ id: *const c_void,
+ ) -> kernel::ffi::c_int {
+ // SAFETY: The WMI core only ever calls the probe callback with a valid pointer to a
+ // `struct wmi_device`.
+ //
+ // INVARIANT: `wdev` is valid for the duration of `probe_callback()`.
+ let wdev = unsafe { &*wdev.cast::<Device<device::CoreInternal>>() };
+
+ let id = id as usize;
+ let info = T::TABLE.info(id);
+
+ from_result(|| {
+ let data = T::probe(wdev, info);
+
+ wdev.as_ref().set_drvdata(data)?;
+ Ok(0)
+ })
+ }
+
+ extern "C" fn notify_callback(
+ wdev: *mut bindings::wmi_device,
+ obj: *mut bindings::acpi_object,
+ ) {
+ // SAFETY: The WMI system only ever calls the notify callback with a valid pointer to a
+ // `struct wmi_device`.
+ let wdev = unsafe { &*wdev.cast::<Device<device::CoreInternal>>() };
+ // SAFETY:
+ // - AcpiObject is repr(transparent) wrapper around FFI object, so it's safe to cast
+ // raw pointer to reference (in terms of alignment and etc),
+ // - Option<&ref> is guaranteed to have same layout as raw pointer (with NULL representing
+ // None) by Rust's "nullable pointer optimization".
+ let obj: Option<&AcpiObject> = unsafe { core::mem::transmute(obj as *const AcpiObject) };
+
+ // SAFETY: `notify_callback` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `T`.
+ let this = unsafe { wdev.as_ref().drvdata_borrow::<T>() };
+ this.notify(wdev, obj);
+ }
+
+ extern "C" fn remove_callback(wdev: *mut bindings::wmi_device) {
+ // SAFETY: The WMI system only ever calls the remove callback with a valid pointer to a
+ // `struct wmi_device`.
+ let wdev = unsafe { &*wdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `remove_callback` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `T`.
+ let this = unsafe { wdev.as_ref().drvdata_borrow::<T>() };
+ this.unbind(wdev);
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
+ fn as_ref(&self) -> &device::Device<Ctx> {
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct platform_device`.
+ let dev = unsafe { &raw mut (*self.inner.get()).dev };
+
+ // SAFETY: `dev` points to a valid `struct device`.
+ unsafe { device::Device::from_raw(dev) }
+ }
+}
+
+kernel::impl_device_context_deref!(unsafe { Device });
+kernel::impl_device_context_into_aref!(Device);
+
+// SAFETY: Instances of `Device` are always reference-counted.
+unsafe impl crate::sync::aref::AlwaysRefCounted for Device {
+ fn inc_ref(&self) {
+ // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
+ unsafe { bindings::get_device(self.as_ref().as_raw()) };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+ unsafe { bindings::put_device(&raw mut (*obj.as_ref().as_raw()).dev) }
+ }
+}
+
+/// Abstraction for the WMI device ID structure, i.e. [`struct wmi_device_id`].
+///
+/// [`struct wmi_device_id`]: https://docs.kernel.org/driver-api/basics.html#c.wmi_device_id
+#[repr(transparent)]
+pub struct DeviceId(bindings::wmi_device_id);
+
+impl DeviceId {
+ /// Constructs new DeviceId from GUID string.
+ pub const fn new(guid: &[u8; bindings::UUID_STRING_LEN as usize]) -> Self {
+ // SAFETY: FFI type is valid to be zero-initialized.
+ let mut inner: bindings::wmi_device_id = unsafe { MaybeUninit::zeroed().assume_init() };
+
+ build_assert!(inner.guid_string.len() == bindings::UUID_STRING_LEN as usize + 1);
+
+ // SAFETY: It's safe to copy UUID_STRING_LEN, because we validated lengths.
+ // Also we leave last byte zeroed, so guid_string is valid C string.
+ unsafe {
+ ::core::ptr::copy_nonoverlapping(
+ guid.as_ptr(),
+ &raw mut inner.guid_string[0],
+ bindings::UUID_STRING_LEN as usize,
+ );
+ }
+
+ Self(inner)
+ }
+}
+
+// SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `wmi_device_id` and does not add
+// additional invariants, so it's safe to transmute to `RawType`.
+unsafe impl RawDeviceId for DeviceId {
+ type RawType = bindings::wmi_device_id;
+}
+
+// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `context` field.
+unsafe impl RawDeviceIdIndex for DeviceId {
+ const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::wmi_device_id, context);
+
+ fn index(&self) -> usize {
+ self.0.context as usize
+ }
+}
+
+/// Declares a kernel module that exposes a single WMI driver.
+///
+/// # Examples
+///
+/// ```ignore
+/// module_wmi_driver! {
+/// type: MyDriver,
+/// name: "Module name",
+/// author: ["Author name"],
+/// description: "Description",
+/// license: "GPL v2",
+/// }
+/// ```
+#[macro_export]
+macro_rules! module_wmi_driver {
+ ($($f:tt)*) => {
+ $crate::module_driver!(<T>, $crate::wmi::Adapter<T>, { $($f)* });
+ }
+}
+
+/// Create a WMI `IdTable` with its alias for modpost.
+#[macro_export]
+macro_rules! wmi_device_table {
+ ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
+ const $table_name: $crate::device_id::IdArray<
+ $crate::wmi::DeviceId,
+ $id_info_type,
+ { $table_data.len() },
+ > = $crate::device_id::IdArray::new($table_data);
+
+ $crate::module_device_table!("wmi", $module_table_name, $table_name);
+ };
+}
--
2.52.0
On Wed, 7 Jan 2026 at 22:56, Gladyshev Ilya <foxido@foxido.dev> wrote:
<snip>
> +impl DeviceId {
> + /// Constructs new DeviceId from GUID string.
> + pub const fn new(guid: &[u8; bindings::UUID_STRING_LEN as usize]) -> Self {
> + // SAFETY: FFI type is valid to be zero-initialized.
> + let mut inner: bindings::wmi_device_id = unsafe { MaybeUninit::zeroed().assume_init() };
> +
> + build_assert!(inner.guid_string.len() == bindings::UUID_STRING_LEN as usize + 1);
> +
> + // SAFETY: It's safe to copy UUID_STRING_LEN, because we validated lengths.
> + // Also we leave last byte zeroed, so guid_string is valid C string.
> + unsafe {
> + ::core::ptr::copy_nonoverlapping(
> + guid.as_ptr(),
> + &raw mut inner.guid_string[0],
> + bindings::UUID_STRING_LEN as usize,
> + );
> + }
Just use while here so no unsafe is needed at all. Then probably patch
1/3 is not needed.
> +
> + Self(inner)
> + }
> +}
On 1/8/26 23:48, Kari Argillander wrote:
> On Wed, 7 Jan 2026 at 22:56, Gladyshev Ilya <foxido@foxido.dev> wrote:
> <snip>
>
>> +impl DeviceId {
>> + /// Constructs new DeviceId from GUID string.
>> + pub const fn new(guid: &[u8; bindings::UUID_STRING_LEN as usize]) -> Self {
>> + // SAFETY: FFI type is valid to be zero-initialized.
>> + let mut inner: bindings::wmi_device_id = unsafe { MaybeUninit::zeroed().assume_init() };
>> +
>> + build_assert!(inner.guid_string.len() == bindings::UUID_STRING_LEN as usize + 1);
>> +
>> + // SAFETY: It's safe to copy UUID_STRING_LEN, because we validated lengths.
>> + // Also we leave last byte zeroed, so guid_string is valid C string.
>> + unsafe {
>> + ::core::ptr::copy_nonoverlapping(
>> + guid.as_ptr(),
>> + &raw mut inner.guid_string[0],
>> + bindings::UUID_STRING_LEN as usize,
>> + );
>> + }
>
> Just use while here so no unsafe is needed at all. Then probably patch
> 1/3 is not needed.
Overall this operation is still unsafe because we are constructing C
string in FFI object. So for me avoiding `unsafe` via less readable
(imo) loop will just mask unsafe operation without any real benefits.
Ideally this function should receive c string and just validate it's
length, but IIRC I had troubles with build-time validation of C string
length
>> +
>> + Self(inner)
>> + }
>> +}
pe 9.1.2026 klo 13.01 Gladyshev Ilya (foxido@foxido.dev) kirjoitti:
>
> On 1/8/26 23:48, Kari Argillander wrote:
> > On Wed, 7 Jan 2026 at 22:56, Gladyshev Ilya <foxido@foxido.dev> wrote:
> > <snip>
> >
> >> +impl DeviceId {
> >> + /// Constructs new DeviceId from GUID string.
> >> + pub const fn new(guid: &[u8; bindings::UUID_STRING_LEN as usize]) -> Self {
> >> + // SAFETY: FFI type is valid to be zero-initialized.
> >> + let mut inner: bindings::wmi_device_id = unsafe { MaybeUninit::zeroed().assume_init() };
> >> +
> >> + build_assert!(inner.guid_string.len() == bindings::UUID_STRING_LEN as usize + 1);
> >> +
> >> + // SAFETY: It's safe to copy UUID_STRING_LEN, because we validated lengths.
> >> + // Also we leave last byte zeroed, so guid_string is valid C string.
> >> + unsafe {
> >> + ::core::ptr::copy_nonoverlapping(
> >> + guid.as_ptr(),
> >> + &raw mut inner.guid_string[0],
> >> + bindings::UUID_STRING_LEN as usize,
> >> + );
> >> + }
> >
> > Just use while here so no unsafe is needed at all. Then probably patch
> > 1/3 is not needed.
>
> Overall this operation is still unsafe because we are constructing C
> string in FFI object. So for me avoiding `unsafe` via less readable
> (imo) loop will just mask unsafe operation without any real benefits.
It is not unsafe if you also use pin_init::zeroed()
let mut inner: bindings::wmi_device_id = pin_init::zeroed();
let mut i = 0usize;
while i < bindings::UUID_STRING_LEN as usize {
inner.guid_string[i] = guid[i];
i += 1;
}
you can then also remove 'core::mem::MaybeUninit'
> Ideally this function should receive c string and just validate it's
> length, but IIRC I had troubles with build-time validation of C string
> length
>
> >> +
> >> + Self(inner)
> >> + }
> >> +}
On 1/9/26 14:15, Kari Argillander wrote:
> pe 9.1.2026 klo 13.01 Gladyshev Ilya (foxido@foxido.dev) kirjoitti:
>>
>> On 1/8/26 23:48, Kari Argillander wrote:
>>> On Wed, 7 Jan 2026 at 22:56, Gladyshev Ilya <foxido@foxido.dev> wrote:
>>> <snip>
>>>
>>>> +impl DeviceId {
>>>> + /// Constructs new DeviceId from GUID string.
>>>> + pub const fn new(guid: &[u8; bindings::UUID_STRING_LEN as usize]) -> Self {
>>>> + // SAFETY: FFI type is valid to be zero-initialized.
>>>> + let mut inner: bindings::wmi_device_id = unsafe { MaybeUninit::zeroed().assume_init() };
>>>> +
>>>> + build_assert!(inner.guid_string.len() == bindings::UUID_STRING_LEN as usize + 1);
>>>> +
>>>> + // SAFETY: It's safe to copy UUID_STRING_LEN, because we validated lengths.
>>>> + // Also we leave last byte zeroed, so guid_string is valid C string.
>>>> + unsafe {
>>>> + ::core::ptr::copy_nonoverlapping(
>>>> + guid.as_ptr(),
>>>> + &raw mut inner.guid_string[0],
>>>> + bindings::UUID_STRING_LEN as usize,
>>>> + );
>>>> + }
>>>
>>> Just use while here so no unsafe is needed at all. Then probably patch
>>> 1/3 is not needed.
>>
>> Overall this operation is still unsafe because we are constructing C
>> string in FFI object. So for me avoiding `unsafe` via less readable
>> (imo) loop will just mask unsafe operation without any real benefits.
>
> It is not unsafe if you also use pin_init::zeroed()
> let mut inner: bindings::wmi_device_id = pin_init::zeroed();
>
> let mut i = 0usize;
> while i < bindings::UUID_STRING_LEN as usize {
> inner.guid_string[i] = guid[i];
> i += 1;
> }
>
> you can then also remove 'core::mem::MaybeUninit'
I was talking more about "constructing C string is still 'unsafe'
because you shouldn't miss the \0 byte". IMO unsafe but primitive memcpy
is more readable and alerting than while loop, but that's not something
I will insist on
I'll play around more with `guid: &CStr` API, probably I missed
something simple)
On Wed, 7 Jan 2026 at 22:56, Gladyshev Ilya <foxido@foxido.dev> wrote:
>
> This introduces Rust abstraction for WMI subsystem via wmi::Driver trait
> and module_wmi_driver/wmi_device_table macros. Driver can be probed,
> notified on events and removed -- almost everything C-side can do.
>
> This abstraction will be used by subsequent redmi_wmi_rs reference
> driver.
>
> Signed-off-by: Gladyshev Ilya <foxido@foxido.dev>
> ---
> MAINTAINERS | 1 +
> rust/bindings/bindings_helper.h | 1 +
> rust/kernel/lib.rs | 2 +
> rust/kernel/wmi.rs | 277 ++++++++++++++++++++++++++++++++
> 4 files changed, 281 insertions(+)
> create mode 100644 rust/kernel/wmi.rs
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 765ad2daa218..4909ae8be6e3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -404,6 +404,7 @@ F: Documentation/driver-api/wmi.rst
> F: Documentation/wmi/
> F: drivers/platform/wmi/
> F: include/uapi/linux/wmi.h
> +F: rust/kernel/wmi.rs
>
> ACRN HYPERVISOR SERVICE MODULE
> M: Fei Li <fei1.li@intel.com>
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index a067038b4b42..f9671280c6b5 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -85,6 +85,7 @@
> #include <linux/usb.h>
> #include <linux/wait.h>
> #include <linux/workqueue.h>
> +#include <linux/wmi.h>
> #include <linux/xarray.h>
> #include <trace/events/rust_sample.h>
>
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index a6eccdba50b5..29bc2b87a103 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -152,6 +152,8 @@
> pub mod uaccess;
> #[cfg(CONFIG_USB = "y")]
> pub mod usb;
> +#[cfg(CONFIG_ACPI_WMI)]
> +pub mod wmi;
> pub mod workqueue;
> pub mod xarray;
>
> diff --git a/rust/kernel/wmi.rs b/rust/kernel/wmi.rs
> new file mode 100644
> index 000000000000..a15258365a2e
> --- /dev/null
> +++ b/rust/kernel/wmi.rs
> @@ -0,0 +1,277 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Abstractions for the WMI devices.
> +//!
> +//! C header: [`include/linux/wmi.h`](srctree/include/linux/wmi.h).
> +
> +use crate::{
> + acpi::AcpiObject,
> + device,
> + device_id::{
> + RawDeviceId,
> + RawDeviceIdIndex, //
> + },
> + driver,
> + error::{
> + from_result,
> + to_result,
> + VTABLE_DEFAULT_ERROR, //
> + },
> + prelude::*,
> + types::Opaque, //
> +};
> +use core::{marker::PhantomData, mem::MaybeUninit, ptr::NonNull};
Also for these use vertical style.
> +use macros::vtable;
> +
> +/// [`IdTable`](kernel::device_id::IdTable) type for WMI.
> +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>;
> +
> +/// The WMI driver trait.
> +#[vtable]
> +pub trait Driver: Send {
> + /// The type holding information about each one of the device ids supported by the driver.
> + type IdInfo: 'static;
> +
> + /// The table of device ids supported by the driver.
> + const TABLE: IdTable<Self::IdInfo>;
> +
> + /// WMI driver probe.
> + ///
> + /// Called when a new WMI device is bound to this driver.
> + /// Implementers should attempt to initialize the driver here.
> + fn probe(dev: &Device<device::Core>, id_info: &Self::IdInfo) -> impl PinInit<Self, Error>;
> +
> + /// WMI device notify.
> + ///
> + /// Called when new WMI event received from bounded device.
> + fn notify(self: Pin<&Self>, _dev: &Device<device::Core>, _event: Option<&AcpiObject>) {
This should be device::Bound
Also probably _ marks are not needed. I think compiler does give
unused build warnings.
I do not know reason but usually other drivers use this over self. And
device first so this
would be:
fn notify(dev: &Device<device::Bound>, this: Pin<&Self>, event:
Option<&AcpiObject>) {
Same also in unbind. But like I said I'm not completely sure about this.
> + build_error!(VTABLE_DEFAULT_ERROR);
> + }
> +
> + /// WMI driver remove.
> + fn unbind(self: Pin<&Self>, _dev: &Device<device::Core>) {
> + build_error!(VTABLE_DEFAULT_ERROR);
> + }
unbind should not be mandatory so here just do
let _ = (self, dev);
Also comment can say little more.
> +}
> +
> +/// A WMI device.
> +///
> +/// This structure represents the Rust abstraction for a C [`struct wmi_device`].
> +/// The implementation abstracts the usage of a C [`struct wmi_device`] passed
> +/// in from the C side.
> +pub struct Device<Cxt: device::DeviceContext = device::Normal> {
> + inner: Opaque<bindings::wmi_device>,
> + _p: PhantomData<Cxt>,
> +}
> +
> +impl<Cxt: device::DeviceContext> Device<Cxt> {
> + fn as_raw(&self) -> *mut bindings::wmi_device {
> + self.inner.get()
> + }
> +}
> +
> +/// An adapter for the registration of WMI drivers.
> +pub struct Adapter<T: Driver>(T);
> +
> +// SAFETY: A call to `unregister` for a given instance of `RegType` is guaranteed to be valid if
> +// a preceding call to `register` has been successful.
> +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> {
> + type RegType = bindings::wmi_driver;
> +
> + unsafe fn register(
> + wdrv: &Opaque<Self::RegType>,
> + name: &'static CStr,
> + module: &'static ThisModule,
> + ) -> Result {
> + macro_rules! map_callback {
> + ($flag:ident -> $callback:ident) => {
> + if T::$flag {
> + Some(Self::$callback)
> + } else {
> + None
> + }
> + };
> + }
> +
> + // SAFETY: It's safe to set the fields of `struct wmi_driver` on initialization.
> + unsafe {
> + (*wdrv.get()).driver.name = name.as_char_ptr();
> + (*wdrv.get()).driver.probe_type = bindings::probe_type_PROBE_PREFER_ASYNCHRONOUS;
> + (*wdrv.get()).id_table = T::TABLE.as_ptr();
> + (*wdrv.get()).probe = map_callback!(HAS_PROBE -> probe_callback);
> + (*wdrv.get()).notify = map_callback!(HAS_NOTIFY -> notify_callback);
> + (*wdrv.get()).remove = map_callback!(HAS_UNBIND -> remove_callback);
> + (*wdrv.get()).shutdown = None;
> + (*wdrv.get()).no_singleton = true;
> + (*wdrv.get()).no_notify_data = true;
> + }
> +
> + // SAFETY: `wdrv` is guaranteed to be a valid `RegType`.
> + to_result(unsafe { bindings::__wmi_driver_register(wdrv.get(), module.0) })
Many drivers use .0 but there is ongoing work that .0 is not used. So
can you use
.as_ptr() here.
> + }
> +
> + unsafe fn unregister(wdrv: &Opaque<Self::RegType>) {
> + // SAFETY: `wdrv` is guaranteed to be a valid `RegType`.
> + unsafe { bindings::wmi_driver_unregister(wdrv.get()) };
> + }
> +}
> +
> +impl<T: Driver + 'static> Adapter<T> {
> + extern "C" fn probe_callback(
> + wdev: *mut bindings::wmi_device,
> + id: *const c_void,
> + ) -> kernel::ffi::c_int {
> + // SAFETY: The WMI core only ever calls the probe callback with a valid pointer to a
> + // `struct wmi_device`.
> + //
> + // INVARIANT: `wdev` is valid for the duration of `probe_callback()`.
> + let wdev = unsafe { &*wdev.cast::<Device<device::CoreInternal>>() };
> +
> + let id = id as usize;
> + let info = T::TABLE.info(id);
> +
> + from_result(|| {
> + let data = T::probe(wdev, info);
> +
> + wdev.as_ref().set_drvdata(data)?;
> + Ok(0)
> + })
> + }
> +
> + extern "C" fn notify_callback(
> + wdev: *mut bindings::wmi_device,
> + obj: *mut bindings::acpi_object,
> + ) {
> + // SAFETY: The WMI system only ever calls the notify callback with a valid pointer to a
> + // `struct wmi_device`.
> + let wdev = unsafe { &*wdev.cast::<Device<device::CoreInternal>>() };
> + // SAFETY:
> + // - AcpiObject is repr(transparent) wrapper around FFI object, so it's safe to cast
> + // raw pointer to reference (in terms of alignment and etc),
> + // - Option<&ref> is guaranteed to have same layout as raw pointer (with NULL representing
> + // None) by Rust's "nullable pointer optimization".
> + let obj: Option<&AcpiObject> = unsafe { core::mem::transmute(obj as *const AcpiObject) };
> +
> + // SAFETY: `notify_callback` is only ever called after a successful call to
> + // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
> + // and stored a `T`.
> + let this = unsafe { wdev.as_ref().drvdata_borrow::<T>() };
> + this.notify(wdev, obj);
> + }
> +
> + extern "C" fn remove_callback(wdev: *mut bindings::wmi_device) {
> + // SAFETY: The WMI system only ever calls the remove callback with a valid pointer to a
> + // `struct wmi_device`.
> + let wdev = unsafe { &*wdev.cast::<Device<device::CoreInternal>>() };
> +
> + // SAFETY: `remove_callback` is only ever called after a successful call to
> + // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
> + // and stored a `T`.
> + let this = unsafe { wdev.as_ref().drvdata_borrow::<T>() };
> + this.unbind(wdev);
> + }
> +}
> +
> +impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
> + fn as_ref(&self) -> &device::Device<Ctx> {
> + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
> + // `struct platform_device`.
> + let dev = unsafe { &raw mut (*self.inner.get()).dev };
> +
> + // SAFETY: `dev` points to a valid `struct device`.
> + unsafe { device::Device::from_raw(dev) }
> + }
> +}
> +
> +kernel::impl_device_context_deref!(unsafe { Device });
Missing safety comment.
> +kernel::impl_device_context_into_aref!(Device);
> +
> +// SAFETY: Instances of `Device` are always reference-counted.
> +unsafe impl crate::sync::aref::AlwaysRefCounted for Device {
> + fn inc_ref(&self) {
> + // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
> + unsafe { bindings::get_device(self.as_ref().as_raw()) };
> + }
> +
> + unsafe fn dec_ref(obj: NonNull<Self>) {
> + // SAFETY: The safety requirements guarantee that the refcount is non-zero.
> + unsafe { bindings::put_device(&raw mut (*obj.as_ref().as_raw()).dev) }
> + }
> +}
> +
> +/// Abstraction for the WMI device ID structure, i.e. [`struct wmi_device_id`].
> +///
> +/// [`struct wmi_device_id`]: https://docs.kernel.org/driver-api/basics.html#c.wmi_device_id
> +#[repr(transparent)]
> +pub struct DeviceId(bindings::wmi_device_id);
> +
> +impl DeviceId {
> + /// Constructs new DeviceId from GUID string.
> + pub const fn new(guid: &[u8; bindings::UUID_STRING_LEN as usize]) -> Self {
> + // SAFETY: FFI type is valid to be zero-initialized.
> + let mut inner: bindings::wmi_device_id = unsafe { MaybeUninit::zeroed().assume_init() };
Use pin_init::zeroed() so no unsafe needed.
> + build_assert!(inner.guid_string.len() == bindings::UUID_STRING_LEN as usize + 1);
> +
> + // SAFETY: It's safe to copy UUID_STRING_LEN, because we validated lengths.
> + // Also we leave last byte zeroed, so guid_string is valid C string.
> + unsafe {
> + ::core::ptr::copy_nonoverlapping(
> + guid.as_ptr(),
> + &raw mut inner.guid_string[0],
> + bindings::UUID_STRING_LEN as usize,
> + );
> + }
> +
> + Self(inner)
> + }
> +}
> +
> +// SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `wmi_device_id` and does not add
> +// additional invariants, so it's safe to transmute to `RawType`.
> +unsafe impl RawDeviceId for DeviceId {
> + type RawType = bindings::wmi_device_id;
> +}
> +
> +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `context` field.
> +unsafe impl RawDeviceIdIndex for DeviceId {
> + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::wmi_device_id, context);
> +
> + fn index(&self) -> usize {
> + self.0.context as usize
> + }
> +}
> +
> +/// Declares a kernel module that exposes a single WMI driver.
> +///
> +/// # Examples
> +///
> +/// ```ignore
> +/// module_wmi_driver! {
> +/// type: MyDriver,
> +/// name: "Module name",
> +/// author: ["Author name"],
> +/// description: "Description",
> +/// license: "GPL v2",
> +/// }
> +/// ```
> +#[macro_export]
> +macro_rules! module_wmi_driver {
> + ($($f:tt)*) => {
> + $crate::module_driver!(<T>, $crate::wmi::Adapter<T>, { $($f)* });
> + }
> +}
> +
> +/// Create a WMI `IdTable` with its alias for modpost.
> +#[macro_export]
> +macro_rules! wmi_device_table {
> + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
> + const $table_name: $crate::device_id::IdArray<
> + $crate::wmi::DeviceId,
> + $id_info_type,
> + { $table_data.len() },
> + > = $crate::device_id::IdArray::new($table_data);
> +
> + $crate::module_device_table!("wmi", $module_table_name, $table_name);
> + };
> +}
> --
> 2.52.0
>
>
On 1/8/26 22:48, Kari Argillander wrote:
> On Wed, 7 Jan 2026 at 22:56, Gladyshev Ilya <foxido@foxido.dev> wrote:
[snip]
>> + /// WMI device notify.
>> + ///
>> + /// Called when new WMI event received from bounded device.
>> + fn notify(self: Pin<&Self>, _dev: &Device<device::Core>, _event: Option<&AcpiObject>) {
>
> This should be device::Bound
>
> Also probably _ marks are not needed. I think compiler does give
> unused build warnings.
>
> I do not know reason but usually other drivers use this over self. And
> device first so this
> would be:
>
> fn notify(dev: &Device<device::Bound>, this: Pin<&Self>, event:
> Option<&AcpiObject>) {
>
> Same also in unbind. But like I said I'm not completely sure about this.
I thought the reason for using this instead of self was because of the
limited set of possible self types in previous versions of Rust...
IMO using Rust's self is more readable
>> + build_error!(VTABLE_DEFAULT_ERROR);
>> + }
>> +
>> + /// WMI driver remove.
>> + fn unbind(self: Pin<&Self>, _dev: &Device<device::Core>) {
>> + build_error!(VTABLE_DEFAULT_ERROR);
>> + }
>
> unbind should not be mandatory so here just do
It's not mandatory, that why there is default implementation. See
https://rust.docs.kernel.org/macros/attr.vtable.html
For other comments: Ack, thanks!
pe 9.1.2026 klo 13.12 Gladyshev Ilya (foxido@foxido.dev) kirjoitti:
>
> On 1/8/26 22:48, Kari Argillander wrote:
> > On Wed, 7 Jan 2026 at 22:56, Gladyshev Ilya <foxido@foxido.dev> wrote:
> [snip]
> >> + /// WMI device notify.
> >> + ///
> >> + /// Called when new WMI event received from bounded device.
> >> + fn notify(self: Pin<&Self>, _dev: &Device<device::Core>, _event: Option<&AcpiObject>) {
> >
> > This should be device::Bound
> >
> > Also probably _ marks are not needed. I think compiler does give
> > unused build warnings.
> >
> > I do not know reason but usually other drivers use this over self. And
> > device first so this
> > would be:
> >
> > fn notify(dev: &Device<device::Bound>, this: Pin<&Self>, event:
> > Option<&AcpiObject>) {
> >
> > Same also in unbind. But like I said I'm not completely sure about this.
>
> I thought the reason for using this instead of self was because of the
> limited set of possible self types in previous versions of Rust...
Nice. Didn't know this. Thanks.
> IMO using Rust's self is more readable
Agreed.
> >> + build_error!(VTABLE_DEFAULT_ERROR);
> >> + }
> >> +
> >> + /// WMI driver remove.
> >> + fn unbind(self: Pin<&Self>, _dev: &Device<device::Core>) {
> >> + build_error!(VTABLE_DEFAULT_ERROR);
> >> + }
> >
> > unbind should not be mandatory so here just do
> It's not mandatory, that why there is default implementation. See
> https://rust.docs.kernel.org/macros/attr.vtable.html
>
>
> For other comments: Ack, thanks!
© 2016 - 2026 Red Hat, Inc.