| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright(c) 2017 Intel Corporation. All rights reserved. | |
| */ | |
| #include <linux/libnvdimm.h> | |
| #include <linux/badblocks.h> | |
| #include <linux/export.h> | |
| #include <linux/module.h> | |
| #include <linux/blkdev.h> | |
| #include <linux/device.h> | |
| #include <linux/ctype.h> | |
| #include <linux/ndctl.h> | |
| #include <linux/mutex.h> | |
| #include <linux/slab.h> | |
| #include <linux/io.h> | |
| #include "nd-core.h" | |
| #include "nd.h" | |
| void badrange_init(struct badrange *badrange) | |
| { | |
| INIT_LIST_HEAD(&badrange->list); | |
| spin_lock_init(&badrange->lock); | |
| } | |
| EXPORT_SYMBOL_GPL(badrange_init); | |
| static void append_badrange_entry(struct badrange *badrange, | |
| struct badrange_entry *bre, u64 addr, u64 length) | |
| { | |
| lockdep_assert_held(&badrange->lock); | |
| bre->start = addr; | |
| bre->length = length; | |
| list_add_tail(&bre->list, &badrange->list); | |
| } | |
| static int alloc_and_append_badrange_entry(struct badrange *badrange, | |
| u64 addr, u64 length, gfp_t flags) | |
| { | |
| struct badrange_entry *bre; | |
| bre = kzalloc(sizeof(*bre), flags); | |
| if (!bre) | |
| return -ENOMEM; | |
| append_badrange_entry(badrange, bre, addr, length); | |
| return 0; | |
| } | |
| static int add_badrange(struct badrange *badrange, u64 addr, u64 length) | |
| { | |
| struct badrange_entry *bre, *bre_new; | |
| spin_unlock(&badrange->lock); | |
| bre_new = kzalloc(sizeof(*bre_new), GFP_KERNEL); | |
| spin_lock(&badrange->lock); | |
| if (list_empty(&badrange->list)) { | |
| if (!bre_new) | |
| return -ENOMEM; | |
| append_badrange_entry(badrange, bre_new, addr, length); | |
| return 0; | |
| } | |
| /* | |
| * There is a chance this is a duplicate, check for those first. | |
| * This will be the common case as ARS_STATUS returns all known | |
| * errors in the SPA space, and we can't query it per region | |
| */ | |
| list_for_each_entry(bre, &badrange->list, list) | |
| if (bre->start == addr) { | |
| /* If length has changed, update this list entry */ | |
| if (bre->length != length) | |
| bre->length = length; | |
| kfree(bre_new); | |
| return 0; | |
| } | |
| /* | |
| * If not a duplicate or a simple length update, add the entry as is, | |
| * as any overlapping ranges will get resolved when the list is consumed | |
| * and converted to badblocks | |
| */ | |
| if (!bre_new) | |
| return -ENOMEM; | |
| append_badrange_entry(badrange, bre_new, addr, length); | |
| return 0; | |
| } | |
| int badrange_add(struct badrange *badrange, u64 addr, u64 length) | |
| { | |
| int rc; | |
| spin_lock(&badrange->lock); | |
| rc = add_badrange(badrange, addr, length); | |
| spin_unlock(&badrange->lock); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL_GPL(badrange_add); | |
| void badrange_forget(struct badrange *badrange, phys_addr_t start, | |
| unsigned int len) | |
| { | |
| struct list_head *badrange_list = &badrange->list; | |
| u64 clr_end = start + len - 1; | |
| struct badrange_entry *bre, *next; | |
| spin_lock(&badrange->lock); | |
| /* | |
| * [start, clr_end] is the badrange interval being cleared. | |
| * [bre->start, bre_end] is the badrange_list entry we're comparing | |
| * the above interval against. The badrange list entry may need | |
| * to be modified (update either start or length), deleted, or | |
| * split into two based on the overlap characteristics | |
| */ | |
| list_for_each_entry_safe(bre, next, badrange_list, list) { | |
| u64 bre_end = bre->start + bre->length - 1; | |
| /* Skip intervals with no intersection */ | |
| if (bre_end < start) | |
| continue; | |
| if (bre->start > clr_end) | |
| continue; | |
| /* Delete completely overlapped badrange entries */ | |
| if ((bre->start >= start) && (bre_end <= clr_end)) { | |
| list_del(&bre->list); | |
| kfree(bre); | |
| continue; | |
| } | |
| /* Adjust start point of partially cleared entries */ | |
| if ((start <= bre->start) && (clr_end > bre->start)) { | |
| bre->length -= clr_end - bre->start + 1; | |
| bre->start = clr_end + 1; | |
| continue; | |
| } | |
| /* Adjust bre->length for partial clearing at the tail end */ | |
| if ((bre->start < start) && (bre_end <= clr_end)) { | |
| /* bre->start remains the same */ | |
| bre->length = start - bre->start; | |
| continue; | |
| } | |
| /* | |
| * If clearing in the middle of an entry, we split it into | |
| * two by modifying the current entry to represent one half of | |
| * the split, and adding a new entry for the second half. | |
| */ | |
| if ((bre->start < start) && (bre_end > clr_end)) { | |
| u64 new_start = clr_end + 1; | |
| u64 new_len = bre_end - new_start + 1; | |
| /* Add new entry covering the right half */ | |
| alloc_and_append_badrange_entry(badrange, new_start, | |
| new_len, GFP_NOWAIT); | |
| /* Adjust this entry to cover the left half */ | |
| bre->length = start - bre->start; | |
| continue; | |
| } | |
| } | |
| spin_unlock(&badrange->lock); | |
| } | |
| EXPORT_SYMBOL_GPL(badrange_forget); | |
| static void set_badblock(struct badblocks *bb, sector_t s, int num) | |
| { | |
| dev_dbg(bb->dev, "Found a bad range (0x%llx, 0x%llx)\n", | |
| (u64) s * 512, (u64) num * 512); | |
| /* this isn't an error as the hardware will still throw an exception */ | |
| if (badblocks_set(bb, s, num, 1)) | |
| dev_info_once(bb->dev, "%s: failed for sector %llx\n", | |
| __func__, (u64) s); | |
| } | |
| /** | |
| * __add_badblock_range() - Convert a physical address range to bad sectors | |
| * @bb: badblocks instance to populate | |
| * @ns_offset: namespace offset where the error range begins (in bytes) | |
| * @len: number of bytes of badrange to be added | |
| * | |
| * This assumes that the range provided with (ns_offset, len) is within | |
| * the bounds of physical addresses for this namespace, i.e. lies in the | |
| * interval [ns_start, ns_start + ns_size) | |
| */ | |
| static void __add_badblock_range(struct badblocks *bb, u64 ns_offset, u64 len) | |
| { | |
| const unsigned int sector_size = 512; | |
| sector_t start_sector, end_sector; | |
| u64 num_sectors; | |
| u32 rem; | |
| start_sector = div_u64(ns_offset, sector_size); | |
| end_sector = div_u64_rem(ns_offset + len, sector_size, &rem); | |
| if (rem) | |
| end_sector++; | |
| num_sectors = end_sector - start_sector; | |
| if (unlikely(num_sectors > (u64)INT_MAX)) { | |
| u64 remaining = num_sectors; | |
| sector_t s = start_sector; | |
| while (remaining) { | |
| int done = min_t(u64, remaining, INT_MAX); | |
| set_badblock(bb, s, done); | |
| remaining -= done; | |
| s += done; | |
| } | |
| } else | |
| set_badblock(bb, start_sector, num_sectors); | |
| } | |
| static void badblocks_populate(struct badrange *badrange, | |
| struct badblocks *bb, const struct resource *res) | |
| { | |
| struct badrange_entry *bre; | |
| if (list_empty(&badrange->list)) | |
| return; | |
| list_for_each_entry(bre, &badrange->list, list) { | |
| u64 bre_end = bre->start + bre->length - 1; | |
| /* Discard intervals with no intersection */ | |
| if (bre_end < res->start) | |
| continue; | |
| if (bre->start > res->end) | |
| continue; | |
| /* Deal with any overlap after start of the namespace */ | |
| if (bre->start >= res->start) { | |
| u64 start = bre->start; | |
| u64 len; | |
| if (bre_end <= res->end) | |
| len = bre->length; | |
| else | |
| len = res->start + resource_size(res) | |
| - bre->start; | |
| __add_badblock_range(bb, start - res->start, len); | |
| continue; | |
| } | |
| /* | |
| * Deal with overlap for badrange starting before | |
| * the namespace. | |
| */ | |
| if (bre->start < res->start) { | |
| u64 len; | |
| if (bre_end < res->end) | |
| len = bre->start + bre->length - res->start; | |
| else | |
| len = resource_size(res); | |
| __add_badblock_range(bb, 0, len); | |
| } | |
| } | |
| } | |
| /** | |
| * nvdimm_badblocks_populate() - Convert a list of badranges to badblocks | |
| * @region: parent region of the range to interrogate | |
| * @bb: badblocks instance to populate | |
| * @res: resource range to consider | |
| * | |
| * The badrange list generated during bus initialization may contain | |
| * multiple, possibly overlapping physical address ranges. Compare each | |
| * of these ranges to the resource range currently being initialized, | |
| * and add badblocks entries for all matching sub-ranges | |
| */ | |
| void nvdimm_badblocks_populate(struct nd_region *nd_region, | |
| struct badblocks *bb, const struct resource *res) | |
| { | |
| struct nvdimm_bus *nvdimm_bus; | |
| if (!is_memory(&nd_region->dev)) { | |
| dev_WARN_ONCE(&nd_region->dev, 1, | |
| "%s only valid for pmem regions\n", __func__); | |
| return; | |
| } | |
| nvdimm_bus = walk_to_nvdimm_bus(&nd_region->dev); | |
| nvdimm_bus_lock(&nvdimm_bus->dev); | |
| badblocks_populate(&nvdimm_bus->badrange, bb, res); | |
| nvdimm_bus_unlock(&nvdimm_bus->dev); | |
| } | |
| EXPORT_SYMBOL_GPL(nvdimm_badblocks_populate); |