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
188{188{
189 struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);189 struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);
190 190
191 xa_destroy(&cxl_nvd->pmem_regions);
191 kfree(cxl_nvd);192 kfree(cxl_nvd);
192}193}
193 194
@@ -230,6 +231,7 @@ static struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_memdev *cxlmd) · 36 unmodified lines
230 231
231 dev = &cxl_nvd->dev;232 dev = &cxl_nvd->dev;
232 cxl_nvd->cxlmd = cxlmd;233 cxl_nvd->cxlmd = cxlmd;
234 xa_init(&cxl_nvd->pmem_regions);
233 device_initialize(dev);235 device_initialize(dev);
234 lockdep_set_class(&dev->mutex, &cxl_nvdimm_key);236 lockdep_set_class(&dev->mutex, &cxl_nvdimm_key);
235 device_set_pm_not_required(dev);237 device_set_pm_not_required(dev);
drivers/cxl/cxl.h+1−1
@@ -423,7 +423,7 @@ struct cxl_nvdimm { · 422 unmodified lines
423 struct device dev;423 struct device dev;
424 struct cxl_memdev *cxlmd;424 struct cxl_memdev *cxlmd;
425 struct cxl_nvdimm_bridge *bridge;425 struct cxl_nvdimm_bridge *bridge;
426 struct cxl_pmem_region *region;426 struct xarray pmem_regions;
427};427};
428 428
429struct cxl_pmem_region_mapping {429struct cxl_pmem_region_mapping {
drivers/cxl/pmem.c+65−36
@@ -30,17 +30,20 @@ static void unregister_nvdimm(void *nvdimm) · 29 unmodified lines
30 struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);30 struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
31 struct cxl_nvdimm_bridge *cxl_nvb = cxl_nvd->bridge;31 struct cxl_nvdimm_bridge *cxl_nvb = cxl_nvd->bridge;
32 struct cxl_pmem_region *cxlr_pmem;32 struct cxl_pmem_region *cxlr_pmem;
33 unsigned long index;
33 34
34 device_lock(&cxl_nvb->dev);35 device_lock(&cxl_nvb->dev);
35 cxlr_pmem = cxl_nvd->region;
36 dev_set_drvdata(&cxl_nvd->dev, NULL);36 dev_set_drvdata(&cxl_nvd->dev, NULL);
37 cxl_nvd->region = NULL;37 xa_for_each(&cxl_nvd->pmem_regions, index, cxlr_pmem) {
38 device_unlock(&cxl_nvb->dev);38 get_device(&cxlr_pmem->dev);
39 device_unlock(&cxl_nvb->dev);
39 40
40 if (cxlr_pmem) {
41 device_release_driver(&cxlr_pmem->dev);41 device_release_driver(&cxlr_pmem->dev);
42 put_device(&cxlr_pmem->dev);42 put_device(&cxlr_pmem->dev);
43
44 device_lock(&cxl_nvb->dev);
43 }45 }
46 device_unlock(&cxl_nvb->dev);
44 47
45 nvdimm_delete(nvdimm);48 nvdimm_delete(nvdimm);
46 cxl_nvd->bridge = NULL;49 cxl_nvd->bridge = NULL;
@@ -366,25 +369,49 @@ static int match_cxl_nvdimm(struct device *dev, void *data) · 319 unmodified lines
366 369
367static void unregister_nvdimm_region(void *nd_region)370static void unregister_nvdimm_region(void *nd_region)
368{371{
369 struct cxl_nvdimm_bridge *cxl_nvb;372 nvdimm_region_delete(nd_region);
370 struct cxl_pmem_region *cxlr_pmem;373}
374
375static 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
389static 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
401static void release_mappings(void *data)
402{
371 int i;403 int i;
404 struct cxl_pmem_region *cxlr_pmem = data;
405 struct cxl_nvdimm_bridge *cxl_nvb = cxlr_pmem->bridge;
372 406
373 cxlr_pmem = nd_region_provider_data(nd_region);
374 cxl_nvb = cxlr_pmem->bridge;
375 device_lock(&cxl_nvb->dev);407 device_lock(&cxl_nvb->dev);
376 for (i = 0; i < cxlr_pmem->nr_mappings; i++) {408 for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
377 struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];409 struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
378 struct cxl_nvdimm *cxl_nvd = m->cxl_nvd;410 struct cxl_nvdimm *cxl_nvd = m->cxl_nvd;
379 411
380 if (cxl_nvd->region) {412 cxl_nvdimm_del_region(cxl_nvd, cxlr_pmem);
381 put_device(&cxlr_pmem->dev);
382 cxl_nvd->region = NULL;
383 }
384 }413 }
385 device_unlock(&cxl_nvb->dev);414 device_unlock(&cxl_nvb->dev);
386
387 nvdimm_region_delete(nd_region);
388}415}
389 416
390static void cxlr_pmem_remove_resource(void *res)417static void cxlr_pmem_remove_resource(void *res)
@@ -422,7 +449,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 31 unmodified lines
422 if (!cxl_nvb->nvdimm_bus) {449 if (!cxl_nvb->nvdimm_bus) {
423 dev_dbg(dev, "nvdimm bus not found\n");450 dev_dbg(dev, "nvdimm bus not found\n");
424 rc = -ENXIO;451 rc = -ENXIO;
425 goto err;452 goto out_nvb;
426 }453 }
427 454
428 memset(&mappings, 0, sizeof(mappings));455 memset(&mappings, 0, sizeof(mappings));
@@ -431,7 +458,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 2 unmodified lines
431 res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);458 res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);
432 if (!res) {459 if (!res) {
433 rc = -ENOMEM;460 rc = -ENOMEM;
434 goto err;461 goto out_nvb;
435 }462 }
436 463
437 res->name = "Persistent Memory";464 res->name = "Persistent Memory";
@@ -442,11 +469,11 @@ static int cxl_pmem_region_probe(struct device *dev) · 4 unmodified lines
442 469
443 rc = insert_resource(&iomem_resource, res);470 rc = insert_resource(&iomem_resource, res);
444 if (rc)471 if (rc)
445 goto err;472 goto out_nvb;
446 473
447 rc = devm_add_action_or_reset(dev, cxlr_pmem_remove_resource, res);474 rc = devm_add_action_or_reset(dev, cxlr_pmem_remove_resource, res);
448 if (rc)475 if (rc)
449 goto err;476 goto out_nvb;
450 477
451 ndr_desc.res = res;478 ndr_desc.res = res;
452 ndr_desc.provider_data = cxlr_pmem;479 ndr_desc.provider_data = cxlr_pmem;
@@ -462,7 +489,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 9 unmodified lines
462 nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL);489 nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL);
463 if (!nd_set) {490 if (!nd_set) {
464 rc = -ENOMEM;491 rc = -ENOMEM;
465 goto err;492 goto out_nvb;
466 }493 }
467 494
468 ndr_desc.memregion = cxlr->id;495 ndr_desc.memregion = cxlr->id;
@@ -472,9 +499,13 @@ static int cxl_pmem_region_probe(struct device *dev) · 3 unmodified lines
472 info = kmalloc_array(cxlr_pmem->nr_mappings, sizeof(*info), GFP_KERNEL);499 info = kmalloc_array(cxlr_pmem->nr_mappings, sizeof(*info), GFP_KERNEL);
473 if (!info) {500 if (!info) {
474 rc = -ENOMEM;501 rc = -ENOMEM;
475 goto err;502 goto out_nvb;
476 }503 }
477 504
505 rc = devm_add_action_or_reset(dev, release_mappings, cxlr_pmem);
506 if (rc)
507 goto out_nvd;
508
478 for (i = 0; i < cxlr_pmem->nr_mappings; i++) {509 for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
479 struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];510 struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
480 struct cxl_memdev *cxlmd = m->cxlmd;511 struct cxl_memdev *cxlmd = m->cxlmd;
@@ -486,7 +517,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 5 unmodified lines
486 dev_dbg(dev, "[%d]: %s: no cxl_nvdimm found\n", i,517 dev_dbg(dev, "[%d]: %s: no cxl_nvdimm found\n", i,
487 dev_name(&cxlmd->dev));518 dev_name(&cxlmd->dev));
488 rc = -ENODEV;519 rc = -ENODEV;
489 goto err;520 goto out_nvd;
490 }521 }
491 522
492 /* safe to drop ref now with bridge lock held */523 /* 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
498 dev_dbg(dev, "[%d]: %s: no nvdimm found\n", i,529 dev_dbg(dev, "[%d]: %s: no nvdimm found\n", i,
499 dev_name(&cxlmd->dev));530 dev_name(&cxlmd->dev));
500 rc = -ENODEV;531 rc = -ENODEV;
501 goto err;532 goto out_nvd;
502 }533 }
503 cxl_nvd->region = cxlr_pmem;534
504 get_device(&cxlr_pmem->dev);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;
505 m->cxl_nvd = cxl_nvd;543 m->cxl_nvd = cxl_nvd;
506 mappings[i] = (struct nd_mapping_desc) {544 mappings[i] = (struct nd_mapping_desc) {
507 .nvdimm = nvdimm,545 .nvdimm = nvdimm,
@@ -527,27 +565,18 @@ static int cxl_pmem_region_probe(struct device *dev) · 19 unmodified lines
527 nvdimm_pmem_region_create(cxl_nvb->nvdimm_bus, &ndr_desc);565 nvdimm_pmem_region_create(cxl_nvb->nvdimm_bus, &ndr_desc);
528 if (!cxlr_pmem->nd_region) {566 if (!cxlr_pmem->nd_region) {
529 rc = -ENOMEM;567 rc = -ENOMEM;
530 goto err;568 goto out_nvd;
531 }569 }
532 570
533 rc = devm_add_action_or_reset(dev, unregister_nvdimm_region,571 rc = devm_add_action_or_reset(dev, unregister_nvdimm_region,
534 cxlr_pmem->nd_region);572 cxlr_pmem->nd_region);
535out:573out_nvd:
536 kfree(info);574 kfree(info);
575out_nvb:
537 device_unlock(&cxl_nvb->dev);576 device_unlock(&cxl_nvb->dev);
538 put_device(&cxl_nvb->dev);577 put_device(&cxl_nvb->dev);
539 578
540 return rc;579 return rc;
541
542err:
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;
551}580}
552 581
553static struct cxl_driver cxl_pmem_region_driver = {582static 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