Skip to content

Commit

Permalink
tools/testing/cxl: Introduce a mocked-up CXL port hierarchy
Browse files Browse the repository at this point in the history
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: Ben Widawsky <[email protected]>
Reported-by: Vishal Verma <[email protected]>
Link: https://lore.kernel.org/r/[email protected] [1]
Link: https://lore.kernel.org/r/163164680798.2831381.838684634806668012.stgit@dwillia2-desk3.amr.corp.intel.com
Reviewed-by: Jonathan Cameron <[email protected]>
Signed-off-by: Dan Williams <[email protected]>
  • Loading branch information
djbw committed Sep 21, 2021
1 parent 2e52b62 commit 67dcdd4
Show file tree
Hide file tree
Showing 9 changed files with 908 additions and 15 deletions.
36 changes: 21 additions & 15 deletions drivers/cxl/acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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);

Expand Down
16 changes: 16 additions & 0 deletions drivers/cxl/cxl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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__ */
36 changes: 36 additions & 0 deletions tools/testing/cxl/Kbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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/
13 changes: 13 additions & 0 deletions tools/testing/cxl/config_check.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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));
}
109 changes: 109 additions & 0 deletions tools/testing/cxl/mock_acpi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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;
}
6 changes: 6 additions & 0 deletions tools/testing/cxl/test/Kbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 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
Loading

0 comments on commit 67dcdd4

Please sign in to comment.