[PATCH 10/18] HID: logitech-hidpp: manage G920 range sysfs attribute via dev_groups

From: Dmitry Torokhov

Date: Mon Nov 09 2026 - 01:18:41 EST


Creating and removing the G920 range attribute manually inside force
feedback initialization and destruction ties sysfs cleanup to input
device release rather than driver unbind, leaving a dangling attribute
if userspace holds an open event handle across disconnect. Manual sysfs
management also adds avoidable boilerplate.

Register the attribute group through driver dev_groups with a visibility
callback checking initialized G920 force-feedback state, allowing driver
core to create and tear down the attribute cleanly around probe and
remove.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@xxxxxxxxx>
---
drivers/hid/hid-logitech-hidpp.c | 45 ++++++++++++++++++++++++++------
1 file changed, 37 insertions(+), 8 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 1ed940352fd2..ad3edd15cdd2 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2807,16 +2807,47 @@ static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute

static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp_ff_range_show, hidpp_ff_range_store);

+static struct attribute *g920_range_attrs[] = {
+ &dev_attr_range.attr,
+ NULL
+};
+
+static umode_t g920_range_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ struct g920_data *g920;
+
+ if (!hidpp || !(hidpp->quirks & HIDPP_QUIRK_CLASS_G920))
+ return 0;
+
+ g920 = hidpp->private_data;
+ if (!g920 || !g920->ff)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group g920_range_group = {
+ .attrs = g920_range_attrs,
+ .is_visible = g920_range_attr_is_visible,
+};
+
+static const struct attribute_group *hidpp_dev_groups[] = {
+ &g920_range_group,
+ NULL
+};
+
static void hidpp_ff_destroy(struct ff_device *ff)
{
struct hidpp_ff_private_data *data = ff->private;
struct hidpp_device *hidpp = data->hidpp;
- struct hid_device *hid = hidpp->hid_dev;
struct g920_data *g920 = hidpp->private_data;

- hid_info(hid, "Unloading HID++ force feedback.\n");
+ hid_info(hidpp->hid_dev, "Unloading HID++ force feedback.\n");

- device_remove_file(&hid->dev, &dev_attr_range);
destroy_workqueue(data->wq);
g920->ff = NULL;
}
@@ -2895,11 +2926,6 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, struct input_dev *dev)

dev->close = hidpp_ff_close;

- /* Create sysfs interface */
- error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
- if (error)
- hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d!\n", error);
-
hid_info(hid, "Force feedback support loaded (firmware release %d).\n",
version);

@@ -5033,6 +5059,9 @@ static struct hid_driver hidpp_driver = {
.input_configured = hidpp_input_configured,
.input_mapping = hidpp_input_mapping,
.input_mapped = hidpp_input_mapped,
+ .driver = {
+ .dev_groups = hidpp_dev_groups,
+ },
};

module_hid_driver(hidpp_driver);
--
2.56.0.506.g506a7eabc5-goog