Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers
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
In-Reply-To: <20260222215611.79760-2-sergeantsagara@protonmail.com>



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

* [PATCH v6 2/2] rust: hid: Glorious PC Gaming Race Model O and O- mice reference driver
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
In-Reply-To: <20260222215611.79760-1-sergeantsagara@protonmail.com>

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

* [PATCH v6 1/2] rust: core abstractions for HID drivers
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
In-Reply-To: <20260222215611.79760-1-sergeantsagara@protonmail.com>

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

* [PATCH v6 0/2] Initial work for Rust abstraction for HID device driver development
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

* [6.18.] ThinkPad T14 Gen 2 AMD (LEN2073) - Synaptics touchpad remains PS/2, intertouch=1 ineffective, lost sync events
From: Rácz Máté @ 2026-02-22 18:37 UTC (permalink / raw)
  To: linux-input

Hello,

I am reporting a possible missing DMI quirk or ACPI limitation on:
Lenovo ThinkPad T14 Gen 2 (AMD) Ryzen 5 Pro 5650U
Touchpad PNP ID: LEN2073 (PNP0f13)

Kernel:
6.18.12-200.fc43.x86_64

Distribution:
Fedora 43 (stock kernel)

BIOS version:
R1MET62W (1.32 )

During boot I receive the following message:

  psmouse serio1: synaptics: Your touchpad (PNP: LEN2073 PNP0f13)
  says it can support a different bus. If i2c-hid and hid-rmi are not
  used, you might want to try setting psmouse.synaptics_intertouch=1
  and report this to linux-input@vger.kernel.org.

I tested with:

  psmouse.synaptics_intertouch=1

However, the device remains in PS/2 mode:

  SynPS/2 Synaptics TouchPad
  TPPS/2 Elan TrackPoint

No i2c-hid or hid-rmi device is created.
The system has functional I2C controllers, but the touchpad does not
enumerate as an I2C HID device.

Additionally, I am observing repeated:

  psmouse serio1: TouchPad ... lost sync at byte 1

events, which temporarily freeze the touchpad.

I have observed the same behavior on 6.18.9 and 6.18.10 as well.
The machine is relatively new to me, so I cannot confirm behavior on
older major kernel versions.

ACPI tables do not appear to expose LEN2073 as an I2C device.

Is this a missing DMI quirk, or is the platform firmware not exposing
the device via I2C on this model?

I can provide full dmesg and acpidump output if required.

Best regards,
Mate Racz

^ permalink raw reply

* Re: [PATCH 7/7] Input: cros_ec_keyb - factor out column processing
From: Tzung-Bi Shih @ 2026-02-22 10:48 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Fabio Baltieri, Benson Leung, Guenter Roeck, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-7-dmitry.torokhov@gmail.com>

On Sat, Feb 21, 2026 at 04:37:15PM -0800, Dmitry Torokhov wrote:
> +static void cros_ec_keyy_process_col(struct cros_ec_keyb *ckdev, int col,
> +				     u8 col_state, u8 changed)

What does the second 'y' of keyy stand for?

> +{
> +	for (int row = 0; row < ckdev->rows; row++) {
> +		if (changed & BIT(row)) {

Maybe

    if ((changed & BIT(row)) == 0)
        continue;
or
    if (~changed & BIT(row))
        continue;
or
    if (!test_bit(row, &changed))
        continue;

to save an indent level.

^ permalink raw reply

* Re: [PATCH 6/7] Input: cros_ec_keyb - do not allocate keyboard state separately
From: Tzung-Bi Shih @ 2026-02-22 10:48 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Fabio Baltieri, Benson Leung, Guenter Roeck, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-6-dmitry.torokhov@gmail.com>

On Sat, Feb 21, 2026 at 04:37:14PM -0800, Dmitry Torokhov wrote:
> Now that we know the upper bound for the number of columnts, and know
> that it is pretty small, there is no point in allocating it separately.
> We are wasting more memory tracking the allocations.
> 
> Embed valid_keys and old_kb_state directly into cros_ec_keyb structure.
> 
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Reviewed-by: Tzung-Bi Shih <tzungbi@kernel.org>

^ permalink raw reply

* Re: [PATCH 5/7] Input: cros_ec_keyb - simplify cros_ec_keyb_work()
From: Tzung-Bi Shih @ 2026-02-22 10:47 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Fabio Baltieri, Benson Leung, Guenter Roeck, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-5-dmitry.torokhov@gmail.com>

On Sat, Feb 21, 2026 at 04:37:13PM -0800, Dmitry Torokhov wrote:
> @@ -376,12 +378,10 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
>  		pm_wakeup_event(ckdev->dev, 0);
>  
>  		if (ckdev->ec->event_data.event_type == EC_MKBP_EVENT_BUTTON) {

This can also be simplified: `event_data->event_type`.

^ permalink raw reply

* Re: [PATCH 4/7] Input: cros_ec_keyb - use BIT() macro instead of open-coding shifts
From: Tzung-Bi Shih @ 2026-02-22 10:47 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Fabio Baltieri, Benson Leung, Guenter Roeck, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-4-dmitry.torokhov@gmail.com>

On Sat, Feb 21, 2026 at 04:37:12PM -0800, Dmitry Torokhov wrote:
> Using the macro clarifies the code.
> 
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Reviewed-by: Tzung-Bi Shih <tzungbi@kernel.org>

> @@ -780,8 +780,7 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
>  	idev->dev.parent = dev;
>  	idev->setkeycode = cros_ec_keyb_setkeycode;
>  
> -	ckdev->ghost_filter = device_property_read_bool(dev,
> -					"google,needs-ghost-filter");
> +	ckdev->ghost_filter = device_property_read_bool(dev, "google,needs-ghost-filter");

This change seems unrelated to the patch, but it's fine as-is.  I don't think
it requires a separate commit.

^ permalink raw reply

* Re: [PATCH 3/7] Input: cros_ec_keyb - use u8 instead of uint8_t
From: Tzung-Bi Shih @ 2026-02-22 10:47 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Fabio Baltieri, Benson Leung, Guenter Roeck, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-3-dmitry.torokhov@gmail.com>

On Sat, Feb 21, 2026 at 04:37:11PM -0800, Dmitry Torokhov wrote:
> In the kernel u8/u16/u32 are preferred to the uint*_t variants.
> 
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Reviewed-by: Tzung-Bi Shih <tzungbi@kernel.org>

^ permalink raw reply

* Re: [PATCH 2/7] Input: cros_ec_keyb - add function key support
From: Tzung-Bi Shih @ 2026-02-22 10:46 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Fabio Baltieri, Benson Leung, Guenter Roeck, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-2-dmitry.torokhov@gmail.com>

On Sat, Feb 21, 2026 at 04:37:10PM -0800, Dmitry Torokhov wrote:
> From: Fabio Baltieri <fabiobaltieri@chromium.org>
> 
> Add support for handling an Fn button and sending separate keycodes for
> a subset of keys in the matrix defined in the upper half of the keymap.
> 
> Signed-off-by: Fabio Baltieri <fabiobaltieri@chromium.org>
> Reviewed-by: Simon Glass <sjg@chromium.org>
> Link: https://patch.msgid.link/20260211173421.1206478-3-fabiobaltieri@chromium.org
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

The patch looks good to me, just a few minor nits below.  I don't insist on
these being fixed for this patch to be merged,

Reviewed-by: Tzung-Bi Shih <tzungbi@kernel.org>

> +static void cros_ec_keyb_process_key_fn_map(struct cros_ec_keyb *ckdev,
> +					    int row, int col, bool state)
> +{
> +	struct input_dev *idev = ckdev->idev;
> +	const unsigned short *keycodes = idev->keycode;
> +	unsigned int pos, fn_pos;
> +	unsigned int code, fn_code;

Nit: does declaring `code` and `fn_code` as unsigned short make more sense?
Or they can be declared in the same line.

> +
> +	pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
> +	code = keycodes[pos];
> +
> +	if (code == KEY_FN) {
> +		ckdev->fn_active = state;
> +		if (state) {
> +			ckdev->fn_combo_active = false;
> +		} else if (!ckdev->fn_combo_active) {
> +			/*
> +			 * Send both Fn press and release events if nothing
> +			 * else has been pressed together with Fn.
> +			 */
> +			cros_ec_emit_fn_key(idev, pos);
> +		}
> +		return;
> +	}
> +
> +	fn_pos = MATRIX_SCAN_CODE(row + ckdev->rows, col, ckdev->row_shift);
> +	fn_code = keycodes[fn_pos];
> +
> +	if (state) {
> +		if (ckdev->fn_active) {
> +			ckdev->fn_combo_active = true;
> +			if (!fn_code)
> +				return; /* Discard if no Fn mapping exists */
> +
> +			code = fn_code;
> +			pos = fn_pos;

Nit: assigning `pos` before `code` makes it neater.

> +		}
> +	} else {
> +		/*
> +		 * If the Fn-remapped code is currently pressed, release it.
> +		 * Otherwise, release the standard code (if it was pressed).
> +		 */
> +		if (fn_code && test_bit(fn_code, idev->key)) {
> +			code = fn_code;
> +			pos = fn_pos;

Nit: same here.

^ permalink raw reply

* Re: [PATCH 1/7] Input: export input_default_setkeycode
From: Tzung-Bi Shih @ 2026-02-22 10:46 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Fabio Baltieri, Benson Leung, Guenter Roeck, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

On Sat, Feb 21, 2026 at 04:37:09PM -0800, Dmitry Torokhov wrote:
> From: Fabio Baltieri <fabiobaltieri@chromium.org>
> 
> Export input_default_setkeycode so that a driver can set a custom
> setkeycode handler to take some driver specific action but still call
> the default handler at some point.
> 
> Signed-off-by: Fabio Baltieri <fabiobaltieri@chromium.org>
> Link: https://patch.msgid.link/20260211173421.1206478-2-fabiobaltieri@chromium.org
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Reviewed-by: Tzung-Bi Shih <tzungbi@kernel.org>

^ permalink raw reply

* Re: [PATCH v7 2/2] Input: cros_ec_keyb - add function key support
From: Dmitry Torokhov @ 2026-02-22  0:39 UTC (permalink / raw)
  To: Fabio Baltieri
  Cc: Benson Leung, Guenter Roeck, Tzung-Bi Shih, Simon Glass,
	linux-input, chrome-platform, linux-kernel
In-Reply-To: <20260211173421.1206478-3-fabiobaltieri@chromium.org>

Hi Fabio,

On Wed, Feb 11, 2026 at 05:34:21PM +0000, Fabio Baltieri wrote:
>  
> +/* Returns true if there is a KEY_FN code defined in the normal keymap */
> +static bool cros_ec_keyb_has_fn_key(struct cros_ec_keyb *ckdev)
> +{
> +	struct input_dev *idev = ckdev->idev;
> +	const unsigned short *keycodes = idev->keycode;
> +
> +	for (int row = 0; row < ckdev->rows; row++) {
> +		for (int col = 0; col < ckdev->cols; col++) {
> +			int pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
> +
> +			if (keycodes[pos] == KEY_FN)
> +				return true;
> +		}

We can simply scan the bottom half of the map linearly, I think this is
simpler.

> +	}
> +
> +	return false;
> +}
> +
> +/*
> + * Returns true if there is a KEY_FN defined and at least one key in the fn
> + * layer keymap
> + */
> +static bool cros_ec_keyb_has_fn_map(struct cros_ec_keyb *ckdev)
> +{
> +	if (!cros_ec_keyb_has_fn_key(ckdev))
> +		return false;
> +
> +	for (int row = 0; row < ckdev->rows; row++) {
> +		for (int col = 0; col < ckdev->cols; col++) {
> +			if (cros_ec_keyb_fn_code(ckdev, row, col, NULL) != 0)
> +				return true;

Same here.

I made a bunch of changes to your patch and then a bunch of changes to
the driver in general. Please take a look.

Thanks.

-- 
Dmitry

^ permalink raw reply

* [PATCH 6/7] Input: cros_ec_keyb - do not allocate keyboard state separately
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

Now that we know the upper bound for the number of columnts, and know
that it is pretty small, there is no point in allocating it separately.
We are wasting more memory tracking the allocations.

Embed valid_keys and old_kb_state directly into cros_ec_keyb structure.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 12 ++----------
 1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index 1b4ee30e1998..02176aee0530 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -59,8 +59,8 @@ struct cros_ec_keyb {
 	unsigned int cols;
 	int row_shift;
 	bool ghost_filter;
-	u8 *valid_keys;
-	u8 *old_kb_state;
+	u8 valid_keys[CROS_EC_KEYBOARD_COLS_MAX];
+	u8 old_kb_state[CROS_EC_KEYBOARD_COLS_MAX];
 
 	struct device *dev;
 	struct cros_ec_device *ec;
@@ -750,14 +750,6 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 		return -EINVAL;
 	}
 
-	ckdev->valid_keys = devm_kzalloc(dev, ckdev->cols, GFP_KERNEL);
-	if (!ckdev->valid_keys)
-		return -ENOMEM;
-
-	ckdev->old_kb_state = devm_kzalloc(dev, ckdev->cols, GFP_KERNEL);
-	if (!ckdev->old_kb_state)
-		return -ENOMEM;
-
 	/*
 	 * We call the keyboard matrix 'input0'. Allocate phys before input
 	 * dev, to ensure correct tear-down ordering.
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 7/7] Input: cros_ec_keyb - factor out column processing
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

Factor out column processing and eagerly skip processing columns that do
not have any changes in them.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 47 +++++++++++++++------------
 1 file changed, 27 insertions(+), 20 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index 02176aee0530..8cf9129f6198 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -254,6 +254,26 @@ static void cros_ec_keyb_process_key_fn_map(struct cros_ec_keyb *ckdev,
 	input_report_key(idev, code, state);
 }
 
+static void cros_ec_keyy_process_col(struct cros_ec_keyb *ckdev, int col,
+				     u8 col_state, u8 changed)
+{
+	for (int row = 0; row < ckdev->rows; row++) {
+		if (changed & BIT(row)) {
+			u8 key_state = col_state & BIT(row);
+
+			dev_dbg(ckdev->dev, "changed: [r%d c%d]: byte %02x\n",
+				row, col, key_state);
+
+			if (ckdev->has_fn_map)
+				cros_ec_keyb_process_key_fn_map(ckdev, row, col,
+								key_state);
+			else
+				cros_ec_keyb_process_key_plain(ckdev, row, col,
+							       key_state);
+		}
+	}
+}
+
 /*
  * Compares the new keyboard state to the old one and produces key
  * press/release events accordingly.  The keyboard state is one byte
@@ -261,10 +281,6 @@ static void cros_ec_keyb_process_key_fn_map(struct cros_ec_keyb *ckdev,
  */
 static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, u8 *kb_state, int len)
 {
-	int col, row;
-	int new_state;
-	int old_state;
-
 	if (ckdev->ghost_filter && cros_ec_keyb_has_ghosting(ckdev, kb_state)) {
 		/*
 		 * Simple-minded solution: ignore this state. The obvious
@@ -275,24 +291,15 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, u8 *kb_state, int l
 		return;
 	}
 
-	for (col = 0; col < ckdev->cols; col++) {
-		for (row = 0; row < ckdev->rows; row++) {
-			new_state = kb_state[col] & BIT(row);
-			old_state = ckdev->old_kb_state[col] & BIT(row);
-
-			if (new_state == old_state)
-				continue;
-
-			dev_dbg(ckdev->dev, "changed: [r%d c%d]: byte %02x\n",
-				row, col, new_state);
+	for (int col = 0; col < ckdev->cols; col++) {
+		u8 changed = kb_state[col] ^ ckdev->old_kb_state[col];
 
-			if (ckdev->has_fn_map)
-				cros_ec_keyb_process_key_fn_map(ckdev, row, col, new_state);
-			else
-				cros_ec_keyb_process_key_plain(ckdev, row, col, new_state);
-		}
-		ckdev->old_kb_state[col] = kb_state[col];
+		if (changed)
+			cros_ec_keyy_process_col(ckdev, col, kb_state[col],
+						 changed);
 	}
+
+	memcpy(ckdev->old_kb_state, kb_state, sizeof(ckdev->old_kb_state));
 	input_sync(ckdev->idev);
 }
 
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 5/7] Input: cros_ec_keyb - simplify cros_ec_keyb_work()
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

Introduce temporaries for event_data pointer and event_size to simplify
the code a bit.

In cros_ec_keyb_compute_valid_keys() explicitly compare with
KEY_RESERVED to make the intent of the comparison more clear.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 28 +++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index fabbdbc22785..1b4ee30e1998 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -330,8 +330,10 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
 {
 	struct cros_ec_keyb *ckdev = container_of(nb, struct cros_ec_keyb,
 						  notifier);
-	u32 val;
+	struct ec_response_get_next_event_v3 *event_data;
+	unsigned int event_size;
 	unsigned int ev_type;
+	u32 val;
 
 	/*
 	 * If not wake enabled, discard key state changes during
@@ -341,32 +343,32 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
 	if (queued_during_suspend && !device_may_wakeup(ckdev->dev))
 		return NOTIFY_OK;
 
-	switch (ckdev->ec->event_data.event_type) {
+	event_data = &ckdev->ec->event_data;
+	event_size = ckdev->ec->event_size;
+
+	switch (event_data->event_type) {
 	case EC_MKBP_EVENT_KEY_MATRIX:
 		pm_wakeup_event(ckdev->dev, 0);
 
 		if (!ckdev->idev) {
-			dev_warn_once(ckdev->dev,
-				      "Unexpected key matrix event\n");
+			dev_warn_once(ckdev->dev, "Unexpected key matrix event\n");
 			return NOTIFY_OK;
 		}
 
-		if (ckdev->ec->event_size != ckdev->cols) {
+		if (event_size != ckdev->cols) {
 			dev_err(ckdev->dev,
 				"Discarded key matrix event, unexpected length: %d != %d\n",
 				ckdev->ec->event_size, ckdev->cols);
 			return NOTIFY_OK;
 		}
 
-		cros_ec_keyb_process(ckdev,
-				     ckdev->ec->event_data.data.key_matrix,
-				     ckdev->ec->event_size);
+		cros_ec_keyb_process(ckdev, event_data->data.key_matrix, event_size);
 		break;
 
 	case EC_MKBP_EVENT_SYSRQ:
 		pm_wakeup_event(ckdev->dev, 0);
 
-		val = get_unaligned_le32(&ckdev->ec->event_data.data.sysrq);
+		val = get_unaligned_le32(&event_data->data.sysrq);
 		dev_dbg(ckdev->dev, "sysrq code from EC: %#x\n", val);
 		handle_sysrq(val);
 		break;
@@ -376,12 +378,10 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
 		pm_wakeup_event(ckdev->dev, 0);
 
 		if (ckdev->ec->event_data.event_type == EC_MKBP_EVENT_BUTTON) {
-			val = get_unaligned_le32(
-					&ckdev->ec->event_data.data.buttons);
+			val = get_unaligned_le32(&event_data->data.buttons);
 			ev_type = EV_KEY;
 		} else {
-			val = get_unaligned_le32(
-					&ckdev->ec->event_data.data.switches);
+			val = get_unaligned_le32(&event_data->data.switches);
 			ev_type = EV_SW;
 		}
 		cros_ec_keyb_report_bs(ckdev, ev_type, val);
@@ -410,7 +410,7 @@ static void cros_ec_keyb_compute_valid_keys(struct cros_ec_keyb *ckdev)
 	for (col = 0; col < ckdev->cols; col++) {
 		for (row = 0; row < ckdev->rows; row++) {
 			code = keymap[MATRIX_SCAN_CODE(row, col, row_shift)];
-			if (code && (code != KEY_BATTERY))
+			if (code != KEY_RESERVED && code != KEY_BATTERY)
 				ckdev->valid_keys[col] |= BIT(row);
 		}
 		dev_dbg(ckdev->dev, "valid_keys[%02d] = 0x%02x\n",
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 4/7] Input: cros_ec_keyb - use BIT() macro instead of open-coding shifts
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

Using the macro clarifies the code.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index 2f78a19521ab..fabbdbc22785 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -277,8 +277,8 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, u8 *kb_state, int l
 
 	for (col = 0; col < ckdev->cols; col++) {
 		for (row = 0; row < ckdev->rows; row++) {
-			new_state = kb_state[col] & (1 << row);
-			old_state = ckdev->old_kb_state[col] & (1 << row);
+			new_state = kb_state[col] & BIT(row);
+			old_state = ckdev->old_kb_state[col] & BIT(row);
 
 			if (new_state == old_state)
 				continue;
@@ -411,7 +411,7 @@ static void cros_ec_keyb_compute_valid_keys(struct cros_ec_keyb *ckdev)
 		for (row = 0; row < ckdev->rows; row++) {
 			code = keymap[MATRIX_SCAN_CODE(row, col, row_shift)];
 			if (code && (code != KEY_BATTERY))
-				ckdev->valid_keys[col] |= 1 << row;
+				ckdev->valid_keys[col] |= BIT(row);
 		}
 		dev_dbg(ckdev->dev, "valid_keys[%02d] = 0x%02x\n",
 			col, ckdev->valid_keys[col]);
@@ -780,8 +780,7 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	idev->dev.parent = dev;
 	idev->setkeycode = cros_ec_keyb_setkeycode;
 
-	ckdev->ghost_filter = device_property_read_bool(dev,
-					"google,needs-ghost-filter");
+	ckdev->ghost_filter = device_property_read_bool(dev, "google,needs-ghost-filter");
 
 	err = matrix_keypad_build_keymap(NULL, ckdev->rows * 2, ckdev->cols,
 					 NULL, idev);
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 3/7] Input: cros_ec_keyb - use u8 instead of uint8_t
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

In the kernel u8/u16/u32 are preferred to the uint*_t variants.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index bdbfd219f2e8..2f78a19521ab 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -59,8 +59,8 @@ struct cros_ec_keyb {
 	unsigned int cols;
 	int row_shift;
 	bool ghost_filter;
-	uint8_t *valid_keys;
-	uint8_t *old_kb_state;
+	u8 *valid_keys;
+	u8 *old_kb_state;
 
 	struct device *dev;
 	struct cros_ec_device *ec;
@@ -145,11 +145,11 @@ static const struct cros_ec_bs_map cros_ec_keyb_bs[] = {
  * Returns true when there is at least one combination of pressed keys that
  * results in ghosting.
  */
-static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, uint8_t *buf)
+static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, u8 *buf)
 {
 	int col1, col2, buf1, buf2;
 	struct device *dev = ckdev->dev;
-	uint8_t *valid_keys = ckdev->valid_keys;
+	u8 *valid_keys = ckdev->valid_keys;
 
 	/*
 	 * Ghosting happens if for any pressed key X there are other keys
@@ -259,8 +259,7 @@ static void cros_ec_keyb_process_key_fn_map(struct cros_ec_keyb *ckdev,
  * press/release events accordingly.  The keyboard state is one byte
  * per column.
  */
-static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev,
-			 uint8_t *kb_state, int len)
+static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, u8 *kb_state, int len)
 {
 	int col, row;
 	int new_state;
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 2/7] Input: cros_ec_keyb - add function key support
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

From: Fabio Baltieri <fabiobaltieri@chromium.org>

Add support for handling an Fn button and sending separate keycodes for
a subset of keys in the matrix defined in the upper half of the keymap.

Signed-off-by: Fabio Baltieri <fabiobaltieri@chromium.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
Link: https://patch.msgid.link/20260211173421.1206478-3-fabiobaltieri@chromium.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 182 +++++++++++++++++++++++---
 1 file changed, 166 insertions(+), 16 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index 066aaa87d93f..bdbfd219f2e8 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -29,6 +29,12 @@
 
 #include <linux/unaligned.h>
 
+/*
+ * Maximum size of the normal key matrix, this is limited by the host command
+ * key_matrix field defined in ec_response_get_next_data_v3
+ */
+#define CROS_EC_KEYBOARD_COLS_MAX 18
+
 /**
  * struct cros_ec_keyb - Structure representing EC keyboard device
  *
@@ -44,6 +50,9 @@
  * @bs_idev: The input device for non-matrix buttons and switches (or NULL).
  * @notifier: interrupt event notifier for transport devices
  * @vdata: vivaldi function row data
+ * @has_fn_map: whether the driver uses an fn function-map layer
+ * @fn_active: tracks whether the function key is currently pressed
+ * @fn_combo_active: tracks whether another key was pressed while fn is active
  */
 struct cros_ec_keyb {
 	unsigned int rows;
@@ -61,6 +70,10 @@ struct cros_ec_keyb {
 	struct notifier_block notifier;
 
 	struct vivaldi_data vdata;
+
+	bool has_fn_map;
+	bool fn_active;
+	bool fn_combo_active;
 };
 
 /**
@@ -166,16 +179,89 @@ static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, uint8_t *buf)
 	return false;
 }
 
+static void cros_ec_emit_fn_key(struct input_dev *input, unsigned int pos)
+{
+	input_event(input, EV_MSC, MSC_SCAN, pos);
+	input_report_key(input, KEY_FN, true);
+	input_sync(input);
+
+	input_event(input, EV_MSC, MSC_SCAN, pos);
+	input_report_key(input, KEY_FN, false);
+}
+
+static void cros_ec_keyb_process_key_plain(struct cros_ec_keyb *ckdev,
+					   int row, int col, bool state)
+{
+	struct input_dev *idev = ckdev->idev;
+	const unsigned short *keycodes = idev->keycode;
+	int pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
+
+	input_event(idev, EV_MSC, MSC_SCAN, pos);
+	input_report_key(idev, keycodes[pos], state);
+}
+
+static void cros_ec_keyb_process_key_fn_map(struct cros_ec_keyb *ckdev,
+					    int row, int col, bool state)
+{
+	struct input_dev *idev = ckdev->idev;
+	const unsigned short *keycodes = idev->keycode;
+	unsigned int pos, fn_pos;
+	unsigned int code, fn_code;
+
+	pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
+	code = keycodes[pos];
+
+	if (code == KEY_FN) {
+		ckdev->fn_active = state;
+		if (state) {
+			ckdev->fn_combo_active = false;
+		} else if (!ckdev->fn_combo_active) {
+			/*
+			 * Send both Fn press and release events if nothing
+			 * else has been pressed together with Fn.
+			 */
+			cros_ec_emit_fn_key(idev, pos);
+		}
+		return;
+	}
+
+	fn_pos = MATRIX_SCAN_CODE(row + ckdev->rows, col, ckdev->row_shift);
+	fn_code = keycodes[fn_pos];
+
+	if (state) {
+		if (ckdev->fn_active) {
+			ckdev->fn_combo_active = true;
+			if (!fn_code)
+				return; /* Discard if no Fn mapping exists */
+
+			code = fn_code;
+			pos = fn_pos;
+		}
+	} else {
+		/*
+		 * If the Fn-remapped code is currently pressed, release it.
+		 * Otherwise, release the standard code (if it was pressed).
+		 */
+		if (fn_code && test_bit(fn_code, idev->key)) {
+			code = fn_code;
+			pos = fn_pos;
+		} else if (!test_bit(code, idev->key)) {
+			return; /* Discard, key press code was not sent */
+		}
+	}
+
+	input_event(idev, EV_MSC, MSC_SCAN, pos);
+	input_report_key(idev, code, state);
+}
 
 /*
  * Compares the new keyboard state to the old one and produces key
- * press/release events accordingly.  The keyboard state is 13 bytes (one byte
- * per column)
+ * press/release events accordingly.  The keyboard state is one byte
+ * per column.
  */
 static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev,
 			 uint8_t *kb_state, int len)
 {
-	struct input_dev *idev = ckdev->idev;
 	int col, row;
 	int new_state;
 	int old_state;
@@ -192,20 +278,19 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev,
 
 	for (col = 0; col < ckdev->cols; col++) {
 		for (row = 0; row < ckdev->rows; row++) {
-			int pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
-			const unsigned short *keycodes = idev->keycode;
-
 			new_state = kb_state[col] & (1 << row);
 			old_state = ckdev->old_kb_state[col] & (1 << row);
-			if (new_state != old_state) {
-				dev_dbg(ckdev->dev,
-					"changed: [r%d c%d]: byte %02x\n",
-					row, col, new_state);
-
-				input_event(idev, EV_MSC, MSC_SCAN, pos);
-				input_report_key(idev, keycodes[pos],
-						 new_state);
-			}
+
+			if (new_state == old_state)
+				continue;
+
+			dev_dbg(ckdev->dev, "changed: [r%d c%d]: byte %02x\n",
+				row, col, new_state);
+
+			if (ckdev->has_fn_map)
+				cros_ec_keyb_process_key_fn_map(ckdev, row, col, new_state);
+			else
+				cros_ec_keyb_process_key_plain(ckdev, row, col, new_state);
 		}
 		ckdev->old_kb_state[col] = kb_state[col];
 	}
@@ -583,6 +668,62 @@ static void cros_ec_keyb_parse_vivaldi_physmap(struct cros_ec_keyb *ckdev)
 	ckdev->vdata.num_function_row_keys = n_physmap;
 }
 
+/* Returns true if there is a KEY_FN code defined in the normal keymap */
+static bool cros_ec_keyb_has_fn_key(struct cros_ec_keyb *ckdev)
+{
+	const unsigned short *keycodes = ckdev->idev->keycode;
+	int i;
+
+	for (i = 0; i < MATRIX_SCAN_CODE(ckdev->rows, 0, ckdev->row_shift); i++) {
+		if (keycodes[i] == KEY_FN)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Returns true if there is a KEY_FN defined and at least one key in the fn
+ * layer keymap
+ */
+static bool cros_ec_keyb_has_fn_map(struct cros_ec_keyb *ckdev)
+{
+	struct input_dev *idev = ckdev->idev;
+	const unsigned short *keycodes = ckdev->idev->keycode;
+	int i;
+
+	if (!cros_ec_keyb_has_fn_key(ckdev))
+		return false;
+
+	for (i = MATRIX_SCAN_CODE(ckdev->rows, 0, ckdev->row_shift);
+	     i < idev->keycodemax; i++) {
+		if (keycodes[i] != KEY_RESERVED)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Custom handler for the set keycode ioctl, calls the default handler and
+ * recomputes has_fn_map.
+ */
+static int cros_ec_keyb_setkeycode(struct input_dev *idev,
+				   const struct input_keymap_entry *ke,
+				   unsigned int *old_keycode)
+{
+	struct cros_ec_keyb *ckdev = input_get_drvdata(idev);
+	int ret;
+
+	ret = input_default_setkeycode(idev, ke, old_keycode);
+	if (ret)
+		return ret;
+
+	ckdev->has_fn_map = cros_ec_keyb_has_fn_map(ckdev);
+
+	return 0;
+}
+
 /**
  * cros_ec_keyb_register_matrix - Register matrix keys
  *
@@ -604,6 +745,12 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	if (err)
 		return err;
 
+	if (ckdev->cols > CROS_EC_KEYBOARD_COLS_MAX) {
+		dev_err(dev, "keypad,num-columns too large: %d (max: %d)\n",
+			ckdev->cols, CROS_EC_KEYBOARD_COLS_MAX);
+		return -EINVAL;
+	}
+
 	ckdev->valid_keys = devm_kzalloc(dev, ckdev->cols, GFP_KERNEL);
 	if (!ckdev->valid_keys)
 		return -ENOMEM;
@@ -632,11 +779,12 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	idev->id.version = 1;
 	idev->id.product = 0;
 	idev->dev.parent = dev;
+	idev->setkeycode = cros_ec_keyb_setkeycode;
 
 	ckdev->ghost_filter = device_property_read_bool(dev,
 					"google,needs-ghost-filter");
 
-	err = matrix_keypad_build_keymap(NULL, ckdev->rows, ckdev->cols,
+	err = matrix_keypad_build_keymap(NULL, ckdev->rows * 2, ckdev->cols,
 					 NULL, idev);
 	if (err) {
 		dev_err(dev, "cannot build key matrix\n");
@@ -651,6 +799,8 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	cros_ec_keyb_compute_valid_keys(ckdev);
 	cros_ec_keyb_parse_vivaldi_physmap(ckdev);
 
+	ckdev->has_fn_map = cros_ec_keyb_has_fn_map(ckdev);
+
 	err = input_register_device(ckdev->idev);
 	if (err) {
 		dev_err(dev, "cannot register input device\n");
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 1/7] Input: export input_default_setkeycode
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel

From: Fabio Baltieri <fabiobaltieri@chromium.org>

Export input_default_setkeycode so that a driver can set a custom
setkeycode handler to take some driver specific action but still call
the default handler at some point.

Signed-off-by: Fabio Baltieri <fabiobaltieri@chromium.org>
Link: https://patch.msgid.link/20260211173421.1206478-2-fabiobaltieri@chromium.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/input.c | 23 ++++++++++++++++++++---
 include/linux/input.h |  4 ++++
 2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/input/input.c b/drivers/input/input.c
index ceb134044175..9336980ee320 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -861,14 +861,30 @@ static int input_default_getkeycode(struct input_dev *dev,
 	return 0;
 }
 
-static int input_default_setkeycode(struct input_dev *dev,
-				    const struct input_keymap_entry *ke,
-				    unsigned int *old_keycode)
+/**
+ * input_default_setkeycode - default setkeycode method
+ * @dev: input device which keymap is being updated.
+ * @ke: new keymap entry.
+ * @old_keycode: pointer to the location where old keycode should be stored.
+ *
+ * This function is the default implementation of &input_dev.setkeycode()
+ * method. It is typically used when a driver does not provide its own
+ * implementation, but it is also exported so drivers can extend it.
+ *
+ * The function must be called with &input_dev.event_lock held.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int input_default_setkeycode(struct input_dev *dev,
+			     const struct input_keymap_entry *ke,
+			     unsigned int *old_keycode)
 {
 	unsigned int index;
 	int error;
 	int i;
 
+	lockdep_assert_held(&dev->event_lock);
+
 	if (!dev->keycodesize)
 		return -EINVAL;
 
@@ -922,6 +938,7 @@ static int input_default_setkeycode(struct input_dev *dev,
 	__set_bit(ke->keycode, dev->keybit);
 	return 0;
 }
+EXPORT_SYMBOL(input_default_setkeycode);
 
 /**
  * input_get_keycode - retrieve keycode currently mapped to a given scancode
diff --git a/include/linux/input.h b/include/linux/input.h
index 7d7cb0593a63..06ca62328db1 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -517,6 +517,10 @@ INPUT_GENERATE_ABS_ACCESSORS(res, resolution)
 int input_scancode_to_scalar(const struct input_keymap_entry *ke,
 			     unsigned int *scancode);
 
+int input_default_setkeycode(struct input_dev *dev,
+			     const struct input_keymap_entry *ke,
+			     unsigned int *old_keycode);
+
 int input_get_keycode(struct input_dev *dev, struct input_keymap_entry *ke);
 int input_set_keycode(struct input_dev *dev,
 		      const struct input_keymap_entry *ke);
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* Re: [PATCH 1/1] HID: uhid: Fix out-of-bounds write caused by raw events mismanagement
From: Jiri Kosina @ 2026-02-21 19:46 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Lee Jones, David Rheinsberg, linux-input, linux-kernel, stable
In-Reply-To: <aZmsTQeeGf26FqvY@plouf>

On Sat, 21 Feb 2026, Benjamin Tissoires wrote:

> > Since the report ID is located within the data buffer, overwriting it
> > would mean that any subsequent matching could cause a disparity in
> > assumed allocated buffer size.  This in turn could trivially result in
> > an out-of-bounds condition.  To mitigate this issue, let's refuse to
> > overwrite a given report's data area if the ID in get_report_reply
> > doesn't match.
> 
> That's a strong assumption and a breakage of the userspace FWIW. The CI
> is now full of errors:
> https://gitlab.freedesktop.org/bentiss/hid/-/commits/for-7.0/upstream-fixes
> 
> It is pretty common to allocate the buffer and not initialize it in
> get_report operations.
> 
> It was a bad API choice to have rnum and data[0] for all HID requests
> (internally, externally), but we should stick to it. The CI breakage in
> itself is not a big issue TBH, but if it breaks here, it will probably
> break existing users.

Lee,

was this found via code inspection, fuzzing, or is there some real-world 
report behind it?

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* Re: [PATCH v3] HID: generic: add LampArray support via hid-lamparray helper
From: kernel test robot @ 2026-02-21 15:49 UTC (permalink / raw)
  To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
  Cc: llvm, oe-kbuild-all, wse, Tim Guttzeit, linux-kernel, linux-input
In-Reply-To: <20260220135309.151487-1-tgu@tuxedocomputers.com>

Hi Tim,

kernel test robot noticed the following build errors:

[auto build test ERROR on hid/for-next]
[also build test ERROR on linus/master v6.19 next-20260220]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Tim-Guttzeit/HID-generic-add-LampArray-support-via-hid-lamparray-helper/20260220-215620
base:   https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link:    https://lore.kernel.org/r/20260220135309.151487-1-tgu%40tuxedocomputers.com
patch subject: [PATCH v3] HID: generic: add LampArray support via hid-lamparray helper
config: i386-randconfig-002-20260221 (https://download.01.org/0day-ci/archive/20260221/202602212327.QsyQtOFJ-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260221/202602212327.QsyQtOFJ-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602212327.QsyQtOFJ-lkp@intel.com/

All errors (new ones prefixed by >>):

>> ld.lld: error: undefined symbol: hid_hw_request
   >>> referenced by hid-lamparray.c:210 (drivers/hid/hid-lamparray.c:210)
   >>>               drivers/hid/hid-lamparray.o:(lamparray_register) in archive vmlinux.a
   >>> referenced by hid-lamparray.c:316 (drivers/hid/hid-lamparray.c:316)
   >>>               drivers/hid/hid-lamparray.o:(lamparray_hw_set_autonomous) in archive vmlinux.a
   >>> referenced by hid-lamparray.c:392 (drivers/hid/hid-lamparray.c:392)
   >>>               drivers/hid/hid-lamparray.o:(lamparray_led_brightness_get) in archive vmlinux.a
   >>> referenced 1 more times

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH 1/1] HID: uhid: Fix out-of-bounds write caused by raw events mismanagement
From: Benjamin Tissoires @ 2026-02-21 13:03 UTC (permalink / raw)
  To: Lee Jones
  Cc: David Rheinsberg, Jiri Kosina, linux-input, linux-kernel, stable
In-Reply-To: <20260211164025.171242-1-lee@kernel.org>

On Feb 11 2026, Lee Jones wrote:
> Since the report ID is located within the data buffer, overwriting it
> would mean that any subsequent matching could cause a disparity in
> assumed allocated buffer size.  This in turn could trivially result in
> an out-of-bounds condition.  To mitigate this issue, let's refuse to
> overwrite a given report's data area if the ID in get_report_reply
> doesn't match.

That's a strong assumption and a breakage of the userspace FWIW. The CI
is now full of errors:
https://gitlab.freedesktop.org/bentiss/hid/-/commits/for-7.0/upstream-fixes

It is pretty common to allocate the buffer and not initialize it in
get_report operations.

It was a bad API choice to have rnum and data[0] for all HID requests
(internally, externally), but we should stick to it. The CI breakage in
itself is not a big issue TBH, but if it breaks here, it will probably
break existing users.

Cheers,
Benjamin

> 
> Cc: stable@vger.kernel.org
> Fixes: fcfcf0deb89ec ("HID: uhid: implement feature requests")
> Signed-off-by: Lee Jones <lee@kernel.org>
> ---
>  drivers/hid/uhid.c | 4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
> index 21a70420151e..a0ee4e86656f 100644
> --- a/drivers/hid/uhid.c
> +++ b/drivers/hid/uhid.c
> @@ -262,6 +262,10 @@ static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum,
>  	req = &uhid->report_buf.u.get_report_reply;
>  	if (req->err) {
>  		ret = -EIO;
> +	} else if (rnum != req->data[0]) {
> +		hid_err(hid, "Report ID mismatch - refusing to overwrite the data buffer\n");
> +		ret = -EINVAL;
> +		goto unlock;
>  	} else {
>  		ret = min3(count, (size_t)req->size, (size_t)UHID_DATA_MAX);
>  		memcpy(buf, req->data, ret);
> -- 
> 2.53.0.273.g2a3d683680-goog
> 

^ permalink raw reply

* RE: [PATCH v3] selftests: hid: tests: test_wacom_generic: add tests for display devices and opaque devices
From: Jiri Kosina @ 2026-02-21  9:56 UTC (permalink / raw)
  To: Skomra, Erin
  Cc: Alex Tran, Benjamin Tissoires, Shuah Khan, Cheng, Ping,
	Gerecke, Jason, Erin Skomra, linux-input@vger.kernel.org,
	linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <AS8PR07MB8373592F3C7C20ACE67808EDF598A@AS8PR07MB8373.eurprd07.prod.outlook.com>

On Wed, 4 Feb 2026, Skomra, Erin wrote:

> Tested by Erin Skomra <erin.skomra@wacom.com>
> Reviewed-by Erin Skomra <erin.skomra@wacom.com>

Erin,

thanks for testing and reviewing the patch.
Next time please make sure that you send the tags in an appropriate format 
so that it can be automatically picked by the tools like b4 when applying.

I've fixed it manually this time and applied.

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* Re: [PATCH] HID: multitouch: new class MT_CLS_EGALAX_P80H84
From: Jiri Kosina @ 2026-02-21  9:54 UTC (permalink / raw)
  To: Ian Ray; +Cc: Benjamin Tissoires, Sebastian Reichel, linux-input, linux-kernel
In-Reply-To: <20260217115152.35910-1-ian.ray@gehealthcare.com>

On Tue, 17 Feb 2026, Ian Ray wrote:

> Add class MT_CLS_EGALAX_P80H84 to describe eGalaxy P80H84 touchscreen.
> 
> The eGalaxy P80H84 touchscreen reports 'HID_GROUP_MULTITOUCH_WIN_8' and
> this caused the generic 'MT_CLS_WIN_8' class to be detected.
> 
> The new class does _not_ export all inputs, since doing so results in a
> visible mouse cursor when only the touchscreen is connected.
> 
> The visible mouse cursor was exposed by c23e2043d5f7 ("HID: multitouch:
> do not filter mice nodes").

Applied to hid.git#for-7.0/upstream-fixes. Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox