diff options
author | Dave Jiang <dave.jiang@intel.com> | 2017-04-13 17:25:17 -0400 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2017-04-13 17:23:51 -0400 |
commit | b3b454f694db663773bc22002e10909afe9c1739 (patch) | |
tree | cca98ec1b74eb5b95569476bec8782d79aa04829 | |
parent | efebc711180f7fed701f6e23f23814fcfda7fbfc (diff) |
libnvdimm: fix clear poison locking with spinlock and GFP_NOWAIT allocation
The following warning results from holding a lane spinlock,
preempt_disable(), or the btt map spinlock and then trying to take the
reconfig_mutex to walk the poison list and potentially add new entries.
BUG: sleeping function called from invalid context at kernel/locking/mutex.
c:747
in_atomic(): 1, irqs_disabled(): 0, pid: 17159, name: dd
[..]
Call Trace:
dump_stack+0x85/0xc8
___might_sleep+0x184/0x250
__might_sleep+0x4a/0x90
__mutex_lock+0x58/0x9b0
? nvdimm_bus_lock+0x21/0x30 [libnvdimm]
? __nvdimm_bus_badblocks_clear+0x2f/0x60 [libnvdimm]
? acpi_nfit_forget_poison+0x79/0x80 [nfit]
? _raw_spin_unlock+0x27/0x40
mutex_lock_nested+0x1b/0x20
nvdimm_bus_lock+0x21/0x30 [libnvdimm]
nvdimm_forget_poison+0x25/0x50 [libnvdimm]
nvdimm_clear_poison+0x106/0x140 [libnvdimm]
nsio_rw_bytes+0x164/0x270 [libnvdimm]
btt_write_pg+0x1de/0x3e0 [nd_btt]
? blk_queue_enter+0x30/0x290
btt_make_request+0x11a/0x310 [nd_btt]
? blk_queue_enter+0xb7/0x290
? blk_queue_enter+0x30/0x290
generic_make_request+0x118/0x3b0
A spinlock is introduced to protect the poison list. This allows us to not
having to acquire the reconfig_mutex for touching the poison list. The
add_poison() function has been broken out into two helper functions. One to
allocate the poison entry and the other to apppend the entry. This allows us
to unlock the poison_lock in non-I/O path and continue to be able to allocate
the poison entry with GFP_KERNEL. We will use GFP_NOWAIT in the I/O path in
order to satisfy being in atomic context.
Reviewed-by: Vishal Verma <vishal.l.verma@intel.com>
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
-rw-r--r-- | drivers/nvdimm/bus.c | 7 | ||||
-rw-r--r-- | drivers/nvdimm/core.c | 56 | ||||
-rw-r--r-- | drivers/nvdimm/nd-core.h | 1 | ||||
-rw-r--r-- | include/linux/libnvdimm.h | 2 |
4 files changed, 38 insertions, 28 deletions
diff --git a/drivers/nvdimm/bus.c b/drivers/nvdimm/bus.c index 5ad2e5909e1a..d214ac44d111 100644 --- a/drivers/nvdimm/bus.c +++ b/drivers/nvdimm/bus.c | |||
@@ -296,6 +296,7 @@ struct nvdimm_bus *nvdimm_bus_register(struct device *parent, | |||
296 | init_waitqueue_head(&nvdimm_bus->probe_wait); | 296 | init_waitqueue_head(&nvdimm_bus->probe_wait); |
297 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); | 297 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); |
298 | mutex_init(&nvdimm_bus->reconfig_mutex); | 298 | mutex_init(&nvdimm_bus->reconfig_mutex); |
299 | spin_lock_init(&nvdimm_bus->poison_lock); | ||
299 | if (nvdimm_bus->id < 0) { | 300 | if (nvdimm_bus->id < 0) { |
300 | kfree(nvdimm_bus); | 301 | kfree(nvdimm_bus); |
301 | return NULL; | 302 | return NULL; |
@@ -364,9 +365,9 @@ static int nd_bus_remove(struct device *dev) | |||
364 | nd_synchronize(); | 365 | nd_synchronize(); |
365 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); | 366 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); |
366 | 367 | ||
367 | nvdimm_bus_lock(&nvdimm_bus->dev); | 368 | spin_lock(&nvdimm_bus->poison_lock); |
368 | free_poison_list(&nvdimm_bus->poison_list); | 369 | free_poison_list(&nvdimm_bus->poison_list); |
369 | nvdimm_bus_unlock(&nvdimm_bus->dev); | 370 | spin_unlock(&nvdimm_bus->poison_lock); |
370 | 371 | ||
371 | nvdimm_bus_destroy_ndctl(nvdimm_bus); | 372 | nvdimm_bus_destroy_ndctl(nvdimm_bus); |
372 | 373 | ||
@@ -990,7 +991,7 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm, | |||
990 | 991 | ||
991 | if (clear_err->cleared) { | 992 | if (clear_err->cleared) { |
992 | /* clearing the poison list we keep track of */ | 993 | /* clearing the poison list we keep track of */ |
993 | __nvdimm_forget_poison(nvdimm_bus, clear_err->address, | 994 | nvdimm_forget_poison(nvdimm_bus, clear_err->address, |
994 | clear_err->cleared); | 995 | clear_err->cleared); |
995 | 996 | ||
996 | /* now sync the badblocks lists */ | 997 | /* now sync the badblocks lists */ |
diff --git a/drivers/nvdimm/core.c b/drivers/nvdimm/core.c index 40a3da088fd2..2dee908e4bae 100644 --- a/drivers/nvdimm/core.c +++ b/drivers/nvdimm/core.c | |||
@@ -518,6 +518,15 @@ void nvdimm_badblocks_populate(struct nd_region *nd_region, | |||
518 | } | 518 | } |
519 | EXPORT_SYMBOL_GPL(nvdimm_badblocks_populate); | 519 | EXPORT_SYMBOL_GPL(nvdimm_badblocks_populate); |
520 | 520 | ||
521 | static void append_poison_entry(struct nvdimm_bus *nvdimm_bus, | ||
522 | struct nd_poison *pl, u64 addr, u64 length) | ||
523 | { | ||
524 | lockdep_assert_held(&nvdimm_bus->poison_lock); | ||
525 | pl->start = addr; | ||
526 | pl->length = length; | ||
527 | list_add_tail(&pl->list, &nvdimm_bus->poison_list); | ||
528 | } | ||
529 | |||
521 | static int add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length, | 530 | static int add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length, |
522 | gfp_t flags) | 531 | gfp_t flags) |
523 | { | 532 | { |
@@ -527,19 +536,24 @@ static int add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length, | |||
527 | if (!pl) | 536 | if (!pl) |
528 | return -ENOMEM; | 537 | return -ENOMEM; |
529 | 538 | ||
530 | pl->start = addr; | 539 | append_poison_entry(nvdimm_bus, pl, addr, length); |
531 | pl->length = length; | ||
532 | list_add_tail(&pl->list, &nvdimm_bus->poison_list); | ||
533 | |||
534 | return 0; | 540 | return 0; |
535 | } | 541 | } |
536 | 542 | ||
537 | static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | 543 | static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) |
538 | { | 544 | { |
539 | struct nd_poison *pl; | 545 | struct nd_poison *pl, *pl_new; |
540 | 546 | ||
541 | if (list_empty(&nvdimm_bus->poison_list)) | 547 | spin_unlock(&nvdimm_bus->poison_lock); |
542 | return add_poison(nvdimm_bus, addr, length, GFP_KERNEL); | 548 | pl_new = kzalloc(sizeof(*pl_new), GFP_KERNEL); |
549 | spin_lock(&nvdimm_bus->poison_lock); | ||
550 | |||
551 | if (list_empty(&nvdimm_bus->poison_list)) { | ||
552 | if (!pl_new) | ||
553 | return -ENOMEM; | ||
554 | append_poison_entry(nvdimm_bus, pl_new, addr, length); | ||
555 | return 0; | ||
556 | } | ||
543 | 557 | ||
544 | /* | 558 | /* |
545 | * There is a chance this is a duplicate, check for those first. | 559 | * There is a chance this is a duplicate, check for those first. |
@@ -551,6 +565,7 @@ static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | |||
551 | /* If length has changed, update this list entry */ | 565 | /* If length has changed, update this list entry */ |
552 | if (pl->length != length) | 566 | if (pl->length != length) |
553 | pl->length = length; | 567 | pl->length = length; |
568 | kfree(pl_new); | ||
554 | return 0; | 569 | return 0; |
555 | } | 570 | } |
556 | 571 | ||
@@ -559,30 +574,33 @@ static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | |||
559 | * as any overlapping ranges will get resolved when the list is consumed | 574 | * as any overlapping ranges will get resolved when the list is consumed |
560 | * and converted to badblocks | 575 | * and converted to badblocks |
561 | */ | 576 | */ |
562 | return add_poison(nvdimm_bus, addr, length, GFP_KERNEL); | 577 | if (!pl_new) |
578 | return -ENOMEM; | ||
579 | append_poison_entry(nvdimm_bus, pl_new, addr, length); | ||
580 | |||
581 | return 0; | ||
563 | } | 582 | } |
564 | 583 | ||
565 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | 584 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) |
566 | { | 585 | { |
567 | int rc; | 586 | int rc; |
568 | 587 | ||
569 | nvdimm_bus_lock(&nvdimm_bus->dev); | 588 | spin_lock(&nvdimm_bus->poison_lock); |
570 | rc = bus_add_poison(nvdimm_bus, addr, length); | 589 | rc = bus_add_poison(nvdimm_bus, addr, length); |
571 | nvdimm_bus_unlock(&nvdimm_bus->dev); | 590 | spin_unlock(&nvdimm_bus->poison_lock); |
572 | 591 | ||
573 | return rc; | 592 | return rc; |
574 | } | 593 | } |
575 | EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison); | 594 | EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison); |
576 | 595 | ||
577 | void __nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, phys_addr_t start, | 596 | void nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, phys_addr_t start, |
578 | unsigned int len) | 597 | unsigned int len) |
579 | { | 598 | { |
580 | struct list_head *poison_list = &nvdimm_bus->poison_list; | 599 | struct list_head *poison_list = &nvdimm_bus->poison_list; |
581 | u64 clr_end = start + len - 1; | 600 | u64 clr_end = start + len - 1; |
582 | struct nd_poison *pl, *next; | 601 | struct nd_poison *pl, *next; |
583 | 602 | ||
584 | lockdep_assert_held(&nvdimm_bus->reconfig_mutex); | 603 | spin_lock(&nvdimm_bus->poison_lock); |
585 | |||
586 | WARN_ON_ONCE(list_empty(poison_list)); | 604 | WARN_ON_ONCE(list_empty(poison_list)); |
587 | 605 | ||
588 | /* | 606 | /* |
@@ -629,21 +647,13 @@ void __nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, phys_addr_t start, | |||
629 | u64 new_len = pl_end - new_start + 1; | 647 | u64 new_len = pl_end - new_start + 1; |
630 | 648 | ||
631 | /* Add new entry covering the right half */ | 649 | /* Add new entry covering the right half */ |
632 | add_poison(nvdimm_bus, new_start, new_len, GFP_NOIO); | 650 | add_poison(nvdimm_bus, new_start, new_len, GFP_NOWAIT); |
633 | /* Adjust this entry to cover the left half */ | 651 | /* Adjust this entry to cover the left half */ |
634 | pl->length = start - pl->start; | 652 | pl->length = start - pl->start; |
635 | continue; | 653 | continue; |
636 | } | 654 | } |
637 | } | 655 | } |
638 | } | 656 | spin_unlock(&nvdimm_bus->poison_lock); |
639 | EXPORT_SYMBOL_GPL(__nvdimm_forget_poison); | ||
640 | |||
641 | void nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, | ||
642 | phys_addr_t start, unsigned int len) | ||
643 | { | ||
644 | nvdimm_bus_lock(&nvdimm_bus->dev); | ||
645 | __nvdimm_forget_poison(nvdimm_bus, start, len); | ||
646 | nvdimm_bus_unlock(&nvdimm_bus->dev); | ||
647 | } | 657 | } |
648 | EXPORT_SYMBOL_GPL(nvdimm_forget_poison); | 658 | EXPORT_SYMBOL_GPL(nvdimm_forget_poison); |
649 | 659 | ||
diff --git a/drivers/nvdimm/nd-core.h b/drivers/nvdimm/nd-core.h index 8623e57c2ce3..4c4bd209e725 100644 --- a/drivers/nvdimm/nd-core.h +++ b/drivers/nvdimm/nd-core.h | |||
@@ -32,6 +32,7 @@ struct nvdimm_bus { | |||
32 | struct list_head poison_list; | 32 | struct list_head poison_list; |
33 | struct list_head mapping_list; | 33 | struct list_head mapping_list; |
34 | struct mutex reconfig_mutex; | 34 | struct mutex reconfig_mutex; |
35 | spinlock_t poison_lock; | ||
35 | }; | 36 | }; |
36 | 37 | ||
37 | struct nvdimm { | 38 | struct nvdimm { |
diff --git a/include/linux/libnvdimm.h b/include/linux/libnvdimm.h index 1c609e89048a..98b207611b06 100644 --- a/include/linux/libnvdimm.h +++ b/include/linux/libnvdimm.h | |||
@@ -122,8 +122,6 @@ static inline struct nd_blk_region_desc *to_blk_region_desc( | |||
122 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length); | 122 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length); |
123 | void nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, | 123 | void nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, |
124 | phys_addr_t start, unsigned int len); | 124 | phys_addr_t start, unsigned int len); |
125 | void __nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, | ||
126 | phys_addr_t start, unsigned int len); | ||
127 | struct nvdimm_bus *nvdimm_bus_register(struct device *parent, | 125 | struct nvdimm_bus *nvdimm_bus_register(struct device *parent, |
128 | struct nvdimm_bus_descriptor *nfit_desc); | 126 | struct nvdimm_bus_descriptor *nfit_desc); |
129 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus); | 127 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus); |