[PATCH] driver core: add a virtual bus for use when a simple device/bus is needed

From: Greg Kroah-Hartman
Date: Fri Jan 31 2025 - 09:01:32 EST


Many drivers abuse the platform driver/bus system as it provides a
simple way to create and bind a device to a driver-specific set of
probe/release functions. Instead of doing that, and wasting all of the
memory associated with a platform device, here is a "virtual" bus that
can be used instead.

Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
drivers/base/Makefile | 2 +-
drivers/base/base.h | 1 +
drivers/base/init.c | 1 +
drivers/base/virtual.c | 196 +++++++++++++++++++++++++++++++++
drivers/regulator/dummy.c | 35 ++----
include/linux/device/virtual.h | 32 ++++++
6 files changed, 239 insertions(+), 28 deletions(-)
create mode 100644 drivers/base/virtual.c
create mode 100644 include/linux/device/virtual.h

diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 7fb21768ca36..13eec7a1a9db 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -6,7 +6,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \
cpu.o firmware.o init.o map.o devres.o \
attribute_container.o transport_class.o \
topology.o container.o property.o cacheinfo.o \
- swnode.o
+ swnode.o virtual.o
obj-$(CONFIG_AUXILIARY_BUS) += auxiliary.o
obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
obj-y += power/
diff --git a/drivers/base/base.h b/drivers/base/base.h
index 8cf04a557bdb..1eb68e416ee1 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -137,6 +137,7 @@ int hypervisor_init(void);
static inline int hypervisor_init(void) { return 0; }
#endif
int platform_bus_init(void);
+int virtual_bus_init(void);
void cpu_dev_init(void);
void container_dev_init(void);
#ifdef CONFIG_AUXILIARY_BUS
diff --git a/drivers/base/init.c b/drivers/base/init.c
index c4954835128c..58c98a156220 100644
--- a/drivers/base/init.c
+++ b/drivers/base/init.c
@@ -35,6 +35,7 @@ void __init driver_init(void)
of_core_init();
platform_bus_init();
auxiliary_bus_init();
+ virtual_bus_init();
memory_dev_init();
node_dev_init();
cpu_dev_init();
diff --git a/drivers/base/virtual.c b/drivers/base/virtual.c
new file mode 100644
index 000000000000..b05db4618d5c
--- /dev/null
+++ b/drivers/base/virtual.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025 Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
+ * Copyright (c) 2025 The Linux Foundation
+ *
+ * A "simple" virtual bus that allows devices to be created and added
+ * automatically to it. Whenever you need a device that is not "real",
+ * use this interface instead of even thinking of using a platform device.
+ *
+ */
+#include <linux/device/virtual.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include "base.h"
+
+/*
+ * Internal rapper structure so we can hold the memory
+ * for the driver and the name string of the virtual device.
+ */
+struct virtual_object {
+ struct virtual_device virt_dev;
+ struct device_driver driver;
+ const struct virtual_driver_ops *virt_ops;
+ char name[];
+};
+#define to_virtual_object(x) container_of_const(dev, struct virtual_object, virt_dev.dev);
+
+static struct device virtual_bus = {
+ .init_name = "virt_bus",
+};
+
+static int virtual_match(struct device *dev, const struct device_driver *drv)
+{
+ struct virtual_object *virt_obj = to_virtual_object(dev);
+
+ dev_info(dev, "%s: driver: %s\n", __func__, drv->name);
+
+ /* Match is simple, strcmp()! */
+ return (strcmp(virt_obj->name, drv->name) == 0);
+}
+
+static int virtual_probe(struct device *dev)
+{
+ struct virtual_object *virt_obj = to_virtual_object(dev);
+ struct virtual_device *virt_dev = &virt_obj->virt_dev;
+ const struct virtual_driver_ops *virt_ops = virt_obj->virt_ops;
+ int ret = 0;
+
+ dev_info(dev, "%s\n", __func__);
+
+ if (virt_ops->probe)
+ ret = virt_ops->probe(virt_dev);
+
+ return ret;
+}
+
+static void virtual_remove(struct device *dev)
+{
+ struct virtual_object *virt_obj = to_virtual_object(dev);
+ struct virtual_device *virt_dev = &virt_obj->virt_dev;
+ const struct virtual_driver_ops *virt_ops = virt_obj->virt_ops;
+
+ dev_info(dev, "%s\n", __func__);
+
+ if (virt_ops->remove)
+ virt_ops->remove(virt_dev);
+}
+
+static const struct bus_type virtual_bus_type = {
+ .name = "virtual",
+ .match = virtual_match,
+ .probe = virtual_probe,
+ .remove = virtual_remove,
+};
+
+static void virtual_device_release(struct device *dev)
+{
+ struct virtual_object *virt_obj = to_virtual_object(dev);
+ struct device_driver *drv = &virt_obj->driver;
+
+ /*
+ * Now that the device is going away, it has been unbound from the
+ * driver we created for it, so it is safe to unregister the driver from
+ * the system.
+ */
+ driver_unregister(drv);
+
+ kfree(virt_obj);
+}
+
+/**
+ * __virtual_device_create - create and register a virtual device and driver
+ * @virt_ops: struct virtual_driver_ops that the new device will call back into
+ * @name: name of the device and driver we are adding
+ * @owner: module owner of the device/driver
+ *
+ * Create a new virtual device and driver, both with the same name, and register
+ * them in the driver core properly. The probe() callback of @virt_ops will be
+ * called with the new device that is created for the caller to do something
+ * with.
+ */
+struct virtual_device *__virtual_device_create(struct virtual_driver_ops *virt_ops,
+ const char *name, struct module *owner)
+{
+ struct device_driver *drv;
+ struct device *dev;
+ struct virtual_object *virt_obj;
+ struct virtual_device *virt_dev;
+ int ret;
+
+ pr_info("%s: %s\n", __func__, name);
+
+ virt_obj = kzalloc(sizeof(*virt_obj) + strlen(name) + 1, GFP_KERNEL);
+ if (!virt_obj)
+ return NULL;
+
+ /* Save off the name of the object into local memory */
+ strcpy(virt_obj->name, name);
+
+ /* Initialize the driver portion and register it with the driver core */
+ virt_obj->virt_ops = virt_ops;
+ drv = &virt_obj->driver;
+
+ drv->owner = owner;
+ drv->name = virt_obj->name;
+ drv->bus = &virtual_bus_type;
+ drv->probe_type = PROBE_PREFER_ASYNCHRONOUS;
+
+ ret = driver_register(drv);
+ if (ret) {
+ pr_err("%s: driver_register for %s virtual driver failed with %d\n",
+ __func__, name, ret);
+ kfree(virt_obj);
+ return NULL;
+ }
+
+ /* Initialize the device portion and register it with the driver core */
+ virt_dev = &virt_obj->virt_dev;
+ dev = &virt_dev->dev;
+
+ device_initialize(dev);
+ dev->release = virtual_device_release;
+ dev->parent = &virtual_bus;
+ dev->bus = &virtual_bus_type;
+ dev_set_name(dev, "%s", name);
+
+ ret = device_add(dev);
+ if (ret) {
+ pr_err("%s: device_add for %s virtual device failed with %d\n",
+ __func__, name, ret);
+ put_device(dev);
+ return NULL;
+ }
+
+ return virt_dev;
+}
+EXPORT_SYMBOL_GPL(__virtual_device_create);
+
+/**
+ * virtual_device_destroy - destroy a virtual device
+ * @virt_dev: virtual device to destroy
+ *
+ * Unregister and free all memory associated with a virtual device.
+ */
+void virtual_device_destroy(struct virtual_device *virt_dev)
+{
+ struct device *dev = &virt_dev->dev;
+
+ if (IS_ERR_OR_NULL(virt_dev))
+ return;
+
+ device_del(dev);
+
+ /* The final put_device() will clean up the driver we created for this device. */
+ put_device(dev);
+}
+EXPORT_SYMBOL_GPL(virtual_device_destroy);
+
+int __init virtual_bus_init(void)
+{
+ int error;
+
+ error = device_register(&virtual_bus);
+ if (error) {
+ put_device(&virtual_bus);
+ return error;
+ }
+
+ error = bus_register(&virtual_bus_type);
+ if (error)
+ device_unregister(&virtual_bus);
+
+ return error;
+}
diff --git a/drivers/regulator/dummy.c b/drivers/regulator/dummy.c
index 5b9b9e4e762d..875c36a66971 100644
--- a/drivers/regulator/dummy.c
+++ b/drivers/regulator/dummy.c
@@ -13,7 +13,7 @@

#include <linux/err.h>
#include <linux/export.h>
-#include <linux/platform_device.h>
+#include <linux/device/virtual.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>

@@ -37,15 +37,15 @@ static const struct regulator_desc dummy_desc = {
.ops = &dummy_ops,
};

-static int dummy_regulator_probe(struct platform_device *pdev)
+static int dummy_regulator_probe(struct virtual_device *vdev)
{
struct regulator_config config = { };
int ret;

- config.dev = &pdev->dev;
+ config.dev = &vdev->dev;
config.init_data = &dummy_initdata;

- dummy_regulator_rdev = devm_regulator_register(&pdev->dev, &dummy_desc,
+ dummy_regulator_rdev = devm_regulator_register(&vdev->dev, &dummy_desc,
&config);
if (IS_ERR(dummy_regulator_rdev)) {
ret = PTR_ERR(dummy_regulator_rdev);
@@ -56,36 +56,17 @@ static int dummy_regulator_probe(struct platform_device *pdev)
return 0;
}

-static struct platform_driver dummy_regulator_driver = {
+struct virtual_driver_ops dummy_regulator_driver = {
.probe = dummy_regulator_probe,
- .driver = {
- .name = "reg-dummy",
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- },
};

-static struct platform_device *dummy_pdev;
+static struct virtual_device *dummy_vdev;

void __init regulator_dummy_init(void)
{
- int ret;
-
- dummy_pdev = platform_device_alloc("reg-dummy", -1);
- if (!dummy_pdev) {
+ dummy_vdev = virtual_device_create(&dummy_regulator_driver, "reg-dummy");
+ if (!dummy_vdev) {
pr_err("Failed to allocate dummy regulator device\n");
return;
}
-
- ret = platform_device_add(dummy_pdev);
- if (ret != 0) {
- pr_err("Failed to register dummy regulator device: %d\n", ret);
- platform_device_put(dummy_pdev);
- return;
- }
-
- ret = platform_driver_register(&dummy_regulator_driver);
- if (ret != 0) {
- pr_err("Failed to register dummy regulator driver: %d\n", ret);
- platform_device_unregister(dummy_pdev);
- }
}
diff --git a/include/linux/device/virtual.h b/include/linux/device/virtual.h
new file mode 100644
index 000000000000..cfd1c6ab541d
--- /dev/null
+++ b/include/linux/device/virtual.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025 Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
+ * Copyright (c) 2025 The Linux Foundation
+ *
+ * A "simple" virtual bus that allows devices to be created and added
+ * automatically to it. Whenever you need a device that is not "real",
+ * use this interface instead of even thinking of using a platform device.
+ *
+ */
+#ifndef _VIRTUAL_DEVICE_H_
+#define _VIRTUAL_DEVICE_H_
+
+#include <linux/module.h>
+#include <linux/device.h>
+
+struct virtual_device {
+ struct device dev;
+};
+#define to_virtual_device(x) container_of_const((x), struct virtual_device, dev)
+
+struct virtual_driver_ops {
+ int (*probe)(struct virtual_device *virt_dev);
+ void (*remove)(struct virtual_device *virt_dev);
+};
+
+#define virtual_device_create(virt_ops, name) __virtual_device_create(virt_ops, name, THIS_MODULE)
+struct virtual_device *__virtual_device_create(struct virtual_driver_ops *virt_ops,
+ const char *name, struct module *module);
+void virtual_device_destroy(struct virtual_device *virt_dev);
+
+#endif /* _VIRTUAL_DEVICE_H_ */
--
2.48.1