From: Ira Weiny <[email protected]>

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 <[email protected]>
Signed-off-by: Anisa Su <[email protected]>
Reviewed-by: Dave Jiang <[email protected]>
---
 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


Reply via email to