mail archive of the barebox mailing list
 help / color / mirror / Atom feed
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




  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