Merge branch 'smp-hotplug-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[deliverable/linux.git] / drivers / nvdimm / bus.c
index 19f822d7f65274cb3e4a737240b0bdbfd12a8548..458daf9273362a19cc26d6a4f2c9113764f493b8 100644 (file)
@@ -31,6 +31,7 @@
 int nvdimm_major;
 static int nvdimm_bus_major;
 static struct class *nd_class;
+static DEFINE_IDA(nd_ida);
 
 static int to_nd_device_type(struct device *dev)
 {
@@ -40,6 +41,8 @@ static int to_nd_device_type(struct device *dev)
                return ND_DEVICE_REGION_PMEM;
        else if (is_nd_blk(dev))
                return ND_DEVICE_REGION_BLK;
+       else if (is_nd_dax(dev))
+               return ND_DEVICE_DAX_PMEM;
        else if (is_nd_pmem(dev->parent) || is_nd_blk(dev->parent))
                return nd_region_to_nstype(to_nd_region(dev->parent));
 
@@ -58,20 +61,13 @@ static int nvdimm_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
                        to_nd_device_type(dev));
 }
 
-static int nvdimm_bus_match(struct device *dev, struct device_driver *drv)
-{
-       struct nd_device_driver *nd_drv = to_nd_device_driver(drv);
-
-       return !!test_bit(to_nd_device_type(dev), &nd_drv->type);
-}
-
 static struct module *to_bus_provider(struct device *dev)
 {
        /* pin bus providers while regions are enabled */
        if (is_nd_pmem(dev) || is_nd_blk(dev)) {
                struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
 
-               return nvdimm_bus->module;
+               return nvdimm_bus->nd_desc->module;
        }
        return NULL;
 }
@@ -122,9 +118,10 @@ static int nvdimm_bus_remove(struct device *dev)
        struct nd_device_driver *nd_drv = to_nd_device_driver(dev->driver);
        struct module *provider = to_bus_provider(dev);
        struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
-       int rc;
+       int rc = 0;
 
-       rc = nd_drv->remove(dev);
+       if (nd_drv->remove)
+               rc = nd_drv->remove(dev);
        nd_region_disable(nvdimm_bus, dev);
 
        dev_dbg(&nvdimm_bus->dev, "%s.remove(%s) = %d\n", dev->driver->name,
@@ -133,6 +130,21 @@ static int nvdimm_bus_remove(struct device *dev)
        return rc;
 }
 
+static void nvdimm_bus_shutdown(struct device *dev)
+{
+       struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
+       struct nd_device_driver *nd_drv = NULL;
+
+       if (dev->driver)
+               nd_drv = to_nd_device_driver(dev->driver);
+
+       if (nd_drv && nd_drv->shutdown) {
+               nd_drv->shutdown(dev);
+               dev_dbg(&nvdimm_bus->dev, "%s.shutdown(%s)\n",
+                               dev->driver->name, dev_name(dev));
+       }
+}
+
 void nd_device_notify(struct device *dev, enum nvdimm_event event)
 {
        device_lock(dev);
@@ -205,14 +217,187 @@ long nvdimm_clear_poison(struct device *dev, phys_addr_t phys,
 }
 EXPORT_SYMBOL_GPL(nvdimm_clear_poison);
 
+static int nvdimm_bus_match(struct device *dev, struct device_driver *drv);
+
 static struct bus_type nvdimm_bus_type = {
        .name = "nd",
        .uevent = nvdimm_bus_uevent,
        .match = nvdimm_bus_match,
        .probe = nvdimm_bus_probe,
        .remove = nvdimm_bus_remove,
+       .shutdown = nvdimm_bus_shutdown,
 };
 
+static void nvdimm_bus_release(struct device *dev)
+{
+       struct nvdimm_bus *nvdimm_bus;
+
+       nvdimm_bus = container_of(dev, struct nvdimm_bus, dev);
+       ida_simple_remove(&nd_ida, nvdimm_bus->id);
+       kfree(nvdimm_bus);
+}
+
+static bool is_nvdimm_bus(struct device *dev)
+{
+       return dev->release == nvdimm_bus_release;
+}
+
+struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev)
+{
+       struct device *dev;
+
+       for (dev = nd_dev; dev; dev = dev->parent)
+               if (is_nvdimm_bus(dev))
+                       break;
+       dev_WARN_ONCE(nd_dev, !dev, "invalid dev, not on nd bus\n");
+       if (dev)
+               return to_nvdimm_bus(dev);
+       return NULL;
+}
+
+struct nvdimm_bus *to_nvdimm_bus(struct device *dev)
+{
+       struct nvdimm_bus *nvdimm_bus;
+
+       nvdimm_bus = container_of(dev, struct nvdimm_bus, dev);
+       WARN_ON(!is_nvdimm_bus(dev));
+       return nvdimm_bus;
+}
+EXPORT_SYMBOL_GPL(to_nvdimm_bus);
+
+struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
+               struct nvdimm_bus_descriptor *nd_desc)
+{
+       struct nvdimm_bus *nvdimm_bus;
+       int rc;
+
+       nvdimm_bus = kzalloc(sizeof(*nvdimm_bus), GFP_KERNEL);
+       if (!nvdimm_bus)
+               return NULL;
+       INIT_LIST_HEAD(&nvdimm_bus->list);
+       INIT_LIST_HEAD(&nvdimm_bus->mapping_list);
+       INIT_LIST_HEAD(&nvdimm_bus->poison_list);
+       init_waitqueue_head(&nvdimm_bus->probe_wait);
+       nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL);
+       mutex_init(&nvdimm_bus->reconfig_mutex);
+       if (nvdimm_bus->id < 0) {
+               kfree(nvdimm_bus);
+               return NULL;
+       }
+       nvdimm_bus->nd_desc = nd_desc;
+       nvdimm_bus->dev.parent = parent;
+       nvdimm_bus->dev.release = nvdimm_bus_release;
+       nvdimm_bus->dev.groups = nd_desc->attr_groups;
+       nvdimm_bus->dev.bus = &nvdimm_bus_type;
+       dev_set_name(&nvdimm_bus->dev, "ndbus%d", nvdimm_bus->id);
+       rc = device_register(&nvdimm_bus->dev);
+       if (rc) {
+               dev_dbg(&nvdimm_bus->dev, "registration failed: %d\n", rc);
+               goto err;
+       }
+
+       return nvdimm_bus;
+ err:
+       put_device(&nvdimm_bus->dev);
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(nvdimm_bus_register);
+
+void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus)
+{
+       if (!nvdimm_bus)
+               return;
+       device_unregister(&nvdimm_bus->dev);
+}
+EXPORT_SYMBOL_GPL(nvdimm_bus_unregister);
+
+static int child_unregister(struct device *dev, void *data)
+{
+       /*
+        * the singular ndctl class device per bus needs to be
+        * "device_destroy"ed, so skip it here
+        *
+        * i.e. remove classless children
+        */
+       if (dev->class)
+               /* pass */;
+       else
+               nd_device_unregister(dev, ND_SYNC);
+       return 0;
+}
+
+static void free_poison_list(struct list_head *poison_list)
+{
+       struct nd_poison *pl, *next;
+
+       list_for_each_entry_safe(pl, next, poison_list, list) {
+               list_del(&pl->list);
+               kfree(pl);
+       }
+       list_del_init(poison_list);
+}
+
+static int nd_bus_remove(struct device *dev)
+{
+       struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev);
+
+       mutex_lock(&nvdimm_bus_list_mutex);
+       list_del_init(&nvdimm_bus->list);
+       mutex_unlock(&nvdimm_bus_list_mutex);
+
+       nd_synchronize();
+       device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister);
+
+       nvdimm_bus_lock(&nvdimm_bus->dev);
+       free_poison_list(&nvdimm_bus->poison_list);
+       nvdimm_bus_unlock(&nvdimm_bus->dev);
+
+       nvdimm_bus_destroy_ndctl(nvdimm_bus);
+
+       return 0;
+}
+
+static int nd_bus_probe(struct device *dev)
+{
+       struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev);
+       int rc;
+
+       rc = nvdimm_bus_create_ndctl(nvdimm_bus);
+       if (rc)
+               return rc;
+
+       mutex_lock(&nvdimm_bus_list_mutex);
+       list_add_tail(&nvdimm_bus->list, &nvdimm_bus_list);
+       mutex_unlock(&nvdimm_bus_list_mutex);
+
+       /* enable bus provider attributes to look up their local context */
+       dev_set_drvdata(dev, nvdimm_bus->nd_desc);
+
+       return 0;
+}
+
+static struct nd_device_driver nd_bus_driver = {
+       .probe = nd_bus_probe,
+       .remove = nd_bus_remove,
+       .drv = {
+               .name = "nd_bus",
+               .suppress_bind_attrs = true,
+               .bus = &nvdimm_bus_type,
+               .owner = THIS_MODULE,
+               .mod_name = KBUILD_MODNAME,
+       },
+};
+
+static int nvdimm_bus_match(struct device *dev, struct device_driver *drv)
+{
+       struct nd_device_driver *nd_drv = to_nd_device_driver(drv);
+
+       if (is_nvdimm_bus(dev) && nd_drv == &nd_bus_driver)
+               return true;
+
+       return !!test_bit(to_nd_device_type(dev), &nd_drv->type);
+}
+
 static ASYNC_DOMAIN_EXCLUSIVE(nd_async_domain);
 
 void nd_synchronize(void)
@@ -246,6 +431,8 @@ static void nd_async_device_unregister(void *d, async_cookie_t cookie)
 
 void __nd_device_register(struct device *dev)
 {
+       if (!dev)
+               return;
        dev->bus = &nvdimm_bus_type;
        get_device(dev);
        async_schedule_domain(nd_async_device_register, dev,
@@ -292,8 +479,8 @@ int __nd_driver_register(struct nd_device_driver *nd_drv, struct module *owner,
                return -EINVAL;
        }
 
-       if (!nd_drv->probe || !nd_drv->remove) {
-               pr_debug("->probe() and ->remove() must be specified\n");
+       if (!nd_drv->probe) {
+               pr_debug("%s ->probe() must be specified\n", mod_name);
                return -EINVAL;
        }
 
@@ -307,7 +494,7 @@ EXPORT_SYMBOL(__nd_driver_register);
 
 int nvdimm_revalidate_disk(struct gendisk *disk)
 {
-       struct device *dev = disk->driverfs_dev;
+       struct device *dev = disk_to_dev(disk)->parent;
        struct nd_region *nd_region = to_nd_region(dev->parent);
        const char *pol = nd_region->ro ? "only" : "write";
 
@@ -390,12 +577,10 @@ int nvdimm_bus_create_ndctl(struct nvdimm_bus *nvdimm_bus)
        dev = device_create(nd_class, &nvdimm_bus->dev, devt, nvdimm_bus,
                        "ndctl%d", nvdimm_bus->id);
 
-       if (IS_ERR(dev)) {
+       if (IS_ERR(dev))
                dev_dbg(&nvdimm_bus->dev, "failed to register ndctl%d: %ld\n",
                                nvdimm_bus->id, PTR_ERR(dev));
-               return PTR_ERR(dev);
-       }
-       return 0;
+       return PTR_ERR_OR_ZERO(dev);
 }
 
 void nvdimm_bus_destroy_ndctl(struct nvdimm_bus *nvdimm_bus)
@@ -439,6 +624,12 @@ static const struct nd_cmd_desc __nd_cmd_dimm_descs[] = {
                .out_num = 3,
                .out_sizes = { 4, 4, UINT_MAX, },
        },
+       [ND_CMD_CALL] = {
+               .in_num = 2,
+               .in_sizes = { sizeof(struct nd_cmd_pkg), UINT_MAX, },
+               .out_num = 1,
+               .out_sizes = { UINT_MAX, },
+       },
 };
 
 const struct nd_cmd_desc *nd_cmd_dimm_desc(int cmd)
@@ -473,6 +664,12 @@ static const struct nd_cmd_desc __nd_cmd_bus_descs[] = {
                .out_num = 3,
                .out_sizes = { 4, 4, 8, },
        },
+       [ND_CMD_CALL] = {
+               .in_num = 2,
+               .in_sizes = { sizeof(struct nd_cmd_pkg), UINT_MAX, },
+               .out_num = 1,
+               .out_sizes = { UINT_MAX, },
+       },
 };
 
 const struct nd_cmd_desc *nd_cmd_bus_desc(int cmd)
@@ -500,6 +697,10 @@ u32 nd_cmd_in_size(struct nvdimm *nvdimm, int cmd,
                struct nd_cmd_vendor_hdr *hdr = buf;
 
                return hdr->in_length;
+       } else if (cmd == ND_CMD_CALL) {
+               struct nd_cmd_pkg *pkg = buf;
+
+               return pkg->nd_size_in;
        }
 
        return UINT_MAX;
@@ -522,6 +723,12 @@ u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
                return out_field[1];
        else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2)
                return out_field[1] - 8;
+       else if (cmd == ND_CMD_CALL) {
+               struct nd_cmd_pkg *pkg = (struct nd_cmd_pkg *) in_field;
+
+               return pkg->nd_size_out;
+       }
+
 
        return UINT_MAX;
 }
@@ -588,25 +795,31 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
        unsigned int cmd = _IOC_NR(ioctl_cmd);
        void __user *p = (void __user *) arg;
        struct device *dev = &nvdimm_bus->dev;
+       struct nd_cmd_pkg pkg;
        const char *cmd_name, *dimm_name;
-       unsigned long dsm_mask;
+       unsigned long cmd_mask;
        void *buf;
        int rc, i;
 
        if (nvdimm) {
                desc = nd_cmd_dimm_desc(cmd);
                cmd_name = nvdimm_cmd_name(cmd);
-               dsm_mask = nvdimm->dsm_mask ? *(nvdimm->dsm_mask) : 0;
+               cmd_mask = nvdimm->cmd_mask;
                dimm_name = dev_name(&nvdimm->dev);
        } else {
                desc = nd_cmd_bus_desc(cmd);
                cmd_name = nvdimm_bus_cmd_name(cmd);
-               dsm_mask = nd_desc->dsm_mask;
+               cmd_mask = nd_desc->cmd_mask;
                dimm_name = "bus";
        }
 
+       if (cmd == ND_CMD_CALL) {
+               if (copy_from_user(&pkg, p, sizeof(pkg)))
+                       return -EFAULT;
+       }
+
        if (!desc || (desc->out_num + desc->in_num == 0) ||
-                       !test_bit(cmd, &dsm_mask))
+                       !test_bit(cmd, &cmd_mask))
                return -ENOTTY;
 
        /* fail write commands (when read-only) */
@@ -616,6 +829,7 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
                case ND_CMD_SET_CONFIG_DATA:
                case ND_CMD_ARS_START:
                case ND_CMD_CLEAR_ERROR:
+               case ND_CMD_CALL:
                        dev_dbg(&nvdimm_bus->dev, "'%s' command while read-only.\n",
                                        nvdimm ? nvdimm_cmd_name(cmd)
                                        : nvdimm_bus_cmd_name(cmd));
@@ -643,6 +857,16 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
                in_len += in_size;
        }
 
+       if (cmd == ND_CMD_CALL) {
+               dev_dbg(dev, "%s:%s, idx: %llu, in: %zu, out: %zu, len %zu\n",
+                               __func__, dimm_name, pkg.nd_command,
+                               in_len, out_len, buf_len);
+
+               for (i = 0; i < ARRAY_SIZE(pkg.nd_reserved2); i++)
+                       if (pkg.nd_reserved2[i])
+                               return -EINVAL;
+       }
+
        /* process an output envelope */
        for (i = 0; i < desc->out_num; i++) {
                u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i,
@@ -783,6 +1007,9 @@ int __init nvdimm_bus_init(void)
 {
        int rc;
 
+       BUILD_BUG_ON(sizeof(struct nd_smart_payload) != 128);
+       BUILD_BUG_ON(sizeof(struct nd_smart_threshold_payload) != 8);
+
        rc = bus_register(&nvdimm_bus_type);
        if (rc)
                return rc;
@@ -803,8 +1030,14 @@ int __init nvdimm_bus_init(void)
                goto err_class;
        }
 
+       rc = driver_register(&nd_bus_driver.drv);
+       if (rc)
+               goto err_nd_bus;
+
        return 0;
 
+ err_nd_bus:
+       class_destroy(nd_class);
  err_class:
        unregister_chrdev(nvdimm_major, "dimmctl");
  err_dimm_chrdev:
@@ -817,8 +1050,10 @@ int __init nvdimm_bus_init(void)
 
 void nvdimm_bus_exit(void)
 {
+       driver_unregister(&nd_bus_driver.drv);
        class_destroy(nd_class);
        unregister_chrdev(nvdimm_bus_major, "ndctl");
        unregister_chrdev(nvdimm_major, "dimmctl");
        bus_unregister(&nvdimm_bus_type);
+       ida_destroy(&nd_ida);
 }
This page took 0.044403 seconds and 5 git commands to generate.