libnvdimm, pmem: clear poison on write
[cascardo/linux.git] / drivers / nvdimm / pmem.c
index 8d0b546..adc3872 100644 (file)
@@ -62,26 +62,56 @@ static bool is_bad_pmem(struct badblocks *bb, sector_t sector, unsigned int len)
        return false;
 }
 
+static void pmem_clear_poison(struct pmem_device *pmem, phys_addr_t offset,
+               unsigned int len)
+{
+       struct device *dev = disk_to_dev(pmem->pmem_disk);
+       sector_t sector;
+       long cleared;
+
+       sector = (offset - pmem->data_offset) / 512;
+       cleared = nvdimm_clear_poison(dev, pmem->phys_addr + offset, len);
+
+       if (cleared > 0 && cleared / 512) {
+               dev_dbg(dev, "%s: %llx clear %ld sector%s\n",
+                               __func__, (unsigned long long) sector,
+                               cleared / 512, cleared / 512 > 1 ? "s" : "");
+               badblocks_clear(&pmem->bb, sector, cleared / 512);
+       }
+       invalidate_pmem(pmem->virt_addr + offset, len);
+}
+
 static int pmem_do_bvec(struct pmem_device *pmem, struct page *page,
                        unsigned int len, unsigned int off, int rw,
                        sector_t sector)
 {
+       int rc = 0;
+       bool bad_pmem = false;
        void *mem = kmap_atomic(page);
        phys_addr_t pmem_off = sector * 512 + pmem->data_offset;
        void __pmem *pmem_addr = pmem->virt_addr + pmem_off;
 
+       if (unlikely(is_bad_pmem(&pmem->bb, sector, len)))
+               bad_pmem = true;
+
        if (rw == READ) {
-               if (unlikely(is_bad_pmem(&pmem->bb, sector, len)))
-                       return -EIO;
-               memcpy_from_pmem(mem + off, pmem_addr, len);
-               flush_dcache_page(page);
+               if (unlikely(bad_pmem))
+                       rc = -EIO;
+               else {
+                       memcpy_from_pmem(mem + off, pmem_addr, len);
+                       flush_dcache_page(page);
+               }
        } else {
                flush_dcache_page(page);
                memcpy_to_pmem(pmem_addr, mem + off, len);
+               if (unlikely(bad_pmem)) {
+                       pmem_clear_poison(pmem, pmem_off, len);
+                       memcpy_to_pmem(pmem_addr, mem + off, len);
+               }
        }
 
        kunmap_atomic(mem);
-       return 0;
+       return rc;
 }
 
 static blk_qc_t pmem_make_request(struct request_queue *q, struct bio *bio)
@@ -488,12 +518,27 @@ static int nd_pmem_remove(struct device *dev)
        return 0;
 }
 
+static void nd_pmem_notify(struct device *dev, enum nvdimm_event event)
+{
+       struct pmem_device *pmem = dev_get_drvdata(dev);
+       struct nd_namespace_common *ndns = pmem->ndns;
+
+       if (event != NVDIMM_REVALIDATE_POISON)
+               return;
+
+       if (is_nd_btt(dev))
+               nvdimm_namespace_add_poison(ndns, &pmem->bb, 0);
+       else
+               nvdimm_namespace_add_poison(ndns, &pmem->bb, pmem->data_offset);
+}
+
 MODULE_ALIAS("pmem");
 MODULE_ALIAS_ND_DEVICE(ND_DEVICE_NAMESPACE_IO);
 MODULE_ALIAS_ND_DEVICE(ND_DEVICE_NAMESPACE_PMEM);
 static struct nd_device_driver nd_pmem_driver = {
        .probe = nd_pmem_probe,
        .remove = nd_pmem_remove,
+       .notify = nd_pmem_notify,
        .drv = {
                .name = "nd_pmem",
        },