diff options
author | Dan Williams <dan.j.williams@intel.com> | 2016-01-10 10:53:55 -0500 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2016-01-10 10:53:55 -0500 |
commit | 8b63b6bfc1a551acf154061699028c7032d7890c (patch) | |
tree | 16882e9bc9e35eacb870a6d8a71617e579c4ffdc /drivers/nvdimm | |
parent | e07ecd76d4db7bda1e9495395b2110a3fe28845a (diff) | |
parent | 55f5560d8c18fe33fc169f8d244a9247dcac7612 (diff) |
Merge branch 'for-4.5/block-dax' into for-4.5/libnvdimm
Diffstat (limited to 'drivers/nvdimm')
-rw-r--r-- | drivers/nvdimm/core.c | 169 | ||||
-rw-r--r-- | drivers/nvdimm/nd-core.h | 1 | ||||
-rw-r--r-- | drivers/nvdimm/nd.h | 8 | ||||
-rw-r--r-- | drivers/nvdimm/pmem.c | 64 |
4 files changed, 233 insertions, 9 deletions
diff --git a/drivers/nvdimm/core.c b/drivers/nvdimm/core.c index 82c49bb87055..2e2832b83c93 100644 --- a/drivers/nvdimm/core.c +++ b/drivers/nvdimm/core.c | |||
@@ -11,6 +11,7 @@ | |||
11 | * General Public License for more details. | 11 | * General Public License for more details. |
12 | */ | 12 | */ |
13 | #include <linux/libnvdimm.h> | 13 | #include <linux/libnvdimm.h> |
14 | #include <linux/badblocks.h> | ||
14 | #include <linux/export.h> | 15 | #include <linux/export.h> |
15 | #include <linux/module.h> | 16 | #include <linux/module.h> |
16 | #include <linux/blkdev.h> | 17 | #include <linux/blkdev.h> |
@@ -325,6 +326,7 @@ struct nvdimm_bus *__nvdimm_bus_register(struct device *parent, | |||
325 | if (!nvdimm_bus) | 326 | if (!nvdimm_bus) |
326 | return NULL; | 327 | return NULL; |
327 | INIT_LIST_HEAD(&nvdimm_bus->list); | 328 | INIT_LIST_HEAD(&nvdimm_bus->list); |
329 | INIT_LIST_HEAD(&nvdimm_bus->poison_list); | ||
328 | init_waitqueue_head(&nvdimm_bus->probe_wait); | 330 | init_waitqueue_head(&nvdimm_bus->probe_wait); |
329 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); | 331 | nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); |
330 | mutex_init(&nvdimm_bus->reconfig_mutex); | 332 | mutex_init(&nvdimm_bus->reconfig_mutex); |
@@ -359,6 +361,172 @@ struct nvdimm_bus *__nvdimm_bus_register(struct device *parent, | |||
359 | } | 361 | } |
360 | EXPORT_SYMBOL_GPL(__nvdimm_bus_register); | 362 | EXPORT_SYMBOL_GPL(__nvdimm_bus_register); |
361 | 363 | ||
364 | static void set_badblock(struct badblocks *bb, sector_t s, int num) | ||
365 | { | ||
366 | dev_dbg(bb->dev, "Found a poison range (0x%llx, 0x%llx)\n", | ||
367 | (u64) s * 512, (u64) num * 512); | ||
368 | /* this isn't an error as the hardware will still throw an exception */ | ||
369 | if (badblocks_set(bb, s, num, 1)) | ||
370 | dev_info_once(bb->dev, "%s: failed for sector %llx\n", | ||
371 | __func__, (u64) s); | ||
372 | } | ||
373 | |||
374 | /** | ||
375 | * __add_badblock_range() - Convert a physical address range to bad sectors | ||
376 | * @bb: badblocks instance to populate | ||
377 | * @ns_offset: namespace offset where the error range begins (in bytes) | ||
378 | * @len: number of bytes of poison to be added | ||
379 | * | ||
380 | * This assumes that the range provided with (ns_offset, len) is within | ||
381 | * the bounds of physical addresses for this namespace, i.e. lies in the | ||
382 | * interval [ns_start, ns_start + ns_size) | ||
383 | */ | ||
384 | static void __add_badblock_range(struct badblocks *bb, u64 ns_offset, u64 len) | ||
385 | { | ||
386 | const unsigned int sector_size = 512; | ||
387 | sector_t start_sector; | ||
388 | u64 num_sectors; | ||
389 | u32 rem; | ||
390 | |||
391 | start_sector = div_u64(ns_offset, sector_size); | ||
392 | num_sectors = div_u64_rem(len, sector_size, &rem); | ||
393 | if (rem) | ||
394 | num_sectors++; | ||
395 | |||
396 | if (unlikely(num_sectors > (u64)INT_MAX)) { | ||
397 | u64 remaining = num_sectors; | ||
398 | sector_t s = start_sector; | ||
399 | |||
400 | while (remaining) { | ||
401 | int done = min_t(u64, remaining, INT_MAX); | ||
402 | |||
403 | set_badblock(bb, s, done); | ||
404 | remaining -= done; | ||
405 | s += done; | ||
406 | } | ||
407 | } else | ||
408 | set_badblock(bb, start_sector, num_sectors); | ||
409 | } | ||
410 | |||
411 | /** | ||
412 | * nvdimm_namespace_add_poison() - Convert a list of poison ranges to badblocks | ||
413 | * @ndns: the namespace containing poison ranges | ||
414 | * @bb: badblocks instance to populate | ||
415 | * @offset: offset at the start of the namespace before 'sector 0' | ||
416 | * | ||
417 | * The poison list generated during NFIT initialization may contain multiple, | ||
418 | * possibly overlapping ranges in the SPA (System Physical Address) space. | ||
419 | * Compare each of these ranges to the namespace currently being initialized, | ||
420 | * and add badblocks to the gendisk for all matching sub-ranges | ||
421 | */ | ||
422 | void nvdimm_namespace_add_poison(struct nd_namespace_common *ndns, | ||
423 | struct badblocks *bb, resource_size_t offset) | ||
424 | { | ||
425 | struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); | ||
426 | struct nd_region *nd_region = to_nd_region(ndns->dev.parent); | ||
427 | struct nvdimm_bus *nvdimm_bus; | ||
428 | struct list_head *poison_list; | ||
429 | u64 ns_start, ns_end, ns_size; | ||
430 | struct nd_poison *pl; | ||
431 | |||
432 | ns_size = nvdimm_namespace_capacity(ndns) - offset; | ||
433 | ns_start = nsio->res.start + offset; | ||
434 | ns_end = nsio->res.end; | ||
435 | |||
436 | nvdimm_bus = to_nvdimm_bus(nd_region->dev.parent); | ||
437 | poison_list = &nvdimm_bus->poison_list; | ||
438 | if (list_empty(poison_list)) | ||
439 | return; | ||
440 | |||
441 | list_for_each_entry(pl, poison_list, list) { | ||
442 | u64 pl_end = pl->start + pl->length - 1; | ||
443 | |||
444 | /* Discard intervals with no intersection */ | ||
445 | if (pl_end < ns_start) | ||
446 | continue; | ||
447 | if (pl->start > ns_end) | ||
448 | continue; | ||
449 | /* Deal with any overlap after start of the namespace */ | ||
450 | if (pl->start >= ns_start) { | ||
451 | u64 start = pl->start; | ||
452 | u64 len; | ||
453 | |||
454 | if (pl_end <= ns_end) | ||
455 | len = pl->length; | ||
456 | else | ||
457 | len = ns_start + ns_size - pl->start; | ||
458 | __add_badblock_range(bb, start - ns_start, len); | ||
459 | continue; | ||
460 | } | ||
461 | /* Deal with overlap for poison starting before the namespace */ | ||
462 | if (pl->start < ns_start) { | ||
463 | u64 len; | ||
464 | |||
465 | if (pl_end < ns_end) | ||
466 | len = pl->start + pl->length - ns_start; | ||
467 | else | ||
468 | len = ns_size; | ||
469 | __add_badblock_range(bb, 0, len); | ||
470 | } | ||
471 | } | ||
472 | } | ||
473 | EXPORT_SYMBOL_GPL(nvdimm_namespace_add_poison); | ||
474 | |||
475 | static int __add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | ||
476 | { | ||
477 | struct nd_poison *pl; | ||
478 | |||
479 | pl = kzalloc(sizeof(*pl), GFP_KERNEL); | ||
480 | if (!pl) | ||
481 | return -ENOMEM; | ||
482 | |||
483 | pl->start = addr; | ||
484 | pl->length = length; | ||
485 | list_add_tail(&pl->list, &nvdimm_bus->poison_list); | ||
486 | |||
487 | return 0; | ||
488 | } | ||
489 | |||
490 | int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length) | ||
491 | { | ||
492 | struct nd_poison *pl; | ||
493 | |||
494 | if (list_empty(&nvdimm_bus->poison_list)) | ||
495 | return __add_poison(nvdimm_bus, addr, length); | ||
496 | |||
497 | /* | ||
498 | * There is a chance this is a duplicate, check for those first. | ||
499 | * This will be the common case as ARS_STATUS returns all known | ||
500 | * errors in the SPA space, and we can't query it per region | ||
501 | */ | ||
502 | list_for_each_entry(pl, &nvdimm_bus->poison_list, list) | ||
503 | if (pl->start == addr) { | ||
504 | /* If length has changed, update this list entry */ | ||
505 | if (pl->length != length) | ||
506 | pl->length = length; | ||
507 | return 0; | ||
508 | } | ||
509 | |||
510 | /* | ||
511 | * If not a duplicate or a simple length update, add the entry as is, | ||
512 | * as any overlapping ranges will get resolved when the list is consumed | ||
513 | * and converted to badblocks | ||
514 | */ | ||
515 | return __add_poison(nvdimm_bus, addr, length); | ||
516 | } | ||
517 | EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison); | ||
518 | |||
519 | static void free_poison_list(struct list_head *poison_list) | ||
520 | { | ||
521 | struct nd_poison *pl, *next; | ||
522 | |||
523 | list_for_each_entry_safe(pl, next, poison_list, list) { | ||
524 | list_del(&pl->list); | ||
525 | kfree(pl); | ||
526 | } | ||
527 | list_del_init(poison_list); | ||
528 | } | ||
529 | |||
362 | static int child_unregister(struct device *dev, void *data) | 530 | static int child_unregister(struct device *dev, void *data) |
363 | { | 531 | { |
364 | /* | 532 | /* |
@@ -385,6 +553,7 @@ void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus) | |||
385 | 553 | ||
386 | nd_synchronize(); | 554 | nd_synchronize(); |
387 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); | 555 | device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister); |
556 | free_poison_list(&nvdimm_bus->poison_list); | ||
388 | nvdimm_bus_destroy_ndctl(nvdimm_bus); | 557 | nvdimm_bus_destroy_ndctl(nvdimm_bus); |
389 | 558 | ||
390 | device_unregister(&nvdimm_bus->dev); | 559 | device_unregister(&nvdimm_bus->dev); |
diff --git a/drivers/nvdimm/nd-core.h b/drivers/nvdimm/nd-core.h index 3249c498892a..1d1500f3d8b5 100644 --- a/drivers/nvdimm/nd-core.h +++ b/drivers/nvdimm/nd-core.h | |||
@@ -30,6 +30,7 @@ struct nvdimm_bus { | |||
30 | struct list_head list; | 30 | struct list_head list; |
31 | struct device dev; | 31 | struct device dev; |
32 | int id, probe_active; | 32 | int id, probe_active; |
33 | struct list_head poison_list; | ||
33 | struct mutex reconfig_mutex; | 34 | struct mutex reconfig_mutex; |
34 | }; | 35 | }; |
35 | 36 | ||
diff --git a/drivers/nvdimm/nd.h b/drivers/nvdimm/nd.h index e4e9f9ae0cc8..ba1633b9da31 100644 --- a/drivers/nvdimm/nd.h +++ b/drivers/nvdimm/nd.h | |||
@@ -31,6 +31,12 @@ enum { | |||
31 | INT_LBASIZE_ALIGNMENT = 64, | 31 | INT_LBASIZE_ALIGNMENT = 64, |
32 | }; | 32 | }; |
33 | 33 | ||
34 | struct nd_poison { | ||
35 | u64 start; | ||
36 | u64 length; | ||
37 | struct list_head list; | ||
38 | }; | ||
39 | |||
34 | struct nvdimm_drvdata { | 40 | struct nvdimm_drvdata { |
35 | struct device *dev; | 41 | struct device *dev; |
36 | int nsindex_size; | 42 | int nsindex_size; |
@@ -256,6 +262,8 @@ int nvdimm_namespace_attach_btt(struct nd_namespace_common *ndns); | |||
256 | int nvdimm_namespace_detach_btt(struct nd_namespace_common *ndns); | 262 | int nvdimm_namespace_detach_btt(struct nd_namespace_common *ndns); |
257 | const char *nvdimm_namespace_disk_name(struct nd_namespace_common *ndns, | 263 | const char *nvdimm_namespace_disk_name(struct nd_namespace_common *ndns, |
258 | char *name); | 264 | char *name); |
265 | void nvdimm_namespace_add_poison(struct nd_namespace_common *ndns, | ||
266 | struct badblocks *bb, resource_size_t offset); | ||
259 | int nd_blk_region_init(struct nd_region *nd_region); | 267 | int nd_blk_region_init(struct nd_region *nd_region); |
260 | void __nd_iostat_start(struct bio *bio, unsigned long *start); | 268 | void __nd_iostat_start(struct bio *bio, unsigned long *start); |
261 | static inline bool nd_iostat_start(struct bio *bio, unsigned long *start) | 269 | static inline bool nd_iostat_start(struct bio *bio, unsigned long *start) |
diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c index ab689bca727d..b493ff3fccb2 100644 --- a/drivers/nvdimm/pmem.c +++ b/drivers/nvdimm/pmem.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <linux/module.h> | 23 | #include <linux/module.h> |
24 | #include <linux/memory_hotplug.h> | 24 | #include <linux/memory_hotplug.h> |
25 | #include <linux/moduleparam.h> | 25 | #include <linux/moduleparam.h> |
26 | #include <linux/badblocks.h> | ||
26 | #include <linux/vmalloc.h> | 27 | #include <linux/vmalloc.h> |
27 | #include <linux/slab.h> | 28 | #include <linux/slab.h> |
28 | #include <linux/pmem.h> | 29 | #include <linux/pmem.h> |
@@ -41,11 +42,25 @@ struct pmem_device { | |||
41 | phys_addr_t data_offset; | 42 | phys_addr_t data_offset; |
42 | void __pmem *virt_addr; | 43 | void __pmem *virt_addr; |
43 | size_t size; | 44 | size_t size; |
45 | struct badblocks bb; | ||
44 | }; | 46 | }; |
45 | 47 | ||
46 | static int pmem_major; | 48 | static int pmem_major; |
47 | 49 | ||
48 | static void pmem_do_bvec(struct pmem_device *pmem, struct page *page, | 50 | static bool is_bad_pmem(struct badblocks *bb, sector_t sector, unsigned int len) |
51 | { | ||
52 | if (bb->count) { | ||
53 | sector_t first_bad; | ||
54 | int num_bad; | ||
55 | |||
56 | return !!badblocks_check(bb, sector, len / 512, &first_bad, | ||
57 | &num_bad); | ||
58 | } | ||
59 | |||
60 | return false; | ||
61 | } | ||
62 | |||
63 | static int pmem_do_bvec(struct pmem_device *pmem, struct page *page, | ||
49 | unsigned int len, unsigned int off, int rw, | 64 | unsigned int len, unsigned int off, int rw, |
50 | sector_t sector) | 65 | sector_t sector) |
51 | { | 66 | { |
@@ -54,6 +69,8 @@ static void pmem_do_bvec(struct pmem_device *pmem, struct page *page, | |||
54 | void __pmem *pmem_addr = pmem->virt_addr + pmem_off; | 69 | void __pmem *pmem_addr = pmem->virt_addr + pmem_off; |
55 | 70 | ||
56 | if (rw == READ) { | 71 | if (rw == READ) { |
72 | if (unlikely(is_bad_pmem(&pmem->bb, sector, len))) | ||
73 | return -EIO; | ||
57 | memcpy_from_pmem(mem + off, pmem_addr, len); | 74 | memcpy_from_pmem(mem + off, pmem_addr, len); |
58 | flush_dcache_page(page); | 75 | flush_dcache_page(page); |
59 | } else { | 76 | } else { |
@@ -62,10 +79,12 @@ static void pmem_do_bvec(struct pmem_device *pmem, struct page *page, | |||
62 | } | 79 | } |
63 | 80 | ||
64 | kunmap_atomic(mem); | 81 | kunmap_atomic(mem); |
82 | return 0; | ||
65 | } | 83 | } |
66 | 84 | ||
67 | static blk_qc_t pmem_make_request(struct request_queue *q, struct bio *bio) | 85 | static blk_qc_t pmem_make_request(struct request_queue *q, struct bio *bio) |
68 | { | 86 | { |
87 | int rc = 0; | ||
69 | bool do_acct; | 88 | bool do_acct; |
70 | unsigned long start; | 89 | unsigned long start; |
71 | struct bio_vec bvec; | 90 | struct bio_vec bvec; |
@@ -74,9 +93,15 @@ static blk_qc_t pmem_make_request(struct request_queue *q, struct bio *bio) | |||
74 | struct pmem_device *pmem = bdev->bd_disk->private_data; | 93 | struct pmem_device *pmem = bdev->bd_disk->private_data; |
75 | 94 | ||
76 | do_acct = nd_iostat_start(bio, &start); | 95 | do_acct = nd_iostat_start(bio, &start); |
77 | bio_for_each_segment(bvec, bio, iter) | 96 | bio_for_each_segment(bvec, bio, iter) { |
78 | pmem_do_bvec(pmem, bvec.bv_page, bvec.bv_len, bvec.bv_offset, | 97 | rc = pmem_do_bvec(pmem, bvec.bv_page, bvec.bv_len, |
79 | bio_data_dir(bio), iter.bi_sector); | 98 | bvec.bv_offset, bio_data_dir(bio), |
99 | iter.bi_sector); | ||
100 | if (rc) { | ||
101 | bio->bi_error = rc; | ||
102 | break; | ||
103 | } | ||
104 | } | ||
80 | if (do_acct) | 105 | if (do_acct) |
81 | nd_iostat_end(bio, start); | 106 | nd_iostat_end(bio, start); |
82 | 107 | ||
@@ -91,13 +116,22 @@ static int pmem_rw_page(struct block_device *bdev, sector_t sector, | |||
91 | struct page *page, int rw) | 116 | struct page *page, int rw) |
92 | { | 117 | { |
93 | struct pmem_device *pmem = bdev->bd_disk->private_data; | 118 | struct pmem_device *pmem = bdev->bd_disk->private_data; |
119 | int rc; | ||
94 | 120 | ||
95 | pmem_do_bvec(pmem, page, PAGE_CACHE_SIZE, 0, rw, sector); | 121 | rc = pmem_do_bvec(pmem, page, PAGE_CACHE_SIZE, 0, rw, sector); |
96 | if (rw & WRITE) | 122 | if (rw & WRITE) |
97 | wmb_pmem(); | 123 | wmb_pmem(); |
98 | page_endio(page, rw & WRITE, 0); | ||
99 | 124 | ||
100 | return 0; | 125 | /* |
126 | * The ->rw_page interface is subtle and tricky. The core | ||
127 | * retries on any error, so we can only invoke page_endio() in | ||
128 | * the successful completion case. Otherwise, we'll see crashes | ||
129 | * caused by double completion. | ||
130 | */ | ||
131 | if (rc == 0) | ||
132 | page_endio(page, rw & WRITE, 0); | ||
133 | |||
134 | return rc; | ||
101 | } | 135 | } |
102 | 136 | ||
103 | static long pmem_direct_access(struct block_device *bdev, sector_t sector, | 137 | static long pmem_direct_access(struct block_device *bdev, sector_t sector, |
@@ -195,7 +229,12 @@ static int pmem_attach_disk(struct device *dev, | |||
195 | disk->driverfs_dev = dev; | 229 | disk->driverfs_dev = dev; |
196 | set_capacity(disk, (pmem->size - pmem->data_offset) / 512); | 230 | set_capacity(disk, (pmem->size - pmem->data_offset) / 512); |
197 | pmem->pmem_disk = disk; | 231 | pmem->pmem_disk = disk; |
232 | devm_exit_badblocks(dev, &pmem->bb); | ||
233 | if (devm_init_badblocks(dev, &pmem->bb)) | ||
234 | return -ENOMEM; | ||
235 | nvdimm_namespace_add_poison(ndns, &pmem->bb, pmem->data_offset); | ||
198 | 236 | ||
237 | disk->bb = &pmem->bb; | ||
199 | add_disk(disk); | 238 | add_disk(disk); |
200 | revalidate_disk(disk); | 239 | revalidate_disk(disk); |
201 | 240 | ||
@@ -212,9 +251,13 @@ static int pmem_rw_bytes(struct nd_namespace_common *ndns, | |||
212 | return -EFAULT; | 251 | return -EFAULT; |
213 | } | 252 | } |
214 | 253 | ||
215 | if (rw == READ) | 254 | if (rw == READ) { |
255 | unsigned int sz_align = ALIGN(size + (offset & (512 - 1)), 512); | ||
256 | |||
257 | if (unlikely(is_bad_pmem(&pmem->bb, offset / 512, sz_align))) | ||
258 | return -EIO; | ||
216 | memcpy_from_pmem(buf, pmem->virt_addr + offset, size); | 259 | memcpy_from_pmem(buf, pmem->virt_addr + offset, size); |
217 | else { | 260 | } else { |
218 | memcpy_to_pmem(pmem->virt_addr + offset, buf, size); | 261 | memcpy_to_pmem(pmem->virt_addr + offset, buf, size); |
219 | wmb_pmem(); | 262 | wmb_pmem(); |
220 | } | 263 | } |
@@ -377,6 +420,9 @@ static int nd_pmem_probe(struct device *dev) | |||
377 | pmem->ndns = ndns; | 420 | pmem->ndns = ndns; |
378 | dev_set_drvdata(dev, pmem); | 421 | dev_set_drvdata(dev, pmem); |
379 | ndns->rw_bytes = pmem_rw_bytes; | 422 | ndns->rw_bytes = pmem_rw_bytes; |
423 | if (devm_init_badblocks(dev, &pmem->bb)) | ||
424 | return -ENOMEM; | ||
425 | nvdimm_namespace_add_poison(ndns, &pmem->bb, 0); | ||
380 | 426 | ||
381 | if (is_nd_btt(dev)) | 427 | if (is_nd_btt(dev)) |
382 | return nvdimm_namespace_attach_btt(ndns); | 428 | return nvdimm_namespace_attach_btt(ndns); |