libnvdimm, nvdimm: dimm driver and base libnvdimm device-driver infrastructure
* Implement the device-model infrastructure for loading modules and attaching drivers to nvdimm devices. This is a simple association of a nd-device-type number with a driver that has a bitmask of supported device types. To facilitate userspace bind/unbind operations 'modalias' and 'devtype', that also appear in the uevent, are added as generic sysfs attributes for all nvdimm devices. The reason for the device-type number is to support sub-types within a given parent devtype, be it a vendor-specific sub-type or otherwise. * The first consumer of this infrastructure is the driver for dimm devices. It simply uses control messages to retrieve and store the configuration-data image (label set) from each dimm. Note: nd_device_register() arranges for asynchronous registration of nvdimm bus devices by default. Cc: Greg KH <gregkh@linuxfoundation.org> Cc: Neil Brown <neilb@suse.de> 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>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
* General Public License for more details.
|
||||
*/
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/ndctl.h>
|
||||
#include <linux/slab.h>
|
||||
@@ -18,9 +19,115 @@
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mm.h>
|
||||
#include "nd-core.h"
|
||||
#include "nd.h"
|
||||
|
||||
static DEFINE_IDA(dimm_ida);
|
||||
|
||||
/*
|
||||
* Retrieve bus and dimm handle and return if this bus supports
|
||||
* get_config_data commands
|
||||
*/
|
||||
static int __validate_dimm(struct nvdimm_drvdata *ndd)
|
||||
{
|
||||
struct nvdimm *nvdimm;
|
||||
|
||||
if (!ndd)
|
||||
return -EINVAL;
|
||||
|
||||
nvdimm = to_nvdimm(ndd->dev);
|
||||
|
||||
if (!nvdimm->dsm_mask)
|
||||
return -ENXIO;
|
||||
if (!test_bit(ND_CMD_GET_CONFIG_DATA, nvdimm->dsm_mask))
|
||||
return -ENXIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int validate_dimm(struct nvdimm_drvdata *ndd)
|
||||
{
|
||||
int rc = __validate_dimm(ndd);
|
||||
|
||||
if (rc && ndd)
|
||||
dev_dbg(ndd->dev, "%pf: %s error: %d\n",
|
||||
__builtin_return_address(0), __func__, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* nvdimm_init_nsarea - determine the geometry of a dimm's namespace area
|
||||
* @nvdimm: dimm to initialize
|
||||
*/
|
||||
int nvdimm_init_nsarea(struct nvdimm_drvdata *ndd)
|
||||
{
|
||||
struct nd_cmd_get_config_size *cmd = &ndd->nsarea;
|
||||
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(ndd->dev);
|
||||
struct nvdimm_bus_descriptor *nd_desc;
|
||||
int rc = validate_dimm(ndd);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (cmd->config_size)
|
||||
return 0; /* already valid */
|
||||
|
||||
memset(cmd, 0, sizeof(*cmd));
|
||||
nd_desc = nvdimm_bus->nd_desc;
|
||||
return nd_desc->ndctl(nd_desc, to_nvdimm(ndd->dev),
|
||||
ND_CMD_GET_CONFIG_SIZE, cmd, sizeof(*cmd));
|
||||
}
|
||||
|
||||
int nvdimm_init_config_data(struct nvdimm_drvdata *ndd)
|
||||
{
|
||||
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(ndd->dev);
|
||||
struct nd_cmd_get_config_data_hdr *cmd;
|
||||
struct nvdimm_bus_descriptor *nd_desc;
|
||||
int rc = validate_dimm(ndd);
|
||||
u32 max_cmd_size, config_size;
|
||||
size_t offset;
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (ndd->data)
|
||||
return 0;
|
||||
|
||||
if (ndd->nsarea.status || ndd->nsarea.max_xfer == 0)
|
||||
return -ENXIO;
|
||||
|
||||
ndd->data = kmalloc(ndd->nsarea.config_size, GFP_KERNEL);
|
||||
if (!ndd->data)
|
||||
ndd->data = vmalloc(ndd->nsarea.config_size);
|
||||
|
||||
if (!ndd->data)
|
||||
return -ENOMEM;
|
||||
|
||||
max_cmd_size = min_t(u32, PAGE_SIZE, ndd->nsarea.max_xfer);
|
||||
cmd = kzalloc(max_cmd_size + sizeof(*cmd), GFP_KERNEL);
|
||||
if (!cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
nd_desc = nvdimm_bus->nd_desc;
|
||||
for (config_size = ndd->nsarea.config_size, offset = 0;
|
||||
config_size; config_size -= cmd->in_length,
|
||||
offset += cmd->in_length) {
|
||||
cmd->in_length = min(config_size, max_cmd_size);
|
||||
cmd->in_offset = offset;
|
||||
rc = nd_desc->ndctl(nd_desc, to_nvdimm(ndd->dev),
|
||||
ND_CMD_GET_CONFIG_DATA, cmd,
|
||||
cmd->in_length + sizeof(*cmd));
|
||||
if (rc || cmd->status) {
|
||||
rc = -ENXIO;
|
||||
break;
|
||||
}
|
||||
memcpy(ndd->data + offset, cmd->out_buf, cmd->in_length);
|
||||
}
|
||||
dev_dbg(ndd->dev, "%s: len: %zu rc: %d\n", __func__, offset, rc);
|
||||
kfree(cmd);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void nvdimm_release(struct device *dev)
|
||||
{
|
||||
struct nvdimm *nvdimm = to_nvdimm(dev);
|
||||
@@ -111,14 +218,33 @@ struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
|
||||
dev_set_name(dev, "nmem%d", nvdimm->id);
|
||||
dev->parent = &nvdimm_bus->dev;
|
||||
dev->type = &nvdimm_device_type;
|
||||
dev->bus = &nvdimm_bus_type;
|
||||
dev->devt = MKDEV(nvdimm_major, nvdimm->id);
|
||||
dev->groups = groups;
|
||||
if (device_register(dev) != 0) {
|
||||
put_device(dev);
|
||||
return NULL;
|
||||
}
|
||||
nd_device_register(dev);
|
||||
|
||||
return nvdimm;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nvdimm_create);
|
||||
|
||||
static int count_dimms(struct device *dev, void *c)
|
||||
{
|
||||
int *count = c;
|
||||
|
||||
if (is_nvdimm(dev))
|
||||
(*count)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nvdimm_bus_check_dimm_count(struct nvdimm_bus *nvdimm_bus, int dimm_count)
|
||||
{
|
||||
int count = 0;
|
||||
/* Flush any possible dimm registration failures */
|
||||
nd_synchronize();
|
||||
|
||||
device_for_each_child(&nvdimm_bus->dev, &count, count_dimms);
|
||||
dev_dbg(&nvdimm_bus->dev, "%s: count: %d\n", __func__, count);
|
||||
if (count != dimm_count)
|
||||
return -ENXIO;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nvdimm_bus_check_dimm_count);
|
||||
|
Reference in New Issue
Block a user