The per-region scans in cxlr_add_extent() and uuid_claim_tagged() only catch a tag re-appearing on the same cxlr_dax. The orchestrator owns tag allocation and is responsible for global uniqueness, but a buggy FM (or firmware redelivering a tag for a previously-closed allocation) can still hand the same uuid to extents on two different regions or memdevs, and the per-region checks accept the second one — leaving two independent cxl_dc_tag_group objects with the same uuid.
Add a host-wide registry of live tag groups with non-null uuids. alloc_tag_group() inserts on success, free_tag_group() removes; both skip the null-uuid case since the spec defines no cross-chain identity for untagged allocations. A second group with the same uuid is then rejected: cxl_validate_group() consults the registry via cxl_tag_already_committed() and returns -EEXIST before the group is realized, and cxl_tag_register() returns -EBUSY as a backstop against a racing insert between validate and realize. No exit hook is needed: cxl_core only unloads after every dependent module has, by which point every live tag group has been freed and the registry is empty. Signed-off-by: Anisa Su <[email protected]> Reviewed-by: Dave Jiang <[email protected]> --- drivers/cxl/core/core.h | 5 ++++ drivers/cxl/core/extent.c | 59 +++++++++++++++++++++++++++++++++++++++ drivers/cxl/core/mbox.c | 16 +++++++++++ drivers/cxl/cxl.h | 3 ++ 4 files changed, 83 insertions(+) diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index bbbb86ababad..ab75cc67c24d 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -67,6 +67,7 @@ int devm_cxl_add_pmem_region(struct cxl_region *cxlr); int cxl_add_extent(struct cxl_memdev_state *mds, struct cxl_extent *extent, u16 seq_num); +bool cxl_tag_already_committed(const uuid_t *tag); int cxl_rm_extent(struct cxl_memdev_state *mds, struct cxl_extent *extent); int online_tag_group(struct cxl_dc_tag_group *group, bool skip_release); #else @@ -90,6 +91,10 @@ static inline int online_tag_group(struct cxl_dc_tag_group *group, { return 0; } +static inline bool cxl_tag_already_committed(const uuid_t *tag) +{ + return false; +} static inline struct cxl_region *cxl_dpa_to_region(const struct cxl_memdev *cxlmd, u64 dpa, struct cxl_endpoint_decoder **cxled) diff --git a/drivers/cxl/core/extent.c b/drivers/cxl/core/extent.c index a590a89f3580..36be56ca1097 100644 --- a/drivers/cxl/core/extent.c +++ b/drivers/cxl/core/extent.c @@ -18,8 +18,60 @@ static void cxled_release_extent(struct cxl_endpoint_decoder *cxled, memdev_release_extent(mds, &dc_extent->dpa_range); } +/* + * Host-wide registry of live tag groups with non-null uuids. Enforces + * that within this host, a tag uuid identifies exactly one allocation + * across all regions and memdevs — closing the gap left by the + * per-region scans in cxlr_add_extent() and uuid_claim_tagged(). The + * orchestrator (FM) owns tag-uuid allocation per spec; this is a + * defense against firmware bugs and orchestrator misbehavior. Untagged + * (null uuid) allocations are not tracked: the spec defines no + * cross-chain identity for them. + */ +static DEFINE_MUTEX(cxl_tag_lock); +static LIST_HEAD(cxl_tag_groups); + +static int cxl_tag_register(struct cxl_dc_tag_group *grp) +{ + struct cxl_dc_tag_group *g; + + if (uuid_is_null(&grp->uuid)) + return 0; + + guard(mutex)(&cxl_tag_lock); + list_for_each_entry(g, &cxl_tag_groups, registry_node) + if (uuid_equal(&g->uuid, &grp->uuid)) + return -EBUSY; + list_add_tail(&grp->registry_node, &cxl_tag_groups); + return 0; +} + +static void cxl_tag_unregister(struct cxl_dc_tag_group *grp) +{ + if (uuid_is_null(&grp->uuid)) + return; + + guard(mutex)(&cxl_tag_lock); + list_del(&grp->registry_node); +} + +bool cxl_tag_already_committed(const uuid_t *tag) +{ + struct cxl_dc_tag_group *g; + + if (uuid_is_null(tag)) + return false; + + guard(mutex)(&cxl_tag_lock); + list_for_each_entry(g, &cxl_tag_groups, registry_node) + if (uuid_equal(&g->uuid, tag)) + return true; + return false; +} + static void free_tag_group(struct cxl_dc_tag_group *group) { + cxl_tag_unregister(group); xa_destroy(&group->dc_extents); /* Drop the pin taken in alloc_tag_group(). */ put_device(&group->cxlr_dax->dev); @@ -60,12 +112,19 @@ alloc_tag_group(struct cxl_dax_region *cxlr_dax, uuid_t *uuid) { struct cxl_dc_tag_group *group __free(kfree) = kzalloc(sizeof(*group), GFP_KERNEL); + int rc; + if (!group) return ERR_PTR(-ENOMEM); group->cxlr_dax = cxlr_dax; uuid_copy(&group->uuid, uuid); xa_init(&group->dc_extents); + INIT_LIST_HEAD(&group->registry_node); + + rc = cxl_tag_register(group); + if (rc) + return ERR_PTR(rc); /* * Pin cxlr_dax: it is used after cxl_rwsem.region is dropped, so a diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index a072355f2f7c..0e6d6ad0390b 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -1540,6 +1540,22 @@ static int cxl_validate_group(struct cxl_memdev_state *mds, const uuid_t *tag, struct device *dev = mds->cxlds.dev; struct cxl_extent_list_node *pos; + /* + * Cross-More-chain uniqueness. A non-null tag seen in this group must + * not already correspond to a committed tag group anywhere on this + * host. More=0 was supposed to close that allocation, and tag uuids + * must be unique across all regions and memdevs (the orchestrator owns + * assignment per spec). Either constraint failing — same chain + * redelivered, or two distinct allocations colliding on the same uuid — + * is a firmware/orchestrator bug; reject the whole group. + */ + if (cxl_tag_already_committed(tag)) { + dev_warn(dev, + "Tag %pUb: dropping group, tag already committed (firmware/orchestrator bug)\n", + tag); + return -EEXIST; + } + /* Sequence-number integrity */ if (cxl_check_group_seq(dev, tag, group, shareable)) return -EINVAL; diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h index aae7eecd191a..e82d8bf1388b 100644 --- a/drivers/cxl/cxl.h +++ b/drivers/cxl/cxl.h @@ -598,6 +598,8 @@ struct cxl_dax_region { * allocations. * @nr_extents: live count of dc_extents in the group; the group is freed * when the last dc_extent device is released. + * @registry_node: anchor in the host-wide non-null-tag registry that + * enforces tag uuid uniqueness across all regions and memdevs. * @skip_device_release: tear the group down without sending a Release DC * command to the device. Set when rejecting a group whose * extents this host never accepted, so they are omitted from the @@ -609,6 +611,7 @@ struct cxl_dc_tag_group { uuid_t uuid; struct xarray dc_extents; unsigned int nr_extents; + struct list_head registry_node; bool skip_device_release; }; -- 2.43.0

