mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 00/19] driver: add devm_ support
@ 2026-10-28  9:14 Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 01/19] commands: devunbind: refuse to unbind devices that were never bound Ahmad Fatoum
                   ` (18 more replies)
  0 siblings, 19 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

We currently have only devm_ macro fakery that leaks resources on device
removal. This is flagged by static analysis and requires modifications
to ported kernel code that uses devres-managed actions.

Implement destructor support into talloc and use it implement a subset
of the Linux devm functionality. 

How devm_ allocated device resources are freed is a bit different from
Linux:

- Runtime unbounding of a device driver or removal of a device
  frees _all_ device resources

- Boot time invocation of the remove callback is followed by running
  _only_ the destructors. Allocations are leaked as the kernel will
  reclaim all memory anyway.

Ahmad Fatoum (19):
  commands: devunbind: refuse to unbind devices that were never bound
  commands: devunbind: don't poison the active list entry
  talloc: remove unused talloc_steal() and talloc_steal_children()
  talloc: free children in a loop
  talloc: add support for destructors
  test: self: talloc: add destructor tests
  talloc: add talloc_run_destructors()
  mfd: pca9450: don't free device-managed memory in probe error path
  video: backlight-pwm: allocate brightness levels with devm_kcalloc
  driver: implement devm_ allocation helpers on top of talloc
  devres: Provide kmalloc_obj*-style API for devm_kmalloc
  memory: atmel-ebi: use generic devm_kzalloc()
  driver: add devm_add_action()
  test: self: add devm_ selftest
  driver: add devm_request_mem_resource()
  driver: make dev_platform_ioremap_resource() device-managed
  treewide: rename dev_platform_ioremap_resource() to
    devm_platform_ioremap_resource()
  driver: make dev_platform_get_and_ioremap_resource() device-managed
  serial: stm32: request the registers with devm_request_mem_resource()

 commands/devunbind.c                          |  11 +-
 drivers/base/Makefile                         |   1 +
 drivers/base/devres.c                         | 259 ++++++++++++++
 drivers/base/driver.c                         |  61 ++--
 drivers/hw_random/atmel-rng.c                 |   2 +-
 drivers/hw_random/bcm2835-rng.c               |   2 +-
 drivers/hw_random/iproc-rng200.c              |   2 +-
 drivers/hw_random/omap-rng.c                  |   2 +-
 drivers/hw_random/timeriomem-rng.c            |   2 +-
 drivers/memory/atmel-ebi.c                    |   6 +-
 drivers/mfd/atmel-flexcom.c                   |   2 +-
 drivers/mfd/pca9450.c                         |  20 +-
 .../phy/rockchip/phy-rockchip-inno-dsidphy.c  |   2 +-
 drivers/phy/rockchip/phy-rockchip-usbdp.c     |   2 +-
 drivers/pmdomain/imx/imx8mp-blk-ctrl.c        |   2 +-
 drivers/pmdomain/imx/imx93-blk-ctrl.c         |   2 +-
 drivers/pmdomain/imx/imx93-pd.c               |   2 +-
 drivers/pwm/pwm-imx-tpm.c                     |   2 +-
 drivers/pwm/pwm-rockchip.c                    |   2 +-
 drivers/serial/serial_stm32.c                 |   6 +-
 drivers/spi/spi-fsl-qspi.c                    |   2 +-
 drivers/usb/dwc3/dwc3-imx8mp.c                |   2 +-
 drivers/video/backlight-pwm.c                 |   6 +-
 drivers/video/rockchip/dw-mipi-dsi-rockchip.c |   2 +-
 include/device.h                              |   3 +
 include/driver.h                              |  27 +-
 include/linux/device.h                        | 152 ++++++--
 include/talloc.h                              |   5 +-
 lib/talloc.c                                  | 205 ++++++-----
 test/self/Kconfig                             |   6 +
 test/self/Makefile                            |   1 +
 test/self/devres.c                            | 327 ++++++++++++++++++
 test/self/talloc.c                            | 207 +++++++++++
 33 files changed, 1148 insertions(+), 187 deletions(-)
 create mode 100644 drivers/base/devres.c
 create mode 100644 test/self/devres.c

-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 01/19] commands: devunbind: refuse to unbind devices that were never bound
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 02/19] commands: devunbind: don't poison the active list entry Ahmad Fatoum
                   ` (17 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

devunbind on a device without a driver crashes in get_device_remove(),
which dereferences dev->driver unchecked. Print a message instead.

-f is not affected, unregistering an unbound device is fine.

Fixes: 4d515afe56eb ("driver: make CONFIG_DEBUG_PROBES more useful for removal")
Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 commands/devunbind.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/commands/devunbind.c b/commands/devunbind.c
index d30193b28527..76dd0b715e6e 100644
--- a/commands/devunbind.c
+++ b/commands/devunbind.c
@@ -52,6 +52,12 @@ static int do_devunbind(int argc, char *argv[])
 			continue;
 		}
 
+		if (!dev->driver) {
+			printf("%s is not bound to a driver\n", argv[i]);
+			ret = COMMAND_ERROR;
+			continue;
+		}
+
 		if (!device_remove(dev)) {
 			printf("no remove callback registered for %s\n", argv[i]);
 			ret = COMMAND_ERROR;
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 02/19] commands: devunbind: don't poison the active list entry
  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 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 03/19] talloc: remove unused talloc_steal() and talloc_steal_children() Ahmad Fatoum
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

devunbind removes the device from the active list with list_del(),
which poisons the entry, so a later devunbind -f crashes when
unregister_device() removes it a second time. Use list_del_init()
instead.

Fixes: 90244aef7299 ("commands: add new devunbind debugging command")
Assisted-by: Claude:opus-5.5
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 commands/devunbind.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/commands/devunbind.c b/commands/devunbind.c
index 76dd0b715e6e..9fd7483210f3 100644
--- a/commands/devunbind.c
+++ b/commands/devunbind.c
@@ -65,7 +65,7 @@ static int do_devunbind(int argc, char *argv[])
 		}
 
 		dev->driver = NULL;
-		list_del(&dev->active);
+		list_del_init(&dev->active);
 	}
 
 	return ret;
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 03/19] talloc: remove unused talloc_steal() and talloc_steal_children()
  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 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 04/19] talloc: free children in a loop Ahmad Fatoum
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Neither has users and devres doesn't reparent, so let's drop them
instead of teaching them about destructors. Internally, call
mem_set_parent() directly, which talloc_steal() just wrapped.

Assisted-by: Claude:opus-5.5
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 include/talloc.h |  2 --
 lib/talloc.c     | 67 ++----------------------------------------------
 2 files changed, 2 insertions(+), 67 deletions(-)

diff --git a/include/talloc.h b/include/talloc.h
index 79a175bd5441..1f2dd346a7c1 100644
--- a/include/talloc.h
+++ b/include/talloc.h
@@ -35,8 +35,6 @@ const char *talloc_strdup_const(const void *parent, const char *str);
 void talloc_free_const(const void *ptr);
 
 void *talloc_parent(const void *mem);
-void talloc_steal(const void *parent, void *mem);
-void talloc_steal_children(const void *parent, void *mem);
 
 size_t talloc_total_blocks(const void *mem);
 
diff --git a/lib/talloc.c b/lib/talloc.c
index a1a7f8f0adc8..2bfbac2e9c29 100644
--- a/lib/talloc.c
+++ b/lib/talloc.c
@@ -34,7 +34,6 @@
 #include <asm/sections.h>
 #include "talloc.h"
 
-// TODO difference between set_parent and steal?
 // TODO use a bit from TLSF for extra safety to differentiate talloc
 // TODO allow leaf nodes (that have no children to be destructors!!)
 #define  is_root(hdr) (!(hdr)->prev)
@@ -69,7 +68,7 @@ static void *talloc_ctx_init(struct talloc *hdr, const void *parent)
 	memset(hdr, 0, sizeof(*hdr));
 
 	mem = hdr2mem(hdr);
-	talloc_steal(parent, mem);
+	mem_set_parent(hdr, mem2hdr(parent));
 	return mem;
 }
 
@@ -261,7 +260,7 @@ void talloc_free(void *mem)
 	if (ZERO_OR_NULL_PTR(mem))
 		return;
 
-	talloc_steal(NULL, mem);
+	mem_set_parent(hdr, NULL);
 
 	__tfree(hdr->child);
 
@@ -335,68 +334,6 @@ void mem_set_parent(struct talloc *hdr, struct talloc *parent_hdr)
 }
 EXPORT_SYMBOL(mem_set_parent);
 
-/**
- * talloc_steal() - Reparent talloc'ed memory chunk
- *
- * @parent: pointer to previously talloc'ed memory chunk from which this
- *          chunk depends, or NULL.
- * @mem: pointer to previously talloc'ed memory chunk.
- *
- * Change the parent of a talloc'ed memory chunk. This will affect the
- * dependencies of the entire subtree rooted at the given chunk.
- */
-void talloc_steal(const void *parent, void *mem)
-{
-	struct talloc *hdr = mem2hdr(mem);
-	struct talloc *parent_hdr = mem2hdr(parent);
-
-	mem_set_parent(hdr, parent_hdr);
-}
-EXPORT_SYMBOL(talloc_steal);
-
-/**
- * talloc_steal_children() - Remove a talloc'ed memory chunk from the dependency tree
- *
- * @parent: pointer to previously talloc'ed memory chunk from which this
- *          chunk's children will depend, or NULL.
- * @mem: pointer to previously talloc'ed memory chunk.
- *
- * Removing a talloc'ed memory chunk from the dependency tree takes care of its
- * children (they will depend on the specified parent).
- */
-void talloc_steal_children(const void *parent, void *mem)
-{
-	struct talloc *hdr = mem2hdr(mem);
-	struct talloc *parent_hdr = mem2hdr(parent);
-
-	if (ZERO_OR_NULL_PTR(hdr))
-		return;
-
-	talloc_steal(NULL, mem);
-
-	if (!hdr->child)
-		return;
-
-	if (parent_hdr) {
-		/* Insert mem children in front of the list of parent children. */
-		if (parent_hdr->child) {
-			struct talloc *last = hdr->child;
-
-			while (last->next)
-				last = last->next;
-
-			parent_hdr->child->prev = last;
-			last->next = parent_hdr->child;
-		}
-
-		parent_hdr->child = hdr->child;
-	}
-
-	hdr->child->parent = parent_hdr;
-	hdr->child = NULL;
-}
-EXPORT_SYMBOL(talloc_steal_children);
-
 size_t talloc_total_blocks(const void *mem)
 {
 	struct talloc *hdr = mem2hdr(mem);
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 04/19] talloc: free children in a loop
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (2 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 03/19] talloc: remove unused talloc_steal() and talloc_steal_children() Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 05/19] talloc: add support for destructors Ahmad Fatoum
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

__tfree() recurses along the sibling list too, so stack usage grows
with the number of siblings and a chunk is only freed after all of its
older siblings.

Let's free the children in a loop instead, each one completely before
the next, most recently added first, like Samba does. This will
determine the order destructors run in.

Assisted-by: Claude:opus-5.5
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 lib/talloc.c | 22 +++++++---------------
 1 file changed, 7 insertions(+), 15 deletions(-)

diff --git a/lib/talloc.c b/lib/talloc.c
index 2bfbac2e9c29..6a0fe81b9762 100644
--- a/lib/talloc.c
+++ b/lib/talloc.c
@@ -229,22 +229,18 @@ void *talloc_realloc_size(const void *parent, void *mem, size_t size)
 EXPORT_SYMBOL(talloc_realloc_size);
 
 /**
- * __tfree() - Deallocate all the descendants of given parent recursively.
+ * __talloc_free() - Deallocate a memory chunk and all its descendants.
  *
  * @hdr: pointer to previously talloc'ed memory chunk.
  */
-static void __tfree(struct talloc *hdr)
+static void __talloc_free(struct talloc *hdr)
 {
-	if (ZERO_OR_NULL_PTR(hdr))
-		return;
+	/* Unlink first so cycles terminate */
+	mem_set_parent(hdr, NULL);
 
-	/* Fail if the tree hierarchy has cycles. */
+	while (hdr->child)
+		__talloc_free(hdr->child);
 
-	ASSERT(hdr->prev);
-	hdr->prev = NULL;
-
-	__tfree(hdr->child);
-	__tfree(hdr->next);
 	free(hdr);
 }
 
@@ -260,11 +256,7 @@ void talloc_free(void *mem)
 	if (ZERO_OR_NULL_PTR(mem))
 		return;
 
-	mem_set_parent(hdr, NULL);
-
-	__tfree(hdr->child);
-
-	free(hdr);
+	__talloc_free(hdr);
 }
 EXPORT_SYMBOL(talloc_free);
 
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 05/19] talloc: add support for destructors
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (3 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 04/19] talloc: free children in a loop Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 06/19] test: self: talloc: add destructor tests Ahmad Fatoum
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

devm_ on top of talloc needs code to run when a chunk is freed. Samba
has talloc_set_destructor() for that, but its destructor gets the chunk
and can veto the free, while devm_add_action() needs any number of
callbacks with their own argument. Let's add talloc_add_destructor()
for that instead.

Destructors are leaf chunks marked through their child slot, so the
header stays at three pointers. They run in reverse order of adding,
interleaved with freeing the other children. A chunk is marked while
being freed, so destructors freeing it again do nothing.

Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 include/talloc.h |  2 ++
 lib/talloc.c     | 72 ++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 69 insertions(+), 5 deletions(-)

diff --git a/include/talloc.h b/include/talloc.h
index 1f2dd346a7c1..806921898b6c 100644
--- a/include/talloc.h
+++ b/include/talloc.h
@@ -30,6 +30,8 @@ void *talloc_zero_size(const void *parent, size_t size);
 void *talloc_realloc_size(const void *parent, void *mem, size_t size);
 void talloc_free(void *mem);
 
+int talloc_add_destructor(const void *parent, void (*fn)(void *), void *data);
+
 char *talloc_strdup(const void *parent, const char *str);
 const char *talloc_strdup_const(const void *parent, const char *str);
 void talloc_free_const(const void *ptr);
diff --git a/lib/talloc.c b/lib/talloc.c
index 6a0fe81b9762..97787b2d6503 100644
--- a/lib/talloc.c
+++ b/lib/talloc.c
@@ -12,6 +12,9 @@
  * |  child  | sibling | sibling | chunk
  * +---------+---------+---------+--------···
  *
+ * Destructors are leaf chunks below the chunk they belong to, marked by
+ * a child slot pointing at talloc_destructor_mark.
+ *
  * Thus, a talloc hierarchy tree would look like this:
  *
  *   NULL <-- chunk --> NULL
@@ -28,6 +31,7 @@
 
 #include <stdlib.h>
 #include <string.h>
+#include <errno.h>
 #include <linux/bug.h>
 #include <linux/export.h>
 #include <linux/container_of.h>
@@ -35,9 +39,20 @@
 #include "talloc.h"
 
 // TODO use a bit from TLSF for extra safety to differentiate talloc
-// TODO allow leaf nodes (that have no children to be destructors!!)
 #define  is_root(hdr) (!(hdr)->prev)
 #define is_first(hdr) ((hdr)->prev->next != (hdr))
+/* A chunk is unlinked while being freed, so next can mark it */
+#define is_freeing(hdr) ((hdr)->next == (hdr))
+
+struct talloc_destructor {
+	void (*fn)(void *);
+	void *data;
+};
+
+/* Leaves have no children, so this in the child slot marks a destructor */
+static struct talloc talloc_destructor_mark;
+
+#define is_destructor(hdr) ((hdr)->child == &talloc_destructor_mark)
 
 static inline void *hdr2mem(struct talloc *hdr)
 {
@@ -235,8 +250,19 @@ EXPORT_SYMBOL(talloc_realloc_size);
  */
 static void __talloc_free(struct talloc *hdr)
 {
-	/* Unlink first so cycles terminate */
+	if (is_destructor(hdr)) {
+		struct talloc_destructor *d = hdr2mem(hdr);
+
+		d->fn(d->data);
+
+		mem_set_parent(hdr, NULL);
+		free(hdr);
+		return;
+	}
+
+	/* Mark it, so destructors can't free it again */
 	mem_set_parent(hdr, NULL);
+	hdr->next = hdr;
 
 	while (hdr->child)
 		__talloc_free(hdr->child);
@@ -248,18 +274,52 @@ static void __talloc_free(struct talloc *hdr)
  * talloc_free() - Deallocate a talloc'ed memory chunk and all the chunks depending on it.
  *
  * @mem: pointer to previously talloc'ed memory chunk.
+ *
+ * Runs the destructors of the chunk and its descendants. Does nothing
+ * if the chunk is already being freed.
  */
 void talloc_free(void *mem)
 {
 	struct talloc *hdr = mem2hdr(mem);
 
-	if (ZERO_OR_NULL_PTR(mem))
+	if (ZERO_OR_NULL_PTR(mem) || is_freeing(hdr))
 		return;
 
 	__talloc_free(hdr);
 }
 EXPORT_SYMBOL(talloc_free);
 
+/**
+ * talloc_add_destructor() - Add function to run when memory chunk is freed
+ *
+ * @parent: pointer to previously talloc'ed memory chunk.
+ * @fn: function to call with @data as argument when @parent is freed.
+ * @data: argument to pass to @fn.
+ *
+ * Any number of these can be added. They run in reverse order of adding,
+ * interleaved with the freeing of @parent's children.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int talloc_add_destructor(const void *parent, void (*fn)(void *), void *data)
+{
+	struct talloc_destructor *d;
+
+	if (ZERO_OR_NULL_PTR(parent))
+		return -EINVAL;
+
+	d = talloc_size(parent, sizeof(*d));
+	if (!d)
+		return -ENOMEM;
+
+	mem2hdr(d)->child = &talloc_destructor_mark;
+	d->fn = fn;
+	d->data = data;
+
+	return 0;
+}
+EXPORT_SYMBOL(talloc_add_destructor);
+
 /**
  * talloc_free_const() - call talloc_free, unless read/only memory
  *
@@ -336,8 +396,10 @@ size_t talloc_total_blocks(const void *mem)
 
 	nblocks++;
 
-	for (hdr = hdr->child; hdr; hdr = hdr->next)
-		nblocks++;
+	for (hdr = hdr->child; hdr; hdr = hdr->next) {
+		if (!is_destructor(hdr))
+			nblocks++;
+	}
 
 	return nblocks;
 }
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 06/19] test: self: talloc: add destructor tests
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (4 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 05/19] talloc: add support for destructors Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 07/19] talloc: add talloc_run_destructors() Ahmad Fatoum
                   ` (12 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Cover ordering relative to children and siblings, realloc and frees
from within destructors.

Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 test/self/talloc.c | 124 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 124 insertions(+)

diff --git a/test/self/talloc.c b/test/self/talloc.c
index a7bbf90b59fb..85fc74336f43 100644
--- a/test/self/talloc.c
+++ b/test/self/talloc.c
@@ -142,11 +142,135 @@ static void talloc_corner_cases(void)
 	talloc_free(root);
 }
 
+static char destructor_log[16];
+static unsigned destructor_log_len;
+
+static void reset_destructor_log(void)
+{
+	memset(destructor_log, 0, sizeof(destructor_log));
+	destructor_log_len = 0;
+}
+
+static void log_destructor(void *data)
+{
+	if (destructor_log_len < sizeof(destructor_log) - 1)
+		destructor_log[destructor_log_len++] = *(char *)data;
+}
+
+static void self_free_destructor(void *data)
+{
+	log_destructor(data);
+	/* Must do nothing, we are being freed already */
+	talloc_free(data);
+}
+
+static void *other_to_free;
+
+static void other_free_destructor(void *data)
+{
+	log_destructor(data);
+	talloc_free(other_to_free);
+}
+
+/* Chunk holding @c, with a destructor that is passed the chunk */
+static void *talloc_char(const void *parent, char c, void (*fn)(void *))
+{
+	char *mem = talloc_size(parent, 1);
+
+	if (!selftest_check(mem))
+		return NULL;
+
+	*mem = c;
+	selftest_check(talloc_add_destructor(mem, fn, mem) == 0);
+
+	return mem;
+}
+
+static void talloc_destructor_tests(void)
+{
+	void *root, *a, *b, *c, *d, *x, *tmp;
+
+	selftest_check(talloc_add_destructor(NULL, log_destructor, "") == -EINVAL);
+
+	root = talloc_new(NULL);
+
+	/* Children are freed before their parent's destructors run */
+	reset_destructor_log();
+	a = talloc_char(root, 'a', log_destructor);
+	b = talloc_char(a, 'b', log_destructor);
+	c = talloc_char(a, 'c', log_destructor);
+	d = talloc_char(c, 'd', log_destructor);
+	if (!a || !b || !c || !d)
+		return;
+
+	talloc_free(a);
+	selftest_check(!strcmp(destructor_log, "dcba"));
+	selftest_check(talloc_total_blocks(root) == 1);
+
+	/* Destructors run in reverse order, interleaved with frees */
+	reset_destructor_log();
+	a = talloc_new(root);
+	selftest_check(talloc_add_destructor(a, log_destructor, "1") == 0);
+	b = talloc_char(a, 'b', log_destructor);
+	selftest_check(talloc_add_destructor(a, log_destructor, "2") == 0);
+	selftest_check(talloc_total_blocks(a) == 2);
+	talloc_free(a);
+	selftest_check(!strcmp(destructor_log, "2b1"));
+
+	/* Destructors survive realloc */
+	reset_destructor_log();
+	a = talloc_size(root, 1);
+	selftest_check(talloc_add_destructor(a, log_destructor, "a") == 0);
+	tmp = talloc_realloc_size(root, a, SZ_64K);
+	if (selftest_check(tmp))
+		a = tmp;
+	talloc_free(a);
+	selftest_check(!strcmp(destructor_log, "a"));
+
+	/* Freeing a chunk from its own destructor does nothing */
+	reset_destructor_log();
+	a = talloc_char(root, 'a', self_free_destructor);
+	talloc_free(a);
+	selftest_check(!strcmp(destructor_log, "a"));
+	selftest_check(talloc_total_blocks(root) == 1);
+
+	/* Same for an ancestor that is being freed */
+	reset_destructor_log();
+	x = talloc_char(root, 'x', log_destructor);
+	a = talloc_char(x, 'a', other_free_destructor);
+	other_to_free = x;
+	talloc_free(x);
+	selftest_check(!strcmp(destructor_log, "ax"));
+	selftest_check(talloc_total_blocks(root) == 1);
+
+	/* Ancestors of the freed chunk can be freed */
+	reset_destructor_log();
+	x = talloc_char(root, 'x', log_destructor);
+	a = talloc_char(x, 'a', log_destructor);
+	b = talloc_char(a, 'b', other_free_destructor);
+	other_to_free = x;
+	talloc_free(a);
+	selftest_check(!strcmp(destructor_log, "bxa"));
+	selftest_check(talloc_total_blocks(root) == 1);
+
+	/* So can siblings that are still to be freed */
+	reset_destructor_log();
+	a = talloc_char(root, 'a', log_destructor);
+	other_to_free = talloc_char(a, 'b', log_destructor);
+	c = talloc_char(a, 'c', other_free_destructor);
+	talloc_free(a);
+	selftest_check(!strcmp(destructor_log, "cba"));
+	selftest_check(talloc_total_blocks(root) == 1);
+
+	talloc_free(root);
+}
+
 static void test_talloc(void)
 {
 	talloc_basic_tests();
 	talloc_hierarchy_tests();
 	talloc_stress_tests();
 	talloc_corner_cases();
+	talloc_destructor_tests();
 }
 bselftest(core, test_talloc);
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 07/19] talloc: add talloc_run_destructors()
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (5 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 06/19] test: self: talloc: add destructor tests Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 08/19] mfd: pca9450: don't free device-managed memory in probe error path Ahmad Fatoum
                   ` (11 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

On shutdown, we want to run the devm_ actions, but not free managed
memory one allocation at a time, as the kernel reclaims it anyway.

Let's add talloc_run_destructors(), which runs and drops destructors in
talloc_free() order, but keeps all chunks. It only takes chunks without
parent, which is all devres needs, as a destructor could otherwise free
the chunk along with its parent while it's being walked.

Assisted-by: Claude:opus-5.5
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 include/talloc.h   |  1 +
 lib/talloc.c       | 52 +++++++++++++++++++++++++++++
 test/self/talloc.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 136 insertions(+)

diff --git a/include/talloc.h b/include/talloc.h
index 806921898b6c..a1a65267b2e3 100644
--- a/include/talloc.h
+++ b/include/talloc.h
@@ -31,6 +31,7 @@ void *talloc_realloc_size(const void *parent, void *mem, size_t size);
 void talloc_free(void *mem);
 
 int talloc_add_destructor(const void *parent, void (*fn)(void *), void *data);
+void talloc_run_destructors(void *mem);
 
 char *talloc_strdup(const void *parent, const char *str);
 const char *talloc_strdup_const(const void *parent, const char *str);
diff --git a/lib/talloc.c b/lib/talloc.c
index 97787b2d6503..f5c414e82ae7 100644
--- a/lib/talloc.c
+++ b/lib/talloc.c
@@ -320,6 +320,58 @@ int talloc_add_destructor(const void *parent, void (*fn)(void *), void *data)
 }
 EXPORT_SYMBOL(talloc_add_destructor);
 
+/*
+ * Like __talloc_free(), but children are parked in @kept instead of being
+ * freed. @hdr and @kept are marked, so destructors can't free them.
+ */
+static void talloc_run_child_destructors(struct talloc *hdr)
+{
+	struct talloc kept = {};
+
+	kept.next = &kept;
+	hdr->next = hdr;
+
+	while (hdr->child) {
+		struct talloc *child = hdr->child;
+
+		if (is_destructor(child)) {
+			__talloc_free(child);
+			continue;
+		}
+
+		mem_set_parent(child, NULL);
+		talloc_run_child_destructors(child);
+		mem_set_parent(child, &kept);
+	}
+
+	while (kept.child)
+		mem_set_parent(kept.child, hdr);
+}
+
+/**
+ * talloc_run_destructors() - Run destructors without freeing any memory
+ *
+ * @mem: pointer to previously talloc'ed memory chunk without parent.
+ *
+ * Runs the destructors of @mem and its descendants in the order
+ * talloc_free() would and drops them, but keeps all chunks.
+ */
+void talloc_run_destructors(void *mem)
+{
+	struct talloc *hdr = mem2hdr(mem);
+
+	if (ZERO_OR_NULL_PTR(mem) || is_freeing(hdr))
+		return;
+
+	/* A destructor could free the parent and @mem along with it */
+	if (WARN_ON(!is_root(hdr)))
+		return;
+
+	talloc_run_child_destructors(hdr);
+	hdr->next = NULL;
+}
+EXPORT_SYMBOL(talloc_run_destructors);
+
 /**
  * talloc_free_const() - call talloc_free, unless read/only memory
  *
diff --git a/test/self/talloc.c b/test/self/talloc.c
index 85fc74336f43..12aef4c879ee 100644
--- a/test/self/talloc.c
+++ b/test/self/talloc.c
@@ -172,6 +172,13 @@ static void other_free_destructor(void *data)
 	talloc_free(other_to_free);
 }
 
+static void other_parent_free_destructor(void *data)
+{
+	log_destructor(data);
+	/* Must do nothing, other_to_free is parked while we run */
+	talloc_free(talloc_parent(other_to_free));
+}
+
 /* Chunk holding @c, with a destructor that is passed the chunk */
 static void *talloc_char(const void *parent, char c, void (*fn)(void *))
 {
@@ -265,6 +272,81 @@ static void talloc_destructor_tests(void)
 	talloc_free(root);
 }
 
+static void talloc_run_destructors_tests(void)
+{
+	void *root, *a, *b, *c, *d, *x;
+
+	root = talloc_new(NULL);
+
+	/* Same order as talloc_free(), but everything stays */
+	reset_destructor_log();
+	a = talloc_char(root, 'a', log_destructor);
+	b = talloc_char(a, 'b', log_destructor);
+	selftest_check(talloc_add_destructor(a, log_destructor, "1") == 0);
+	c = talloc_char(a, 'c', log_destructor);
+	d = talloc_char(c, 'd', log_destructor);
+	if (!a || !b || !c || !d)
+		return;
+
+	talloc_run_destructors(root);
+	selftest_check(!strcmp(destructor_log, "dc1ba"));
+	selftest_check(talloc_parent(a) == root);
+	selftest_check(talloc_parent(b) == a && talloc_parent(c) == a);
+	selftest_check(talloc_parent(d) == c && *(char *)d == 'd');
+	selftest_check(talloc_total_blocks(a) == 3);
+
+	/* Destructors that ran are gone, new ones run */
+	selftest_check(talloc_add_destructor(c, log_destructor, "2") == 0);
+	talloc_run_destructors(root);
+	talloc_free(a);
+	selftest_check(!strcmp(destructor_log, "dc1ba2"));
+
+	/* Chunks being walked can't be freed from destructors */
+	reset_destructor_log();
+	a = talloc_char(root, 'a', log_destructor);
+	b = talloc_char(a, 'b', self_free_destructor);
+	c = talloc_char(b, 'c', other_free_destructor);
+	other_to_free = a;
+	talloc_run_destructors(root);
+	selftest_check(!strcmp(destructor_log, "cba"));
+	selftest_check(talloc_parent(c) == b && talloc_parent(b) == a);
+	selftest_check(talloc_parent(a) == root);
+	talloc_free(a);
+
+	/* Nor the parent of a chunk that was walked already */
+	reset_destructor_log();
+	a = talloc_char(root, 'a', log_destructor);
+	c = talloc_char(a, 'c', other_parent_free_destructor);
+	other_to_free = talloc_char(a, 'b', log_destructor);
+	talloc_run_destructors(root);
+	selftest_check(!strcmp(destructor_log, "bca"));
+	selftest_check(talloc_parent(other_to_free) == a && talloc_parent(c) == a);
+	talloc_free(a);
+
+	/* Other chunks can, even siblings that are still to be walked */
+	reset_destructor_log();
+	a = talloc_char(root, 'a', log_destructor);
+	other_to_free = talloc_char(a, 'b', log_destructor);
+	c = talloc_char(a, 'c', other_free_destructor);
+	talloc_run_destructors(root);
+	selftest_check(!strcmp(destructor_log, "cba"));
+	selftest_check(talloc_total_blocks(a) == 2 && *(char *)c == 'c');
+	talloc_free(a);
+
+	selftest_check(talloc_total_blocks(root) == 1);
+	talloc_free(root);
+
+	/* @mem can't be freed from its destructors and is usable after */
+	reset_destructor_log();
+	x = talloc_char(NULL, 'x', self_free_destructor);
+	talloc_run_destructors(x);
+	selftest_check(!strcmp(destructor_log, "x"));
+	selftest_check(*(char *)x == 'x' && !talloc_parent(x));
+	selftest_check(talloc_add_destructor(x, log_destructor, "X") == 0);
+	talloc_free(x);
+	selftest_check(!strcmp(destructor_log, "xX"));
+}
+
 static void test_talloc(void)
 {
 	talloc_basic_tests();
@@ -272,5 +354,6 @@ static void test_talloc(void)
 	talloc_stress_tests();
 	talloc_corner_cases();
 	talloc_destructor_tests();
+	talloc_run_destructors_tests();
 }
 bselftest(core, test_talloc);
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 08/19] mfd: pca9450: don't free device-managed memory in probe error path
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (6 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 07/19] talloc: add talloc_run_destructors() Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 09/19] video: backlight-pwm: allocate brightness levels with devm_kcalloc Ahmad Fatoum
                   ` (10 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

The driver data is allocated with devm_kzalloc(), but free()d on probe
failure, which only works as long as devm_ is a dummy. Drop the free,
managed memory is released when probe fails.

Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/mfd/pca9450.c | 20 +++++---------------
 1 file changed, 5 insertions(+), 15 deletions(-)

diff --git a/drivers/mfd/pca9450.c b/drivers/mfd/pca9450.c
index 523071bf12ff..ff608dd1af8b 100644
--- a/drivers/mfd/pca9450.c
+++ b/drivers/mfd/pca9450.c
@@ -100,21 +100,19 @@ static int __init pca9450_probe(struct device *dev)
 	pca9450->type = (enum pca9450_chip_type)(uintptr_t)device_get_match_data(dev);
 
 	regmap = regmap_init_i2c(to_i2c_client(dev), &pca9450_regmap_i2c_config);
-	if (IS_ERR(regmap)) {
-		ret = PTR_ERR(regmap);
-		goto free;
-	}
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
 
 	pca9450->regmap = regmap;
 
 	ret = regmap_register_cdev(regmap, NULL);
 	if (ret)
-		goto free;
+		return ret;
 
 	ret = regmap_read(regmap, PCA9450_REG_DEV_ID, &reg);
 	if (ret) {
 		dev_err(dev, "Unable to read PMIC Chip ID\n");
-		goto free;
+		return ret;
 	}
 
 	/* Chip ID defined in bits [7:4] */
@@ -138,15 +136,7 @@ static int __init pca9450_probe(struct device *dev)
 
 	pca9450_get_reset_source(dev, regmap);
 
-	ret = mfd_add_devices(dev, pca9450_cells, ARRAY_SIZE(pca9450_cells));
-	if (ret)
-		goto free;
-
-	return 0;
-
-free:
-	free(pca9450);
-	return ret;
+	return mfd_add_devices(dev, pca9450_cells, ARRAY_SIZE(pca9450_cells));
 }
 
 static __maybe_unused struct of_device_id pca9450_dt_ids[] = {
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 09/19] video: backlight-pwm: allocate brightness levels with devm_kcalloc
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (7 preceding siblings ...)
  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 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 10/19] driver: implement devm_ allocation helpers on top of talloc Ahmad Fatoum
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

The levels table is allocated with xzalloc(), but freed with
devm_kfree() when it's replaced by the interpolated table. That only
works as long as devm_kfree() is a plain free(), so allocate it with
devm_kcalloc() like Linux does.

Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/video/backlight-pwm.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/video/backlight-pwm.c b/drivers/video/backlight-pwm.c
index 581fa0237c0f..4ec0f6fe5b23 100644
--- a/drivers/video/backlight-pwm.c
+++ b/drivers/video/backlight-pwm.c
@@ -127,9 +127,9 @@ static int pwm_backlight_parse_dt(struct device *dev,
 
 	/* read brightness levels from DT property */
 	if (length > 0) {
-		size_t size = sizeof(*pwm_backlight->levels) * length;
-
-		pwm_backlight->levels = xzalloc(size);
+		pwm_backlight->levels = devm_kcalloc(dev, length,
+						     sizeof(*pwm_backlight->levels),
+						     GFP_KERNEL);
 
 		ret = of_property_read_u32_array(node, "brightness-levels",
 						 pwm_backlight->levels,
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 10/19] driver: implement devm_ allocation helpers on top of talloc
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (8 preceding siblings ...)
  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
  2026-10-28  9:14 ` [PATCH 11/19] devres: Provide kmalloc_obj*-style API for devm_kmalloc Ahmad Fatoum
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

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




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 11/19] devres: Provide kmalloc_obj*-style API for devm_kmalloc
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (9 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 10/19] driver: implement devm_ allocation helpers on top of talloc Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 12/19] memory: atmel-ebi: use generic devm_kzalloc() Ahmad Fatoum
                   ` (7 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

This is a port of Linux commit 29a86e8818a8b7043643b85b0603eb9f4c225b55:

| Author:     Kees Cook <kees@kernel.org>
| AuthorDate: Mon Sep 14 15:32:17 2026 -0700
|
| devres: Provide kmalloc_obj*-style API for devm_kmalloc
|
| Mirroring the type-based allocation now in use for the kmalloc family of
| APIs, provide the same for the devm_kmalloc family of APIs. This will
| allow for future plumbing of type information (e.g. alignment) into the
| underlying allocations. This immediately gains type check on return
| values (instead of the prior "void *" return type).
|
| Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
| Link: https://patch.msgid.link/20260914223219.901347-2-kees@kernel.org
| Signed-off-by: Kees Cook <kees@kernel.org>

The _flex() variants are left out like for kmalloc_obj(), as barebox
lacks __counted_by(). The kerneldoc drops the NULL on failure, as
managed allocations can't fail.

Assisted-by: Claude:opus-5.5
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 include/linux/device.h | 55 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/include/linux/device.h b/include/linux/device.h
index 0f4f2a40a11d..595abf412180 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -27,6 +27,61 @@ char *devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt,
 char *devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...)
 	__printf(3, 4);
 
+/**
+ * __devm_alloc_objs - Device-managed allocation of objects of a given type
+ * @DEVM_ALLOC: which device-managed size-based allocator to use.
+ * @DEV: device to scope the allocation lifetime to.
+ * @GFP: GFP flags for the allocation.
+ * @TYPE: type to allocate space for.
+ * @COUNT: how many @TYPE objects to allocate.
+ *
+ * Device-managed counterpart of __alloc_objs().
+ * @DEVM_ALLOC takes (@DEV, size, gfp).
+ *
+ * Returns: Newly allocated pointer to (first) @TYPE of @COUNT-many allocated
+ * @TYPE objects.
+ */
+#define __devm_alloc_objs(DEVM_ALLOC, DEV, GFP, TYPE, COUNT)		\
+({									\
+	const size_t __obj_size = size_mul(sizeof(TYPE), COUNT);	\
+	(TYPE *)DEVM_ALLOC(DEV, __obj_size, GFP);			\
+})
+
+/**
+ * devm_kmalloc_obj - Device-managed allocation of a single typed object
+ * @DEV: Device to scope the allocation lifetime to.
+ * @VAR_OR_TYPE: Variable or type to allocate.
+ * @GFP: Optional GFP flags (defaults to %GFP_KERNEL).
+ *
+ * Device-managed counterpart of kmalloc_obj(): the allocation is sized from
+ * @VAR_OR_TYPE and returned as a pointer to that type, not void *.
+ *
+ * Returns: newly allocated pointer to a @VAR_OR_TYPE.
+ */
+#define devm_kmalloc_obj(DEV, VAR_OR_TYPE, ...)				\
+	__devm_alloc_objs(devm_kmalloc, DEV, default_gfp(__VA_ARGS__),	\
+			  typeof(VAR_OR_TYPE), 1)
+
+/**
+ * devm_kmalloc_objs - Device-managed allocation of an array of a typed object
+ * @DEV: Device to scope the allocation lifetime to.
+ * @VAR_OR_TYPE: Variable or type to allocate an array of.
+ * @COUNT: How many elements in the array.
+ * @GFP: Optional GFP flags (defaults to %GFP_KERNEL).
+ *
+ * Returns: newly allocated pointer to an array of @VAR_OR_TYPE.
+ */
+#define devm_kmalloc_objs(DEV, VAR_OR_TYPE, COUNT, ...) \
+	__devm_alloc_objs(devm_kmalloc, DEV, default_gfp(__VA_ARGS__), \
+			  typeof(VAR_OR_TYPE), COUNT)
+
+/* All devm_kzalloc aliases for devm_kmalloc_(obj|objs). */
+#define devm_kzalloc_obj(DEV, P, ...) \
+	__devm_alloc_objs(devm_kzalloc, DEV, default_gfp(__VA_ARGS__), typeof(P), 1)
+#define devm_kzalloc_objs(DEV, P, COUNT, ...) \
+	__devm_alloc_objs(devm_kzalloc, DEV, default_gfp(__VA_ARGS__), \
+			  typeof(P), COUNT)
+
 static inline void *devm_kmalloc_array(struct device *dev, size_t n,
 				       size_t size, gfp_t gfp)
 {
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 12/19] memory: atmel-ebi: use generic devm_kzalloc()
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (10 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 11/19] devres: Provide kmalloc_obj*-style API for devm_kmalloc Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 13/19] driver: add devm_add_action() Ahmad Fatoum
                   ` (6 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Now that <linux/device.h> provides a real devm_kzalloc(), drop the local
stand-in.

Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/memory/atmel-ebi.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/drivers/memory/atmel-ebi.c b/drivers/memory/atmel-ebi.c
index 293c3d7f9d56..6a1cbfcd35a5 100644
--- a/drivers/memory/atmel-ebi.c
+++ b/drivers/memory/atmel-ebi.c
@@ -15,6 +15,7 @@
 #include <driver.h>
 #include <of_device.h>
 #include <linux/regmap.h>
+#include <linux/device.h>
 #include <soc/at91/atmel-sfr.h>
 #include <linux/time.h>
 #include <linux/printk.h>
@@ -79,11 +80,6 @@ struct atmel_smc_timing_xlate {
 #define ATMEL_SMC_CYCLE_XLATE(nm, pos)	\
 	{ .name = nm, .converter = atmel_smc_cs_conf_set_cycle, .shift = pos}
 
-static inline void *devm_kzalloc(struct device *dev, size_t size, gfp_t flags)
-{
-	return kzalloc(size, flags);
-}
-
 static void at91sam9_ebi_get_config(struct atmel_ebi_dev *ebid,
 				    struct atmel_ebi_dev_config *conf)
 {
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 13/19] driver: add devm_add_action()
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (11 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 12/19] memory: atmel-ebi: use generic devm_kzalloc() Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 14/19] test: self: add devm_ selftest Ahmad Fatoum
                   ` (5 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Add devm_add_action() and devm_add_action_or_reset() on top of
talloc_add_destructor() for drivers that need to undo things on remove.

As barebox calls the remove callbacks on shutdown, let's run the actions
there as well, but keep the managed memory, which may still be
referenced afterwards.

Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/base/devres.c  | 23 +++++++++++++++++++++++
 drivers/base/driver.c  |  1 +
 include/driver.h       |  1 +
 include/linux/device.h | 38 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 63 insertions(+)

diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index 26abc47cd997..bf118ae0820d 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -36,6 +36,18 @@ void devres_release_all(struct device *dev)
 	dev->devres = NULL;
 }
 
+/**
+ * devres_run_actions() - run the managed actions of a device
+ * @dev: device that is shut down
+ *
+ * Like devres_release_all(), but keeps the managed memory, which may
+ * still be referenced after shutdown.
+ */
+void devres_run_actions(struct device *dev)
+{
+	talloc_run_destructors(dev->devres);
+}
+
 /*
  * 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().
@@ -145,3 +157,14 @@ char *devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...)
 	return p;
 }
 EXPORT_SYMBOL(devm_kasprintf);
+
+int devm_add_action(struct device *dev, void (*action)(void *), void *data)
+{
+	void *ctx = devres_ctx(dev);
+
+	if (!ctx)
+		return -ENOMEM;
+
+	return talloc_add_destructor(ctx, action, data);
+}
+EXPORT_SYMBOL(devm_add_action);
diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index e214408e12a3..f87ecb200feb 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -720,6 +720,7 @@ static void devices_shutdown(void)
 			pr_report_probe("%*sremove-> %s\n", 1 * 4, "", dev_name(dev));
 			remove(dev);
 		}
+		devres_run_actions(dev);
 		dev->driver = NULL;
 	}
 }
diff --git a/include/driver.h b/include/driver.h
index 372b1297c6c4..c394c455b971 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -112,6 +112,7 @@ void free_device_res(struct device *dev);
 void free_device(struct device *dev);
 
 void devres_release_all(struct device *dev);
+void devres_run_actions(struct device *dev);
 
 static inline void device_rescan(struct device *dev)
 {
diff --git a/include/linux/device.h b/include/linux/device.h
index 595abf412180..9b14bc74fef6 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -101,6 +101,44 @@ static inline unsigned long *devm_bitmap_zalloc(struct device *dev,
 			    gfp);
 }
 
+/**
+ * devm_add_action() - add a custom action to the managed resources
+ * @dev: device that owns the action
+ * @action: function to call when the device is unbound, unregistered
+ *          or shut down
+ * @data: argument to pass to @action
+ *
+ * Actions run in reverse order of registration, interleaved with the
+ * freeing of managed memory.
+ *
+ * Return: 0 on success, -ENOMEM if the action could not be registered.
+ */
+int devm_add_action(struct device *dev, void (*action)(void *), void *data);
+
+/**
+ * devm_add_action_or_reset() - add a custom action, running it on failure
+ * @dev: device that owns the action
+ * @action: function to call when the device is unbound, unregistered
+ *          or shut down
+ * @data: argument to pass to @action
+ *
+ * Like devm_add_action(), but @action is called right away if it could
+ * not be registered.
+ *
+ * Return: 0 on success, -ENOMEM if the action could not be registered.
+ */
+static inline int devm_add_action_or_reset(struct device *dev,
+					   void (*action)(void *), void *data)
+{
+	int ret;
+
+	ret = devm_add_action(dev, action, data);
+	if (ret)
+		action(data);
+
+	return ret;
+}
+
 #define device_register register_device
 #define device_unregister unregister_device
 
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 14/19] test: self: add devm_ selftest
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (12 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 13/19] driver: add devm_add_action() Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 15/19] driver: add devm_request_mem_resource() Ahmad Fatoum
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Check alignment, zeroing, early devm_kfree() and action order as well
as the release points: failed probe, remove, unbind of a driver without
.remove and unregistering a device with children in managed memory.
Also check that shutdown runs the actions, but keeps the memory.

Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 test/self/Kconfig  |   6 +
 test/self/Makefile |   1 +
 test/self/devres.c | 289 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 296 insertions(+)
 create mode 100644 test/self/devres.c

diff --git a/test/self/Kconfig b/test/self/Kconfig
index bdee21f17d7e..e655740c41cb 100644
--- a/test/self/Kconfig
+++ b/test/self/Kconfig
@@ -53,6 +53,7 @@ config SELFTEST_ENABLE_ALL
 	select SELFTEST_DM
 	select SELFTEST_LVM if BLOCK
 	select SELFTEST_TALLOC
+	select SELFTEST_DEVRES
 	select SELFTEST_BLSPEC if BLSPEC && DEFAULT_ENVIRONMENT
 	help
 	  Selects all self-tests compatible with current configuration
@@ -87,6 +88,11 @@ config SELFTEST_TALLOC
 	help
 	  Tests barebox talloc allocator
 
+config SELFTEST_DEVRES
+	bool "devm_ selftest"
+	help
+	  Tests device-managed resources
+
 config SELFTEST_PRINTF
 	bool "printf selftest"
 	help
diff --git a/test/self/Makefile b/test/self/Makefile
index 0fba7ac56654..337a211d761c 100644
--- a/test/self/Makefile
+++ b/test/self/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_SELFTEST_RANGE) += range.o
 obj-$(CONFIG_SELFTEST_MALLOC) += malloc.o
 obj-$(CONFIG_SELFTEST_CLEANUP) += cleanup.o
 obj-$(CONFIG_SELFTEST_TALLOC) += talloc.o
+obj-$(CONFIG_SELFTEST_DEVRES) += devres.o
 obj-$(CONFIG_SELFTEST_PRINTF) += printf.o
 CFLAGS_printf.o += -Wno-format-security -Wno-format
 obj-$(CONFIG_SELFTEST_PROGRESS_NOTIFIER) += progress-notifier.o
diff --git a/test/self/devres.c b/test/self/devres.c
new file mode 100644
index 000000000000..4fdc71064b4a
--- /dev/null
+++ b/test/self/devres.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "devres: " fmt
+
+#include <common.h>
+#include <bselftest.h>
+#include <dma.h>
+#include <linux/bitmap.h>
+#include <driver.h>
+#include <linux/device.h>
+
+BSELFTEST_GLOBALS();
+
+static bool __selftest_check(bool cond, const char *func, int line)
+{
+	total_tests++;
+	if (cond)
+		return true;
+
+	pr_err("assertion failure at %s:%d\n", func, line);
+
+	failed_tests++;
+	return false;
+}
+#define selftest_check(cond) __selftest_check((cond), __func__, __LINE__)
+
+static char action_log[8];
+static unsigned action_log_len;
+
+static void log_action(void *data)
+{
+	if (action_log_len < sizeof(action_log) - 1)
+		action_log[action_log_len++] = *(char *)data;
+}
+
+static void reset_action_log(void)
+{
+	memset(action_log, 0, sizeof(action_log));
+	action_log_len = 0;
+}
+
+static void test_devres_alloc(void)
+{
+	struct device *dev = device_alloc("devres-test", DEVICE_ID_DYNAMIC);
+	unsigned long *bitmap;
+	const char *cstr;
+	char *str, *buf;
+	int *arr;
+
+	if (selftest_check(register_device(dev) == 0) == false)
+		return;
+
+	buf = devm_kmalloc(dev, 1, GFP_KERNEL);
+	selftest_check(buf && PTR_IS_ALIGNED(buf, DMA_ALIGNMENT));
+	buf[0] = 0xff;
+
+	arr = devm_kzalloc_objs(dev, *arr, 100);
+	selftest_check(arr && PTR_IS_ALIGNED(arr, DMA_ALIGNMENT));
+	selftest_check(memchr_inv(arr, 0, 100 * sizeof(*arr)) == NULL);
+	arr[0] = 42;
+	arr[99] = 42;
+
+	bitmap = devm_bitmap_zalloc(dev, 100, GFP_KERNEL);
+	selftest_check(bitmap && bitmap_empty(bitmap, 100));
+
+	str = devm_kstrdup(dev, "hello", GFP_KERNEL);
+	selftest_check(str && !strcmp(str, "hello"));
+	selftest_check(devm_kstrdup(dev, NULL, GFP_KERNEL) == NULL);
+
+	cstr = devm_kstrdup_const(dev, "const", GFP_KERNEL);
+	selftest_check(cstr && !strcmp(cstr, "const"));
+
+	str = devm_kasprintf(dev, GFP_KERNEL, "%s%d", "test", 123);
+	selftest_check(str && !strcmp(str, "test123"));
+
+	selftest_check(devm_kmalloc(dev, 0, GFP_KERNEL) == ZERO_SIZE_PTR);
+
+	/* Early frees, including the ones that must be no-ops */
+	devm_kfree(dev, arr);
+	devm_kfree(dev, cstr);
+	devm_kfree(dev, NULL);
+	devm_kfree(dev, ZERO_SIZE_PTR);
+
+	selftest_check(buf[0] == 0xff && !strcmp(str, "test123"));
+	selftest_check(dev->devres != NULL);
+
+	unregister_device(dev);
+	selftest_check(dev->devres == NULL);
+	free_device(dev);
+}
+
+static void test_devres_action(void)
+{
+	struct device *dev = device_alloc("devres-test", DEVICE_ID_DYNAMIC);
+	static char a = 'a', b = 'b', c = 'c';
+
+	if (selftest_check(register_device(dev) == 0) == false)
+		return;
+
+	reset_action_log();
+
+	selftest_check(devm_add_action(dev, log_action, &a) == 0);
+	selftest_check(devm_add_action_or_reset(dev, log_action, &b) == 0);
+	selftest_check(devm_add_action(dev, log_action, &c) == 0);
+	selftest_check(action_log_len == 0);
+
+	/* Actions run in reverse order of registration */
+	unregister_device(dev);
+	selftest_check(!strcmp(action_log, "cba"));
+	free_device(dev);
+}
+
+static void test_devres_shutdown(void)
+{
+	struct device *dev = device_alloc("devres-test", DEVICE_ID_DYNAMIC);
+	static char a = 'a', b = 'b';
+	char *buf;
+
+	if (selftest_check(register_device(dev) == 0) == false)
+		return;
+
+	reset_action_log();
+
+	buf = devm_kstrdup(dev, "kept", GFP_KERNEL);
+	selftest_check(devm_add_action(dev, log_action, &a) == 0);
+	selftest_check(devm_add_action(dev, log_action, &b) == 0);
+
+	/* Shutdown runs the actions, but keeps the memory */
+	devres_run_actions(dev);
+	selftest_check(!strcmp(action_log, "ba"));
+	selftest_check(!strcmp(buf, "kept"));
+
+	/* Releasing doesn't run them again */
+	unregister_device(dev);
+	selftest_check(!strcmp(action_log, "ba"));
+	free_device(dev);
+}
+
+static int probe_result;
+static char *probe_buf;
+
+static int devres_test_probe(struct device *dev)
+{
+	static char p = 'p';
+
+	probe_buf = devm_kzalloc(dev, 16, GFP_KERNEL);
+	strcpy(probe_buf, "probed");
+	devm_add_action(dev, log_action, &p);
+
+	return probe_result;
+}
+
+static void devres_test_remove(struct device *dev)
+{
+	static char r = 'r';
+
+	/* Managed resources must still be intact during remove */
+	log_action(&r);
+	selftest_check(!strcmp(probe_buf, "probed"));
+}
+
+static struct driver devres_test_driver = {
+	.name = "devres-test",
+	.probe = devres_test_probe,
+	.remove = devres_test_remove,
+};
+
+static void test_devres_probe(void)
+{
+	struct device *dev;
+
+	if (selftest_check(platform_driver_register(&devres_test_driver) == 0) == false)
+		return;
+
+	/* Failed probe releases everything */
+	reset_action_log();
+	probe_result = -ENODEV;
+	dev = device_alloc("devres-test", DEVICE_ID_DYNAMIC);
+	platform_device_register(dev);
+	selftest_check(dev->driver == NULL);
+	selftest_check(!strcmp(action_log, "p"));
+	selftest_check(dev->devres == NULL);
+	unregister_device(dev);
+	free_device(dev);
+
+	/* Successful probe keeps everything until remove */
+	reset_action_log();
+	probe_result = 0;
+	dev = device_alloc("devres-test", DEVICE_ID_DYNAMIC);
+	platform_device_register(dev);
+	selftest_check(dev->driver == &devres_test_driver);
+	selftest_check(action_log_len == 0);
+	selftest_check(dev->devres != NULL);
+	unregister_device(dev);
+	selftest_check(!strcmp(action_log, "rp"));
+	selftest_check(dev->devres == NULL);
+	free_device(dev);
+
+	unregister_driver(&devres_test_driver);
+}
+
+static int devres_noremove_probe(struct device *dev)
+{
+	static char n = 'n';
+
+	devm_add_action(dev, log_action, &n);
+
+	return 0;
+}
+
+static struct driver devres_noremove_driver = {
+	.name = "devres-noremove",
+	.probe = devres_noremove_probe,
+};
+
+static void test_devres_unbind(void)
+{
+	struct device *dev;
+
+	if (selftest_check(platform_driver_register(&devres_noremove_driver) == 0) == false)
+		return;
+
+	reset_action_log();
+	dev = device_alloc("devres-noremove", DEVICE_ID_DYNAMIC);
+	platform_device_register(dev);
+	selftest_check(dev->driver == &devres_noremove_driver);
+	selftest_check(dev->devres != NULL);
+
+	/* A driver without remove callback releases its resources as well */
+	unregister_driver(&devres_noremove_driver);
+	selftest_check(dev->driver == NULL);
+	selftest_check(dev->devres == NULL);
+	selftest_check(!strcmp(action_log, "n"));
+
+	unregister_device(dev);
+	free_device(dev);
+}
+
+struct devres_parent_priv {
+	struct device child;
+};
+
+static int devres_parent_probe(struct device *dev)
+{
+	struct devres_parent_priv *priv;
+
+	priv = devm_kzalloc_obj(dev, *priv);
+	dev_set_name(&priv->child, "devres-child");
+	priv->child.id = DEVICE_ID_DYNAMIC;
+	priv->child.parent = dev;
+
+	return register_device(&priv->child);
+}
+
+static struct driver devres_parent_driver = {
+	.name = "devres-parent",
+	.probe = devres_parent_probe,
+};
+
+static void test_devres_child(void)
+{
+	struct device *dev;
+
+	if (selftest_check(platform_driver_register(&devres_parent_driver) == 0) == false)
+		return;
+
+	dev = device_alloc("devres-parent", DEVICE_ID_DYNAMIC);
+	platform_device_register(dev);
+	selftest_check(dev->driver == &devres_parent_driver);
+	selftest_check(!list_empty(&dev->children));
+
+	/* The child lives in managed memory and must be unregistered first */
+	unregister_device(dev);
+	selftest_check(dev->devres == NULL);
+	free_device(dev);
+
+	unregister_driver(&devres_parent_driver);
+}
+
+static void test_devres(void)
+{
+	test_devres_alloc();
+	test_devres_action();
+	test_devres_shutdown();
+	test_devres_probe();
+	test_devres_unbind();
+	test_devres_child();
+}
+bselftest(core, test_devres);
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 15/19] driver: add devm_request_mem_resource()
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (13 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 14/19] test: self: add devm_ selftest Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 16/19] driver: make dev_platform_ioremap_resource() device-managed Ahmad Fatoum
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Drivers using dev_request_mem_resource() have to release the region in
every later error path and on remove. Add a managed variant that does
this for them.

Assisted-by: Claude:opus-5-1m
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/base/devres.c | 36 ++++++++++++++++++++++++++++++++++++
 include/driver.h      |  3 +++
 test/self/devres.c    | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 77 insertions(+)

diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index bf118ae0820d..efc810a8898f 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -5,6 +5,7 @@
  * Managed resources are children of a per-device talloc context.
  */
 
+#include <barebox.h>
 #include <driver.h>
 #include <dma.h>
 #include <talloc.h>
@@ -168,3 +169,38 @@ int devm_add_action(struct device *dev, void (*action)(void *), void *data)
 	return talloc_add_destructor(ctx, action, data);
 }
 EXPORT_SYMBOL(devm_add_action);
+
+static void devm_region_release(void *res)
+{
+	/* No need to release regions when shutting down */
+	if (barebox_system_state == BAREBOX_EXITING)
+		return;
+
+	release_region(res);
+}
+
+/**
+ * devm_request_mem_resource() - exclusively request resource 'num' for a device
+ * @dev: device to request the memory resource for
+ * @num: index of the memory resource
+ *
+ * Like dev_request_mem_resource(), but device-managed.
+ *
+ * Return: the requested resource or an ERR_PTR() on failure.
+ */
+struct resource *devm_request_mem_resource(struct device *dev, int num)
+{
+	struct resource *res;
+	int ret;
+
+	res = dev_request_mem_resource(dev, num);
+	if (IS_ERR(res))
+		return res;
+
+	ret = devm_add_action_or_reset(dev, devm_region_release, res);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return res;
+}
+EXPORT_SYMBOL(devm_request_mem_resource);
diff --git a/include/driver.h b/include/driver.h
index c394c455b971..4005804bb1a9 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -193,6 +193,9 @@ struct resource *dev_request_mem_resource(struct device *dev, int num);
 struct resource *dev_request_mem_resource_by_name(struct device *dev,
 						  const char *name);
 
+/* Like dev_request_mem_resource(), but released when the device goes away */
+struct resource *devm_request_mem_resource(struct device *dev, int num);
+
 void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev,
 						    int num,
 						    struct resource **out_res);
diff --git a/test/self/devres.c b/test/self/devres.c
index 4fdc71064b4a..6992eb20ace3 100644
--- a/test/self/devres.c
+++ b/test/self/devres.c
@@ -8,6 +8,7 @@
 #include <linux/bitmap.h>
 #include <driver.h>
 #include <linux/device.h>
+#include <linux/sizes.h>
 
 BSELFTEST_GLOBALS();
 
@@ -136,6 +137,42 @@ static void test_devres_shutdown(void)
 	free_device(dev);
 }
 
+static void test_devres_region(void)
+{
+	/* The last page of the address space, skipped if it's in use */
+	resource_size_t start = iomem_resource.end - SZ_4K + 1;
+	enum system_states state = barebox_system_state;
+	struct device *dev = device_alloc("devres-test", DEVICE_ID_DYNAMIC);
+	struct resource *res;
+
+	device_add_resource(dev, NULL, start, SZ_4K, IORESOURCE_MEM);
+	if (selftest_check(register_device(dev) == 0) == false)
+		return;
+
+	res = devm_request_mem_resource(dev, 0);
+	if (IS_ERR(res)) {
+		skipped_tests++;
+		goto out;
+	}
+
+	/* Shutdown keeps the region, but drops the action all the same */
+	barebox_system_state = BAREBOX_EXITING;
+	devres_run_actions(dev);
+	barebox_system_state = state;
+	if (selftest_check(IS_ERR(devm_request_mem_resource(dev, 0))))
+		release_region(res);
+
+	/* Releasing frees it */
+	selftest_check(!IS_ERR(devm_request_mem_resource(dev, 0)));
+	devres_release_all(dev);
+	res = request_iomem_region("devres-test", start, iomem_resource.end);
+	if (selftest_check(!IS_ERR(res)))
+		release_region(res);
+out:
+	unregister_device(dev);
+	free_device(dev);
+}
+
 static int probe_result;
 static char *probe_buf;
 
@@ -282,6 +319,7 @@ static void test_devres(void)
 	test_devres_alloc();
 	test_devres_action();
 	test_devres_shutdown();
+	test_devres_region();
 	test_devres_probe();
 	test_devres_unbind();
 	test_devres_child();
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 16/19] driver: make dev_platform_ioremap_resource() device-managed
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (14 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 15/19] driver: add devm_request_mem_resource() Ahmad Fatoum
@ 2026-10-28  9:14 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 17/19] treewide: rename dev_platform_ioremap_resource() to devm_platform_ioremap_resource() Ahmad Fatoum
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Unlike devm_platform_ioremap_resource() in Linux, the barebox stand-in
never releases its region. Most users call clk_get() or regulator_get()
after it, so a deferred probe leaves the region requested and the retry
fails with -EBUSY.

Implement it on top of devm_request_mem_resource() and move it out of
line to devres.c.

Assisted-by: Claude:opus-5-1m
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/base/devres.c  | 32 ++++++++++++++++++++++++++++++++
 include/linux/device.h | 11 +----------
 2 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index efc810a8898f..f68b8e7b6590 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -7,6 +7,7 @@
 
 #include <barebox.h>
 #include <driver.h>
+#include <io.h>
 #include <dma.h>
 #include <talloc.h>
 #include <xfuncs.h>
@@ -204,3 +205,34 @@ struct resource *devm_request_mem_resource(struct device *dev, int num)
 	return res;
 }
 EXPORT_SYMBOL(devm_request_mem_resource);
+
+/**
+ * dev_platform_ioremap_resource() - request and map resource 'num' for a device
+ * @dev: device to request the memory resource for
+ * @num: index of the memory resource
+ *
+ * barebox maps everything outside the RAM banks suitably for MMIO, so this
+ * only requests the device-managed region.
+ *
+ * Return: a pointer to the registers or an ERR_PTR() on failure.
+ */
+void __iomem *dev_platform_ioremap_resource(struct device *dev, int num)
+{
+	struct resource *res;
+
+	res = devm_request_mem_resource(dev, num);
+	if (IS_ERR(res))
+		return IOMEM_ERR_PTR(PTR_ERR(res));
+
+	/* As everything is mapped 1:1 by default, drivers on unlucky
+	 * platforms can end up successfully requesting memory, but
+	 * getting a base address that looks like an error pointer.
+	 * Let's warn loudly about this case, as these drivers
+	 * should be using devm_request_mem_resource instead.
+	 */
+	if (WARN_ON(IS_ERR_VALUE(res->start)))
+		return IOMEM_ERR_PTR(-EINVAL);
+
+	return IOMEM(res->start);
+}
+EXPORT_SYMBOL(dev_platform_ioremap_resource);
diff --git a/include/linux/device.h b/include/linux/device.h
index 9b14bc74fef6..2b33580cd573 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -146,16 +146,7 @@ static inline int devm_add_action_or_reset(struct device *dev,
 #define driver_unregister unregister_driver
 
 
-static inline void __iomem *dev_platform_ioremap_resource(struct device *dev,
-							  int resource)
-{
-	/*
-	 * barebox maps everything outside the RAM banks suitably for MMIO,
-	 * so we don't need to do anything besides requesting the regions
-	 * and can leave the memory attributes unchanged.
-	 */
-	return dev_request_mem_region(dev, resource);
-}
+void __iomem *dev_platform_ioremap_resource(struct device *dev, int num);
 
 static inline void __iomem *devm_ioremap(struct device *dev,
 					 resource_size_t start,
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 17/19] treewide: rename dev_platform_ioremap_resource() to devm_platform_ioremap_resource()
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (15 preceding siblings ...)
  2026-10-28  9:14 ` [PATCH 16/19] driver: make dev_platform_ioremap_resource() device-managed Ahmad Fatoum
@ 2026-10-28  9:14 ` 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
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Now that it's device-managed, give dev_platform_ioremap_resource() the
Linux name, so ported drivers can use it unchanged.

Assisted-by: Claude:opus-5-1m
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/base/devres.c                            | 6 +++---
 drivers/hw_random/atmel-rng.c                    | 2 +-
 drivers/hw_random/bcm2835-rng.c                  | 2 +-
 drivers/hw_random/iproc-rng200.c                 | 2 +-
 drivers/hw_random/omap-rng.c                     | 2 +-
 drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c | 2 +-
 drivers/pmdomain/imx/imx8mp-blk-ctrl.c           | 2 +-
 drivers/pmdomain/imx/imx93-blk-ctrl.c            | 2 +-
 drivers/pmdomain/imx/imx93-pd.c                  | 2 +-
 drivers/pwm/pwm-imx-tpm.c                        | 2 +-
 drivers/pwm/pwm-rockchip.c                       | 2 +-
 drivers/usb/dwc3/dwc3-imx8mp.c                   | 2 +-
 include/linux/device.h                           | 2 +-
 13 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index f68b8e7b6590..cfa6cd3e3d9e 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -207,7 +207,7 @@ struct resource *devm_request_mem_resource(struct device *dev, int num)
 EXPORT_SYMBOL(devm_request_mem_resource);
 
 /**
- * dev_platform_ioremap_resource() - request and map resource 'num' for a device
+ * devm_platform_ioremap_resource() - request and map resource 'num' for a device
  * @dev: device to request the memory resource for
  * @num: index of the memory resource
  *
@@ -216,7 +216,7 @@ EXPORT_SYMBOL(devm_request_mem_resource);
  *
  * Return: a pointer to the registers or an ERR_PTR() on failure.
  */
-void __iomem *dev_platform_ioremap_resource(struct device *dev, int num)
+void __iomem *devm_platform_ioremap_resource(struct device *dev, int num)
 {
 	struct resource *res;
 
@@ -235,4 +235,4 @@ void __iomem *dev_platform_ioremap_resource(struct device *dev, int num)
 
 	return IOMEM(res->start);
 }
-EXPORT_SYMBOL(dev_platform_ioremap_resource);
+EXPORT_SYMBOL(devm_platform_ioremap_resource);
diff --git a/drivers/hw_random/atmel-rng.c b/drivers/hw_random/atmel-rng.c
index bdd2139b08a5..0c6355b3d83c 100644
--- a/drivers/hw_random/atmel-rng.c
+++ b/drivers/hw_random/atmel-rng.c
@@ -106,7 +106,7 @@ static int atmel_trng_probe(struct device *dev)
 	if (!trng)
 		return -ENOMEM;
 
-	trng->base = dev_platform_ioremap_resource(dev, 0);
+	trng->base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(trng->base))
 		return PTR_ERR(trng->base);
 
diff --git a/drivers/hw_random/bcm2835-rng.c b/drivers/hw_random/bcm2835-rng.c
index d82331c950d2..76d3c13335f0 100644
--- a/drivers/hw_random/bcm2835-rng.c
+++ b/drivers/hw_random/bcm2835-rng.c
@@ -146,7 +146,7 @@ static int bcm2835_rng_probe(struct device *dev)
 		return -ENOMEM;
 
 	/* map peripheral */
-	priv->base = dev_platform_ioremap_resource(dev, 0);
+	priv->base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(priv->base))
 		return PTR_ERR(priv->base);
 
diff --git a/drivers/hw_random/iproc-rng200.c b/drivers/hw_random/iproc-rng200.c
index 4cb3573a7d20..22cdeebeba46 100644
--- a/drivers/hw_random/iproc-rng200.c
+++ b/drivers/hw_random/iproc-rng200.c
@@ -169,7 +169,7 @@ static int iproc_rng200_probe(struct device *dev)
 		return -ENOMEM;
 
 	/* Map peripheral */
-	priv->base = dev_platform_ioremap_resource(dev, 0);
+	priv->base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(priv->base)) {
 		dev_err(dev, "failed to remap rng regs\n");
 		return PTR_ERR(priv->base);
diff --git a/drivers/hw_random/omap-rng.c b/drivers/hw_random/omap-rng.c
index e6974d7769f4..7a299f49a523 100644
--- a/drivers/hw_random/omap-rng.c
+++ b/drivers/hw_random/omap-rng.c
@@ -373,7 +373,7 @@ static int omap_rng_probe(struct device *dev)
 	dev->priv = priv;
 	priv->dev = dev;
 
-	priv->base = dev_platform_ioremap_resource(dev, 0);
+	priv->base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(priv->base)) {
 		ret = PTR_ERR(priv->base);
 		goto err_ioremap;
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c b/drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c
index 161c9d7c0d1c..905d292816c7 100644
--- a/drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c
@@ -698,7 +698,7 @@ static int inno_dsidphy_probe(struct device *dev)
 	inno->pdata = device_get_match_data(inno->dev);
 	dev_set_drvdata(dev, inno);
 
-	inno->phy_base = dev_platform_ioremap_resource(dev, 0);
+	inno->phy_base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(inno->phy_base))
 		return PTR_ERR(inno->phy_base);
 
diff --git a/drivers/pmdomain/imx/imx8mp-blk-ctrl.c b/drivers/pmdomain/imx/imx8mp-blk-ctrl.c
index 3db26e91b31b..9f32bed5a319 100644
--- a/drivers/pmdomain/imx/imx8mp-blk-ctrl.c
+++ b/drivers/pmdomain/imx/imx8mp-blk-ctrl.c
@@ -409,7 +409,7 @@ static int imx8mp_blk_ctrl_probe(struct device *dev)
 	bc_data = of_device_get_match_data(dev);
 	num_domains = bc_data->num_domains;
 
-	base = dev_platform_ioremap_resource(dev, 0);
+	base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
diff --git a/drivers/pmdomain/imx/imx93-blk-ctrl.c b/drivers/pmdomain/imx/imx93-blk-ctrl.c
index 242e3047fc0c..1d296023d2f6 100644
--- a/drivers/pmdomain/imx/imx93-blk-ctrl.c
+++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c
@@ -208,7 +208,7 @@ static int imx93_blk_ctrl_probe(struct device *dev)
 
 	bc_data = of_device_get_match_data(dev);
 
-	base = dev_platform_ioremap_resource(dev, 0);
+	base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
diff --git a/drivers/pmdomain/imx/imx93-pd.c b/drivers/pmdomain/imx/imx93-pd.c
index 3222fc17e9d0..faab1bcb7c80 100644
--- a/drivers/pmdomain/imx/imx93-pd.c
+++ b/drivers/pmdomain/imx/imx93-pd.c
@@ -93,7 +93,7 @@ static int imx93_pd_probe(struct device *dev)
 	if (!domain)
 		return -ENOMEM;
 
-	domain->addr = dev_platform_ioremap_resource(dev, 0);
+	domain->addr = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(domain->addr))
 		return PTR_ERR(domain->addr);
 
diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
index f2b0b8b9038e..9d71b5113b88 100644
--- a/drivers/pwm/pwm-imx-tpm.c
+++ b/drivers/pwm/pwm-imx-tpm.c
@@ -352,7 +352,7 @@ static int pwm_imx_tpm_probe(struct device *dev)
 	u32 val;
 	int i;
 
-	base = dev_platform_ioremap_resource(dev, 0);
+	base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c
index b302a84d5f3f..9b208467f2ee 100644
--- a/drivers/pwm/pwm-rockchip.c
+++ b/drivers/pwm/pwm-rockchip.c
@@ -261,7 +261,7 @@ static int rockchip_pwm_probe(struct device *dev)
 	if (!pc)
 		return -ENOMEM;
 
-	pc->base = dev_platform_ioremap_resource(dev, 0);
+	pc->base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(pc->base))
 		return PTR_ERR(pc->base);
 
diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c
index d165b95a2eec..71bcd68b0a31 100644
--- a/drivers/usb/dwc3/dwc3-imx8mp.c
+++ b/drivers/usb/dwc3/dwc3-imx8mp.c
@@ -109,7 +109,7 @@ static int dwc3_imx8mp_probe(struct device *dev)
 
 	dwc3_imx->dev = dev;
 
-	dwc3_imx->hsio_blk_base = dev_platform_ioremap_resource(dev, 0);
+	dwc3_imx->hsio_blk_base = devm_platform_ioremap_resource(dev, 0);
 	if (IS_ERR(dwc3_imx->hsio_blk_base))
 		return PTR_ERR(dwc3_imx->hsio_blk_base);
 
diff --git a/include/linux/device.h b/include/linux/device.h
index 2b33580cd573..6654bf97f91d 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -146,7 +146,7 @@ static inline int devm_add_action_or_reset(struct device *dev,
 #define driver_unregister unregister_driver
 
 
-void __iomem *dev_platform_ioremap_resource(struct device *dev, int num);
+void __iomem *devm_platform_ioremap_resource(struct device *dev, int num);
 
 static inline void __iomem *devm_ioremap(struct device *dev,
 					 resource_size_t start,
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 18/19] driver: make dev_platform_get_and_ioremap_resource() device-managed
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (16 preceding siblings ...)
  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 ` Ahmad Fatoum
  2026-10-28  9:14 ` [PATCH 19/19] serial: stm32: request the registers with devm_request_mem_resource() Ahmad Fatoum
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

dev_platform_get_and_ioremap_resource() has the same -EBUSY problem on
deferred probe. Implement it on top of devm_request_mem_resource() as
well, move it to devres.c and give it the Linux name, which leaves
devm_platform_ioremap_resource() as a thin wrapper like upstream.

Assisted-by: Claude:opus-5-1m
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/base/devres.c                         | 25 ++++++++++++++++--
 drivers/base/driver.c                         | 26 -------------------
 drivers/hw_random/timeriomem-rng.c            |  2 +-
 drivers/mfd/atmel-flexcom.c                   |  2 +-
 drivers/phy/rockchip/phy-rockchip-usbdp.c     |  2 +-
 drivers/spi/spi-fsl-qspi.c                    |  2 +-
 drivers/video/rockchip/dw-mipi-dsi-rockchip.c |  2 +-
 include/driver.h                              |  6 ++---
 8 files changed, 31 insertions(+), 36 deletions(-)

diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index cfa6cd3e3d9e..f1cf8e465074 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -207,16 +207,18 @@ struct resource *devm_request_mem_resource(struct device *dev, int num)
 EXPORT_SYMBOL(devm_request_mem_resource);
 
 /**
- * devm_platform_ioremap_resource() - request and map resource 'num' for a device
+ * devm_platform_get_and_ioremap_resource() - request and map resource 'num'
  * @dev: device to request the memory resource for
  * @num: index of the memory resource
+ * @out_res: where to store the requested resource, may be NULL
  *
  * barebox maps everything outside the RAM banks suitably for MMIO, so this
  * only requests the device-managed region.
  *
  * Return: a pointer to the registers or an ERR_PTR() on failure.
  */
-void __iomem *devm_platform_ioremap_resource(struct device *dev, int num)
+void __iomem *devm_platform_get_and_ioremap_resource(struct device *dev, int num,
+						     struct resource **out_res)
 {
 	struct resource *res;
 
@@ -233,6 +235,25 @@ void __iomem *devm_platform_ioremap_resource(struct device *dev, int num)
 	if (WARN_ON(IS_ERR_VALUE(res->start)))
 		return IOMEM_ERR_PTR(-EINVAL);
 
+	if (out_res)
+		*out_res = res;
+
 	return IOMEM(res->start);
 }
+EXPORT_SYMBOL(devm_platform_get_and_ioremap_resource);
+
+/**
+ * devm_platform_ioremap_resource() - request and map resource 'num' for a device
+ * @dev: device to request the memory resource for
+ * @num: index of the memory resource
+ *
+ * Like devm_platform_get_and_ioremap_resource(), but without handing the
+ * resource out.
+ *
+ * Return: a pointer to the registers or an ERR_PTR() on failure.
+ */
+void __iomem *devm_platform_ioremap_resource(struct device *dev, int num)
+{
+	return devm_platform_get_and_ioremap_resource(dev, num, NULL);
+}
 EXPORT_SYMBOL(devm_platform_ioremap_resource);
diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index f87ecb200feb..33dad6ab6b16 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -557,32 +557,6 @@ void __iomem *dev_request_mem_region_by_name(struct device *dev,
 }
 EXPORT_SYMBOL(dev_request_mem_region_by_name);
 
-void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev,
-						    int num,
-						    struct resource **out_res)
-{
-	struct resource *res;
-
-	res = dev_request_mem_resource(dev, num);
-	if (IS_ERR(res))
-		return IOMEM_ERR_PTR(PTR_ERR(res));
-
-	/* As everything is mapped 1:1 by default, drivers on unlucky
-	 * platforms can end up successfully requesting memory, but
-	 * getting a base address that looks like an error pointer.
-	 * Let's warn loudly about this case, as these drivers
-	 * should be using dev_request_mem_resource instead.
-	 */
-	if (WARN_ON(IS_ERR_VALUE(res->start)))
-		return IOMEM_ERR_PTR(-EINVAL);
-
-	if (out_res)
-		*out_res = res;
-
-	return IOMEM(res->start);
-}
-EXPORT_SYMBOL(dev_platform_get_and_ioremap_resource);
-
 struct resource *dev_request_mem_resource(struct device *dev, int num)
 {
 	struct resource *res;
diff --git a/drivers/hw_random/timeriomem-rng.c b/drivers/hw_random/timeriomem-rng.c
index 8d4705830653..4936c3c9676f 100644
--- a/drivers/hw_random/timeriomem-rng.c
+++ b/drivers/hw_random/timeriomem-rng.c
@@ -93,7 +93,7 @@ static int timeriomem_rng_probe(struct device *dev)
 	if (!priv)
 		return -ENOMEM;
 
-	priv->io_base = dev_platform_get_and_ioremap_resource(dev, 0, &res);
+	priv->io_base = devm_platform_get_and_ioremap_resource(dev, 0, &res);
 	if (IS_ERR(priv->io_base))
 		return PTR_ERR(priv->io_base);
 
diff --git a/drivers/mfd/atmel-flexcom.c b/drivers/mfd/atmel-flexcom.c
index 11daeafc5ffe..a7a650690e3f 100644
--- a/drivers/mfd/atmel-flexcom.c
+++ b/drivers/mfd/atmel-flexcom.c
@@ -35,7 +35,7 @@ static int atmel_flexcom_probe(struct device *dev)
 	if (opmode < ATMEL_FLEXCOM_MODE_USART || opmode > ATMEL_FLEXCOM_MODE_TWI)
 		return -EINVAL;
 
-	base = dev_platform_get_and_ioremap_resource(dev, 0, NULL);
+	base = devm_platform_get_and_ioremap_resource(dev, 0, NULL);
 	if (IS_ERR(base))
 		return dev_err_probe(dev, PTR_ERR(base), "Could not get memory region\n");
 
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index 9b08e60bbf6f..fee565cb231c 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -1053,7 +1053,7 @@ static int rk_udphy_probe(struct device *dev)
 	if (!udphy->cfgs)
 		return dev_err_probe(dev, -EINVAL, "missing match data\n");
 
-	base = dev_platform_get_and_ioremap_resource(dev, 0, &res);
+	base = devm_platform_get_and_ioremap_resource(dev, 0, &res);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c
index cda0b06a5446..029f267453bc 100644
--- a/drivers/spi/spi-fsl-qspi.c
+++ b/drivers/spi/spi-fsl-qspi.c
@@ -798,7 +798,7 @@ static int fsl_qspi_probe(struct device *dev)
 		goto err_put_ctrl;
 	}
 
-	q->ahb_addr = dev_platform_get_and_ioremap_resource(dev, 1, &res);
+	q->ahb_addr = devm_platform_get_and_ioremap_resource(dev, 1, &res);
 	if (IS_ERR(q->ahb_addr)) {
 		ret = PTR_ERR(q->ahb_addr);
 		goto err_put_ctrl;
diff --git a/drivers/video/rockchip/dw-mipi-dsi-rockchip.c b/drivers/video/rockchip/dw-mipi-dsi-rockchip.c
index f065ebcefa00..ace989427422 100644
--- a/drivers/video/rockchip/dw-mipi-dsi-rockchip.c
+++ b/drivers/video/rockchip/dw-mipi-dsi-rockchip.c
@@ -1032,7 +1032,7 @@ static int dw_mipi_dsi_rockchip_probe(struct device *dev)
 	if (!dsi)
 		return -ENOMEM;
 
-	dsi->base = dev_platform_get_and_ioremap_resource(dev, 0, &res);
+	dsi->base = devm_platform_get_and_ioremap_resource(dev, 0, &res);
 	if (IS_ERR(dsi->base)) {
 		DRM_DEV_ERROR(dev, "Unable to get dsi registers\n");
 		return PTR_ERR(dsi->base);
diff --git a/include/driver.h b/include/driver.h
index 4005804bb1a9..e065e89547c9 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -196,9 +196,9 @@ struct resource *dev_request_mem_resource_by_name(struct device *dev,
 /* Like dev_request_mem_resource(), but released when the device goes away */
 struct resource *devm_request_mem_resource(struct device *dev, int num);
 
-void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev,
-						    int num,
-						    struct resource **out_res);
+void __iomem *devm_platform_get_and_ioremap_resource(struct device *dev,
+						     int num,
+						     struct resource **out_res);
 
 /*
  * exlusively request register base 'num' for a device
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 19/19] serial: stm32: request the registers with devm_request_mem_resource()
  2026-10-28  9:14 [PATCH 00/19] driver: add devm_ support Ahmad Fatoum
                   ` (17 preceding siblings ...)
  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 ` Ahmad Fatoum
  18 siblings, 0 replies; 20+ messages in thread
From: Ahmad Fatoum @ 2026-10-28  9:14 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

From: Ahmad Fatoum <a.fatoum@barebox.org>

Use the managed request and drop the io_release error label.

The driver data stays on xzalloc(), as console_register() keeps a
pointer to it and console_unregister() may refuse with -EBUSY.

Assisted-by: Claude:opus-5-1m
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
 drivers/serial/serial_stm32.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/serial/serial_stm32.c b/drivers/serial/serial_stm32.c
index b3c4eaf90fcc..ca2f1bdab323 100644
--- a/drivers/serial/serial_stm32.c
+++ b/drivers/serial/serial_stm32.c
@@ -156,7 +156,7 @@ static int stm32_serial_probe(struct device *dev)
 	stm32 = xzalloc(sizeof(*stm32));
 	cdev = &stm32->cdev;
 
-	res = dev_request_mem_resource(dev, 0);
+	res = devm_request_mem_resource(dev, 0);
 	if (IS_ERR(res)) {
 		ret = PTR_ERR(res);
 		goto err_free;
@@ -171,7 +171,7 @@ static int stm32_serial_probe(struct device *dev)
 	if (IS_ERR(stm32->clk)) {
 		ret = dev_err_probe(dev, PTR_ERR(stm32->clk),
 				    "Failed to get/enable UART clock\n");
-		goto io_release;
+		goto err_free;
 	}
 
 	cdev->dev    = dev;
@@ -197,8 +197,6 @@ static int stm32_serial_probe(struct device *dev)
 		return 0;
 
 	clk_put(stm32->clk);
-io_release:
-	release_region(res);
 err_free:
 	free(stm32);
 
-- 
2.47.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-10-28  9:52 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH 10/19] driver: implement devm_ allocation helpers on top of talloc Ahmad Fatoum
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox