acpi, nfit: add dimm device notification support
authorDan Williams <dan.j.williams@intel.com>
Tue, 23 Aug 2016 02:28:37 +0000 (19:28 -0700)
committerDan Williams <dan.j.williams@intel.com>
Mon, 29 Aug 2016 21:55:17 +0000 (14:55 -0700)
Per "ACPI 6.1 Section 9.20.3" NVDIMM devices, children of the ACPI0012
NVDIMM Root device, can receive health event notifications.

Given that these devices are precluded from registering a notification
handler via acpi_driver.acpi_device_ops (due to no _HID), we use
acpi_install_notify_handler() directly.  The registered handler,
acpi_nvdimm_notify(), triggers a poll(2) event on the nmemX/nfit/flags
sysfs attribute when a health event notification is received.

Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Toshi Kani <toshi.kani@hpe.com>
Reviewed-by: Vishal Verma <vishal.l.verma@intel.com>
Acked-by: Rafael J. Wysocki <rafael@kernel.org>
Reviewed-by: Toshi Kani <toshi.kani@hpe.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
drivers/acpi/nfit/core.c
drivers/acpi/nfit/nfit.h
drivers/nvdimm/dimm_devs.c
include/linux/libnvdimm.h

index 8120e82..4af1db1 100644 (file)
@@ -1248,6 +1248,43 @@ static struct nvdimm *acpi_nfit_dimm_by_handle(struct acpi_nfit_desc *acpi_desc,
        return NULL;
 }
 
+static void __acpi_nvdimm_notify(struct device *dev, u32 event)
+{
+       struct nfit_mem *nfit_mem;
+       struct acpi_nfit_desc *acpi_desc;
+
+       dev_dbg(dev->parent, "%s: %s: event: %d\n", dev_name(dev), __func__,
+                       event);
+
+       if (event != NFIT_NOTIFY_DIMM_HEALTH) {
+               dev_dbg(dev->parent, "%s: unknown event: %d\n", dev_name(dev),
+                               event);
+               return;
+       }
+
+       acpi_desc = dev_get_drvdata(dev->parent);
+       if (!acpi_desc)
+               return;
+
+       /*
+        * If we successfully retrieved acpi_desc, then we know nfit_mem data
+        * is still valid.
+        */
+       nfit_mem = dev_get_drvdata(dev);
+       if (nfit_mem && nfit_mem->flags_attr)
+               sysfs_notify_dirent(nfit_mem->flags_attr);
+}
+
+static void acpi_nvdimm_notify(acpi_handle handle, u32 event, void *data)
+{
+       struct acpi_device *adev = data;
+       struct device *dev = &adev->dev;
+
+       device_lock(dev->parent);
+       __acpi_nvdimm_notify(dev, event);
+       device_unlock(dev->parent);
+}
+
 static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
                struct nfit_mem *nfit_mem, u32 device_handle)
 {
@@ -1272,6 +1309,13 @@ static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
                return force_enable_dimms ? 0 : -ENODEV;
        }
 
+       if (ACPI_FAILURE(acpi_install_notify_handler(adev_dimm->handle,
+               ACPI_DEVICE_NOTIFY, acpi_nvdimm_notify, adev_dimm))) {
+               dev_err(dev, "%s: notification registration failed\n",
+                               dev_name(&adev_dimm->dev));
+               return -ENXIO;
+       }
+
        /*
         * Until standardization materializes we need to consider 4
         * different command sets.  Note, that checking for function0 (bit0)
@@ -1310,18 +1354,38 @@ static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
        return 0;
 }
 
+static void shutdown_dimm_notify(void *data)
+{
+       struct acpi_nfit_desc *acpi_desc = data;
+       struct nfit_mem *nfit_mem;
+
+       mutex_lock(&acpi_desc->init_mutex);
+       /*
+        * Clear out the nfit_mem->flags_attr and shut down dimm event
+        * notifications.
+        */
+       list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
+               if (nfit_mem->flags_attr) {
+                       sysfs_put(nfit_mem->flags_attr);
+                       nfit_mem->flags_attr = NULL;
+               }
+               acpi_remove_notify_handler(nfit_mem->adev->handle,
+                               ACPI_DEVICE_NOTIFY, acpi_nvdimm_notify);
+       }
+       mutex_unlock(&acpi_desc->init_mutex);
+}
+
 static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
 {
        struct nfit_mem *nfit_mem;
-       int dimm_count = 0;
+       int dimm_count = 0, rc;
+       struct nvdimm *nvdimm;
 
        list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
                struct acpi_nfit_flush_address *flush;
                unsigned long flags = 0, cmd_mask;
-               struct nvdimm *nvdimm;
                u32 device_handle;
                u16 mem_flags;
-               int rc;
 
                device_handle = __to_nfit_memdev(nfit_mem)->device_handle;
                nvdimm = acpi_nfit_dimm_by_handle(acpi_desc, device_handle);
@@ -1374,7 +1438,30 @@ static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
 
        }
 
-       return nvdimm_bus_check_dimm_count(acpi_desc->nvdimm_bus, dimm_count);
+       rc = nvdimm_bus_check_dimm_count(acpi_desc->nvdimm_bus, dimm_count);
+       if (rc)
+               return rc;
+
+       /*
+        * Now that dimms are successfully registered, and async registration
+        * is flushed, attempt to enable event notification.
+        */
+       list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
+               struct kernfs_node *nfit_kernfs;
+
+               nvdimm = nfit_mem->nvdimm;
+               nfit_kernfs = sysfs_get_dirent(nvdimm_kobj(nvdimm)->sd, "nfit");
+               if (nfit_kernfs)
+                       nfit_mem->flags_attr = sysfs_get_dirent(nfit_kernfs,
+                                       "flags");
+               sysfs_put(nfit_kernfs);
+               if (!nfit_mem->flags_attr)
+                       dev_warn(acpi_desc->dev, "%s: notifications disabled\n",
+                                       nvdimm_name(nvdimm));
+       }
+
+       return devm_add_action_or_reset(acpi_desc->dev, shutdown_dimm_notify,
+                       acpi_desc);
 }
 
 static void acpi_nfit_init_dsms(struct acpi_nfit_desc *acpi_desc)
index 5237034..1319582 100644 (file)
@@ -82,6 +82,10 @@ enum nfit_root_notifiers {
        NFIT_NOTIFY_UPDATE = 0x80,
 };
 
+enum nfit_dimm_notifiers {
+       NFIT_NOTIFY_DIMM_HEALTH = 0x81,
+};
+
 struct nfit_spa {
        struct list_head list;
        struct nd_region *nd_region;
@@ -128,6 +132,7 @@ struct nfit_mem {
        struct acpi_nfit_system_address *spa_bdw;
        struct acpi_nfit_interleave *idt_dcr;
        struct acpi_nfit_interleave *idt_bdw;
+       struct kernfs_node *flags_attr;
        struct nfit_flush *nfit_flush;
        struct list_head list;
        struct acpi_device *adev;
index d9bba5e..ce75cc3 100644 (file)
@@ -263,6 +263,12 @@ const char *nvdimm_name(struct nvdimm *nvdimm)
 }
 EXPORT_SYMBOL_GPL(nvdimm_name);
 
+struct kobject *nvdimm_kobj(struct nvdimm *nvdimm)
+{
+       return &nvdimm->dev.kobj;
+}
+EXPORT_SYMBOL_GPL(nvdimm_kobj);
+
 unsigned long nvdimm_cmd_mask(struct nvdimm *nvdimm)
 {
        return nvdimm->cmd_mask;
index b519e13..ad18d05 100644 (file)
@@ -139,6 +139,7 @@ struct nd_blk_region *to_nd_blk_region(struct device *dev);
 struct nvdimm_bus_descriptor *to_nd_desc(struct nvdimm_bus *nvdimm_bus);
 struct device *to_nvdimm_bus_dev(struct nvdimm_bus *nvdimm_bus);
 const char *nvdimm_name(struct nvdimm *nvdimm);
+struct kobject *nvdimm_kobj(struct nvdimm *nvdimm);
 unsigned long nvdimm_cmd_mask(struct nvdimm *nvdimm);
 void *nvdimm_provider_data(struct nvdimm *nvdimm);
 struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,