From nobody Wed Apr 1 22:21:55 2026 Received: from mail-24416.protonmail.ch (mail-24416.protonmail.ch [109.224.244.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DE65B3806BB for ; Sat, 14 Mar 2026 21:56:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=109.224.244.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773525397; cv=none; b=hyVpMI591KGEG2Mnmuhv1OX3VfQBGEOBWkk4S6OBYLA4B4Cj2v5FtPV6Q7rqRi81lGg+fONLW557gBGe+Ac3MlQTyjYxoS1hpon0puOzpwGcy9QPP4pco3RtinKSlyPsWQo87eNZryF14uYAijxFAC0MaKzeJPUohb5Uy13k8VA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773525397; c=relaxed/simple; bh=0+ZjA4QHnlc8O1yj+aZppGz3XsQG80Tu0bZZPZRifzk=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=n5KQasdYAe6gNoppvqOHiRpR6QrW/kgbzPib0pX4VTQpLRrobpFZaL8adWNouoOscpCsBYgX818DSibaCb/6dZW/L1YBhjuk/3bRplVmRIULlQ6XYZPXFsRtM/kT1WCiig/wE+uWX0j2Q2Qvnqg74PDGM4V3fzlLHy6Lbak91ho= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=protonmail.com; spf=pass smtp.mailfrom=protonmail.com; dkim=pass (2048-bit key) header.d=protonmail.com header.i=@protonmail.com header.b=x91n+6Q/; arc=none smtp.client-ip=109.224.244.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=protonmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=protonmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=protonmail.com header.i=@protonmail.com header.b="x91n+6Q/" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com; s=protonmail3; t=1773525390; x=1773784590; bh=9TvS7U0k0vJSFxz/9zk2qVTql2xYA5GNpDbpWY9PXGY=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=x91n+6Q/gsgYsZA5Uys+n/XnGk1GBn9bp7uw22bMBYFVmuz33ej5isiON7NX3zSJb pkaZb6G8zWKEKOdtlzwuNPwN2bVtsVJQoeZ/UTLtEnreLsPvJyDiTWMn/lDIynQFYG 8Xpsd2dxWBi39X7QOvyreajbDkbbzhiJygjEMA1UV5Bc0osrh79pH0sDg1D+XJBOpU M6u7I0mELCggyLXFnLbc94+MvMUJm5ZHXilmXfTMQKwxerH8aPBErfiRHF9I874MBc AZqe3LbhNrSdoqtlBUARmc1eKy0dTDavIFlaqeY1xz9IYlK3zjGF8r6cui3eMoHUkr cL2PGdcUhTS8w== Date: Sat, 14 Mar 2026 21:56:24 +0000 To: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org From: Rahul Rameshbabu Cc: a.hindborg@kernel.org, alex.gaynor@gmail.com, aliceryhl@google.com, benjamin.tissoires@redhat.com, benno.lossin@proton.me, bjorn3_gh@protonmail.com, boqun.feng@gmail.com, dakr@kernel.org, db48x@db48x.net, gary@garyguo.net, jikos@kernel.org, ojeda@kernel.org, peter.hutterer@who-t.net, tmgross@umich.edu, linuxhid@cosmicgizmosystems.com, Rahul Rameshbabu Subject: [PATCH v7 1/2] rust: core abstractions for HID drivers Message-ID: <20260314215527.57575-2-sergeantsagara@protonmail.com> In-Reply-To: <20260314215527.57575-1-sergeantsagara@protonmail.com> References: <20260314215527.57575-1-sergeantsagara@protonmail.com> Feedback-ID: 26003777:user:proton X-Pm-Message-ID: 100a9830eeab3901f41a2d7022fa2bea768fbc65 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- Notes: Changelog: =20 v6->v7: * Implemented HID items as bitflags * Used #[inline] for into_u16 and Device::as_raw * Defined a custom GroupUnknownError type * Fixed the description of DeviceId::new_usb * Made report_fixup use a single lifetime value due to references being covariant * Simplified error handling in report_fixup_callback v5->v6: * Converted From for Group to TryFrom 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 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 s= truct 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 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 | 509 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 7 files changed, 538 insertions(+) create mode 100644 drivers/hid/rust/Kconfig create mode 100644 rust/kernel/hid.rs diff --git a/MAINTAINERS b/MAINTAINERS index 77fdfcb55f06..be3638209bf3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11316,6 +11316,14 @@ F: include/uapi/linux/hid* F: samples/hid/ F: tools/testing/selftests/hid/ =20 +HID CORE LAYER [RUST] +M: Rahul Rameshbabu +R: Benjamin Tissoires +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/rust/*.rs +F: rust/kernel/hid.rs + HID LOGITECH DRIVERS R: Filipe La=C3=ADns 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 =20 source "drivers/hid/bpf/Kconfig" =20 +source "drivers/hid/rust/Kconfig" + source "drivers/hid/i2c-hid/Kconfig" =20 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) +=3D hid-haptic.o =20 obj-$(CONFIG_HID_BPF) +=3D bpf/ =20 +obj-$(CONFIG_RUST_HID_ABSTRACTIONS) +=3D rust/ + obj-$(CONFIG_HID) +=3D hid.o obj-$(CONFIG_UHID) +=3D uhid.o =20 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=3Dy + 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_helpe= r.h index 083cc44aa952..200e58af27a3 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,8 @@ #include #include #include +#include +#include "../../drivers/hid/hid-ids.h" #include #include #include diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs new file mode 100644 index 000000000000..cae9b1980553 --- /dev/null +++ b/rust/kernel/hid.rs @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Rahul Rameshbabu + +//! Abstractions for the HID interface. +//! +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h) + +use core::{ + marker::PhantomData, + ptr::{ + addr_of_mut, + NonNull, // + } // +}; +use crate::{ + device, + device_id::{ + RawDeviceId, + RawDeviceIdIndex, // + }, + driver, + error::*, + impl_flags, + prelude::*, + types::Opaque, // +}; +use kernel::fmt; + +impl_flags!( + /// Represents a combination of HID items. + #[derive(Debug, Clone, Default, Copy, PartialEq, Eq)] + pub struct Items(u32); + + /// Represents a single HID item. + /// + /// 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 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum Item { + /// Indicates the item is static read-only. + Constant =3D bindings::HID_MAIN_ITEM_CONSTANT, + + /// Indicates the item represents data from a physical control. + Variable =3D bindings::HID_MAIN_ITEM_VARIABLE, + + /// Indicates the item should be treated as a relative change from= the + /// previous report. + Relative =3D bindings::HID_MAIN_ITEM_RELATIVE, + + /// Indicates the item should wrap around when reaching the extrem= e high + /// or extreme low values. + Wrap =3D bindings::HID_MAIN_ITEM_WRAP, + + /// Indicates the item should wrap around when reaching the extrem= e high + /// or extreme low values. + NonLinear =3D bindings::HID_MAIN_ITEM_NONLINEAR, + + /// Indicates whether the control has a preferred state it will + /// physically return to without user intervention. + NoPreferred =3D bindings::HID_MAIN_ITEM_NO_PREFERRED, + + /// Indicates whether the control has a physical state where it wi= ll not + /// send any reports. + NullState =3D bindings::HID_MAIN_ITEM_NULL_STATE, + + /// Indicates whether the control requires host system logic to ch= ange + /// state. + Volatile =3D bindings::HID_MAIN_ITEM_VOLATILE, + + /// Indicates whether the item is fixed size or a variable buffer = of + /// bytes. + BufferedBytw =3D bindings::HID_MAIN_ITEM_BUFFERED_BYTE, + } +); + +/// 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 correctl= y. +#[repr(u16)] +pub enum Group { + /// Used to match a device against any group when probing. + Any =3D bindings::HID_GROUP_ANY as u16, + + /// Indicates a generic device that should need no custom logic from t= he + /// core HID stack. + Generic =3D bindings::HID_GROUP_GENERIC as u16, + + /// Maps multitouch devices to hid-multitouch instead of hid-generic. + Multitouch =3D bindings::HID_GROUP_MULTITOUCH as u16, + + /// Used for autodetecing and mapping of HID sensor hubs to + /// hid-sensor-hub. + SensorHub =3D bindings::HID_GROUP_SENSOR_HUB as u16, + + /// Used for autodetecing and mapping Win 8 multitouch devices to set = the + /// needed quirks. + MultitouchWin8 =3D 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 ever= ything + /// else will be managed by hid-rmi. + RMI =3D bindings::HID_GROUP_RMI as u16, + + /// Used for hid-core handling to automatically identify Wacom devices= and + /// have them probed by hid-wacom. + Wacom =3D 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 w= ith + /// the device driver. + LogitechDJDevice =3D 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 =3D bindings::HID_GROUP_STEAM as u16, + + /// Used to differentiate 27 Mhz frequency Logitech DJ devices from ot= her + /// Logitech DJ devices. + Logitech27MHzDevice =3D bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u= 16, + + /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi. + Vivaldi =3D bindings::HID_GROUP_VIVALDI as u16, +} + +// TODO: use `const_trait_impl` once stabilized: +// +// ``` +// impl const From 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`]. + #[inline] + const fn into_u16(self) -> u16 { + self as u16 + } +} + +/// Error type used when a HID group value from C cannot be safely represe= nted +/// with the Rust `Group` abstraction. +#[derive(Debug, Clone)] +pub struct GroupUnknownError(u16); + +impl From for u16 { + fn from(value: GroupUnknownError) -> Self { + value.0 + } +} + +impl fmt::Display for GroupUnknownError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Unknown HID group {:#x} encountered!", self.0) + } +} + +impl TryFrom for Group { + type Error =3D GroupUnknownError; + + /// Valid [`u16`] values can be converted to [`Group`] variants. + fn try_from(value: u16) -> Result { + match value.into() { + bindings::HID_GROUP_GENERIC =3D> Ok(Group::Generic), + bindings::HID_GROUP_MULTITOUCH =3D> Ok(Group::Multitouch), + bindings::HID_GROUP_SENSOR_HUB =3D> Ok(Group::SensorHub), + bindings::HID_GROUP_MULTITOUCH_WIN_8 =3D> Ok(Group::Multitouch= Win8), + bindings::HID_GROUP_RMI =3D> Ok(Group::RMI), + bindings::HID_GROUP_WACOM =3D> Ok(Group::Wacom), + bindings::HID_GROUP_LOGITECH_DJ_DEVICE =3D> Ok(Group::Logitech= DJDevice), + bindings::HID_GROUP_STEAM =3D> Ok(Group::Steam), + bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE =3D> Ok(Group::Logit= ech27MHzDevice), + bindings::HID_GROUP_VIVALDI =3D> Ok(Group::Vivaldi), + _ =3D> Err(GroupUnknownError(value)), + } + } +} + +/// The HID device representation. +/// +/// This structure represents the Rust abstraction for a C `struct hid_dev= ice`. +/// 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 b= y the +/// C portion of the kernel. +#[repr(transparent)] +pub struct Device( + Opaque, + PhantomData, +); + +impl Device { + #[inline] + 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_devic= e` + unsafe { *self.as_raw() }.bus + } + + /// Returns the HID report group. + pub fn group(&self) -> Result { + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_devic= e` + 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_devic= e` + 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_devic= e` + 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) { + // SAFETY: The safety requirements guarantee that the refcount is = non-zero. + unsafe { bindings::put_device(&raw mut (*obj.cast::().as_ptr()).dev) } + } +} + +impl AsRef> for Device { + fn as_ref(&self) -> &device::Device { + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a p= ointer to a valid + // `struct hid_device`. + let dev =3D 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 { + /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID + /// number. + /// + /// Equivalent to C's `HID_USB_DEVICE` macro. + 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 { + 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 =3D 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 =3D core::mem::offset_of!(bindings::hi= d_device_id, driver_data); + + fn index(&self) -> usize { + self.0.driver_data + } +} + +/// [`IdTable`] type for HID. +pub type IdTable =3D &'static dyn kernel::device_id::IdTable; + +/// 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, $tabl= e_data: expr) =3D> { + const $table_name: $crate::device_id::IdArray< + $crate::hid::DeviceId, + $id_info_type, + { $table_data.len() }, + > =3D $crate::device_id::IdArray::new($table_data); + + $crate::module_device_table!("hid", $module_table_name, $table_nam= e); + }; +} + +/// The HID driver trait. +/// +/// # Examples +/// +/// ``` +/// use kernel::{bindings, device, hid}; +/// +/// struct MyDriver; +/// +/// kernel::hid_device_table!( +/// HID_TABLE, +/// MODULE_HID_TABLE, +/// ::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 =3D (); +/// const ID_TABLE: hid::IdTable =3D &HID_TABLE; +/// +/// /// This function is optional to implement. +/// fn report_fixup<'a>(_hdev: &hid::Device, rdesc: &'a = mut [u8]) -> &'a [u8] { +/// // Perform some report descriptor fixup. +/// rdesc +/// } +/// } +/// ``` +/// Drivers must implement this trait in order to get a HID driver registe= red. +/// 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 =3D (); + // ``` + type IdInfo: 'static; + + /// The table of device ids supported by the driver. + const ID_TABLE: IdTable; + + /// Called before report descriptor parsing. Can be used to mutate the + /// report descriptor before the core HID logic processes the descript= or. + /// Useful for problematic report descriptors that prevent HID devices= from + /// functioning correctly. + /// + /// Optional to implement. + fn report_fixup<'a>(_hdev: &Device, _rdesc: &'a mut [u8]= ) -> &'a [u8] { + build_error!(VTABLE_DEFAULT_ERROR) + } +} + +/// An adapter for the registration of HID drivers. +pub struct Adapter(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 `st= ruct device_driver`. +unsafe impl driver::DriverLayout for Adapter { + type DriverType =3D bindings::hid_driver; + type DriverData =3D T; + const DEVICE_DRIVER_OFFSET: usize =3D core::mem::offset_of!(Self::Driv= erType, 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 driver::RegistrationOps for Adapter { + unsafe fn register( + hdrv: &Opaque, + name: &'static CStr, + module: &'static ThisModule, + ) -> Result { + // SAFETY: It's safe to set the fields of `struct hid_driver` on i= nitialization. + unsafe { + (*hdrv.get()).name =3D name.as_char_ptr(); + (*hdrv.get()).id_table =3D T::ID_TABLE.as_ptr(); + (*hdrv.get()).report_fixup =3D 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) { + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType` + unsafe { bindings::hid_unregister_driver(hdrv.get()) } + } +} + +impl Adapter { + 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 call= back + // with a valid pointer to a `struct hid_device`. + // + // INVARIANT: `hdev` is valid for the duration of + // `report_fixup_callback()`. + let hdev =3D unsafe { &*hdev.cast::>() }; + + // SAFETY: The HID subsystem only ever calls the report_fixup call= back + // 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 =3D unsafe { *size } as usize; + + // Build a mutable Rust slice from `buf` and `size`. + // + // SAFETY: The HID subsystem only ever calls the `report_fixup cal= lback` + // with a valid pointer to a `u8` buffer. + // + // INVARIANT: `buf` is valid for the duration of + // `report_fixup_callback()`. + let rdesc_slice =3D unsafe { core::slice::from_raw_parts_mut(buf, = buf_len) }; + let rdesc_slice =3D T::report_fixup(hdev, rdesc_slice); + + // HID report descriptors are typically not gigabytes in size. A R= ust + // `panic!` can only be triggered if a report descriptor greater t= han + // 4GB is used. + let tmp_size: kernel::ffi::c_uint =3D rdesc_slice + .len() + .try_into() + .expect("Fixed report descriptor is too large!"); + + // SAFETY: The HID subsystem only ever calls the report_fixup call= back + // with a valid pointer to a `kernel::ffi::c_uint`. + // + // INVARIANT: `size` is valid for the duration of + // `report_fixup_callback()`. + unsafe { *size =3D tmp_size }; + + 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)*) =3D> { + $crate::module_driver!(, $crate::hid::Adapter, { $($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; --=20 2.52.0 From nobody Wed Apr 1 22:21:55 2026 Received: from mail-106121.protonmail.ch (mail-106121.protonmail.ch [79.135.106.121]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2AD13384224 for ; Sat, 14 Mar 2026 21:56:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.121 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773525411; cv=none; b=YvY4KLJTq9vKF9wxMPhIHGb5Dg7t8womnteA3fPlnAL6Zf6RoSRdEBt01KtVQzOXtddzrGhDWc0V2+An6nKVId+q87r6cJ7oX0ws2/Xr9ZY8XXY6WfwSdsk5oYv/0gXY2wMUkhWdD9ORlsfdZqnfGfweXRX1rCvzXNVSg0BKd+o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773525411; c=relaxed/simple; bh=pGIfi+gJfbYBcYeRmdqBYUu+37Q7jVeRyZuzBSmy+Fo=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=WTGISgEaQxTgoEkvh3XhQaV9eQuLpMkKdH9tIX28h2OlwBZTvUTv/GT9XR+b6Sb0FBGtDtKn/pnG0tD8bnxIOYIGf+qDYzUWiB0aXAFuTI/b4gUiT/rVRlTtwCa+iIlOdsM5nzSdZQTq5dbWyWlZgKgH68rn0Y+sLXIcw0ZDVN4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=protonmail.com; spf=pass smtp.mailfrom=protonmail.com; dkim=pass (2048-bit key) header.d=protonmail.com header.i=@protonmail.com header.b=Q3RCptXb; arc=none smtp.client-ip=79.135.106.121 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=protonmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=protonmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=protonmail.com header.i=@protonmail.com header.b="Q3RCptXb" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com; s=protonmail3; t=1773525402; x=1773784602; bh=FpxDasQC2M25ArxR1D/QwcKHAY17oNNAdzqo9i0QNTs=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=Q3RCptXbz0Z6nZNPDlBiRofdy78E57MCNgA+pCZ6OuJCW68RqNSXHy0QiPIxtl9R8 1f90fzuXQfFEEM2SPgVCc51A7jikxpChWb2xzjaxDF4MGmr9kAxxKf5W2zsaSDybj3 YO+JFkuJUzQW3vpwCkv3YXve18ji9tB8wSchz8nVrm2Rkj8MxSi/bAXojh3b/JKVbX +cB0IDhyjwsgIUidAZZC4NvMJwZxoO0DK3jN0R7jpqXmsubFa2I1xdgHnhrkMSsz4j 6BkJ8bjAz3rPLxKlUvNOoN0qWruae/fXFomlnpegsmhoaynAzYdREDp8KgH/0R1Qup 21Ygm024rnqjQ== Date: Sat, 14 Mar 2026 21:56:35 +0000 To: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org From: Rahul Rameshbabu Cc: a.hindborg@kernel.org, alex.gaynor@gmail.com, aliceryhl@google.com, benjamin.tissoires@redhat.com, benno.lossin@proton.me, bjorn3_gh@protonmail.com, boqun.feng@gmail.com, dakr@kernel.org, db48x@db48x.net, gary@garyguo.net, jikos@kernel.org, ojeda@kernel.org, peter.hutterer@who-t.net, tmgross@umich.edu, linuxhid@cosmicgizmosystems.com, Rahul Rameshbabu Subject: [PATCH v7 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver Message-ID: <20260314215527.57575-3-sergeantsagara@protonmail.com> In-Reply-To: <20260314215527.57575-1-sergeantsagara@protonmail.com> References: <20260314215527.57575-1-sergeantsagara@protonmail.com> Feedback-ID: 26003777:user:proton X-Pm-Message-ID: d0012c9a737ccfda2a163f06a67ade57a61d47de Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- Notes: Changelog: =20 v6->v7: * Removed duplicate driver/hid/hid_glorious_rust.rs source * Made use of HID item bitflags * Updated report_fixup to use a single lifetime 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/rust/Kconfig | 16 +++++++ drivers/hid/rust/Makefile | 6 +++ drivers/hid/rust/hid_glorious_rust.rs | 67 +++++++++++++++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 drivers/hid/rust/Makefile create mode 100644 drivers/hid/rust/hid_glorious_rust.rs diff --git a/MAINTAINERS b/MAINTAINERS index be3638209bf3..66d977114efa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10805,6 +10805,12 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/gigabyte-wmi.c =20 +GLORIOUS RUST DRIVER [RUST] +M: Rahul Rameshbabu +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/rust/hid_glorious_rust.rs + GNSS SUBSYSTEM M: Johan Hovold 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, } =20 static const struct hid_device_id glorious_devices[] =3D { +#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/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. =20 +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) +=3D hid_glorious_rust.o diff --git a/drivers/hid/rust/hid_glorious_rust.rs b/drivers/hid/rust/hid_g= lorious_rust.rs new file mode 100644 index 000000000000..09951fcdfb0d --- /dev/null +++ b/drivers/hid/rust/hid_glorious_rust.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Rahul Rameshbabu + +//! 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, + ::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 =3D (); + const ID_TABLE: hid::IdTable =3D &HID_TABLE; + + /// Fix the Glorious Model O and O- consumer input report descriptor t= o 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>(hdev: &hid::Device, rdesc: &'a mut [= u8]) -> &'a [u8] { + if rdesc.len() =3D=3D 213 + && (rdesc[84] =3D=3D 0x81 && rdesc[85] =3D=3D 0x3) + && (rdesc[112] =3D=3D 0x81 && rdesc[113] =3D=3D 0x3) + && (rdesc[140] =3D=3D 0x81 && rdesc[141] =3D=3D 0x3) + { + dev_info!( + hdev.as_ref(), + "patching Glorious Model O consumer control report descrip= tor\n" + ); + + let desc_val =3D u32::from(hid::Item::Relative | hid::Item::Va= riable) as u8; + rdesc[85] =3D desc_val; + rdesc[113] =3D desc_val; + rdesc[141] =3D desc_val; + } + + rdesc + } +} + +kernel::module_hid_driver! { + type: GloriousRust, + name: "GloriousRust", + authors: ["Rahul Rameshbabu "], + description: "Rust reference HID driver for Glorious Model O and O- mi= ce", + license: "GPL", +} --=20 2.52.0