From: Rong Zhang <i@rong.moe>
To: Jiri Kosina <jikos@kernel.org>,
Benjamin Tissoires <bentiss@kernel.org>,
Angela Czubak <aczubak@google.com>,
Henrik Rydberg <rydberg@bitmath.org>,
Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
Rong Zhang <i@rong.moe>
Subject: [PATCH v3 7/9] HID: Add hid-plugin framework for loadable plugin modules
Date: Fri, 13 Nov 2026 01:54:00 +0800 [thread overview]
Message-ID: <20261113-hid-haptic-kmod-v3-7-e7dfa8766c0d@rong.moe> (raw)
In-Reply-To: <20261113-hid-haptic-kmod-v3-0-e7dfa8766c0d@rong.moe>
Some devices support some additional HID usage pages which enhance their
capabilities. The support code for these usage pages is called "HID plugin".
The corresponding HID driver calls functions provided by the plugin to
take the advantage of such enhancements. Without the framework, the HID
driver must have a linkage dependence on the plugin, which is not
optimal because the plugin is always loaded into memory even if no
device actually supports it.
Logically, the dependence should be reversed, i.e., the plugin should be
an optional component of the HID driver, which is only loaded when the
driver finds that the device needs it.
Introduce hid-plugin framework for loadable plugin modules to achieve
the said logical dependence. Each plugin has a unique hid_plugin_id when
enabled, so that HID drivers can request it accordingly. The framework
aims to be lightweight. It loads the plugin module when needed. Once
loaded (or if the plugin is builtin), requesting the plugin is just an
RCU dereference plusing an module refcount increment.
When a plugin is disabled, its id maps to HID_PLUGIN_DISABLED, and calls
to the plugin helper functions are eliminated by the compiler.
Thanks Benjamin Tissoires for the inspiration for the implementation.
Suggested-by: Benjamin Tissoires <bentiss@kernel.org>
Link: https://lore.kernel.org/linux-input/auypydfkhx2eg7vp764way4batdilzc35inqda3exwzs3tk3ff@oagat6g46zto/
Signed-off-by: Rong Zhang <i@rong.moe>
---
Changes in v3:
- Add missing Return: to hid_plugin_register()'s kernel-doc (thanks
Sashiko)
---
MAINTAINERS | 6 ++
drivers/hid/Kconfig | 14 +++++
drivers/hid/Makefile | 2 +
drivers/hid/hid-plugin.c | 105 +++++++++++++++++++++++++++++++++++
drivers/hid/hid-plugin.h | 141 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 268 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 222b7fc2c3aa..06c82b4a7614 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11905,6 +11905,12 @@ L: linux-input@vger.kernel.org
S: Supported
F: drivers/hid/hid-playstation.c
+HID PLUGINS
+M: Rong Zhang <i@rong.moe>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/hid/hid-plugin.*
+
HID PULSAR DRIVER
M: Nikolas Koesling <nikolas@koesling.info>
L: linux-input@vger.kernel.org
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 2481bc01c435..604683b4a4a9 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,6 +92,20 @@ config HID_GENERIC
If unsure, say Y.
+menuconfig HID_PLUGIN
+ bool "HID plugins"
+ help
+ Support for HID plugins.
+
+ Say Y here if you want to enhance HID drivers with additional
+ functionalities provided by corresponding plugins.
+
+ Plugins can be either builtin or modules. A plugin compiled as a module
+ is made available to HID drivers by automatically loading it when
+ requested.
+
+ If unsure, say N.
+
config HID_HAPTIC
bool "Haptic touchpad support"
default n
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 43a8eb0f389b..b41aa3f52a94 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -4,6 +4,8 @@
#
hid-y := hid-core.o hid-input.o hid-quirks.o
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
+hid-$(CONFIG_HID_PLUGIN) += hid-plugin.o
+
hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o
obj-$(CONFIG_HID_BPF) += bpf/
diff --git a/drivers/hid/hid-plugin.c b/drivers/hid/hid-plugin.c
new file mode 100644
index 000000000000..a83275df038c
--- /dev/null
+++ b/drivers/hid/hid-plugin.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HID plugin helpers
+ *
+ * Copyright (c) 2026 Rong Zhang <i@rong.moe>
+ */
+
+#define pr_fmt(fmt) "hid-plugin: " fmt
+
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/compiler.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/rcupdate.h>
+
+#include "hid-plugin.h"
+
+static const char * const hid_plugin_mod_names[NR_HID_PLUGINS] = {
+};
+
+static struct hid_plugin __rcu *hid_plugins[NR_HID_PLUGINS];
+
+static struct hid_plugin *hid_plugin_try_module_get(enum hid_plugin_id id)
+{
+ guard(rcu)();
+
+ struct hid_plugin *plugin = rcu_dereference(hid_plugins[id]);
+
+ if (!plugin)
+ return NULL;
+
+ /* Is the module unloading? */
+ return try_module_get(plugin->owner) ? plugin : ERR_PTR(-ENXIO);
+}
+
+struct hid_plugin *__hid_plugin_get(enum hid_plugin_id id)
+{
+ struct hid_plugin *plugin;
+ const char *mod_name;
+ int ret;
+
+ /*
+ * Compile the function as a stub if plugin support is enabled but no
+ * plugin is actually enabled. Utilizing dead code elimination is the
+ * best thing we can do as it's impossible to evaluate an enumeration in
+ * macros.
+ */
+ if (NR_HID_PLUGINS <= 0)
+ return ERR_PTR(-ENOENT);
+
+ plugin = hid_plugin_try_module_get(id);
+ if (plugin)
+ return plugin;
+
+ mod_name = hid_plugin_mod_names[id];
+ if (WARN_ON(!mod_name)) /* Huh? Builtin plugins can't reach here. */
+ return ERR_PTR(-ENXIO);
+
+ ret = request_module(mod_name);
+ if (ret) {
+ pr_warn("failed to load [%d]%s: %d\n", id, mod_name, ret);
+ return ERR_PTR(-ENXIO); /* Don't return positive error code. */
+ }
+
+ plugin = hid_plugin_try_module_get(id);
+ return plugin ?: ERR_PTR(-ENXIO);
+}
+EXPORT_SYMBOL_GPL(__hid_plugin_get);
+
+int __hid_plugin_register(enum hid_plugin_id id, struct hid_plugin *plugin)
+{
+ if (NR_HID_PLUGINS <= 0)
+ return -ENOENT;
+
+ /*
+ * The corresponding plugin is the only writer of hid_plugins[id], so
+ * there is no need to protect the read access with RCU or to protect
+ * the write access with an extra lock.
+ *
+ * However, try our best to do some sanity checks.
+ */
+ if (WARN_ON(rcu_access_pointer(hid_plugins[id])))
+ return -EEXIST;
+
+ rcu_assign_pointer(hid_plugins[id], plugin);
+ synchronize_rcu();
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__hid_plugin_register);
+
+void __hid_plugin_unregister(enum hid_plugin_id id, struct hid_plugin *plugin)
+{
+ if (NR_HID_PLUGINS <= 0)
+ return;
+
+ if (WARN_ON(rcu_access_pointer(hid_plugins[id]) != plugin))
+ return;
+
+ rcu_assign_pointer(hid_plugins[id], NULL);
+ synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(__hid_plugin_unregister);
diff --git a/drivers/hid/hid-plugin.h b/drivers/hid/hid-plugin.h
new file mode 100644
index 000000000000..e7bc98639d9f
--- /dev/null
+++ b/drivers/hid/hid-plugin.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * HID plugin helpers
+ *
+ * Copyright (c) 2026 Rong Zhang <i@rong.moe>
+ */
+
+#ifndef __HID_PLUGIN_H
+#define __HID_PLUGIN_H
+
+#include <linux/bug.h>
+#include <linux/build_bug.h>
+#include <linux/compiler.h>
+#include <linux/err.h>
+#include <linux/kconfig.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+enum hid_plugin_id {
+ HID_PLUGIN_DISABLED = -1,
+
+ NR_HID_PLUGINS,
+};
+
+#if !IS_ENABLED(CONFIG_HID_PLUGIN)
+static_assert(NR_HID_PLUGINS == 0);
+#endif
+
+struct hid_plugin {
+ struct module *owner;
+};
+
+struct hid_plugin *__hid_plugin_get(enum hid_plugin_id id);
+int __hid_plugin_register(enum hid_plugin_id id, struct hid_plugin *plugin);
+void __hid_plugin_unregister(enum hid_plugin_id id, struct hid_plugin *plugin);
+
+/**
+ * hid_plugin_enabled() - Check if an HID plugin is enabled.
+ * @id: Unique ID of the desired HID plugin
+ *
+ * Return: boolean.
+ */
+static __always_inline bool hid_plugin_enabled(enum hid_plugin_id id)
+{
+ BUILD_BUG_ON(!__builtin_constant_p(id) || id >= NR_HID_PLUGINS || id < HID_PLUGIN_DISABLED);
+
+ return id != HID_PLUGIN_DISABLED;
+}
+
+/**
+ * hid_plugin_get() - Get an HID plugin and increment its refcount.
+ * @id: Unique ID of the desired HID plugin
+ *
+ * If the plugin is builtin or loaded, a pointer to the plugin is returned with
+ * the plugin's module refcount incremented. If the plugin is unloading during
+ * the process, ERR_PTR(-ENXIO) is returned.
+ *
+ * If the plugin is disabled, ERR_PTR(-ENOENT) is returned.
+ *
+ * Otherwise, try loading the module with request_module() and then increment
+ * its refcount. If the plugin fails to load, ERR_PTR(-ENXIO) is returned.
+ *
+ * Return: The HID plugin corresponding to @id on success, or an ERR_PTR().
+ * Never NULL.
+ */
+static __always_inline struct hid_plugin *hid_plugin_get(enum hid_plugin_id id)
+{
+ if (hid_plugin_enabled(id))
+ return __hid_plugin_get(id);
+
+ return ERR_PTR(-ENOENT);
+}
+
+/**
+ * hid_plugin_put() - Decrement an HID plugin's refcount.
+ * @id: Unique ID of @plugin
+ * @plugin: Pointer to the HID plugin
+ *
+ * Decrement @plugin's refcount, unless it's disabled.
+ */
+static __always_inline void hid_plugin_put(enum hid_plugin_id id, struct hid_plugin *plugin)
+{
+ if (hid_plugin_enabled(id) && !IS_ERR_OR_NULL(plugin))
+ module_put(plugin->owner);
+}
+
+/**
+ * hid_plugin_register() - Register an HID plugin.
+ * @id: Unique ID of @plugin
+ * @plugin: Pointer to the HID plugin
+ *
+ * Return: 0 on success, or an error.
+ */
+static __always_inline int hid_plugin_register(enum hid_plugin_id id, struct hid_plugin *plugin)
+{
+ BUILD_BUG_ON(!hid_plugin_enabled(id));
+
+ return __hid_plugin_register(id, plugin);
+}
+
+/**
+ * hid_plugin_unregister() - Unregister an HID plugin.
+ * @id: Unique ID of @plugin
+ * @plugin: Pointer to the HID plugin
+ */
+static __always_inline void hid_plugin_unregister(enum hid_plugin_id id, struct hid_plugin *plugin)
+{
+ BUILD_BUG_ON(!hid_plugin_enabled(id));
+
+ __hid_plugin_unregister(id, plugin);
+}
+
+/*
+ * Usage:
+ *
+ * struct foo_plugin {
+ * struct hid_plugin hp;
+ * void (*bar)(struct hid_device *hdev);
+ * };
+ *
+ * static struct foo_plugin foo_plugin = {
+ * .hp = {
+ * .owner = THIS_MODULE,
+ * },
+ * .bar = hid_foo_bar,
+ * };
+ * module_hid_plugin(foo, HID_PLUGIN_FOO, &foo_plugin.hp);
+ */
+#define module_hid_plugin(_name, _id, _plugin) \
+static int __init hid_plugin_##_name##_init(void) \
+{ \
+ return hid_plugin_register(_id, _plugin); \
+} \
+subsys_initcall(hid_plugin_##_name##_init); \
+static void __exit hid_plugin_##_name##_exit(void) \
+{ \
+ hid_plugin_unregister(_id, _plugin); \
+} \
+module_exit(hid_plugin_##_name##_exit)
+
+#endif /* __HID_PLUGIN_H */
--
2.55.0
next prev parent reply other threads:[~2026-11-12 17:55 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 17:53 [PATCH v3 0/9] HID: Add hid-plugin framework & convert haptic into a plugin Rong Zhang
2026-11-12 17:53 ` [PATCH v3 1/9] HID: haptic: Fix use-after-free when draining workqueue Rong Zhang
2026-11-12 17:53 ` [PATCH v3 2/9] HID: haptic: Get rid of input_ff_destroy() in error paths Rong Zhang
2026-11-13 5:50 ` Dmitry Torokhov
2026-11-12 17:53 ` [PATCH v3 3/9] HID: haptic, multitouch: Check haptic prerequisite properly Rong Zhang
2026-11-12 17:53 ` [PATCH v3 4/9] HID: haptic, multitouch: Fix haptic device double-free and unnecessary allocation Rong Zhang
2026-11-12 17:53 ` [PATCH v3 5/9] HID: haptic: Postpone GET_REPORT to init Rong Zhang
2026-11-12 17:53 ` [PATCH v3 6/9] HID: haptic: Convert simple methods into static inline ones Rong Zhang
2026-11-12 17:54 ` Rong Zhang [this message]
2026-11-12 17:54 ` [PATCH v3 8/9] HID: haptic: Convert into a loadable plugin module Rong Zhang
2026-11-12 17:54 ` [PATCH v3 9/9] HID: haptic: Remove stubs in favor of inlined dead code elimination Rong Zhang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261113-hid-haptic-kmod-v3-7-e7dfa8766c0d@rong.moe \
--to=i@rong.moe \
--cc=aczubak@google.com \
--cc=bentiss@kernel.org \
--cc=dmitry.torokhov@gmail.com \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=rydberg@bitmath.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®