| @@ -30,17 +30,20 @@ static void unregister_nvdimm(void *nvdimm) · 29 unmodified lines |
| 30 | 30 | struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm); |
| 31 | 31 | struct cxl_nvdimm_bridge *cxl_nvb = cxl_nvd->bridge; |
| 32 | 32 | struct cxl_pmem_region *cxlr_pmem; |
| 33 | + unsigned long index; |
| 33 | 34 | |
| 34 | 35 | device_lock(&cxl_nvb->dev); |
| 35 | | − cxlr_pmem = cxl_nvd->region; |
| 36 | 36 | 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); |
| 39 | 40 | |
| 40 | | − if (cxlr_pmem) { |
| 41 | 41 | device_release_driver(&cxlr_pmem->dev); |
| 42 | 42 | put_device(&cxlr_pmem->dev); |
| 43 | + |
| 44 | + device_lock(&cxl_nvb->dev); |
| 43 | 45 | } |
| 46 | + device_unlock(&cxl_nvb->dev); |
| 44 | 47 | |
| 45 | 48 | nvdimm_delete(nvdimm); |
| 46 | 49 | cxl_nvd->bridge = NULL; |
| @@ -366,25 +369,49 @@ static int match_cxl_nvdimm(struct device *dev, void *data) · 319 unmodified lines |
| 366 | 369 | |
| 367 | 370 | static void unregister_nvdimm_region(void *nd_region) |
| 368 | 371 | { |
| 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 | +{ |
| 371 | 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 | 407 | device_lock(&cxl_nvb->dev); |
| 376 | 408 | for (i = 0; i < cxlr_pmem->nr_mappings; i++) { |
| 377 | 409 | struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i]; |
| 378 | 410 | struct cxl_nvdimm *cxl_nvd = m->cxl_nvd; |
| 379 | 411 | |
| 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); |
| 384 | 413 | } |
| 385 | 414 | device_unlock(&cxl_nvb->dev); |
| 386 | | − |
| 387 | | − nvdimm_region_delete(nd_region); |
| 388 | 415 | } |
| 389 | 416 | |
| 390 | 417 | static void cxlr_pmem_remove_resource(void *res) |
| @@ -422,7 +449,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 31 unmodified lines |
| 422 | 449 | if (!cxl_nvb->nvdimm_bus) { |
| 423 | 450 | dev_dbg(dev, "nvdimm bus not found\n"); |
| 424 | 451 | rc = -ENXIO; |
| 425 | | − goto err; |
| 452 | + goto out_nvb; |
| 426 | 453 | } |
| 427 | 454 | |
| 428 | 455 | memset(&mappings, 0, sizeof(mappings)); |
| @@ -431,7 +458,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 2 unmodified lines |
| 431 | 458 | res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL); |
| 432 | 459 | if (!res) { |
| 433 | 460 | rc = -ENOMEM; |
| 434 | | − goto err; |
| 461 | + goto out_nvb; |
| 435 | 462 | } |
| 436 | 463 | |
| 437 | 464 | res->name = "Persistent Memory"; |
| @@ -442,11 +469,11 @@ static int cxl_pmem_region_probe(struct device *dev) · 4 unmodified lines |
| 442 | 469 | |
| 443 | 470 | rc = insert_resource(&iomem_resource, res); |
| 444 | 471 | if (rc) |
| 445 | | − goto err; |
| 472 | + goto out_nvb; |
| 446 | 473 | |
| 447 | 474 | rc = devm_add_action_or_reset(dev, cxlr_pmem_remove_resource, res); |
| 448 | 475 | if (rc) |
| 449 | | − goto err; |
| 476 | + goto out_nvb; |
| 450 | 477 | |
| 451 | 478 | ndr_desc.res = res; |
| 452 | 479 | ndr_desc.provider_data = cxlr_pmem; |
| @@ -462,7 +489,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 9 unmodified lines |
| 462 | 489 | nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL); |
| 463 | 490 | if (!nd_set) { |
| 464 | 491 | rc = -ENOMEM; |
| 465 | | − goto err; |
| 492 | + goto out_nvb; |
| 466 | 493 | } |
| 467 | 494 | |
| 468 | 495 | ndr_desc.memregion = cxlr->id; |
| @@ -472,9 +499,13 @@ static int cxl_pmem_region_probe(struct device *dev) · 3 unmodified lines |
| 472 | 499 | info = kmalloc_array(cxlr_pmem->nr_mappings, sizeof(*info), GFP_KERNEL); |
| 473 | 500 | if (!info) { |
| 474 | 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 | 509 | for (i = 0; i < cxlr_pmem->nr_mappings; i++) { |
| 479 | 510 | struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i]; |
| 480 | 511 | struct cxl_memdev *cxlmd = m->cxlmd; |
| @@ -486,7 +517,7 @@ static int cxl_pmem_region_probe(struct device *dev) · 5 unmodified lines |
| 486 | 517 | dev_dbg(dev, "[%d]: %s: no cxl_nvdimm found\n", i, |
| 487 | 518 | dev_name(&cxlmd->dev)); |
| 488 | 519 | rc = -ENODEV; |
| 489 | | − goto err; |
| 520 | + goto out_nvd; |
| 490 | 521 | } |
| 491 | 522 | |
| 492 | 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 | 529 | dev_dbg(dev, "[%d]: %s: no nvdimm found\n", i, |
| 499 | 530 | dev_name(&cxlmd->dev)); |
| 500 | 531 | rc = -ENODEV; |
| 501 | | − goto err; |
| 532 | + goto out_nvd; |
| 502 | 533 | } |
| 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; |
| 505 | 543 | m->cxl_nvd = cxl_nvd; |
| 506 | 544 | mappings[i] = (struct nd_mapping_desc) { |
| 507 | 545 | .nvdimm = nvdimm, |
| @@ -527,27 +565,18 @@ static int cxl_pmem_region_probe(struct device *dev) · 19 unmodified lines |
| 527 | 565 | nvdimm_pmem_region_create(cxl_nvb->nvdimm_bus, &ndr_desc); |
| 528 | 566 | if (!cxlr_pmem->nd_region) { |
| 529 | 567 | rc = -ENOMEM; |
| 530 | | − goto err; |
| 568 | + goto out_nvd; |
| 531 | 569 | } |
| 532 | 570 | |
| 533 | 571 | rc = devm_add_action_or_reset(dev, unregister_nvdimm_region, |
| 534 | 572 | cxlr_pmem->nd_region); |
| 535 | | −out: |
| 573 | +out_nvd: |
| 536 | 574 | kfree(info); |
| 575 | +out_nvb: |
| 537 | 576 | device_unlock(&cxl_nvb->dev); |
| 538 | 577 | put_device(&cxl_nvb->dev); |
| 539 | 578 | |
| 540 | 579 | 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; |
| 551 | 580 | } |
| 552 | 581 | |
| 553 | 582 | static struct cxl_driver cxl_pmem_region_driver = { |