Skip to content

Commit

Permalink
libnvdimm: support for legacy (non-aliasing) nvdimms
Browse files Browse the repository at this point in the history
The libnvdimm region driver is an intermediary driver that translates
non-volatile "region"s into "namespace" sub-devices that are surfaced by
persistent memory block-device drivers (PMEM and BLK).

ACPI 6 introduces the concept that a given nvdimm may simultaneously
offer multiple access modes to its media through direct PMEM load/store
access, or windowed BLK mode.  Existing nvdimms mostly implement a PMEM
interface, some offer a BLK-like mode, but never both as ACPI 6 defines.
If an nvdimm is single interfaced, then there is no need for dimm
metadata labels.  For these devices we can take the region boundaries
directly to create a child namespace device (nd_namespace_io).

Acked-by: Christoph Hellwig <hch@lst.de>
Tested-by: Toshi Kani <toshi.kani@hp.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
  • Loading branch information
Dan Williams committed Jun 25, 2015
1 parent 1f7df6f commit 3d88002
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 8 deletions.
1 change: 1 addition & 0 deletions drivers/acpi/nfit.c
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ static struct attribute_group acpi_nfit_region_attribute_group = {
static const struct attribute_group *acpi_nfit_region_attribute_groups[] = {
&nd_region_attribute_group,
&nd_mapping_attribute_group,
&nd_device_attribute_group,
&acpi_nfit_region_attribute_group,
NULL,
};
Expand Down
2 changes: 2 additions & 0 deletions drivers/nvdimm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ libnvdimm-y += bus.o
libnvdimm-y += dimm_devs.o
libnvdimm-y += dimm.o
libnvdimm-y += region_devs.o
libnvdimm-y += region.o
libnvdimm-y += namespace_devs.o
26 changes: 26 additions & 0 deletions 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/vmalloc.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/fcntl.h>
#include <linux/async.h>
#include <linux/ndctl.h>
Expand All @@ -33,6 +34,12 @@ static int to_nd_device_type(struct device *dev)
{
if (is_nvdimm(dev))
return ND_DEVICE_DIMM;
else if (is_nd_pmem(dev))
return ND_DEVICE_REGION_PMEM;
else if (is_nd_blk(dev))
return ND_DEVICE_REGION_BLK;
else if (is_nd_pmem(dev->parent) || is_nd_blk(dev->parent))
return nd_region_to_nstype(to_nd_region(dev->parent));

return 0;
}
Expand All @@ -50,27 +57,46 @@ static int nvdimm_bus_match(struct device *dev, struct 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 NULL;
}

static int nvdimm_bus_probe(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;

if (!try_module_get(provider))
return -ENXIO;

rc = nd_drv->probe(dev);
dev_dbg(&nvdimm_bus->dev, "%s.probe(%s) = %d\n", dev->driver->name,
dev_name(dev), rc);
if (rc != 0)
module_put(provider);
return rc;
}

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;

rc = nd_drv->remove(dev);
dev_dbg(&nvdimm_bus->dev, "%s.remove(%s) = %d\n", dev->driver->name,
dev_name(dev), rc);
module_put(provider);
return rc;
}

Expand Down
44 changes: 41 additions & 3 deletions drivers/nvdimm/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,36 @@ LIST_HEAD(nvdimm_bus_list);
DEFINE_MUTEX(nvdimm_bus_list_mutex);
static DEFINE_IDA(nd_ida);

void nvdimm_bus_lock(struct device *dev)
{
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);

if (!nvdimm_bus)
return;
mutex_lock(&nvdimm_bus->reconfig_mutex);
}
EXPORT_SYMBOL(nvdimm_bus_lock);

void nvdimm_bus_unlock(struct device *dev)
{
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);

if (!nvdimm_bus)
return;
mutex_unlock(&nvdimm_bus->reconfig_mutex);
}
EXPORT_SYMBOL(nvdimm_bus_unlock);

bool is_nvdimm_bus_locked(struct device *dev)
{
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);

if (!nvdimm_bus)
return false;
return mutex_is_locked(&nvdimm_bus->reconfig_mutex);
}
EXPORT_SYMBOL(is_nvdimm_bus_locked);

static void nvdimm_bus_release(struct device *dev)
{
struct nvdimm_bus *nvdimm_bus;
Expand Down Expand Up @@ -135,8 +165,8 @@ struct attribute_group nvdimm_bus_attribute_group = {
};
EXPORT_SYMBOL_GPL(nvdimm_bus_attribute_group);

struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
struct nvdimm_bus_descriptor *nd_desc)
struct nvdimm_bus *__nvdimm_bus_register(struct device *parent,
struct nvdimm_bus_descriptor *nd_desc, struct module *module)
{
struct nvdimm_bus *nvdimm_bus;
int rc;
Expand All @@ -146,11 +176,13 @@ struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
return NULL;
INIT_LIST_HEAD(&nvdimm_bus->list);
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->module = module;
nvdimm_bus->dev.parent = parent;
nvdimm_bus->dev.release = nvdimm_bus_release;
nvdimm_bus->dev.groups = nd_desc->attr_groups;
Expand All @@ -174,7 +206,7 @@ struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
put_device(&nvdimm_bus->dev);
return NULL;
}
EXPORT_SYMBOL_GPL(nvdimm_bus_register);
EXPORT_SYMBOL_GPL(__nvdimm_bus_register);

static int child_unregister(struct device *dev, void *data)
{
Expand Down Expand Up @@ -218,7 +250,12 @@ static __init int libnvdimm_init(void)
rc = nvdimm_init();
if (rc)
goto err_dimm;
rc = nd_region_init();
if (rc)
goto err_region;
return 0;
err_region:
nvdimm_exit();
err_dimm:
nvdimm_bus_exit();
return rc;
Expand All @@ -227,6 +264,7 @@ static __init int libnvdimm_init(void)
static __exit void libnvdimm_exit(void)
{
WARN_ON(!list_empty(&nvdimm_bus_list));
nd_region_exit();
nvdimm_exit();
nvdimm_bus_exit();
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/nvdimm/dimm.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ int __init nvdimm_init(void)
return nd_driver_register(&nvdimm_driver);
}

void __exit nvdimm_exit(void)
void nvdimm_exit(void)
{
driver_unregister(&nvdimm_driver.drv);
}
Expand Down
111 changes: 111 additions & 0 deletions drivers/nvdimm/namespace_devs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/nd.h>
#include "nd.h"

static void namespace_io_release(struct device *dev)
{
struct nd_namespace_io *nsio = to_nd_namespace_io(dev);

kfree(nsio);
}

static struct device_type namespace_io_device_type = {
.name = "nd_namespace_io",
.release = namespace_io_release,
};

static ssize_t nstype_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nd_region *nd_region = to_nd_region(dev->parent);

return sprintf(buf, "%d\n", nd_region_to_nstype(nd_region));
}
static DEVICE_ATTR_RO(nstype);

static struct attribute *nd_namespace_attributes[] = {
&dev_attr_nstype.attr,
NULL,
};

static struct attribute_group nd_namespace_attribute_group = {
.attrs = nd_namespace_attributes,
};

static const struct attribute_group *nd_namespace_attribute_groups[] = {
&nd_device_attribute_group,
&nd_namespace_attribute_group,
NULL,
};

static struct device **create_namespace_io(struct nd_region *nd_region)
{
struct nd_namespace_io *nsio;
struct device *dev, **devs;
struct resource *res;

nsio = kzalloc(sizeof(*nsio), GFP_KERNEL);
if (!nsio)
return NULL;

devs = kcalloc(2, sizeof(struct device *), GFP_KERNEL);
if (!devs) {
kfree(nsio);
return NULL;
}

dev = &nsio->dev;
dev->type = &namespace_io_device_type;
dev->parent = &nd_region->dev;
res = &nsio->res;
res->name = dev_name(&nd_region->dev);
res->flags = IORESOURCE_MEM;
res->start = nd_region->ndr_start;
res->end = res->start + nd_region->ndr_size - 1;

devs[0] = dev;
return devs;
}

int nd_region_register_namespaces(struct nd_region *nd_region, int *err)
{
struct device **devs = NULL;
int i;

*err = 0;
switch (nd_region_to_nstype(nd_region)) {
case ND_DEVICE_NAMESPACE_IO:
devs = create_namespace_io(nd_region);
break;
default:
break;
}

if (!devs)
return -ENODEV;

for (i = 0; devs[i]; i++) {
struct device *dev = devs[i];

dev_set_name(dev, "namespace%d.%d", nd_region->id, i);
dev->groups = nd_namespace_attribute_groups;
nd_device_register(dev);
}
kfree(devs);

return i;
}
6 changes: 5 additions & 1 deletion drivers/nvdimm/nd-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ extern int nvdimm_major;

struct nvdimm_bus {
struct nvdimm_bus_descriptor *nd_desc;
struct module *module;
struct list_head list;
struct device dev;
int id;
struct mutex reconfig_mutex;
};

struct nvdimm {
Expand All @@ -34,6 +36,9 @@ struct nvdimm {
int id;
};

bool is_nvdimm(struct device *dev);
bool is_nd_blk(struct device *dev);
bool is_nd_pmem(struct device *dev);
struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev);
int __init nvdimm_bus_init(void);
void nvdimm_bus_exit(void);
Expand All @@ -43,5 +48,4 @@ void nd_synchronize(void);
int nvdimm_bus_register_dimms(struct nvdimm_bus *nvdimm_bus);
int nvdimm_bus_register_regions(struct nvdimm_bus *nvdimm_bus);
int nd_match_dimm(struct device *dev, void *data);
bool is_nvdimm(struct device *dev);
#endif /* __ND_CORE_H__ */
13 changes: 13 additions & 0 deletions drivers/nvdimm/nd.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ struct nvdimm_drvdata {
void *data;
};

struct nd_region_namespaces {
int count;
int active;
};

struct nd_region {
struct device dev;
u16 ndr_mappings;
Expand All @@ -41,7 +46,15 @@ enum nd_async_mode {
void nd_device_register(struct device *dev);
void nd_device_unregister(struct device *dev, enum nd_async_mode mode);
int __init nvdimm_init(void);
int __init nd_region_init(void);
void nvdimm_exit(void);
void nd_region_exit(void);
int nvdimm_init_nsarea(struct nvdimm_drvdata *ndd);
int nvdimm_init_config_data(struct nvdimm_drvdata *ndd);
struct nd_region *to_nd_region(struct device *dev);
int nd_region_to_nstype(struct nd_region *nd_region);
int nd_region_register_namespaces(struct nd_region *nd_region, int *err);
void nvdimm_bus_lock(struct device *dev);
void nvdimm_bus_unlock(struct device *dev);
bool is_nvdimm_bus_locked(struct device *dev);
#endif /* __ND_H__ */
Loading

0 comments on commit 3d88002

Please sign in to comment.