From 6bc756193ff61bf5e7b3cfedfbb0873bf40f8055 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 17 Jun 2015 17:23:32 -0400 Subject: tools/testing/nvdimm: libnvdimm unit test infrastructure 'libnvdimm' is the first driver sub-system in the kernel to implement mocking for unit test coverage. The nfit_test module gets built as an external module and arranges for external module replacements of nfit, libnvdimm, nd_pmem, and nd_blk. These replacements use the linker --wrap option to redirect calls to ioremap() + request_mem_region() to custom defined unit test resources. The end result is a fully functional nvdimm_bus, as far as userspace is concerned, but with the capability to perform otherwise destructive tests on emulated resources. Q: Why not use QEMU for this emulation? QEMU is not suitable for unit testing. QEMU's role is to faithfully emulate the platform. A unit test's role is to unfaithfully implement the platform with the goal of triggering bugs in the corners of the sub-system implementation. As bugs are discovered in platforms, or the sub-system itself, the unit tests are extended to backstop a fix with a reproducer unit test. Another problem with QEMU is that it would require coordination of 3 software projects instead of 2 (kernel + libndctl [1]) to maintain and execute the tests. The chances for bit rot and the difficulty of getting the tests running goes up non-linearly the more components involved. Q: Why submit this to the kernel tree instead of external modules in libndctl? Simple, to alleviate the same risk that out-of-tree external modules face. Updates to drivers/nvdimm/ can be immediately evaluated to see if they have any impact on tools/testing/nvdimm/. Q: What are the negative implications of merging this? It is a unique maintenance burden because the purpose of mocking an interface to enable a unit test is to purposefully short circuit the semantics of a routine to enable testing. For example __wrap_ioremap_cache() fakes the pmem driver into "ioremap()'ing" a test resource buffer allocated by dma_alloc_coherent(). The future maintenance burden hits when someone changes the semantics of ioremap_cache() and wonders what the implications are for the unit test. [1]: https://github.com/pmem/ndctl Cc: Cc: Lv Zheng Cc: Robert Moore Cc: Rafael J. Wysocki Cc: Christoph Hellwig Signed-off-by: Dan Williams --- tools/testing/nvdimm/test/iomap.c | 151 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 tools/testing/nvdimm/test/iomap.c (limited to 'tools/testing/nvdimm/test/iomap.c') diff --git a/tools/testing/nvdimm/test/iomap.c b/tools/testing/nvdimm/test/iomap.c new file mode 100644 index 000000000000..c85a6f6ba559 --- /dev/null +++ b/tools/testing/nvdimm/test/iomap.c @@ -0,0 +1,151 @@ +/* + * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include "nfit_test.h" + +static LIST_HEAD(iomap_head); + +static struct iomap_ops { + nfit_test_lookup_fn nfit_test_lookup; + struct list_head list; +} iomap_ops = { + .list = LIST_HEAD_INIT(iomap_ops.list), +}; + +void nfit_test_setup(nfit_test_lookup_fn lookup) +{ + iomap_ops.nfit_test_lookup = lookup; + list_add_rcu(&iomap_ops.list, &iomap_head); +} +EXPORT_SYMBOL(nfit_test_setup); + +void nfit_test_teardown(void) +{ + list_del_rcu(&iomap_ops.list); + synchronize_rcu(); +} +EXPORT_SYMBOL(nfit_test_teardown); + +static struct nfit_test_resource *get_nfit_res(resource_size_t resource) +{ + struct iomap_ops *ops; + + ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list); + if (ops) + return ops->nfit_test_lookup(resource); + return NULL; +} + +void __iomem *__nfit_test_ioremap(resource_size_t offset, unsigned long size, + void __iomem *(*fallback_fn)(resource_size_t, unsigned long)) +{ + struct nfit_test_resource *nfit_res; + + rcu_read_lock(); + nfit_res = get_nfit_res(offset); + rcu_read_unlock(); + if (nfit_res) + return (void __iomem *) nfit_res->buf + offset + - nfit_res->res->start; + return fallback_fn(offset, size); +} + +void __iomem *__wrap_ioremap_cache(resource_size_t offset, unsigned long size) +{ + return __nfit_test_ioremap(offset, size, ioremap_cache); +} +EXPORT_SYMBOL(__wrap_ioremap_cache); + +void __iomem *__wrap_ioremap_nocache(resource_size_t offset, unsigned long size) +{ + return __nfit_test_ioremap(offset, size, ioremap_nocache); +} +EXPORT_SYMBOL(__wrap_ioremap_nocache); + +void __wrap_iounmap(volatile void __iomem *addr) +{ + struct nfit_test_resource *nfit_res; + + rcu_read_lock(); + nfit_res = get_nfit_res((unsigned long) addr); + rcu_read_unlock(); + if (nfit_res) + return; + return iounmap(addr); +} +EXPORT_SYMBOL(__wrap_iounmap); + +struct resource *__wrap___request_region(struct resource *parent, + resource_size_t start, resource_size_t n, const char *name, + int flags) +{ + struct nfit_test_resource *nfit_res; + + if (parent == &iomem_resource) { + rcu_read_lock(); + nfit_res = get_nfit_res(start); + rcu_read_unlock(); + if (nfit_res) { + struct resource *res = nfit_res->res + 1; + + if (start + n > nfit_res->res->start + + resource_size(nfit_res->res)) { + pr_debug("%s: start: %llx n: %llx overflow: %pr\n", + __func__, start, n, + nfit_res->res); + return NULL; + } + + res->start = start; + res->end = start + n - 1; + res->name = name; + res->flags = resource_type(parent); + res->flags |= IORESOURCE_BUSY | flags; + pr_debug("%s: %pr\n", __func__, res); + return res; + } + } + return __request_region(parent, start, n, name, flags); +} +EXPORT_SYMBOL(__wrap___request_region); + +void __wrap___release_region(struct resource *parent, resource_size_t start, + resource_size_t n) +{ + struct nfit_test_resource *nfit_res; + + if (parent == &iomem_resource) { + rcu_read_lock(); + nfit_res = get_nfit_res(start); + rcu_read_unlock(); + if (nfit_res) { + struct resource *res = nfit_res->res + 1; + + if (start != res->start || resource_size(res) != n) + pr_info("%s: start: %llx n: %llx mismatch: %pr\n", + __func__, start, n, res); + else + memset(res, 0, sizeof(*res)); + return; + } + } + __release_region(parent, start, n); +} +EXPORT_SYMBOL(__wrap___release_region); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-11-g984f