Skip to content

Commit

Permalink
acpi, nfit: add dimm device notification support
Browse files Browse the repository at this point in the history
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 <[email protected]>
Tested-by: Toshi Kani <[email protected]>
Reviewed-by: Vishal Verma <[email protected]>
Acked-by: Rafael J. Wysocki <[email protected]>
Reviewed-by: Toshi Kani <[email protected]>
Signed-off-by: Dan Williams <[email protected]>
  • Loading branch information
djbw committed Aug 29, 2016
1 parent c14a868 commit ba9c8dd
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 4 deletions.
95 changes: 91 additions & 4 deletions drivers/acpi/nfit/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions drivers/acpi/nfit/nfit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions drivers/nvdimm/dimm_devs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions include/linux/libnvdimm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit ba9c8dd

Please sign in to comment.