The previous commit fully-fleshed out validation for individual extents.
This commit completes tag-group validation.
Add two group-level gates to cxl_add_pending() that
cxl_validate_extent()'s per-extent view can't see:
- 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 is a
firmware bug.
Non-sharable partitions leave the field reserved and are not checked.
- 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.
Signed-off-by: Ira Weiny <[email protected]>
Signed-off-by: John Groves <[email protected]>
Signed-off-by: Anisa Su <[email protected]>
---
Changes:
1. Fix sequence number check
Previously, it was assumed that shared extent sequence nums
start at 1, which is wrong. The spec states "For extents
describing shareable regions this field shall be within the range
of 0 to n-1 where n is the number of extents, with each value
appearing only once." Fix to start at 0.
2. Use partition->shareable attribute (from DSMAS flags) as the
source of truth for extent shareability, not sequence num.
Not only was it assumed sequence nums start at 1 for shared extents,
it was also assumed that unshared extents have a sequence num of 0.
So seq num was used to determine if an extent was shareable, which is
wrong.
---
drivers/cxl/core/core.h | 4 +
drivers/cxl/core/extent.c | 2 +-
drivers/cxl/core/mbox.c | 168 +++++++++++++++++++++++++++++++++-----
3 files changed, 154 insertions(+), 20 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 2c1df75ebbc5..6ac68f46a18e 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -178,6 +178,10 @@ int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct
access_coordinate *c);
int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
struct access_coordinate *c);
void memdev_release_extent(struct cxl_memdev_state *mds, struct range *range);
+const struct cxl_dpa_partition *
+cxl_extent_dc_partition(struct cxl_memdev_state *mds,
+ struct cxl_extent *extent,
+ struct range *ext_range);
static inline struct device *port_to_host(struct cxl_port *port)
{
diff --git a/drivers/cxl/core/extent.c b/drivers/cxl/core/extent.c
index 2e770c5279c2..0ebb581ca833 100644
--- a/drivers/cxl/core/extent.c
+++ b/drivers/cxl/core/extent.c
@@ -82,7 +82,7 @@ alloc_tag_group(struct cxl_dax_region *cxlr_dax, uuid_t *uuid)
* The returned pointer is owned by mds->cxlds.part[] and lives for the
* lifetime of the memdev.
*/
-static const struct cxl_dpa_partition *
+const struct cxl_dpa_partition *
cxl_extent_dc_partition(struct cxl_memdev_state *mds,
struct cxl_extent *extent,
struct range *ext_range)
diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c
index 14ba263044f0..7967b0db2c51 100644
--- a/drivers/cxl/core/mbox.c
+++ b/drivers/cxl/core/mbox.c
@@ -1362,28 +1362,148 @@ static void drop_extent_group(struct list_head *group)
}
/*
- * Realize a tag @group: add each extent via cxl_add_extent(), then online
- * the resulting tag group. Returns the number of accepted extents (>= 0)
- * with @group left holding them for the caller to splice, or a negative
- * errno on failure with @group untouched for the caller to drop.
+ * 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 partitions leave shared_extn_seq reserved; sharability is
+ * determined by the partition, not the seq value, so there is nothing to
+ * validate here — the caller assigns assembly order by arrival.
+ */
+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(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(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
+ * number of accepted extents (>= 0) with @group left holding them for the
+ * caller to splice, or a negative errno on failure with @group untouched for
+ * the caller to drop.
+ *
+ * A shared extent carries the device-assigned shared_extn_seq (dense 0..n-1).
+ * Non-sharable groups have no meaningful per-extent sequence, so number them
+ * by arrival order. The counter advances for every member so a failed add
+ * leaves a gap and the partial group is later refused rather than carved.
*/
static int cxl_realize_group(struct cxl_memdev_state *mds, const uuid_t *tag,
- struct list_head *group, bool existing)
+ struct list_head *group, bool shareable,
+ bool existing)
{
struct device *dev = mds->cxlds.dev;
struct cxl_extent_list_node *pos, *tmp;
struct cxl_dc_tag_group *tag_group;
int group_cnt = 0;
+ u16 seq_num;
int rc;
+ seq_num = -1;
list_for_each_entry_safe(pos, tmp, group, list) {
- /*
- * Pass the device-stamped 0-based shared_extn_seq through
- * unchanged as the dax-side @seq_num (0..n-1).
- */
- u16 seq = le16_to_cpu(pos->extent->shared_extn_seq);
+ if (shareable)
+ seq_num = le16_to_cpu(pos->extent->shared_extn_seq);
+ else
+ seq_num++;
- if (cxl_add_extent(mds, pos->extent, seq) < 0) {
+ if (cxl_add_extent(mds, pos->extent, seq_num) < 0) {
dev_dbg(dev,
"Tag %pUb: failed to add extent DPA:%#llx
LEN:%#llx\n",
tag,
@@ -1412,15 +1532,22 @@ static int cxl_realize_group(struct cxl_memdev_state
*mds, const uuid_t *tag,
/*
* Validate a tag @group before realizing it. Returns 0 if the group may be
- * added, or a negative errno if it must be dropped. Further gates layer in
- * here in later commits.
+ * 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)) {
@@ -1439,9 +1566,10 @@ static int cxl_validate_group(struct cxl_memdev_state
*mds, const uuid_t *tag,
/*
* Drive the pending Add-Capacity records through cxl_realize_group(),
- * grouped by tag. Per group: extract from pending, stable-sort by
- * shared_extn_seq, validate, realize the group, and on success move it onto
- * the accepted list.
+ * grouped by tag. Per group: extract from pending; for a sharable partition
+ * stable-sort by the device's shared_extn_seq (non-sharable groups keep
+ * arrival order), validate, then realize the group, moving it onto the
+ * accepted list on success.
*/
static int cxl_add_pending(struct cxl_memdev_state *mds, bool existing)
{
@@ -1451,6 +1579,7 @@ static int cxl_add_pending(struct cxl_memdev_state *mds,
bool existing)
while (!list_empty(pending)) {
LIST_HEAD(group);
+ bool shareable;
uuid_t tag;
int cnt;
@@ -1466,13 +1595,14 @@ static int cxl_add_pending(struct cxl_memdev_state
*mds, bool existing)
* 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, existing);
+ cnt = cxl_realize_group(mds, &tag, &group, shareable, existing);
if (cnt < 0) {
drop_extent_group(&group);
continue;
--
2.43.0