[PATCH v1 09/14] cxl/mem: Enforce tag-group semantics

From: Anisa Su

Date: Wed Nov 04 2026 - 22:44:57 EST


The previous commits added validation for individual extents. Add
two group-level checks to cxl_validate_group():

- Sequence integrity (cxl_check_group_seq): sharability is taken from
the group's DC partition attribute (cxl_group_is_shareable).

For a sharable partition, extents have a shared_extn_seq number,
which must be 0..n-1 for a tag. Any gaps or duplicate values are an
error.

The field is reserved for non-sharable extents, and
cxl_validate_extent() rejects a non-zero value there when the group
is realized. Therefore, cxl_check_group_seq only applies to
sharable groups.

- cxl_check_group_partition: verify tagged allocations
don't span DC partitions.

Each check drops the whole group on violation.

Tag uniqueness checks land in a subsequent commit.

Based on patches by John Groves.

Co-developed-by: John Groves <John@xxxxxxxxxx>
Signed-off-by: John Groves <John@xxxxxxxxxx>
Signed-off-by: Anisa Su <anisa.su@xxxxxxxxxxx>
Tested-by: Wonjae Lee <wj28.lee@xxxxxxxxxxx>
Tested-by: Junhee Park <jh9934.park@xxxxxxxxxxx>
Tested-by: Heesoo Kim <habil.kim@xxxxxxxxxxx>
---
drivers/cxl/core/mbox.c | 138 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 133 insertions(+), 5 deletions(-)

diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c
index a605ffc4c64f..a1d3e04a1e1b 100644
--- a/drivers/cxl/core/mbox.c
+++ b/drivers/cxl/core/mbox.c
@@ -1452,6 +1452,119 @@ static void extract_tag_group(struct list_head *pending,
}
}

+/*
+ * Validate shared_extn_seq across a tag group from a sharable partition,
+ * already sorted ascending. Per CXL r4.0 Table 8-230 the device stamps
+ * each extent of an n-extent sharable allocation with a unique value in
+ * 0..n-1, so the sorted group must be exactly 0, 1, ..., n-1. A gap,
+ * duplicate, or out-of-range value is a device firmware bug.
+ *
+ * Non-sharable extents must carry 0 here, which cxl_validate_extent()
+ * enforces per extent when the group is realized, so only sharable
+ * groups are checked; sharability comes from the partition, not the
+ * seq value.
+ */
+static int cxl_check_group_seq(struct device *dev,
+ const uuid_t *tag,
+ const struct list_head *group,
+ bool shareable)
+{
+ struct cxl_extent_list_node *pos;
+ u16 expected = 0;
+
+ if (!shareable)
+ return 0;
+
+ list_for_each_entry(pos, group, list) {
+ u16 s = le16_to_cpu(pos->extent->shared_extn_seq);
+
+ if (s != expected) {
+ dev_warn_ratelimited(dev,
+ "Tag %pUb: sharable shared_extn_seq must be dense 0..n-1: expected %u got %u (firmware bug)\n",
+ tag, expected, s);
+ return -EINVAL;
+ }
+ expected++;
+ }
+ return 0;
+}
+
+/*
+ * A tag group's sharability is a property of the DC partition holding its
+ * extents (cxl_check_group_partition() separately enforces that the group
+ * does not span partitions). Resolve it from the first extent; an empty
+ * group or an extent outside any DC partition is treated as non-sharable.
+ */
+static bool cxl_group_is_shareable(struct cxl_memdev_state *mds,
+ const struct list_head *group)
+{
+ const struct cxl_dpa_partition *part;
+ struct cxl_extent_list_node *first;
+ struct cxl_extent *extent;
+ struct range ext_range;
+
+ if (list_empty(group))
+ return false;
+
+ first = list_first_entry(group, struct cxl_extent_list_node, list);
+ extent = first->extent;
+ ext_range = (struct range) {
+ .start = le64_to_cpu(extent->start_dpa),
+ .end = le64_to_cpu(extent->start_dpa) +
+ le64_to_cpu(extent->length) - 1,
+ };
+ part = cxl_extent_dc_partition(mds, extent, &ext_range);
+ return part && part->shareable;
+}
+
+/*
+ * For tagged groups, reject allocations that span DC partitions. A tag
+ * is an allocation identity; the partition's CDAT DSMAS entry is what
+ * tells the host which attributes (sharable, writable, coherency)
+ * apply. Untagged groups are skipped — the spec does not define a
+ * cross-chain identity for them.
+ */
+static int cxl_check_group_partition(struct cxl_memdev_state *mds,
+ const uuid_t *tag,
+ const struct list_head *group)
+{
+ struct device *dev = mds->cxlds.dev;
+ const struct cxl_dpa_partition *first_part = NULL;
+ u64 first_dpa = 0;
+ struct cxl_extent_list_node *pos;
+
+ if (uuid_is_null(tag) || list_empty(group))
+ return 0;
+
+ list_for_each_entry(pos, group, list) {
+ struct cxl_extent *extent = pos->extent;
+ struct range ext_range = (struct range) {
+ .start = le64_to_cpu(extent->start_dpa),
+ .end = le64_to_cpu(extent->start_dpa) +
+ le64_to_cpu(extent->length) - 1,
+ };
+ const struct cxl_dpa_partition *part;
+
+ part = cxl_extent_dc_partition(mds, extent, &ext_range);
+ if (!part)
+ return -ENXIO;
+
+ if (!first_part) {
+ first_part = part;
+ first_dpa = ext_range.start;
+ continue;
+ }
+
+ if (part != first_part) {
+ dev_warn_ratelimited(dev,
+ "Tag %pUb: extents span DC partitions (DPA:%#llx and DPA:%#llx), firmware bug\n",
+ tag, first_dpa, ext_range.start);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
/*
* Realize a tag @group: assign each extent its dax-side @seq_num and add it
* via cxl_add_extent(), then online the resulting tag group. Returns the
@@ -1460,7 +1573,7 @@ static void extract_tag_group(struct list_head *pending,
* the caller to drop.
*/
static int cxl_realize_group(struct cxl_memdev_state *mds, const uuid_t *tag,
- struct list_head *group)
+ struct list_head *group, bool shareable)
{
struct device *dev = mds->cxlds.dev;
struct cxl_extent_list_node *pos;
@@ -1475,7 +1588,12 @@ static int cxl_realize_group(struct cxl_memdev_state *mds, const uuid_t *tag,
/* Each extent gets a dense dax-side seq_num, 0..n-1 */
seq_num = -1;
list_for_each_entry(pos, group, list) {
- seq_num++;
+ /* Sharable: device-assigned dense 0..n-1; otherwise by arrival */
+ if (shareable)
+ seq_num = le16_to_cpu(pos->extent->shared_extn_seq);
+ else
+ seq_num++;
+
rc = cxl_add_extent(mds, pos->extent, seq_num);
if (rc < 0) {
dev_dbg(dev,
@@ -1517,11 +1635,19 @@ static int cxl_realize_group(struct cxl_memdev_state *mds, const uuid_t *tag,
* added, or a negative errno if it must be dropped.
*/
static int cxl_validate_group(struct cxl_memdev_state *mds, const uuid_t *tag,
- struct list_head *group)
+ struct list_head *group, bool shareable)
{
struct device *dev = mds->cxlds.dev;
struct cxl_extent_list_node *pos;

+ /* Sequence-number integrity */
+ if (cxl_check_group_seq(dev, tag, group, shareable))
+ return -EINVAL;
+
+ /* Partition equality (skipped for null UUID) */
+ if (cxl_check_group_partition(mds, tag, group))
+ return -EINVAL;
+
/* Alignment gate — drop the group if any member fails */
list_for_each_entry(pos, group, list) {
if (!cxl_extent_dcd_aligned(pos->extent)) {
@@ -1559,6 +1685,7 @@ static int cxl_add_pending(struct cxl_memdev_state *mds,
while (!list_empty(pending)) {
struct cxl_extent_list_node *first;
LIST_HEAD(group);
+ bool shareable;
uuid_t tag;
int cnt;

@@ -1581,13 +1708,14 @@ static int cxl_add_pending(struct cxl_memdev_state *mds,
* the stable sort maintains arrival order.
*/
list_sort(NULL, &group, extent_seq_compare);
+ shareable = cxl_group_is_shareable(mds, &group);

- if (cxl_validate_group(mds, &tag, &group)) {
+ if (cxl_validate_group(mds, &tag, &group, shareable)) {
drop_extent_group(&group);
continue;
}

- cnt = cxl_realize_group(mds, &tag, &group);
+ cnt = cxl_realize_group(mds, &tag, &group, shareable);
if (cnt < 0) {
drop_extent_group(&group);
continue;
--
2.43.0