[PATCH RFC 11/40] devres: add devm_krealloc

Casey Connolly <[email protected]> Thu, 19 Mar 2026 21:56:33 +0100
Newsgroups io.groups.u-boot-amlogic,org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Loosely based on the Linux version, this makes it possible to krealloc()
devm managed memory. Currently it depends on tracking the allocation
size in struct devres even when DEBUG_DEVRES is disabled, but it's a
relatively small tradeoff for the additional functionality.

Signed-off-by: Casey Connolly <[email protected]>
---
 drivers/core/devres.c | 83 ++++++++++++++++++++++++++++++++++++++++++++-------
 include/dm/devres.h   | 10 +++++++
 2 files changed, 83 insertions(+), 10 deletions(-)

diff --git a/drivers/core/devres.c b/drivers/core/devres.c
index 8df08b91021c..88f7c832f43e 100644
--- a/drivers/core/devres.c
+++ b/drivers/core/devres.c
@@ -45,18 +45,17 @@ struct devres {
 	dr_release_t			release;
 	enum devres_phase		phase;
 #ifdef CONFIG_DEBUG_DEVRES
 	const char			*name;
-	size_t				size;
 #endif
+	size_t				size;
 	unsigned long long		data[];
 };
 
 #ifdef CONFIG_DEBUG_DEVRES
-static void set_node_dbginfo(struct devres *dr, const char *name, size_t size)
+static void set_node_dbginfo(struct devres *dr, const char *name)
 {
 	dr->name = name;
-	dr->size = size;
 }
 
 static void devres_log(struct udevice *dev, struct devres *dr,
 		       const char *op)
@@ -64,9 +63,9 @@ static void devres_log(struct udevice *dev, struct devres *dr,
 	log_debug("%s: DEVRES %3s %p %s (%lu bytes)\n", dev->name, op, dr,
 		  dr->name, (unsigned long)dr->size);
 }
 #else /* CONFIG_DEBUG_DEVRES */
-#define set_node_dbginfo(dr, n, s)	do {} while (0)
+#define set_node_dbginfo(dr, n)	do {} while (0)
 #define devres_log(dev, dr, op)		do {} while (0)
 #endif
 
 #if CONFIG_DEBUG_DEVRES
@@ -84,9 +83,10 @@ void *_devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
 		return NULL;
 
 	INIT_LIST_HEAD(&dr->entry);
 	dr->release = release;
-	set_node_dbginfo(dr, name, size);
+	dr->size = size;
+	set_node_dbginfo(dr, name);
 
 	return dr->data;
 }
 
@@ -99,23 +99,35 @@ void devres_free(void *res)
 		kfree(dr);
 	}
 }
 
-void devres_add(struct udevice *dev, void *res)
+static void devres_set_phase(struct udevice *dev, struct devres *dr)
 {
-	struct devres *dr = container_of(res, struct devres, data);
-
-	devres_log(dev, dr, "ADD");
-	assert_noisy(list_empty(&dr->entry));
 	if (dev_get_flags(dev) & DM_FLAG_PLATDATA_VALID)
 		dr->phase = DEVRES_PHASE_PROBE;
 	else if (dev_get_flags(dev) & DM_FLAG_BOUND)
 		dr->phase = DEVRES_PHASE_OFDATA;
 	else
 		dr->phase = DEVRES_PHASE_BIND;
+}
+
+void devres_add(struct udevice *dev, void *res)
+{
+	struct devres *dr = container_of(res, struct devres, data);
+
+	devres_log(dev, dr, "ADD");
+	assert_noisy(list_empty(&dr->entry));
+	devres_set_phase(dev, dr);
 	list_add_tail(&dr->entry, &dev->devres_head);
 }
 
+static void devres_replace(struct udevice *dev, struct devres *old, struct devres *new)
+{
+	devres_log(dev, old, "REPLACE");
+	devres_set_phase(dev, new);
+	list_replace(&old->entry, &new->entry);
+}
+
 void *devres_find(struct udevice *dev, dr_release_t release,
 		  dr_match_t match, void *match_data)
 {
 	struct devres *dr;
@@ -290,4 +302,55 @@ void devm_kfree(struct udevice *dev, void *p)
 
 	rc = devres_destroy(dev, devm_kmalloc_release, devm_kmalloc_match, p);
 	assert_noisy(!rc);
 }
+
+/**
+ * devm_krealloc - Resource-managed krealloc()
+ * @dev: Device to re-allocate memory for
+ * @ptr: Pointer to the memory chunk to re-allocate
+ * @new_size: New allocation size
+ * @gfp: Allocation gfp flags
+ *
+ * Managed krealloc(). Resizes the memory chunk allocated with devm_kmalloc().
+ * Behaves similarly to regular krealloc(): if @ptr is NULL or ZERO_SIZE_PTR,
+ * it's the equivalent of devm_kmalloc(). If new_size is zero, it frees the
+ * previously allocated memory and returns ZERO_SIZE_PTR. This function doesn't
+ * change the order in which the release callback for the re-alloc'ed devres
+ * will be called (except when falling back to devm_kmalloc() or when freeing
+ * resources when new_size is zero). The contents of the memory are preserved
+ * up to the lesser of new and old sizes.
+ */
+void *devm_krealloc(struct udevice *dev, void *ptr, size_t new_size, gfp_t gfp)
+{
+	struct devres *old_dr, *new_dr;
+
+	if (unlikely(!new_size)) {
+		devm_kfree(dev, ptr);
+		return NULL;
+	}
+
+	if (unlikely(!ptr))
+		return devm_kmalloc(dev, new_size, gfp);
+
+	old_dr = devres_find(dev, devm_kmalloc_release, devm_kmalloc_match, ptr);
+	if (!old_dr) {
+		printf("Devres realloc failed, memory chunk not managed or managed by a different device\n");
+		return NULL;
+	}
+
+	/* No need to realloc to shrink */
+	if (old_dr->size < new_size)
+		return old_dr->data;
+
+	new_dr = _devres_alloc(devm_kmalloc_release, new_size, gfp);
+	if (!new_dr)
+		return NULL;
+
+	devres_replace(dev, old_dr, new_dr);
+	memcpy(new_dr->data, old_dr->data,
+	       old_dr->size);
+
+	devres_free(old_dr->data);
+
+	return new_dr->data;
+}
diff --git a/include/dm/devres.h b/include/dm/devres.h
index 406d18686f4d..13d7d0eec43a 100644
--- a/include/dm/devres.h
+++ b/include/dm/devres.h
@@ -194,8 +194,18 @@ static inline void *devm_kcalloc(struct udevice *dev,
 {
 	return devm_kmalloc_array(dev, n, size, flags | __GFP_ZERO);
 }
 
+void *devm_krealloc(struct udevice *dev, void *ptr, size_t new_size, gfp_t gfp);
+
+static inline void *devm_krealloc_array(struct udevice *dev, void *p, size_t new_n, size_t new_size, gfp_t flags)
+{
+	if (new_size != 0 && new_n > SIZE_MAX / new_size)
+		return NULL;
+
+	return devm_krealloc(dev, p, new_n * new_size, flags);
+}
+
 /**
  * devm_kfree() - Resource-managed kfree
  * @dev: Device this memory belongs to
  * @ptr: Memory to free

-- 
2.51.0