History

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.

Dan Williams committed 4 years ago

Commit 4d07ae21 parent 0d9e7343 files · +68 −37Browse files

Unified · Split

3 files
  1. drivers/cxl/core/pmem.c +2 −0
  2. drivers/cxl/cxl.h +1 −1
  3. drivers/cxl/pmem.c +65 −36
drivers/cxl/core/pmem.c+2−0
@@ -188,6 +188,7 @@ static void cxl_nvdimm_release(struct device *dev) · 187 unmodified lines
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) · 36 unmodified lines
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+1−1
@@ -423,7 +423,7 @@ struct cxl_nvdimm { · 422 unmodified lines
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+65−36
@@ -30,17 +30,20 @@ static void unregister_nvdimm(void *nvdimm) · 29 unmodified lines
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) · 319 unmodified lines
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) · 31 unmodified lines
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) · 2 unmodified lines
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) · 4 unmodified lines
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) · 9 unmodified lines
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) · 3 unmodified lines
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) · 5 unmodified lines
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) · 5 unmodified lines
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) · 19 unmodified lines
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 = {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t