Merge branch 'libnvdimm-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm

Pull libnvdimm fixes from Dan Williams:
 "Several fixes to the DSM (ACPI device specific method) marshaling
  implementation.

  I consider these urgent enough to send for 4.9 consideration since
  they fix the kernel's handling of ARS (Address Range Scrub) commands.
  Especially for platforms without machine-check-recovery capabilities,
  successful execution of ARS commands enables the platform to
  potentially break out of an infinite reboot problem if a media error
  is present in the boot path. There is also a one line fix for a
  device-dax read-only mapping regression.

  Commits 9a901f5495 ("acpi, nfit: fix extended status translations
  for ACPI DSMs") and 325896ffdf ("device-dax: fix private mapping
  restriction, permit read-only") are true regression fixes for changes
  introduced this cycle.

  Commit efda1b5d87 ("acpi, nfit, libnvdimm: fix / harden ars_status
  output length handling") fixes the kernel's handling of zero-length
  results, this never would have worked in the past, but we only just
  recently discovered a BIOS implementation that emits this arguably
  spec non-compliant result.

  The remaining two commits are additional fall out from thinking
  through the implications of a zero / truncated length result of the
  ARS Status command.

  In order to mitigate the risk that these changes introduce yet more
  regressions they are backstopped by a new unit test in commit
  a7de92dac9 ("tools/testing/nvdimm: unit test acpi_nfit_ctl()") that
  mocks up inputs to acpi_nfit_ctl()"

* 'libnvdimm-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm:
  device-dax: fix private mapping restriction, permit read-only
  tools/testing/nvdimm: unit test acpi_nfit_ctl()
  acpi, nfit: fix bus vs dimm confusion in xlat_status
  acpi, nfit: validate ars_status output buffer size
  acpi, nfit, libnvdimm: fix / harden ars_status output length handling
  acpi, nfit: fix extended status translations for ACPI DSMs
This commit is contained in:
Linus Torvalds 2016-12-09 11:27:22 -08:00
commit 810ac7b755
9 changed files with 326 additions and 28 deletions

View File

@ -94,7 +94,7 @@ 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, u32 status)
static int xlat_bus_status(void *buf, unsigned int cmd, u32 status)
{
struct nd_cmd_clear_error *clear_err;
struct nd_cmd_ars_status *ars_status;
@ -113,7 +113,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
flags = ND_ARS_PERSISTENT | ND_ARS_VOLATILE;
if ((status >> 16 & flags) == 0)
return -ENOTTY;
break;
return 0;
case ND_CMD_ARS_START:
/* ARS is in progress */
if ((status & 0xffff) == NFIT_ARS_START_BUSY)
@ -122,7 +122,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
/* Command failed */
if (status & 0xffff)
return -EIO;
break;
return 0;
case ND_CMD_ARS_STATUS:
ars_status = buf;
/* Command failed */
@ -146,7 +146,8 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
* then just continue with the returned results.
*/
if (status == NFIT_ARS_STATUS_INTR) {
if (ars_status->flags & NFIT_ARS_F_OVERFLOW)
if (ars_status->out_length >= 40 && (ars_status->flags
& NFIT_ARS_F_OVERFLOW))
return -ENOSPC;
return 0;
}
@ -154,7 +155,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
/* Unknown status */
if (status >> 16)
return -EIO;
break;
return 0;
case ND_CMD_CLEAR_ERROR:
clear_err = buf;
if (status & 0xffff)
@ -163,7 +164,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
return -EIO;
if (clear_err->length > clear_err->cleared)
return clear_err->cleared;
break;
return 0;
default:
break;
}
@ -174,9 +175,18 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
return 0;
}
static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
struct nvdimm *nvdimm, unsigned int cmd, void *buf,
unsigned int buf_len, int *cmd_rc)
static int xlat_status(struct nvdimm *nvdimm, void *buf, unsigned int cmd,
u32 status)
{
if (!nvdimm)
return xlat_bus_status(buf, cmd, status);
if (status)
return -EIO;
return 0;
}
int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc)
{
struct acpi_nfit_desc *acpi_desc = to_acpi_nfit_desc(nd_desc);
union acpi_object in_obj, in_buf, *out_obj;
@ -298,7 +308,8 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
for (i = 0, offset = 0; i < desc->out_num; i++) {
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i, buf,
(u32 *) out_obj->buffer.pointer);
(u32 *) out_obj->buffer.pointer,
out_obj->buffer.length - offset);
if (offset + out_size > out_obj->buffer.length) {
dev_dbg(dev, "%s:%s output object underflow cmd: %s field: %d\n",
@ -333,7 +344,8 @@ 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, fw_status);
*cmd_rc = xlat_status(nvdimm, 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,
@ -343,7 +355,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
} else {
rc = 0;
if (cmd_rc)
*cmd_rc = xlat_status(buf, cmd, fw_status);
*cmd_rc = xlat_status(nvdimm, buf, cmd, fw_status);
}
out:
@ -351,6 +363,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
return rc;
}
EXPORT_SYMBOL_GPL(acpi_nfit_ctl);
static const char *spa_type_name(u16 type)
{
@ -2001,19 +2014,32 @@ static int ars_get_status(struct acpi_nfit_desc *acpi_desc)
return cmd_rc;
}
static int ars_status_process_records(struct nvdimm_bus *nvdimm_bus,
static int ars_status_process_records(struct acpi_nfit_desc *acpi_desc,
struct nd_cmd_ars_status *ars_status)
{
struct nvdimm_bus *nvdimm_bus = acpi_desc->nvdimm_bus;
int rc;
u32 i;
/*
* First record starts at 44 byte offset from the start of the
* payload.
*/
if (ars_status->out_length < 44)
return 0;
for (i = 0; i < ars_status->num_records; i++) {
/* only process full records */
if (ars_status->out_length
< 44 + sizeof(struct nd_ars_record) * (i + 1))
break;
rc = nvdimm_bus_add_poison(nvdimm_bus,
ars_status->records[i].err_address,
ars_status->records[i].length);
if (rc)
return rc;
}
if (i < ars_status->num_records)
dev_warn(acpi_desc->dev, "detected truncated ars results\n");
return 0;
}
@ -2266,8 +2292,7 @@ static int acpi_nfit_query_poison(struct acpi_nfit_desc *acpi_desc,
if (rc < 0 && rc != -ENOSPC)
return rc;
if (ars_status_process_records(acpi_desc->nvdimm_bus,
acpi_desc->ars_status))
if (ars_status_process_records(acpi_desc, acpi_desc->ars_status))
return -ENOMEM;
return 0;

View File

@ -240,5 +240,7 @@ const u8 *to_nfit_uuid(enum nfit_uuids id);
int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *nfit, acpi_size sz);
void __acpi_nfit_notify(struct device *dev, acpi_handle handle, u32 event);
void __acpi_nvdimm_notify(struct device *dev, u32 event);
int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc);
void acpi_nfit_desc_init(struct acpi_nfit_desc *acpi_desc, struct device *dev);
#endif /* __NFIT_H__ */

View File

@ -271,7 +271,7 @@ static int check_vma(struct dax_dev *dax_dev, struct vm_area_struct *vma,
return -ENXIO;
/* prevent private mappings from being established */
if ((vma->vm_flags & VM_SHARED) != VM_SHARED) {
if ((vma->vm_flags & VM_MAYSHARE) != VM_MAYSHARE) {
dev_info(dev, "%s: %s: fail, attempted private mapping\n",
current->comm, func);
return -EINVAL;

View File

@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(nd_cmd_in_size);
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
const u32 *out_field)
const u32 *out_field, unsigned long remainder)
{
if (idx >= desc->out_num)
return UINT_MAX;
@ -727,9 +727,24 @@ u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
return in_field[1];
else if (nvdimm && cmd == ND_CMD_VENDOR && idx == 2)
return out_field[1];
else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2)
return out_field[1] - 8;
else if (cmd == ND_CMD_CALL) {
else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2) {
/*
* Per table 9-276 ARS Data in ACPI 6.1, out_field[1] is
* "Size of Output Buffer in bytes, including this
* field."
*/
if (out_field[1] < 4)
return 0;
/*
* ACPI 6.1 is ambiguous if 'status' is included in the
* output size. If we encounter an output size that
* overshoots the remainder by 4 bytes, assume it was
* including 'status'.
*/
if (out_field[1] - 8 == remainder)
return remainder;
return out_field[1] - 4;
} else if (cmd == ND_CMD_CALL) {
struct nd_cmd_pkg *pkg = (struct nd_cmd_pkg *) in_field;
return pkg->nd_size_out;
@ -876,7 +891,7 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
/* process an output envelope */
for (i = 0; i < desc->out_num; i++) {
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i,
(u32 *) in_env, (u32 *) out_env);
(u32 *) in_env, (u32 *) out_env, 0);
u32 copy;
if (out_size == UINT_MAX) {

View File

@ -143,7 +143,7 @@ u32 nd_cmd_in_size(struct nvdimm *nvdimm, int cmd,
const struct nd_cmd_desc *desc, int idx, void *buf);
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
const u32 *out_field);
const u32 *out_field, unsigned long remainder);
int nvdimm_bus_check_dimm_count(struct nvdimm_bus *nvdimm_bus, int dimm_count);
struct nd_region *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus,
struct nd_region_desc *ndr_desc);

View File

@ -14,6 +14,7 @@ ldflags-y += --wrap=devm_memremap_pages
ldflags-y += --wrap=insert_resource
ldflags-y += --wrap=remove_resource
ldflags-y += --wrap=acpi_evaluate_object
ldflags-y += --wrap=acpi_evaluate_dsm
DRIVERS := ../../../drivers
NVDIMM_SRC := $(DRIVERS)/nvdimm

View File

@ -26,14 +26,17 @@ static LIST_HEAD(iomap_head);
static struct iomap_ops {
nfit_test_lookup_fn nfit_test_lookup;
nfit_test_evaluate_dsm_fn evaluate_dsm;
struct list_head list;
} iomap_ops = {
.list = LIST_HEAD_INIT(iomap_ops.list),
};
void nfit_test_setup(nfit_test_lookup_fn lookup)
void nfit_test_setup(nfit_test_lookup_fn lookup,
nfit_test_evaluate_dsm_fn evaluate)
{
iomap_ops.nfit_test_lookup = lookup;
iomap_ops.evaluate_dsm = evaluate;
list_add_rcu(&iomap_ops.list, &iomap_head);
}
EXPORT_SYMBOL(nfit_test_setup);
@ -367,4 +370,22 @@ acpi_status __wrap_acpi_evaluate_object(acpi_handle handle, acpi_string path,
}
EXPORT_SYMBOL(__wrap_acpi_evaluate_object);
union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const u8 *uuid,
u64 rev, u64 func, union acpi_object *argv4)
{
union acpi_object *obj = ERR_PTR(-ENXIO);
struct iomap_ops *ops;
rcu_read_lock();
ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list);
if (ops)
obj = ops->evaluate_dsm(handle, uuid, rev, func, argv4);
rcu_read_unlock();
if (IS_ERR(obj))
return acpi_evaluate_dsm(handle, uuid, rev, func, argv4);
return obj;
}
EXPORT_SYMBOL(__wrap_acpi_evaluate_dsm);
MODULE_LICENSE("GPL v2");

View File

@ -23,6 +23,7 @@
#include <linux/sizes.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <nd-core.h>
#include <nfit.h>
#include <nd.h>
#include "nfit_test.h"
@ -1506,6 +1507,225 @@ static int nfit_test_blk_do_io(struct nd_blk_region *ndbr, resource_size_t dpa,
return 0;
}
static unsigned long nfit_ctl_handle;
union acpi_object *result;
static union acpi_object *nfit_test_evaluate_dsm(acpi_handle handle,
const u8 *uuid, u64 rev, u64 func, union acpi_object *argv4)
{
if (handle != &nfit_ctl_handle)
return ERR_PTR(-ENXIO);
return result;
}
static int setup_result(void *buf, size_t size)
{
result = kmalloc(sizeof(union acpi_object) + size, GFP_KERNEL);
if (!result)
return -ENOMEM;
result->package.type = ACPI_TYPE_BUFFER,
result->buffer.pointer = (void *) (result + 1);
result->buffer.length = size;
memcpy(result->buffer.pointer, buf, size);
memset(buf, 0, size);
return 0;
}
static int nfit_ctl_test(struct device *dev)
{
int rc, cmd_rc;
struct nvdimm *nvdimm;
struct acpi_device *adev;
struct nfit_mem *nfit_mem;
struct nd_ars_record *record;
struct acpi_nfit_desc *acpi_desc;
const u64 test_val = 0x0123456789abcdefULL;
unsigned long mask, cmd_size, offset;
union {
struct nd_cmd_get_config_size cfg_size;
struct nd_cmd_ars_status ars_stat;
struct nd_cmd_ars_cap ars_cap;
char buf[sizeof(struct nd_cmd_ars_status)
+ sizeof(struct nd_ars_record)];
} cmds;
adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL);
if (!adev)
return -ENOMEM;
*adev = (struct acpi_device) {
.handle = &nfit_ctl_handle,
.dev = {
.init_name = "test-adev",
},
};
acpi_desc = devm_kzalloc(dev, sizeof(*acpi_desc), GFP_KERNEL);
if (!acpi_desc)
return -ENOMEM;
*acpi_desc = (struct acpi_nfit_desc) {
.nd_desc = {
.cmd_mask = 1UL << ND_CMD_ARS_CAP
| 1UL << ND_CMD_ARS_START
| 1UL << ND_CMD_ARS_STATUS
| 1UL << ND_CMD_CLEAR_ERROR,
.module = THIS_MODULE,
.provider_name = "ACPI.NFIT",
.ndctl = acpi_nfit_ctl,
},
.dev = &adev->dev,
};
nfit_mem = devm_kzalloc(dev, sizeof(*nfit_mem), GFP_KERNEL);
if (!nfit_mem)
return -ENOMEM;
mask = 1UL << ND_CMD_SMART | 1UL << ND_CMD_SMART_THRESHOLD
| 1UL << ND_CMD_DIMM_FLAGS | 1UL << ND_CMD_GET_CONFIG_SIZE
| 1UL << ND_CMD_GET_CONFIG_DATA | 1UL << ND_CMD_SET_CONFIG_DATA
| 1UL << ND_CMD_VENDOR;
*nfit_mem = (struct nfit_mem) {
.adev = adev,
.family = NVDIMM_FAMILY_INTEL,
.dsm_mask = mask,
};
nvdimm = devm_kzalloc(dev, sizeof(*nvdimm), GFP_KERNEL);
if (!nvdimm)
return -ENOMEM;
*nvdimm = (struct nvdimm) {
.provider_data = nfit_mem,
.cmd_mask = mask,
.dev = {
.init_name = "test-dimm",
},
};
/* basic checkout of a typical 'get config size' command */
cmd_size = sizeof(cmds.cfg_size);
cmds.cfg_size = (struct nd_cmd_get_config_size) {
.status = 0,
.config_size = SZ_128K,
.max_xfer = SZ_4K,
};
rc = setup_result(cmds.buf, cmd_size);
if (rc)
return rc;
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, nvdimm, ND_CMD_GET_CONFIG_SIZE,
cmds.buf, cmd_size, &cmd_rc);
if (rc < 0 || cmd_rc || cmds.cfg_size.status != 0
|| cmds.cfg_size.config_size != SZ_128K
|| cmds.cfg_size.max_xfer != SZ_4K) {
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
__func__, __LINE__, rc, cmd_rc);
return -EIO;
}
/* test ars_status with zero output */
cmd_size = offsetof(struct nd_cmd_ars_status, address);
cmds.ars_stat = (struct nd_cmd_ars_status) {
.out_length = 0,
};
rc = setup_result(cmds.buf, cmd_size);
if (rc)
return rc;
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_STATUS,
cmds.buf, cmd_size, &cmd_rc);
if (rc < 0 || cmd_rc) {
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
__func__, __LINE__, rc, cmd_rc);
return -EIO;
}
/* test ars_cap with benign extended status */
cmd_size = sizeof(cmds.ars_cap);
cmds.ars_cap = (struct nd_cmd_ars_cap) {
.status = ND_ARS_PERSISTENT << 16,
};
offset = offsetof(struct nd_cmd_ars_cap, status);
rc = setup_result(cmds.buf + offset, cmd_size - offset);
if (rc)
return rc;
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_CAP,
cmds.buf, cmd_size, &cmd_rc);
if (rc < 0 || cmd_rc) {
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
__func__, __LINE__, rc, cmd_rc);
return -EIO;
}
/* test ars_status with 'status' trimmed from 'out_length' */
cmd_size = sizeof(cmds.ars_stat) + sizeof(struct nd_ars_record);
cmds.ars_stat = (struct nd_cmd_ars_status) {
.out_length = cmd_size - 4,
};
record = &cmds.ars_stat.records[0];
*record = (struct nd_ars_record) {
.length = test_val,
};
rc = setup_result(cmds.buf, cmd_size);
if (rc)
return rc;
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_STATUS,
cmds.buf, cmd_size, &cmd_rc);
if (rc < 0 || cmd_rc || record->length != test_val) {
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
__func__, __LINE__, rc, cmd_rc);
return -EIO;
}
/* test ars_status with 'Output (Size)' including 'status' */
cmd_size = sizeof(cmds.ars_stat) + sizeof(struct nd_ars_record);
cmds.ars_stat = (struct nd_cmd_ars_status) {
.out_length = cmd_size,
};
record = &cmds.ars_stat.records[0];
*record = (struct nd_ars_record) {
.length = test_val,
};
rc = setup_result(cmds.buf, cmd_size);
if (rc)
return rc;
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_STATUS,
cmds.buf, cmd_size, &cmd_rc);
if (rc < 0 || cmd_rc || record->length != test_val) {
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
__func__, __LINE__, rc, cmd_rc);
return -EIO;
}
/* test extended status for get_config_size results in failure */
cmd_size = sizeof(cmds.cfg_size);
cmds.cfg_size = (struct nd_cmd_get_config_size) {
.status = 1 << 16,
};
rc = setup_result(cmds.buf, cmd_size);
if (rc)
return rc;
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, nvdimm, ND_CMD_GET_CONFIG_SIZE,
cmds.buf, cmd_size, &cmd_rc);
if (rc < 0 || cmd_rc >= 0) {
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
__func__, __LINE__, rc, cmd_rc);
return -EIO;
}
return 0;
}
static int nfit_test_probe(struct platform_device *pdev)
{
struct nvdimm_bus_descriptor *nd_desc;
@ -1516,6 +1736,12 @@ static int nfit_test_probe(struct platform_device *pdev)
union acpi_object *obj;
int rc;
if (strcmp(dev_name(&pdev->dev), "nfit_test.0") == 0) {
rc = nfit_ctl_test(&pdev->dev);
if (rc)
return rc;
}
nfit_test = to_nfit_test(&pdev->dev);
/* common alloc */
@ -1639,11 +1865,13 @@ static __init int nfit_test_init(void)
{
int rc, i;
nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm");
if (IS_ERR(nfit_test_dimm))
return PTR_ERR(nfit_test_dimm);
nfit_test_setup(nfit_test_lookup, nfit_test_evaluate_dsm);
nfit_test_setup(nfit_test_lookup);
nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm");
if (IS_ERR(nfit_test_dimm)) {
rc = PTR_ERR(nfit_test_dimm);
goto err_register;
}
for (i = 0; i < NUM_NFITS; i++) {
struct nfit_test *nfit_test;

View File

@ -31,11 +31,17 @@ struct nfit_test_resource {
void *buf;
};
union acpi_object;
typedef void *acpi_handle;
typedef struct nfit_test_resource *(*nfit_test_lookup_fn)(resource_size_t);
typedef union acpi_object *(*nfit_test_evaluate_dsm_fn)(acpi_handle handle,
const u8 *uuid, u64 rev, u64 func, union acpi_object *argv4);
void __iomem *__wrap_ioremap_nocache(resource_size_t offset,
unsigned long size);
void __wrap_iounmap(volatile void __iomem *addr);
void nfit_test_setup(nfit_test_lookup_fn lookup);
void nfit_test_setup(nfit_test_lookup_fn lookup,
nfit_test_evaluate_dsm_fn evaluate);
void nfit_test_teardown(void);
struct nfit_test_resource *get_nfit_res(resource_size_t resource);
#endif