#include <linux/nd.h>
 #include "nd-core.h"
 #include "nd.h"
+#include "pfn.h"
 
 int nvdimm_major;
 static int nvdimm_bus_major;
        if (cmd_rc < 0)
                return cmd_rc;
 
-       nvdimm_clear_from_poison_list(nvdimm_bus, phys, len);
+       nvdimm_forget_poison(nvdimm_bus, phys, len);
        return clear_err.cleared;
 }
 EXPORT_SYMBOL_GPL(nvdimm_clear_poison);
 
+void __nvdimm_bus_badblocks_clear(struct nvdimm_bus *nvdimm_bus,
+               struct resource *res)
+{
+       lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
+       device_for_each_child(&nvdimm_bus->dev, (void *)res,
+                       nvdimm_region_badblocks_clear);
+}
+EXPORT_SYMBOL_GPL(__nvdimm_bus_badblocks_clear);
+
 static int nvdimm_bus_match(struct device *dev, struct device_driver *drv);
 
 static struct bus_type nvdimm_bus_type = {
        } while (true);
 }
 
-static int pmem_active(struct device *dev, void *data)
+static int nd_pmem_forget_poison_check(struct device *dev, void *data)
 {
-       if (is_nd_pmem(dev) && dev->driver)
+       struct nd_cmd_clear_error *clear_err =
+               (struct nd_cmd_clear_error *)data;
+       struct nd_btt *nd_btt = is_nd_btt(dev) ? to_nd_btt(dev) : NULL;
+       struct nd_pfn *nd_pfn = is_nd_pfn(dev) ? to_nd_pfn(dev) : NULL;
+       struct nd_dax *nd_dax = is_nd_dax(dev) ? to_nd_dax(dev) : NULL;
+       struct nd_namespace_common *ndns = NULL;
+       struct nd_namespace_io *nsio;
+       resource_size_t offset = 0, end_trunc = 0, start, end, pstart, pend;
+
+       if (nd_dax || !dev->driver)
+               return 0;
+
+       start = clear_err->address;
+       end = clear_err->address + clear_err->cleared - 1;
+
+       if (nd_btt || nd_pfn || nd_dax) {
+               if (nd_btt)
+                       ndns = nd_btt->ndns;
+               else if (nd_pfn)
+                       ndns = nd_pfn->ndns;
+               else if (nd_dax)
+                       ndns = nd_dax->nd_pfn.ndns;
+
+               if (!ndns)
+                       return 0;
+       } else
+               ndns = to_ndns(dev);
+
+       nsio = to_nd_namespace_io(&ndns->dev);
+       pstart = nsio->res.start + offset;
+       pend = nsio->res.end - end_trunc;
+
+       if ((pstart >= start) && (pend <= end))
                return -EBUSY;
+
        return 0;
+
+}
+
+static int nd_ns_forget_poison_check(struct device *dev, void *data)
+{
+       return device_for_each_child(dev, data, nd_pmem_forget_poison_check);
 }
 
 /* set_config requires an idle interleave set */
 static int nd_cmd_clear_to_send(struct nvdimm_bus *nvdimm_bus,
-               struct nvdimm *nvdimm, unsigned int cmd)
+               struct nvdimm *nvdimm, unsigned int cmd, void *data)
 {
        struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
 
 
        /* require clear error to go through the pmem driver */
        if (!nvdimm && cmd == ND_CMD_CLEAR_ERROR)
-               return device_for_each_child(&nvdimm_bus->dev, NULL,
-                               pmem_active);
+               return device_for_each_child(&nvdimm_bus->dev, data,
+                               nd_ns_forget_poison_check);
 
        if (!nvdimm || cmd != ND_CMD_SET_CONFIG_DATA)
                return 0;
        const char *cmd_name, *dimm_name;
        unsigned long cmd_mask;
        void *buf;
-       int rc, i;
+       int rc, i, cmd_rc;
 
        if (nvdimm) {
                desc = nd_cmd_dimm_desc(cmd);
        }
 
        nvdimm_bus_lock(&nvdimm_bus->dev);
-       rc = nd_cmd_clear_to_send(nvdimm_bus, nvdimm, cmd);
+       rc = nd_cmd_clear_to_send(nvdimm_bus, nvdimm, cmd, buf);
        if (rc)
                goto out_unlock;
 
-       rc = nd_desc->ndctl(nd_desc, nvdimm, cmd, buf, buf_len, NULL);
+       rc = nd_desc->ndctl(nd_desc, nvdimm, cmd, buf, buf_len, &cmd_rc);
        if (rc < 0)
                goto out_unlock;
+
+       if (!nvdimm && cmd == ND_CMD_CLEAR_ERROR && cmd_rc >= 0) {
+               struct nd_cmd_clear_error *clear_err = buf;
+               struct resource res;
+
+               if (clear_err->cleared) {
+                       /* clearing the poison list we keep track of */
+                       __nvdimm_forget_poison(nvdimm_bus, clear_err->address,
+                                       clear_err->cleared);
+
+                       /* now sync the badblocks lists */
+                       res.start = clear_err->address;
+                       res.end = clear_err->address + clear_err->cleared - 1;
+                       __nvdimm_bus_badblocks_clear(nvdimm_bus, &res);
+               }
+       }
        nvdimm_bus_unlock(&nvdimm_bus->dev);
 
        if (copy_to_user(p, buf, buf_len))
 
 }
 EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison);
 
-void nvdimm_clear_from_poison_list(struct nvdimm_bus *nvdimm_bus,
-               phys_addr_t start, unsigned int len)
+void __nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus, phys_addr_t start,
+               unsigned int len)
 {
        struct list_head *poison_list = &nvdimm_bus->poison_list;
        u64 clr_end = start + len - 1;
        struct nd_poison *pl, *next;
 
-       nvdimm_bus_lock(&nvdimm_bus->dev);
+       lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
+
        WARN_ON_ONCE(list_empty(poison_list));
 
        /*
                        continue;
                }
        }
+}
+EXPORT_SYMBOL_GPL(__nvdimm_forget_poison);
+
+void nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus,
+               phys_addr_t start, unsigned int len)
+{
+       nvdimm_bus_lock(&nvdimm_bus->dev);
+       __nvdimm_forget_poison(nvdimm_bus, start, len);
        nvdimm_bus_unlock(&nvdimm_bus->dev);
 }
-EXPORT_SYMBOL_GPL(nvdimm_clear_from_poison_list);
+EXPORT_SYMBOL_GPL(nvdimm_forget_poison);
 
 #ifdef CONFIG_BLK_DEV_INTEGRITY
 int nd_integrity_init(struct gendisk *disk, unsigned long meta_size)
 
        device_for_each_child(dev, &event, child_notify);
 }
 
+int nvdimm_region_badblocks_clear(struct device *dev, void *data)
+{
+       struct resource *res = (struct resource *)data;
+       struct nd_region *nd_region;
+       resource_size_t ndr_end;
+       sector_t sector;
+
+       /* make sure device is a region */
+       if (!is_nd_pmem(dev))
+               return 0;
+
+       nd_region = to_nd_region(dev);
+       ndr_end = nd_region->ndr_start + nd_region->ndr_size - 1;
+
+       /* make sure we are in the region */
+       if (res->start < nd_region->ndr_start || res->end > ndr_end)
+               return 0;
+
+       sector = (res->start - nd_region->ndr_start) >> 9;
+       badblocks_clear(&nd_region->bb, sector, resource_size(res) >> 9);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(nvdimm_region_badblocks_clear);
+
 static struct nd_device_driver nd_region_driver = {
        .probe = nd_region_probe,
        .remove = nd_region_remove,
 
 }
 
 int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length);
-void nvdimm_clear_from_poison_list(struct nvdimm_bus *nvdimm_bus,
+void nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus,
+               phys_addr_t start, unsigned int len);
+void __nvdimm_forget_poison(struct nvdimm_bus *nvdimm_bus,
                phys_addr_t start, unsigned int len);
 struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
                struct nvdimm_bus_descriptor *nfit_desc);
 u64 nd_fletcher64(void *addr, size_t len, bool le);
 void nvdimm_flush(struct nd_region *nd_region);
 int nvdimm_has_flush(struct nd_region *nd_region);
+int nvdimm_region_badblocks_clear(struct device *dev, void *data);
+void __nvdimm_bus_badblocks_clear(struct nvdimm_bus *nvdimm_bus,
+               struct resource *res);
 #endif /* __LIBNVDIMM_H__ */