A cxl create-region failure after reserving endpoint DPA deletes the
region but leaves those reservations behind. Later creates can fail with
ENOSPC on capacity stranded by the earlier attempt.

Track reservations made by the current create and release them after
deleting the failed region.

Fixes: 21b089025178 ("cxl: add a 'create-region' command")
Assisted-by: LLM
Signed-off-by: Alison Schofield <[email protected]>
---
 cxl/region.c              | 35 ++++++++++++++++++++++++--
 test/cxl-create-region.sh | 52 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/cxl/region.c b/cxl/region.c
index 2bb3b90940b0..e4fc8bb2942c 100644
--- a/cxl/region.c
+++ b/cxl/region.c
@@ -712,14 +712,30 @@ static int cxl_region_determine_granularity(struct 
cxl_region *region,
        return -EINVAL;
 }
 
+/* Delete the region before releasing its endpoint DPA. */
+static void unreserve_dpa(struct cxl_decoder **ep_decoders, int nr)
+{
+       int i, rc;
+
+       for (i = 0; i < nr; i++) {
+               rc = cxl_decoder_set_dpa_size(ep_decoders[i], 0);
+               if (rc)
+                       log_err(&rl, "%s: failed to release dpa: %s\n",
+                               cxl_decoder_get_devname(ep_decoders[i]),
+                               strerror(abs(rc)));
+       }
+}
+
 static int create_region(struct cxl_ctx *ctx, int *count,
                         struct parsed_params *p)
 {
        unsigned long flags = UTIL_JSON_TARGETS;
+       struct cxl_decoder **ep_decoders;
        struct json_object *jregion;
        struct cxl_region *region;
        bool default_size = true;
        int i, rc, granularity;
+       int nr_reserved = 0;
        u64 size, max_extent;
        const char *devname;
 
@@ -775,6 +791,12 @@ static int create_region(struct cxl_ctx *ctx, int *count,
 
        devname = cxl_region_get_devname(region);
 
+       ep_decoders = calloc(p->ways, sizeof(*ep_decoders));
+       if (!ep_decoders) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
        rc = cxl_region_determine_granularity(region, p);
        if (rc < 0)
                goto out;
@@ -810,6 +832,7 @@ static int create_region(struct cxl_ctx *ctx, int *count,
                        try(cxl_decoder, set_mode, ep_decoder, p->mode);
                }
                try(cxl_decoder, set_dpa_size, ep_decoder, size/p->ways);
+               ep_decoders[nr_reserved++] = ep_decoder;
                rc = cxl_region_set_target(region, i, ep_decoder);
                if (rc) {
                        log_err(&rl, "%s: failed to set target%d to %s\n",
@@ -842,8 +865,16 @@ static int create_region(struct cxl_ctx *ctx, int *count,
 
 out:
        json_object_put(p->memdevs);
-       if (rc)
-               cxl_region_delete(region);
+       if (rc) {
+               int delete_rc = cxl_region_delete(region);
+
+               if (delete_rc)
+                       log_err(&rl, "%s: failed to delete: %s\n", devname,
+                               strerror(abs(delete_rc)));
+               else
+                       unreserve_dpa(ep_decoders, nr_reserved);
+       }
+       free(ep_decoders);
        return rc;
 }
 
diff --git a/test/cxl-create-region.sh b/test/cxl-create-region.sh
index 6ffe36b852a2..02358af7837a 100644
--- a/test/cxl-create-region.sh
+++ b/test/cxl-create-region.sh
@@ -340,6 +340,57 @@ refuse_bad_target_list()
                -d "$x2_decoder" -m "$pos0,nomemdev"
 }
 
+# Snapshot endpoint DPA reservations.
+reserved_dpa()
+{
+       $CXL list -b cxl_test -D -d endpoint -i |
+       jq -r ".[] |
+       select((.dpa_size // 0) > 0) |
+       \"\(.decoder) \(.dpa_size)\"" | sort
+}
+
+# Fail after earlier targets reserve DPA and verify it is released.
+release_dpa_on_failure()
+{
+       if ! setup_interleave_shape; then
+               echo "skipping dpa release on failure"
+               return
+       fi
+
+       # an x1 window over position 2's host bridge, to fill it through
+       decoder=$(find_x1_decoder "$pos2")
+
+       if [[ ! $decoder ]]; then
+               echo "no x1 pmem decoder over $pos2, skipping dpa release"
+               return
+       fi
+
+       filler=$($CXL create-region -b cxl_test -d "$decoder" -m "$pos2" |
+                jq -r ".region")
+       if [[ ! $filler ]]; then
+               echo "$decoder: unable to fill $pos2"
+               err "$LINENO"
+       fi
+
+       dpa_before="$(reserved_dpa)"
+
+       # Force the failure into endpoint DPA setup; default sizing would
+       # reject the request before reserving anything.
+       size=$((256 << 20))
+       create_expect_fail "set_dpa_size failed" \
+               -d "$x2_decoder" -m "$pos0 $pos1 $pos2 $pos3" \
+               -s $((size * 4))
+
+       dpa_after="$(reserved_dpa)"
+       if [[ "$dpa_after" != "$dpa_before" ]]; then
+               echo "dpa left reserved by the failed create:"
+               diff <(echo "$dpa_before") <(echo "$dpa_after") || true
+               err "$LINENO"
+       fi
+
+       destroy_regions "$filler"
+}
+
 # test region creation on devices behind a single-port host-bridge
 create_single
 
@@ -355,6 +406,7 @@ done
 
 create_csv_targets
 refuse_bad_target_list
+release_dpa_on_failure
 
 # Repeat x1/x2/x3/x4 region creation with XOR interleave arithmetic.
 load_cxl_test interleave_arithmetic=1
-- 
2.37.3


Reply via email to