[PATCH 13/21] nd: add interleave-set state-tracking infrastructure
On platforms that have firmware support for reading/writing per-dimm label space, a portion of the dimm may be accessible via an interleave set PMEM mapping in addition to the dimm's BLK (block-data-window aperture(s)) interface. A label, stored in a "configuration data region" on the dimm, disambiguates which dimm addresses are accessed through which exclusive interface. Add infrastructure that allows the kernel to block modifications to a label in the set while any member dimm is active. Note that this is meant only for enforcing "no modifications of active labels" via the coarse ioctl command. Adding/deleting namespaces from an active interleave set will only be possible via sysfs. Another aspect of tracking interleave sets is tracking their integrity when DIMMs in a set are physically re-ordered. For this purpose we generate an "interleave-set cookie" that can be recorded in a label and validated against the current configuration. Signed-off-by: Dan Williams --- drivers/block/nd/bus.c | 41 + drivers/block/nd/core.c| 51 drivers/block/nd/dimm_devs.c | 18 drivers/block/nd/nd-private.h | 17 drivers/block/nd/nd.h |4 + drivers/block/nd/region_devs.c | 176 6 files changed, 305 insertions(+), 2 deletions(-) diff --git a/drivers/block/nd/bus.c b/drivers/block/nd/bus.c index c98fe05a4c9b..944d7d7845fe 100644 --- a/drivers/block/nd/bus.c +++ b/drivers/block/nd/bus.c @@ -79,7 +79,10 @@ static int nd_bus_probe(struct device *dev) if (!try_module_get(provider)) return -ENXIO; + nd_region_probe_start(nd_bus, dev); rc = nd_drv->probe(dev); + nd_region_probe_end(nd_bus, dev, rc); + dev_dbg(_bus->dev, "%s.probe(%s) = %d\n", dev->driver->name, dev_name(dev), rc); if (rc != 0) @@ -95,6 +98,8 @@ static int nd_bus_remove(struct device *dev) int rc; rc = nd_drv->remove(dev); + nd_region_notify_remove(nd_bus, dev, rc); + dev_dbg(_bus->dev, "%s.remove(%s) = %d\n", dev->driver->name, dev_name(dev), rc); module_put(provider); @@ -269,6 +274,33 @@ void nd_bus_destroy_ndctl(struct nd_bus *nd_bus) device_destroy(nd_class, MKDEV(nd_bus_major, nd_bus->id)); } +static void wait_nd_bus_probe_idle(struct nd_bus *nd_bus) +{ + do { + if (nd_bus->probe_active == 0) + break; + nd_bus_unlock(_bus->dev); + wait_event(nd_bus->probe_wait, nd_bus->probe_active == 0); + nd_bus_lock(_bus->dev); + } while (true); +} + +/* set_config requires an idle interleave set */ +static int nd_cmd_clear_to_send(struct nd_dimm *nd_dimm, unsigned int cmd) +{ + struct nd_bus *nd_bus; + + if (!nd_dimm || cmd != NFIT_CMD_SET_CONFIG_DATA) + return 0; + + nd_bus = walk_to_nd_bus(_dimm->dev); + wait_nd_bus_probe_idle(nd_bus); + + if (atomic_read(_dimm->busy)) + return -EBUSY; + return 0; +} + static int __nd_ioctl(struct nd_bus *nd_bus, struct nd_dimm *nd_dimm, int read_only, unsigned int cmd, unsigned long arg) { @@ -399,11 +431,18 @@ static int __nd_ioctl(struct nd_bus *nd_bus, struct nd_dimm *nd_dimm, goto out; } + nd_bus_lock(_bus->dev); + rc = nd_cmd_clear_to_send(nd_dimm, _IOC_NR(cmd)); + if (rc) + goto out_unlock; + rc = nfit_desc->nfit_ctl(nfit_desc, nd_dimm, _IOC_NR(cmd), buf, buf_len); if (rc < 0) - goto out; + goto out_unlock; if (copy_to_user(p, buf, buf_len)) rc = -EFAULT; + out_unlock: + nd_bus_unlock(_bus->dev); out: if (is_vmalloc_addr(buf)) vfree(buf); diff --git a/drivers/block/nd/core.c b/drivers/block/nd/core.c index c795e8057061..976cd5e3ebaf 100644 --- a/drivers/block/nd/core.c +++ b/drivers/block/nd/core.c @@ -31,6 +31,36 @@ static bool warn_checksum; module_param(warn_checksum, bool, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(warn_checksum, "Turn checksum errors into warnings"); +void nd_bus_lock(struct device *dev) +{ + struct nd_bus *nd_bus = walk_to_nd_bus(dev); + + if (!nd_bus) + return; + mutex_lock(_bus->reconfig_mutex); +} +EXPORT_SYMBOL(nd_bus_lock); + +void nd_bus_unlock(struct device *dev) +{ + struct nd_bus *nd_bus = walk_to_nd_bus(dev); + + if (!nd_bus) + return; + mutex_unlock(_bus->reconfig_mutex); +} +EXPORT_SYMBOL(nd_bus_unlock); + +bool is_nd_bus_locked(struct device *dev) +{ + struct nd_bus *nd_bus = walk_to_nd_bus(dev); + + if (!nd_bus) + return false; + return mutex_is_locked(_bus->reconfig_mutex); +} +EXPORT_SYMBOL(is_nd_bus_locked); + /** * nd_dimm_by_handle - lookup an nd_dimm by its corresponding nfit_handle *
[PATCH 13/21] nd: add interleave-set state-tracking infrastructure
On platforms that have firmware support for reading/writing per-dimm label space, a portion of the dimm may be accessible via an interleave set PMEM mapping in addition to the dimm's BLK (block-data-window aperture(s)) interface. A label, stored in a configuration data region on the dimm, disambiguates which dimm addresses are accessed through which exclusive interface. Add infrastructure that allows the kernel to block modifications to a label in the set while any member dimm is active. Note that this is meant only for enforcing no modifications of active labels via the coarse ioctl command. Adding/deleting namespaces from an active interleave set will only be possible via sysfs. Another aspect of tracking interleave sets is tracking their integrity when DIMMs in a set are physically re-ordered. For this purpose we generate an interleave-set cookie that can be recorded in a label and validated against the current configuration. Signed-off-by: Dan Williams dan.j.willi...@intel.com --- drivers/block/nd/bus.c | 41 + drivers/block/nd/core.c| 51 drivers/block/nd/dimm_devs.c | 18 drivers/block/nd/nd-private.h | 17 drivers/block/nd/nd.h |4 + drivers/block/nd/region_devs.c | 176 6 files changed, 305 insertions(+), 2 deletions(-) diff --git a/drivers/block/nd/bus.c b/drivers/block/nd/bus.c index c98fe05a4c9b..944d7d7845fe 100644 --- a/drivers/block/nd/bus.c +++ b/drivers/block/nd/bus.c @@ -79,7 +79,10 @@ static int nd_bus_probe(struct device *dev) if (!try_module_get(provider)) return -ENXIO; + nd_region_probe_start(nd_bus, dev); rc = nd_drv-probe(dev); + nd_region_probe_end(nd_bus, dev, rc); + dev_dbg(nd_bus-dev, %s.probe(%s) = %d\n, dev-driver-name, dev_name(dev), rc); if (rc != 0) @@ -95,6 +98,8 @@ static int nd_bus_remove(struct device *dev) int rc; rc = nd_drv-remove(dev); + nd_region_notify_remove(nd_bus, dev, rc); + dev_dbg(nd_bus-dev, %s.remove(%s) = %d\n, dev-driver-name, dev_name(dev), rc); module_put(provider); @@ -269,6 +274,33 @@ void nd_bus_destroy_ndctl(struct nd_bus *nd_bus) device_destroy(nd_class, MKDEV(nd_bus_major, nd_bus-id)); } +static void wait_nd_bus_probe_idle(struct nd_bus *nd_bus) +{ + do { + if (nd_bus-probe_active == 0) + break; + nd_bus_unlock(nd_bus-dev); + wait_event(nd_bus-probe_wait, nd_bus-probe_active == 0); + nd_bus_lock(nd_bus-dev); + } while (true); +} + +/* set_config requires an idle interleave set */ +static int nd_cmd_clear_to_send(struct nd_dimm *nd_dimm, unsigned int cmd) +{ + struct nd_bus *nd_bus; + + if (!nd_dimm || cmd != NFIT_CMD_SET_CONFIG_DATA) + return 0; + + nd_bus = walk_to_nd_bus(nd_dimm-dev); + wait_nd_bus_probe_idle(nd_bus); + + if (atomic_read(nd_dimm-busy)) + return -EBUSY; + return 0; +} + static int __nd_ioctl(struct nd_bus *nd_bus, struct nd_dimm *nd_dimm, int read_only, unsigned int cmd, unsigned long arg) { @@ -399,11 +431,18 @@ static int __nd_ioctl(struct nd_bus *nd_bus, struct nd_dimm *nd_dimm, goto out; } + nd_bus_lock(nd_bus-dev); + rc = nd_cmd_clear_to_send(nd_dimm, _IOC_NR(cmd)); + if (rc) + goto out_unlock; + rc = nfit_desc-nfit_ctl(nfit_desc, nd_dimm, _IOC_NR(cmd), buf, buf_len); if (rc 0) - goto out; + goto out_unlock; if (copy_to_user(p, buf, buf_len)) rc = -EFAULT; + out_unlock: + nd_bus_unlock(nd_bus-dev); out: if (is_vmalloc_addr(buf)) vfree(buf); diff --git a/drivers/block/nd/core.c b/drivers/block/nd/core.c index c795e8057061..976cd5e3ebaf 100644 --- a/drivers/block/nd/core.c +++ b/drivers/block/nd/core.c @@ -31,6 +31,36 @@ static bool warn_checksum; module_param(warn_checksum, bool, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(warn_checksum, Turn checksum errors into warnings); +void nd_bus_lock(struct device *dev) +{ + struct nd_bus *nd_bus = walk_to_nd_bus(dev); + + if (!nd_bus) + return; + mutex_lock(nd_bus-reconfig_mutex); +} +EXPORT_SYMBOL(nd_bus_lock); + +void nd_bus_unlock(struct device *dev) +{ + struct nd_bus *nd_bus = walk_to_nd_bus(dev); + + if (!nd_bus) + return; + mutex_unlock(nd_bus-reconfig_mutex); +} +EXPORT_SYMBOL(nd_bus_unlock); + +bool is_nd_bus_locked(struct device *dev) +{ + struct nd_bus *nd_bus = walk_to_nd_bus(dev); + + if (!nd_bus) + return false; + return mutex_is_locked(nd_bus-reconfig_mutex); +} +EXPORT_SYMBOL(is_nd_bus_locked); + /** * nd_dimm_by_handle - lookup an nd_dimm by its corresponding