diff options
-rw-r--r-- | drivers/core/Kconfig | 10 | ||||
-rw-r--r-- | drivers/core/Makefile | 2 | ||||
-rw-r--r-- | drivers/core/device-remove.c | 5 | ||||
-rw-r--r-- | drivers/core/device.c | 3 | ||||
-rw-r--r-- | drivers/core/devres.c | 196 | ||||
-rw-r--r-- | include/dm/device-internal.h | 19 | ||||
-rw-r--r-- | include/dm/device.h | 140 |
7 files changed, 374 insertions, 1 deletions
diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig index 5d0e949f05d..a002d69d79d 100644 --- a/drivers/core/Kconfig +++ b/drivers/core/Kconfig @@ -78,3 +78,13 @@ config SYSCON as a group by a single driver. Some common functionality is provided by this uclass, including accessing registers via regmap and assigning a unique number to each. + +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 ce3027a8512..260833e8ee8 100644 --- a/drivers/core/Makefile +++ b/drivers/core/Makefile @@ -4,7 +4,7 @@ # SPDX-License-Identifier: GPL-2.0+ # -obj-y += device.o lists.o root.o uclass.o util.o +obj-y += 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 45d6543067b..bd6d4062c93 100644 --- a/drivers/core/device-remove.c +++ b/drivers/core/device-remove.c @@ -95,6 +95,9 @@ int device_unbind(struct udevice *dev) if (dev->parent) list_del(&dev->sibling_node); + + devres_release_all(dev); + free(dev); return 0; @@ -128,6 +131,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 bf6f2716da7..e3a42dc928f 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 00000000000..49c270c57c8 --- /dev/null +++ b/drivers/core/devres.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com> + * + * Based on the original work in Linux by + * Copyright (c) 2006 SUSE Linux Products GmbH + * Copyright (c) 2006 Tejun Heo <teheo@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 - Bookkeeping info for managed device resource + * @entry: List to associate this structure with a device + * @release: Callback invoked when this resource is released + * @probe: Flag to show when this resource was allocated + (true = probe, false = bind) + * @name: Name of release function + * @size: Size of resource data + * @data: Resource data + */ +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 + +#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; +} + +void devres_free(void *res) +{ + if (res) { + struct devres *dr = container_of(res, struct devres, data); + + BUG_ON(!list_empty(&dr->entry)); + kfree(dr); + } +} + +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); +} + +void *devres_find(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + struct devres *dr; + + list_for_each_entry_reverse(dr, &dev->devres_head, entry) { + if (dr->release != release) + continue; + if (match && !match(dev, dr->data, match_data)) + continue; + return dr->data; + } + + return NULL; +} + +void *devres_get(struct udevice *dev, void *new_res, + dr_match_t match, void *match_data) +{ + struct devres *new_dr = container_of(new_res, struct devres, data); + void *res; + + res = devres_find(dev, new_dr->release, match, match_data); + if (!res) { + devres_add(dev, new_res); + res = new_res; + new_res = NULL; + } + devres_free(new_res); + + return res; +} + +void *devres_remove(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + void *res; + + res = devres_find(dev, release, match, match_data); + if (res) { + struct devres *dr = container_of(res, struct devres, data); + + list_del_init(&dr->entry); + devres_log(dev, dr, "REM"); + } + + return res; +} + +int devres_destroy(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + void *res; + + res = devres_remove(dev, release, match, match_data); + if (unlikely(!res)) + return -ENOENT; + + devres_free(res); + return 0; +} + +int devres_release(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + void *res; + + res = devres_remove(dev, release, match, match_data); + if (unlikely(!res)) + return -ENOENT; + + (*release)(dev, res); + devres_free(res); + return 0; +} + +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); + } +} + +void devres_release_probe(struct udevice *dev) +{ + release_nodes(dev, &dev->devres_head, true); +} + +void devres_release_all(struct udevice *dev) +{ + release_nodes(dev, &dev->devres_head, false); +} diff --git a/include/dm/device-internal.h b/include/dm/device-internal.h index 1a9ba01b3b8..a2bf0579ff5 100644 --- a/include/dm/device-internal.h +++ b/include/dm/device-internal.h @@ -155,4 +155,23 @@ fdt_addr_t simple_bus_translate(struct udevice *dev, fdt_addr_t addr); #define DM_ROOT_NON_CONST (((gd_t *)gd)->dm_root) #define DM_UCLASS_ROOT_NON_CONST (((gd_t *)gd)->uclass_root) +/* device resource management */ +/** + * 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); + +/** + * 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); + #endif diff --git a/include/dm/device.h b/include/dm/device.h index 4cd7ba33863..85513e10eef 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 */ @@ -465,4 +466,143 @@ 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); +typedef int (*dr_match_t)(struct udevice *dev, void *res, void *match_data); + +#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 + +/** + * 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 associated + * with @release. The returned pointer can be passed to + * other devres_*() functions. + * + * RETURNS: + * Pointer to allocated devres on success, NULL on failure. + */ +#define devres_alloc(release, size, gfp) \ + _devres_alloc(release, size, gfp | __GFP_ZERO) + +/** + * devres_free - Free device resource data + * @res: Pointer to devres data to free + * + * Free devres created with devres_alloc(). + */ +void devres_free(void *res); + +/** + * 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); + +/** + * devres_find - Find device resource + * @dev: Device to lookup resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev which is associated with @release + * and for which @match returns 1. If @match is NULL, it's considered + * to match all. + * + * RETURNS: + * Pointer to found devres, NULL if not found. + */ +void *devres_find(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data); + +/** + * devres_get - Find devres, if non-existent, add one atomically + * @dev: Device to lookup or add devres for + * @new_res: Pointer to new initialized devres to add if not found + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev which has the same release function + * as @new_res and for which @match return 1. If found, @new_res is + * freed; otherwise, @new_res is added atomically. + * + * RETURNS: + * Pointer to found or added devres. + */ +void *devres_get(struct udevice *dev, void *new_res, + dr_match_t match, void *match_data); + +/** + * devres_remove - Find a device resource and remove it + * @dev: Device to find resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev associated with @release and for + * which @match returns 1. If @match is NULL, it's considered to + * match all. If found, the resource is removed atomically and + * returned. + * + * RETURNS: + * Pointer to removed devres on success, NULL if not found. + */ +void *devres_remove(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data); + +/** + * devres_destroy - Find a device resource and destroy it + * @dev: Device to find resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev associated with @release and for + * which @match returns 1. If @match is NULL, it's considered to + * match all. If found, the resource is removed atomically and freed. + * + * Note that the release function for the resource will not be called, + * only the devres-allocated data will be freed. The caller becomes + * responsible for freeing any other data. + * + * RETURNS: + * 0 if devres is found and freed, -ENOENT if not found. + */ +int devres_destroy(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data); + +/** + * devres_release - Find a device resource and destroy it, calling release + * @dev: Device to find resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev associated with @release and for + * which @match returns 1. If @match is NULL, it's considered to + * match all. If found, the resource is removed atomically, the + * release function called and the resource freed. + * + * RETURNS: + * 0 if devres is found and freed, -ENOENT if not found. + */ +int devres_release(struct udevice *dev, dr_release_t release, + dr_match_t match, void *match_data); + #endif |