[PATCH v4 1/2] cxl/hdm: Allow zero sized HDM decoders
From: Richard Cheng
Date: Sun Jun 07 2026 - 04:14:47 EST
CXL r3.2 8.2.4.20.12 and 14.13.10 permit committing an HDM decoder with
size 0. BIOS commits and locks such decoders to burn the trailing, unused
slots so the OS cannot program regions through them, e.g. a Type 3 device
in a TSP-established TCB. init_hdm_decoder() rejected these with -ENXIO and
aborted port enumeration, so "cxl list" showed nothing.
Enumerate the decoder with its hardware LOCK state and skip the DPA
reservation it does not need.
commit_end may now reference a decoder with no DPA resource, so
poison_by_decoder() must still scan the unmapped DPA tail.
Signed-off-by: Vishal Aslot <vaslot@xxxxxxxxxx>
Signed-off-by: Richard Cheng <icheng@xxxxxxxxxx>
---
v3->v4:
- Drop the port->hdm_end advance for zero-size decoders (sashiko AI
review). The write was outside cxl_rwsem.dpa, and advancing the
watermark without a balanced release strands hdm_end and breaks
LIFO teardown. It is also unnecessary -- zero-size committed slots
are trailing and locked, so the in-order check in
__cxl_dpa_reserve() is never reached past them.
v2->v3:
- Advance port->hdm_end for the committed zero-size decoder so a
following committed decoder still passes the in-order check in
__cxl_dpa_reserve() (it was left un-incremented in v2).
- Fold the poison fix into this patch: commit_end may now reference a
zero-size decoder with no DPA resource, so poison_by_decoder() falls
through to run cxl_get_poison_unmapped() and scan the unmapped DPA
tail.
v1->v2:
- Add zero-size committed decoders to the topology instead of
skipping them. Drop v1's -ENOSPC sentinel and the matching
"continue" in devm_cxl_enumerate_decoders(); fall through so
add_hdm_decoder() registers the decoder.
- Set port->commit_end unconditionally for any committed decoder,
not only non-zero-size ones, so subsequent decoders satisfy the
out-of-order check.
- Add an explicit early-return before devm_cxl_dpa_reserve() in the
endpoint-decoder path. __cxl_dpa_reserve() rejects zero-size
decoders.
- Spell out TSP and TCB and cite spec sections in commit message.
- Reorder series, implementation first.
---
drivers/cxl/core/hdm.c | 23 +++++++++++++++------
drivers/cxl/core/region.c | 42 +++++++++++++++++++--------------------
2 files changed, 38 insertions(+), 27 deletions(-)
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 0c80b76a5f9b..b61f51134551 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -1031,13 +1031,17 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
return -ENXIO;
}
- if (size == 0) {
- dev_warn(&port->dev,
- "decoder%d.%d: Committed with zero size\n",
- port->id, cxld->id);
- return -ENXIO;
- }
port->commit_end = cxld->id;
+
+ /*
+ * CXL r3.2 8.2.4.20.12 permits committing an HDM decoder with
+ * size 0. Enumerate it into the topology with its HW-reported
+ * LOCK state instead of aborting the port.
+ */
+ if (size == 0)
+ dev_dbg(&port->dev,
+ "decoder%d.%d: Committed with zero size\n",
+ port->id, cxld->id);
} else {
if (cxled) {
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
@@ -1096,6 +1100,13 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
if (!committed)
return 0;
+ /*
+ * A committed zero-size decoder reserves no DPA. Leave port->hdm_end
+ * untouched.
+ */
+ if (size == 0)
+ return 0;
+
dpa_size = div_u64_rem(size, cxld->interleave_ways, &remainder);
if (remainder) {
dev_err(&port->dev,
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index e50dc716d4e8..a353d8e7489d 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -2907,38 +2907,38 @@ static int poison_by_decoder(struct device *dev, void *arg)
return rc;
cxled = to_cxl_endpoint_decoder(dev);
- if (!cxled->dpa_res)
- return rc;
- cxlmd = cxled_to_memdev(cxled);
- cxlds = cxlmd->cxlds;
- mode = cxlds->part[cxled->part].mode;
+ if (cxled->dpa_res) {
+ cxlmd = cxled_to_memdev(cxled);
+ cxlds = cxlmd->cxlds;
+ mode = cxlds->part[cxled->part].mode;
+
+ if (cxled->skip) {
+ offset = cxled->dpa_res->start - cxled->skip;
+ length = cxled->skip;
+ rc = cxl_mem_get_poison(cxlmd, offset, length, NULL);
+ if (rc == -EFAULT && mode == CXL_PARTMODE_RAM)
+ rc = 0;
+ if (rc)
+ return rc;
+ }
- if (cxled->skip) {
- offset = cxled->dpa_res->start - cxled->skip;
- length = cxled->skip;
- rc = cxl_mem_get_poison(cxlmd, offset, length, NULL);
+ offset = cxled->dpa_res->start;
+ length = cxled->dpa_res->end - offset + 1;
+ rc = cxl_mem_get_poison(cxlmd, offset, length, cxled->cxld.region);
if (rc == -EFAULT && mode == CXL_PARTMODE_RAM)
rc = 0;
if (rc)
return rc;
- }
- offset = cxled->dpa_res->start;
- length = cxled->dpa_res->end - offset + 1;
- rc = cxl_mem_get_poison(cxlmd, offset, length, cxled->cxld.region);
- if (rc == -EFAULT && mode == CXL_PARTMODE_RAM)
- rc = 0;
- if (rc)
- return rc;
-
- /* Iterate until commit_end is reached */
- if (cxled->cxld.id == ctx->port->commit_end) {
ctx->offset = cxled->dpa_res->end + 1;
ctx->part = cxled->part;
- return 1;
}
+ /* Iterate until commit_end is reached */
+ if (cxled->cxld.id == ctx->port->commit_end)
+ return 1;
+
return 0;
}
--
2.43.0