[RFC PATCH 08/15] dm: core: Add a uclass-adjunct API
Simon Glass
sjg at chromium.org
Thu Mar 19 22:35:38 CET 2026
Add the adjunct API and its implementation, allowing a device to
register in additional uclasses beyond its primary one.
Most of the code lives in a new adjunct.c file, with its functions
called from elsewhere in driver model.
Add shared ucm_alloc_plat() and ucm_alloc_priv() helpers used by both
the primary bind/probe path and adjuncts, to avoid duplicating the
allocation logic.
Add a new device_get_uclass_ops() function to look up ops by uclass ID;
this maps to device_get_ops() when DM_UC_ADJUNCT is disabled to avoid
adding to code size.
Signed-off-by: Simon Glass <sjg at chromium.org>
---
drivers/core/Makefile | 1 +
drivers/core/adjunct.c | 209 +++++++++++++++++++++++++++++++++++
drivers/core/device.c | 20 +++-
include/dm/device-internal.h | 165 +++++++++++++++++++++++++++
include/dm/device.h | 40 +++++++
5 files changed, 434 insertions(+), 1 deletion(-)
create mode 100644 drivers/core/adjunct.c
diff --git a/drivers/core/Makefile b/drivers/core/Makefile
index a549890c22b..4733e05e3d3 100644
--- a/drivers/core/Makefile
+++ b/drivers/core/Makefile
@@ -6,6 +6,7 @@ obj-y += device.o fdtaddr.o lists.o root.o uclass.o util.o tag.o
obj-$(CONFIG_$(PHASE_)ACPIGEN) += acpi.o
obj-$(CONFIG_$(PHASE_)DEVRES) += devres.o
obj-$(CONFIG_$(PHASE_)DM_DEVICE_REMOVE) += device-remove.o
+obj-$(CONFIG_$(PHASE_)DM_UC_ADJUNCT) += adjunct.o
obj-$(CONFIG_$(PHASE_)SIMPLE_BUS) += simple-bus.o
obj-$(CONFIG_SIMPLE_PM_BUS) += simple-pm-bus.o
obj-$(CONFIG_DM) += dump.o
diff --git a/drivers/core/adjunct.c b/drivers/core/adjunct.c
new file mode 100644
index 00000000000..07fd0917050
--- /dev/null
+++ b/drivers/core/adjunct.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Uclass adjunct support
+ *
+ * Copyright 2026 Canonical Ltd.
+ * Simon Glass <simon.glass at canonical.com>
+ */
+
+#include <dm.h>
+#include <malloc.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+
+void device_init_adj(struct udevice *dev)
+{
+ INIT_LIST_HEAD(&dev->adj_head);
+ dev->ucm.ops = dev->driver->ops;
+ list_add(&dev->ucm.adj_node, &dev->adj_head);
+}
+
+int device_bind_adj_(struct udevice *dev, enum uclass_id id, const void *ops)
+{
+ struct uclass_member *ucm;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(id, &uc);
+ if (ret)
+ return ret;
+
+ ucm = calloc(1, sizeof(*ucm));
+ if (!ucm)
+ return -ENOMEM;
+
+ ucm->dev = dev;
+ ucm->uc = uc;
+ ucm->ops = ops;
+ ucm->seq = uclass_find_next_free_seq(uc);
+
+ ret = ucm_alloc_plat(ucm);
+ if (ret)
+ goto err_ucm;
+
+ list_add_tail(&ucm->adj_node, &dev->adj_head);
+ list_add_tail(&ucm->uc_node, &uc->dev_head);
+
+ if (uc->uc_drv->post_bind) {
+ ret = uc->uc_drv->post_bind(dev);
+ if (ret)
+ goto err_list;
+ }
+
+ return 0;
+
+err_list:
+ list_del(&ucm->uc_node);
+ list_del(&ucm->adj_node);
+ free(ucm->uc_plat);
+err_ucm:
+ free(ucm);
+ return ret;
+}
+
+int device_add_uclass_adj(struct udevice *dev, enum uclass_id id,
+ const void *ops)
+{
+ int ret;
+
+ ret = device_bind_adj_(dev, id, ops);
+ if (ret)
+ return ret;
+
+ if (device_active(dev))
+ ret = device_probe_adjs_(dev);
+
+ return ret;
+}
+
+void *device_get_adj_uclass_priv(struct udevice *dev, enum uclass_id id)
+{
+ struct uclass_member *ucm;
+
+ list_for_each_entry(ucm, &dev->adj_head, adj_node) {
+ if (ucm->uc->uc_drv->id == id)
+ return ucm->uc_priv;
+ }
+
+ return NULL;
+}
+
+void *device_get_adj_uclass_plat(struct udevice *dev, enum uclass_id id)
+{
+ struct uclass_member *ucm;
+
+ list_for_each_entry(ucm, &dev->adj_head, adj_node) {
+ if (ucm->uc->uc_drv->id == id)
+ return ucm->uc_plat;
+ }
+
+ return NULL;
+}
+
+int ucm_alloc_plat(struct uclass_member *ucm)
+{
+ int size = ucm->uc->uc_drv->per_device_plat_auto;
+
+ if (size) {
+ ucm->uc_plat = calloc(1, size);
+ if (!ucm->uc_plat)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+int ucm_alloc_priv(struct uclass_member *ucm)
+{
+ struct uclass_driver *uc_drv = ucm->uc->uc_drv;
+
+ if (uc_drv->per_device_auto && !ucm->uc_priv) {
+ ucm->uc_priv = alloc_priv(uc_drv->per_device_auto,
+ uc_drv->flags);
+ if (!ucm->uc_priv)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+int device_probe_adjs_(struct udevice *dev)
+{
+ struct uclass_driver *uc_drv;
+ struct uclass_member *ucm;
+ int ret;
+
+ list_for_each_entry(ucm, &dev->adj_head, adj_node) {
+ if (ucm_is_primary(ucm))
+ continue;
+ if (ucm_alloc_priv(ucm))
+ return -ENOMEM;
+
+ uc_drv = ucm->uc->uc_drv;
+ if (uc_drv->post_probe) {
+ ret = uc_drv->post_probe(dev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void ucm_free(struct uclass_member *ucm)
+{
+ list_del(&ucm->uc_node);
+ list_del(&ucm->adj_node);
+ free(ucm->uc_priv);
+ free(ucm->uc_plat);
+ free(ucm);
+}
+
+int device_remove_adjs(struct udevice *dev)
+{
+ struct uclass_member *ucm;
+ int ret;
+
+ list_for_each_entry(ucm, &dev->adj_head, adj_node) {
+ if (!ucm_is_primary(ucm)) {
+ if (ucm->uc->uc_drv->pre_remove) {
+ ret = ucm->uc->uc_drv->pre_remove(dev);
+ if (ret)
+ return ret;
+ }
+ free(ucm->uc_priv);
+ ucm->uc_priv = NULL;
+ }
+ }
+
+ return 0;
+}
+
+int device_unbind_adjs(struct udevice *dev)
+{
+ struct uclass_member *ucm, *tmp;
+ int ret;
+
+ list_for_each_entry_safe(ucm, tmp, &dev->adj_head, adj_node) {
+ if (!ucm_is_primary(ucm)) {
+ if (ucm->uc->uc_drv->pre_unbind) {
+ ret = ucm->uc->uc_drv->pre_unbind(dev);
+ if (ret)
+ return ret;
+ }
+ ucm_free(ucm);
+ }
+ }
+
+ return 0;
+}
+
+void uclass_destroy_adjs(struct uclass *uc)
+{
+ struct uclass_member *ucm, *tmp;
+
+ list_for_each_entry_safe(ucm, tmp, &uc->dev_head, uc_node) {
+ if (!ucm_is_primary(ucm))
+ ucm_free(ucm);
+ }
+}
diff --git a/drivers/core/device.c b/drivers/core/device.c
index f90c436e0a9..1de392238f8 100644
--- a/drivers/core/device.c
+++ b/drivers/core/device.c
@@ -301,7 +301,7 @@ int device_reparent(struct udevice *dev, struct udevice *new_parent)
return 0;
}
-static void *alloc_priv(int size, uint flags)
+void *alloc_priv(int size, uint flags)
{
void *priv;
@@ -1186,6 +1186,24 @@ void dev_int_set_seq(struct udevice *dev, int seq)
dev->ucm.seq = seq;
}
+const void *device_get_uclass_ops(struct udevice *dev, enum uclass_id id)
+{
+ if (CONFIG_IS_ENABLED(DM_UC_ADJUNCT)) {
+ struct uclass_member *ucm;
+
+ /* Walk primary + adjunct memberships for matching uclass */
+ list_for_each_entry(ucm, &dev->adj_head, adj_node) {
+ if (ucm->uc->uc_drv->id == id)
+ return ucm->ops;
+ }
+ } else {
+ if (dev->ucm.uc->uc_drv->id == id)
+ return dev->driver->ops;
+ }
+
+ return NULL;
+}
+
#if CONFIG_IS_ENABLED(OF_REAL)
bool device_is_compatible(const struct udevice *dev, const char *compat)
{
diff --git a/include/dm/device-internal.h b/include/dm/device-internal.h
index 9382c6c23de..a129ddecf87 100644
--- a/include/dm/device-internal.h
+++ b/include/dm/device-internal.h
@@ -16,6 +16,8 @@
struct device_node;
struct driver_info;
+struct uclass;
+struct uclass_member;
struct udevice;
/*
@@ -440,4 +442,167 @@ static inline int device_notify(const struct udevice *dev, enum event_t type)
void dev_int_set_seq(struct udevice *dev, int seq);
+/**
+ * alloc_priv() - Allocate private data with optional DMA alignment
+ *
+ * @size: Size of the allocation
+ * @flags: Driver flags (DM_FLAG_ALLOC_PRIV_DMA for DMA-aligned allocation)
+ * Return: pointer to allocated memory, or NULL on failure
+ */
+void *alloc_priv(int size, uint flags);
+
+/**
+ * ucm_alloc_plat() - Allocate uclass platform data for a uclass member
+ *
+ * Uses per_device_plat_auto from the uclass driver to allocate uc_plat.
+ * Called at bind time for both primary and adjunct memberships.
+ *
+ * @ucm: uclass member to allocate for
+ * Return: 0 if OK, -ENOMEM if out of memory
+ */
+int ucm_alloc_plat(struct uclass_member *ucm);
+
+/**
+ * ucm_alloc_priv() - Allocate uclass private data for a uclass member
+ *
+ * Uses per_device_auto from the uclass driver to allocate uc_priv.
+ * Called at probe time for both primary and adjunct memberships.
+ *
+ * @ucm: uclass member to allocate for
+ * Return: 0 if OK, -ENOMEM if out of memory
+ */
+int ucm_alloc_priv(struct uclass_member *ucm);
+
+/**
+ * ucm_is_primary() - Check if a uclass member is the primary membership
+ *
+ * Each device has an embedded uclass_member for its primary uclass.
+ * Adjunct memberships are separately allocated. This macro distinguishes
+ * between the two.
+ *
+ * @ucm: uclass member to check
+ * Return: true if @ucm is the device's primary uclass membership
+ */
+#define ucm_is_primary(_ucm) ((_ucm) == &(_ucm)->dev->ucm)
+
+#if CONFIG_IS_ENABLED(DM_UC_ADJUNCT)
+/**
+ * device_bind_adj_() - Bind an adjunct uclass for a device
+ *
+ * Allocates a uclass_member, assigns ops, allocates uclass platform data
+ * and adds the device to the adjunct uclass's member list. Uclass private
+ * data is allocated later by device_probe_adjs_() at probe time.
+ *
+ * @dev: Device to add the adjunct to
+ * @id: Uclass ID for the adjunct uclass
+ * @ops: Operations pointer for the adjunct uclass
+ * Return: 0 if OK, -ve on error
+ */
+int device_bind_adj_(struct udevice *dev, enum uclass_id id,
+ const void *ops);
+
+/**
+ * device_get_adj_uclass_priv() - Get uclass private data for an adjunct
+ *
+ * @dev: Device to query
+ * @id: Uclass ID of the adjunct uclass
+ * Return: private data pointer, or NULL if not found
+ */
+void *device_get_adj_uclass_priv(struct udevice *dev, enum uclass_id id);
+
+/**
+ * device_get_adj_uclass_plat() - Get uclass platform data for an adjunct
+ *
+ * @dev: Device to query
+ * @id: Uclass ID of the adjunct uclass
+ * Return: platform data pointer, or NULL if not found
+ */
+void *device_get_adj_uclass_plat(struct udevice *dev, enum uclass_id id);
+
+/**
+ * device_probe_adjs_() - Allocate private data for adjunct uclasses
+ *
+ * Called during probe to allocate per_device_auto data for each adjunct.
+ *
+ * @dev: Device to probe adjuncts for
+ * Return: 0 if OK, -ve on error
+ */
+int device_probe_adjs_(struct udevice *dev);
+
+/**
+ * device_remove_adjs() - Remove adjunct uclasses from a device
+ *
+ * Called during remove to free per_device_auto data for each adjunct.
+ * The adjuncts remain bound.
+ *
+ * @dev: Device to remove adjunct private data for
+ * Return: 0 if OK, -ve on error
+ */
+int device_remove_adjs(struct udevice *dev);
+
+/**
+ * device_unbind_adjs() - Unbind all adjuncts from a device
+ *
+ * @dev: Device to unbind adjuncts from
+ * Return: 0 if OK, -ve on error
+ */
+int device_unbind_adjs(struct udevice *dev);
+
+/**
+ * device_init_adj() - Initialise the adjunct list for a device
+ *
+ * @dev: Device to initialise
+ */
+void device_init_adj(struct udevice *dev);
+
+/**
+ * uclass_destroy_adjs() - Destroy all adjuncts in a uclass
+ *
+ * @uc: Uclass to clean up
+ */
+void uclass_destroy_adjs(struct uclass *uc);
+#else
+static inline int device_bind_adj_(struct udevice *dev,
+ enum uclass_id id,
+ const void *ops)
+{
+ return -ENOSYS;
+}
+
+static inline void *device_get_adj_uclass_priv(struct udevice *dev,
+ enum uclass_id id)
+{
+ return NULL;
+}
+
+static inline void *device_get_adj_uclass_plat(struct udevice *dev,
+ enum uclass_id id)
+{
+ return NULL;
+}
+
+static inline int device_probe_adjs_(struct udevice *dev)
+{
+ return 0;
+}
+
+static inline int device_remove_adjs(struct udevice *dev)
+{
+ return 0;
+}
+
+static inline int device_unbind_adjs(struct udevice *dev)
+{
+ return 0;
+}
+
+static inline void device_init_adj(struct udevice *dev)
+{
+}
+
+static inline void uclass_destroy_adjs(struct uclass *uc)
+{
+}
+#endif /* DM_UC_ADJUNCT */
+
#endif
diff --git a/include/dm/device.h b/include/dm/device.h
index ebc253397a4..2a78911b942 100644
--- a/include/dm/device.h
+++ b/include/dm/device.h
@@ -203,6 +203,9 @@ struct udevice {
struct udevice *parent;
void *priv_;
struct uclass_member ucm;
+#if CONFIG_IS_ENABLED(DM_UC_ADJUNCT)
+ struct list_head adj_head;
+#endif
void *parent_priv_;
struct list_head child_head;
struct list_head sibling_node;
@@ -422,6 +425,43 @@ struct driver {
#endif
};
+/**
+ * device_get_uclass_ops() - Get the ops for a device in a given uclass
+ *
+ * If the device's driver provides ops for the given uclass (either as its
+ * primary uclass or via an adjunct registration), return them.
+ *
+ * @dev: Device to query
+ * @id: Uclass ID to look up ops for
+ * Return: ops pointer, or NULL if not found
+ */
+const void *device_get_uclass_ops(struct udevice *dev, enum uclass_id id);
+
+/**
+ * device_add_uclass_adj() - Add an adjunct uclass for a device
+ *
+ * Binds the adjunct and, if the device is already probed, also allocates
+ * uclass private data. This is the main entry point for drivers that need
+ * to register an adjunct dynamically: it does the right thing regardless of
+ * whether it is called from bind() or probe()
+ *
+ * @dev: Device to add the adjunct to
+ * @id: Uclass ID for the adjunct uclass
+ * @ops: Operations pointer for the adjunct uclass
+ * Return: 0 if OK, -ve on error
+ */
+#if CONFIG_IS_ENABLED(DM_UC_ADJUNCT)
+int device_add_uclass_adj(struct udevice *dev, enum uclass_id id,
+ const void *ops);
+#else
+static inline int device_add_uclass_adj(struct udevice *dev,
+ enum uclass_id id,
+ const void *ops)
+{
+ return -ENOSYS;
+}
+#endif
+
/**
* U_BOOT_DRIVER() - Declare a new U-Boot driver
* @__name: name of the driver
--
2.43.0
More information about the U-Boot
mailing list