[PATCH v11 22/31] cxl + dax: Release dax_resources on DCD Release Capacity events
From: Anisa Su
Date: Thu Jun 25 2026 - 07:32:46 EST
From: Ira Weiny <iweiny@xxxxxxxxxx>
Implement the release path that mirrors the add path: when the device
asks for capacity back, the dax layer tears down the per-extent
resources for the whole tag group atomically via
dax_region_rm_resources().
If any extent in the group is still mapped by a dev_dax, the release
is refused with -EBUSY and no state changes; the cxl side then leaves
the tag group intact and the device retries.
Based on an original patch by Navneet Singh.
Signed-off-by: Ira Weiny <iweiny@xxxxxxxxxx>
Signed-off-by: Anisa Su <anisa.su@xxxxxxxxxxx>
Reviewed-by: Dave Jiang <dave.jiang@xxxxxxxxx>
---
drivers/cxl/core/extent.c | 12 +++++++++++
drivers/dax/bus.c | 43 +++++++++++++++++++++++++++++++++++++++
drivers/dax/cxl.c | 39 +++++++++++++++++++++++++----------
drivers/dax/dax-private.h | 8 ++++++--
4 files changed, 89 insertions(+), 13 deletions(-)
diff --git a/drivers/cxl/core/extent.c b/drivers/cxl/core/extent.c
index 59db1878b5e2..7009ac6a51b4 100644
--- a/drivers/cxl/core/extent.c
+++ b/drivers/cxl/core/extent.c
@@ -627,6 +627,18 @@ int cxl_rm_extent(struct cxl_memdev_state *mds, struct cxl_extent *extent)
if (rc)
return rc;
+ rc = cxlr_notify_extent(cxlr, DCD_RELEASE_CAPACITY, group);
+ if (rc) {
+ /*
+ * dax layer refused (-EBUSY) or failed (-ENOMEM, etc.). Do
+ * not proceed to tear down the tag group — leave its
+ * dax_resources alive so we do not free them out from under
+ * live dev_dax ranges. The device will retry the release.
+ */
+ return 0;
+ }
+
+ /* Release the entire tag group */
rm_tag_group(group);
return 0;
}
diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c
index 9b5c03616b83..95683dc8fcd0 100644
--- a/drivers/dax/bus.c
+++ b/drivers/dax/bus.c
@@ -282,6 +282,14 @@ static int __dax_region_rm_resource(struct dax_region *dax_region,
return 0;
}
+int dax_region_rm_resource(struct dax_region *dax_region,
+ struct device *dev)
+{
+ guard(rwsem_write)(&dax_region_rwsem);
+ return __dax_region_rm_resource(dax_region, dev);
+}
+EXPORT_SYMBOL_GPL(dax_region_rm_resource);
+
/**
* dax_region_add_resources - atomically add a set of dax_resources.
*
@@ -314,6 +322,41 @@ int dax_region_add_resources(struct dax_region *dax_region,
}
EXPORT_SYMBOL_GPL(dax_region_add_resources);
+/**
+ * dax_region_rm_resources - atomically remove a set of dax_resources.
+ *
+ * Walk @devs twice under dax_region_rwsem. First pass refuses the
+ * operation if any member's use_cnt is non-zero; second pass releases
+ * each. This gives refuse-all-or-none semantics across the set, which
+ * a tag group's atomic release relies on. Devices with no
+ * dax_resource attached are silently skipped.
+ */
+int dax_region_rm_resources(struct dax_region *dax_region,
+ struct device * const *devs, unsigned int n)
+{
+ unsigned int i;
+
+ guard(rwsem_write)(&dax_region_rwsem);
+
+ for (i = 0; i < n; i++) {
+ struct dax_resource *r = dev_get_drvdata(devs[i]);
+
+ if (r && r->use_cnt)
+ return -EBUSY;
+ }
+
+ for (i = 0; i < n; i++) {
+ struct dax_resource *r = dev_get_drvdata(devs[i]);
+
+ if (!r)
+ continue;
+ __dax_release_resource(r);
+ dev_set_drvdata(devs[i], NULL);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dax_region_rm_resources);
+
bool static_dev_dax(struct dev_dax *dev_dax)
{
return is_static(dev_dax->region);
diff --git a/drivers/dax/cxl.c b/drivers/dax/cxl.c
index 5d33be342d42..d885b6e698ef 100644
--- a/drivers/dax/cxl.c
+++ b/drivers/dax/cxl.c
@@ -40,13 +40,33 @@ static int cxl_dax_group_add(struct dax_region *dax_region,
return rc;
}
-/*
- * RELEASE is still a stub here — the atomic dax_region_rm_resources API
- * and its wire-up land in the next commit. An incoming RELEASE returns
- * success and the cxl side proceeds to rm_tag_group(), which device-
- * unregisters each dc_extent; the devm action armed by
- * dax_region_add_resource() then tears down each dax_resource.
- */
+static int cxl_dax_group_rm(struct dax_region *dax_region,
+ struct cxl_dc_tag_group *group)
+{
+ struct dc_extent *dc_extent;
+ struct device **devs;
+ unsigned long index;
+ unsigned int n = 0;
+ int rc;
+
+ if (!group->nr_extents)
+ return 0;
+
+ devs = kmalloc_array(group->nr_extents, sizeof(*devs), GFP_KERNEL);
+ if (!devs)
+ return -ENOMEM;
+
+ xa_for_each(&group->dc_extents, index, dc_extent) {
+ if (n == group->nr_extents)
+ break;
+ devs[n++] = &dc_extent->dev;
+ }
+
+ rc = dax_region_rm_resources(dax_region, devs, n);
+ kfree(devs);
+ return rc;
+}
+
static int cxl_dax_region_notify(struct device *dev,
struct cxl_notify_data *notify_data)
{
@@ -58,10 +78,7 @@ static int cxl_dax_region_notify(struct device *dev,
case DCD_ADD_CAPACITY:
return cxl_dax_group_add(dax_region, group);
case DCD_RELEASE_CAPACITY:
- dev_dbg(&cxlr_dax->dev,
- "DCD RELEASE notify (tag %pUb): no-op (stub)\n",
- &group->uuid);
- return 0;
+ return cxl_dax_group_rm(dax_region, group);
case DCD_FORCED_CAPACITY_RELEASE:
default:
dev_err(&cxlr_dax->dev, "Unknown DC event %d\n",
diff --git a/drivers/dax/dax-private.h b/drivers/dax/dax-private.h
index 8d98fc9adb4b..59ba929e14fd 100644
--- a/drivers/dax/dax-private.h
+++ b/drivers/dax/dax-private.h
@@ -146,13 +146,17 @@ struct dax_resource {
};
/*
- * Similar to run_dax() dax_region_add_resource() is exported but is not
- * intended to be a generic operation outside the dax subsystem. It is only
+ * Similar to run_dax() dax_region_{add,rm}_resource() are exported but are not
+ * intended to be generic operations outside the dax subsystem. They are only
* generic between the dax layer and the dax drivers.
*/
int dax_region_add_resource(struct dax_region *dax_region, struct device *dev,
resource_size_t start, resource_size_t length,
const uuid_t *tag, u16 seq_num);
+int dax_region_rm_resource(struct dax_region *dax_region,
+ struct device *dev);
+int dax_region_rm_resources(struct dax_region *dax_region,
+ struct device * const *devs, unsigned int n);
/* One resource to add as part of an atomic dax_region_add_resources() set. */
struct dax_resource_spec {
--
2.43.0