forked from Minki/linux
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. Commits9a901f5495
("acpi, nfit: fix extended status translations for ACPI DSMs") and325896ffdf
("device-dax: fix private mapping restriction, permit read-only") are true regression fixes for changes introduced this cycle. Commitefda1b5d87
("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 commita7de92dac9
("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:
commit
810ac7b755
@ -94,7 +94,7 @@ static struct acpi_device *to_acpi_dev(struct acpi_nfit_desc *acpi_desc)
|
|||||||
return to_acpi_device(acpi_desc->dev);
|
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_clear_error *clear_err;
|
||||||
struct nd_cmd_ars_status *ars_status;
|
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;
|
flags = ND_ARS_PERSISTENT | ND_ARS_VOLATILE;
|
||||||
if ((status >> 16 & flags) == 0)
|
if ((status >> 16 & flags) == 0)
|
||||||
return -ENOTTY;
|
return -ENOTTY;
|
||||||
break;
|
return 0;
|
||||||
case ND_CMD_ARS_START:
|
case ND_CMD_ARS_START:
|
||||||
/* ARS is in progress */
|
/* ARS is in progress */
|
||||||
if ((status & 0xffff) == NFIT_ARS_START_BUSY)
|
if ((status & 0xffff) == NFIT_ARS_START_BUSY)
|
||||||
@ -122,7 +122,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
|
|||||||
/* Command failed */
|
/* Command failed */
|
||||||
if (status & 0xffff)
|
if (status & 0xffff)
|
||||||
return -EIO;
|
return -EIO;
|
||||||
break;
|
return 0;
|
||||||
case ND_CMD_ARS_STATUS:
|
case ND_CMD_ARS_STATUS:
|
||||||
ars_status = buf;
|
ars_status = buf;
|
||||||
/* Command failed */
|
/* Command failed */
|
||||||
@ -146,7 +146,8 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
|
|||||||
* then just continue with the returned results.
|
* then just continue with the returned results.
|
||||||
*/
|
*/
|
||||||
if (status == NFIT_ARS_STATUS_INTR) {
|
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 -ENOSPC;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -154,7 +155,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
|
|||||||
/* Unknown status */
|
/* Unknown status */
|
||||||
if (status >> 16)
|
if (status >> 16)
|
||||||
return -EIO;
|
return -EIO;
|
||||||
break;
|
return 0;
|
||||||
case ND_CMD_CLEAR_ERROR:
|
case ND_CMD_CLEAR_ERROR:
|
||||||
clear_err = buf;
|
clear_err = buf;
|
||||||
if (status & 0xffff)
|
if (status & 0xffff)
|
||||||
@ -163,7 +164,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
|
|||||||
return -EIO;
|
return -EIO;
|
||||||
if (clear_err->length > clear_err->cleared)
|
if (clear_err->length > clear_err->cleared)
|
||||||
return clear_err->cleared;
|
return clear_err->cleared;
|
||||||
break;
|
return 0;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -174,9 +175,18 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
|
static int xlat_status(struct nvdimm *nvdimm, void *buf, unsigned int cmd,
|
||||||
struct nvdimm *nvdimm, unsigned int cmd, void *buf,
|
u32 status)
|
||||||
unsigned int buf_len, int *cmd_rc)
|
{
|
||||||
|
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);
|
struct acpi_nfit_desc *acpi_desc = to_acpi_nfit_desc(nd_desc);
|
||||||
union acpi_object in_obj, in_buf, *out_obj;
|
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++) {
|
for (i = 0, offset = 0; i < desc->out_num; i++) {
|
||||||
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i, buf,
|
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) {
|
if (offset + out_size > out_obj->buffer.length) {
|
||||||
dev_dbg(dev, "%s:%s output object underflow cmd: %s field: %d\n",
|
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;
|
rc = buf_len - offset - in_buf.buffer.length;
|
||||||
if (cmd_rc)
|
if (cmd_rc)
|
||||||
*cmd_rc = xlat_status(buf, cmd, fw_status);
|
*cmd_rc = xlat_status(nvdimm, buf, cmd,
|
||||||
|
fw_status);
|
||||||
} else {
|
} else {
|
||||||
dev_err(dev, "%s:%s underrun cmd: %s buf_len: %d out_len: %d\n",
|
dev_err(dev, "%s:%s underrun cmd: %s buf_len: %d out_len: %d\n",
|
||||||
__func__, dimm_name, cmd_name, buf_len,
|
__func__, dimm_name, cmd_name, buf_len,
|
||||||
@ -343,7 +355,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
|
|||||||
} else {
|
} else {
|
||||||
rc = 0;
|
rc = 0;
|
||||||
if (cmd_rc)
|
if (cmd_rc)
|
||||||
*cmd_rc = xlat_status(buf, cmd, fw_status);
|
*cmd_rc = xlat_status(nvdimm, buf, cmd, fw_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -351,6 +363,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
|
|||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(acpi_nfit_ctl);
|
||||||
|
|
||||||
static const char *spa_type_name(u16 type)
|
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;
|
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 nd_cmd_ars_status *ars_status)
|
||||||
{
|
{
|
||||||
|
struct nvdimm_bus *nvdimm_bus = acpi_desc->nvdimm_bus;
|
||||||
int rc;
|
int rc;
|
||||||
u32 i;
|
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++) {
|
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,
|
rc = nvdimm_bus_add_poison(nvdimm_bus,
|
||||||
ars_status->records[i].err_address,
|
ars_status->records[i].err_address,
|
||||||
ars_status->records[i].length);
|
ars_status->records[i].length);
|
||||||
if (rc)
|
if (rc)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
if (i < ars_status->num_records)
|
||||||
|
dev_warn(acpi_desc->dev, "detected truncated ars results\n");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2266,8 +2292,7 @@ static int acpi_nfit_query_poison(struct acpi_nfit_desc *acpi_desc,
|
|||||||
if (rc < 0 && rc != -ENOSPC)
|
if (rc < 0 && rc != -ENOSPC)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
if (ars_status_process_records(acpi_desc->nvdimm_bus,
|
if (ars_status_process_records(acpi_desc, acpi_desc->ars_status))
|
||||||
acpi_desc->ars_status))
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -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);
|
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_nfit_notify(struct device *dev, acpi_handle handle, u32 event);
|
||||||
void __acpi_nvdimm_notify(struct device *dev, 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);
|
void acpi_nfit_desc_init(struct acpi_nfit_desc *acpi_desc, struct device *dev);
|
||||||
#endif /* __NFIT_H__ */
|
#endif /* __NFIT_H__ */
|
||||||
|
@ -271,7 +271,7 @@ static int check_vma(struct dax_dev *dax_dev, struct vm_area_struct *vma,
|
|||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
|
|
||||||
/* prevent private mappings from being established */
|
/* 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",
|
dev_info(dev, "%s: %s: fail, attempted private mapping\n",
|
||||||
current->comm, func);
|
current->comm, func);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(nd_cmd_in_size);
|
|||||||
|
|
||||||
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
|
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
|
||||||
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
|
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)
|
if (idx >= desc->out_num)
|
||||||
return UINT_MAX;
|
return UINT_MAX;
|
||||||
@ -727,9 +727,24 @@ u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
|
|||||||
return in_field[1];
|
return in_field[1];
|
||||||
else if (nvdimm && cmd == ND_CMD_VENDOR && idx == 2)
|
else if (nvdimm && cmd == ND_CMD_VENDOR && idx == 2)
|
||||||
return out_field[1];
|
return out_field[1];
|
||||||
else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2)
|
else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2) {
|
||||||
return out_field[1] - 8;
|
/*
|
||||||
else if (cmd == ND_CMD_CALL) {
|
* 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;
|
struct nd_cmd_pkg *pkg = (struct nd_cmd_pkg *) in_field;
|
||||||
|
|
||||||
return pkg->nd_size_out;
|
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 */
|
/* process an output envelope */
|
||||||
for (i = 0; i < desc->out_num; i++) {
|
for (i = 0; i < desc->out_num; i++) {
|
||||||
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, 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;
|
u32 copy;
|
||||||
|
|
||||||
if (out_size == UINT_MAX) {
|
if (out_size == UINT_MAX) {
|
||||||
|
@ -143,7 +143,7 @@ u32 nd_cmd_in_size(struct nvdimm *nvdimm, int cmd,
|
|||||||
const struct nd_cmd_desc *desc, int idx, void *buf);
|
const struct nd_cmd_desc *desc, int idx, void *buf);
|
||||||
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
|
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
|
||||||
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
|
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);
|
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 *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus,
|
||||||
struct nd_region_desc *ndr_desc);
|
struct nd_region_desc *ndr_desc);
|
||||||
|
@ -14,6 +14,7 @@ ldflags-y += --wrap=devm_memremap_pages
|
|||||||
ldflags-y += --wrap=insert_resource
|
ldflags-y += --wrap=insert_resource
|
||||||
ldflags-y += --wrap=remove_resource
|
ldflags-y += --wrap=remove_resource
|
||||||
ldflags-y += --wrap=acpi_evaluate_object
|
ldflags-y += --wrap=acpi_evaluate_object
|
||||||
|
ldflags-y += --wrap=acpi_evaluate_dsm
|
||||||
|
|
||||||
DRIVERS := ../../../drivers
|
DRIVERS := ../../../drivers
|
||||||
NVDIMM_SRC := $(DRIVERS)/nvdimm
|
NVDIMM_SRC := $(DRIVERS)/nvdimm
|
||||||
|
@ -26,14 +26,17 @@ static LIST_HEAD(iomap_head);
|
|||||||
|
|
||||||
static struct iomap_ops {
|
static struct iomap_ops {
|
||||||
nfit_test_lookup_fn nfit_test_lookup;
|
nfit_test_lookup_fn nfit_test_lookup;
|
||||||
|
nfit_test_evaluate_dsm_fn evaluate_dsm;
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
} iomap_ops = {
|
} iomap_ops = {
|
||||||
.list = LIST_HEAD_INIT(iomap_ops.list),
|
.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.nfit_test_lookup = lookup;
|
||||||
|
iomap_ops.evaluate_dsm = evaluate;
|
||||||
list_add_rcu(&iomap_ops.list, &iomap_head);
|
list_add_rcu(&iomap_ops.list, &iomap_head);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(nfit_test_setup);
|
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);
|
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");
|
MODULE_LICENSE("GPL v2");
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <linux/sizes.h>
|
#include <linux/sizes.h>
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
#include <nd-core.h>
|
||||||
#include <nfit.h>
|
#include <nfit.h>
|
||||||
#include <nd.h>
|
#include <nd.h>
|
||||||
#include "nfit_test.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;
|
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)
|
static int nfit_test_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct nvdimm_bus_descriptor *nd_desc;
|
struct nvdimm_bus_descriptor *nd_desc;
|
||||||
@ -1516,6 +1736,12 @@ static int nfit_test_probe(struct platform_device *pdev)
|
|||||||
union acpi_object *obj;
|
union acpi_object *obj;
|
||||||
int rc;
|
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);
|
nfit_test = to_nfit_test(&pdev->dev);
|
||||||
|
|
||||||
/* common alloc */
|
/* common alloc */
|
||||||
@ -1639,11 +1865,13 @@ static __init int nfit_test_init(void)
|
|||||||
{
|
{
|
||||||
int rc, i;
|
int rc, i;
|
||||||
|
|
||||||
nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm");
|
nfit_test_setup(nfit_test_lookup, nfit_test_evaluate_dsm);
|
||||||
if (IS_ERR(nfit_test_dimm))
|
|
||||||
return PTR_ERR(nfit_test_dimm);
|
|
||||||
|
|
||||||
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++) {
|
for (i = 0; i < NUM_NFITS; i++) {
|
||||||
struct nfit_test *nfit_test;
|
struct nfit_test *nfit_test;
|
||||||
|
@ -31,11 +31,17 @@ struct nfit_test_resource {
|
|||||||
void *buf;
|
void *buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
union acpi_object;
|
||||||
|
typedef void *acpi_handle;
|
||||||
|
|
||||||
typedef struct nfit_test_resource *(*nfit_test_lookup_fn)(resource_size_t);
|
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,
|
void __iomem *__wrap_ioremap_nocache(resource_size_t offset,
|
||||||
unsigned long size);
|
unsigned long size);
|
||||||
void __wrap_iounmap(volatile void __iomem *addr);
|
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);
|
void nfit_test_teardown(void);
|
||||||
struct nfit_test_resource *get_nfit_res(resource_size_t resource);
|
struct nfit_test_resource *get_nfit_res(resource_size_t resource);
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user