From: Ahmad Fatoum <a.fatoum@pengutronix.de>
To: barebox@lists.infradead.org
Cc: Ahmad Fatoum <a.fatoum@barebox.org>
Subject: [PATCH 10/19] driver: implement devm_ allocation helpers on top of talloc
Date: Wed, 28 Oct 2026 10:14:44 +0100 [thread overview]
Message-ID: <20261028092442.3300753-11-a.fatoum@pengutronix.de> (raw)
In-Reply-To: <20261028092442.3300753-1-a.fatoum@pengutronix.de>
From: Ahmad Fatoum <a.fatoum@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@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
next prev parent reply other threads:[~2026-10-28 9:29 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 01/19] commands: devunbind: refuse to unbind devices that were never bound Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 02/19] commands: devunbind: don't poison the active list entry Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 03/19] talloc: remove unused talloc_steal() and talloc_steal_children() Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 04/19] talloc: free children in a loop Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 05/19] talloc: add support for destructors Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 06/19] test: self: talloc: add destructor tests Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 07/19] talloc: add talloc_run_destructors() Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 08/19] mfd: pca9450: don't free device-managed memory in probe error path Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 09/19] video: backlight-pwm: allocate brightness levels with devm_kcalloc Ahmad Fatoum
2026-10-28 9:14 ` Ahmad Fatoum [this message]
2026-10-28 9:14 ` [PATCH 11/19] devres: Provide kmalloc_obj*-style API for devm_kmalloc Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 12/19] memory: atmel-ebi: use generic devm_kzalloc() Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 13/19] driver: add devm_add_action() Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 14/19] test: self: add devm_ selftest Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 15/19] driver: add devm_request_mem_resource() Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 16/19] driver: make dev_platform_ioremap_resource() device-managed Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 17/19] treewide: rename dev_platform_ioremap_resource() to devm_platform_ioremap_resource() Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 18/19] driver: make dev_platform_get_and_ioremap_resource() device-managed Ahmad Fatoum
2026-10-28 9:14 ` [PATCH 19/19] serial: stm32: request the registers with devm_request_mem_resource() Ahmad Fatoum
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261028092442.3300753-11-a.fatoum@pengutronix.de \
--to=a.fatoum@pengutronix.de \
--cc=a.fatoum@barebox.org \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox