* [PATCH v6 0/2] Initial work for Rust abstraction for HID device driver development
@ 2026-02-22 21:56 Rahul Rameshbabu
2026-02-22 21:56 ` [PATCH v6 1/2] rust: core abstractions for HID drivers Rahul Rameshbabu
2026-02-22 21:56 ` [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver Rahul Rameshbabu
0 siblings, 2 replies; 11+ messages in thread
From: Rahul Rameshbabu @ 2026-02-22 21:56 UTC (permalink / raw)
To: linux-input, linux-kernel, rust-for-linux
Cc: a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires,
benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos,
ojeda, peter.hutterer, tmgross, Rahul Rameshbabu
Hi folks,
It has been a while since I last posted an updated. Had some big life changes
that have kept me from working on this more actively, so I have not been able to
get on top of this till now. Hoping my pace going forward should be a lot
faster. Using the conversation from LPC as well as other refactors needed, I
have rebased the series on top of rust-next. My next steps are to get RazerBlade
controls working with a Rust HID driver.
Link: https://youtu.be/c8JAZm-QinY
Link: https://lore.kernel.org/rust-for-linux/wjfjzjc626n55zvhksiyldobwubr2imbvfavqej333lvnka2wn@r4zfcjqtanvu/
Link: https://lore.kernel.org/rust-for-linux/175810473311.3076338.14309101339951114135.b4-ty@kernel.org/
Thanks,
Rahul Rameshbabu
Rahul Rameshbabu (2):
rust: core abstractions for HID drivers
rust: hid: Glorious PC Gaming Race Model O and O- mice reference
driver
MAINTAINERS | 14 +
drivers/hid/Kconfig | 2 +
drivers/hid/Makefile | 2 +
drivers/hid/hid-glorious.c | 2 +
drivers/hid/hid_glorious_rust.rs | 60 +++
drivers/hid/rust/Kconfig | 28 ++
drivers/hid/rust/Makefile | 6 +
drivers/hid/rust/hid_glorious_rust.rs | 60 +++
rust/bindings/bindings_helper.h | 3 +
rust/kernel/hid.rs | 530 ++++++++++++++++++++++++++
rust/kernel/lib.rs | 2 +
11 files changed, 709 insertions(+)
create mode 100644 drivers/hid/hid_glorious_rust.rs
create mode 100644 drivers/hid/rust/Kconfig
create mode 100644 drivers/hid/rust/Makefile
create mode 100644 drivers/hid/rust/hid_glorious_rust.rs
create mode 100644 rust/kernel/hid.rs
--
2.52.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v6 1/2] rust: core abstractions for HID drivers 2026-02-22 21:56 [PATCH v6 0/2] Initial work for Rust abstraction for HID device driver development Rahul Rameshbabu @ 2026-02-22 21:56 ` Rahul Rameshbabu 2026-02-22 23:39 ` Terry Junge 2026-02-27 15:27 ` Gary Guo 2026-02-22 21:56 ` [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver Rahul Rameshbabu 1 sibling, 2 replies; 11+ messages in thread From: Rahul Rameshbabu @ 2026-02-22 21:56 UTC (permalink / raw) To: linux-input, linux-kernel, rust-for-linux Cc: a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos, ojeda, peter.hutterer, tmgross, Rahul Rameshbabu These abstractions enable the development of HID drivers in Rust by binding with the HID core C API. They provide Rust types that map to the equivalents in C. In this initial draft, only hid_device and hid_device_id are provided direct Rust type equivalents. hid_driver is specially wrapped with a custom Driver type. The module_hid_driver! macro provides analogous functionality to its C equivalent. Only the .report_fixup callback is binded to Rust so far. Future work for these abstractions would include more bindings for common HID-related types, such as hid_field, hid_report_enum, and hid_report as well as more bus callbacks. Providing Rust equivalents to useful core HID functions will also be necessary for HID driver development in Rust. Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com> --- Notes: Changelog: v5->v6: * Converted From<u16> for Group to TryFrom<u16> to properly handle error case * Renamed into method for Group to into_u16 to not conflate with the From trait * Refactored new upstream changes to RegistrationOps * Implemented DriverLayout trait for hid::Adapter<T> v4->v5: * Add rust/ to drivers/hid/Makefile * Implement RawDeviceIdIndex trait v3->v4: * Removed specifying tree in MAINTAINERS file since that is up for debate * Minor rebase cleanup * Moved driver logic under drivers/hid/rust v2->v3: * Implemented AlwaysRefCounted trait using embedded struct device's reference counts instead of the separate reference counter in struct hid_device * Used &raw mut as appropriate * Binded include/linux/device.h for get_device and put_device * Cleaned up various comment related formatting * Minified dev_err! format string * Updated Group enum to be repr(u16) * Implemented From<u16> trait for Group * Added TODO comment when const_trait_impl stabilizes * Made group getter functions return a Group variant instead of a raw number * Made sure example code builds v1->v2: * Binded drivers/hid/hid-ids.h for use in Rust drivers * Remove pre-emptive referencing of a C HID driver instance before it is fully initialized in the driver registration path * Moved static getters to generic Device trait implementation, so they can be used by all device::DeviceContext * Use core macros for supporting DeviceContext transitions * Implemented the AlwaysRefCounted and AsRef traits * Make use for dev_err! as appropriate RFC->v1: * Use Danilo's core infrastructure * Account for HID device groups * Remove probe and remove callbacks * Implement report_fixup support * Properly comment code including SAFETY comments MAINTAINERS | 8 + drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/rust/Kconfig | 12 + rust/bindings/bindings_helper.h | 3 + rust/kernel/hid.rs | 530 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 7 files changed, 559 insertions(+) create mode 100644 drivers/hid/rust/Kconfig create mode 100644 rust/kernel/hid.rs diff --git a/MAINTAINERS b/MAINTAINERS index b8d8a5c41597..1fee14024fa2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11319,6 +11319,14 @@ F: include/uapi/linux/hid* F: samples/hid/ F: tools/testing/selftests/hid/ +HID CORE LAYER [RUST] +M: Rahul Rameshbabu <sergeantsagara@protonmail.com> +R: Benjamin Tissoires <bentiss@kernel.org> +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/rust/*.rs +F: rust/kernel/hid.rs + HID LOGITECH DRIVERS R: Filipe Laíns <lains@riseup.net> L: linux-input@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index c1d9f7c6a5f2..750c2d49a806 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1439,6 +1439,8 @@ endmenu source "drivers/hid/bpf/Kconfig" +source "drivers/hid/rust/Kconfig" + source "drivers/hid/i2c-hid/Kconfig" source "drivers/hid/intel-ish-hid/Kconfig" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index e01838239ae6..b78ab84c47b4 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -8,6 +8,8 @@ hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o obj-$(CONFIG_HID_BPF) += bpf/ +obj-$(CONFIG_RUST_HID_ABSTRACTIONS) += rust/ + obj-$(CONFIG_HID) += hid.o obj-$(CONFIG_UHID) += uhid.o diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig new file mode 100644 index 000000000000..d3247651829e --- /dev/null +++ b/drivers/hid/rust/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "Rust HID support" + +config RUST_HID_ABSTRACTIONS + bool "Rust HID abstractions support" + depends on RUST + depends on HID=y + help + Adds support needed for HID drivers written in Rust. It provides a + wrapper around the C hid core. + +endmenu # Rust HID support diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 083cc44aa952..200e58af27a3 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -48,6 +48,7 @@ #include <linux/cpumask.h> #include <linux/cred.h> #include <linux/debugfs.h> +#include <linux/device.h> #include <linux/device/faux.h> #include <linux/dma-direction.h> #include <linux/dma-mapping.h> @@ -60,6 +61,8 @@ #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/io-pgtable.h> +#include <linux/hid.h> +#include "../../drivers/hid/hid-ids.h" #include <linux/ioport.h> #include <linux/jiffies.h> #include <linux/jump_label.h> diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs new file mode 100644 index 000000000000..b9db542d923a --- /dev/null +++ b/rust/kernel/hid.rs @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com> + +//! Abstractions for the HID interface. +//! +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h) + +use crate::{ + device, + device_id::{ + RawDeviceId, + RawDeviceIdIndex, // + }, + driver, + error::*, + prelude::*, + types::Opaque, // +}; +use core::{ + marker::PhantomData, + ptr::{ + addr_of_mut, + NonNull, // + } // +}; + +/// Indicates the item is static read-only. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_CONSTANT: u8 = bindings::HID_MAIN_ITEM_CONSTANT as u8; + +/// Indicates the item represents data from a physical control. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_VARIABLE: u8 = bindings::HID_MAIN_ITEM_VARIABLE as u8; + +/// Indicates the item should be treated as a relative change from the previous +/// report. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_RELATIVE: u8 = bindings::HID_MAIN_ITEM_RELATIVE as u8; + +/// Indicates the item should wrap around when reaching the extreme high or +/// extreme low values. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_WRAP: u8 = bindings::HID_MAIN_ITEM_WRAP as u8; + +/// Indicates the item should wrap around when reaching the extreme high or +/// extreme low values. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_NONLINEAR: u8 = bindings::HID_MAIN_ITEM_NONLINEAR as u8; + +/// Indicates whether the control has a preferred state it will physically +/// return to without user intervention. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_NO_PREFERRED: u8 = bindings::HID_MAIN_ITEM_NO_PREFERRED as u8; + +/// Indicates whether the control has a physical state where it will not send +/// any reports. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_NULL_STATE: u8 = bindings::HID_MAIN_ITEM_NULL_STATE as u8; + +/// Indicates whether the control requires host system logic to change state. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_VOLATILE: u8 = bindings::HID_MAIN_ITEM_VOLATILE as u8; + +/// Indicates whether the item is fixed size or a variable buffer of bytes. +/// +/// Refer to [Device Class Definition for HID 1.11] +/// Section 6.2.2.5 Input, Output, and Feature Items. +/// +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf +pub const MAIN_ITEM_BUFFERED_BYTE: u8 = bindings::HID_MAIN_ITEM_BUFFERED_BYTE as u8; + +/// HID device groups are intended to help categories HID devices based on a set +/// of common quirks and logic that they will require to function correctly. +#[repr(u16)] +pub enum Group { + /// Used to match a device against any group when probing. + Any = bindings::HID_GROUP_ANY as u16, + + /// Indicates a generic device that should need no custom logic from the + /// core HID stack. + Generic = bindings::HID_GROUP_GENERIC as u16, + + /// Maps multitouch devices to hid-multitouch instead of hid-generic. + Multitouch = bindings::HID_GROUP_MULTITOUCH as u16, + + /// Used for autodetecing and mapping of HID sensor hubs to + /// hid-sensor-hub. + SensorHub = bindings::HID_GROUP_SENSOR_HUB as u16, + + /// Used for autodetecing and mapping Win 8 multitouch devices to set the + /// needed quirks. + MultitouchWin8 = bindings::HID_GROUP_MULTITOUCH_WIN_8 as u16, + + // Vendor-specific device groups. + /// Used to distinguish Synpatics touchscreens from other products. The + /// touchscreens will be handled by hid-multitouch instead, while everything + /// else will be managed by hid-rmi. + RMI = bindings::HID_GROUP_RMI as u16, + + /// Used for hid-core handling to automatically identify Wacom devices and + /// have them probed by hid-wacom. + Wacom = bindings::HID_GROUP_WACOM as u16, + + /// Used by logitech-djreceiver and logitech-djdevice to autodetect if + /// devices paied to the DJ receivers are DJ devices and handle them with + /// the device driver. + LogitechDJDevice = bindings::HID_GROUP_LOGITECH_DJ_DEVICE as u16, + + /// Since the Valve Steam Controller only has vendor-specific usages, + /// prevent hid-generic from parsing its reports since there would be + /// nothing hid-generic could do for the device. + Steam = bindings::HID_GROUP_STEAM as u16, + + /// Used to differentiate 27 Mhz frequency Logitech DJ devices from other + /// Logitech DJ devices. + Logitech27MHzDevice = bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u16, + + /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi. + Vivaldi = bindings::HID_GROUP_VIVALDI as u16, +} + +// TODO: use `const_trait_impl` once stabilized: +// +// ``` +// impl const From<Group> for u16 { +// /// [`Group`] variants are represented by [`u16`] values. +// fn from(value: Group) -> Self { +// value as Self +// } +// } +// ``` +impl Group { + /// Internal function used to convert [`Group`] variants into [`u16`]. + const fn into_u16(self) -> u16 { + self as u16 + } +} + +impl TryFrom<u16> for Group { + type Error = &'static str; + + /// [`u16`] values can be safely converted to [`Group`] variants. + fn try_from(value: u16) -> Result<Group, Self::Error> { + match value.into() { + bindings::HID_GROUP_GENERIC => Ok(Group::Generic), + bindings::HID_GROUP_MULTITOUCH => Ok(Group::Multitouch), + bindings::HID_GROUP_SENSOR_HUB => Ok(Group::SensorHub), + bindings::HID_GROUP_MULTITOUCH_WIN_8 => Ok(Group::MultitouchWin8), + bindings::HID_GROUP_RMI => Ok(Group::RMI), + bindings::HID_GROUP_WACOM => Ok(Group::Wacom), + bindings::HID_GROUP_LOGITECH_DJ_DEVICE => Ok(Group::LogitechDJDevice), + bindings::HID_GROUP_STEAM => Ok(Group::Steam), + bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE => Ok(Group::Logitech27MHzDevice), + bindings::HID_GROUP_VIVALDI => Ok(Group::Vivaldi), + _ => Err("Unknown HID group encountered!"), + } + } +} + +/// The HID device representation. +/// +/// This structure represents the Rust abstraction for a C `struct hid_device`. +/// The implementation abstracts the usage of an already existing C `struct +/// hid_device` within Rust code that we get passed from the C side. +/// +/// # Invariants +/// +/// A [`Device`] instance represents a valid `struct hid_device` created by the +/// C portion of the kernel. +#[repr(transparent)] +pub struct Device<Ctx: device::DeviceContext = device::Normal>( + Opaque<bindings::hid_device>, + PhantomData<Ctx>, +); + +impl<Ctx: device::DeviceContext> Device<Ctx> { + fn as_raw(&self) -> *mut bindings::hid_device { + self.0.get() + } + + /// Returns the HID transport bus ID. + pub fn bus(&self) -> u16 { + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` + unsafe { *self.as_raw() }.bus + } + + /// Returns the HID report group. + pub fn group(&self) -> Result<Group, &'static str> { + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` + unsafe { *self.as_raw() }.group.try_into() + } + + /// Returns the HID vendor ID. + pub fn vendor(&self) -> u32 { + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` + unsafe { *self.as_raw() }.vendor + } + + /// Returns the HID product ID. + pub fn product(&self) -> u32 { + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` + unsafe { *self.as_raw() }.product + } +} + +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic +// argument. +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::types::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(&raw mut (*self.as_raw()).dev) }; + } + + 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.cast::<bindings::hid_device>().as_ptr()).dev) } + } +} + +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 hid_device`. + let dev = unsafe { addr_of_mut!((*self.as_raw()).dev) }; + + // SAFETY: `dev` points to a valid `struct device`. + unsafe { device::Device::from_raw(dev) } + } +} + +/// Abstraction for the HID device ID structure `struct hid_device_id`. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct DeviceId(bindings::hid_device_id); + +impl DeviceId { + /// Equivalent to C's `HID_USB_DEVICE` macro. + /// + /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID + /// number. + pub const fn new_usb(group: Group, vendor: u32, product: u32) -> Self { + Self(bindings::hid_device_id { + bus: 0x3, // BUS_USB + group: group.into_u16(), + vendor, + product, + driver_data: 0, + }) + } + + /// Returns the HID transport bus ID. + pub fn bus(&self) -> u16 { + self.0.bus + } + + /// Returns the HID report group. + pub fn group(&self) -> Result<Group, &'static str> { + self.0.group.try_into() + } + + /// Returns the HID vendor ID. + pub fn vendor(&self) -> u32 { + self.0.vendor + } + + /// Returns the HID product ID. + pub fn product(&self) -> u32 { + self.0.product + } +} + +// SAFETY: +// * `DeviceId` is a `#[repr(transparent)` wrapper of `hid_device_id` and does not add +// additional invariants, so it's safe to transmute to `RawType`. +// * `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. +unsafe impl RawDeviceId for DeviceId { + type RawType = bindings::hid_device_id; +} + +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. +unsafe impl RawDeviceIdIndex for DeviceId { + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::hid_device_id, driver_data); + + fn index(&self) -> usize { + self.0.driver_data + } +} + +/// [`IdTable`] type for HID. +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>; + +/// Create a HID [`IdTable`] with its alias for modpost. +#[macro_export] +macro_rules! hid_device_table { + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { + const $table_name: $crate::device_id::IdArray< + $crate::hid::DeviceId, + $id_info_type, + { $table_data.len() }, + > = $crate::device_id::IdArray::new($table_data); + + $crate::module_device_table!("hid", $module_table_name, $table_name); + }; +} + +/// The HID driver trait. +/// +/// # Examples +/// +/// ``` +/// use kernel::{bindings, device, hid}; +/// +/// struct MyDriver; +/// +/// kernel::hid_device_table!( +/// HID_TABLE, +/// MODULE_HID_TABLE, +/// <MyDriver as hid::Driver>::IdInfo, +/// [( +/// hid::DeviceId::new_usb( +/// hid::Group::Steam, +/// bindings::USB_VENDOR_ID_VALVE, +/// bindings::USB_DEVICE_ID_STEAM_DECK, +/// ), +/// (), +/// )] +/// ); +/// +/// #[vtable] +/// impl hid::Driver for MyDriver { +/// type IdInfo = (); +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; +/// +/// /// This function is optional to implement. +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { +/// // Perform some report descriptor fixup. +/// rdesc +/// } +/// } +/// ``` +/// Drivers must implement this trait in order to get a HID driver registered. +/// Please refer to the `Adapter` documentation for an example. +#[vtable] +pub trait Driver: Send { + /// The type holding information about each device id supported by the driver. + // TODO: Use `associated_type_defaults` once stabilized: + // + // ``` + // type IdInfo: 'static = (); + // ``` + type IdInfo: 'static; + + /// The table of device ids supported by the driver. + const ID_TABLE: IdTable<Self::IdInfo>; + + /// Called before report descriptor parsing. Can be used to mutate the + /// report descriptor before the core HID logic processes the descriptor. + /// Useful for problematic report descriptors that prevent HID devices from + /// functioning correctly. + /// + /// Optional to implement. + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] { + build_error!(VTABLE_DEFAULT_ERROR) + } +} + +/// An adapter for the registration of HID drivers. +pub struct Adapter<T: Driver>(T); + +// SAFETY: +// - `bindings::hid_driver` is a C type declared as `repr(C)`. +// - `T` is the type of the driver's device private data. +// - `struct hid_driver` embeds a `struct device_driver`. +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`. +unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> { + type DriverType = bindings::hid_driver; + type DriverData = T; + const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver); +} + +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if +// a preceding call to `register` has been successful. +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> { + unsafe fn register( + hdrv: &Opaque<Self::DriverType>, + name: &'static CStr, + module: &'static ThisModule, + ) -> Result { + // SAFETY: It's safe to set the fields of `struct hid_driver` on initialization. + unsafe { + (*hdrv.get()).name = name.as_char_ptr(); + (*hdrv.get()).id_table = T::ID_TABLE.as_ptr(); + (*hdrv.get()).report_fixup = if T::HAS_REPORT_FIXUP { + Some(Self::report_fixup_callback) + } else { + None + }; + } + + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` + to_result(unsafe { + bindings::__hid_register_driver(hdrv.get(), module.0, name.as_char_ptr()) + }) + } + + unsafe fn unregister(hdrv: &Opaque<Self::DriverType>) { + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` + unsafe { bindings::hid_unregister_driver(hdrv.get()) } + } +} + +impl<T: Driver + 'static> Adapter<T> { + extern "C" fn report_fixup_callback( + hdev: *mut bindings::hid_device, + buf: *mut u8, + size: *mut kernel::ffi::c_uint, + ) -> *const u8 { + // SAFETY: The HID subsystem only ever calls the report_fixup callback + // with a valid pointer to a `struct hid_device`. + // + // INVARIANT: `hdev` is valid for the duration of + // `report_fixup_callback()`. + let hdev = unsafe { &*hdev.cast::<Device<device::Core>>() }; + + // SAFETY: The HID subsystem only ever calls the report_fixup callback + // with a valid pointer to a `kernel::ffi::c_uint`. + // + // INVARIANT: `size` is valid for the duration of + // `report_fixup_callback()`. + let buf_len: usize = match unsafe { *size }.try_into() { + Ok(len) => len, + Err(e) => { + dev_err!( + hdev.as_ref(), + "Cannot fix report description due to {:?}!\n", + e + ); + + return buf; + } + }; + + // Build a mutable Rust slice from `buf` and `size`. + // + // SAFETY: The HID subsystem only ever calls the `report_fixup callback` + // with a valid pointer to a `u8` buffer. + // + // INVARIANT: `buf` is valid for the duration of + // `report_fixup_callback()`. + let rdesc_slice = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; + let rdesc_slice = T::report_fixup(hdev, rdesc_slice); + + match rdesc_slice.len().try_into() { + // SAFETY: The HID subsystem only ever calls the report_fixup + // callback with a valid pointer to a `kernel::ffi::c_uint`. + // + // INVARIANT: `size` is valid for the duration of + // `report_fixup_callback()`. + Ok(len) => unsafe { *size = len }, + Err(e) => { + dev_err!( + hdev.as_ref(), + "Fixed report description will not be used due to {:?}!\n", + e + ); + + return buf; + } + } + + rdesc_slice.as_ptr() + } +} + +/// Declares a kernel module that exposes a single HID driver. +/// +/// # Examples +/// +/// ```ignore +/// kernel::module_hid_driver! { +/// type: MyDriver, +/// name: "Module name", +/// authors: ["Author name"], +/// description: "Description", +/// license: "GPL", +/// } +/// ``` +#[macro_export] +macro_rules! module_hid_driver { + ($($f:tt)*) => { + $crate::module_driver!(<T>, $crate::hid::Adapter<T>, { $($f)* }); + }; +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 3da92f18f4ee..e2dcacd9369e 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -102,6 +102,8 @@ pub mod id_pool; #[doc(hidden)] pub mod impl_flags; +#[cfg(CONFIG_RUST_HID_ABSTRACTIONS)] +pub mod hid; pub mod init; pub mod io; pub mod ioctl; -- 2.52.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers 2026-02-22 21:56 ` [PATCH v6 1/2] rust: core abstractions for HID drivers Rahul Rameshbabu @ 2026-02-22 23:39 ` Terry Junge 2026-02-23 2:47 ` Rahul Rameshbabu 2026-02-27 15:27 ` Gary Guo 1 sibling, 1 reply; 11+ messages in thread From: Terry Junge @ 2026-02-22 23:39 UTC (permalink / raw) To: Rahul Rameshbabu, linux-input, linux-kernel, rust-for-linux Cc: a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos, ojeda, peter.hutterer, tmgross On 2/22/26 1:56 PM, Rahul Rameshbabu wrote: > These abstractions enable the development of HID drivers in Rust by binding > with the HID core C API. They provide Rust types that map to the > equivalents in C. In this initial draft, only hid_device and hid_device_id > are provided direct Rust type equivalents. hid_driver is specially wrapped > with a custom Driver type. The module_hid_driver! macro provides analogous > functionality to its C equivalent. Only the .report_fixup callback is > binded to Rust so far. > > Future work for these abstractions would include more bindings for common > HID-related types, such as hid_field, hid_report_enum, and hid_report as > well as more bus callbacks. Providing Rust equivalents to useful core HID > functions will also be necessary for HID driver development in Rust. > > Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com> > --- > > Notes: > Changelog: > > v5->v6: > * Converted From<u16> for Group to TryFrom<u16> to properly handle > error case > * Renamed into method for Group to into_u16 to not conflate with the > From trait > * Refactored new upstream changes to RegistrationOps > * Implemented DriverLayout trait for hid::Adapter<T> > v4->v5: > * Add rust/ to drivers/hid/Makefile > * Implement RawDeviceIdIndex trait > v3->v4: > * Removed specifying tree in MAINTAINERS file since that is up for > debate > * Minor rebase cleanup > * Moved driver logic under drivers/hid/rust > v2->v3: > * Implemented AlwaysRefCounted trait using embedded struct device's > reference counts instead of the separate reference counter in struct > hid_device > * Used &raw mut as appropriate > * Binded include/linux/device.h for get_device and put_device > * Cleaned up various comment related formatting > * Minified dev_err! format string > * Updated Group enum to be repr(u16) > * Implemented From<u16> trait for Group > * Added TODO comment when const_trait_impl stabilizes > * Made group getter functions return a Group variant instead of a raw > number > * Made sure example code builds > v1->v2: > * Binded drivers/hid/hid-ids.h for use in Rust drivers > * Remove pre-emptive referencing of a C HID driver instance before > it is fully initialized in the driver registration path > * Moved static getters to generic Device trait implementation, so > they can be used by all device::DeviceContext > * Use core macros for supporting DeviceContext transitions > * Implemented the AlwaysRefCounted and AsRef traits > * Make use for dev_err! as appropriate > RFC->v1: > * Use Danilo's core infrastructure > * Account for HID device groups > * Remove probe and remove callbacks > * Implement report_fixup support > * Properly comment code including SAFETY comments > > MAINTAINERS | 8 + > drivers/hid/Kconfig | 2 + > drivers/hid/Makefile | 2 + > drivers/hid/rust/Kconfig | 12 + > rust/bindings/bindings_helper.h | 3 + > rust/kernel/hid.rs | 530 ++++++++++++++++++++++++++++++++ > rust/kernel/lib.rs | 2 + > 7 files changed, 559 insertions(+) > create mode 100644 drivers/hid/rust/Kconfig > create mode 100644 rust/kernel/hid.rs > > diff --git a/MAINTAINERS b/MAINTAINERS > index b8d8a5c41597..1fee14024fa2 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -11319,6 +11319,14 @@ F: include/uapi/linux/hid* > F: samples/hid/ > F: tools/testing/selftests/hid/ > > +HID CORE LAYER [RUST] > +M: Rahul Rameshbabu <sergeantsagara@protonmail.com> > +R: Benjamin Tissoires <bentiss@kernel.org> > +L: linux-input@vger.kernel.org > +S: Maintained > +F: drivers/hid/rust/*.rs > +F: rust/kernel/hid.rs > + > HID LOGITECH DRIVERS > R: Filipe Laíns <lains@riseup.net> > L: linux-input@vger.kernel.org > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig > index c1d9f7c6a5f2..750c2d49a806 100644 > --- a/drivers/hid/Kconfig > +++ b/drivers/hid/Kconfig > @@ -1439,6 +1439,8 @@ endmenu > > source "drivers/hid/bpf/Kconfig" > > +source "drivers/hid/rust/Kconfig" > + > source "drivers/hid/i2c-hid/Kconfig" > > source "drivers/hid/intel-ish-hid/Kconfig" > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile > index e01838239ae6..b78ab84c47b4 100644 > --- a/drivers/hid/Makefile > +++ b/drivers/hid/Makefile > @@ -8,6 +8,8 @@ hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o > > obj-$(CONFIG_HID_BPF) += bpf/ > > +obj-$(CONFIG_RUST_HID_ABSTRACTIONS) += rust/ > + > obj-$(CONFIG_HID) += hid.o > obj-$(CONFIG_UHID) += uhid.o > > diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig > new file mode 100644 > index 000000000000..d3247651829e > --- /dev/null > +++ b/drivers/hid/rust/Kconfig > @@ -0,0 +1,12 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +menu "Rust HID support" > + > +config RUST_HID_ABSTRACTIONS > + bool "Rust HID abstractions support" > + depends on RUST > + depends on HID=y > + help > + Adds support needed for HID drivers written in Rust. It provides a > + wrapper around the C hid core. > + > +endmenu # Rust HID support > diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h > index 083cc44aa952..200e58af27a3 100644 > --- a/rust/bindings/bindings_helper.h > +++ b/rust/bindings/bindings_helper.h > @@ -48,6 +48,7 @@ > #include <linux/cpumask.h> > #include <linux/cred.h> > #include <linux/debugfs.h> > +#include <linux/device.h> > #include <linux/device/faux.h> > #include <linux/dma-direction.h> > #include <linux/dma-mapping.h> > @@ -60,6 +61,8 @@ > #include <linux/i2c.h> > #include <linux/interrupt.h> > #include <linux/io-pgtable.h> > +#include <linux/hid.h> > +#include "../../drivers/hid/hid-ids.h" > #include <linux/ioport.h> > #include <linux/jiffies.h> > #include <linux/jump_label.h> > diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs > new file mode 100644 > index 000000000000..b9db542d923a > --- /dev/null > +++ b/rust/kernel/hid.rs > @@ -0,0 +1,530 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com> > + > +//! Abstractions for the HID interface. > +//! > +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h) > + > +use crate::{ > + device, > + device_id::{ > + RawDeviceId, > + RawDeviceIdIndex, // > + }, > + driver, > + error::*, > + prelude::*, > + types::Opaque, // > +}; > +use core::{ > + marker::PhantomData, > + ptr::{ > + addr_of_mut, > + NonNull, // > + } // > +}; > + > +/// Indicates the item is static read-only. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_CONSTANT: u8 = bindings::HID_MAIN_ITEM_CONSTANT as u8; > + > +/// Indicates the item represents data from a physical control. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_VARIABLE: u8 = bindings::HID_MAIN_ITEM_VARIABLE as u8; > + > +/// Indicates the item should be treated as a relative change from the previous > +/// report. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_RELATIVE: u8 = bindings::HID_MAIN_ITEM_RELATIVE as u8; > + > +/// Indicates the item should wrap around when reaching the extreme high or > +/// extreme low values. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_WRAP: u8 = bindings::HID_MAIN_ITEM_WRAP as u8; > + > +/// Indicates the item should wrap around when reaching the extreme high or > +/// extreme low values. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_NONLINEAR: u8 = bindings::HID_MAIN_ITEM_NONLINEAR as u8; > + > +/// Indicates whether the control has a preferred state it will physically > +/// return to without user intervention. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_NO_PREFERRED: u8 = bindings::HID_MAIN_ITEM_NO_PREFERRED as u8; > + > +/// Indicates whether the control has a physical state where it will not send > +/// any reports. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_NULL_STATE: u8 = bindings::HID_MAIN_ITEM_NULL_STATE as u8; > + > +/// Indicates whether the control requires host system logic to change state. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_VOLATILE: u8 = bindings::HID_MAIN_ITEM_VOLATILE as u8; > + > +/// Indicates whether the item is fixed size or a variable buffer of bytes. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_BUFFERED_BYTE: u8 = bindings::HID_MAIN_ITEM_BUFFERED_BYTE as u8; > HID_MAIN_ITEM_BUFFERED_BYTE has a value of 0x100 which will not fit in a u8. Maybe all the MAIN_ITEM_* bits should bound as u16? HID 1.11 actually defines them as a u32 with bits 9-31 reserved for future use. Thanks Terry + > +/// HID device groups are intended to help categories HID devices based on a set > +/// of common quirks and logic that they will require to function correctly. > +#[repr(u16)] > +pub enum Group { > + /// Used to match a device against any group when probing. > + Any = bindings::HID_GROUP_ANY as u16, > + > + /// Indicates a generic device that should need no custom logic from the > + /// core HID stack. > + Generic = bindings::HID_GROUP_GENERIC as u16, > + > + /// Maps multitouch devices to hid-multitouch instead of hid-generic. > + Multitouch = bindings::HID_GROUP_MULTITOUCH as u16, > + > + /// Used for autodetecing and mapping of HID sensor hubs to > + /// hid-sensor-hub. > + SensorHub = bindings::HID_GROUP_SENSOR_HUB as u16, > + > + /// Used for autodetecing and mapping Win 8 multitouch devices to set the > + /// needed quirks. > + MultitouchWin8 = bindings::HID_GROUP_MULTITOUCH_WIN_8 as u16, > + > + // Vendor-specific device groups. > + /// Used to distinguish Synpatics touchscreens from other products. The > + /// touchscreens will be handled by hid-multitouch instead, while everything > + /// else will be managed by hid-rmi. > + RMI = bindings::HID_GROUP_RMI as u16, > + > + /// Used for hid-core handling to automatically identify Wacom devices and > + /// have them probed by hid-wacom. > + Wacom = bindings::HID_GROUP_WACOM as u16, > + > + /// Used by logitech-djreceiver and logitech-djdevice to autodetect if > + /// devices paied to the DJ receivers are DJ devices and handle them with > + /// the device driver. > + LogitechDJDevice = bindings::HID_GROUP_LOGITECH_DJ_DEVICE as u16, > + > + /// Since the Valve Steam Controller only has vendor-specific usages, > + /// prevent hid-generic from parsing its reports since there would be > + /// nothing hid-generic could do for the device. > + Steam = bindings::HID_GROUP_STEAM as u16, > + > + /// Used to differentiate 27 Mhz frequency Logitech DJ devices from other > + /// Logitech DJ devices. > + Logitech27MHzDevice = bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u16, > + > + /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi. > + Vivaldi = bindings::HID_GROUP_VIVALDI as u16, > +} > + > +// TODO: use `const_trait_impl` once stabilized: > +// > +// ``` > +// impl const From<Group> for u16 { > +// /// [`Group`] variants are represented by [`u16`] values. > +// fn from(value: Group) -> Self { > +// value as Self > +// } > +// } > +// ``` > +impl Group { > + /// Internal function used to convert [`Group`] variants into [`u16`]. > + const fn into_u16(self) -> u16 { > + self as u16 > + } > +} > + > +impl TryFrom<u16> for Group { > + type Error = &'static str; > + > + /// [`u16`] values can be safely converted to [`Group`] variants. > + fn try_from(value: u16) -> Result<Group, Self::Error> { > + match value.into() { > + bindings::HID_GROUP_GENERIC => Ok(Group::Generic), > + bindings::HID_GROUP_MULTITOUCH => Ok(Group::Multitouch), > + bindings::HID_GROUP_SENSOR_HUB => Ok(Group::SensorHub), > + bindings::HID_GROUP_MULTITOUCH_WIN_8 => Ok(Group::MultitouchWin8), > + bindings::HID_GROUP_RMI => Ok(Group::RMI), > + bindings::HID_GROUP_WACOM => Ok(Group::Wacom), > + bindings::HID_GROUP_LOGITECH_DJ_DEVICE => Ok(Group::LogitechDJDevice), > + bindings::HID_GROUP_STEAM => Ok(Group::Steam), > + bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE => Ok(Group::Logitech27MHzDevice), > + bindings::HID_GROUP_VIVALDI => Ok(Group::Vivaldi), > + _ => Err("Unknown HID group encountered!"), > + } > + } > +} > + > +/// The HID device representation. > +/// > +/// This structure represents the Rust abstraction for a C `struct hid_device`. > +/// The implementation abstracts the usage of an already existing C `struct > +/// hid_device` within Rust code that we get passed from the C side. > +/// > +/// # Invariants > +/// > +/// A [`Device`] instance represents a valid `struct hid_device` created by the > +/// C portion of the kernel. > +#[repr(transparent)] > +pub struct Device<Ctx: device::DeviceContext = device::Normal>( > + Opaque<bindings::hid_device>, > + PhantomData<Ctx>, > +); > + > +impl<Ctx: device::DeviceContext> Device<Ctx> { > + fn as_raw(&self) -> *mut bindings::hid_device { > + self.0.get() > + } > + > + /// Returns the HID transport bus ID. > + pub fn bus(&self) -> u16 { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.bus > + } > + > + /// Returns the HID report group. > + pub fn group(&self) -> Result<Group, &'static str> { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.group.try_into() > + } > + > + /// Returns the HID vendor ID. > + pub fn vendor(&self) -> u32 { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.vendor > + } > + > + /// Returns the HID product ID. > + pub fn product(&self) -> u32 { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.product > + } > +} > + > +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic > +// argument. > +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::types::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(&raw mut (*self.as_raw()).dev) }; > + } > + > + 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.cast::<bindings::hid_device>().as_ptr()).dev) } > + } > +} > + > +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 hid_device`. > + let dev = unsafe { addr_of_mut!((*self.as_raw()).dev) }; > + > + // SAFETY: `dev` points to a valid `struct device`. > + unsafe { device::Device::from_raw(dev) } > + } > +} > + > +/// Abstraction for the HID device ID structure `struct hid_device_id`. > +#[repr(transparent)] > +#[derive(Clone, Copy)] > +pub struct DeviceId(bindings::hid_device_id); > + > +impl DeviceId { > + /// Equivalent to C's `HID_USB_DEVICE` macro. > + /// > + /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID > + /// number. > + pub const fn new_usb(group: Group, vendor: u32, product: u32) -> Self { > + Self(bindings::hid_device_id { > + bus: 0x3, // BUS_USB > + group: group.into_u16(), > + vendor, > + product, > + driver_data: 0, > + }) > + } > + > + /// Returns the HID transport bus ID. > + pub fn bus(&self) -> u16 { > + self.0.bus > + } > + > + /// Returns the HID report group. > + pub fn group(&self) -> Result<Group, &'static str> { > + self.0.group.try_into() > + } > + > + /// Returns the HID vendor ID. > + pub fn vendor(&self) -> u32 { > + self.0.vendor > + } > + > + /// Returns the HID product ID. > + pub fn product(&self) -> u32 { > + self.0.product > + } > +} > + > +// SAFETY: > +// * `DeviceId` is a `#[repr(transparent)` wrapper of `hid_device_id` and does not add > +// additional invariants, so it's safe to transmute to `RawType`. > +// * `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. > +unsafe impl RawDeviceId for DeviceId { > + type RawType = bindings::hid_device_id; > +} > + > +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. > +unsafe impl RawDeviceIdIndex for DeviceId { > + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::hid_device_id, driver_data); > + > + fn index(&self) -> usize { > + self.0.driver_data > + } > +} > + > +/// [`IdTable`] type for HID. > +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>; > + > +/// Create a HID [`IdTable`] with its alias for modpost. > +#[macro_export] > +macro_rules! hid_device_table { > + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { > + const $table_name: $crate::device_id::IdArray< > + $crate::hid::DeviceId, > + $id_info_type, > + { $table_data.len() }, > + > = $crate::device_id::IdArray::new($table_data); > + > + $crate::module_device_table!("hid", $module_table_name, $table_name); > + }; > +} > + > +/// The HID driver trait. > +/// > +/// # Examples > +/// > +/// ``` > +/// use kernel::{bindings, device, hid}; > +/// > +/// struct MyDriver; > +/// > +/// kernel::hid_device_table!( > +/// HID_TABLE, > +/// MODULE_HID_TABLE, > +/// <MyDriver as hid::Driver>::IdInfo, > +/// [( > +/// hid::DeviceId::new_usb( > +/// hid::Group::Steam, > +/// bindings::USB_VENDOR_ID_VALVE, > +/// bindings::USB_DEVICE_ID_STEAM_DECK, > +/// ), > +/// (), > +/// )] > +/// ); > +/// > +/// #[vtable] > +/// impl hid::Driver for MyDriver { > +/// type IdInfo = (); > +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; > +/// > +/// /// This function is optional to implement. > +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { > +/// // Perform some report descriptor fixup. > +/// rdesc > +/// } > +/// } > +/// ``` > +/// Drivers must implement this trait in order to get a HID driver registered. > +/// Please refer to the `Adapter` documentation for an example. > +#[vtable] > +pub trait Driver: Send { > + /// The type holding information about each device id supported by the driver. > + // TODO: Use `associated_type_defaults` once stabilized: > + // > + // ``` > + // type IdInfo: 'static = (); > + // ``` > + type IdInfo: 'static; > + > + /// The table of device ids supported by the driver. > + const ID_TABLE: IdTable<Self::IdInfo>; > + > + /// Called before report descriptor parsing. Can be used to mutate the > + /// report descriptor before the core HID logic processes the descriptor. > + /// Useful for problematic report descriptors that prevent HID devices from > + /// functioning correctly. > + /// > + /// Optional to implement. > + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] { > + build_error!(VTABLE_DEFAULT_ERROR) > + } > +} > + > +/// An adapter for the registration of HID drivers. > +pub struct Adapter<T: Driver>(T); > + > +// SAFETY: > +// - `bindings::hid_driver` is a C type declared as `repr(C)`. > +// - `T` is the type of the driver's device private data. > +// - `struct hid_driver` embeds a `struct device_driver`. > +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`. > +unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> { > + type DriverType = bindings::hid_driver; > + type DriverData = T; > + const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver); > +} > + > +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if > +// a preceding call to `register` has been successful. > +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> { > + unsafe fn register( > + hdrv: &Opaque<Self::DriverType>, > + name: &'static CStr, > + module: &'static ThisModule, > + ) -> Result { > + // SAFETY: It's safe to set the fields of `struct hid_driver` on initialization. > + unsafe { > + (*hdrv.get()).name = name.as_char_ptr(); > + (*hdrv.get()).id_table = T::ID_TABLE.as_ptr(); > + (*hdrv.get()).report_fixup = if T::HAS_REPORT_FIXUP { > + Some(Self::report_fixup_callback) > + } else { > + None > + }; > + } > + > + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` > + to_result(unsafe { > + bindings::__hid_register_driver(hdrv.get(), module.0, name.as_char_ptr()) > + }) > + } > + > + unsafe fn unregister(hdrv: &Opaque<Self::DriverType>) { > + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` > + unsafe { bindings::hid_unregister_driver(hdrv.get()) } > + } > +} > + > +impl<T: Driver + 'static> Adapter<T> { > + extern "C" fn report_fixup_callback( > + hdev: *mut bindings::hid_device, > + buf: *mut u8, > + size: *mut kernel::ffi::c_uint, > + ) -> *const u8 { > + // SAFETY: The HID subsystem only ever calls the report_fixup callback > + // with a valid pointer to a `struct hid_device`. > + // > + // INVARIANT: `hdev` is valid for the duration of > + // `report_fixup_callback()`. > + let hdev = unsafe { &*hdev.cast::<Device<device::Core>>() }; > + > + // SAFETY: The HID subsystem only ever calls the report_fixup callback > + // with a valid pointer to a `kernel::ffi::c_uint`. > + // > + // INVARIANT: `size` is valid for the duration of > + // `report_fixup_callback()`. > + let buf_len: usize = match unsafe { *size }.try_into() { > + Ok(len) => len, > + Err(e) => { > + dev_err!( > + hdev.as_ref(), > + "Cannot fix report description due to {:?}!\n", > + e > + ); > + > + return buf; > + } > + }; > + > + // Build a mutable Rust slice from `buf` and `size`. > + // > + // SAFETY: The HID subsystem only ever calls the `report_fixup callback` > + // with a valid pointer to a `u8` buffer. > + // > + // INVARIANT: `buf` is valid for the duration of > + // `report_fixup_callback()`. > + let rdesc_slice = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; > + let rdesc_slice = T::report_fixup(hdev, rdesc_slice); > + > + match rdesc_slice.len().try_into() { > + // SAFETY: The HID subsystem only ever calls the report_fixup > + // callback with a valid pointer to a `kernel::ffi::c_uint`. > + // > + // INVARIANT: `size` is valid for the duration of > + // `report_fixup_callback()`. > + Ok(len) => unsafe { *size = len }, > + Err(e) => { > + dev_err!( > + hdev.as_ref(), > + "Fixed report description will not be used due to {:?}!\n", > + e > + ); > + > + return buf; > + } > + } > + > + rdesc_slice.as_ptr() > + } > +} > + > +/// Declares a kernel module that exposes a single HID driver. > +/// > +/// # Examples > +/// > +/// ```ignore > +/// kernel::module_hid_driver! { > +/// type: MyDriver, > +/// name: "Module name", > +/// authors: ["Author name"], > +/// description: "Description", > +/// license: "GPL", > +/// } > +/// ``` > +#[macro_export] > +macro_rules! module_hid_driver { > + ($($f:tt)*) => { > + $crate::module_driver!(<T>, $crate::hid::Adapter<T>, { $($f)* }); > + }; > +} > diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs > index 3da92f18f4ee..e2dcacd9369e 100644 > --- a/rust/kernel/lib.rs > +++ b/rust/kernel/lib.rs > @@ -102,6 +102,8 @@ > pub mod id_pool; > #[doc(hidden)] > pub mod impl_flags; > +#[cfg(CONFIG_RUST_HID_ABSTRACTIONS)] > +pub mod hid; > pub mod init; > pub mod io; > pub mod ioctl; ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers 2026-02-22 23:39 ` Terry Junge @ 2026-02-23 2:47 ` Rahul Rameshbabu 0 siblings, 0 replies; 11+ messages in thread From: Rahul Rameshbabu @ 2026-02-23 2:47 UTC (permalink / raw) To: Terry Junge Cc: linux-input, linux-kernel, rust-for-linux, a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos, ojeda, peter.hutterer, tmgross On Sun, 22 Feb, 2026 15:39:45 -0800 "Terry Junge" <linuxhid@cosmicgizmosystems.com> wrote: > On 2/22/26 1:56 PM, Rahul Rameshbabu wrote: >> These abstractions enable the development of HID drivers in Rust by binding >> with the HID core C API. They provide Rust types that map to the >> equivalents in C. In this initial draft, only hid_device and hid_device_id >> are provided direct Rust type equivalents. hid_driver is specially wrapped >> with a custom Driver type. The module_hid_driver! macro provides analogous >> functionality to its C equivalent. Only the .report_fixup callback is >> binded to Rust so far. >> >> Future work for these abstractions would include more bindings for common >> HID-related types, such as hid_field, hid_report_enum, and hid_report as >> well as more bus callbacks. Providing Rust equivalents to useful core HID >> functions will also be necessary for HID driver development in Rust. >> >> Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com> >> --- >> >> Notes: >> Changelog: >> >> v5->v6: >> * Converted From<u16> for Group to TryFrom<u16> to properly handle >> error case >> * Renamed into method for Group to into_u16 to not conflate with the >> From trait >> * Refactored new upstream changes to RegistrationOps >> * Implemented DriverLayout trait for hid::Adapter<T> >> v4->v5: >> * Add rust/ to drivers/hid/Makefile >> * Implement RawDeviceIdIndex trait >> v3->v4: >> * Removed specifying tree in MAINTAINERS file since that is up for >> debate >> * Minor rebase cleanup >> * Moved driver logic under drivers/hid/rust >> v2->v3: >> * Implemented AlwaysRefCounted trait using embedded struct device's >> reference counts instead of the separate reference counter in struct >> hid_device >> * Used &raw mut as appropriate >> * Binded include/linux/device.h for get_device and put_device >> * Cleaned up various comment related formatting >> * Minified dev_err! format string >> * Updated Group enum to be repr(u16) >> * Implemented From<u16> trait for Group >> * Added TODO comment when const_trait_impl stabilizes >> * Made group getter functions return a Group variant instead of a raw >> number >> * Made sure example code builds >> v1->v2: >> * Binded drivers/hid/hid-ids.h for use in Rust drivers >> * Remove pre-emptive referencing of a C HID driver instance before >> it is fully initialized in the driver registration path >> * Moved static getters to generic Device trait implementation, so >> they can be used by all device::DeviceContext >> * Use core macros for supporting DeviceContext transitions >> * Implemented the AlwaysRefCounted and AsRef traits >> * Make use for dev_err! as appropriate >> RFC->v1: >> * Use Danilo's core infrastructure >> * Account for HID device groups >> * Remove probe and remove callbacks >> * Implement report_fixup support >> * Properly comment code including SAFETY comments >> >> MAINTAINERS | 8 + >> drivers/hid/Kconfig | 2 + >> drivers/hid/Makefile | 2 + >> drivers/hid/rust/Kconfig | 12 + >> rust/bindings/bindings_helper.h | 3 + >> rust/kernel/hid.rs | 530 ++++++++++++++++++++++++++++++++ >> rust/kernel/lib.rs | 2 + >> 7 files changed, 559 insertions(+) >> create mode 100644 drivers/hid/rust/Kconfig >> create mode 100644 rust/kernel/hid.rs >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index b8d8a5c41597..1fee14024fa2 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -11319,6 +11319,14 @@ F: include/uapi/linux/hid* >> F: samples/hid/ >> F: tools/testing/selftests/hid/ >> >> +HID CORE LAYER [RUST] >> +M: Rahul Rameshbabu <sergeantsagara@protonmail.com> >> +R: Benjamin Tissoires <bentiss@kernel.org> >> +L: linux-input@vger.kernel.org >> +S: Maintained >> +F: drivers/hid/rust/*.rs >> +F: rust/kernel/hid.rs >> + >> HID LOGITECH DRIVERS >> R: Filipe Laíns <lains@riseup.net> >> L: linux-input@vger.kernel.org >> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig >> index c1d9f7c6a5f2..750c2d49a806 100644 >> --- a/drivers/hid/Kconfig >> +++ b/drivers/hid/Kconfig >> @@ -1439,6 +1439,8 @@ endmenu >> >> source "drivers/hid/bpf/Kconfig" >> >> +source "drivers/hid/rust/Kconfig" >> + >> source "drivers/hid/i2c-hid/Kconfig" >> >> source "drivers/hid/intel-ish-hid/Kconfig" >> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile >> index e01838239ae6..b78ab84c47b4 100644 >> --- a/drivers/hid/Makefile >> +++ b/drivers/hid/Makefile >> @@ -8,6 +8,8 @@ hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o >> >> obj-$(CONFIG_HID_BPF) += bpf/ >> >> +obj-$(CONFIG_RUST_HID_ABSTRACTIONS) += rust/ >> + >> obj-$(CONFIG_HID) += hid.o >> obj-$(CONFIG_UHID) += uhid.o >> >> diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig >> new file mode 100644 >> index 000000000000..d3247651829e >> --- /dev/null >> +++ b/drivers/hid/rust/Kconfig >> @@ -0,0 +1,12 @@ >> +# SPDX-License-Identifier: GPL-2.0-only >> +menu "Rust HID support" >> + >> +config RUST_HID_ABSTRACTIONS >> + bool "Rust HID abstractions support" >> + depends on RUST >> + depends on HID=y >> + help >> + Adds support needed for HID drivers written in Rust. It provides a >> + wrapper around the C hid core. >> + >> +endmenu # Rust HID support >> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h >> index 083cc44aa952..200e58af27a3 100644 >> --- a/rust/bindings/bindings_helper.h >> +++ b/rust/bindings/bindings_helper.h >> @@ -48,6 +48,7 @@ >> #include <linux/cpumask.h> >> #include <linux/cred.h> >> #include <linux/debugfs.h> >> +#include <linux/device.h> >> #include <linux/device/faux.h> >> #include <linux/dma-direction.h> >> #include <linux/dma-mapping.h> >> @@ -60,6 +61,8 @@ >> #include <linux/i2c.h> >> #include <linux/interrupt.h> >> #include <linux/io-pgtable.h> >> +#include <linux/hid.h> >> +#include "../../drivers/hid/hid-ids.h" >> #include <linux/ioport.h> >> #include <linux/jiffies.h> >> #include <linux/jump_label.h> >> diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs >> new file mode 100644 >> index 000000000000..b9db542d923a >> --- /dev/null >> +++ b/rust/kernel/hid.rs >> @@ -0,0 +1,530 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com> >> + >> +//! Abstractions for the HID interface. >> +//! >> +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h) >> + >> +use crate::{ >> + device, >> + device_id::{ >> + RawDeviceId, >> + RawDeviceIdIndex, // >> + }, >> + driver, >> + error::*, >> + prelude::*, >> + types::Opaque, // >> +}; >> +use core::{ >> + marker::PhantomData, >> + ptr::{ >> + addr_of_mut, >> + NonNull, // >> + } // >> +}; >> + >> +/// Indicates the item is static read-only. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_CONSTANT: u8 = bindings::HID_MAIN_ITEM_CONSTANT as u8; >> + >> +/// Indicates the item represents data from a physical control. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_VARIABLE: u8 = bindings::HID_MAIN_ITEM_VARIABLE as u8; >> + >> +/// Indicates the item should be treated as a relative change from the previous >> +/// report. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_RELATIVE: u8 = bindings::HID_MAIN_ITEM_RELATIVE as u8; >> + >> +/// Indicates the item should wrap around when reaching the extreme high or >> +/// extreme low values. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_WRAP: u8 = bindings::HID_MAIN_ITEM_WRAP as u8; >> + >> +/// Indicates the item should wrap around when reaching the extreme high or >> +/// extreme low values. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_NONLINEAR: u8 = bindings::HID_MAIN_ITEM_NONLINEAR as u8; >> + >> +/// Indicates whether the control has a preferred state it will physically >> +/// return to without user intervention. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_NO_PREFERRED: u8 = bindings::HID_MAIN_ITEM_NO_PREFERRED as u8; >> + >> +/// Indicates whether the control has a physical state where it will not send >> +/// any reports. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_NULL_STATE: u8 = bindings::HID_MAIN_ITEM_NULL_STATE as u8; >> + >> +/// Indicates whether the control requires host system logic to change state. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_VOLATILE: u8 = bindings::HID_MAIN_ITEM_VOLATILE as u8; >> + >> +/// Indicates whether the item is fixed size or a variable buffer of bytes. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_BUFFERED_BYTE: u8 = bindings::HID_MAIN_ITEM_BUFFERED_BYTE as u8; >> > > HID_MAIN_ITEM_BUFFERED_BYTE has a value of 0x100 which will not fit in a u8. > Maybe all the MAIN_ITEM_* bits should bound as u16? > > HID 1.11 actually defines them as a u32 with bits 9-31 reserved for future use. Thanks Terry for pointing this out. I will give some time for other reviewers to comment but will make the needed change for a v7. Thanks, Rahul Rameshbabu > > Thanks > Terry > > + >> +/// HID device groups are intended to help categories HID devices based on a set >> +/// of common quirks and logic that they will require to function correctly. >> +#[repr(u16)] >> +pub enum Group { >> + /// Used to match a device against any group when probing. >> + Any = bindings::HID_GROUP_ANY as u16, >> + >> + /// Indicates a generic device that should need no custom logic from the >> + /// core HID stack. >> + Generic = bindings::HID_GROUP_GENERIC as u16, >> + >> + /// Maps multitouch devices to hid-multitouch instead of hid-generic. >> + Multitouch = bindings::HID_GROUP_MULTITOUCH as u16, >> + >> + /// Used for autodetecing and mapping of HID sensor hubs to >> + /// hid-sensor-hub. >> + SensorHub = bindings::HID_GROUP_SENSOR_HUB as u16, >> + >> + /// Used for autodetecing and mapping Win 8 multitouch devices to set the >> + /// needed quirks. >> + MultitouchWin8 = bindings::HID_GROUP_MULTITOUCH_WIN_8 as u16, >> + >> + // Vendor-specific device groups. >> + /// Used to distinguish Synpatics touchscreens from other products. The >> + /// touchscreens will be handled by hid-multitouch instead, while everything >> + /// else will be managed by hid-rmi. >> + RMI = bindings::HID_GROUP_RMI as u16, >> + >> + /// Used for hid-core handling to automatically identify Wacom devices and >> + /// have them probed by hid-wacom. >> + Wacom = bindings::HID_GROUP_WACOM as u16, >> + >> + /// Used by logitech-djreceiver and logitech-djdevice to autodetect if >> + /// devices paied to the DJ receivers are DJ devices and handle them with >> + /// the device driver. >> + LogitechDJDevice = bindings::HID_GROUP_LOGITECH_DJ_DEVICE as u16, >> + >> + /// Since the Valve Steam Controller only has vendor-specific usages, >> + /// prevent hid-generic from parsing its reports since there would be >> + /// nothing hid-generic could do for the device. >> + Steam = bindings::HID_GROUP_STEAM as u16, >> + >> + /// Used to differentiate 27 Mhz frequency Logitech DJ devices from other >> + /// Logitech DJ devices. >> + Logitech27MHzDevice = bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u16, >> + >> + /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi. >> + Vivaldi = bindings::HID_GROUP_VIVALDI as u16, >> +} >> + >> +// TODO: use `const_trait_impl` once stabilized: >> +// >> +// ``` >> +// impl const From<Group> for u16 { >> +// /// [`Group`] variants are represented by [`u16`] values. >> +// fn from(value: Group) -> Self { >> +// value as Self >> +// } >> +// } >> +// ``` >> +impl Group { >> + /// Internal function used to convert [`Group`] variants into [`u16`]. >> + const fn into_u16(self) -> u16 { >> + self as u16 >> + } >> +} >> + >> +impl TryFrom<u16> for Group { >> + type Error = &'static str; >> + >> + /// [`u16`] values can be safely converted to [`Group`] variants. >> + fn try_from(value: u16) -> Result<Group, Self::Error> { >> + match value.into() { >> + bindings::HID_GROUP_GENERIC => Ok(Group::Generic), >> + bindings::HID_GROUP_MULTITOUCH => Ok(Group::Multitouch), >> + bindings::HID_GROUP_SENSOR_HUB => Ok(Group::SensorHub), >> + bindings::HID_GROUP_MULTITOUCH_WIN_8 => Ok(Group::MultitouchWin8), >> + bindings::HID_GROUP_RMI => Ok(Group::RMI), >> + bindings::HID_GROUP_WACOM => Ok(Group::Wacom), >> + bindings::HID_GROUP_LOGITECH_DJ_DEVICE => Ok(Group::LogitechDJDevice), >> + bindings::HID_GROUP_STEAM => Ok(Group::Steam), >> + bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE => Ok(Group::Logitech27MHzDevice), >> + bindings::HID_GROUP_VIVALDI => Ok(Group::Vivaldi), >> + _ => Err("Unknown HID group encountered!"), >> + } >> + } >> +} >> + >> +/// The HID device representation. >> +/// >> +/// This structure represents the Rust abstraction for a C `struct hid_device`. >> +/// The implementation abstracts the usage of an already existing C `struct >> +/// hid_device` within Rust code that we get passed from the C side. >> +/// >> +/// # Invariants >> +/// >> +/// A [`Device`] instance represents a valid `struct hid_device` created by the >> +/// C portion of the kernel. >> +#[repr(transparent)] >> +pub struct Device<Ctx: device::DeviceContext = device::Normal>( >> + Opaque<bindings::hid_device>, >> + PhantomData<Ctx>, >> +); >> + >> +impl<Ctx: device::DeviceContext> Device<Ctx> { >> + fn as_raw(&self) -> *mut bindings::hid_device { >> + self.0.get() >> + } >> + >> + /// Returns the HID transport bus ID. >> + pub fn bus(&self) -> u16 { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.bus >> + } >> + >> + /// Returns the HID report group. >> + pub fn group(&self) -> Result<Group, &'static str> { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.group.try_into() >> + } >> + >> + /// Returns the HID vendor ID. >> + pub fn vendor(&self) -> u32 { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.vendor >> + } >> + >> + /// Returns the HID product ID. >> + pub fn product(&self) -> u32 { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.product >> + } >> +} >> + >> +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic >> +// argument. >> +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::types::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(&raw mut (*self.as_raw()).dev) }; >> + } >> + >> + 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.cast::<bindings::hid_device>().as_ptr()).dev) } >> + } >> +} >> + >> +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 hid_device`. >> + let dev = unsafe { addr_of_mut!((*self.as_raw()).dev) }; >> + >> + // SAFETY: `dev` points to a valid `struct device`. >> + unsafe { device::Device::from_raw(dev) } >> + } >> +} >> + >> +/// Abstraction for the HID device ID structure `struct hid_device_id`. >> +#[repr(transparent)] >> +#[derive(Clone, Copy)] >> +pub struct DeviceId(bindings::hid_device_id); >> + >> +impl DeviceId { >> + /// Equivalent to C's `HID_USB_DEVICE` macro. >> + /// >> + /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID >> + /// number. >> + pub const fn new_usb(group: Group, vendor: u32, product: u32) -> Self { >> + Self(bindings::hid_device_id { >> + bus: 0x3, // BUS_USB >> + group: group.into_u16(), >> + vendor, >> + product, >> + driver_data: 0, >> + }) >> + } >> + >> + /// Returns the HID transport bus ID. >> + pub fn bus(&self) -> u16 { >> + self.0.bus >> + } >> + >> + /// Returns the HID report group. >> + pub fn group(&self) -> Result<Group, &'static str> { >> + self.0.group.try_into() >> + } >> + >> + /// Returns the HID vendor ID. >> + pub fn vendor(&self) -> u32 { >> + self.0.vendor >> + } >> + >> + /// Returns the HID product ID. >> + pub fn product(&self) -> u32 { >> + self.0.product >> + } >> +} >> + >> +// SAFETY: >> +// * `DeviceId` is a `#[repr(transparent)` wrapper of `hid_device_id` and does not add >> +// additional invariants, so it's safe to transmute to `RawType`. >> +// * `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. >> +unsafe impl RawDeviceId for DeviceId { >> + type RawType = bindings::hid_device_id; >> +} >> + >> +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. >> +unsafe impl RawDeviceIdIndex for DeviceId { >> + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::hid_device_id, driver_data); >> + >> + fn index(&self) -> usize { >> + self.0.driver_data >> + } >> +} >> + >> +/// [`IdTable`] type for HID. >> +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>; >> + >> +/// Create a HID [`IdTable`] with its alias for modpost. >> +#[macro_export] >> +macro_rules! hid_device_table { >> + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { >> + const $table_name: $crate::device_id::IdArray< >> + $crate::hid::DeviceId, >> + $id_info_type, >> + { $table_data.len() }, >> + > = $crate::device_id::IdArray::new($table_data); >> + >> + $crate::module_device_table!("hid", $module_table_name, $table_name); >> + }; >> +} >> + >> +/// The HID driver trait. >> +/// >> +/// # Examples >> +/// >> +/// ``` >> +/// use kernel::{bindings, device, hid}; >> +/// >> +/// struct MyDriver; >> +/// >> +/// kernel::hid_device_table!( >> +/// HID_TABLE, >> +/// MODULE_HID_TABLE, >> +/// <MyDriver as hid::Driver>::IdInfo, >> +/// [( >> +/// hid::DeviceId::new_usb( >> +/// hid::Group::Steam, >> +/// bindings::USB_VENDOR_ID_VALVE, >> +/// bindings::USB_DEVICE_ID_STEAM_DECK, >> +/// ), >> +/// (), >> +/// )] >> +/// ); >> +/// >> +/// #[vtable] >> +/// impl hid::Driver for MyDriver { >> +/// type IdInfo = (); >> +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; >> +/// >> +/// /// This function is optional to implement. >> +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { >> +/// // Perform some report descriptor fixup. >> +/// rdesc >> +/// } >> +/// } >> +/// ``` >> +/// Drivers must implement this trait in order to get a HID driver registered. >> +/// Please refer to the `Adapter` documentation for an example. >> +#[vtable] >> +pub trait Driver: Send { >> + /// The type holding information about each device id supported by the driver. >> + // TODO: Use `associated_type_defaults` once stabilized: >> + // >> + // ``` >> + // type IdInfo: 'static = (); >> + // ``` >> + type IdInfo: 'static; >> + >> + /// The table of device ids supported by the driver. >> + const ID_TABLE: IdTable<Self::IdInfo>; >> + >> + /// Called before report descriptor parsing. Can be used to mutate the >> + /// report descriptor before the core HID logic processes the descriptor. >> + /// Useful for problematic report descriptors that prevent HID devices from >> + /// functioning correctly. >> + /// >> + /// Optional to implement. >> + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] { >> + build_error!(VTABLE_DEFAULT_ERROR) >> + } >> +} >> + >> +/// An adapter for the registration of HID drivers. >> +pub struct Adapter<T: Driver>(T); >> + >> +// SAFETY: >> +// - `bindings::hid_driver` is a C type declared as `repr(C)`. >> +// - `T` is the type of the driver's device private data. >> +// - `struct hid_driver` embeds a `struct device_driver`. >> +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`. >> +unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> { >> + type DriverType = bindings::hid_driver; >> + type DriverData = T; >> + const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver); >> +} >> + >> +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if >> +// a preceding call to `register` has been successful. >> +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> { >> + unsafe fn register( >> + hdrv: &Opaque<Self::DriverType>, >> + name: &'static CStr, >> + module: &'static ThisModule, >> + ) -> Result { >> + // SAFETY: It's safe to set the fields of `struct hid_driver` on initialization. >> + unsafe { >> + (*hdrv.get()).name = name.as_char_ptr(); >> + (*hdrv.get()).id_table = T::ID_TABLE.as_ptr(); >> + (*hdrv.get()).report_fixup = if T::HAS_REPORT_FIXUP { >> + Some(Self::report_fixup_callback) >> + } else { >> + None >> + }; >> + } >> + >> + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` >> + to_result(unsafe { >> + bindings::__hid_register_driver(hdrv.get(), module.0, name.as_char_ptr()) >> + }) >> + } >> + >> + unsafe fn unregister(hdrv: &Opaque<Self::DriverType>) { >> + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` >> + unsafe { bindings::hid_unregister_driver(hdrv.get()) } >> + } >> +} >> + >> +impl<T: Driver + 'static> Adapter<T> { >> + extern "C" fn report_fixup_callback( >> + hdev: *mut bindings::hid_device, >> + buf: *mut u8, >> + size: *mut kernel::ffi::c_uint, >> + ) -> *const u8 { >> + // SAFETY: The HID subsystem only ever calls the report_fixup callback >> + // with a valid pointer to a `struct hid_device`. >> + // >> + // INVARIANT: `hdev` is valid for the duration of >> + // `report_fixup_callback()`. >> + let hdev = unsafe { &*hdev.cast::<Device<device::Core>>() }; >> + >> + // SAFETY: The HID subsystem only ever calls the report_fixup callback >> + // with a valid pointer to a `kernel::ffi::c_uint`. >> + // >> + // INVARIANT: `size` is valid for the duration of >> + // `report_fixup_callback()`. >> + let buf_len: usize = match unsafe { *size }.try_into() { >> + Ok(len) => len, >> + Err(e) => { >> + dev_err!( >> + hdev.as_ref(), >> + "Cannot fix report description due to {:?}!\n", >> + e >> + ); >> + >> + return buf; >> + } >> + }; >> + >> + // Build a mutable Rust slice from `buf` and `size`. >> + // >> + // SAFETY: The HID subsystem only ever calls the `report_fixup callback` >> + // with a valid pointer to a `u8` buffer. >> + // >> + // INVARIANT: `buf` is valid for the duration of >> + // `report_fixup_callback()`. >> + let rdesc_slice = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; >> + let rdesc_slice = T::report_fixup(hdev, rdesc_slice); >> + >> + match rdesc_slice.len().try_into() { >> + // SAFETY: The HID subsystem only ever calls the report_fixup >> + // callback with a valid pointer to a `kernel::ffi::c_uint`. >> + // >> + // INVARIANT: `size` is valid for the duration of >> + // `report_fixup_callback()`. >> + Ok(len) => unsafe { *size = len }, >> + Err(e) => { >> + dev_err!( >> + hdev.as_ref(), >> + "Fixed report description will not be used due to {:?}!\n", >> + e >> + ); >> + >> + return buf; >> + } >> + } >> + >> + rdesc_slice.as_ptr() >> + } >> +} >> + >> +/// Declares a kernel module that exposes a single HID driver. >> +/// >> +/// # Examples >> +/// >> +/// ```ignore >> +/// kernel::module_hid_driver! { >> +/// type: MyDriver, >> +/// name: "Module name", >> +/// authors: ["Author name"], >> +/// description: "Description", >> +/// license: "GPL", >> +/// } >> +/// ``` >> +#[macro_export] >> +macro_rules! module_hid_driver { >> + ($($f:tt)*) => { >> + $crate::module_driver!(<T>, $crate::hid::Adapter<T>, { $($f)* }); >> + }; >> +} >> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs >> index 3da92f18f4ee..e2dcacd9369e 100644 >> --- a/rust/kernel/lib.rs >> +++ b/rust/kernel/lib.rs >> @@ -102,6 +102,8 @@ >> pub mod id_pool; >> #[doc(hidden)] >> pub mod impl_flags; >> +#[cfg(CONFIG_RUST_HID_ABSTRACTIONS)] >> +pub mod hid; >> pub mod init; >> pub mod io; >> pub mod ioctl; ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers 2026-02-22 21:56 ` [PATCH v6 1/2] rust: core abstractions for HID drivers Rahul Rameshbabu 2026-02-22 23:39 ` Terry Junge @ 2026-02-27 15:27 ` Gary Guo 2026-03-01 6:48 ` Rahul Rameshbabu 1 sibling, 1 reply; 11+ messages in thread From: Gary Guo @ 2026-02-27 15:27 UTC (permalink / raw) To: Rahul Rameshbabu, linux-input, linux-kernel, rust-for-linux Cc: a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos, ojeda, peter.hutterer, tmgross On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote: > These abstractions enable the development of HID drivers in Rust by binding > with the HID core C API. They provide Rust types that map to the > equivalents in C. In this initial draft, only hid_device and hid_device_id > are provided direct Rust type equivalents. hid_driver is specially wrapped > with a custom Driver type. The module_hid_driver! macro provides analogous > functionality to its C equivalent. Only the .report_fixup callback is > binded to Rust so far. > > Future work for these abstractions would include more bindings for common > HID-related types, such as hid_field, hid_report_enum, and hid_report as > well as more bus callbacks. Providing Rust equivalents to useful core HID > functions will also be necessary for HID driver development in Rust. > > Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com> > --- > > Notes: > Changelog: > > v5->v6: > * Converted From<u16> for Group to TryFrom<u16> to properly handle > error case > * Renamed into method for Group to into_u16 to not conflate with the > From trait > * Refactored new upstream changes to RegistrationOps > * Implemented DriverLayout trait for hid::Adapter<T> > v4->v5: > * Add rust/ to drivers/hid/Makefile > * Implement RawDeviceIdIndex trait > v3->v4: > * Removed specifying tree in MAINTAINERS file since that is up for > debate > * Minor rebase cleanup > * Moved driver logic under drivers/hid/rust > v2->v3: > * Implemented AlwaysRefCounted trait using embedded struct device's > reference counts instead of the separate reference counter in struct > hid_device > * Used &raw mut as appropriate > * Binded include/linux/device.h for get_device and put_device > * Cleaned up various comment related formatting > * Minified dev_err! format string > * Updated Group enum to be repr(u16) > * Implemented From<u16> trait for Group > * Added TODO comment when const_trait_impl stabilizes > * Made group getter functions return a Group variant instead of a raw > number > * Made sure example code builds > v1->v2: > * Binded drivers/hid/hid-ids.h for use in Rust drivers > * Remove pre-emptive referencing of a C HID driver instance before > it is fully initialized in the driver registration path > * Moved static getters to generic Device trait implementation, so > they can be used by all device::DeviceContext > * Use core macros for supporting DeviceContext transitions > * Implemented the AlwaysRefCounted and AsRef traits > * Make use for dev_err! as appropriate > RFC->v1: > * Use Danilo's core infrastructure > * Account for HID device groups > * Remove probe and remove callbacks > * Implement report_fixup support > * Properly comment code including SAFETY comments > > MAINTAINERS | 8 + > drivers/hid/Kconfig | 2 + > drivers/hid/Makefile | 2 + > drivers/hid/rust/Kconfig | 12 + > rust/bindings/bindings_helper.h | 3 + > rust/kernel/hid.rs | 530 ++++++++++++++++++++++++++++++++ > rust/kernel/lib.rs | 2 + > 7 files changed, 559 insertions(+) > create mode 100644 drivers/hid/rust/Kconfig > create mode 100644 rust/kernel/hid.rs > > diff --git a/MAINTAINERS b/MAINTAINERS > index b8d8a5c41597..1fee14024fa2 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -11319,6 +11319,14 @@ F: include/uapi/linux/hid* > F: samples/hid/ > F: tools/testing/selftests/hid/ > > +HID CORE LAYER [RUST] > +M: Rahul Rameshbabu <sergeantsagara@protonmail.com> > +R: Benjamin Tissoires <bentiss@kernel.org> > +L: linux-input@vger.kernel.org > +S: Maintained > +F: drivers/hid/rust/*.rs > +F: rust/kernel/hid.rs > + > HID LOGITECH DRIVERS > R: Filipe Laíns <lains@riseup.net> > L: linux-input@vger.kernel.org > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig > index c1d9f7c6a5f2..750c2d49a806 100644 > --- a/drivers/hid/Kconfig > +++ b/drivers/hid/Kconfig > @@ -1439,6 +1439,8 @@ endmenu > > source "drivers/hid/bpf/Kconfig" > > +source "drivers/hid/rust/Kconfig" > + > source "drivers/hid/i2c-hid/Kconfig" > > source "drivers/hid/intel-ish-hid/Kconfig" > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile > index e01838239ae6..b78ab84c47b4 100644 > --- a/drivers/hid/Makefile > +++ b/drivers/hid/Makefile > @@ -8,6 +8,8 @@ hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o > > obj-$(CONFIG_HID_BPF) += bpf/ > > +obj-$(CONFIG_RUST_HID_ABSTRACTIONS) += rust/ > + > obj-$(CONFIG_HID) += hid.o > obj-$(CONFIG_UHID) += uhid.o > > diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig > new file mode 100644 > index 000000000000..d3247651829e > --- /dev/null > +++ b/drivers/hid/rust/Kconfig > @@ -0,0 +1,12 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +menu "Rust HID support" > + > +config RUST_HID_ABSTRACTIONS > + bool "Rust HID abstractions support" > + depends on RUST > + depends on HID=y > + help > + Adds support needed for HID drivers written in Rust. It provides a > + wrapper around the C hid core. > + > +endmenu # Rust HID support > diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h > index 083cc44aa952..200e58af27a3 100644 > --- a/rust/bindings/bindings_helper.h > +++ b/rust/bindings/bindings_helper.h > @@ -48,6 +48,7 @@ > #include <linux/cpumask.h> > #include <linux/cred.h> > #include <linux/debugfs.h> > +#include <linux/device.h> > #include <linux/device/faux.h> > #include <linux/dma-direction.h> > #include <linux/dma-mapping.h> > @@ -60,6 +61,8 @@ > #include <linux/i2c.h> > #include <linux/interrupt.h> > #include <linux/io-pgtable.h> > +#include <linux/hid.h> > +#include "../../drivers/hid/hid-ids.h" > #include <linux/ioport.h> > #include <linux/jiffies.h> > #include <linux/jump_label.h> > diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs > new file mode 100644 > index 000000000000..b9db542d923a > --- /dev/null > +++ b/rust/kernel/hid.rs > @@ -0,0 +1,530 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com> > + > +//! Abstractions for the HID interface. > +//! > +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h) > + > +use crate::{ > + device, > + device_id::{ > + RawDeviceId, > + RawDeviceIdIndex, // > + }, > + driver, > + error::*, > + prelude::*, > + types::Opaque, // > +}; > +use core::{ > + marker::PhantomData, > + ptr::{ > + addr_of_mut, > + NonNull, // > + } // > +}; > + > +/// Indicates the item is static read-only. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_CONSTANT: u8 = bindings::HID_MAIN_ITEM_CONSTANT as u8; These can be bitflags with `impl_flags!`? > + > +/// Indicates the item represents data from a physical control. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_VARIABLE: u8 = bindings::HID_MAIN_ITEM_VARIABLE as u8; > + > +/// Indicates the item should be treated as a relative change from the previous > +/// report. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_RELATIVE: u8 = bindings::HID_MAIN_ITEM_RELATIVE as u8; > + > +/// Indicates the item should wrap around when reaching the extreme high or > +/// extreme low values. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_WRAP: u8 = bindings::HID_MAIN_ITEM_WRAP as u8; > + > +/// Indicates the item should wrap around when reaching the extreme high or > +/// extreme low values. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_NONLINEAR: u8 = bindings::HID_MAIN_ITEM_NONLINEAR as u8; > + > +/// Indicates whether the control has a preferred state it will physically > +/// return to without user intervention. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_NO_PREFERRED: u8 = bindings::HID_MAIN_ITEM_NO_PREFERRED as u8; > + > +/// Indicates whether the control has a physical state where it will not send > +/// any reports. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_NULL_STATE: u8 = bindings::HID_MAIN_ITEM_NULL_STATE as u8; > + > +/// Indicates whether the control requires host system logic to change state. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_VOLATILE: u8 = bindings::HID_MAIN_ITEM_VOLATILE as u8; > + > +/// Indicates whether the item is fixed size or a variable buffer of bytes. > +/// > +/// Refer to [Device Class Definition for HID 1.11] > +/// Section 6.2.2.5 Input, Output, and Feature Items. > +/// > +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf > +pub const MAIN_ITEM_BUFFERED_BYTE: u8 = bindings::HID_MAIN_ITEM_BUFFERED_BYTE as u8; > + > +/// HID device groups are intended to help categories HID devices based on a set > +/// of common quirks and logic that they will require to function correctly. > +#[repr(u16)] > +pub enum Group { > + /// Used to match a device against any group when probing. > + Any = bindings::HID_GROUP_ANY as u16, > + > + /// Indicates a generic device that should need no custom logic from the > + /// core HID stack. > + Generic = bindings::HID_GROUP_GENERIC as u16, > + > + /// Maps multitouch devices to hid-multitouch instead of hid-generic. > + Multitouch = bindings::HID_GROUP_MULTITOUCH as u16, > + > + /// Used for autodetecing and mapping of HID sensor hubs to > + /// hid-sensor-hub. > + SensorHub = bindings::HID_GROUP_SENSOR_HUB as u16, > + > + /// Used for autodetecing and mapping Win 8 multitouch devices to set the > + /// needed quirks. > + MultitouchWin8 = bindings::HID_GROUP_MULTITOUCH_WIN_8 as u16, > + > + // Vendor-specific device groups. > + /// Used to distinguish Synpatics touchscreens from other products. The > + /// touchscreens will be handled by hid-multitouch instead, while everything > + /// else will be managed by hid-rmi. > + RMI = bindings::HID_GROUP_RMI as u16, > + > + /// Used for hid-core handling to automatically identify Wacom devices and > + /// have them probed by hid-wacom. > + Wacom = bindings::HID_GROUP_WACOM as u16, > + > + /// Used by logitech-djreceiver and logitech-djdevice to autodetect if > + /// devices paied to the DJ receivers are DJ devices and handle them with > + /// the device driver. > + LogitechDJDevice = bindings::HID_GROUP_LOGITECH_DJ_DEVICE as u16, > + > + /// Since the Valve Steam Controller only has vendor-specific usages, > + /// prevent hid-generic from parsing its reports since there would be > + /// nothing hid-generic could do for the device. > + Steam = bindings::HID_GROUP_STEAM as u16, > + > + /// Used to differentiate 27 Mhz frequency Logitech DJ devices from other > + /// Logitech DJ devices. > + Logitech27MHzDevice = bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u16, > + > + /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi. > + Vivaldi = bindings::HID_GROUP_VIVALDI as u16, > +} > + > +// TODO: use `const_trait_impl` once stabilized: > +// > +// ``` > +// impl const From<Group> for u16 { > +// /// [`Group`] variants are represented by [`u16`] values. > +// fn from(value: Group) -> Self { > +// value as Self > +// } > +// } > +// ``` > +impl Group { > + /// Internal function used to convert [`Group`] variants into [`u16`]. > + const fn into_u16(self) -> u16 { This and many functions in the abstraction can be `#[inline]`. > + self as u16 > + } > +} > + > +impl TryFrom<u16> for Group { > + type Error = &'static str; > + > + /// [`u16`] values can be safely converted to [`Group`] variants. > + fn try_from(value: u16) -> Result<Group, Self::Error> { > + match value.into() { > + bindings::HID_GROUP_GENERIC => Ok(Group::Generic), > + bindings::HID_GROUP_MULTITOUCH => Ok(Group::Multitouch), > + bindings::HID_GROUP_SENSOR_HUB => Ok(Group::SensorHub), > + bindings::HID_GROUP_MULTITOUCH_WIN_8 => Ok(Group::MultitouchWin8), > + bindings::HID_GROUP_RMI => Ok(Group::RMI), > + bindings::HID_GROUP_WACOM => Ok(Group::Wacom), > + bindings::HID_GROUP_LOGITECH_DJ_DEVICE => Ok(Group::LogitechDJDevice), > + bindings::HID_GROUP_STEAM => Ok(Group::Steam), > + bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE => Ok(Group::Logitech27MHzDevice), > + bindings::HID_GROUP_VIVALDI => Ok(Group::Vivaldi), > + _ => Err("Unknown HID group encountered!"), Please define a custom error type or just use a kernel error code. > + } > + } > +} > + > +/// The HID device representation. > +/// > +/// This structure represents the Rust abstraction for a C `struct hid_device`. > +/// The implementation abstracts the usage of an already existing C `struct > +/// hid_device` within Rust code that we get passed from the C side. > +/// > +/// # Invariants > +/// > +/// A [`Device`] instance represents a valid `struct hid_device` created by the > +/// C portion of the kernel. > +#[repr(transparent)] > +pub struct Device<Ctx: device::DeviceContext = device::Normal>( > + Opaque<bindings::hid_device>, > + PhantomData<Ctx>, > +); > + > +impl<Ctx: device::DeviceContext> Device<Ctx> { > + fn as_raw(&self) -> *mut bindings::hid_device { > + self.0.get() > + } > + > + /// Returns the HID transport bus ID. > + pub fn bus(&self) -> u16 { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.bus > + } > + > + /// Returns the HID report group. > + pub fn group(&self) -> Result<Group, &'static str> { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.group.try_into() > + } > + > + /// Returns the HID vendor ID. > + pub fn vendor(&self) -> u32 { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.vendor > + } > + > + /// Returns the HID product ID. > + pub fn product(&self) -> u32 { > + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` > + unsafe { *self.as_raw() }.product > + } > +} > + > +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic > +// argument. > +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::types::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(&raw mut (*self.as_raw()).dev) }; > + } > + > + 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.cast::<bindings::hid_device>().as_ptr()).dev) } > + } > +} > + > +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 hid_device`. > + let dev = unsafe { addr_of_mut!((*self.as_raw()).dev) }; > + > + // SAFETY: `dev` points to a valid `struct device`. > + unsafe { device::Device::from_raw(dev) } > + } > +} > + > +/// Abstraction for the HID device ID structure `struct hid_device_id`. > +#[repr(transparent)] > +#[derive(Clone, Copy)] > +pub struct DeviceId(bindings::hid_device_id); > + > +impl DeviceId { > + /// Equivalent to C's `HID_USB_DEVICE` macro. > + /// > + /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID > + /// number. The main description should be the first sentence, and C equivalent sentence follows. > + pub const fn new_usb(group: Group, vendor: u32, product: u32) -> Self { > + Self(bindings::hid_device_id { > + bus: 0x3, // BUS_USB > + group: group.into_u16(), > + vendor, > + product, > + driver_data: 0, > + }) > + } > + > + /// Returns the HID transport bus ID. > + pub fn bus(&self) -> u16 { > + self.0.bus > + } > + > + /// Returns the HID report group. > + pub fn group(&self) -> Result<Group, &'static str> { > + self.0.group.try_into() > + } > + > + /// Returns the HID vendor ID. > + pub fn vendor(&self) -> u32 { > + self.0.vendor > + } > + > + /// Returns the HID product ID. > + pub fn product(&self) -> u32 { > + self.0.product > + } > +} > + > +// SAFETY: > +// * `DeviceId` is a `#[repr(transparent)` wrapper of `hid_device_id` and does not add > +// additional invariants, so it's safe to transmute to `RawType`. > +// * `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. > +unsafe impl RawDeviceId for DeviceId { > + type RawType = bindings::hid_device_id; > +} > + > +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. > +unsafe impl RawDeviceIdIndex for DeviceId { > + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::hid_device_id, driver_data); > + > + fn index(&self) -> usize { > + self.0.driver_data > + } > +} > + > +/// [`IdTable`] type for HID. > +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>; > + > +/// Create a HID [`IdTable`] with its alias for modpost. > +#[macro_export] > +macro_rules! hid_device_table { > + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { > + const $table_name: $crate::device_id::IdArray< > + $crate::hid::DeviceId, > + $id_info_type, > + { $table_data.len() }, > + > = $crate::device_id::IdArray::new($table_data); > + > + $crate::module_device_table!("hid", $module_table_name, $table_name); > + }; > +} > + > +/// The HID driver trait. > +/// > +/// # Examples > +/// > +/// ``` > +/// use kernel::{bindings, device, hid}; > +/// > +/// struct MyDriver; > +/// > +/// kernel::hid_device_table!( > +/// HID_TABLE, > +/// MODULE_HID_TABLE, > +/// <MyDriver as hid::Driver>::IdInfo, > +/// [( > +/// hid::DeviceId::new_usb( > +/// hid::Group::Steam, > +/// bindings::USB_VENDOR_ID_VALVE, > +/// bindings::USB_DEVICE_ID_STEAM_DECK, > +/// ), > +/// (), > +/// )] > +/// ); > +/// > +/// #[vtable] > +/// impl hid::Driver for MyDriver { > +/// type IdInfo = (); > +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; > +/// > +/// /// This function is optional to implement. > +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { > +/// // Perform some report descriptor fixup. > +/// rdesc > +/// } > +/// } > +/// ``` > +/// Drivers must implement this trait in order to get a HID driver registered. > +/// Please refer to the `Adapter` documentation for an example. > +#[vtable] > +pub trait Driver: Send { > + /// The type holding information about each device id supported by the driver. > + // TODO: Use `associated_type_defaults` once stabilized: > + // > + // ``` > + // type IdInfo: 'static = (); > + // ``` > + type IdInfo: 'static; > + > + /// The table of device ids supported by the driver. > + const ID_TABLE: IdTable<Self::IdInfo>; > + > + /// Called before report descriptor parsing. Can be used to mutate the > + /// report descriptor before the core HID logic processes the descriptor. > + /// Useful for problematic report descriptors that prevent HID devices from > + /// functioning correctly. > + /// > + /// Optional to implement. > + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] { I think this can just use a single lifetime? > + build_error!(VTABLE_DEFAULT_ERROR) > + } > +} > + > +/// An adapter for the registration of HID drivers. > +pub struct Adapter<T: Driver>(T); > + > +// SAFETY: > +// - `bindings::hid_driver` is a C type declared as `repr(C)`. > +// - `T` is the type of the driver's device private data. > +// - `struct hid_driver` embeds a `struct device_driver`. > +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`. > +unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> { > + type DriverType = bindings::hid_driver; > + type DriverData = T; > + const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver); > +} > + > +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if > +// a preceding call to `register` has been successful. > +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> { > + unsafe fn register( > + hdrv: &Opaque<Self::DriverType>, > + name: &'static CStr, > + module: &'static ThisModule, > + ) -> Result { > + // SAFETY: It's safe to set the fields of `struct hid_driver` on initialization. > + unsafe { > + (*hdrv.get()).name = name.as_char_ptr(); > + (*hdrv.get()).id_table = T::ID_TABLE.as_ptr(); > + (*hdrv.get()).report_fixup = if T::HAS_REPORT_FIXUP { > + Some(Self::report_fixup_callback) > + } else { > + None > + }; > + } > + > + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` > + to_result(unsafe { > + bindings::__hid_register_driver(hdrv.get(), module.0, name.as_char_ptr()) > + }) > + } > + > + unsafe fn unregister(hdrv: &Opaque<Self::DriverType>) { > + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` > + unsafe { bindings::hid_unregister_driver(hdrv.get()) } > + } > +} > + > +impl<T: Driver + 'static> Adapter<T> { > + extern "C" fn report_fixup_callback( > + hdev: *mut bindings::hid_device, > + buf: *mut u8, > + size: *mut kernel::ffi::c_uint, > + ) -> *const u8 { > + // SAFETY: The HID subsystem only ever calls the report_fixup callback > + // with a valid pointer to a `struct hid_device`. > + // > + // INVARIANT: `hdev` is valid for the duration of > + // `report_fixup_callback()`. > + let hdev = unsafe { &*hdev.cast::<Device<device::Core>>() }; > + > + // SAFETY: The HID subsystem only ever calls the report_fixup callback > + // with a valid pointer to a `kernel::ffi::c_uint`. > + // > + // INVARIANT: `size` is valid for the duration of > + // `report_fixup_callback()`. > + let buf_len: usize = match unsafe { *size }.try_into() { This can just be a cast. In all kernel envs `usize` is at least as large as `u32`. > + Ok(len) => len, > + Err(e) => { > + dev_err!( > + hdev.as_ref(), > + "Cannot fix report description due to {:?}!\n", > + e > + ); > + > + return buf; > + } > + }; > + > + // Build a mutable Rust slice from `buf` and `size`. > + // > + // SAFETY: The HID subsystem only ever calls the `report_fixup callback` > + // with a valid pointer to a `u8` buffer. > + // > + // INVARIANT: `buf` is valid for the duration of > + // `report_fixup_callback()`. > + let rdesc_slice = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; > + let rdesc_slice = T::report_fixup(hdev, rdesc_slice); > + > + match rdesc_slice.len().try_into() { I'm somewhat inclined to just `BUG()` (i.e. `.expect()`) in this case. A driver that hits this error condition would need to leak >= 4G of memory to satisfy the lifetime bound. > + // SAFETY: The HID subsystem only ever calls the report_fixup > + // callback with a valid pointer to a `kernel::ffi::c_uint`. > + // > + // INVARIANT: `size` is valid for the duration of > + // `report_fixup_callback()`. > + Ok(len) => unsafe { *size = len }, > + Err(e) => { > + dev_err!( > + hdev.as_ref(), as_ref() shouldn't be needed now. Best, Gary > + "Fixed report description will not be used due to {:?}!\n", > + e > + ); > + > + return buf; > + } > + } > + > + rdesc_slice.as_ptr() > + } > +} > + > +/// Declares a kernel module that exposes a single HID driver. > +/// > +/// # Examples > +/// > +/// ```ignore > +/// kernel::module_hid_driver! { > +/// type: MyDriver, > +/// name: "Module name", > +/// authors: ["Author name"], > +/// description: "Description", > +/// license: "GPL", > +/// } > +/// ``` > +#[macro_export] > +macro_rules! module_hid_driver { > + ($($f:tt)*) => { > + $crate::module_driver!(<T>, $crate::hid::Adapter<T>, { $($f)* }); > + }; > +} > diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs > index 3da92f18f4ee..e2dcacd9369e 100644 > --- a/rust/kernel/lib.rs > +++ b/rust/kernel/lib.rs > @@ -102,6 +102,8 @@ > pub mod id_pool; > #[doc(hidden)] > pub mod impl_flags; > +#[cfg(CONFIG_RUST_HID_ABSTRACTIONS)] > +pub mod hid; > pub mod init; > pub mod io; > pub mod ioctl; ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers 2026-02-27 15:27 ` Gary Guo @ 2026-03-01 6:48 ` Rahul Rameshbabu 2026-03-01 12:30 ` Alice Ryhl 2026-03-01 13:17 ` Gary Guo 0 siblings, 2 replies; 11+ messages in thread From: Rahul Rameshbabu @ 2026-03-01 6:48 UTC (permalink / raw) To: Gary Guo Cc: linux-input, linux-kernel, rust-for-linux, a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, jikos, ojeda, peter.hutterer, tmgross On Fri, 27 Feb, 2026 15:27:59 +0000 "Gary Guo" <gary@garyguo.net> wrote: > On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote: >> These abstractions enable the development of HID drivers in Rust by binding >> with the HID core C API. They provide Rust types that map to the >> equivalents in C. In this initial draft, only hid_device and hid_device_id >> are provided direct Rust type equivalents. hid_driver is specially wrapped >> with a custom Driver type. The module_hid_driver! macro provides analogous >> functionality to its C equivalent. Only the .report_fixup callback is >> binded to Rust so far. >> >> Future work for these abstractions would include more bindings for common >> HID-related types, such as hid_field, hid_report_enum, and hid_report as >> well as more bus callbacks. Providing Rust equivalents to useful core HID >> functions will also be necessary for HID driver development in Rust. >> >> Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com> >> --- >> >> Notes: >> Changelog: >> >> v5->v6: >> * Converted From<u16> for Group to TryFrom<u16> to properly handle >> error case >> * Renamed into method for Group to into_u16 to not conflate with the >> From trait >> * Refactored new upstream changes to RegistrationOps >> * Implemented DriverLayout trait for hid::Adapter<T> >> v4->v5: >> * Add rust/ to drivers/hid/Makefile >> * Implement RawDeviceIdIndex trait >> v3->v4: >> * Removed specifying tree in MAINTAINERS file since that is up for >> debate >> * Minor rebase cleanup >> * Moved driver logic under drivers/hid/rust >> v2->v3: >> * Implemented AlwaysRefCounted trait using embedded struct device's >> reference counts instead of the separate reference counter in struct >> hid_device >> * Used &raw mut as appropriate >> * Binded include/linux/device.h for get_device and put_device >> * Cleaned up various comment related formatting >> * Minified dev_err! format string >> * Updated Group enum to be repr(u16) >> * Implemented From<u16> trait for Group >> * Added TODO comment when const_trait_impl stabilizes >> * Made group getter functions return a Group variant instead of a raw >> number >> * Made sure example code builds >> v1->v2: >> * Binded drivers/hid/hid-ids.h for use in Rust drivers >> * Remove pre-emptive referencing of a C HID driver instance before >> it is fully initialized in the driver registration path >> * Moved static getters to generic Device trait implementation, so >> they can be used by all device::DeviceContext >> * Use core macros for supporting DeviceContext transitions >> * Implemented the AlwaysRefCounted and AsRef traits >> * Make use for dev_err! as appropriate >> RFC->v1: >> * Use Danilo's core infrastructure >> * Account for HID device groups >> * Remove probe and remove callbacks >> * Implement report_fixup support >> * Properly comment code including SAFETY comments >> >> MAINTAINERS | 8 + >> drivers/hid/Kconfig | 2 + >> drivers/hid/Makefile | 2 + >> drivers/hid/rust/Kconfig | 12 + >> rust/bindings/bindings_helper.h | 3 + >> rust/kernel/hid.rs | 530 ++++++++++++++++++++++++++++++++ >> rust/kernel/lib.rs | 2 + >> 7 files changed, 559 insertions(+) >> create mode 100644 drivers/hid/rust/Kconfig >> create mode 100644 rust/kernel/hid.rs >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index b8d8a5c41597..1fee14024fa2 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -11319,6 +11319,14 @@ F: include/uapi/linux/hid* >> F: samples/hid/ >> F: tools/testing/selftests/hid/ >> >> +HID CORE LAYER [RUST] >> +M: Rahul Rameshbabu <sergeantsagara@protonmail.com> >> +R: Benjamin Tissoires <bentiss@kernel.org> >> +L: linux-input@vger.kernel.org >> +S: Maintained >> +F: drivers/hid/rust/*.rs >> +F: rust/kernel/hid.rs >> + >> HID LOGITECH DRIVERS >> R: Filipe Laíns <lains@riseup.net> >> L: linux-input@vger.kernel.org >> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig >> index c1d9f7c6a5f2..750c2d49a806 100644 >> --- a/drivers/hid/Kconfig >> +++ b/drivers/hid/Kconfig >> @@ -1439,6 +1439,8 @@ endmenu >> >> source "drivers/hid/bpf/Kconfig" >> >> +source "drivers/hid/rust/Kconfig" >> + >> source "drivers/hid/i2c-hid/Kconfig" >> >> source "drivers/hid/intel-ish-hid/Kconfig" >> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile >> index e01838239ae6..b78ab84c47b4 100644 >> --- a/drivers/hid/Makefile >> +++ b/drivers/hid/Makefile >> @@ -8,6 +8,8 @@ hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o >> >> obj-$(CONFIG_HID_BPF) += bpf/ >> >> +obj-$(CONFIG_RUST_HID_ABSTRACTIONS) += rust/ >> + >> obj-$(CONFIG_HID) += hid.o >> obj-$(CONFIG_UHID) += uhid.o >> >> diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig >> new file mode 100644 >> index 000000000000..d3247651829e >> --- /dev/null >> +++ b/drivers/hid/rust/Kconfig >> @@ -0,0 +1,12 @@ >> +# SPDX-License-Identifier: GPL-2.0-only >> +menu "Rust HID support" >> + >> +config RUST_HID_ABSTRACTIONS >> + bool "Rust HID abstractions support" >> + depends on RUST >> + depends on HID=y >> + help >> + Adds support needed for HID drivers written in Rust. It provides a >> + wrapper around the C hid core. >> + >> +endmenu # Rust HID support >> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h >> index 083cc44aa952..200e58af27a3 100644 >> --- a/rust/bindings/bindings_helper.h >> +++ b/rust/bindings/bindings_helper.h >> @@ -48,6 +48,7 @@ >> #include <linux/cpumask.h> >> #include <linux/cred.h> >> #include <linux/debugfs.h> >> +#include <linux/device.h> >> #include <linux/device/faux.h> >> #include <linux/dma-direction.h> >> #include <linux/dma-mapping.h> >> @@ -60,6 +61,8 @@ >> #include <linux/i2c.h> >> #include <linux/interrupt.h> >> #include <linux/io-pgtable.h> >> +#include <linux/hid.h> >> +#include "../../drivers/hid/hid-ids.h" >> #include <linux/ioport.h> >> #include <linux/jiffies.h> >> #include <linux/jump_label.h> >> diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs >> new file mode 100644 >> index 000000000000..b9db542d923a >> --- /dev/null >> +++ b/rust/kernel/hid.rs >> @@ -0,0 +1,530 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com> >> + >> +//! Abstractions for the HID interface. >> +//! >> +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h) >> + >> +use crate::{ >> + device, >> + device_id::{ >> + RawDeviceId, >> + RawDeviceIdIndex, // >> + }, >> + driver, >> + error::*, >> + prelude::*, >> + types::Opaque, // >> +}; >> +use core::{ >> + marker::PhantomData, >> + ptr::{ >> + addr_of_mut, >> + NonNull, // >> + } // >> +}; >> + >> +/// Indicates the item is static read-only. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_CONSTANT: u8 = bindings::HID_MAIN_ITEM_CONSTANT as u8; > > These can be bitflags with `impl_flags!`? > >> + >> +/// Indicates the item represents data from a physical control. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_VARIABLE: u8 = bindings::HID_MAIN_ITEM_VARIABLE as u8; >> + >> +/// Indicates the item should be treated as a relative change from the previous >> +/// report. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_RELATIVE: u8 = bindings::HID_MAIN_ITEM_RELATIVE as u8; >> + >> +/// Indicates the item should wrap around when reaching the extreme high or >> +/// extreme low values. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_WRAP: u8 = bindings::HID_MAIN_ITEM_WRAP as u8; >> + >> +/// Indicates the item should wrap around when reaching the extreme high or >> +/// extreme low values. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_NONLINEAR: u8 = bindings::HID_MAIN_ITEM_NONLINEAR as u8; >> + >> +/// Indicates whether the control has a preferred state it will physically >> +/// return to without user intervention. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_NO_PREFERRED: u8 = bindings::HID_MAIN_ITEM_NO_PREFERRED as u8; >> + >> +/// Indicates whether the control has a physical state where it will not send >> +/// any reports. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_NULL_STATE: u8 = bindings::HID_MAIN_ITEM_NULL_STATE as u8; >> + >> +/// Indicates whether the control requires host system logic to change state. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_VOLATILE: u8 = bindings::HID_MAIN_ITEM_VOLATILE as u8; >> + >> +/// Indicates whether the item is fixed size or a variable buffer of bytes. >> +/// >> +/// Refer to [Device Class Definition for HID 1.11] >> +/// Section 6.2.2.5 Input, Output, and Feature Items. >> +/// >> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf >> +pub const MAIN_ITEM_BUFFERED_BYTE: u8 = bindings::HID_MAIN_ITEM_BUFFERED_BYTE as u8; >> + >> +/// HID device groups are intended to help categories HID devices based on a set >> +/// of common quirks and logic that they will require to function correctly. >> +#[repr(u16)] >> +pub enum Group { >> + /// Used to match a device against any group when probing. >> + Any = bindings::HID_GROUP_ANY as u16, >> + >> + /// Indicates a generic device that should need no custom logic from the >> + /// core HID stack. >> + Generic = bindings::HID_GROUP_GENERIC as u16, >> + >> + /// Maps multitouch devices to hid-multitouch instead of hid-generic. >> + Multitouch = bindings::HID_GROUP_MULTITOUCH as u16, >> + >> + /// Used for autodetecing and mapping of HID sensor hubs to >> + /// hid-sensor-hub. >> + SensorHub = bindings::HID_GROUP_SENSOR_HUB as u16, >> + >> + /// Used for autodetecing and mapping Win 8 multitouch devices to set the >> + /// needed quirks. >> + MultitouchWin8 = bindings::HID_GROUP_MULTITOUCH_WIN_8 as u16, >> + >> + // Vendor-specific device groups. >> + /// Used to distinguish Synpatics touchscreens from other products. The >> + /// touchscreens will be handled by hid-multitouch instead, while everything >> + /// else will be managed by hid-rmi. >> + RMI = bindings::HID_GROUP_RMI as u16, >> + >> + /// Used for hid-core handling to automatically identify Wacom devices and >> + /// have them probed by hid-wacom. >> + Wacom = bindings::HID_GROUP_WACOM as u16, >> + >> + /// Used by logitech-djreceiver and logitech-djdevice to autodetect if >> + /// devices paied to the DJ receivers are DJ devices and handle them with >> + /// the device driver. >> + LogitechDJDevice = bindings::HID_GROUP_LOGITECH_DJ_DEVICE as u16, >> + >> + /// Since the Valve Steam Controller only has vendor-specific usages, >> + /// prevent hid-generic from parsing its reports since there would be >> + /// nothing hid-generic could do for the device. >> + Steam = bindings::HID_GROUP_STEAM as u16, >> + >> + /// Used to differentiate 27 Mhz frequency Logitech DJ devices from other >> + /// Logitech DJ devices. >> + Logitech27MHzDevice = bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u16, >> + >> + /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi. >> + Vivaldi = bindings::HID_GROUP_VIVALDI as u16, >> +} >> + >> +// TODO: use `const_trait_impl` once stabilized: >> +// >> +// ``` >> +// impl const From<Group> for u16 { >> +// /// [`Group`] variants are represented by [`u16`] values. >> +// fn from(value: Group) -> Self { >> +// value as Self >> +// } >> +// } >> +// ``` >> +impl Group { >> + /// Internal function used to convert [`Group`] variants into [`u16`]. >> + const fn into_u16(self) -> u16 { > > This and many functions in the abstraction can be `#[inline]`. > >> + self as u16 >> + } >> +} >> + >> +impl TryFrom<u16> for Group { >> + type Error = &'static str; >> + >> + /// [`u16`] values can be safely converted to [`Group`] variants. >> + fn try_from(value: u16) -> Result<Group, Self::Error> { >> + match value.into() { >> + bindings::HID_GROUP_GENERIC => Ok(Group::Generic), >> + bindings::HID_GROUP_MULTITOUCH => Ok(Group::Multitouch), >> + bindings::HID_GROUP_SENSOR_HUB => Ok(Group::SensorHub), >> + bindings::HID_GROUP_MULTITOUCH_WIN_8 => Ok(Group::MultitouchWin8), >> + bindings::HID_GROUP_RMI => Ok(Group::RMI), >> + bindings::HID_GROUP_WACOM => Ok(Group::Wacom), >> + bindings::HID_GROUP_LOGITECH_DJ_DEVICE => Ok(Group::LogitechDJDevice), >> + bindings::HID_GROUP_STEAM => Ok(Group::Steam), >> + bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE => Ok(Group::Logitech27MHzDevice), >> + bindings::HID_GROUP_VIVALDI => Ok(Group::Vivaldi), >> + _ => Err("Unknown HID group encountered!"), > > Please define a custom error type or just use a kernel error code. > >> + } >> + } >> +} >> + >> +/// The HID device representation. >> +/// >> +/// This structure represents the Rust abstraction for a C `struct hid_device`. >> +/// The implementation abstracts the usage of an already existing C `struct >> +/// hid_device` within Rust code that we get passed from the C side. >> +/// >> +/// # Invariants >> +/// >> +/// A [`Device`] instance represents a valid `struct hid_device` created by the >> +/// C portion of the kernel. >> +#[repr(transparent)] >> +pub struct Device<Ctx: device::DeviceContext = device::Normal>( >> + Opaque<bindings::hid_device>, >> + PhantomData<Ctx>, >> +); >> + >> +impl<Ctx: device::DeviceContext> Device<Ctx> { >> + fn as_raw(&self) -> *mut bindings::hid_device { >> + self.0.get() >> + } >> + >> + /// Returns the HID transport bus ID. >> + pub fn bus(&self) -> u16 { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.bus >> + } >> + >> + /// Returns the HID report group. >> + pub fn group(&self) -> Result<Group, &'static str> { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.group.try_into() >> + } >> + >> + /// Returns the HID vendor ID. >> + pub fn vendor(&self) -> u32 { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.vendor >> + } >> + >> + /// Returns the HID product ID. >> + pub fn product(&self) -> u32 { >> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device` >> + unsafe { *self.as_raw() }.product >> + } >> +} >> + >> +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic >> +// argument. >> +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::types::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(&raw mut (*self.as_raw()).dev) }; >> + } >> + >> + 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.cast::<bindings::hid_device>().as_ptr()).dev) } >> + } >> +} >> + >> +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 hid_device`. >> + let dev = unsafe { addr_of_mut!((*self.as_raw()).dev) }; >> + >> + // SAFETY: `dev` points to a valid `struct device`. >> + unsafe { device::Device::from_raw(dev) } >> + } >> +} >> + >> +/// Abstraction for the HID device ID structure `struct hid_device_id`. >> +#[repr(transparent)] >> +#[derive(Clone, Copy)] >> +pub struct DeviceId(bindings::hid_device_id); >> + >> +impl DeviceId { >> + /// Equivalent to C's `HID_USB_DEVICE` macro. >> + /// >> + /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID >> + /// number. > > The main description should be the first sentence, and C equivalent sentence > follows. > >> + pub const fn new_usb(group: Group, vendor: u32, product: u32) -> Self { >> + Self(bindings::hid_device_id { >> + bus: 0x3, // BUS_USB >> + group: group.into_u16(), >> + vendor, >> + product, >> + driver_data: 0, >> + }) >> + } >> + >> + /// Returns the HID transport bus ID. >> + pub fn bus(&self) -> u16 { >> + self.0.bus >> + } >> + >> + /// Returns the HID report group. >> + pub fn group(&self) -> Result<Group, &'static str> { >> + self.0.group.try_into() >> + } >> + >> + /// Returns the HID vendor ID. >> + pub fn vendor(&self) -> u32 { >> + self.0.vendor >> + } >> + >> + /// Returns the HID product ID. >> + pub fn product(&self) -> u32 { >> + self.0.product >> + } >> +} >> + >> +// SAFETY: >> +// * `DeviceId` is a `#[repr(transparent)` wrapper of `hid_device_id` and does not add >> +// additional invariants, so it's safe to transmute to `RawType`. >> +// * `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. >> +unsafe impl RawDeviceId for DeviceId { >> + type RawType = bindings::hid_device_id; >> +} >> + >> +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. >> +unsafe impl RawDeviceIdIndex for DeviceId { >> + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::hid_device_id, driver_data); >> + >> + fn index(&self) -> usize { >> + self.0.driver_data >> + } >> +} >> + >> +/// [`IdTable`] type for HID. >> +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>; >> + >> +/// Create a HID [`IdTable`] with its alias for modpost. >> +#[macro_export] >> +macro_rules! hid_device_table { >> + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { >> + const $table_name: $crate::device_id::IdArray< >> + $crate::hid::DeviceId, >> + $id_info_type, >> + { $table_data.len() }, >> + > = $crate::device_id::IdArray::new($table_data); >> + >> + $crate::module_device_table!("hid", $module_table_name, $table_name); >> + }; >> +} >> + >> +/// The HID driver trait. >> +/// >> +/// # Examples >> +/// >> +/// ``` >> +/// use kernel::{bindings, device, hid}; >> +/// >> +/// struct MyDriver; >> +/// >> +/// kernel::hid_device_table!( >> +/// HID_TABLE, >> +/// MODULE_HID_TABLE, >> +/// <MyDriver as hid::Driver>::IdInfo, >> +/// [( >> +/// hid::DeviceId::new_usb( >> +/// hid::Group::Steam, >> +/// bindings::USB_VENDOR_ID_VALVE, >> +/// bindings::USB_DEVICE_ID_STEAM_DECK, >> +/// ), >> +/// (), >> +/// )] >> +/// ); >> +/// >> +/// #[vtable] >> +/// impl hid::Driver for MyDriver { >> +/// type IdInfo = (); >> +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; >> +/// >> +/// /// This function is optional to implement. >> +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { >> +/// // Perform some report descriptor fixup. >> +/// rdesc >> +/// } >> +/// } >> +/// ``` >> +/// Drivers must implement this trait in order to get a HID driver registered. >> +/// Please refer to the `Adapter` documentation for an example. >> +#[vtable] >> +pub trait Driver: Send { >> + /// The type holding information about each device id supported by the driver. >> + // TODO: Use `associated_type_defaults` once stabilized: >> + // >> + // ``` >> + // type IdInfo: 'static = (); >> + // ``` >> + type IdInfo: 'static; >> + >> + /// The table of device ids supported by the driver. >> + const ID_TABLE: IdTable<Self::IdInfo>; >> + >> + /// Called before report descriptor parsing. Can be used to mutate the >> + /// report descriptor before the core HID logic processes the descriptor. >> + /// Useful for problematic report descriptors that prevent HID devices from >> + /// functioning correctly. >> + /// >> + /// Optional to implement. >> + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] { > > I think this can just use a single lifetime? > I think the problem is when a driver decides to replace the _rdesc with a static lifetime report descriptor declared in the device driver. My example driver does not do this, but this is another common pattern in C HID drivers. This is why two separate lifetimes were required. >> + build_error!(VTABLE_DEFAULT_ERROR) >> + } >> +} >> + >> +/// An adapter for the registration of HID drivers. >> +pub struct Adapter<T: Driver>(T); >> + >> +// SAFETY: >> +// - `bindings::hid_driver` is a C type declared as `repr(C)`. >> +// - `T` is the type of the driver's device private data. >> +// - `struct hid_driver` embeds a `struct device_driver`. >> +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`. >> +unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> { >> + type DriverType = bindings::hid_driver; >> + type DriverData = T; >> + const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver); >> +} >> + >> +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if >> +// a preceding call to `register` has been successful. >> +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> { >> + unsafe fn register( >> + hdrv: &Opaque<Self::DriverType>, >> + name: &'static CStr, >> + module: &'static ThisModule, >> + ) -> Result { >> + // SAFETY: It's safe to set the fields of `struct hid_driver` on initialization. >> + unsafe { >> + (*hdrv.get()).name = name.as_char_ptr(); >> + (*hdrv.get()).id_table = T::ID_TABLE.as_ptr(); >> + (*hdrv.get()).report_fixup = if T::HAS_REPORT_FIXUP { >> + Some(Self::report_fixup_callback) >> + } else { >> + None >> + }; >> + } >> + >> + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` >> + to_result(unsafe { >> + bindings::__hid_register_driver(hdrv.get(), module.0, name.as_char_ptr()) >> + }) >> + } >> + >> + unsafe fn unregister(hdrv: &Opaque<Self::DriverType>) { >> + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` >> + unsafe { bindings::hid_unregister_driver(hdrv.get()) } >> + } >> +} >> + >> +impl<T: Driver + 'static> Adapter<T> { >> + extern "C" fn report_fixup_callback( >> + hdev: *mut bindings::hid_device, >> + buf: *mut u8, >> + size: *mut kernel::ffi::c_uint, >> + ) -> *const u8 { >> + // SAFETY: The HID subsystem only ever calls the report_fixup callback >> + // with a valid pointer to a `struct hid_device`. >> + // >> + // INVARIANT: `hdev` is valid for the duration of >> + // `report_fixup_callback()`. >> + let hdev = unsafe { &*hdev.cast::<Device<device::Core>>() }; >> + >> + // SAFETY: The HID subsystem only ever calls the report_fixup callback >> + // with a valid pointer to a `kernel::ffi::c_uint`. >> + // >> + // INVARIANT: `size` is valid for the duration of >> + // `report_fixup_callback()`. >> + let buf_len: usize = match unsafe { *size }.try_into() { > > This can just be a cast. In all kernel envs `usize` is at least as large as `u32`. > >> + Ok(len) => len, >> + Err(e) => { >> + dev_err!( >> + hdev.as_ref(), >> + "Cannot fix report description due to {:?}!\n", >> + e >> + ); >> + >> + return buf; >> + } >> + }; >> + >> + // Build a mutable Rust slice from `buf` and `size`. >> + // >> + // SAFETY: The HID subsystem only ever calls the `report_fixup callback` >> + // with a valid pointer to a `u8` buffer. >> + // >> + // INVARIANT: `buf` is valid for the duration of >> + // `report_fixup_callback()`. >> + let rdesc_slice = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; >> + let rdesc_slice = T::report_fixup(hdev, rdesc_slice); >> + >> + match rdesc_slice.len().try_into() { > > I'm somewhat inclined to just `BUG()` (i.e. `.expect()`) in this case. A driver > that hits this error condition would need to leak >= 4G of memory to satisfy the > lifetime bound. > >> + // SAFETY: The HID subsystem only ever calls the report_fixup >> + // callback with a valid pointer to a `kernel::ffi::c_uint`. >> + // >> + // INVARIANT: `size` is valid for the duration of >> + // `report_fixup_callback()`. >> + Ok(len) => unsafe { *size = len }, >> + Err(e) => { >> + dev_err!( >> + hdev.as_ref(), > > as_ref() shouldn't be needed now. > > Best, > Gary > Thanks for the feedback. Aside from my concern regarding the two separate lifetimes, I think all the feedback you provided made sense to me. Applying towards my v7. Thanks, Rahul Rameshbabu >> + "Fixed report description will not be used due to {:?}!\n", >> + e >> + ); >> + >> + return buf; >> + } >> + } >> + >> + rdesc_slice.as_ptr() >> + } >> +} >> + >> +/// Declares a kernel module that exposes a single HID driver. >> +/// >> +/// # Examples >> +/// >> +/// ```ignore >> +/// kernel::module_hid_driver! { >> +/// type: MyDriver, >> +/// name: "Module name", >> +/// authors: ["Author name"], >> +/// description: "Description", >> +/// license: "GPL", >> +/// } >> +/// ``` >> +#[macro_export] >> +macro_rules! module_hid_driver { >> + ($($f:tt)*) => { >> + $crate::module_driver!(<T>, $crate::hid::Adapter<T>, { $($f)* }); >> + }; >> +} >> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs >> index 3da92f18f4ee..e2dcacd9369e 100644 >> --- a/rust/kernel/lib.rs >> +++ b/rust/kernel/lib.rs >> @@ -102,6 +102,8 @@ >> pub mod id_pool; >> #[doc(hidden)] >> pub mod impl_flags; >> +#[cfg(CONFIG_RUST_HID_ABSTRACTIONS)] >> +pub mod hid; >> pub mod init; >> pub mod io; >> pub mod ioctl; ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers 2026-03-01 6:48 ` Rahul Rameshbabu @ 2026-03-01 12:30 ` Alice Ryhl 2026-03-01 13:17 ` Gary Guo 1 sibling, 0 replies; 11+ messages in thread From: Alice Ryhl @ 2026-03-01 12:30 UTC (permalink / raw) To: Rahul Rameshbabu Cc: Gary Guo, linux-input, linux-kernel, rust-for-linux, a.hindborg, alex.gaynor, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, jikos, ojeda, peter.hutterer, tmgross On Sun, Mar 01, 2026 at 06:48:20AM +0000, Rahul Rameshbabu wrote: > On Fri, 27 Feb, 2026 15:27:59 +0000 "Gary Guo" <gary@garyguo.net> wrote: > > On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote: > >> +/// The HID driver trait. > >> +/// > >> +/// # Examples > >> +/// > >> +/// ``` > >> +/// use kernel::{bindings, device, hid}; > >> +/// > >> +/// struct MyDriver; > >> +/// > >> +/// kernel::hid_device_table!( > >> +/// HID_TABLE, > >> +/// MODULE_HID_TABLE, > >> +/// <MyDriver as hid::Driver>::IdInfo, > >> +/// [( > >> +/// hid::DeviceId::new_usb( > >> +/// hid::Group::Steam, > >> +/// bindings::USB_VENDOR_ID_VALVE, > >> +/// bindings::USB_DEVICE_ID_STEAM_DECK, > >> +/// ), > >> +/// (), > >> +/// )] > >> +/// ); > >> +/// > >> +/// #[vtable] > >> +/// impl hid::Driver for MyDriver { > >> +/// type IdInfo = (); > >> +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; > >> +/// > >> +/// /// This function is optional to implement. > >> +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { > >> +/// // Perform some report descriptor fixup. > >> +/// rdesc > >> +/// } > >> +/// } > >> +/// ``` > >> +/// Drivers must implement this trait in order to get a HID driver registered. > >> +/// Please refer to the `Adapter` documentation for an example. > >> +#[vtable] > >> +pub trait Driver: Send { > >> + /// The type holding information about each device id supported by the driver. > >> + // TODO: Use `associated_type_defaults` once stabilized: > >> + // > >> + // ``` > >> + // type IdInfo: 'static = (); > >> + // ``` > >> + type IdInfo: 'static; > >> + > >> + /// The table of device ids supported by the driver. > >> + const ID_TABLE: IdTable<Self::IdInfo>; > >> + > >> + /// Called before report descriptor parsing. Can be used to mutate the > >> + /// report descriptor before the core HID logic processes the descriptor. > >> + /// Useful for problematic report descriptors that prevent HID devices from > >> + /// functioning correctly. > >> + /// > >> + /// Optional to implement. > >> + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] { > > > > I think this can just use a single lifetime? > > > > I think the problem is when a driver decides to replace the _rdesc with > a static lifetime report descriptor declared in the device driver. My > example driver does not do this, but this is another common pattern in C > HID drivers. This is why two separate lifetimes were required. I'm not entirely sure what you mean, but adding a lifetime here does not change what this function is allowed to do at all. Alice ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers 2026-03-01 6:48 ` Rahul Rameshbabu 2026-03-01 12:30 ` Alice Ryhl @ 2026-03-01 13:17 ` Gary Guo 1 sibling, 0 replies; 11+ messages in thread From: Gary Guo @ 2026-03-01 13:17 UTC (permalink / raw) To: Rahul Rameshbabu, Gary Guo Cc: linux-input, linux-kernel, rust-for-linux, a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, jikos, ojeda, peter.hutterer, tmgross On Sun Mar 1, 2026 at 6:48 AM GMT, Rahul Rameshbabu wrote: > On Fri, 27 Feb, 2026 15:27:59 +0000 "Gary Guo" <gary@garyguo.net> wrote: >> On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote: >>> These abstractions enable the development of HID drivers in Rust by binding >>> with the HID core C API. They provide Rust types that map to the >>> equivalents in C. In this initial draft, only hid_device and hid_device_id >>> are provided direct Rust type equivalents. hid_driver is specially wrapped >>> with a custom Driver type. The module_hid_driver! macro provides analogous >>> functionality to its C equivalent. Only the .report_fixup callback is >>> binded to Rust so far. >>> >>> Future work for these abstractions would include more bindings for common >>> HID-related types, such as hid_field, hid_report_enum, and hid_report as >>> well as more bus callbacks. Providing Rust equivalents to useful core HID >>> functions will also be necessary for HID driver development in Rust. >>> >>> Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com> [snip] >>> +pub trait Driver: Send { >>> + /// The type holding information about each device id supported by the driver. >>> + // TODO: Use `associated_type_defaults` once stabilized: >>> + // >>> + // ``` >>> + // type IdInfo: 'static = (); >>> + // ``` >>> + type IdInfo: 'static; >>> + >>> + /// The table of device ids supported by the driver. >>> + const ID_TABLE: IdTable<Self::IdInfo>; >>> + >>> + /// Called before report descriptor parsing. Can be used to mutate the >>> + /// report descriptor before the core HID logic processes the descriptor. >>> + /// Useful for problematic report descriptors that prevent HID devices from >>> + /// functioning correctly. >>> + /// >>> + /// Optional to implement. >>> + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] { >> >> I think this can just use a single lifetime? >> > > I think the problem is when a driver decides to replace the _rdesc with > a static lifetime report descriptor declared in the device driver. My > example driver does not do this, but this is another common pattern in C > HID drivers. This is why two separate lifetimes were required. > Lifetime on references are covariant, so it is okay to convert `&'static [u8]` to `&'b mut [u8]`. Best, Gary ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver 2026-02-22 21:56 [PATCH v6 0/2] Initial work for Rust abstraction for HID device driver development Rahul Rameshbabu 2026-02-22 21:56 ` [PATCH v6 1/2] rust: core abstractions for HID drivers Rahul Rameshbabu @ 2026-02-22 21:56 ` Rahul Rameshbabu 2026-02-23 2:38 ` Greg KH 1 sibling, 1 reply; 11+ messages in thread From: Rahul Rameshbabu @ 2026-02-22 21:56 UTC (permalink / raw) To: linux-input, linux-kernel, rust-for-linux Cc: a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos, ojeda, peter.hutterer, tmgross, Rahul Rameshbabu Demonstrate how to perform a report fixup from a Rust HID driver. The mice specify the const flag incorrectly in the consumer input report descriptor, which leads to inputs being ignored. Correctly patch the report descriptor for the Model O and O- mice. Portions of the HID report post-fixup: device 0:0 ... 0x81, 0x06, // Input (Data,Var,Rel) 84 ... 0x81, 0x06, // Input (Data,Var,Rel) 112 ... 0x81, 0x06, // Input (Data,Var,Rel) 140 Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com> --- Notes: Changelog: v5->v6: * NONE v4->v5: * NONE v3->v4: * Removed specifying tree in MAINTAINERS file since that is up for debate * Minor rebase cleanup * Moved driver logic under drivers/hid/rust * Use hex instead of decimal for the report descriptor comparisons v2->v3: * Fixed docstring formatting * Updated MAINTAINERS file based on v1 and v2 discussion v1->v2: * Use vendor id and device id from drivers/hid/hid-ids.h bindings * Make use for dev_err! as appropriate MAINTAINERS | 6 +++ drivers/hid/hid-glorious.c | 2 + drivers/hid/hid_glorious_rust.rs | 60 +++++++++++++++++++++++++++ drivers/hid/rust/Kconfig | 16 +++++++ drivers/hid/rust/Makefile | 6 +++ drivers/hid/rust/hid_glorious_rust.rs | 60 +++++++++++++++++++++++++++ 6 files changed, 150 insertions(+) create mode 100644 drivers/hid/hid_glorious_rust.rs create mode 100644 drivers/hid/rust/Makefile create mode 100644 drivers/hid/rust/hid_glorious_rust.rs diff --git a/MAINTAINERS b/MAINTAINERS index 1fee14024fa2..1f9167221639 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10808,6 +10808,12 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/gigabyte-wmi.c +GLORIOUS RUST DRIVER [RUST] +M: Rahul Rameshbabu <sergeantsagara@protonmail.com> +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/rust/hid_glorious_rust.rs + GNSS SUBSYSTEM M: Johan Hovold <johan@kernel.org> S: Maintained diff --git a/drivers/hid/hid-glorious.c b/drivers/hid/hid-glorious.c index 5bbd81248053..d7362852c20f 100644 --- a/drivers/hid/hid-glorious.c +++ b/drivers/hid/hid-glorious.c @@ -76,8 +76,10 @@ static int glorious_probe(struct hid_device *hdev, } static const struct hid_device_id glorious_devices[] = { +#if !IS_ENABLED(CONFIG_HID_GLORIOUS_RUST) { HID_USB_DEVICE(USB_VENDOR_ID_SINOWEALTH, USB_DEVICE_ID_GLORIOUS_MODEL_O) }, +#endif { HID_USB_DEVICE(USB_VENDOR_ID_SINOWEALTH, USB_DEVICE_ID_GLORIOUS_MODEL_D) }, { HID_USB_DEVICE(USB_VENDOR_ID_LAVIEW, diff --git a/drivers/hid/hid_glorious_rust.rs b/drivers/hid/hid_glorious_rust.rs new file mode 100644 index 000000000000..8cffc1c605dd --- /dev/null +++ b/drivers/hid/hid_glorious_rust.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com> + +//! Rust reference HID driver for Glorious Model O and O- mice. + +use kernel::{self, bindings, device, hid, prelude::*}; + +struct GloriousRust; + +kernel::hid_device_table!( + HID_TABLE, + MODULE_HID_TABLE, + <GloriousRust as hid::Driver>::IdInfo, + [( + hid::DeviceId::new_usb( + hid::Group::Generic, + bindings::USB_VENDOR_ID_SINOWEALTH, + bindings::USB_DEVICE_ID_GLORIOUS_MODEL_O, + ), + (), + )] +); + +#[vtable] +impl hid::Driver for GloriousRust { + type IdInfo = (); + const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; + + /// Fix the Glorious Model O and O- consumer input report descriptor to use + /// the variable and relative flag, while clearing the const flag. + /// + /// Without this fixup, inputs from the mice will be ignored. + fn report_fixup<'a, 'b: 'a>(hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { + if rdesc.len() == 213 + && (rdesc[84] == 129 && rdesc[85] == 3) + && (rdesc[112] == 129 && rdesc[113] == 3) + && (rdesc[140] == 129 && rdesc[141] == 3) + { + dev_info!( + hdev.as_ref(), + "patching Glorious Model O consumer control report descriptor\n" + ); + + rdesc[85] = hid::MAIN_ITEM_VARIABLE | hid::MAIN_ITEM_RELATIVE; + rdesc[113] = hid::MAIN_ITEM_VARIABLE | hid::MAIN_ITEM_RELATIVE; + rdesc[141] = hid::MAIN_ITEM_VARIABLE | hid::MAIN_ITEM_RELATIVE; + } + + rdesc + } +} + +kernel::module_hid_driver! { + type: GloriousRust, + name: "GloriousRust", + authors: ["Rahul Rameshbabu <sergeantsagara@protonmail.com>"], + description: "Rust reference HID driver for Glorious Model O and O- mice", + license: "GPL", +} diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig index d3247651829e..d7a1bf26bed0 100644 --- a/drivers/hid/rust/Kconfig +++ b/drivers/hid/rust/Kconfig @@ -9,4 +9,20 @@ config RUST_HID_ABSTRACTIONS Adds support needed for HID drivers written in Rust. It provides a wrapper around the C hid core. +if RUST_HID_ABSTRACTIONS + +menu "Special HID drivers" + +config HID_GLORIOUS_RUST + tristate "Glorious O and O- mice Rust reference driver" + depends on USB_HID + depends on RUST_HID_ABSTRACTIONS + help + Support for Glorious PC Gaming Race O and O- mice + in Rust. + +endmenu # Special HID drivers + +endif # RUST_HID_ABSTRACTIONS + endmenu # Rust HID support diff --git a/drivers/hid/rust/Makefile b/drivers/hid/rust/Makefile new file mode 100644 index 000000000000..6676030a2f87 --- /dev/null +++ b/drivers/hid/rust/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Rust HID support +# + +obj-$(CONFIG_HID_GLORIOUS_RUST) += hid_glorious_rust.o diff --git a/drivers/hid/rust/hid_glorious_rust.rs b/drivers/hid/rust/hid_glorious_rust.rs new file mode 100644 index 000000000000..dfc3f2323b60 --- /dev/null +++ b/drivers/hid/rust/hid_glorious_rust.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com> + +//! Rust reference HID driver for Glorious Model O and O- mice. + +use kernel::{self, bindings, device, hid, prelude::*}; + +struct GloriousRust; + +kernel::hid_device_table!( + HID_TABLE, + MODULE_HID_TABLE, + <GloriousRust as hid::Driver>::IdInfo, + [( + hid::DeviceId::new_usb( + hid::Group::Generic, + bindings::USB_VENDOR_ID_SINOWEALTH, + bindings::USB_DEVICE_ID_GLORIOUS_MODEL_O, + ), + (), + )] +); + +#[vtable] +impl hid::Driver for GloriousRust { + type IdInfo = (); + const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE; + + /// Fix the Glorious Model O and O- consumer input report descriptor to use + /// the variable and relative flag, while clearing the const flag. + /// + /// Without this fixup, inputs from the mice will be ignored. + fn report_fixup<'a, 'b: 'a>(hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] { + if rdesc.len() == 213 + && (rdesc[84] == 0x81 && rdesc[85] == 0x3) + && (rdesc[112] == 0x81 && rdesc[113] == 0x3) + && (rdesc[140] == 0x81 && rdesc[141] == 0x3) + { + dev_info!( + hdev.as_ref(), + "patching Glorious Model O consumer control report descriptor\n" + ); + + rdesc[85] = hid::MAIN_ITEM_VARIABLE | hid::MAIN_ITEM_RELATIVE; + rdesc[113] = hid::MAIN_ITEM_VARIABLE | hid::MAIN_ITEM_RELATIVE; + rdesc[141] = hid::MAIN_ITEM_VARIABLE | hid::MAIN_ITEM_RELATIVE; + } + + rdesc + } +} + +kernel::module_hid_driver! { + type: GloriousRust, + name: "GloriousRust", + authors: ["Rahul Rameshbabu <sergeantsagara@protonmail.com>"], + description: "Rust reference HID driver for Glorious Model O and O- mice", + license: "GPL", +} -- 2.52.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver 2026-02-22 21:56 ` [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver Rahul Rameshbabu @ 2026-02-23 2:38 ` Greg KH 2026-02-23 2:52 ` Rahul Rameshbabu 0 siblings, 1 reply; 11+ messages in thread From: Greg KH @ 2026-02-23 2:38 UTC (permalink / raw) To: Rahul Rameshbabu Cc: linux-input, linux-kernel, rust-for-linux, a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos, ojeda, peter.hutterer, tmgross On Sun, Feb 22, 2026 at 09:56:51PM +0000, Rahul Rameshbabu wrote: > MAINTAINERS | 6 +++ > drivers/hid/hid-glorious.c | 2 + > drivers/hid/hid_glorious_rust.rs | 60 +++++++++++++++++++++++++++ > drivers/hid/rust/Kconfig | 16 +++++++ > drivers/hid/rust/Makefile | 6 +++ > drivers/hid/rust/hid_glorious_rust.rs | 60 +++++++++++++++++++++++++++ > 6 files changed, 150 insertions(+) > create mode 100644 drivers/hid/hid_glorious_rust.rs > create mode 100644 drivers/hid/rust/Makefile > create mode 100644 drivers/hid/rust/hid_glorious_rust.rs You seem to have the same file in here in two different locations :( ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver 2026-02-23 2:38 ` Greg KH @ 2026-02-23 2:52 ` Rahul Rameshbabu 0 siblings, 0 replies; 11+ messages in thread From: Rahul Rameshbabu @ 2026-02-23 2:52 UTC (permalink / raw) To: Greg KH Cc: linux-input, linux-kernel, rust-for-linux, a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos, ojeda, peter.hutterer, tmgross On Mon, 23 Feb, 2026 03:38:23 +0100 "Greg KH" <gregkh@linuxfoundation.org> wrote: > On Sun, Feb 22, 2026 at 09:56:51PM +0000, Rahul Rameshbabu wrote: >> MAINTAINERS | 6 +++ >> drivers/hid/hid-glorious.c | 2 + >> drivers/hid/hid_glorious_rust.rs | 60 +++++++++++++++++++++++++++ >> drivers/hid/rust/Kconfig | 16 +++++++ >> drivers/hid/rust/Makefile | 6 +++ >> drivers/hid/rust/hid_glorious_rust.rs | 60 +++++++++++++++++++++++++++ >> 6 files changed, 150 insertions(+) >> create mode 100644 drivers/hid/hid_glorious_rust.rs >> create mode 100644 drivers/hid/rust/Makefile >> create mode 100644 drivers/hid/rust/hid_glorious_rust.rs > > You seem to have the same file in here in two different locations :( Yeah, I noticed this right after sending out the patches.... drivers/hid/hid_glorious_rust.rs was meant to be removed. https://lore.kernel.org/rust-for-linux/wjfjzjc626n55zvhksiyldobwubr2imbvfavqej333lvnka2wn@r4zfcjqtanvu/ I accidentally had a local copy in my work tree and managed to re-add it. I will need a v7 of this series either way, but that was unfortunate.... Thanks, Rahul Rameshbabu ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-03-01 13:17 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-02-22 21:56 [PATCH v6 0/2] Initial work for Rust abstraction for HID device driver development Rahul Rameshbabu 2026-02-22 21:56 ` [PATCH v6 1/2] rust: core abstractions for HID drivers Rahul Rameshbabu 2026-02-22 23:39 ` Terry Junge 2026-02-23 2:47 ` Rahul Rameshbabu 2026-02-27 15:27 ` Gary Guo 2026-03-01 6:48 ` Rahul Rameshbabu 2026-03-01 12:30 ` Alice Ryhl 2026-03-01 13:17 ` Gary Guo 2026-02-22 21:56 ` [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver Rahul Rameshbabu 2026-02-23 2:38 ` Greg KH 2026-02-23 2:52 ` Rahul Rameshbabu
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox