Skip to content

Commit

Permalink
libnvdimm, nfit: dimm/memory-devices
Browse files Browse the repository at this point in the history
Enable nvdimm devices to be registered on a nvdimm_bus.  The kernel
assigned device id for nvdimm devicesis dynamic.  If userspace needs a
more static identifier it should consult a provider-specific attribute.
In the case where NFIT is the provider, the 'nmemX/nfit/handle' or
'nmemX/nfit/serial' attributes may be used for this purpose.

Cc: Neil Brown <neilb@suse.de>
Cc: <linux-acpi@vger.kernel.org>
Cc: Greg KH <gregkh@linuxfoundation.org>
Cc: Robert Moore <robert.moore@intel.com>
Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Christoph Hellwig <hch@lst.de>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Toshi Kani <toshi.kani@hp.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
  • Loading branch information
djbw committed Jun 25, 2015
1 parent 45def22 commit e6dfb2d
Show file tree
Hide file tree
Showing 8 changed files with 321 additions and 4 deletions.
161 changes: 160 additions & 1 deletion drivers/acpi/nfit.c
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,165 @@ static const struct attribute_group *acpi_nfit_attribute_groups[] = {
NULL,
};

static struct acpi_nfit_memory_map *to_nfit_memdev(struct device *dev)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);

return __to_nfit_memdev(nfit_mem);
}

static struct acpi_nfit_control_region *to_nfit_dcr(struct device *dev)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);

return nfit_mem->dcr;
}

static ssize_t handle_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct acpi_nfit_memory_map *memdev = to_nfit_memdev(dev);

return sprintf(buf, "%#x\n", memdev->device_handle);
}
static DEVICE_ATTR_RO(handle);

static ssize_t phys_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct acpi_nfit_memory_map *memdev = to_nfit_memdev(dev);

return sprintf(buf, "%#x\n", memdev->physical_id);
}
static DEVICE_ATTR_RO(phys_id);

static ssize_t vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev);

return sprintf(buf, "%#x\n", dcr->vendor_id);
}
static DEVICE_ATTR_RO(vendor);

static ssize_t rev_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev);

return sprintf(buf, "%#x\n", dcr->revision_id);
}
static DEVICE_ATTR_RO(rev_id);

static ssize_t device_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev);

return sprintf(buf, "%#x\n", dcr->device_id);
}
static DEVICE_ATTR_RO(device);

static ssize_t format_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev);

return sprintf(buf, "%#x\n", dcr->code);
}
static DEVICE_ATTR_RO(format);

static ssize_t serial_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev);

return sprintf(buf, "%#x\n", dcr->serial_number);
}
static DEVICE_ATTR_RO(serial);

static struct attribute *acpi_nfit_dimm_attributes[] = {
&dev_attr_handle.attr,
&dev_attr_phys_id.attr,
&dev_attr_vendor.attr,
&dev_attr_device.attr,
&dev_attr_format.attr,
&dev_attr_serial.attr,
&dev_attr_rev_id.attr,
NULL,
};

static umode_t acpi_nfit_dimm_attr_visible(struct kobject *kobj,
struct attribute *a, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);

if (to_nfit_dcr(dev))
return a->mode;
else
return 0;
}

static struct attribute_group acpi_nfit_dimm_attribute_group = {
.name = "nfit",
.attrs = acpi_nfit_dimm_attributes,
.is_visible = acpi_nfit_dimm_attr_visible,
};

static const struct attribute_group *acpi_nfit_dimm_attribute_groups[] = {
&acpi_nfit_dimm_attribute_group,
NULL,
};

static struct nvdimm *acpi_nfit_dimm_by_handle(struct acpi_nfit_desc *acpi_desc,
u32 device_handle)
{
struct nfit_mem *nfit_mem;

list_for_each_entry(nfit_mem, &acpi_desc->dimms, list)
if (__to_nfit_memdev(nfit_mem)->device_handle == device_handle)
return nfit_mem->nvdimm;

return NULL;
}

static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
{
struct nfit_mem *nfit_mem;

list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
struct nvdimm *nvdimm;
unsigned long flags = 0;
u32 device_handle;

device_handle = __to_nfit_memdev(nfit_mem)->device_handle;
nvdimm = acpi_nfit_dimm_by_handle(acpi_desc, device_handle);
if (nvdimm) {
/*
* If for some reason we find multiple DCRs the
* first one wins
*/
dev_err(acpi_desc->dev, "duplicate DCR detected: %s\n",
nvdimm_name(nvdimm));
continue;
}

if (nfit_mem->bdw && nfit_mem->memdev_pmem)
flags |= NDD_ALIASING;

nvdimm = nvdimm_create(acpi_desc->nvdimm_bus, nfit_mem,
acpi_nfit_dimm_attribute_groups, flags);
if (!nvdimm)
return -ENOMEM;

nfit_mem->nvdimm = nvdimm;
}

return 0;
}

static int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, acpi_size sz)
{
struct device *dev = acpi_desc->dev;
Expand Down Expand Up @@ -408,7 +567,7 @@ static int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, acpi_size sz)
if (nfit_mem_init(acpi_desc) != 0)
return -ENOMEM;

return 0;
return acpi_nfit_register_dimms(acpi_desc);
}

static int acpi_nfit_add(struct acpi_device *adev)
Expand Down
1 change: 1 addition & 0 deletions drivers/acpi/nfit.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct nfit_memdev {

/* assembled tables for a given dimm/memory-device */
struct nfit_mem {
struct nvdimm *nvdimm;
struct acpi_nfit_memory_map *memdev_dcr;
struct acpi_nfit_memory_map *memdev_pmem;
struct acpi_nfit_control_region *dcr;
Expand Down
1 change: 1 addition & 0 deletions drivers/nvdimm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ obj-$(CONFIG_LIBNVDIMM) += libnvdimm.o

libnvdimm-y := core.o
libnvdimm-y += bus.o
libnvdimm-y += dimm_devs.o
14 changes: 13 additions & 1 deletion drivers/nvdimm/bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/uaccess.h>
#include <linux/fcntl.h>
#include <linux/async.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/io.h>
Expand All @@ -21,6 +22,10 @@
static int nvdimm_bus_major;
static struct class *nd_class;

struct bus_type nvdimm_bus_type = {
.name = "nd",
};

int nvdimm_bus_create_ndctl(struct nvdimm_bus *nvdimm_bus)
{
dev_t devt = MKDEV(nvdimm_bus_major, nvdimm_bus->id);
Expand Down Expand Up @@ -59,9 +64,13 @@ int __init nvdimm_bus_init(void)
{
int rc;

rc = bus_register(&nvdimm_bus_type);
if (rc)
return rc;

rc = register_chrdev(0, "ndctl", &nvdimm_bus_fops);
if (rc < 0)
return rc;
goto err_chrdev;
nvdimm_bus_major = rc;

nd_class = class_create(THIS_MODULE, "nd");
Expand All @@ -72,6 +81,8 @@ int __init nvdimm_bus_init(void)

err_class:
unregister_chrdev(nvdimm_bus_major, "ndctl");
err_chrdev:
bus_unregister(&nvdimm_bus_type);

return rc;
}
Expand All @@ -80,4 +91,5 @@ void __exit nvdimm_bus_exit(void)
{
class_destroy(nd_class);
unregister_chrdev(nvdimm_bus_major, "ndctl");
bus_unregister(&nvdimm_bus_type);
}
33 changes: 31 additions & 2 deletions drivers/nvdimm/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
#include <linux/slab.h>
#include "nd-core.h"

static LIST_HEAD(nvdimm_bus_list);
static DEFINE_MUTEX(nvdimm_bus_list_mutex);
LIST_HEAD(nvdimm_bus_list);
DEFINE_MUTEX(nvdimm_bus_list_mutex);
static DEFINE_IDA(nd_ida);

static void nvdimm_bus_release(struct device *dev)
Expand Down Expand Up @@ -48,6 +48,19 @@ struct nvdimm_bus_descriptor *to_nd_desc(struct nvdimm_bus *nvdimm_bus)
}
EXPORT_SYMBOL_GPL(to_nd_desc);

struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev)
{
struct device *dev;

for (dev = nd_dev; dev; dev = dev->parent)
if (dev->release == nvdimm_bus_release)
break;
dev_WARN_ONCE(nd_dev, !dev, "invalid dev, not on nd bus\n");
if (dev)
return to_nvdimm_bus(dev);
return NULL;
}

static const char *nvdimm_bus_provider(struct nvdimm_bus *nvdimm_bus)
{
struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
Expand Down Expand Up @@ -121,6 +134,21 @@ struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
}
EXPORT_SYMBOL_GPL(nvdimm_bus_register);

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
device_unregister(dev);
return 0;
}

void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus)
{
if (!nvdimm_bus)
Expand All @@ -130,6 +158,7 @@ void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus)
list_del_init(&nvdimm_bus->list);
mutex_unlock(&nvdimm_bus_list_mutex);

device_for_each_child(&nvdimm_bus->dev, NULL, child_unregister);
nvdimm_bus_destroy_ndctl(nvdimm_bus);

device_unregister(&nvdimm_bus->dev);
Expand Down
92 changes: 92 additions & 0 deletions drivers/nvdimm/dimm_devs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright(c) 2013-2015 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include "nd-core.h"

static DEFINE_IDA(dimm_ida);

static void nvdimm_release(struct device *dev)
{
struct nvdimm *nvdimm = to_nvdimm(dev);

ida_simple_remove(&dimm_ida, nvdimm->id);
kfree(nvdimm);
}

static struct device_type nvdimm_device_type = {
.name = "nvdimm",
.release = nvdimm_release,
};

static bool is_nvdimm(struct device *dev)
{
return dev->type == &nvdimm_device_type;
}

struct nvdimm *to_nvdimm(struct device *dev)
{
struct nvdimm *nvdimm = container_of(dev, struct nvdimm, dev);

WARN_ON(!is_nvdimm(dev));
return nvdimm;
}
EXPORT_SYMBOL_GPL(to_nvdimm);

const char *nvdimm_name(struct nvdimm *nvdimm)
{
return dev_name(&nvdimm->dev);
}
EXPORT_SYMBOL_GPL(nvdimm_name);

void *nvdimm_provider_data(struct nvdimm *nvdimm)
{
return nvdimm->provider_data;
}
EXPORT_SYMBOL_GPL(nvdimm_provider_data);

struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
const struct attribute_group **groups, unsigned long flags)
{
struct nvdimm *nvdimm = kzalloc(sizeof(*nvdimm), GFP_KERNEL);
struct device *dev;

if (!nvdimm)
return NULL;

nvdimm->id = ida_simple_get(&dimm_ida, 0, 0, GFP_KERNEL);
if (nvdimm->id < 0) {
kfree(nvdimm);
return NULL;
}
nvdimm->provider_data = provider_data;
nvdimm->flags = flags;

dev = &nvdimm->dev;
dev_set_name(dev, "nmem%d", nvdimm->id);
dev->parent = &nvdimm_bus->dev;
dev->type = &nvdimm_device_type;
dev->bus = &nvdimm_bus_type;
dev->groups = groups;
if (device_register(dev) != 0) {
put_device(dev);
return NULL;
}

return nvdimm;
}
EXPORT_SYMBOL_GPL(nvdimm_create);
Loading

0 comments on commit e6dfb2d

Please sign in to comment.