Commit 67dcdd4d authored by Dan Williams's avatar Dan Williams

tools/testing/cxl: Introduce a mocked-up CXL port hierarchy

Create an environment for CXL plumbing unit tests. Especially when it
comes to an algorithm for HDM Decoder (Host-managed Device Memory
Decoder) programming, the availability of an in-kernel-tree emulation
environment for CXL configuration complexity and corner cases speeds
development and deters regressions.

The approach taken mirrors what was done for tools/testing/nvdimm/. I.e.
an external module, cxl_test.ko built out of the tools/testing/cxl/
directory, provides mock implementations of kernel APIs and kernel
objects to simulate a real world device hierarchy.

One feedback for the tools/testing/nvdimm/ proposal was "why not do this
in QEMU?". In fact, the CXL development community has developed a QEMU
model for CXL [1]. However, there are a few blocking issues that keep
QEMU from being a tight fit for topology + provisioning unit tests:

1/ The QEMU community has yet to show interest in merging any of this
   support that has had patches on the list since November 2020. So,
   testing CXL to date involves building custom QEMU with out-of-tree
   patches.

2/ CXL mechanisms like cross-host-bridge interleave do not have a clear
   path to be emulated by QEMU without major infrastructure work. This
   is easier to achieve with the alloc_mock_res() approach taken in this
   patch to shortcut-define emulated system physical address ranges with
   interleave behavior.

The QEMU enabling has been critical to get the driver off the ground,
and may still move forward, but it does not address the ongoing needs of
a regression testing environment and test driven development.

This patch adds an ACPI CXL Platform definition with emulated CXL
multi-ported host-bridges. A follow on patch adds emulated memory
expander devices.
Acked-by: default avatarBen Widawsky <ben.widawsky@intel.com>
Reported-by: default avatarVishal Verma <vishal.l.verma@intel.com>
Link: https://lore.kernel.org/r/20210202005948.241655-1-ben.widawsky@intel.com [1]
Link: https://lore.kernel.org/r/163164680798.2831381.838684634806668012.stgit@dwillia2-desk3.amr.corp.intel.comReviewed-by: default avatarJonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
parent 2e52b625
......@@ -182,15 +182,7 @@ static resource_size_t get_chbcr(struct acpi_cedt_chbs *chbs)
return IS_ERR(chbs) ? CXL_RESOURCE_NONE : chbs->base;
}
struct cxl_walk_context {
struct device *dev;
struct pci_bus *root;
struct cxl_port *port;
int error;
int count;
};
static int match_add_root_ports(struct pci_dev *pdev, void *data)
__mock int match_add_root_ports(struct pci_dev *pdev, void *data)
{
struct cxl_walk_context *ctx = data;
struct pci_bus *root_bus = ctx->root;
......@@ -239,7 +231,8 @@ static struct cxl_dport *find_dport_by_dev(struct cxl_port *port, struct device
return NULL;
}
static struct acpi_device *to_cxl_host_bridge(struct device *dev)
__mock struct acpi_device *to_cxl_host_bridge(struct device *host,
struct device *dev)
{
struct acpi_device *adev = to_acpi_device(dev);
......@@ -257,9 +250,9 @@ static struct acpi_device *to_cxl_host_bridge(struct device *dev)
*/
static int add_host_bridge_uport(struct device *match, void *arg)
{
struct acpi_device *bridge = to_cxl_host_bridge(match);
struct cxl_port *root_port = arg;
struct device *host = root_port->dev.parent;
struct acpi_device *bridge = to_cxl_host_bridge(host, match);
struct acpi_pci_root *pci_root;
struct cxl_walk_context ctx;
struct cxl_decoder *cxld;
......@@ -323,7 +316,7 @@ static int add_host_bridge_dport(struct device *match, void *arg)
struct acpi_cedt_chbs *chbs;
struct cxl_port *root_port = arg;
struct device *host = root_port->dev.parent;
struct acpi_device *bridge = to_cxl_host_bridge(match);
struct acpi_device *bridge = to_cxl_host_bridge(host, match);
if (!bridge)
return 0;
......@@ -375,6 +368,17 @@ static int add_root_nvdimm_bridge(struct device *match, void *data)
return 1;
}
static u32 cedt_instance(struct platform_device *pdev)
{
const bool *native_acpi0017 = acpi_device_get_match_data(&pdev->dev);
if (native_acpi0017 && *native_acpi0017)
return 0;
/* for cxl_test request a non-canonical instance */
return U32_MAX;
}
static int cxl_acpi_probe(struct platform_device *pdev)
{
int rc;
......@@ -388,7 +392,7 @@ static int cxl_acpi_probe(struct platform_device *pdev)
return PTR_ERR(root_port);
dev_dbg(host, "add: %s\n", dev_name(&root_port->dev));
status = acpi_get_table(ACPI_SIG_CEDT, 0, &acpi_cedt);
status = acpi_get_table(ACPI_SIG_CEDT, cedt_instance(pdev), &acpi_cedt);
if (ACPI_FAILURE(status))
return -ENXIO;
......@@ -419,9 +423,11 @@ static int cxl_acpi_probe(struct platform_device *pdev)
return 0;
}
static bool native_acpi0017 = true;
static const struct acpi_device_id cxl_acpi_ids[] = {
{ "ACPI0017", 0 },
{ "", 0 },
{ "ACPI0017", (unsigned long) &native_acpi0017 },
{ },
};
MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids);
......
......@@ -226,6 +226,14 @@ struct cxl_nvdimm {
struct nvdimm *nvdimm;
};
struct cxl_walk_context {
struct device *dev;
struct pci_bus *root;
struct cxl_port *port;
int error;
int count;
};
/**
* struct cxl_port - logical collection of upstream port devices and
* downstream port devices to construct a CXL memory
......@@ -325,4 +333,12 @@ struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev);
bool is_cxl_nvdimm(struct device *dev);
int devm_cxl_add_nvdimm(struct device *host, struct cxl_memdev *cxlmd);
struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(void);
/*
* Unit test builds overrides this to __weak, find the 'strong' version
* of these symbols in tools/testing/cxl/.
*/
#ifndef __mock
#define __mock static
#endif
#endif /* __CXL_H__ */
# SPDX-License-Identifier: GPL-2.0
ldflags-y += --wrap=is_acpi_device_node
ldflags-y += --wrap=acpi_get_table
ldflags-y += --wrap=acpi_put_table
ldflags-y += --wrap=acpi_evaluate_integer
ldflags-y += --wrap=acpi_pci_find_root
ldflags-y += --wrap=pci_walk_bus
ldflags-y += --wrap=nvdimm_bus_register
DRIVERS := ../../../drivers
CXL_SRC := $(DRIVERS)/cxl
CXL_CORE_SRC := $(DRIVERS)/cxl/core
ccflags-y := -I$(srctree)/drivers/cxl/
ccflags-y += -D__mock=__weak
obj-m += cxl_acpi.o
cxl_acpi-y := $(CXL_SRC)/acpi.o
cxl_acpi-y += mock_acpi.o
cxl_acpi-y += config_check.o
obj-m += cxl_pmem.o
cxl_pmem-y := $(CXL_SRC)/pmem.o
cxl_pmem-y += config_check.o
obj-m += cxl_core.o
cxl_core-y := $(CXL_CORE_SRC)/bus.o
cxl_core-y += $(CXL_CORE_SRC)/pmem.o
cxl_core-y += $(CXL_CORE_SRC)/regs.o
cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += config_check.o
obj-m += test/
// SPDX-License-Identifier: GPL-2.0
#include <linux/bug.h>
void check(void)
{
/*
* These kconfig symbols must be set to "m" for cxl_test to load
* and operate.
*/
BUILD_BUG_ON(!IS_MODULE(CONFIG_CXL_BUS));
BUILD_BUG_ON(!IS_MODULE(CONFIG_CXL_ACPI));
BUILD_BUG_ON(!IS_MODULE(CONFIG_CXL_PMEM));
}
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2021 Intel Corporation. All rights reserved. */
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/acpi.h>
#include <linux/pci.h>
#include <cxl.h>
#include "test/mock.h"
struct acpi_device *to_cxl_host_bridge(struct device *host, struct device *dev)
{
int index;
struct acpi_device *adev, *found = NULL;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
if (ops && ops->is_mock_bridge(dev)) {
found = ACPI_COMPANION(dev);
goto out;
}
if (dev->bus == &platform_bus_type)
goto out;
adev = to_acpi_device(dev);
if (!acpi_pci_find_root(adev->handle))
goto out;
if (strcmp(acpi_device_hid(adev), "ACPI0016") == 0) {
found = adev;
dev_dbg(host, "found host bridge %s\n", dev_name(&adev->dev));
}
out:
put_cxl_mock_ops(index);
return found;
}
static int match_add_root_port(struct pci_dev *pdev, void *data)
{
struct cxl_walk_context *ctx = data;
struct pci_bus *root_bus = ctx->root;
struct cxl_port *port = ctx->port;
int type = pci_pcie_type(pdev);
struct device *dev = ctx->dev;
u32 lnkcap, port_num;
int rc;
if (pdev->bus != root_bus)
return 0;
if (!pci_is_pcie(pdev))
return 0;
if (type != PCI_EXP_TYPE_ROOT_PORT)
return 0;
if (pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCAP,
&lnkcap) != PCIBIOS_SUCCESSFUL)
return 0;
/* TODO walk DVSEC to find component register base */
port_num = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
rc = cxl_add_dport(port, &pdev->dev, port_num, CXL_RESOURCE_NONE);
if (rc) {
dev_err(dev, "failed to add dport: %s (%d)\n",
dev_name(&pdev->dev), rc);
ctx->error = rc;
return rc;
}
ctx->count++;
dev_dbg(dev, "add dport%d: %s\n", port_num, dev_name(&pdev->dev));
return 0;
}
static int mock_add_root_port(struct platform_device *pdev, void *data)
{
struct cxl_walk_context *ctx = data;
struct cxl_port *port = ctx->port;
struct device *dev = ctx->dev;
int rc;
rc = cxl_add_dport(port, &pdev->dev, pdev->id, CXL_RESOURCE_NONE);
if (rc) {
dev_err(dev, "failed to add dport: %s (%d)\n",
dev_name(&pdev->dev), rc);
ctx->error = rc;
return rc;
}
ctx->count++;
dev_dbg(dev, "add dport%d: %s\n", pdev->id, dev_name(&pdev->dev));
return 0;
}
int match_add_root_ports(struct pci_dev *dev, void *data)
{
int index, rc;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
struct platform_device *pdev = (struct platform_device *) dev;
if (ops && ops->is_mock_port(pdev))
rc = mock_add_root_port(pdev, data);
else
rc = match_add_root_port(dev, data);
put_cxl_mock_ops(index);
return rc;
}
# SPDX-License-Identifier: GPL-2.0
obj-m += cxl_test.o
obj-m += cxl_mock.o
cxl_test-y := cxl.o
cxl_mock-y := mock.o
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-only
//Copyright(c) 2021 Intel Corporation. All rights reserved.
#include <linux/libnvdimm.h>
#include <linux/rculist.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/acpi.h>
#include <linux/pci.h>
#include "mock.h"
static LIST_HEAD(mock);
void register_cxl_mock_ops(struct cxl_mock_ops *ops)
{
list_add_rcu(&ops->list, &mock);
}
EXPORT_SYMBOL_GPL(register_cxl_mock_ops);
static DEFINE_SRCU(cxl_mock_srcu);
void unregister_cxl_mock_ops(struct cxl_mock_ops *ops)
{
list_del_rcu(&ops->list);
synchronize_srcu(&cxl_mock_srcu);
}
EXPORT_SYMBOL_GPL(unregister_cxl_mock_ops);
struct cxl_mock_ops *get_cxl_mock_ops(int *index)
{
*index = srcu_read_lock(&cxl_mock_srcu);
return list_first_or_null_rcu(&mock, struct cxl_mock_ops, list);
}
EXPORT_SYMBOL_GPL(get_cxl_mock_ops);
void put_cxl_mock_ops(int index)
{
srcu_read_unlock(&cxl_mock_srcu, index);
}
EXPORT_SYMBOL_GPL(put_cxl_mock_ops);
bool __wrap_is_acpi_device_node(const struct fwnode_handle *fwnode)
{
struct acpi_device *adev =
container_of(fwnode, struct acpi_device, fwnode);
int index;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
bool retval = false;
if (ops)
retval = ops->is_mock_adev(adev);
if (!retval)
retval = is_acpi_device_node(fwnode);
put_cxl_mock_ops(index);
return retval;
}
EXPORT_SYMBOL(__wrap_is_acpi_device_node);
acpi_status __wrap_acpi_get_table(char *signature, u32 instance,
struct acpi_table_header **out_table)
{
int index;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
acpi_status status;
if (ops)
status = ops->acpi_get_table(signature, instance, out_table);
else
status = acpi_get_table(signature, instance, out_table);
put_cxl_mock_ops(index);
return status;
}
EXPORT_SYMBOL(__wrap_acpi_get_table);
void __wrap_acpi_put_table(struct acpi_table_header *table)
{
int index;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
if (ops)
ops->acpi_put_table(table);
else
acpi_put_table(table);
put_cxl_mock_ops(index);
}
EXPORT_SYMBOL(__wrap_acpi_put_table);
acpi_status __wrap_acpi_evaluate_integer(acpi_handle handle,
acpi_string pathname,
struct acpi_object_list *arguments,
unsigned long long *data)
{
int index;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
acpi_status status;
if (ops)
status = ops->acpi_evaluate_integer(handle, pathname, arguments,
data);
else
status = acpi_evaluate_integer(handle, pathname, arguments,
data);
put_cxl_mock_ops(index);
return status;
}
EXPORT_SYMBOL(__wrap_acpi_evaluate_integer);
struct acpi_pci_root *__wrap_acpi_pci_find_root(acpi_handle handle)
{
int index;
struct acpi_pci_root *root;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
if (ops)
root = ops->acpi_pci_find_root(handle);
else
root = acpi_pci_find_root(handle);
put_cxl_mock_ops(index);
return root;
}
EXPORT_SYMBOL_GPL(__wrap_acpi_pci_find_root);
void __wrap_pci_walk_bus(struct pci_bus *bus,
int (*cb)(struct pci_dev *, void *), void *userdata)
{
int index;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
if (ops && ops->is_mock_bus(bus)) {
int rc, i;
/*
* Simulate 2 root ports per host-bridge and no
* depth recursion.
*/
for (i = 0; i < 2; i++) {
rc = cb((struct pci_dev *) ops->mock_port(bus, i),
userdata);
if (rc)
break;
}
} else
pci_walk_bus(bus, cb, userdata);
put_cxl_mock_ops(index);
}
EXPORT_SYMBOL_GPL(__wrap_pci_walk_bus);
struct nvdimm_bus *
__wrap_nvdimm_bus_register(struct device *dev,
struct nvdimm_bus_descriptor *nd_desc)
{
int index;
struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
if (ops && ops->is_mock_dev(dev->parent->parent))
nd_desc->provider_name = "cxl_test";
put_cxl_mock_ops(index);
return nvdimm_bus_register(dev, nd_desc);
}
EXPORT_SYMBOL_GPL(__wrap_nvdimm_bus_register);
MODULE_LICENSE("GPL v2");
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/list.h>
#include <linux/acpi.h>
struct cxl_mock_ops {
struct list_head list;
bool (*is_mock_adev)(struct acpi_device *dev);
acpi_status (*acpi_get_table)(char *signature, u32 instance,
struct acpi_table_header **out_table);
void (*acpi_put_table)(struct acpi_table_header *table);
bool (*is_mock_bridge)(struct device *dev);
acpi_status (*acpi_evaluate_integer)(acpi_handle handle,
acpi_string pathname,
struct acpi_object_list *arguments,
unsigned long long *data);
struct acpi_pci_root *(*acpi_pci_find_root)(acpi_handle handle);
struct platform_device *(*mock_port)(struct pci_bus *bus, int index);
bool (*is_mock_bus)(struct pci_bus *bus);
bool (*is_mock_port)(struct platform_device *pdev);
bool (*is_mock_dev)(struct device *dev);
};
void register_cxl_mock_ops(struct cxl_mock_ops *ops);
void unregister_cxl_mock_ops(struct cxl_mock_ops *ops);
struct cxl_mock_ops *get_cxl_mock_ops(int *index);
void put_cxl_mock_ops(int index);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment