From: Ahmad Fatoum <[email protected]> So far, the devm_ functions were wrappers around kmalloc() and friends that never freed anything. Let's implement them for real as talloc children of a per-device context, which is freed when probe fails, the driver is unbound or the device is unregistered.
Like in Linux, device_remove() now releases the context even without a remove callback, so devunbind checks device_has_remove() instead. Managed memory is DMA-aligned like kmalloc()'s and can't fail like xmalloc()'s. Assisted-by: Claude:fable-5.1 Signed-off-by: Ahmad Fatoum <[email protected]> --- commands/devunbind.c | 3 +- drivers/base/Makefile | 1 + drivers/base/devres.c | 147 +++++++++++++++++++++++++++++++++++++++++ drivers/base/driver.c | 34 ++++++++-- include/device.h | 3 + include/driver.h | 17 ++++- include/linux/device.h | 48 ++++++++++---- 7 files changed, 232 insertions(+), 21 deletions(-) create mode 100644 drivers/base/devres.c diff --git a/commands/devunbind.c b/commands/devunbind.c index 9fd7483210f3..5e6fa91b5ebe 100644 --- a/commands/devunbind.c +++ b/commands/devunbind.c @@ -58,12 +58,13 @@ static int do_devunbind(int argc, char *argv[]) continue; } - if (!device_remove(dev)) { + if (!device_has_remove(dev)) { printf("no remove callback registered for %s\n", argv[i]); ret = COMMAND_ERROR; continue; } + device_remove(dev); dev->driver = NULL; list_del_init(&dev->active); } diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 94b677eea0bb..e8cdb1f27f23 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += bus.o obj-y += driver.o +obj-y += devres.o obj-y += platform.o obj-y += resource.o obj-y += class.o diff --git a/drivers/base/devres.c b/drivers/base/devres.c new file mode 100644 index 000000000000..26abc47cd997 --- /dev/null +++ b/drivers/base/devres.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * devres.c - device-managed resources on top of talloc + * + * Managed resources are children of a per-device talloc context. + */ + +#include <driver.h> +#include <dma.h> +#include <talloc.h> +#include <xfuncs.h> +#include <stdio.h> +#include <printf.h> +#include <linux/device.h> +#include <linux/overflow.h> +#include <asm/sections.h> + +static void *devres_ctx(struct device *dev) +{ + if (!dev->devres) + dev->devres = talloc_new(NULL); + + return dev->devres; +} + +/** + * devres_release_all() - release all managed resources of a device + * @dev: device to release resources of + * + * Runs all actions and frees all memory registered with devm_ functions + * in reverse order of registration. + */ +void devres_release_all(struct device *dev) +{ + talloc_free(dev->devres); + dev->devres = NULL; +} + +/* + * Memory must be DMA-safe like kmalloc()'s, so align it within the chunk + * and store the chunk pointer in front of it for devm_kfree(). + */ +static void *devres_alloc(struct device *dev, size_t size) +{ + void *chunk, *mem; + + if (!size) + return ZERO_SIZE_PTR; + + /* Saturated size_mul()/struct_size() results would wrap in ALIGN() */ + if (size > SIZE_MAX - 2 * DMA_ALIGNMENT) + panic("%s: devm allocation of %zu bytes\n", dev_name(dev), size); + + /* chunk is pointer-aligned, so mem is at most DMA_ALIGNMENT bytes in */ + chunk = xtalloc_size(devres_ctx(dev), + DMA_ALIGNMENT + ALIGN(size, DMA_ALIGNMENT)); + mem = PTR_ALIGN(chunk + sizeof(void *), DMA_ALIGNMENT); + ((void **)mem)[-1] = chunk; + + return mem; +} + +void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp) +{ + return devres_alloc(dev, size); +} +EXPORT_SYMBOL(devm_kmalloc); + +void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp) +{ + void *mem = devres_alloc(dev, size); + + memset(mem, 0, size); + + return mem; +} +EXPORT_SYMBOL(devm_kzalloc); + +/** + * devm_kfree() - free memory allocated with devm_kmalloc() early + * @dev: device the memory was allocated for + * @mem: pointer to memory allocated by a devm_ function + */ +void devm_kfree(struct device *dev, const void *mem) +{ + /* NULL/ZERO_SIZE_PTR or .rodata string from devm_kstrdup_const() */ + if (ZERO_OR_NULL_PTR(mem) || is_barebox_rodata((ulong)mem)) + return; + + talloc_free(((void **)mem)[-1]); +} +EXPORT_SYMBOL(devm_kfree); + +void *devm_kmemdup(struct device *dev, const void *src, size_t len, gfp_t gfp) +{ + return memcpy(devres_alloc(dev, len), src, len); +} +EXPORT_SYMBOL(devm_kmemdup); + +char *devm_kstrdup(struct device *dev, const char *s, gfp_t gfp) +{ + if (!s) + return NULL; + + return devm_kmemdup(dev, s, strlen(s) + 1, gfp); +} +EXPORT_SYMBOL(devm_kstrdup); + +const char *devm_kstrdup_const(struct device *dev, const char *s, gfp_t gfp) +{ + if (is_barebox_rodata((ulong)s)) + return s; + + return devm_kstrdup(dev, s, gfp); +} +EXPORT_SYMBOL(devm_kstrdup_const); + +char *devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt, + va_list ap) +{ + unsigned int len; + va_list aq; + char *p; + + va_copy(aq, ap); + len = vsnprintf(NULL, 0, fmt, aq) + 1; + va_end(aq); + + p = devres_alloc(dev, len); + vsnprintf(p, len, fmt, ap); + + return p; +} +EXPORT_SYMBOL(devm_kvasprintf); + +char *devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...) +{ + va_list ap; + char *p; + + va_start(ap, fmt); + p = devm_kvasprintf(dev, gfp, fmt, ap); + va_end(ap); + + return p; +} +EXPORT_SYMBOL(devm_kasprintf); diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 20beb1e9e6fa..e214408e12a3 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -149,6 +149,9 @@ int device_probe(struct device *dev) depth--; + if (ret) + devres_release_all(dev); + switch (ret) { case 0: return 0; @@ -292,6 +295,8 @@ int register_device(struct device *new_device) } EXPORT_SYMBOL(register_device); +static bool __device_remove(struct device *dev); + int unregister_device(struct device *old_dev) { struct device_alias *alias, *at; @@ -305,8 +310,9 @@ int unregister_device(struct device *old_dev) of_pinctrl_unregister_consumer(old_dev); + /* Children may live in managed memory, free_device_res() releases it */ if (old_dev->driver) - device_remove(old_dev); + __device_remove(old_dev); list_for_each_entry_safe(alias, at, &device_alias_list, list) { if(alias->dev == old_dev) @@ -353,6 +359,7 @@ EXPORT_SYMBOL(unregister_device); */ void free_device_res(struct device *dev) { + devres_release_all(dev); devinfo_del(dev, NULL); free(dev->unique_name); dev->unique_name = NULL; @@ -677,17 +684,32 @@ static dev_remove_op_t get_device_remove(struct device *dev) return NULL; } -bool device_remove(struct device *dev) +static bool __device_remove(struct device *dev) { dev_remove_op_t remove = get_device_remove(dev); - if (!remove) - return false; /* nothing to do */ - remove(dev); - return true; + if (remove) + remove(dev); + + return remove != NULL; +} + +bool device_remove(struct device *dev) +{ + bool ret = __device_remove(dev); + + devres_release_all(dev); + + return ret; } EXPORT_SYMBOL_GPL(device_remove); +bool device_has_remove(struct device *dev) +{ + return get_device_remove(dev) != NULL; +} +EXPORT_SYMBOL_GPL(device_has_remove); + static void devices_shutdown(void) { struct device *dev; diff --git a/include/device.h b/include/device.h index 7abeed9c460f..9d5f57a1c496 100644 --- a/include/device.h +++ b/include/device.h @@ -61,6 +61,7 @@ struct of_device_id; * should actually detect client devices. * @rescan: Callback to rescan the device. * @deferred_probe_reason: If a driver probe is deferred, this stores the last error. + * @devres: talloc context of managed resources, see devm_kmalloc(). */ struct device { union { @@ -122,6 +123,8 @@ struct device { void (*rescan)(struct device *); char *deferred_probe_reason; + + void *devres; }; #define bobj_to_dev(__bobj) container_of_const(__bobj, struct device, bobject) diff --git a/include/driver.h b/include/driver.h index a93da2e06f62..372b1297c6c4 100644 --- a/include/driver.h +++ b/include/driver.h @@ -78,11 +78,24 @@ int device_probe(struct device *dev); * * @dev: Device * + * Runs the bus or driver specific removal code, if any, and releases the + * device's managed resources either way. + * * Returns true if there was any bus or driver specific removal - * code that was executed and false if the function was a no-op. + * code that was executed and false if there was none. */ bool device_remove(struct device *dev); +/** + * device_has_remove - Check if a device has removal code to run + * + * @dev: Device + * + * Returns true if device_remove() would execute bus or driver specific + * removal code for @dev. + */ +bool device_has_remove(struct device *dev); + /* detect devices attached to this device (cards, disks,...) */ int device_detect(struct device *dev); int device_detect_by_name(const char *devname); @@ -98,6 +111,8 @@ static inline void put_device(struct device *dev) {} void free_device_res(struct device *dev); void free_device(struct device *dev); +void devres_release_all(struct device *dev); + static inline void device_rescan(struct device *dev) { if (dev->rescan) diff --git a/include/linux/device.h b/include/linux/device.h index e3934b505fe0..0f4f2a40a11d 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -5,24 +5,46 @@ #include <driver.h> #include <linux/slab.h> #include <linux/bug.h> +#include <linux/bitops.h> +#include <linux/overflow.h> +#include <stdarg.h> #include <mmu.h> #define device_driver driver -#define __devm_wrapper(fn, dev, ...) ({ BUG_ON(!dev); fn(__VA_ARGS__); }) +/* + * Managed memory is DMA-safe and can't fail to allocate. It's freed when + * the device is unbound, unregistered or fails to probe. + */ +void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp); +void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp); +void devm_kfree(struct device *dev, const void *mem); +void *devm_kmemdup(struct device *dev, const void *src, size_t len, gfp_t gfp); +char *devm_kstrdup(struct device *dev, const char *s, gfp_t gfp); +const char *devm_kstrdup_const(struct device *dev, const char *s, gfp_t gfp); +char *devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt, + va_list ap); +char *devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...) + __printf(3, 4); -#define devm_kmalloc(...) __devm_wrapper(kmalloc, __VA_ARGS__) -#define devm_kvasprintf(...) __devm_wrapper(kvasprintf, __VA_ARGS__) -#define devm_kasprintf(...) __devm_wrapper(kasprintf, __VA_ARGS__) -#define devm_kzalloc(...) __devm_wrapper(kzalloc, __VA_ARGS__) -#define devm_kmalloc_array(...) __devm_wrapper(kmalloc_array, __VA_ARGS__) -#define devm_kcalloc(...) __devm_wrapper(kcalloc, __VA_ARGS__) -#define devm_kfree(...) __devm_wrapper(kfree, __VA_ARGS__) -#define devm_kstrdup(...) __devm_wrapper(kstrdup, __VA_ARGS__) -#define devm_kstrdup_const(...) __devm_wrapper(kstrdup_const, __VA_ARGS__) -#define devm_kmemdup(...) __devm_wrapper(kmemdup, __VA_ARGS__) -#define devm_bitmap_zalloc(dev, nbits, gfp) \ - __devm_wrapper(bitmap_zalloc, dev, nbits) +static inline void *devm_kmalloc_array(struct device *dev, size_t n, + size_t size, gfp_t gfp) +{ + return devm_kmalloc(dev, size_mul(n, size), gfp); +} + +static inline void *devm_kcalloc(struct device *dev, size_t n, size_t size, + gfp_t gfp) +{ + return devm_kzalloc(dev, size_mul(n, size), gfp); +} + +static inline unsigned long *devm_bitmap_zalloc(struct device *dev, + unsigned int nbits, gfp_t gfp) +{ + return devm_kcalloc(dev, BITS_TO_LONGS(nbits), sizeof(unsigned long), + gfp); +} #define device_register register_device #define device_unregister unregister_device -- 2.47.3
