libnvdimm: Add DSM support for Address Range Scrub commands

Add support for the three ARS DSM commands:
- Query ARS Capabilities - Queries the firmware to check if a given
  range supports scrub, and if so, which type (persistent vs. volatile)
- Start ARS - Starts a scrub for a given range/type
- Query ARS Status - Checks status of a previously started scrub, and
  provides the error logs if any.

  The commands are described by the example DSM spec at:
  http://pmem.io/documents/NVDIMM_DSM_Interface_Example.pdf

Also add these commands to the nfit_test test framework, and return
canned data.

Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
This commit is contained in:
Vishal Verma 2015-07-09 13:25:36 -06:00 committed by Dan Williams
parent ec92777f2b
commit 39c686b862
4 changed files with 152 additions and 59 deletions

View File

@ -868,6 +868,7 @@ static void acpi_nfit_init_dsms(struct acpi_nfit_desc *acpi_desc)
struct acpi_device *adev;
int i;
nd_desc->dsm_mask = acpi_desc->bus_dsm_force_en;
adev = to_acpi_dev(acpi_desc);
if (!adev)
return;

View File

@ -107,6 +107,7 @@ struct acpi_nfit_desc {
struct nvdimm_bus *nvdimm_bus;
struct device *dev;
unsigned long dimm_dsm_force_en;
unsigned long bus_dsm_force_en;
int (*blk_do_io)(struct nd_blk_region *ndbr, resource_size_t dpa,
void *iobuf, u64 len, int rw);
};

View File

@ -111,6 +111,11 @@ enum {
ND_CMD_VENDOR = 9,
};
enum {
ND_ARS_VOLATILE = 1,
ND_ARS_PERSISTENT = 2,
};
static inline const char *nvdimm_bus_cmd_name(unsigned cmd)
{
static const char * const names[] = {
@ -194,4 +199,9 @@ enum nd_driver_flags {
enum {
ND_MIN_NAMESPACE_SIZE = 0x00400000,
};
enum ars_masks {
ARS_STATUS_MASK = 0x0000FFFF,
ARS_EXT_STATUS_SHIFT = 16,
};
#endif /* __NDCTL_H__ */

View File

@ -147,75 +147,153 @@ static struct nfit_test *to_nfit_test(struct device *dev)
return container_of(pdev, struct nfit_test, pdev);
}
static int nfit_test_cmd_get_config_size(struct nd_cmd_get_config_size *nd_cmd,
unsigned int buf_len)
{
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
nd_cmd->status = 0;
nd_cmd->config_size = LABEL_SIZE;
nd_cmd->max_xfer = SZ_4K;
return 0;
}
static int nfit_test_cmd_get_config_data(struct nd_cmd_get_config_data_hdr
*nd_cmd, unsigned int buf_len, void *label)
{
unsigned int len, offset = nd_cmd->in_offset;
int rc;
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
if (offset >= LABEL_SIZE)
return -EINVAL;
if (nd_cmd->in_length + sizeof(*nd_cmd) > buf_len)
return -EINVAL;
nd_cmd->status = 0;
len = min(nd_cmd->in_length, LABEL_SIZE - offset);
memcpy(nd_cmd->out_buf, label + offset, len);
rc = buf_len - sizeof(*nd_cmd) - len;
return rc;
}
static int nfit_test_cmd_set_config_data(struct nd_cmd_set_config_hdr *nd_cmd,
unsigned int buf_len, void *label)
{
unsigned int len, offset = nd_cmd->in_offset;
u32 *status;
int rc;
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
if (offset >= LABEL_SIZE)
return -EINVAL;
if (nd_cmd->in_length + sizeof(*nd_cmd) + 4 > buf_len)
return -EINVAL;
status = (void *)nd_cmd + nd_cmd->in_length + sizeof(*nd_cmd);
*status = 0;
len = min(nd_cmd->in_length, LABEL_SIZE - offset);
memcpy(label + offset, nd_cmd->in_buf, len);
rc = buf_len - sizeof(*nd_cmd) - (len + 4);
return rc;
}
static int nfit_test_cmd_ars_cap(struct nd_cmd_ars_cap *nd_cmd,
unsigned int buf_len)
{
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
nd_cmd->max_ars_out = 256;
nd_cmd->status = (ND_ARS_PERSISTENT | ND_ARS_VOLATILE) << 16;
return 0;
}
static int nfit_test_cmd_ars_start(struct nd_cmd_ars_start *nd_cmd,
unsigned int buf_len)
{
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
nd_cmd->status = 0;
return 0;
}
static int nfit_test_cmd_ars_status(struct nd_cmd_ars_status *nd_cmd,
unsigned int buf_len)
{
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
nd_cmd->out_length = 256;
nd_cmd->num_records = 0;
nd_cmd->status = 0;
return 0;
}
static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc,
struct nvdimm *nvdimm, unsigned int cmd, void *buf,
unsigned int buf_len)
{
struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc);
struct nfit_test *t = container_of(acpi_desc, typeof(*t), acpi_desc);
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
int i, rc;
int i, rc = 0;
if (!nfit_mem || !test_bit(cmd, &nfit_mem->dsm_mask))
return -ENOTTY;
if (nvdimm) {
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
/* lookup label space for the given dimm */
for (i = 0; i < ARRAY_SIZE(handle); i++)
if (__to_nfit_memdev(nfit_mem)->device_handle == handle[i])
if (!nfit_mem || !test_bit(cmd, &nfit_mem->dsm_mask))
return -ENOTTY;
/* lookup label space for the given dimm */
for (i = 0; i < ARRAY_SIZE(handle); i++)
if (__to_nfit_memdev(nfit_mem)->device_handle ==
handle[i])
break;
if (i >= ARRAY_SIZE(handle))
return -ENXIO;
switch (cmd) {
case ND_CMD_GET_CONFIG_SIZE:
rc = nfit_test_cmd_get_config_size(buf, buf_len);
break;
if (i >= ARRAY_SIZE(handle))
return -ENXIO;
case ND_CMD_GET_CONFIG_DATA:
rc = nfit_test_cmd_get_config_data(buf, buf_len,
t->label[i]);
break;
case ND_CMD_SET_CONFIG_DATA:
rc = nfit_test_cmd_set_config_data(buf, buf_len,
t->label[i]);
break;
default:
return -ENOTTY;
}
} else {
if (!nd_desc || !test_bit(cmd, &nd_desc->dsm_mask))
return -ENOTTY;
switch (cmd) {
case ND_CMD_GET_CONFIG_SIZE: {
struct nd_cmd_get_config_size *nd_cmd = buf;
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
nd_cmd->status = 0;
nd_cmd->config_size = LABEL_SIZE;
nd_cmd->max_xfer = SZ_4K;
rc = 0;
break;
}
case ND_CMD_GET_CONFIG_DATA: {
struct nd_cmd_get_config_data_hdr *nd_cmd = buf;
unsigned int len, offset = nd_cmd->in_offset;
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
if (offset >= LABEL_SIZE)
return -EINVAL;
if (nd_cmd->in_length + sizeof(*nd_cmd) > buf_len)
return -EINVAL;
nd_cmd->status = 0;
len = min(nd_cmd->in_length, LABEL_SIZE - offset);
memcpy(nd_cmd->out_buf, t->label[i] + offset, len);
rc = buf_len - sizeof(*nd_cmd) - len;
break;
}
case ND_CMD_SET_CONFIG_DATA: {
struct nd_cmd_set_config_hdr *nd_cmd = buf;
unsigned int len, offset = nd_cmd->in_offset;
u32 *status;
if (buf_len < sizeof(*nd_cmd))
return -EINVAL;
if (offset >= LABEL_SIZE)
return -EINVAL;
if (nd_cmd->in_length + sizeof(*nd_cmd) + 4 > buf_len)
return -EINVAL;
status = buf + nd_cmd->in_length + sizeof(*nd_cmd);
*status = 0;
len = min(nd_cmd->in_length, LABEL_SIZE - offset);
memcpy(t->label[i] + offset, nd_cmd->in_buf, len);
rc = buf_len - sizeof(*nd_cmd) - (len + 4);
break;
}
default:
return -ENOTTY;
switch (cmd) {
case ND_CMD_ARS_CAP:
rc = nfit_test_cmd_ars_cap(buf, buf_len);
break;
case ND_CMD_ARS_START:
rc = nfit_test_cmd_ars_start(buf, buf_len);
break;
case ND_CMD_ARS_STATUS:
rc = nfit_test_cmd_ars_status(buf, buf_len);
break;
default:
return -ENOTTY;
}
}
return rc;
@ -876,6 +954,9 @@ static void nfit_test0_setup(struct nfit_test *t)
set_bit(ND_CMD_GET_CONFIG_SIZE, &acpi_desc->dimm_dsm_force_en);
set_bit(ND_CMD_GET_CONFIG_DATA, &acpi_desc->dimm_dsm_force_en);
set_bit(ND_CMD_SET_CONFIG_DATA, &acpi_desc->dimm_dsm_force_en);
set_bit(ND_CMD_ARS_CAP, &acpi_desc->bus_dsm_force_en);
set_bit(ND_CMD_ARS_START, &acpi_desc->bus_dsm_force_en);
set_bit(ND_CMD_ARS_STATUS, &acpi_desc->bus_dsm_force_en);
nd_desc = &acpi_desc->nd_desc;
nd_desc->ndctl = nfit_test_ctl;
}