[U-Boot] [RFC PATCH 06/12] devres: introduce Devres (Managed Device Resource) framework

Masahiro Yamada yamada.masahiro at socionext.com
Wed Jul 8 06:29:36 CEST 2015


In U-Boot's driver model, memory is basically allocated and freed
in the core framework.  So, low level drivers generally only have
to specify the size of needed memory with .priv_auto_alloc_size,
.platdata_auto_alloc_size, etc.  Nevertheless, some drivers still
need to allocate memory on their own in case they cannot statically
know how much memory is needed.  Moreover, I am afraid the failure
paths of driver model core parts are getting messier as more and
more memory size members are supported, .per_child_auto_alloc_size,
.per_child_platdata_auto_alloc_size...  So, I believe it is
reasonable enough to port Devres into U-boot.

As you know, Devres, which originates in Linux, manages device
resources for each device and automatically releases them on driver
detach.  With devres, device resources are guaranteed to be freed
whether initialization fails half-way or the device gets detached.

The basic idea is totally the same to that of Linux, but I tweaked
it a bit so that it fits in U-Boot's driver model.

In U-Boot, drivers are activated in two steps: binding and probing.
Binding puts a driver and a device together.  It is just data
manipulation on the system memory, so nothing has happened on the
hardware device at this moment.  When the device is really used, it
is probed.  Probing initializes the real hardware device to make it
really ready for use.

So, the resources acquired during the probing process must be freed
when the device is removed.  Likewise, what has been allocated in
binding should be released when the device is unbound.  The struct
devres has a member "probe" to remember when the resource was
allocated.

CONFIG_DEBUG_DEVRES is also supported for easier debugging.
If enabled, debug messages are printed each time a resource is
allocated/freed.

Signed-off-by: Masahiro Yamada <yamada.masahiro at socionext.com>
---

 drivers/core/Kconfig         |  10 ++++
 drivers/core/Makefile        |   2 +-
 drivers/core/device-remove.c |   5 ++
 drivers/core/device.c        |   3 +
 drivers/core/devres.c        | 137 +++++++++++++++++++++++++++++++++++++++++++
 include/dm/device.h          |  17 ++++++
 6 files changed, 173 insertions(+), 1 deletion(-)
 create mode 100644 drivers/core/devres.c

diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig
index 2861b43..5966801 100644
--- a/drivers/core/Kconfig
+++ b/drivers/core/Kconfig
@@ -55,3 +55,13 @@ config DM_SEQ_ALIAS
 	  Most boards will have a '/aliases' node containing the path to
 	  numbered devices (e.g. serial0 = &serial0). This feature can be
 	  disabled if it is not required, to save code space in SPL.
+
+config DEBUG_DEVRES
+	bool "Managed device resources verbose debug messages"
+	depends on DM
+	help
+	  If this option is enabled, devres debug messages are printed.
+	  Select this if you are having a problem with devres or want to
+	  debug resource management for a managed device.
+
+	  If you are unsure about this, Say N here.
diff --git a/drivers/core/Makefile b/drivers/core/Makefile
index a3fec38..cd8c104 100644
--- a/drivers/core/Makefile
+++ b/drivers/core/Makefile
@@ -4,7 +4,7 @@
 # SPDX-License-Identifier:	GPL-2.0+
 #
 
-obj-$(CONFIG_DM)	+= device.o lists.o root.o uclass.o util.o
+obj-$(CONFIG_DM)	+= device.o lists.o root.o uclass.o util.o devres.o
 ifndef CONFIG_SPL_BUILD
 obj-$(CONFIG_OF_CONTROL) += simple-bus.o
 endif
diff --git a/drivers/core/device-remove.c b/drivers/core/device-remove.c
index 20b56f9..e1714b2 100644
--- a/drivers/core/device-remove.c
+++ b/drivers/core/device-remove.c
@@ -109,6 +109,9 @@ int device_unbind(struct udevice *dev)
 
 	if (dev->parent)
 		list_del(&dev->sibling_node);
+
+	devres_release_all(dev);
+
 	free(dev);
 
 	return 0;
@@ -142,6 +145,8 @@ void device_free(struct udevice *dev)
 			dev->parent_priv = NULL;
 		}
 	}
+
+	devres_release_probe(dev);
 }
 
 int device_remove(struct udevice *dev)
diff --git a/drivers/core/device.c b/drivers/core/device.c
index b954974..ac2c4f8 100644
--- a/drivers/core/device.c
+++ b/drivers/core/device.c
@@ -47,6 +47,7 @@ int device_bind(struct udevice *parent, const struct driver *drv,
 	INIT_LIST_HEAD(&dev->sibling_node);
 	INIT_LIST_HEAD(&dev->child_head);
 	INIT_LIST_HEAD(&dev->uclass_node);
+	INIT_LIST_HEAD(&dev->devres_head);
 	dev->platdata = platdata;
 	dev->name = name;
 	dev->of_offset = of_offset;
@@ -170,6 +171,8 @@ fail_alloc2:
 		dev->platdata = NULL;
 	}
 fail_alloc1:
+	devres_release_all(dev);
+
 	free(dev);
 
 	return ret;
diff --git a/drivers/core/devres.c b/drivers/core/devres.c
new file mode 100644
index 0000000..2e967bf
--- /dev/null
+++ b/drivers/core/devres.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015  Masahiro Yamada <yamada.masahiro at socionext.com>
+ *
+ * Based on the original work in Linux by
+ * Copyright (c) 2006  SUSE Linux Products GmbH
+ * Copyright (c) 2006  Tejun Heo <teheo at suse.de>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <linux/compat.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <dm/device.h>
+
+struct devres {
+	struct list_head		entry;
+	dr_release_t			release;
+	bool				probe;
+#ifdef CONFIG_DEBUG_DEVRES
+	const char			*name;
+	size_t				size;
+#endif
+	unsigned long long		data[];
+};
+
+#ifdef CONFIG_DEBUG_DEVRES
+
+static void set_node_dbginfo(struct devres *dr, const char *name, size_t size)
+{
+	dr->name = name;
+	dr->size = size;
+}
+
+static void devres_log(struct udevice *dev, struct devres *dr,
+		       const char *op)
+{
+	printf("%s: DEVRES %3s %p %s (%lu bytes)\n",
+	       dev->name, op, dr, dr->name, (unsigned long)dr->size);
+}
+#else /* CONFIG_DEBUG_DEVRES */
+#define set_node_dbginfo(dr, n, s)	do {} while (0)
+#define devres_log(dev, dr, op)		do {} while (0)
+#endif
+
+/**
+ * devres_alloc - Allocate device resource data
+ * @release: Release function devres will be associated with
+ * @size: Allocation size
+ * @gfp: Allocation flags
+ *
+ * Allocate devres of @size bytes.  The allocated area is zeroed, then
+ * associated with @release.  The returned pointer can be passed to
+ * other devres_*() functions.
+ *
+ * RETURNS:
+ * Pointer to allocated devres on success, NULL on failure.
+ */
+#if CONFIG_DEBUG_DEVRES
+void *__devres_alloc(dr_release_t release, size_t size, gfp_t gfp,
+		     const char *name)
+#else
+void *devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
+#endif
+{
+	size_t tot_size = sizeof(struct devres) + size;
+	struct devres *dr;
+
+	dr = kmalloc(tot_size, gfp);
+	if (unlikely(!dr))
+		return NULL;
+
+	INIT_LIST_HEAD(&dr->entry);
+	dr->release = release;
+	set_node_dbginfo(dr, name, size);
+
+	return dr->data;
+}
+
+/**
+ * devres_add - Register device resource
+ * @dev: Device to add resource to
+ * @res: Resource to register
+ *
+ * Register devres @res to @dev.  @res should have been allocated
+ * using devres_alloc().  On driver detach, the associated release
+ * function will be invoked and devres will be freed automatically.
+ */
+void devres_add(struct udevice *dev, void *res)
+{
+	struct devres *dr = container_of(res, struct devres, data);
+
+	devres_log(dev, dr, "ADD");
+	BUG_ON(!list_empty(&dr->entry));
+	dr->probe = dev->flags & DM_FLAG_BOUND ? true : false;
+	list_add_tail(&dr->entry, &dev->devres_head);
+}
+
+static void release_nodes(struct udevice *dev, struct list_head *head,
+			  bool probe_only)
+{
+	struct devres *dr, *tmp;
+
+	list_for_each_entry_safe_reverse(dr, tmp, head, entry)  {
+		if (probe_only && !dr->probe)
+			break;
+		devres_log(dev, dr, "REL");
+		dr->release(dev, dr->data);
+		list_del(&dr->entry);
+		kfree(dr);
+	}
+}
+
+/**
+ * devres_release_probe - Release managed resources allocated after probing
+ * @dev: Device to release resources for
+ *
+ * Release all resources allocated for @dev when it was probed or later.
+ * This function is called on driver removal.
+ */
+void devres_release_probe(struct udevice *dev)
+{
+	release_nodes(dev, &dev->devres_head, true);
+}
+
+/**
+ * devres_release_all - Release all managed resources
+ * @dev: Device to release resources for
+ *
+ * Release all resources associated with @dev.  This function is
+ * called on driver unbinding.
+ */
+void devres_release_all(struct udevice *dev)
+{
+	release_nodes(dev, &dev->devres_head, false);
+}
diff --git a/include/dm/device.h b/include/dm/device.h
index 3674d19..7b39659 100644
--- a/include/dm/device.h
+++ b/include/dm/device.h
@@ -96,6 +96,7 @@ struct udevice {
 	uint32_t flags;
 	int req_seq;
 	int seq;
+	struct list_head devres_head;
 };
 
 /* Maximum sequence number supported */
@@ -449,4 +450,20 @@ bool device_has_active_children(struct udevice *dev);
  */
 bool device_is_last_sibling(struct udevice *dev);
 
+/* device resource management */
+typedef void (*dr_release_t)(struct udevice *dev, void *res);
+
+#ifdef CONFIG_DEBUG_DEVRES
+void *__devres_alloc(dr_release_t release, size_t size, gfp_t gfp,
+		     const char *name);
+#define devres_alloc(release, size, gfp) \
+	__devres_alloc(release, size, gfp, #release)
+#else
+void *devres_alloc(dr_release_t release, size_t size, gfp_t gfp);
+#endif
+void devres_free(void *res);
+void devres_add(struct udevice *dev, void *res);
+void devres_release_probe(struct udevice *dev);
+void devres_release_all(struct udevice *dev);
+
 #endif
-- 
1.9.1



More information about the U-Boot mailing list