Skip to content

Commit

Permalink
Merge branch 'libnvdimm-fixes' of git://git.kernel.org/pub/scm/linux/…
Browse files Browse the repository at this point in the history
…kernel/git/nvdimm/nvdimm

Pull libnvdimm fixes from Dan Williams:

 - Four fixes for "flush hint" support.

   Flush hints are addresses advertised by the ACPI 6+ NFIT (NVDIMM
   Firmware Interface Table) that when written and fenced guarantee that
   writes pending in platform write buffers (outside the cpu) have been
   flushed to media.  They might also be used by hypervisors as a
   trigger condition to flush guest-persistent memory ranges to storage.

    Fix a potential data corruption issue, a broken definition of the
    hint array, a wrong allocation size for the unit test implementation
    of the flush hint table, and missing NULL check in an error path.

    The unit test, while it did not prevent these bugs from being
    merged, at least triggered occasional crashes in advance of
    production usages.

 - Fix handling of ACPI DSM error status results.  The DSM mechanism
   allows communication with platform and memory device firmware.  We
   correctly parse known errors, but were silently ignoring others.

   Fix it to consistently fail any command with a non-zero status return
   that we otherwise do not interpret / handle.

* 'libnvdimm-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm:
  libnvdimm, region: fix flush hint table thinko
  nfit: fail DSMs that return non-zero status by default
  libnvdimm: fix devm_nvdimm_memremap() error path
  tools/testing/nvdimm: fix allocation range for mock flush hint tables
  nvdimm: fix PHYS_PFN/PFN_PHYS mixup
  • Loading branch information
torvalds committed Sep 29, 2016
2 parents 53061af + 595c730 commit c6169de
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 33 deletions.
48 changes: 28 additions & 20 deletions drivers/acpi/nfit/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,74 +94,70 @@ static struct acpi_device *to_acpi_dev(struct acpi_nfit_desc *acpi_desc)
return to_acpi_device(acpi_desc->dev);
}

static int xlat_status(void *buf, unsigned int cmd)
static int xlat_status(void *buf, unsigned int cmd, u32 status)
{
struct nd_cmd_clear_error *clear_err;
struct nd_cmd_ars_status *ars_status;
struct nd_cmd_ars_start *ars_start;
struct nd_cmd_ars_cap *ars_cap;
u16 flags;

switch (cmd) {
case ND_CMD_ARS_CAP:
ars_cap = buf;
if ((ars_cap->status & 0xffff) == NFIT_ARS_CAP_NONE)
if ((status & 0xffff) == NFIT_ARS_CAP_NONE)
return -ENOTTY;

/* Command failed */
if (ars_cap->status & 0xffff)
if (status & 0xffff)
return -EIO;

/* No supported scan types for this range */
flags = ND_ARS_PERSISTENT | ND_ARS_VOLATILE;
if ((ars_cap->status >> 16 & flags) == 0)
if ((status >> 16 & flags) == 0)
return -ENOTTY;
break;
case ND_CMD_ARS_START:
ars_start = buf;
/* ARS is in progress */
if ((ars_start->status & 0xffff) == NFIT_ARS_START_BUSY)
if ((status & 0xffff) == NFIT_ARS_START_BUSY)
return -EBUSY;

/* Command failed */
if (ars_start->status & 0xffff)
if (status & 0xffff)
return -EIO;
break;
case ND_CMD_ARS_STATUS:
ars_status = buf;
/* Command failed */
if (ars_status->status & 0xffff)
if (status & 0xffff)
return -EIO;
/* Check extended status (Upper two bytes) */
if (ars_status->status == NFIT_ARS_STATUS_DONE)
if (status == NFIT_ARS_STATUS_DONE)
return 0;

/* ARS is in progress */
if (ars_status->status == NFIT_ARS_STATUS_BUSY)
if (status == NFIT_ARS_STATUS_BUSY)
return -EBUSY;

/* No ARS performed for the current boot */
if (ars_status->status == NFIT_ARS_STATUS_NONE)
if (status == NFIT_ARS_STATUS_NONE)
return -EAGAIN;

/*
* ARS interrupted, either we overflowed or some other
* agent wants the scan to stop. If we didn't overflow
* then just continue with the returned results.
*/
if (ars_status->status == NFIT_ARS_STATUS_INTR) {
if (status == NFIT_ARS_STATUS_INTR) {
if (ars_status->flags & NFIT_ARS_F_OVERFLOW)
return -ENOSPC;
return 0;
}

/* Unknown status */
if (ars_status->status >> 16)
if (status >> 16)
return -EIO;
break;
case ND_CMD_CLEAR_ERROR:
clear_err = buf;
if (clear_err->status & 0xffff)
if (status & 0xffff)
return -EIO;
if (!clear_err->cleared)
return -EIO;
Expand All @@ -172,6 +168,9 @@ static int xlat_status(void *buf, unsigned int cmd)
break;
}

/* all other non-zero status results in an error */
if (status)
return -EIO;
return 0;
}

Expand All @@ -186,10 +185,10 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
struct nd_cmd_pkg *call_pkg = NULL;
const char *cmd_name, *dimm_name;
unsigned long cmd_mask, dsm_mask;
u32 offset, fw_status = 0;
acpi_handle handle;
unsigned int func;
const u8 *uuid;
u32 offset;
int rc, i;

func = cmd;
Expand Down Expand Up @@ -317,6 +316,15 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
out_obj->buffer.pointer + offset, out_size);
offset += out_size;
}

/*
* Set fw_status for all the commands with a known format to be
* later interpreted by xlat_status().
*/
if (i >= 1 && ((cmd >= ND_CMD_ARS_CAP && cmd <= ND_CMD_CLEAR_ERROR)
|| (cmd >= ND_CMD_SMART && cmd <= ND_CMD_VENDOR)))
fw_status = *(u32 *) out_obj->buffer.pointer;

if (offset + in_buf.buffer.length < buf_len) {
if (i >= 1) {
/*
Expand All @@ -325,7 +333,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
*/
rc = buf_len - offset - in_buf.buffer.length;
if (cmd_rc)
*cmd_rc = xlat_status(buf, cmd);
*cmd_rc = xlat_status(buf, cmd, fw_status);
} else {
dev_err(dev, "%s:%s underrun cmd: %s buf_len: %d out_len: %d\n",
__func__, dimm_name, cmd_name, buf_len,
Expand All @@ -335,7 +343,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
} else {
rc = 0;
if (cmd_rc)
*cmd_rc = xlat_status(buf, cmd);
*cmd_rc = xlat_status(buf, cmd, fw_status);
}

out:
Expand Down
8 changes: 7 additions & 1 deletion drivers/nvdimm/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ static struct nvdimm_map *alloc_nvdimm_map(struct device *dev,
nvdimm_map->size = size;
kref_init(&nvdimm_map->kref);

if (!request_mem_region(offset, size, dev_name(&nvdimm_bus->dev)))
if (!request_mem_region(offset, size, dev_name(&nvdimm_bus->dev))) {
dev_err(&nvdimm_bus->dev, "failed to request %pa + %zd for %s\n",
&offset, size, dev_name(dev));
goto err_request_region;
}

if (flags)
nvdimm_map->mem = memremap(offset, size, flags);
Expand Down Expand Up @@ -171,6 +174,9 @@ void *devm_nvdimm_memremap(struct device *dev, resource_size_t offset,
kref_get(&nvdimm_map->kref);
nvdimm_bus_unlock(dev);

if (!nvdimm_map)
return NULL;

if (devm_add_action_or_reset(dev, nvdimm_map_put, nvdimm_map))
return NULL;

Expand Down
22 changes: 20 additions & 2 deletions drivers/nvdimm/nd.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,28 @@ struct nvdimm_drvdata {
struct nd_region_data {
int ns_count;
int ns_active;
unsigned int flush_mask;
void __iomem *flush_wpq[0][0];
unsigned int hints_shift;
void __iomem *flush_wpq[0];
};

static inline void __iomem *ndrd_get_flush_wpq(struct nd_region_data *ndrd,
int dimm, int hint)
{
unsigned int num = 1 << ndrd->hints_shift;
unsigned int mask = num - 1;

return ndrd->flush_wpq[dimm * num + (hint & mask)];
}

static inline void ndrd_set_flush_wpq(struct nd_region_data *ndrd, int dimm,
int hint, void __iomem *flush)
{
unsigned int num = 1 << ndrd->hints_shift;
unsigned int mask = num - 1;

ndrd->flush_wpq[dimm * num + (hint & mask)] = flush;
}

static inline struct nd_namespace_index *to_namespace_index(
struct nvdimm_drvdata *ndd, int i)
{
Expand Down
22 changes: 13 additions & 9 deletions drivers/nvdimm/region_devs.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ static int nvdimm_map_flush(struct device *dev, struct nvdimm *nvdimm, int dimm,

dev_dbg(dev, "%s: map %d flush address%s\n", nvdimm_name(nvdimm),
nvdimm->num_flush, nvdimm->num_flush == 1 ? "" : "es");
for (i = 0; i < nvdimm->num_flush; i++) {
for (i = 0; i < (1 << ndrd->hints_shift); i++) {
struct resource *res = &nvdimm->flush_wpq[i];
unsigned long pfn = PHYS_PFN(res->start);
void __iomem *flush_page;
Expand All @@ -54,14 +54,15 @@ static int nvdimm_map_flush(struct device *dev, struct nvdimm *nvdimm, int dimm,

if (j < i)
flush_page = (void __iomem *) ((unsigned long)
ndrd->flush_wpq[dimm][j] & PAGE_MASK);
ndrd_get_flush_wpq(ndrd, dimm, j)
& PAGE_MASK);
else
flush_page = devm_nvdimm_ioremap(dev,
PHYS_PFN(pfn), PAGE_SIZE);
PFN_PHYS(pfn), PAGE_SIZE);
if (!flush_page)
return -ENXIO;
ndrd->flush_wpq[dimm][i] = flush_page
+ (res->start & ~PAGE_MASK);
ndrd_set_flush_wpq(ndrd, dimm, i, flush_page
+ (res->start & ~PAGE_MASK));
}

return 0;
Expand Down Expand Up @@ -93,7 +94,10 @@ int nd_region_activate(struct nd_region *nd_region)
return -ENOMEM;
dev_set_drvdata(dev, ndrd);

ndrd->flush_mask = (1 << ilog2(num_flush)) - 1;
if (!num_flush)
return 0;

ndrd->hints_shift = ilog2(num_flush);
for (i = 0; i < nd_region->ndr_mappings; i++) {
struct nd_mapping *nd_mapping = &nd_region->mapping[i];
struct nvdimm *nvdimm = nd_mapping->nvdimm;
Expand Down Expand Up @@ -900,8 +904,8 @@ void nvdimm_flush(struct nd_region *nd_region)
*/
wmb();
for (i = 0; i < nd_region->ndr_mappings; i++)
if (ndrd->flush_wpq[i][0])
writeq(1, ndrd->flush_wpq[i][idx & ndrd->flush_mask]);
if (ndrd_get_flush_wpq(ndrd, i, 0))
writeq(1, ndrd_get_flush_wpq(ndrd, i, idx));
wmb();
}
EXPORT_SYMBOL_GPL(nvdimm_flush);
Expand All @@ -925,7 +929,7 @@ int nvdimm_has_flush(struct nd_region *nd_region)

for (i = 0; i < nd_region->ndr_mappings; i++)
/* flush hints present, flushing required */
if (ndrd->flush_wpq[i][0])
if (ndrd_get_flush_wpq(ndrd, i, 0))
return 1;

/*
Expand Down
3 changes: 2 additions & 1 deletion tools/testing/nvdimm/test/nfit.c
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,8 @@ static int nfit_test0_alloc(struct nfit_test *t)
return -ENOMEM;
sprintf(t->label[i], "label%d", i);

t->flush[i] = test_alloc(t, sizeof(u64) * NUM_HINTS,
t->flush[i] = test_alloc(t, max(PAGE_SIZE,
sizeof(u64) * NUM_HINTS),
&t->flush_dma[i]);
if (!t->flush[i])
return -ENOMEM;
Expand Down

0 comments on commit c6169de

Please sign in to comment.