diff options
author | Vishal Verma <vishal.l.verma@intel.com> | 2016-09-30 19:19:31 -0400 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2016-09-30 20:03:45 -0400 |
commit | e046114af5fcafe8d6d3f0b6ccb99804bad34bfb (patch) | |
tree | 36b9a1f68cd8d10caf5810932a130f1ad38932b9 | |
parent | bd697a80c329072b991475fa6608bb0e665b3d90 (diff) |
libnvdimm: clear the internal poison_list when clearing badblocks
nvdimm_clear_poison cleared the user-visible badblocks, and sent
commands to the NVDIMM to clear the areas marked as 'poison', but it
neglected to clear the same areas from the internal poison_list which is
used to marshal ARS results before sorting them by namespace. As a
result, once on-demand ARS functionality was added:
37b137f nfit, libnvdimm: allow an ARS scrub to be triggered on demand
A scrub triggered from either sysfs or an MCE was found to be adding
stale entries that had been cleared from gendisk->badblocks, but were
still present in nvdimm_bus->poison_list. Additionally, the stale entries
could be triggered into producing stale disk->badblocks by simply disabling
and re-enabling the namespace or region.
This adds the missing step of clearing poison_list entries when clearing
poison, so that it is always in sync with badblocks.
Fixes: 37b137f ("nfit, libnvdimm: allow an ARS scrub to be triggered on demand")
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
-rw-r--r-- | drivers/nvdimm/bus.c | 2 | ||||
-rw-r--r-- | drivers/nvdimm/core.c | 73 | ||||
-rw-r--r-- | include/linux/libnvdimm.h | 2 |
3 files changed, 73 insertions, 4 deletions
diff --git a/drivers/nvdimm/bus.c b/drivers/nvdimm/bus.c index 458daf927336..8493a2559daa 100644 --- a/drivers/nvdimm/bus.c +++ b/drivers/nvdimm/bus.c | |||
@@ -213,6 +213,8 @@ long nvdimm_clear_poison(struct device *dev, phys_addr_t phys, | |||
213 | return rc; | 213 | return rc; |
214 | if (cmd_rc < 0) | 214 | if (cmd_rc < 0) |
215 | return cmd_rc; | 215 | return cmd_rc; |
216 | |||
217 | nvdimm_clear_from_poison_list(nvdimm_bus, phys, len); | ||
216 | return clear_err.cleared; | 218 | return clear_err.cleared; |
217 | } | 219 | } |
218 | EXPORT_SYMBOL_GPL(nvdimm_clear_poison); | 220 | EXPORT_SYMBOL_GPL(nvdimm_clear_poison); |
diff --git a/drivers/nvdimm/core.c b/drivers/nvdimm/core.c index 715583f69d28..42e40db4651b 100644 --- a/drivers/nvdimm/core.c +++ b/drivers/nvdimm/core.c | |||
@@ -541,11 +541,12 @@ void nvdimm_badblocks_populate(struct nd_region *nd_region, | |||
541 | } | 541 | } |
542 | EXPORT_SYMBOL_GPL(nvdimm_badblocks_populate); | 542 | EXPORT_SYMBOL_GPL(nvdimm_badblocks_populate); |
543 | 543 | ||
544 | static int add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | 544 | static int add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length, |
545 | gfp_t flags) | ||
545 | { | 546 | { |
546 | struct nd_poison *pl; | 547 | struct nd_poison *pl; |
547 | 548 | ||
548 | pl = kzalloc(sizeof(*pl), GFP_KERNEL); | 549 | pl = kzalloc(sizeof(*pl), flags); |
549 | if (!pl) | 550 | if (!pl) |
550 | return -ENOMEM; | 551 | return -ENOMEM; |
551 | 552 | ||
@@ -561,7 +562,7 @@ static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | |||
561 | struct nd_poison *pl; | 562 | struct nd_poison *pl; |
562 | 563 | ||
563 | if (list_empty(&nvdimm_bus->poison_list)) | 564 | if (list_empty(&nvdimm_bus->poison_list)) |
564 | return add_poison(nvdimm_bus, addr, length); | 565 | return add_poison(nvdimm_bus, addr, length, GFP_KERNEL); |
565 | 566 | ||
566 | /* | 567 | /* |
567 | * There is a chance this is a duplicate, check for those first. | 568 | * There is a chance this is a duplicate, check for those first. |
@@ -581,7 +582,7 @@ static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | |||
581 | * as any overlapping ranges will get resolved when the list is consumed | 582 | * as any overlapping ranges will get resolved when the list is consumed |
582 | * and converted to badblocks | 583 | * and converted to badblocks |
583 | */ | 584 | */ |
584 | return add_poison(nvdimm_bus, addr, length); | 585 | return add_poison(nvdimm_bus, addr, length, GFP_KERNEL); |
585 | } | 586 | } |
586 | 587 | ||
587 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | 588 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) |
@@ -596,6 +597,70 @@ int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | |||
596 | } | 597 | } |
597 | EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison); | 598 | EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison); |
598 | 599 | ||
600 | void nvdimm_clear_from_poison_list(struct nvdimm_bus *nvdimm_bus, | ||
601 | phys_addr_t start, unsigned int len) | ||
602 | { | ||
603 | struct list_head *poison_list = &nvdimm_bus->poison_list; | ||
604 | u64 clr_end = start + len - 1; | ||
605 | struct nd_poison *pl, *next; | ||
606 | |||
607 | nvdimm_bus_lock(&nvdimm_bus->dev); | ||
608 | WARN_ON_ONCE(list_empty(poison_list)); | ||
609 | |||
610 | /* | ||
611 | * [start, clr_end] is the poison interval being cleared. | ||
612 | * [pl->start, pl_end] is the poison_list entry we're comparing | ||
613 | * the above interval against. The poison list entry may need | ||
614 | * to be modified (update either start or length), deleted, or | ||
615 | * split into two based on the overlap characteristics | ||
616 | */ | ||
617 | |||
618 | list_for_each_entry_safe(pl, next, poison_list, list) { | ||
619 | u64 pl_end = pl->start + pl->length - 1; | ||
620 | |||
621 | /* Skip intervals with no intersection */ | ||
622 | if (pl_end < start) | ||
623 | continue; | ||
624 | if (pl->start > clr_end) | ||
625 | continue; | ||
626 | /* Delete completely overlapped poison entries */ | ||
627 | if ((pl->start >= start) && (pl_end <= clr_end)) { | ||
628 | list_del(&pl->list); | ||
629 | kfree(pl); | ||
630 | continue; | ||
631 | } | ||
632 | /* Adjust start point of partially cleared entries */ | ||
633 | if ((start <= pl->start) && (clr_end > pl->start)) { | ||
634 | pl->length -= clr_end - pl->start + 1; | ||
635 | pl->start = clr_end + 1; | ||
636 | continue; | ||
637 | } | ||
638 | /* Adjust pl->length for partial clearing at the tail end */ | ||
639 | if ((pl->start < start) && (pl_end <= clr_end)) { | ||
640 | /* pl->start remains the same */ | ||
641 | pl->length = start - pl->start; | ||
642 | continue; | ||
643 | } | ||
644 | /* | ||
645 | * If clearing in the middle of an entry, we split it into | ||
646 | * two by modifying the current entry to represent one half of | ||
647 | * the split, and adding a new entry for the second half. | ||
648 | */ | ||
649 | if ((pl->start < start) && (pl_end > clr_end)) { | ||
650 | u64 new_start = clr_end + 1; | ||
651 | u64 new_len = pl_end - new_start + 1; | ||
652 | |||
653 | /* Add new entry covering the right half */ | ||
654 | add_poison(nvdimm_bus, new_start, new_len, GFP_NOIO); | ||
655 | /* Adjust this entry to cover the left half */ | ||
656 | pl->length = start - pl->start; | ||
657 | continue; | ||
658 | } | ||
659 | } | ||
660 | nvdimm_bus_unlock(&nvdimm_bus->dev); | ||
661 | } | ||
662 | EXPORT_SYMBOL_GPL(nvdimm_clear_from_poison_list); | ||
663 | |||
599 | #ifdef CONFIG_BLK_DEV_INTEGRITY | 664 | #ifdef CONFIG_BLK_DEV_INTEGRITY |
600 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) | 665 | int nd_integrity_init(struct gendisk *disk, unsigned long meta_size) |
601 | { | 666 | { |
diff --git a/include/linux/libnvdimm.h b/include/linux/libnvdimm.h index ad18d0531b6e..4a5f8c51f2a5 100644 --- a/include/linux/libnvdimm.h +++ b/include/linux/libnvdimm.h | |||
@@ -129,6 +129,8 @@ static inline struct nd_blk_region_desc *to_blk_region_desc( | |||
129 | } | 129 | } |
130 | 130 | ||
131 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length); | 131 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length); |
132 | void nvdimm_clear_from_poison_list(struct nvdimm_bus *nvdimm_bus, | ||
133 | phys_addr_t start, unsigned int len); | ||
132 | struct nvdimm_bus *nvdimm_bus_register(struct device *parent, | 134 | struct nvdimm_bus *nvdimm_bus_register(struct device *parent, |
133 | struct nvdimm_bus_descriptor *nfit_desc); | 135 | struct nvdimm_bus_descriptor *nfit_desc); |
134 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus); | 136 | void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus); |