From 608f26c51bebc68db7f2edc7590ee513d2bc5465 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Sat, 25 Jul 2015 21:52:35 +0900 Subject: devres: introduce Devres (Managed Device Resource) framework 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/free memory on their own in case they cannot statically know the necessary memory size. So, I believe it is reasonable enough to port Devres into U-boot. 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 Acked-by: Simon Glass --- drivers/core/devres.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 drivers/core/devres.c (limited to 'drivers/core/devres.c') 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 + * + * Based on the original work in Linux by + * Copyright (c) 2006 SUSE Linux Products GmbH + * Copyright (c) 2006 Tejun Heo + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +/** + * 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); +} -- cgit v1.2.3 From 2b07f6859ad17b74ce490f371f4878add6ae5a11 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Sat, 25 Jul 2015 21:52:36 +0900 Subject: devres: add devm_kmalloc() and friends (managed memory allocators) devm_kmalloc() is identical to kmalloc() except that the memory allocated with it is managed and will be automatically released when the device is removed/unbound. Likewise for the other variants. Signed-off-by: Masahiro Yamada Acked-by: Simon Glass --- drivers/core/devres.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'drivers/core/devres.c') diff --git a/drivers/core/devres.c b/drivers/core/devres.c index 49c270c57c8..f235c1bcfdd 100644 --- a/drivers/core/devres.c +++ b/drivers/core/devres.c @@ -194,3 +194,37 @@ void devres_release_all(struct udevice *dev) { release_nodes(dev, &dev->devres_head, false); } + +/* + * Managed kmalloc/kfree + */ +static void devm_kmalloc_release(struct udevice *dev, void *res) +{ + /* noop */ +} + +static int devm_kmalloc_match(struct udevice *dev, void *res, void *data) +{ + return res == data; +} + +void *devm_kmalloc(struct udevice *dev, size_t size, gfp_t gfp) +{ + void *data; + + data = _devres_alloc(devm_kmalloc_release, size, gfp); + if (unlikely(!data)) + return NULL; + + devres_add(dev, data); + + return data; +} + +void devm_kfree(struct udevice *dev, void *p) +{ + int rc; + + rc = devres_destroy(dev, devm_kmalloc_release, devm_kmalloc_match, p); + WARN_ON(rc); +} -- cgit v1.2.3 From 40b6f2d020ca8074ed38a1b6bfa170aead1a170e Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Sat, 25 Jul 2015 21:52:38 +0900 Subject: devres: add debug command to dump device resources This new command can dump all device resources associated to each device. The fields in every line shows: - The address of the resource - The size of the resource - The name of the release function - The stage in which the resource has been acquired (BIND/PROBE) Currently, there is no driver using devres, but if such drivers are implemented, the output of this command should look like this: => dm devres - root_driver - soc - extbus - serial@54006800 bfb541e8 (8 byte) devm_kmalloc_release BIND bfb54440 (4 byte) devm_kmalloc_release PROBE bfb54460 (4 byte) devm_kmalloc_release PROBE - serial@54006900 bfb54270 (8 byte) devm_kmalloc_release BIND - gpio@55000000 - i2c@58780000 bfb5bce8 (12 byte) devm_kmalloc_release PROBE bfb5bd10 (4 byte) devm_kmalloc_release PROBE - eeprom bfb54418 (12 byte) devm_kmalloc_release BIND Signed-off-by: Masahiro Yamada Acked-by: Simon Glass --- drivers/core/devres.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'drivers/core/devres.c') diff --git a/drivers/core/devres.c b/drivers/core/devres.c index f235c1bcfdd..605295bd14d 100644 --- a/drivers/core/devres.c +++ b/drivers/core/devres.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include /** * struct devres - Bookkeeping info for managed device resource @@ -195,6 +197,33 @@ void devres_release_all(struct udevice *dev) release_nodes(dev, &dev->devres_head, false); } +#ifdef CONFIG_DEBUG_DEVRES +static void dump_resources(struct udevice *dev, int depth) +{ + struct devres *dr; + struct udevice *child; + + printf("- %s\n", dev->name); + + list_for_each_entry(dr, &dev->devres_head, entry) + printf(" %p (%lu byte) %s %s\n", dr, + (unsigned long)dr->size, dr->name, + dr->probe ? "PROBE" : "BIND"); + + list_for_each_entry(child, &dev->child_head, sibling_node) + dump_resources(child, depth + 1); +} + +void dm_dump_devres(void) +{ + struct udevice *root; + + root = dm_root(); + if (root) + dump_resources(root, 0); +} +#endif + /* * Managed kmalloc/kfree */ -- cgit v1.2.3