[PATCH 10/19] driver: implement devm_ allocation helpers on top of talloc
Ahmad Fatoum
a.fatoum at pengutronix.de
Wed Oct 28 02:14:44 PDT 2026
From: Ahmad Fatoum <a.fatoum at barebox.org>
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 <a.fatoum at barebox.org>
---
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
More information about the barebox
mailing list