Skip to content

Commit 4d07ae2

Browse files
committed
cxl/pmem: Fix cxl_pmem_region and cxl_memdev leak
When a cxl_nvdimm object goes through a ->remove() event (device physically removed, nvdimm-bridge disabled, or nvdimm device disabled), then any associated regions must also be disabled. As highlighted by the cxl-create-region.sh test [1], a single device may host multiple regions, but the driver was only tracking one region at a time. This leads to a situation where only the last enabled region per nvdimm device is cleaned up properly. Other regions are leaked, and this also causes cxl_memdev reference leaks. Fix the tracking by allowing cxl_nvdimm objects to track multiple region associations. Cc: <[email protected]> Link: https://github.com/pmem/ndctl/blob/main/test/cxl-create-region.sh [1] Reported-by: Vishal Verma <[email protected]> Fixes: 04ad63f ("cxl/region: Introduce cxl_pmem_region objects") Reviewed-by: Dave Jiang <[email protected]> Reviewed-by: Vishal Verma <[email protected]> Link: https://lore.kernel.org/r/166752183647.947915.2045230911503793901.stgit@dwillia2-xfh.jf.intel.com Signed-off-by: Dan Williams <[email protected]>
1 parent 0d9e734 commit 4d07ae2

File tree

3 files changed

+68
-37
lines changed

3 files changed

+68
-37
lines changed

drivers/cxl/core/pmem.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ static void cxl_nvdimm_release(struct device *dev)
188188
{
189189
struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);
190190

191+
xa_destroy(&cxl_nvd->pmem_regions);
191192
kfree(cxl_nvd);
192193
}
193194

@@ -230,6 +231,7 @@ static struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_memdev *cxlmd)
230231

231232
dev = &cxl_nvd->dev;
232233
cxl_nvd->cxlmd = cxlmd;
234+
xa_init(&cxl_nvd->pmem_regions);
233235
device_initialize(dev);
234236
lockdep_set_class(&dev->mutex, &cxl_nvdimm_key);
235237
device_set_pm_not_required(dev);

drivers/cxl/cxl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ struct cxl_nvdimm {
423423
struct device dev;
424424
struct cxl_memdev *cxlmd;
425425
struct cxl_nvdimm_bridge *bridge;
426-
struct cxl_pmem_region *region;
426+
struct xarray pmem_regions;
427427
};
428428

429429
struct cxl_pmem_region_mapping {

drivers/cxl/pmem.c

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,20 @@ static void unregister_nvdimm(void *nvdimm)
3030
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
3131
struct cxl_nvdimm_bridge *cxl_nvb = cxl_nvd->bridge;
3232
struct cxl_pmem_region *cxlr_pmem;
33+
unsigned long index;
3334

3435
device_lock(&cxl_nvb->dev);
35-
cxlr_pmem = cxl_nvd->region;
3636
dev_set_drvdata(&cxl_nvd->dev, NULL);
37-
cxl_nvd->region = NULL;
38-
device_unlock(&cxl_nvb->dev);
37+
xa_for_each(&cxl_nvd->pmem_regions, index, cxlr_pmem) {
38+
get_device(&cxlr_pmem->dev);
39+
device_unlock(&cxl_nvb->dev);
3940

40-
if (cxlr_pmem) {
4141
device_release_driver(&cxlr_pmem->dev);
4242
put_device(&cxlr_pmem->dev);
43+
44+
device_lock(&cxl_nvb->dev);
4345
}
46+
device_unlock(&cxl_nvb->dev);
4447

4548
nvdimm_delete(nvdimm);
4649
cxl_nvd->bridge = NULL;
@@ -366,25 +369,49 @@ static int match_cxl_nvdimm(struct device *dev, void *data)
366369

367370
static void unregister_nvdimm_region(void *nd_region)
368371
{
369-
struct cxl_nvdimm_bridge *cxl_nvb;
370-
struct cxl_pmem_region *cxlr_pmem;
372+
nvdimm_region_delete(nd_region);
373+
}
374+
375+
static int cxl_nvdimm_add_region(struct cxl_nvdimm *cxl_nvd,
376+
struct cxl_pmem_region *cxlr_pmem)
377+
{
378+
int rc;
379+
380+
rc = xa_insert(&cxl_nvd->pmem_regions, (unsigned long)cxlr_pmem,
381+
cxlr_pmem, GFP_KERNEL);
382+
if (rc)
383+
return rc;
384+
385+
get_device(&cxlr_pmem->dev);
386+
return 0;
387+
}
388+
389+
static void cxl_nvdimm_del_region(struct cxl_nvdimm *cxl_nvd,
390+
struct cxl_pmem_region *cxlr_pmem)
391+
{
392+
/*
393+
* It is possible this is called without a corresponding
394+
* cxl_nvdimm_add_region for @cxlr_pmem
395+
*/
396+
cxlr_pmem = xa_erase(&cxl_nvd->pmem_regions, (unsigned long)cxlr_pmem);
397+
if (cxlr_pmem)
398+
put_device(&cxlr_pmem->dev);
399+
}
400+
401+
static void release_mappings(void *data)
402+
{
371403
int i;
404+
struct cxl_pmem_region *cxlr_pmem = data;
405+
struct cxl_nvdimm_bridge *cxl_nvb = cxlr_pmem->bridge;
372406

373-
cxlr_pmem = nd_region_provider_data(nd_region);
374-
cxl_nvb = cxlr_pmem->bridge;
375407
device_lock(&cxl_nvb->dev);
376408
for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
377409
struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
378410
struct cxl_nvdimm *cxl_nvd = m->cxl_nvd;
379411

380-
if (cxl_nvd->region) {
381-
put_device(&cxlr_pmem->dev);
382-
cxl_nvd->region = NULL;
383-
}
412+
cxl_nvdimm_del_region(cxl_nvd, cxlr_pmem);
384413
}
385414
device_unlock(&cxl_nvb->dev);
386-
387-
nvdimm_region_delete(nd_region);
388415
}
389416

390417
static void cxlr_pmem_remove_resource(void *res)
@@ -422,7 +449,7 @@ static int cxl_pmem_region_probe(struct device *dev)
422449
if (!cxl_nvb->nvdimm_bus) {
423450
dev_dbg(dev, "nvdimm bus not found\n");
424451
rc = -ENXIO;
425-
goto err;
452+
goto out_nvb;
426453
}
427454

428455
memset(&mappings, 0, sizeof(mappings));
@@ -431,7 +458,7 @@ static int cxl_pmem_region_probe(struct device *dev)
431458
res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);
432459
if (!res) {
433460
rc = -ENOMEM;
434-
goto err;
461+
goto out_nvb;
435462
}
436463

437464
res->name = "Persistent Memory";
@@ -442,11 +469,11 @@ static int cxl_pmem_region_probe(struct device *dev)
442469

443470
rc = insert_resource(&iomem_resource, res);
444471
if (rc)
445-
goto err;
472+
goto out_nvb;
446473

447474
rc = devm_add_action_or_reset(dev, cxlr_pmem_remove_resource, res);
448475
if (rc)
449-
goto err;
476+
goto out_nvb;
450477

451478
ndr_desc.res = res;
452479
ndr_desc.provider_data = cxlr_pmem;
@@ -462,7 +489,7 @@ static int cxl_pmem_region_probe(struct device *dev)
462489
nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL);
463490
if (!nd_set) {
464491
rc = -ENOMEM;
465-
goto err;
492+
goto out_nvb;
466493
}
467494

468495
ndr_desc.memregion = cxlr->id;
@@ -472,9 +499,13 @@ static int cxl_pmem_region_probe(struct device *dev)
472499
info = kmalloc_array(cxlr_pmem->nr_mappings, sizeof(*info), GFP_KERNEL);
473500
if (!info) {
474501
rc = -ENOMEM;
475-
goto err;
502+
goto out_nvb;
476503
}
477504

505+
rc = devm_add_action_or_reset(dev, release_mappings, cxlr_pmem);
506+
if (rc)
507+
goto out_nvd;
508+
478509
for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
479510
struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
480511
struct cxl_memdev *cxlmd = m->cxlmd;
@@ -486,7 +517,7 @@ static int cxl_pmem_region_probe(struct device *dev)
486517
dev_dbg(dev, "[%d]: %s: no cxl_nvdimm found\n", i,
487518
dev_name(&cxlmd->dev));
488519
rc = -ENODEV;
489-
goto err;
520+
goto out_nvd;
490521
}
491522

492523
/* safe to drop ref now with bridge lock held */
@@ -498,10 +529,17 @@ static int cxl_pmem_region_probe(struct device *dev)
498529
dev_dbg(dev, "[%d]: %s: no nvdimm found\n", i,
499530
dev_name(&cxlmd->dev));
500531
rc = -ENODEV;
501-
goto err;
532+
goto out_nvd;
502533
}
503-
cxl_nvd->region = cxlr_pmem;
504-
get_device(&cxlr_pmem->dev);
534+
535+
/*
536+
* Pin the region per nvdimm device as those may be released
537+
* out-of-order with respect to the region, and a single nvdimm
538+
* maybe associated with multiple regions
539+
*/
540+
rc = cxl_nvdimm_add_region(cxl_nvd, cxlr_pmem);
541+
if (rc)
542+
goto out_nvd;
505543
m->cxl_nvd = cxl_nvd;
506544
mappings[i] = (struct nd_mapping_desc) {
507545
.nvdimm = nvdimm,
@@ -527,27 +565,18 @@ static int cxl_pmem_region_probe(struct device *dev)
527565
nvdimm_pmem_region_create(cxl_nvb->nvdimm_bus, &ndr_desc);
528566
if (!cxlr_pmem->nd_region) {
529567
rc = -ENOMEM;
530-
goto err;
568+
goto out_nvd;
531569
}
532570

533571
rc = devm_add_action_or_reset(dev, unregister_nvdimm_region,
534572
cxlr_pmem->nd_region);
535-
out:
573+
out_nvd:
536574
kfree(info);
575+
out_nvb:
537576
device_unlock(&cxl_nvb->dev);
538577
put_device(&cxl_nvb->dev);
539578

540579
return rc;
541-
542-
err:
543-
dev_dbg(dev, "failed to create nvdimm region\n");
544-
for (i--; i >= 0; i--) {
545-
nvdimm = mappings[i].nvdimm;
546-
cxl_nvd = nvdimm_provider_data(nvdimm);
547-
put_device(&cxl_nvd->region->dev);
548-
cxl_nvd->region = NULL;
549-
}
550-
goto out;
551580
}
552581

553582
static struct cxl_driver cxl_pmem_region_driver = {

0 commit comments

Comments
 (0)