From: David Sauerwein <dssauerw@amazon.de>
To: <stable@vger.kernel.org>
Cc: <nh-open-source@amazon.com>, Danilo Krummrich <dakr@kernel.org>,
"Gui-Dong Han" <hanguidong02@gmail.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Sasha Levin <sashal@kernel.org>,
"David Sauerwein" <dssauerw@amazon.de>
Subject: [PATCH 6.6.y 1/2] driver core: generalize driver_override in struct device
Date: Wed, 20 May 2026 14:01:59 +0000 [thread overview]
Message-ID: <20260520140200.45804-1-dssauerw@amazon.de> (raw)
From: Danilo Krummrich <dakr@kernel.org>
[ Upstream commit cb3d1049f4ea77d5ad93f17d8ac1f2ed4da70501 ]
Currently, there are 12 busses (including platform and PCI) that
duplicate the driver_override logic for their individual devices.
All of them seem to be prone to the bug described in [1].
While this could be solved for every bus individually using a separate
lock, solving this in the driver-core generically results in less (and
cleaner) changes overall.
Thus, move driver_override to struct device, provide corresponding
accessors for busses and handle locking with a separate lock internally.
In particular, add device_set_driver_override(),
device_has_driver_override(), device_match_driver_override() and
generalize the sysfs store() and show() callbacks via a driver_override
feature flag in struct bus_type.
Until all busses have migrated, keep driver_set_override() in place.
Note that we can't use the device lock for the reasons described in [2].
Link: https://bugzilla.kernel.org/show_bug.cgi?id=220789 [1]
Link: https://lore.kernel.org/driver-core/DGRGTIRHA62X.3RY09D9SOK77P@kernel.org/ [2]
Tested-by: Gui-Dong Han <hanguidong02@gmail.com>
Co-developed-by: Gui-Dong Han <hanguidong02@gmail.com>
Signed-off-by: Gui-Dong Han <hanguidong02@gmail.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://patch.msgid.link/20260303115720.48783-2-dakr@kernel.org
[ Use dev->bus instead of sp->bus for consistency; fix commit message to
refer to the struct bus_type's driver_override feature flag. - Danilo ]
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
Stable-dep-of: 2b38efc05bf7 ("driver core: platform: use generic driver_override infrastructure")
Signed-off-by: Sasha Levin <sashal@kernel.org>
Signed-off-by: David Sauerwein <dssauerw@amazon.de>
---
drivers/base/bus.c | 43 ++++++++++++++++++++++++++-
drivers/base/core.c | 2 ++
drivers/base/dd.c | 60 ++++++++++++++++++++++++++++++++++++++
include/linux/device.h | 54 ++++++++++++++++++++++++++++++++++
include/linux/device/bus.h | 4 +++
5 files changed, 162 insertions(+), 1 deletion(-)
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index b97e13a52c33..ae57df879bca 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -463,6 +463,36 @@ int bus_for_each_drv(const struct bus_type *bus, struct device_driver *start,
}
EXPORT_SYMBOL_GPL(bus_for_each_drv);
+static ssize_t driver_override_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = __device_set_driver_override(dev, buf, count);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t driver_override_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ guard(spinlock)(&dev->driver_override.lock);
+ return sysfs_emit(buf, "%s\n", dev->driver_override.name);
+}
+static DEVICE_ATTR_RW(driver_override);
+
+static struct attribute *driver_override_dev_attrs[] = {
+ &dev_attr_driver_override.attr,
+ NULL,
+};
+
+static const struct attribute_group driver_override_dev_group = {
+ .attrs = driver_override_dev_attrs,
+};
+
/**
* bus_add_device - add device to bus
* @dev: device being added
@@ -496,9 +526,15 @@ int bus_add_device(struct device *dev)
if (error)
goto out_put;
+ if (dev->bus->driver_override) {
+ error = device_add_group(dev, &driver_override_dev_group);
+ if (error)
+ goto out_groups;
+ }
+
error = sysfs_create_link(&sp->devices_kset->kobj, &dev->kobj, dev_name(dev));
if (error)
- goto out_groups;
+ goto out_override;
error = sysfs_create_link(&dev->kobj, &sp->subsys.kobj, "subsystem");
if (error)
@@ -509,6 +545,9 @@ int bus_add_device(struct device *dev)
out_subsys:
sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
+out_override:
+ if (dev->bus->driver_override)
+ device_remove_group(dev, &driver_override_dev_group);
out_groups:
device_remove_groups(dev, sp->bus->dev_groups);
out_put:
@@ -567,6 +606,8 @@ void bus_remove_device(struct device *dev)
sysfs_remove_link(&dev->kobj, "subsystem");
sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
+ if (dev->bus->driver_override)
+ device_remove_group(dev, &driver_override_dev_group);
device_remove_groups(dev, dev->bus->dev_groups);
if (klist_node_attached(&dev->p->knode_bus))
klist_del(&dev->p->knode_bus);
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 3c172e6d3fe0..5048849cb97a 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2505,6 +2505,7 @@ static void device_release(struct kobject *kobj)
devres_release_all(dev);
kfree(dev->dma_range_map);
+ kfree(dev->driver_override.name);
if (dev->release)
dev->release(dev);
@@ -3153,6 +3154,7 @@ void device_initialize(struct device *dev)
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
+ spin_lock_init(&dev->driver_override.lock);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index d371c3437dc6..44f9d0a06d5b 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -380,6 +380,66 @@ static void __exit deferred_probe_exit(void)
}
__exitcall(deferred_probe_exit);
+int __device_set_driver_override(struct device *dev, const char *s, size_t len)
+{
+ const char *new, *old;
+ char *cp;
+
+ if (!s)
+ return -EINVAL;
+
+ /*
+ * The stored value will be used in sysfs show callback (sysfs_emit()),
+ * which has a length limit of PAGE_SIZE and adds a trailing newline.
+ * Thus we can store one character less to avoid truncation during sysfs
+ * show.
+ */
+ if (len >= (PAGE_SIZE - 1))
+ return -EINVAL;
+
+ /*
+ * Compute the real length of the string in case userspace sends us a
+ * bunch of \0 characters like python likes to do.
+ */
+ len = strlen(s);
+
+ if (!len) {
+ /* Empty string passed - clear override */
+ spin_lock(&dev->driver_override.lock);
+ old = dev->driver_override.name;
+ dev->driver_override.name = NULL;
+ spin_unlock(&dev->driver_override.lock);
+ kfree(old);
+
+ return 0;
+ }
+
+ cp = strnchr(s, len, '\n');
+ if (cp)
+ len = cp - s;
+
+ new = kstrndup(s, len, GFP_KERNEL);
+ if (!new)
+ return -ENOMEM;
+
+ spin_lock(&dev->driver_override.lock);
+ old = dev->driver_override.name;
+ if (cp != s) {
+ dev->driver_override.name = new;
+ spin_unlock(&dev->driver_override.lock);
+ } else {
+ /* "\n" passed - clear override */
+ dev->driver_override.name = NULL;
+ spin_unlock(&dev->driver_override.lock);
+
+ kfree(new);
+ }
+ kfree(old);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__device_set_driver_override);
+
/**
* device_is_bound() - Check if device is bound to a driver
* @dev: device to check
diff --git a/include/linux/device.h b/include/linux/device.h
index 8fb9bd71fcd0..2a9017eec15c 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -643,6 +643,8 @@ enum struct_device_flags {
* on. This shrinks the "Board Support Packages" (BSPs) and
* minimizes board-specific #ifdefs in drivers.
* @driver_data: Private pointer for driver specific info.
+ * @driver_override: Driver name to force a match. Do not touch directly; use
+ * device_set_driver_override() instead.
* @links: Links to suppliers and consumers of this device.
* @power: For device power management.
* See Documentation/driver-api/pm/devices.rst for details.
@@ -735,6 +737,10 @@ struct device {
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set_drvdata/dev_get_drvdata */
+ struct {
+ const char *name;
+ spinlock_t lock;
+ } driver_override;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
@@ -882,6 +888,54 @@ struct device_link {
#define kobj_to_dev(__kobj) container_of_const(__kobj, struct device, kobj)
+int __device_set_driver_override(struct device *dev, const char *s, size_t len);
+
+/**
+ * device_set_driver_override() - Helper to set or clear driver override.
+ * @dev: Device to change
+ * @s: NUL-terminated string, new driver name to force a match, pass empty
+ * string to clear it ("" or "\n", where the latter is only for sysfs
+ * interface).
+ *
+ * Helper to set or clear driver override of a device.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+static inline int device_set_driver_override(struct device *dev, const char *s)
+{
+ return __device_set_driver_override(dev, s, s ? strlen(s) : 0);
+}
+
+/**
+ * device_has_driver_override() - Check if a driver override has been set.
+ * @dev: device to check
+ *
+ * Returns true if a driver override has been set for this device.
+ */
+static inline bool device_has_driver_override(struct device *dev)
+{
+ guard(spinlock)(&dev->driver_override.lock);
+ return !!dev->driver_override.name;
+}
+
+/**
+ * device_match_driver_override() - Match a driver against the device's driver_override.
+ * @dev: device to check
+ * @drv: driver to match against
+ *
+ * Returns > 0 if a driver override is set and matches the given driver, 0 if a
+ * driver override is set but does not match, or < 0 if a driver override is not
+ * set at all.
+ */
+static inline int device_match_driver_override(struct device *dev,
+ const struct device_driver *drv)
+{
+ guard(spinlock)(&dev->driver_override.lock);
+ if (dev->driver_override.name)
+ return !strcmp(dev->driver_override.name, drv->name);
+ return -1;
+}
+
/**
* device_iommu_mapped - Returns true when the device DMA is translated
* by an IOMMU
diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h
index ae10c4322754..d43ce072d747 100644
--- a/include/linux/device/bus.h
+++ b/include/linux/device/bus.h
@@ -65,6 +65,9 @@ struct fwnode_handle;
* @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU
* driver implementations to a bus and allow the driver to do
* bus-specific setup
+ * @driver_override: Set to true if this bus supports the driver_override
+ * mechanism, which allows userspace to force a specific
+ * driver to bind to a device via a sysfs attribute.
* @need_parent_lock: When probing or removing a device on this bus, the
* device core should lock the device's parent.
*
@@ -106,6 +109,7 @@ struct bus_type {
const struct iommu_ops *iommu_ops;
+ bool driver_override;
bool need_parent_lock;
};
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
next reply other threads:[~2026-05-20 14:02 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-20 14:01 David Sauerwein [this message]
2026-05-20 14:02 ` [PATCH 6.6.y 2/2] driver core: platform: use generic driver_override infrastructure David Sauerwein
2026-05-24 12:09 ` [PATCH 6.6.y 1/2] driver core: generalize driver_override in struct device Sasha Levin
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=20260520140200.45804-1-dssauerw@amazon.de \
--to=dssauerw@amazon.de \
--cc=dakr@kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=hanguidong02@gmail.com \
--cc=nh-open-source@amazon.com \
--cc=sashal@kernel.org \
--cc=stable@vger.kernel.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.