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


Reply via email to