Skip to content

Commit a7de92d

Browse files
committed
tools/testing/nvdimm: unit test acpi_nfit_ctl()
A recent flurry of bug discoveries in the nfit driver's DSM marshalling routine has highlighted the fact that we do not have unit test coverage for this routine. Add a self-test of acpi_nfit_ctl() routine before probing the "nfit_test.0" device. This mocks stimulus to acpi_nfit_ctl() and if any of the tests fail "nfit_test.0" will be unavailable causing the rest of the tests to not run / fail. This unit test will also be a place to land reproductions of quirky BIOS behavior discovered in the field and ensure the kernel does not regress against implementations it has seen in practice. Signed-off-by: Dan Williams <[email protected]>
1 parent d6eb270 commit a7de92d

File tree

6 files changed

+267
-9
lines changed

6 files changed

+267
-9
lines changed

drivers/acpi/nfit/core.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,8 @@ static int xlat_status(struct nvdimm *nvdimm, void *buf, unsigned int cmd,
185185
return 0;
186186
}
187187

188-
static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
189-
struct nvdimm *nvdimm, unsigned int cmd, void *buf,
190-
unsigned int buf_len, int *cmd_rc)
188+
int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
189+
unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc)
191190
{
192191
struct acpi_nfit_desc *acpi_desc = to_acpi_nfit_desc(nd_desc);
193192
union acpi_object in_obj, in_buf, *out_obj;
@@ -364,6 +363,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
364363

365364
return rc;
366365
}
366+
EXPORT_SYMBOL_GPL(acpi_nfit_ctl);
367367

368368
static const char *spa_type_name(u16 type)
369369
{

drivers/acpi/nfit/nfit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,7 @@ const u8 *to_nfit_uuid(enum nfit_uuids id);
240240
int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *nfit, acpi_size sz);
241241
void __acpi_nfit_notify(struct device *dev, acpi_handle handle, u32 event);
242242
void __acpi_nvdimm_notify(struct device *dev, u32 event);
243+
int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
244+
unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc);
243245
void acpi_nfit_desc_init(struct acpi_nfit_desc *acpi_desc, struct device *dev);
244246
#endif /* __NFIT_H__ */

tools/testing/nvdimm/Kbuild

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ldflags-y += --wrap=devm_memremap_pages
1414
ldflags-y += --wrap=insert_resource
1515
ldflags-y += --wrap=remove_resource
1616
ldflags-y += --wrap=acpi_evaluate_object
17+
ldflags-y += --wrap=acpi_evaluate_dsm
1718

1819
DRIVERS := ../../../drivers
1920
NVDIMM_SRC := $(DRIVERS)/nvdimm

tools/testing/nvdimm/test/iomap.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@ static LIST_HEAD(iomap_head);
2626

2727
static struct iomap_ops {
2828
nfit_test_lookup_fn nfit_test_lookup;
29+
nfit_test_evaluate_dsm_fn evaluate_dsm;
2930
struct list_head list;
3031
} iomap_ops = {
3132
.list = LIST_HEAD_INIT(iomap_ops.list),
3233
};
3334

34-
void nfit_test_setup(nfit_test_lookup_fn lookup)
35+
void nfit_test_setup(nfit_test_lookup_fn lookup,
36+
nfit_test_evaluate_dsm_fn evaluate)
3537
{
3638
iomap_ops.nfit_test_lookup = lookup;
39+
iomap_ops.evaluate_dsm = evaluate;
3740
list_add_rcu(&iomap_ops.list, &iomap_head);
3841
}
3942
EXPORT_SYMBOL(nfit_test_setup);
@@ -367,4 +370,22 @@ acpi_status __wrap_acpi_evaluate_object(acpi_handle handle, acpi_string path,
367370
}
368371
EXPORT_SYMBOL(__wrap_acpi_evaluate_object);
369372

373+
union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const u8 *uuid,
374+
u64 rev, u64 func, union acpi_object *argv4)
375+
{
376+
union acpi_object *obj = ERR_PTR(-ENXIO);
377+
struct iomap_ops *ops;
378+
379+
rcu_read_lock();
380+
ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list);
381+
if (ops)
382+
obj = ops->evaluate_dsm(handle, uuid, rev, func, argv4);
383+
rcu_read_unlock();
384+
385+
if (IS_ERR(obj))
386+
return acpi_evaluate_dsm(handle, uuid, rev, func, argv4);
387+
return obj;
388+
}
389+
EXPORT_SYMBOL(__wrap_acpi_evaluate_dsm);
390+
370391
MODULE_LICENSE("GPL v2");

tools/testing/nvdimm/test/nfit.c

Lines changed: 232 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <linux/sizes.h>
2424
#include <linux/list.h>
2525
#include <linux/slab.h>
26+
#include <nd-core.h>
2627
#include <nfit.h>
2728
#include <nd.h>
2829
#include "nfit_test.h"
@@ -1506,6 +1507,225 @@ static int nfit_test_blk_do_io(struct nd_blk_region *ndbr, resource_size_t dpa,
15061507
return 0;
15071508
}
15081509

1510+
static unsigned long nfit_ctl_handle;
1511+
1512+
union acpi_object *result;
1513+
1514+
static union acpi_object *nfit_test_evaluate_dsm(acpi_handle handle,
1515+
const u8 *uuid, u64 rev, u64 func, union acpi_object *argv4)
1516+
{
1517+
if (handle != &nfit_ctl_handle)
1518+
return ERR_PTR(-ENXIO);
1519+
1520+
return result;
1521+
}
1522+
1523+
static int setup_result(void *buf, size_t size)
1524+
{
1525+
result = kmalloc(sizeof(union acpi_object) + size, GFP_KERNEL);
1526+
if (!result)
1527+
return -ENOMEM;
1528+
result->package.type = ACPI_TYPE_BUFFER,
1529+
result->buffer.pointer = (void *) (result + 1);
1530+
result->buffer.length = size;
1531+
memcpy(result->buffer.pointer, buf, size);
1532+
memset(buf, 0, size);
1533+
return 0;
1534+
}
1535+
1536+
static int nfit_ctl_test(struct device *dev)
1537+
{
1538+
int rc, cmd_rc;
1539+
struct nvdimm *nvdimm;
1540+
struct acpi_device *adev;
1541+
struct nfit_mem *nfit_mem;
1542+
struct nd_ars_record *record;
1543+
struct acpi_nfit_desc *acpi_desc;
1544+
const u64 test_val = 0x0123456789abcdefULL;
1545+
unsigned long mask, cmd_size, offset;
1546+
union {
1547+
struct nd_cmd_get_config_size cfg_size;
1548+
struct nd_cmd_ars_status ars_stat;
1549+
struct nd_cmd_ars_cap ars_cap;
1550+
char buf[sizeof(struct nd_cmd_ars_status)
1551+
+ sizeof(struct nd_ars_record)];
1552+
} cmds;
1553+
1554+
adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL);
1555+
if (!adev)
1556+
return -ENOMEM;
1557+
*adev = (struct acpi_device) {
1558+
.handle = &nfit_ctl_handle,
1559+
.dev = {
1560+
.init_name = "test-adev",
1561+
},
1562+
};
1563+
1564+
acpi_desc = devm_kzalloc(dev, sizeof(*acpi_desc), GFP_KERNEL);
1565+
if (!acpi_desc)
1566+
return -ENOMEM;
1567+
*acpi_desc = (struct acpi_nfit_desc) {
1568+
.nd_desc = {
1569+
.cmd_mask = 1UL << ND_CMD_ARS_CAP
1570+
| 1UL << ND_CMD_ARS_START
1571+
| 1UL << ND_CMD_ARS_STATUS
1572+
| 1UL << ND_CMD_CLEAR_ERROR,
1573+
.module = THIS_MODULE,
1574+
.provider_name = "ACPI.NFIT",
1575+
.ndctl = acpi_nfit_ctl,
1576+
},
1577+
.dev = &adev->dev,
1578+
};
1579+
1580+
nfit_mem = devm_kzalloc(dev, sizeof(*nfit_mem), GFP_KERNEL);
1581+
if (!nfit_mem)
1582+
return -ENOMEM;
1583+
1584+
mask = 1UL << ND_CMD_SMART | 1UL << ND_CMD_SMART_THRESHOLD
1585+
| 1UL << ND_CMD_DIMM_FLAGS | 1UL << ND_CMD_GET_CONFIG_SIZE
1586+
| 1UL << ND_CMD_GET_CONFIG_DATA | 1UL << ND_CMD_SET_CONFIG_DATA
1587+
| 1UL << ND_CMD_VENDOR;
1588+
*nfit_mem = (struct nfit_mem) {
1589+
.adev = adev,
1590+
.family = NVDIMM_FAMILY_INTEL,
1591+
.dsm_mask = mask,
1592+
};
1593+
1594+
nvdimm = devm_kzalloc(dev, sizeof(*nvdimm), GFP_KERNEL);
1595+
if (!nvdimm)
1596+
return -ENOMEM;
1597+
*nvdimm = (struct nvdimm) {
1598+
.provider_data = nfit_mem,
1599+
.cmd_mask = mask,
1600+
.dev = {
1601+
.init_name = "test-dimm",
1602+
},
1603+
};
1604+
1605+
1606+
/* basic checkout of a typical 'get config size' command */
1607+
cmd_size = sizeof(cmds.cfg_size);
1608+
cmds.cfg_size = (struct nd_cmd_get_config_size) {
1609+
.status = 0,
1610+
.config_size = SZ_128K,
1611+
.max_xfer = SZ_4K,
1612+
};
1613+
rc = setup_result(cmds.buf, cmd_size);
1614+
if (rc)
1615+
return rc;
1616+
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, nvdimm, ND_CMD_GET_CONFIG_SIZE,
1617+
cmds.buf, cmd_size, &cmd_rc);
1618+
1619+
if (rc < 0 || cmd_rc || cmds.cfg_size.status != 0
1620+
|| cmds.cfg_size.config_size != SZ_128K
1621+
|| cmds.cfg_size.max_xfer != SZ_4K) {
1622+
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
1623+
__func__, __LINE__, rc, cmd_rc);
1624+
return -EIO;
1625+
}
1626+
1627+
1628+
/* test ars_status with zero output */
1629+
cmd_size = offsetof(struct nd_cmd_ars_status, address);
1630+
cmds.ars_stat = (struct nd_cmd_ars_status) {
1631+
.out_length = 0,
1632+
};
1633+
rc = setup_result(cmds.buf, cmd_size);
1634+
if (rc)
1635+
return rc;
1636+
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_STATUS,
1637+
cmds.buf, cmd_size, &cmd_rc);
1638+
1639+
if (rc < 0 || cmd_rc) {
1640+
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
1641+
__func__, __LINE__, rc, cmd_rc);
1642+
return -EIO;
1643+
}
1644+
1645+
1646+
/* test ars_cap with benign extended status */
1647+
cmd_size = sizeof(cmds.ars_cap);
1648+
cmds.ars_cap = (struct nd_cmd_ars_cap) {
1649+
.status = ND_ARS_PERSISTENT << 16,
1650+
};
1651+
offset = offsetof(struct nd_cmd_ars_cap, status);
1652+
rc = setup_result(cmds.buf + offset, cmd_size - offset);
1653+
if (rc)
1654+
return rc;
1655+
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_CAP,
1656+
cmds.buf, cmd_size, &cmd_rc);
1657+
1658+
if (rc < 0 || cmd_rc) {
1659+
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
1660+
__func__, __LINE__, rc, cmd_rc);
1661+
return -EIO;
1662+
}
1663+
1664+
1665+
/* test ars_status with 'status' trimmed from 'out_length' */
1666+
cmd_size = sizeof(cmds.ars_stat) + sizeof(struct nd_ars_record);
1667+
cmds.ars_stat = (struct nd_cmd_ars_status) {
1668+
.out_length = cmd_size - 4,
1669+
};
1670+
record = &cmds.ars_stat.records[0];
1671+
*record = (struct nd_ars_record) {
1672+
.length = test_val,
1673+
};
1674+
rc = setup_result(cmds.buf, cmd_size);
1675+
if (rc)
1676+
return rc;
1677+
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_STATUS,
1678+
cmds.buf, cmd_size, &cmd_rc);
1679+
1680+
if (rc < 0 || cmd_rc || record->length != test_val) {
1681+
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
1682+
__func__, __LINE__, rc, cmd_rc);
1683+
return -EIO;
1684+
}
1685+
1686+
1687+
/* test ars_status with 'Output (Size)' including 'status' */
1688+
cmd_size = sizeof(cmds.ars_stat) + sizeof(struct nd_ars_record);
1689+
cmds.ars_stat = (struct nd_cmd_ars_status) {
1690+
.out_length = cmd_size,
1691+
};
1692+
record = &cmds.ars_stat.records[0];
1693+
*record = (struct nd_ars_record) {
1694+
.length = test_val,
1695+
};
1696+
rc = setup_result(cmds.buf, cmd_size);
1697+
if (rc)
1698+
return rc;
1699+
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, NULL, ND_CMD_ARS_STATUS,
1700+
cmds.buf, cmd_size, &cmd_rc);
1701+
1702+
if (rc < 0 || cmd_rc || record->length != test_val) {
1703+
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
1704+
__func__, __LINE__, rc, cmd_rc);
1705+
return -EIO;
1706+
}
1707+
1708+
1709+
/* test extended status for get_config_size results in failure */
1710+
cmd_size = sizeof(cmds.cfg_size);
1711+
cmds.cfg_size = (struct nd_cmd_get_config_size) {
1712+
.status = 1 << 16,
1713+
};
1714+
rc = setup_result(cmds.buf, cmd_size);
1715+
if (rc)
1716+
return rc;
1717+
rc = acpi_nfit_ctl(&acpi_desc->nd_desc, nvdimm, ND_CMD_GET_CONFIG_SIZE,
1718+
cmds.buf, cmd_size, &cmd_rc);
1719+
1720+
if (rc < 0 || cmd_rc >= 0) {
1721+
dev_dbg(dev, "%s: failed at: %d rc: %d cmd_rc: %d\n",
1722+
__func__, __LINE__, rc, cmd_rc);
1723+
return -EIO;
1724+
}
1725+
1726+
return 0;
1727+
}
1728+
15091729
static int nfit_test_probe(struct platform_device *pdev)
15101730
{
15111731
struct nvdimm_bus_descriptor *nd_desc;
@@ -1516,6 +1736,12 @@ static int nfit_test_probe(struct platform_device *pdev)
15161736
union acpi_object *obj;
15171737
int rc;
15181738

1739+
if (strcmp(dev_name(&pdev->dev), "nfit_test.0") == 0) {
1740+
rc = nfit_ctl_test(&pdev->dev);
1741+
if (rc)
1742+
return rc;
1743+
}
1744+
15191745
nfit_test = to_nfit_test(&pdev->dev);
15201746

15211747
/* common alloc */
@@ -1639,11 +1865,13 @@ static __init int nfit_test_init(void)
16391865
{
16401866
int rc, i;
16411867

1642-
nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm");
1643-
if (IS_ERR(nfit_test_dimm))
1644-
return PTR_ERR(nfit_test_dimm);
1868+
nfit_test_setup(nfit_test_lookup, nfit_test_evaluate_dsm);
16451869

1646-
nfit_test_setup(nfit_test_lookup);
1870+
nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm");
1871+
if (IS_ERR(nfit_test_dimm)) {
1872+
rc = PTR_ERR(nfit_test_dimm);
1873+
goto err_register;
1874+
}
16471875

16481876
for (i = 0; i < NUM_NFITS; i++) {
16491877
struct nfit_test *nfit_test;

tools/testing/nvdimm/test/nfit_test.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ struct nfit_test_resource {
3131
void *buf;
3232
};
3333

34+
union acpi_object;
35+
typedef void *acpi_handle;
36+
3437
typedef struct nfit_test_resource *(*nfit_test_lookup_fn)(resource_size_t);
38+
typedef union acpi_object *(*nfit_test_evaluate_dsm_fn)(acpi_handle handle,
39+
const u8 *uuid, u64 rev, u64 func, union acpi_object *argv4);
3540
void __iomem *__wrap_ioremap_nocache(resource_size_t offset,
3641
unsigned long size);
3742
void __wrap_iounmap(volatile void __iomem *addr);
38-
void nfit_test_setup(nfit_test_lookup_fn lookup);
43+
void nfit_test_setup(nfit_test_lookup_fn lookup,
44+
nfit_test_evaluate_dsm_fn evaluate);
3945
void nfit_test_teardown(void);
4046
struct nfit_test_resource *get_nfit_res(resource_size_t resource);
4147
#endif

0 commit comments

Comments
 (0)