diff options
Diffstat (limited to 'tools/testing/nvdimm/test/nfit.c')
-rw-r--r-- | tools/testing/nvdimm/test/nfit.c | 420 |
1 files changed, 395 insertions, 25 deletions
diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c index f64c57bf1d4b..45be8b55a663 100644 --- a/tools/testing/nvdimm/test/nfit.c +++ b/tools/testing/nvdimm/test/nfit.c @@ -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" @@ -124,14 +125,17 @@ struct nfit_test_dcr { (((node & 0xfff) << 16) | ((socket & 0xf) << 12) \ | ((imc & 0xf) << 8) | ((chan & 0xf) << 4) | (dimm & 0xf)) -static u32 handle[NUM_DCR] = { +static u32 handle[] = { [0] = NFIT_DIMM_HANDLE(0, 0, 0, 0, 0), [1] = NFIT_DIMM_HANDLE(0, 0, 0, 0, 1), [2] = NFIT_DIMM_HANDLE(0, 0, 1, 0, 0), [3] = NFIT_DIMM_HANDLE(0, 0, 1, 0, 1), [4] = NFIT_DIMM_HANDLE(0, 1, 0, 0, 0), + [5] = NFIT_DIMM_HANDLE(1, 0, 0, 0, 0), }; +static unsigned long dimm_fail_cmd_flags[NUM_DCR]; + struct nfit_test { struct acpi_nfit_desc acpi_desc; struct platform_device pdev; @@ -139,6 +143,7 @@ struct nfit_test { void *nfit_buf; dma_addr_t nfit_dma; size_t nfit_size; + int dcr_idx; int num_dcr; int num_pm; void **dimm; @@ -154,11 +159,14 @@ struct nfit_test { int (*alloc)(struct nfit_test *t); void (*setup)(struct nfit_test *t); int setup_hotplug; + union acpi_object **_fit; + dma_addr_t _fit_dma; struct ars_state { struct nd_cmd_ars_status *ars_status; unsigned long deadline; spinlock_t lock; } ars_state; + struct device *dimm_dev[NUM_DCR]; }; static struct nfit_test *to_nfit_test(struct device *dev) @@ -411,23 +419,29 @@ static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc, if (i >= ARRAY_SIZE(handle)) return -ENXIO; + if ((1 << func) & dimm_fail_cmd_flags[i]) + return -EIO; + switch (func) { case ND_CMD_GET_CONFIG_SIZE: rc = nfit_test_cmd_get_config_size(buf, buf_len); break; case ND_CMD_GET_CONFIG_DATA: rc = nfit_test_cmd_get_config_data(buf, buf_len, - t->label[i]); + t->label[i - t->dcr_idx]); break; case ND_CMD_SET_CONFIG_DATA: rc = nfit_test_cmd_set_config_data(buf, buf_len, - t->label[i]); + t->label[i - t->dcr_idx]); break; case ND_CMD_SMART: rc = nfit_test_cmd_smart(buf, buf_len); break; case ND_CMD_SMART_THRESHOLD: rc = nfit_test_cmd_smart_threshold(buf, buf_len); + device_lock(&t->pdev.dev); + __acpi_nvdimm_notify(t->dimm_dev[i], 0x81); + device_unlock(&t->pdev.dev); break; default: return -ENOTTY; @@ -467,14 +481,12 @@ static struct nfit_test *instances[NUM_NFITS]; static void release_nfit_res(void *data) { struct nfit_test_resource *nfit_res = data; - struct resource *res = nfit_res->res; spin_lock(&nfit_test_lock); list_del(&nfit_res->list); spin_unlock(&nfit_test_lock); vfree(nfit_res->buf); - kfree(res); kfree(nfit_res); } @@ -482,12 +494,11 @@ static void *__test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma, void *buf) { struct device *dev = &t->pdev.dev; - struct resource *res = kzalloc(sizeof(*res) * 2, GFP_KERNEL); struct nfit_test_resource *nfit_res = kzalloc(sizeof(*nfit_res), GFP_KERNEL); int rc; - if (!res || !buf || !nfit_res) + if (!buf || !nfit_res) goto err; rc = devm_add_action(dev, release_nfit_res, nfit_res); if (rc) @@ -496,10 +507,11 @@ static void *__test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma, memset(buf, 0, size); nfit_res->dev = dev; nfit_res->buf = buf; - nfit_res->res = res; - res->start = *dma; - res->end = *dma + size - 1; - res->name = "NFIT"; + nfit_res->res.start = *dma; + nfit_res->res.end = *dma + size - 1; + nfit_res->res.name = "NFIT"; + spin_lock_init(&nfit_res->lock); + INIT_LIST_HEAD(&nfit_res->requests); spin_lock(&nfit_test_lock); list_add(&nfit_res->list, &t->resources); spin_unlock(&nfit_test_lock); @@ -508,7 +520,6 @@ static void *__test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma, err: if (buf) vfree(buf); - kfree(res); kfree(nfit_res); return NULL; } @@ -533,13 +544,13 @@ static struct nfit_test_resource *nfit_test_lookup(resource_size_t addr) continue; spin_lock(&nfit_test_lock); list_for_each_entry(n, &t->resources, list) { - if (addr >= n->res->start && (addr < n->res->start - + resource_size(n->res))) { + if (addr >= n->res.start && (addr < n->res.start + + resource_size(&n->res))) { nfit_res = n; break; } else if (addr >= (unsigned long) n->buf && (addr < (unsigned long) n->buf - + resource_size(n->res))) { + + resource_size(&n->res))) { nfit_res = n; break; } @@ -564,6 +575,86 @@ static int ars_state_init(struct device *dev, struct ars_state *ars_state) return 0; } +static void put_dimms(void *data) +{ + struct device **dimm_dev = data; + int i; + + for (i = 0; i < NUM_DCR; i++) + if (dimm_dev[i]) + device_unregister(dimm_dev[i]); +} + +static struct class *nfit_test_dimm; + +static int dimm_name_to_id(struct device *dev) +{ + int dimm; + + if (sscanf(dev_name(dev), "test_dimm%d", &dimm) != 1 + || dimm >= NUM_DCR || dimm < 0) + return -ENXIO; + return dimm; +} + + +static ssize_t handle_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int dimm = dimm_name_to_id(dev); + + if (dimm < 0) + return dimm; + + return sprintf(buf, "%#x", handle[dimm]); +} +DEVICE_ATTR_RO(handle); + +static ssize_t fail_cmd_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int dimm = dimm_name_to_id(dev); + + if (dimm < 0) + return dimm; + + return sprintf(buf, "%#lx\n", dimm_fail_cmd_flags[dimm]); +} + +static ssize_t fail_cmd_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int dimm = dimm_name_to_id(dev); + unsigned long val; + ssize_t rc; + + if (dimm < 0) + return dimm; + + rc = kstrtol(buf, 0, &val); + if (rc) + return rc; + + dimm_fail_cmd_flags[dimm] = val; + return size; +} +static DEVICE_ATTR_RW(fail_cmd); + +static struct attribute *nfit_test_dimm_attributes[] = { + &dev_attr_fail_cmd.attr, + &dev_attr_handle.attr, + NULL, +}; + +static struct attribute_group nfit_test_dimm_attribute_group = { + .attrs = nfit_test_dimm_attributes, +}; + +static const struct attribute_group *nfit_test_dimm_attribute_groups[] = { + &nfit_test_dimm_attribute_group, + NULL, +}; + static int nfit_test0_alloc(struct nfit_test *t) { size_t nfit_size = sizeof(struct acpi_nfit_system_address) * NUM_SPA @@ -593,7 +684,7 @@ static int nfit_test0_alloc(struct nfit_test *t) if (!t->spa_set[2]) return -ENOMEM; - for (i = 0; i < NUM_DCR; i++) { + for (i = 0; i < t->num_dcr; i++) { t->dimm[i] = test_alloc(t, DIMM_SIZE, &t->dimm_dma[i]); if (!t->dimm[i]) return -ENOMEM; @@ -610,12 +701,27 @@ static int nfit_test0_alloc(struct nfit_test *t) return -ENOMEM; } - for (i = 0; i < NUM_DCR; i++) { + for (i = 0; i < t->num_dcr; i++) { t->dcr[i] = test_alloc(t, LABEL_SIZE, &t->dcr_dma[i]); if (!t->dcr[i]) return -ENOMEM; } + t->_fit = test_alloc(t, sizeof(union acpi_object **), &t->_fit_dma); + if (!t->_fit) + return -ENOMEM; + + if (devm_add_action_or_reset(&t->pdev.dev, put_dimms, t->dimm_dev)) + return -ENOMEM; + for (i = 0; i < NUM_DCR; i++) { + t->dimm_dev[i] = device_create_with_groups(nfit_test_dimm, + &t->pdev.dev, 0, NULL, + nfit_test_dimm_attribute_groups, + "test_dimm%d", i); + if (!t->dimm_dev[i]) + return -ENOMEM; + } + return ars_state_init(&t->pdev.dev, &t->ars_state); } @@ -624,6 +730,7 @@ static int nfit_test1_alloc(struct nfit_test *t) size_t nfit_size = sizeof(struct acpi_nfit_system_address) * 2 + sizeof(struct acpi_nfit_memory_map) + offsetof(struct acpi_nfit_control_region, window_size); + int i; t->nfit_buf = test_alloc(t, nfit_size, &t->nfit_dma); if (!t->nfit_buf) @@ -634,6 +741,13 @@ static int nfit_test1_alloc(struct nfit_test *t) if (!t->spa_set[0]) return -ENOMEM; + for (i = 0; i < t->num_dcr; i++) { + t->label[i] = test_alloc(t, LABEL_SIZE, &t->label_dma[i]); + if (!t->label[i]) + return -ENOMEM; + sprintf(t->label[i], "label%d", i); + } + t->spa_set[1] = test_alloc(t, SPA_VCD_SIZE, &t->spa_set_dma[1]); if (!t->spa_set[1]) return -ENOMEM; @@ -1346,7 +1460,7 @@ static void nfit_test1_setup(struct nfit_test *t) memdev = nfit_buf + offset; memdev->header.type = ACPI_NFIT_TYPE_MEMORY_MAP; memdev->header.length = sizeof(*memdev); - memdev->device_handle = 0; + memdev->device_handle = handle[5]; memdev->physical_id = 0; memdev->region_id = 0; memdev->range_index = 0+1; @@ -1368,7 +1482,7 @@ static void nfit_test1_setup(struct nfit_test *t) window_size); dcr->region_index = 0+1; dcr_common_init(dcr); - dcr->serial_number = ~0; + dcr->serial_number = ~handle[5]; dcr->code = NFIT_FIC_BYTE; dcr->windows = 0; @@ -1379,6 +1493,9 @@ static void nfit_test1_setup(struct nfit_test *t) set_bit(ND_CMD_ARS_START, &acpi_desc->bus_cmd_force_en); set_bit(ND_CMD_ARS_STATUS, &acpi_desc->bus_cmd_force_en); set_bit(ND_CMD_CLEAR_ERROR, &acpi_desc->bus_cmd_force_en); + set_bit(ND_CMD_GET_CONFIG_SIZE, &acpi_desc->dimm_cmd_force_en); + set_bit(ND_CMD_GET_CONFIG_DATA, &acpi_desc->dimm_cmd_force_en); + set_bit(ND_CMD_SET_CONFIG_DATA, &acpi_desc->dimm_cmd_force_en); } static int nfit_test_blk_do_io(struct nd_blk_region *ndbr, resource_size_t dpa, @@ -1403,14 +1520,241 @@ 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; struct acpi_nfit_desc *acpi_desc; struct device *dev = &pdev->dev; struct nfit_test *nfit_test; + struct nfit_mem *nfit_mem; + 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 */ @@ -1476,14 +1820,30 @@ static int nfit_test_probe(struct platform_device *pdev) if (nfit_test->setup != nfit_test0_setup) return 0; - flush_work(&acpi_desc->work); nfit_test->setup_hotplug = 1; nfit_test->setup(nfit_test); - rc = acpi_nfit_init(acpi_desc, nfit_test->nfit_buf, - nfit_test->nfit_size); - if (rc) - return rc; + obj = kzalloc(sizeof(*obj), GFP_KERNEL); + if (!obj) + return -ENOMEM; + obj->type = ACPI_TYPE_BUFFER; + obj->buffer.length = nfit_test->nfit_size; + obj->buffer.pointer = nfit_test->nfit_buf; + *(nfit_test->_fit) = obj; + __acpi_nfit_notify(&pdev->dev, nfit_test, 0x80); + + /* associate dimm devices with nfit_mem data for notification testing */ + mutex_lock(&acpi_desc->init_mutex); + list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) { + u32 nfit_handle = __to_nfit_memdev(nfit_mem)->device_handle; + int i; + + for (i = 0; i < NUM_DCR; i++) + if (nfit_handle == handle[i]) + dev_set_drvdata(nfit_test->dimm_dev[i], + nfit_mem); + } + mutex_unlock(&acpi_desc->init_mutex); return 0; } @@ -1518,7 +1878,13 @@ static __init int nfit_test_init(void) { int rc, i; - nfit_test_setup(nfit_test_lookup); + nfit_test_setup(nfit_test_lookup, nfit_test_evaluate_dsm); + + 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; @@ -1533,12 +1899,15 @@ static __init int nfit_test_init(void) switch (i) { case 0: nfit_test->num_pm = NUM_PM; + nfit_test->dcr_idx = 0; nfit_test->num_dcr = NUM_DCR; nfit_test->alloc = nfit_test0_alloc; nfit_test->setup = nfit_test0_setup; break; case 1: nfit_test->num_pm = 1; + nfit_test->dcr_idx = NUM_DCR; + nfit_test->num_dcr = 1; nfit_test->alloc = nfit_test1_alloc; nfit_test->setup = nfit_test1_setup; break; @@ -1584,6 +1953,7 @@ static __exit void nfit_test_exit(void) for (i = 0; i < NUM_NFITS; i++) platform_device_unregister(&instances[i]->pdev); nfit_test_teardown(); + class_destroy(nfit_test_dimm); } module_init(nfit_test_init); |