summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-11-13 17:23:45 +0000
committerMark Brown <broonie@kernel.org>2026-11-13 17:23:45 +0000
commit91e174e86ee7b4c636fea03752d43aa0ded8df4e (patch)
treeac7af2d6e5ab445c4100a968ea9a4a4e4a42a4b3
parent4507970404e9dde2a3cb6b799d36d44f03131879 (diff)
parentd4780f61df995bef6b7b97958b9f1856593740d7 (diff)
downloadlinux-next-91e174e86ee7b4c636fea03752d43aa0ded8df4e.tar.gz
linux-next-91e174e86ee7b4c636fea03752d43aa0ded8df4e.zip
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git
-rw-r--r--Documentation/ABI/testing/sysfs-bus-cxl22
-rw-r--r--Documentation/driver-api/cxl/index.rst1
-rw-r--r--Documentation/driver-api/cxl/maturity-map.rst16
-rw-r--r--Documentation/driver-api/cxl/testing.rst139
-rw-r--r--drivers/cxl/.kunitconfig4
-rw-r--r--drivers/cxl/Kconfig16
-rw-r--r--drivers/cxl/Makefile1
-rw-r--r--drivers/cxl/acpi.c19
-rw-r--r--drivers/cxl/core/Makefile1
-rw-r--r--drivers/cxl/core/cdat.c19
-rw-r--r--drivers/cxl/core/core.h10
-rw-r--r--drivers/cxl/core/hdm.c122
-rw-r--r--drivers/cxl/core/pci.c583
-rw-r--r--drivers/cxl/core/port.c48
-rw-r--r--drivers/cxl/core/region.c451
-rw-r--r--drivers/cxl/core/regs.c14
-rw-r--r--drivers/cxl/core/tests/Makefile5
-rw-r--r--drivers/cxl/core/tests/mbox_kunit.c250
-rw-r--r--drivers/cxl/cxl.h90
-rw-r--r--drivers/cxl/cxlmem.h8
-rw-r--r--drivers/cxl/pci.c8
-rw-r--r--drivers/cxl/port.c62
-rw-r--r--drivers/cxl/tests/Makefile5
-rw-r--r--drivers/cxl/tests/hdm_encoding_kunit.c159
-rw-r--r--include/cxl/cxl.h13
-rw-r--r--tools/testing/cxl/Kbuild1
-rw-r--r--tools/testing/cxl/test/accel.c2
-rw-r--r--tools/testing/cxl/test/cxl.c278
-rw-r--r--tools/testing/cxl/test/mock.c13
-rw-r--r--tools/testing/cxl/test/mock.h1
30 files changed, 2159 insertions, 202 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-cxl b/Documentation/ABI/testing/sysfs-bus-cxl
index 7352dbd70bc7..d5b775bcb9bd 100644
--- a/Documentation/ABI/testing/sysfs-bus-cxl
+++ b/Documentation/ABI/testing/sysfs-bus-cxl
@@ -432,6 +432,28 @@ Description:
current cached value.
+What: /sys/bus/cxl/devices/decoderX.Y/cap_back_invalidate
+Date: October, 2026
+KernelVersion: v7.5
+Contact: linux-cxl@vger.kernel.org
+Description:
+ (RO) When a CXL decoder is of devtype "cxl_decoder_root", '1'
+ indicates the fixed memory window carries the CFMWS
+ Back-Invalidate restriction, so HDM-DB memory may be mapped
+ behind it.
+
+
+What: /sys/bus/cxl/devices/decoderX.Y/back_invalidate
+Date: October, 2026
+KernelVersion: v7.5
+Contact: linux-cxl@vger.kernel.org
+Description:
+ (RO) Shows '1' if this endpoint decoder is currently configured
+ for HDM-DB (device-managed coherency with back-invalidate).
+ The HDM-DB state is inherited from the region the decoder is
+ attached to, which is in turn set from the chosen root
+ decoder's CFMWS BI restriction (see cap_back_invalidate).
+
What: /sys/bus/cxl/devices/decoderX.Y/delete_region
Date: May, 2022
KernelVersion: v6.0
diff --git a/Documentation/driver-api/cxl/index.rst b/Documentation/driver-api/cxl/index.rst
index 3dfae1d310ca..786c189366ca 100644
--- a/Documentation/driver-api/cxl/index.rst
+++ b/Documentation/driver-api/cxl/index.rst
@@ -15,6 +15,7 @@ that have impacts on each other. The docs here break up configurations steps.
theory-of-operation
maturity-map
conventions
+ testing
.. toctree::
:maxdepth: 2
diff --git a/Documentation/driver-api/cxl/maturity-map.rst b/Documentation/driver-api/cxl/maturity-map.rst
index fefd31899229..35097537cf3f 100644
--- a/Documentation/driver-api/cxl/maturity-map.rst
+++ b/Documentation/driver-api/cxl/maturity-map.rst
@@ -63,6 +63,11 @@ in place, but there are several corner cases that are pending closure.
* [0] Decoder target and granularity constraints
+* [1] :ref:`Back-Invalidate (HDM-DB) <back-invalidate>`
+
+ * [3] BI topology enable / disable (endpoint, DSP, USP route table, RP)
+ * [3] Firmware-enabled path adoption
+
* [2] Performance enumeration
* [3] Endpoint CDAT
@@ -170,7 +175,7 @@ Accelerator
-----------
* [0] Accelerator memory enumeration HDM-D (CXL 1.1/2.0 Type-2)
-* [0] Accelerator memory enumeration HDM-DB (CXL 3.0 Type-2)
+* [1] Accelerator memory enumeration HDM-DB (CXL 3.0 Type-2)
* [0] CXL.cache 68b (CXL 2.0)
* [0] CXL.cache 256b Cache IDs (CXL 3.0)
@@ -196,6 +201,15 @@ Details
hiding some standard registers like PCIe Link Status / Capabilities in
the CXL RCRB (Root Complex Register Block).
+.. _back-invalidate:
+
+* **Back-Invalidate**: HDM-DB lets a device snoop the host over the
+ CXL.mem BISnp channel instead of CXL.cache. The kernel enables BI on
+ every port between a device and its root port, requires 256B Flit
+ mode on the path, adopts paths firmware already enabled, and creates
+ HDM-DB regions under CFMWS windows carrying the Back-Invalidate
+ restriction for Type 3 and Type 2 devices alike.
+
.. _background-commands:
* **Background commands**: The CXL background command mechanism is
diff --git a/Documentation/driver-api/cxl/testing.rst b/Documentation/driver-api/cxl/testing.rst
new file mode 100644
index 000000000000..6ce1cb581ec4
--- /dev/null
+++ b/Documentation/driver-api/cxl/testing.rst
@@ -0,0 +1,139 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===========
+Testing CXL
+===========
+
+CXL testing uses two complementary frameworks: cxl_test exercises the driver
+stack against an emulated CXL topology, while KUnit tests self-contained logic
+in isolation. Changes should preserve existing test coverage and extend the
+framework appropriate to the behavior being changed.
+
+cxl_test
+========
+
+The cxl_test modules, built from tools/testing/cxl/, emulate a CXL topology with
+host bridges, switches, memory devices, and mailboxes. This allows the CXL driver
+stack to be tested without hardware. The tests are maintained in the ndctl repo
+at https://github.com/pmem/ndctl, under test/cxl-<name>.sh.
+
+Use cxl_test for behavior that depends on a live topology: port and endpoint
+enumeration, region creation and assembly, decoder commit, and anything that
+crosses the sysfs ABI.
+
+Build the cxl_test modules from the top of the kernel tree::
+
+ make M=tools/testing/cxl
+
+After installing the modules for the running kernel, run the CXL suite from an
+ndctl build::
+
+ meson test -C build --suite cxl
+
+For more on building and running the ndctl unit tests, see:
+
+- the Unit Tests section of the ndctl README,
+ https://github.com/pmem/ndctl?tab=readme-ov-file#unit-tests
+- the ndctl test runner,
+ https://github.com/pmem/ndctl-test-runner?tab=readme-ov-file#ndctl-test-runner
+
+
+KUnit
+=====
+
+KUnit tests run self-contained logic in the kernel without any CXL topology.
+KUnit is suited to logic that can be exercised with controlled test inputs,
+including encode/decode helpers, address translation and interleave calculations,
+parsers of device- or firmware-supplied data, and mailbox command handling
+through a stub transport.
+
+To run all of the CXL KUnit tests, from the top of the kernel tree::
+
+ ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/cxl
+
+This builds a test kernel in .kunit/ using drivers/cxl/.kunitconfig and runs it
+under User Mode Linux (UML). The running kernel is not modified, and no
+installation is required. The test kernel runs the tests, reports the results,
+and exits.
+
+Use --arch=<arch> to run the tests under QEMU instead.
+Use --arch=help to list supported architectures.
+
+Running during development
+--------------------------
+
+kunit.py builds in .kunit/, but kbuild does not allow an out-of-tree build when
+the source tree already contains an in-tree build. If you build the kernel
+in-tree, use a separate worktree for KUnit. The worktree contains the committed
+sources at the selected revision::
+
+ git worktree add ../linux-kunit HEAD
+ cd ../linux-kunit
+ ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/cxl
+
+To run a single suite, name it::
+
+ ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/cxl cxl_core_mbox
+
+Enable KASAN when testing memory safety error paths. It can detect an out-of-bounds
+access even when the resulting value does not cause a test assertion to fail. Run
+under QEMU, for example x86_64::
+
+ ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/cxl \
+ --arch=x86_64 --kconfig_add CONFIG_KASAN=y
+
+Maintaining KUnit tests
+-----------------------
+
+Use loop bounds and table sizes from the code under test, such as ARRAY_SIZE()
+of a lookup table, so tests adapt as the code evolves. Derive expected results
+from the specification, not the implementation, so tests can detect unintended
+behavior changes.
+
+When a patch changes behavior covered by a test, update the test in the same
+patch. For example, add newly supported encodings to a valid-value table or
+adjust the expected errno for a new return code.
+
+A failing test may indicate a regression or a test that needs updating. Run the
+CXL KUnit tests throughout development, not just before posting, to catch either
+case while making changes.
+
+Adding a KUnit test
+-------------------
+
+A CXL KUnit test follows these rules:
+
+- Place the test in a tests/ directory beside the code under test, in a
+ file named <name>_kunit.c.
+
+- Derive the module and suite names from the path below drivers/cxl/, excluding
+ tests/. For example, drivers/cxl/core/tests/port_kunit.c produces module
+ cxl_core_port_kunit and suite cxl_core_port, while drivers/cxl/tests/port_kunit.c
+ produces module cxl_port_kunit and suite cxl_port.
+
+- Build new suites under CONFIG_CXL_KUNIT_TEST rather than introducing a separate
+ Kconfig option for each suite. Individual suites can be selected by name with
+ kunit.py or by loading their module.
+
+- If the code under test depends on another configuration option, such as
+ CONFIG_CXL_REGION, make the test build conditional on that option in
+ tests/Makefile and enable it in drivers/cxl/.kunitconfig.
+
+- Use exported interfaces where possible, importing the CXL symbol namespace as
+ needed.
+
+For an example of testing mailbox command handling with a stub transport, see
+drivers/cxl/core/tests/mbox_kunit.c.
+
+When to add a test
+==================
+
+New or modified self-contained logic should be accompanied by KUnit coverage where
+practical. Extend an existing suite when possible, or introduce a new one for
+functionality that is not yet covered.
+
+Bug fixes should include regression coverage that reproduces the problem without
+the fix.
+
+Changes to topology-dependent behavior should be covered by cxl_test tests in ndctl.
+KUnit coverage complements, but does not replace, cxl_test.
diff --git a/drivers/cxl/.kunitconfig b/drivers/cxl/.kunitconfig
new file mode 100644
index 000000000000..4fee684f5ff6
--- /dev/null
+++ b/drivers/cxl/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_PCI=y
+CONFIG_CXL_BUS=y
+CONFIG_CXL_KUNIT_TEST=y
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 80aeb0d556bd..fdd57a4118db 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -243,4 +243,20 @@ config CXL_ATL
depends on CXL_REGION
depends on ACPI_PRMT && AMD_NB
+config CXL_KUNIT_TEST
+ tristate "KUnit tests for CXL" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the KUnit tests for the CXL subsystem. When built
+ as a module, each test suite is its own cxl_<name>_kunit
+ module. This one option covers every CXL KUnit test, so new
+ tests need no new configuration.
+
+ For more information on KUnit and unit tests in general,
+ please refer to the KUnit documentation in
+ Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
endif
diff --git a/drivers/cxl/Makefile b/drivers/cxl/Makefile
index 2caa90fa4bf2..d57fb0e9e7da 100644
--- a/drivers/cxl/Makefile
+++ b/drivers/cxl/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o
obj-$(CONFIG_CXL_PMEM) += cxl_pmem.o
obj-$(CONFIG_CXL_MEM) += cxl_mem.o
obj-$(CONFIG_CXL_PCI) += cxl_pci.o
+obj-$(CONFIG_CXL_KUNIT_TEST) += tests/
cxl_port-y := port.o
cxl_acpi-y := acpi.o
diff --git a/drivers/cxl/acpi.c b/drivers/cxl/acpi.c
index 4516ad48f5be..666452c04c33 100644
--- a/drivers/cxl/acpi.c
+++ b/drivers/cxl/acpi.c
@@ -142,15 +142,17 @@ static unsigned long cfmws_to_decoder_flags(int restrictions)
unsigned long flags = CXL_DECODER_F_ENABLE;
if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_DEVMEM)
- flags |= CXL_DECODER_F_TYPE2;
+ flags |= CXL_DECODER_F_DEVMEM;
if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM)
- flags |= CXL_DECODER_F_TYPE3;
+ flags |= CXL_DECODER_F_HOSTONLY;
if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE)
flags |= CXL_DECODER_F_RAM;
if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_PMEM)
flags |= CXL_DECODER_F_PMEM;
if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_FIXED)
flags |= CXL_DECODER_F_LOCK;
+ if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_BI)
+ flags |= CXL_DECODER_F_BI;
return flags;
}
@@ -197,6 +199,18 @@ static int cxl_acpi_cfmws_verify(struct device *dev,
dev_dbg(dev, "CFMWS length %d greater than expected %d\n",
cfmws->header.length, expected_len);
+ if ((cfmws->restrictions & ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM) &&
+ (cfmws->restrictions & ACPI_CEDT_CFMWS_RESTRICT_BI)) {
+ dev_err(dev, "CFMWS cannot have both HDM-H and HDM-DB\n");
+ return -EINVAL;
+ }
+
+ if (!(cfmws->restrictions & (ACPI_CEDT_CFMWS_RESTRICT_DEVMEM |
+ ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM))) {
+ dev_err(dev, "CFMWS has no coherency model\n");
+ return -EINVAL;
+ }
+
return 0;
}
@@ -442,7 +456,6 @@ static int __cxl_parse_cfmws(struct acpi_cedt_cfmws *cfmws,
cxld = &cxlrd->cxlsd.cxld;
cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
cxld->hpa_range = (struct range) {
.start = cfmws->base_hpa,
.end = cfmws->base_hpa + cfmws->window_size - 1,
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index ce7213818d3c..b2627933b7fb 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
+obj-$(CONFIG_CXL_KUNIT_TEST) += tests/
ccflags-y += -I$(srctree)/drivers/cxl
CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
diff --git a/drivers/cxl/core/cdat.c b/drivers/cxl/core/cdat.c
index f5f86b69c86d..7acc516ae9fa 100644
--- a/drivers/cxl/core/cdat.c
+++ b/drivers/cxl/core/cdat.c
@@ -1124,8 +1124,8 @@ void cxl_region_shared_upstream_bandwidth_update(struct cxl_region *cxlr)
cxl_region_update_bandwidth(cxlr, usp_xa);
}
-void cxl_region_perf_data_calculate(struct cxl_region *cxlr,
- struct cxl_endpoint_decoder *cxled)
+static void cxl_region_perf_data_add(struct cxl_region *cxlr,
+ struct cxl_endpoint_decoder *cxled)
{
struct cxl_dpa_perf *perf;
@@ -1147,3 +1147,18 @@ void cxl_region_perf_data_calculate(struct cxl_region *cxlr,
cxlr->coord[i].write_bandwidth += perf->coord[i].write_bandwidth;
}
}
+
+/**
+ * cxl_region_perf_data_update - Recalculate region performance data
+ * @cxlr: region with all targets attached
+ *
+ * Reset the access coordinates and calculate them from the current targets.
+ */
+void cxl_region_perf_data_update(struct cxl_region *cxlr)
+{
+ struct cxl_region_params *p = &cxlr->params;
+
+ memset(cxlr->coord, 0, sizeof(cxlr->coord));
+ for (int i = 0; i < p->nr_targets; i++)
+ cxl_region_perf_data_add(cxlr, p->targets[i]);
+}
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 79918fde1575..041c86c43d19 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -204,11 +204,21 @@ static inline void devm_cxl_dport_ras_setup(struct cxl_dport *dport) { }
#endif /* CONFIG_CXL_RAS */
int cxl_gpf_port_setup(struct cxl_dport *dport);
+void devm_cxl_dport_bi_setup(struct cxl_dport *dport);
+
+static inline bool cxl_bi_decoder_enabled(struct cxl_port *port)
+{
+ return FIELD_GET(CXL_BI_DECODER_CTRL_BI_ENABLE,
+ readl(port->regs.bi_decoder +
+ CXL_BI_DECODER_CTRL_OFFSET));
+}
struct cxl_hdm;
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,
struct cxl_endpoint_dvsec_info *info);
+bool cxl_hdm_bi_committed(struct cxl_port *endpoint);
int cxl_port_get_possible_dports(struct cxl_port *port);
+enum cxl_decoder_type cxled_default_type(struct cxl_endpoint_decoder *cxled);
#ifdef CONFIG_CXL_FEATURES
struct cxl_feat_entry *
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 6329eb3b9214..ad60be096449 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -90,6 +90,8 @@ static void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm)
cxlhdm->iw_cap_mask |= BIT(3) | BIT(6) | BIT(12);
if (FIELD_GET(CXL_HDM_DECODER_INTERLEAVE_16_WAY, hdm_cap))
cxlhdm->iw_cap_mask |= BIT(16);
+ cxlhdm->supported_coherency =
+ FIELD_GET(CXL_HDM_DECODER_SUPPORTED_COHERENCY_MASK, hdm_cap);
}
static bool should_emulate_decoders(struct cxl_endpoint_dvsec_info *info)
@@ -191,6 +193,38 @@ static struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port,
return cxlhdm;
}
+/*
+ * Whether any of the endpoint's HDM decoders still decodes with its BI
+ * bit set, read from the hardware: a decoder can outlive the driver's
+ * teardown committed, whether locked, in a region that is never reset,
+ * or never in a region at all.
+ */
+bool cxl_hdm_bi_committed(struct cxl_port *endpoint)
+{
+ struct cxl_hdm *cxlhdm = dev_get_drvdata(&endpoint->dev);
+ void __iomem *hdm;
+ u32 ctrl;
+ int i;
+
+ if (!cxlhdm || !cxlhdm->regs.hdm_decoder)
+ return false;
+ hdm = cxlhdm->regs.hdm_decoder;
+
+ /* nothing decodes with decoding off, emulated, or the device gone */
+ ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ if (ctrl == U32_MAX || !(ctrl & CXL_HDM_DECODER_ENABLE))
+ return false;
+
+ for (i = 0; i < cxlhdm->decoder_count; i++) {
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(i));
+ if ((ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) &&
+ (ctrl & CXL_HDM_DECODER0_CTRL_BI))
+ return true;
+ }
+
+ return false;
+}
+
static void __cxl_dpa_debug(struct seq_file *file, struct resource *r, int depth)
{
unsigned long long start = r->start, end = r->end;
@@ -756,9 +790,26 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
{
+ bool bi = cxld->target_type == CXL_DECODER_DEVMEM &&
+ cxld->region && cxl_root_decoder_is_bi(cxld->region->cxlrd);
+
u32p_replace_bits(ctrl,
!!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
CXL_HDM_DECODER0_CTRL_HOSTONLY);
+ u32p_replace_bits(ctrl, bi, CXL_HDM_DECODER0_CTRL_BI);
+
+ /*
+ * Interleave Set Position: the endpoint's position in the
+ * interleave set, RWL for a BI-capable device and reserved
+ * otherwise (CXL 4.0 Table 8-123).
+ */
+ if (bi && is_endpoint_decoder(&cxld->dev)) {
+ struct cxl_endpoint_decoder *cxled =
+ to_cxl_endpoint_decoder(&cxld->dev);
+
+ u32p_replace_bits(ctrl, cxled->pos,
+ CXL_HDM_DECODER0_CTRL_ISP_MASK);
+ }
}
static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
@@ -856,6 +907,7 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
int id = cxld->id, rc;
+ u32 ctrl;
if (cxld->flags & CXL_DECODER_F_ENABLE)
return 0;
@@ -899,6 +951,10 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
port->commit_end++;
cxld->flags |= CXL_DECODER_F_ENABLE;
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (ctrl & CXL_HDM_DECODER0_CTRL_BI)
+ cxld->flags |= CXL_DECODER_F_BI;
+
return 0;
}
@@ -969,7 +1025,7 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
writel(0, hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
- cxld->flags &= ~CXL_DECODER_F_ENABLE;
+ cxld->flags &= ~(CXL_DECODER_F_ENABLE | CXL_DECODER_F_BI);
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -1022,6 +1078,26 @@ static int cxl_setup_hdm_decoder_from_dvsec(
return 0;
}
+/*
+ * HDMs that advertise support for both coherency modes
+ * (CXL_HDM_DECODER_COHERENCY_BOTH) default to host-only; the region
+ * attach path switches target_type to device-coherent if the region's
+ * root decoder has the CFMWS BI bit set. Only HDMs that strictly
+ * support device-coherent mode default to HDM-DB.
+ */
+enum cxl_decoder_type cxled_default_type(struct cxl_endpoint_decoder *cxled)
+{
+ struct cxl_dev_state *cxlds = cxled_to_memdev(cxled)->cxlds;
+ struct cxl_port *port = cxled_to_port(cxled);
+ struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
+
+ if (cxlds->type == CXL_DEVTYPE_CLASSMEM &&
+ cxlhdm->supported_coherency != CXL_HDM_DECODER_COHERENCY_DEV)
+ return CXL_DECODER_HOSTONLYMEM;
+
+ return CXL_DECODER_DEVMEM;
+}
+
static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
void __iomem *hdm, int which,
u64 *dpa_base, struct cxl_endpoint_dvsec_info *info)
@@ -1069,11 +1145,32 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
cxld->flags |= CXL_DECODER_F_ENABLE;
if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
cxld->flags |= CXL_DECODER_F_LOCK;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_BI)
+ cxld->flags |= CXL_DECODER_F_BI;
+
if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
cxld->target_type = CXL_DECODER_HOSTONLYMEM;
else
cxld->target_type = CXL_DECODER_DEVMEM;
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_BI, ctrl)) {
+ struct cxl_dev_state *cxlds = cxled ?
+ cxled_to_memdev(cxled)->cxlds : NULL;
+
+ if (cxld->target_type == CXL_DECODER_HOSTONLYMEM) {
+ dev_warn(&port->dev,
+ "decoder%d.%d: BI with host-only\n",
+ port->id, cxld->id);
+ return -ENXIO;
+ }
+ if (cxlds && !cxlds->bi_capable) {
+ dev_warn(&port->dev,
+ "decoder%d.%d: path not BI capable\n",
+ port->id, cxld->id);
+ return -ENXIO;
+ }
+ }
+
guard(rwsem_write)(&cxl_rwsem.region);
if (cxld->id != cxl_num_decoders_committed(port)) {
dev_warn(&port->dev,
@@ -1085,17 +1182,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
port->commit_end = cxld->id;
} else {
if (cxled) {
- struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
- struct cxl_dev_state *cxlds = cxlmd->cxlds;
-
- /*
- * Default by devtype until a device arrives that needs
- * more precision.
- */
- if (cxlds->type == CXL_DEVTYPE_CLASSMEM)
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- else
- cxld->target_type = CXL_DECODER_DEVMEM;
+ cxld->target_type = cxled_default_type(cxled);
} else {
/* To be overridden by region type at commit time */
cxld->target_type = CXL_DECODER_HOSTONLYMEM;
@@ -1358,6 +1445,17 @@ int devm_cxl_endpoint_decoders_setup(struct cxl_port *port)
if (rc)
return rc;
+ /*
+ * Between the port's HDM state and its decoders: devres,
+ * unwinding in reverse, brings BI down only after the decoders
+ * quiesce, while its slow walk still precedes the HDM state
+ * free. Must also precede region discovery, where HDM-DB
+ * assembly requires cxlds->bi.
+ */
+ rc = cxl_bi_setup(port);
+ if (rc)
+ dev_dbg(&port->dev, "BI setup failed rc=%d\n", rc);
+
return devm_cxl_enumerate_decoders(cxlhdm, &info);
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_endpoint_decoders_setup, "CXL");
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 125abb36df7f..3c37584a661e 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -2,12 +2,14 @@
/* Copyright(c) 2021 Intel Corporation. All rights reserved. */
#include <linux/units.h>
#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/iopoll.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <cxl/pci.h>
#include <linux/aer.h>
+#include <linux/string_choices.h>
#include <cxlpci.h>
#include <cxlmem.h>
#include <cxl.h>
@@ -949,3 +951,584 @@ int cxl_port_get_possible_dports(struct cxl_port *port)
return ctx.count;
}
+
+static void cxl_dport_map_bi(struct cxl_dport *dport)
+{
+ struct cxl_register_map *map = &dport->reg_map;
+ struct device *dev = dport->dport_dev;
+
+ if (!map->component_map.bi_decoder.valid) {
+ dev_dbg(dev, "BI Decoder registers not found\n");
+ return;
+ }
+
+ if (cxl_map_component_regs(map, &dport->regs.component,
+ BIT(CXL_CM_CAP_CAP_ID_BI_DECODER)))
+ dev_dbg(dev, "Failed to map BI Decoder capability\n");
+}
+
+/**
+ * devm_cxl_dport_bi_setup - Map BI Decoder registers on a CXL dport
+ * @dport: the cxl_dport that needs to be initialized
+ *
+ * Must be called while the dport's devres group is open so iomap
+ * allocations are released on dport removal.
+ */
+void devm_cxl_dport_bi_setup(struct cxl_dport *dport)
+{
+ if (!dev_is_pci(dport->dport_dev))
+ return;
+
+ switch (pci_pcie_type(to_pci_dev(dport->dport_dev))) {
+ case PCI_EXP_TYPE_ROOT_PORT:
+ case PCI_EXP_TYPE_DOWNSTREAM:
+ dport->reg_map.host = dport_to_host(dport);
+ cxl_dport_map_bi(dport);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * BI requires 256B Flit operation on the link. RP/DSP/endpoint must
+ * also have the BI Decoder cap mapped (@bi); for USPs the BI RT cap
+ * is optional per CXL 4.0 8.2.4.26, so absent @bi is allowed.
+ */
+static bool cxl_is_bi_capable(struct pci_dev *pdev, void __iomem *bi)
+{
+ if (!cxl_pci_flit_256(pdev))
+ return false;
+
+ if (pci_pcie_type(pdev) != PCI_EXP_TYPE_UPSTREAM && !bi) {
+ dev_dbg(&pdev->dev, "No BI Decoder registers.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/* limit any insane timeouts from hw */
+#define CXL_BI_COMMIT_MAXTMO_US (20 * USEC_PER_SEC)
+
+static unsigned long __cxl_bi_get_timeout_us(struct device *dev,
+ unsigned int scale,
+ unsigned int base)
+{
+ static const unsigned long scale_tbl[] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
+ };
+
+ if (scale >= ARRAY_SIZE(scale_tbl) || !base) {
+ dev_dbg(dev, "Invalid BI commit timeout: scale=%u base=%u\n",
+ scale, base);
+ return CXL_BI_COMMIT_MAXTMO_US;
+ }
+
+ return scale_tbl[scale] * base;
+}
+
+static int __cxl_bi_wait_commit(struct device *dev, void __iomem *status_reg,
+ u32 committed_bit, u32 err_bit,
+ unsigned int scale, unsigned int base)
+{
+ unsigned long tmo_us, poll_us;
+ ktime_t start;
+ u32 status;
+ int rc;
+
+ tmo_us = min_t(unsigned long, CXL_BI_COMMIT_MAXTMO_US,
+ __cxl_bi_get_timeout_us(dev, scale, base));
+ poll_us = max_t(unsigned long, tmo_us / 10, 1); /* ~10% */
+ start = ktime_get();
+
+ rc = readx_poll_timeout(readl, status_reg, status,
+ status & (committed_bit | err_bit),
+ poll_us, tmo_us);
+ if (rc) {
+ dev_err(dev, "BI-ID commit timed out (%luus)\n", tmo_us);
+ return rc; /* -ETIMEDOUT */
+ }
+
+ if (status & err_bit) {
+ dev_err(dev, "BI-ID commit rejected by hardware\n");
+ return -EIO;
+ }
+
+ dev_dbg(dev, "BI-ID commit wait took %lluus\n",
+ ktime_to_us(ktime_sub(ktime_get(), start)));
+ return 0;
+}
+
+/* BI RT only exists on switch upstream ports. */
+static int __cxl_bi_commit_rt(struct device *dev, void __iomem *bi)
+{
+ u32 status, ctrl;
+ unsigned int scale, base;
+
+ if (!FIELD_GET(CXL_BI_RT_CAPS_EXPLICIT_COMMIT_REQ,
+ readl(bi + CXL_BI_RT_CAPS_OFFSET)))
+ return 0;
+
+ ctrl = readl(bi + CXL_BI_RT_CTRL_OFFSET);
+ writel(ctrl & ~CXL_BI_RT_CTRL_BI_COMMIT, bi + CXL_BI_RT_CTRL_OFFSET);
+ writel(ctrl | CXL_BI_RT_CTRL_BI_COMMIT, bi + CXL_BI_RT_CTRL_OFFSET);
+
+ status = readl(bi + CXL_BI_RT_STATUS_OFFSET);
+ scale = FIELD_GET(CXL_BI_RT_STATUS_BI_COMMIT_TM_SCALE, status);
+ base = FIELD_GET(CXL_BI_RT_STATUS_BI_COMMIT_TM_BASE, status);
+
+ return __cxl_bi_wait_commit(dev, bi + CXL_BI_RT_STATUS_OFFSET,
+ CXL_BI_RT_STATUS_BI_COMMITTED,
+ CXL_BI_RT_STATUS_BI_ERR_NOT_COMMITTED,
+ scale, base);
+}
+
+static int __cxl_bi_commit_decoder(struct device *dev, void __iomem *bi)
+{
+ u32 status, ctrl;
+ unsigned int scale, base;
+
+ if (!FIELD_GET(CXL_BI_DECODER_CAPS_EXPLICIT_COMMIT_REQ,
+ readl(bi + CXL_BI_DECODER_CAPS_OFFSET)))
+ return 0;
+
+ ctrl = readl(bi + CXL_BI_DECODER_CTRL_OFFSET);
+ writel(ctrl & ~CXL_BI_DECODER_CTRL_BI_COMMIT,
+ bi + CXL_BI_DECODER_CTRL_OFFSET);
+ writel(ctrl | CXL_BI_DECODER_CTRL_BI_COMMIT,
+ bi + CXL_BI_DECODER_CTRL_OFFSET);
+
+ status = readl(bi + CXL_BI_DECODER_STATUS_OFFSET);
+ scale = FIELD_GET(CXL_BI_DECODER_STATUS_BI_COMMIT_TM_SCALE, status);
+ base = FIELD_GET(CXL_BI_DECODER_STATUS_BI_COMMIT_TM_BASE, status);
+
+ return __cxl_bi_wait_commit(dev, bi + CXL_BI_DECODER_STATUS_OFFSET,
+ CXL_BI_DECODER_STATUS_BI_COMMITTED,
+ CXL_BI_DECODER_STATUS_BI_ERR_NOT_COMMITTED,
+ scale, base);
+}
+
+/* Committed, or no explicit commit required */
+static bool __cxl_bi_committed(void __iomem *caps_reg, void __iomem *status_reg,
+ u32 req_bit, u32 committed_bit)
+{
+ if (!(readl(caps_reg) & req_bit))
+ return true;
+
+ return readl(status_reg) & committed_bit;
+}
+
+static bool cxl_bi_rt_committed(void __iomem *bi)
+{
+ return __cxl_bi_committed(bi + CXL_BI_RT_CAPS_OFFSET,
+ bi + CXL_BI_RT_STATUS_OFFSET,
+ CXL_BI_RT_CAPS_EXPLICIT_COMMIT_REQ,
+ CXL_BI_RT_STATUS_BI_COMMITTED);
+}
+
+static bool cxl_bi_decoder_committed(void __iomem *bi)
+{
+ return __cxl_bi_committed(bi + CXL_BI_DECODER_CAPS_OFFSET,
+ bi + CXL_BI_DECODER_STATUS_OFFSET,
+ CXL_BI_DECODER_CAPS_EXPLICIT_COMMIT_REQ,
+ CXL_BI_DECODER_STATUS_BI_COMMITTED);
+}
+
+static int cxl_bi_commit_dport(struct cxl_dport *dport)
+{
+ struct cxl_port *port = dport->port;
+ int rc;
+
+ lockdep_assert_held(&port->bi_lock);
+
+ /* root ports never require the explicit commit */
+ if (pci_pcie_type(to_pci_dev(dport->dport_dev)) !=
+ PCI_EXP_TYPE_DOWNSTREAM)
+ return 0;
+
+ rc = __cxl_bi_commit_decoder(dport->dport_dev, dport->regs.bi_decoder);
+ if (rc)
+ return rc;
+
+ if (port->regs.bi_rt)
+ rc = __cxl_bi_commit_rt(&port->dev, port->regs.bi_rt);
+
+ return rc;
+}
+
+/*
+ * Enable BI-ID changes in the given level of the topology.
+ * @direct says the device is connected directly to this dport, which
+ * takes BI Enable; every dport above it takes BI Forward.
+ */
+static int cxl_bi_ctrl_dport_enable(struct cxl_dport *dport, bool direct,
+ bool *adopt)
+{
+ struct cxl_port *port = dport->port;
+ u32 ctrl, value, set, clr;
+ void __iomem *bi;
+ int rc;
+
+ guard(mutex)(&port->bi_lock);
+
+ bi = dport->regs.bi_decoder;
+ if (!bi)
+ return -EINVAL;
+
+ ctrl = readl(bi + CXL_BI_DECODER_CTRL_OFFSET);
+
+ /*
+ * First reference through a level already up: firmware brought
+ * it up, or this driver did for a device it let go of while it
+ * still decoded with BI, which a dport re-created since cannot
+ * otherwise know. Either way it is not this driver's to take
+ * down: only a level it brought up from zero is ever cleared.
+ */
+ if (!dport->nr_bi &&
+ (ctrl & (CXL_BI_DECODER_CTRL_BI_FW | CXL_BI_DECODER_CTRL_BI_ENABLE)))
+ dport->bi_held = true;
+
+ set = direct ? CXL_BI_DECODER_CTRL_BI_ENABLE :
+ CXL_BI_DECODER_CTRL_BI_FW;
+ clr = direct ? CXL_BI_DECODER_CTRL_BI_FW :
+ CXL_BI_DECODER_CTRL_BI_ENABLE;
+
+ value = (ctrl | set) & ~clr;
+
+ /*
+ * Adopt this level as firmware left it: nothing below it that
+ * firmware did not account for, nothing this driver has routed
+ * through it, the value this walk would write already in place,
+ * and the decoder (and route table) committed.
+ */
+ if (*adopt && !dport->nr_bi && value == ctrl &&
+ cxl_bi_decoder_committed(bi) &&
+ (!port->regs.bi_rt || cxl_bi_rt_committed(port->regs.bi_rt)))
+ goto done;
+ /* firmware did not bring this level up, so nothing above may */
+ *adopt = false;
+
+ if (value != ctrl)
+ writel(value, bi + CXL_BI_DECODER_CTRL_OFFSET);
+
+ /* owed per new device below, not per register change */
+ rc = cxl_bi_commit_dport(dport);
+ if (rc) {
+ if (value != ctrl) {
+ /* the undo is a BI-ID change owing its own commit */
+ writel(ctrl, bi + CXL_BI_DECODER_CTRL_OFFSET);
+ cxl_bi_commit_dport(dport);
+ }
+ return rc;
+ }
+done:
+ dport->nr_bi++;
+
+ return 0;
+}
+
+/*
+ * Dealloc BI-ID changes in the given level of the topology. Called
+ * once per endpoint that enabled this level: on teardown, or to
+ * unwind a path that failed partway up. @hold marks the level for a
+ * device below that still decodes with BI, so that no teardown
+ * reaching zero clears it.
+ */
+static int cxl_bi_ctrl_dport_disable(struct cxl_dport *dport, bool hold)
+{
+ struct cxl_port *port = dport->port;
+ void __iomem *bi;
+ u32 ctrl;
+
+ guard(mutex)(&port->bi_lock);
+
+ bi = dport->regs.bi_decoder;
+ if (!bi)
+ return -EINVAL;
+
+ if (WARN_ON_ONCE(dport->nr_bi == 0))
+ return -EINVAL;
+
+ if (hold)
+ dport->bi_held = true;
+
+ /* others below still need this level */
+ if (--dport->nr_bi > 0)
+ return 0;
+
+ /* found up, or a device below still decodes with BI */
+ if (dport->bi_held)
+ return 0;
+
+ ctrl = readl(bi + CXL_BI_DECODER_CTRL_OFFSET);
+ writel(ctrl & ~(CXL_BI_DECODER_CTRL_BI_FW |
+ CXL_BI_DECODER_CTRL_BI_ENABLE),
+ bi + CXL_BI_DECODER_CTRL_OFFSET);
+
+ return cxl_bi_commit_dport(dport);
+}
+
+static int __cxl_bi_ctrl_endpoint(struct cxl_dev_state *cxlds, bool enable)
+{
+ struct cxl_port *endpoint = cxlds->cxlmd->endpoint;
+ void __iomem *bi = endpoint->regs.bi_decoder;
+ u32 ctrl;
+
+ if (!bi)
+ return -EINVAL;
+
+ ctrl = readl(bi + CXL_BI_DECODER_CTRL_OFFSET);
+
+ if (enable) {
+ if (FIELD_GET(CXL_BI_DECODER_CTRL_BI_ENABLE, ctrl)) {
+ if (!cxlds->bi) /* adopt firmware enabled */
+ dev_dbg(cxlds->dev,
+ "adopting firmware-enabled BI\n");
+ goto done;
+ }
+ } else {
+ if (!FIELD_GET(CXL_BI_DECODER_CTRL_BI_ENABLE, ctrl)) {
+ if (!cxlds->bi)
+ return 0;
+ dev_err(cxlds->dev,
+ "BI already disabled in hardware\n");
+ return -EBUSY;
+ }
+ }
+
+ FIELD_MODIFY(CXL_BI_DECODER_CTRL_BI_ENABLE, &ctrl, enable);
+ writel(ctrl, bi + CXL_BI_DECODER_CTRL_OFFSET);
+
+ dev_dbg(cxlds->dev, "BI requests %s\n", str_enabled_disabled(enable));
+done:
+ cxlds->bi = enable;
+ return 0;
+}
+
+static int cxl_bi_ctrl_endpoint_enable(struct cxl_dev_state *cxlds)
+{
+ return __cxl_bi_ctrl_endpoint(cxlds, true);
+}
+
+static int cxl_bi_ctrl_endpoint_disable(struct cxl_dev_state *cxlds)
+{
+ return __cxl_bi_ctrl_endpoint(cxlds, false);
+}
+
+/*
+ * Release the device's BI, then each dport up the path short of @stop,
+ * following the deallocate sequence in the implementation notes of
+ * CXL 4.0 9.14.2. Setup's unwind and teardown alike: a level the
+ * walk adopted was found up and is held, so the unwind leaves its
+ * register as it found it.
+ *
+ * A decoder that still decodes with its BI bit set needs BI Enable,
+ * the device-wide permission behind it, so the device keeps it. The
+ * references are still released - @nr_bi has to stay true to the
+ * endpoints below - and each level is held instead, so a later
+ * teardown reaching zero leaves it alone. An unwind never holds: the
+ * walk failed before the device's BI was taken up, so no path is left
+ * to keep up for it.
+ */
+static void cxl_bi_release(struct cxl_dev_state *cxlds, struct cxl_dport *stop)
+{
+ struct cxl_port *endpoint = cxlds->cxlmd->endpoint;
+ bool live = cxl_hdm_bi_committed(endpoint);
+ bool hold = live && cxlds->bi;
+ struct cxl_dport *dport_iter;
+ struct cxl_port *port_iter;
+
+ /* only the binding that took up the device's BI releases it */
+ if (cxlds->bi && !live)
+ cxl_bi_ctrl_endpoint_disable(cxlds);
+ cxlds->bi = false;
+
+ /*
+ * Walk the same parent_dport chain that enabled the path. A bus
+ * lookup cannot stand in for it: an ancestor-driven teardown
+ * delists the parent port before this devres action runs.
+ */
+ dport_iter = endpoint->parent_dport;
+ port_iter = dport_iter->port;
+ while (!is_cxl_root(port_iter) && dport_iter != stop) {
+ int rc = cxl_bi_ctrl_dport_disable(dport_iter, hold);
+
+ /* best effort */
+ if (rc)
+ dev_dbg(&port_iter->dev,
+ "BI dport disable failed: %d\n", rc);
+
+ dport_iter = port_iter->parent_dport;
+ port_iter = dport_iter->port;
+ }
+}
+
+/*
+ * devm teardown on endpoint port destruction. Registered before the
+ * decoders, so devres runs it after them: regions are detached and
+ * decoders unregistered by the time BI comes down.
+ */
+static void cxl_bi_dealloc(void *data)
+{
+ struct cxl_port *endpoint = data;
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+
+ cxl_bi_release(cxlmd->cxlds, NULL);
+}
+
+/*
+ * Enable BI on every dport in the path, then on the device itself,
+ * following the allocate sequence in the implementation notes of
+ * CXL 4.0 9.14.2. On failure, unwind only the dports that enabled.
+ */
+static int cxl_bi_enable_path(struct cxl_dev_state *cxlds,
+ struct cxl_port *port, struct cxl_dport *dport)
+{
+ struct cxl_port *endpoint = cxlds->cxlmd->endpoint;
+ struct cxl_dport *dport_iter;
+ struct cxl_port *port_iter;
+ bool adopt;
+ int rc;
+
+ /*
+ * Adoption is a path property, not a per-dport one: only when
+ * firmware enabled the device itself does a dport's committed
+ * state cover this device's BI-ID.
+ */
+ adopt = cxl_bi_decoder_enabled(endpoint);
+
+ port_iter = port;
+ dport_iter = dport;
+ while (!is_cxl_root(port_iter)) {
+ rc = cxl_bi_ctrl_dport_enable(dport_iter, dport_iter == dport,
+ &adopt);
+ if (rc)
+ goto err_rollback;
+
+ dport_iter = port_iter->parent_dport;
+ port_iter = dport_iter->port;
+ }
+
+ rc = cxl_bi_ctrl_endpoint_enable(cxlds);
+ if (rc)
+ goto err_rollback;
+
+ return 0;
+
+err_rollback:
+ /* the failed level, and any above it, took no reference */
+ cxl_bi_release(cxlds, dport_iter);
+ return rc;
+}
+
+/*
+ * An SBR wipes the device's BI Enable; an FLR leaves it alone.
+ * The check is against the hardware, not decoder state: BI is
+ * enabled at probe, so it can be wiped with no decoder ever
+ * committed. A wipe invalidates the software state; BI is never
+ * re-enabled here.
+ */
+void cxl_bi_reset_detected(struct cxl_port *endpoint)
+{
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+
+ /*
+ * The BI Decoder mapping belongs to the endpoint port, and an
+ * unbind of that port alone releases it: hold the port while it
+ * is read, and read nothing once the port driver is gone.
+ */
+ guard(device)(&endpoint->dev);
+ if (!endpoint->dev.driver)
+ return;
+
+ if (!READ_ONCE(cxlds->bi))
+ return;
+
+ if (cxl_bi_decoder_enabled(endpoint))
+ return;
+
+ dev_warn(cxlds->dev, "BI disabled by reset\n");
+ WRITE_ONCE(cxlds->bi, false);
+}
+EXPORT_SYMBOL_NS_GPL(cxl_bi_reset_detected, "CXL");
+
+/*
+ * Verify the device and every component in the path up to the root
+ * are BI capable.
+ */
+void cxl_bi_probe_capable(struct cxl_port *endpoint)
+{
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ struct cxl_dport *dport_iter;
+ struct cxl_port *port_iter;
+
+ cxlds->bi_capable = false;
+
+ if (!dev_is_pci(cxlds->dev))
+ return;
+
+ /* BI is VH-only */
+ if (cxlds->rcd)
+ return;
+
+ if (!cxl_is_bi_capable(to_pci_dev(cxlds->dev),
+ endpoint->regs.bi_decoder))
+ return;
+
+ dport_iter = endpoint->parent_dport;
+ port_iter = dport_iter->port;
+ while (!is_cxl_root(port_iter)) {
+ /* check rp, dsp */
+ if (!cxl_is_bi_capable(to_pci_dev(dport_iter->dport_dev),
+ dport_iter->regs.bi_decoder)) {
+ dev_dbg(cxlds->dev, "BI not supported by topology\n");
+ return;
+ }
+
+ /* check usp */
+ if (dev_is_pci(port_iter->uport_dev) &&
+ pci_pcie_type(to_pci_dev(port_iter->uport_dev)) ==
+ PCI_EXP_TYPE_UPSTREAM) {
+ if (!cxl_is_bi_capable(to_pci_dev(port_iter->uport_dev),
+ NULL)) {
+ dev_dbg(cxlds->dev,
+ "BI not supported by USP\n");
+ return;
+ }
+ if (port_iter->reg_map.component_map.bi_rt.valid &&
+ !port_iter->regs.bi_rt) {
+ dev_dbg(cxlds->dev,
+ "BI RT advertised but unmapped\n");
+ return;
+ }
+ }
+
+ dport_iter = port_iter->parent_dport;
+ port_iter = dport_iter->port;
+ }
+
+ cxlds->bi_capable = true;
+}
+EXPORT_SYMBOL_NS_GPL(cxl_bi_probe_capable, "CXL");
+
+int cxl_bi_setup(struct cxl_port *endpoint)
+{
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dport *dport = endpoint->parent_dport;
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ int rc;
+
+ if (!cxlds->bi_capable)
+ return 0;
+
+ rc = cxl_bi_enable_path(cxlds, dport->port, dport);
+ if (rc)
+ return rc;
+
+ return devm_add_action_or_reset(&endpoint->dev, cxl_bi_dealloc,
+ endpoint);
+}
+EXPORT_SYMBOL_NS_GPL(cxl_bi_setup, "CXL");
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index f3705ea44db7..fffcb9954dde 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -129,9 +129,10 @@ static DEVICE_ATTR_RO(name)
CXL_DECODER_FLAG_ATTR(cap_pmem, CXL_DECODER_F_PMEM);
CXL_DECODER_FLAG_ATTR(cap_ram, CXL_DECODER_F_RAM);
-CXL_DECODER_FLAG_ATTR(cap_type2, CXL_DECODER_F_TYPE2);
-CXL_DECODER_FLAG_ATTR(cap_type3, CXL_DECODER_F_TYPE3);
+CXL_DECODER_FLAG_ATTR(cap_type2, CXL_DECODER_F_DEVMEM);
+CXL_DECODER_FLAG_ATTR(cap_type3, CXL_DECODER_F_HOSTONLY);
CXL_DECODER_FLAG_ATTR(locked, CXL_DECODER_F_LOCK);
+CXL_DECODER_FLAG_ATTR(cap_back_invalidate, CXL_DECODER_F_BI);
static ssize_t target_type_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -243,6 +244,26 @@ static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RW(mode);
+static ssize_t back_invalidate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cxl_endpoint_decoder *cxled = to_cxl_endpoint_decoder(dev);
+ struct cxl_dev_state *cxlds = cxled_to_memdev(cxled)->cxlds;
+ struct cxl_region *cxlr;
+
+ guard(rwsem_read)(&cxl_rwsem.region);
+ /*
+ * An endpoint decoder is HDM-DB when the device has BI enabled
+ * (cxlds->bi) and it is attached to a device-coherent (DEVMEM)
+ * region whose root decoder advertises the CFMWS BI restriction.
+ */
+ cxlr = cxled->cxld.region;
+ return sysfs_emit(buf, "%d\n", READ_ONCE(cxlds->bi) && cxlr &&
+ cxled->cxld.target_type == CXL_DECODER_DEVMEM &&
+ cxl_root_decoder_is_bi(cxlr->cxlrd));
+}
+static DEVICE_ATTR_RO(back_invalidate);
+
static ssize_t dpa_resource_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -339,6 +360,7 @@ static struct attribute *cxl_decoder_root_attrs[] = {
&dev_attr_cap_ram.attr,
&dev_attr_cap_type2.attr,
&dev_attr_cap_type3.attr,
+ &dev_attr_cap_back_invalidate.attr,
&dev_attr_target_list.attr,
&dev_attr_qos_class.attr,
SET_CXL_REGION_ATTR(create_pmem_region)
@@ -349,16 +371,24 @@ static struct attribute *cxl_decoder_root_attrs[] = {
static bool can_create_pmem(struct cxl_root_decoder *cxlrd)
{
- unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_PMEM;
+ unsigned long flags = cxlrd->cxlsd.cxld.flags;
+ unsigned long hdm_h, hdm_db;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ hdm_h = CXL_DECODER_F_HOSTONLY | CXL_DECODER_F_PMEM;
+ hdm_db = CXL_DECODER_F_DEVMEM | CXL_DECODER_F_BI | CXL_DECODER_F_PMEM;
+
+ return (flags & hdm_h) == hdm_h || (flags & hdm_db) == hdm_db;
}
static bool can_create_ram(struct cxl_root_decoder *cxlrd)
{
- unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_RAM;
+ unsigned long flags = cxlrd->cxlsd.cxld.flags;
+ unsigned long hdm_h, hdm_db;
+
+ hdm_h = CXL_DECODER_F_HOSTONLY | CXL_DECODER_F_RAM;
+ hdm_db = CXL_DECODER_F_DEVMEM | CXL_DECODER_F_BI | CXL_DECODER_F_RAM;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ return (flags & hdm_h) == hdm_h || (flags & hdm_db) == hdm_db;
}
static umode_t cxl_root_decoder_visible(struct kobject *kobj, struct attribute *a, int n)
@@ -412,6 +442,7 @@ static const struct attribute_group *cxl_decoder_switch_attribute_groups[] = {
static struct attribute *cxl_decoder_endpoint_attrs[] = {
&dev_attr_target_type.attr,
&dev_attr_mode.attr,
+ &dev_attr_back_invalidate.attr,
&dev_attr_dpa_size.attr,
&dev_attr_dpa_resource.attr,
SET_CXL_REGION_ATTR(region)
@@ -751,6 +782,7 @@ static struct cxl_port *cxl_port_alloc(struct device *uport_dev,
xa_init(&port->dports);
xa_init(&port->endpoints);
xa_init(&port->regions);
+ mutex_init(&port->bi_lock);
port->component_reg_phys = CXL_RESOURCE_NONE;
device_initialize(dev);
@@ -1288,8 +1320,10 @@ __devm_cxl_add_dport(struct cxl_port *port, struct device *dport_dev,
cxl_debugfs_create_dport_dir(dport);
- if (!dport->rch)
+ if (!dport->rch) {
devm_cxl_dport_ras_setup(dport);
+ devm_cxl_dport_bi_setup(dport);
+ }
/* keep the group, and mark the end of devm actions */
cxl_dport_close_dr_group(dport, no_free_ptr(dport_dr_group));
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index a048bd5c06cb..390415eb2287 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -144,6 +144,26 @@ static const struct attribute_group *get_cxl_region_access1_group(void)
return &cxl_region_access1_coordinate_group;
}
+/*
+ * Region access attributes depend on calculated performance data, not
+ * NUMA node onlining. Re-evaluate visibility after region completion.
+ */
+static void cxl_region_update_access_groups(struct cxl_region *cxlr)
+{
+ if (sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_access0_group()))
+ dev_dbg(&cxlr->dev, "Failed to update access0 group\n");
+
+ if (sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_access1_group()))
+ dev_dbg(&cxlr->dev, "Failed to update access1 group\n");
+}
+
+static void cxl_region_perf_data_complete(struct cxl_region *cxlr)
+{
+ cxl_region_perf_data_update(cxlr);
+ cxl_region_shared_upstream_bandwidth_update(cxlr);
+ cxl_region_update_access_groups(cxlr);
+}
+
static ssize_t uuid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -313,8 +333,25 @@ static int commit_decoder(struct cxl_decoder *cxld)
static int cxl_region_decode_commit(struct cxl_region *cxlr)
{
struct cxl_region_params *p = &cxlr->params;
+ bool hdm_db;
int i, rc = 0;
+ hdm_db = cxlr->type == CXL_DECODER_DEVMEM &&
+ cxl_root_decoder_is_bi(cxlr->cxlrd);
+
+ /* a reset since attach may have invalidated cxlds->bi */
+ for (i = 0; i < p->nr_targets; i++) {
+ struct cxl_endpoint_decoder *cxled = p->targets[i];
+ struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
+
+ if (hdm_db && !READ_ONCE(cxlmd->cxlds->bi)) {
+ dev_err(&cxlr->dev, "%s:%s BI not enabled on device\n",
+ dev_name(&cxlmd->dev),
+ dev_name(&cxled->cxld.dev));
+ return -ENXIO;
+ }
+ }
+
for (i = 0; i < p->nr_targets; i++) {
struct cxl_endpoint_decoder *cxled = p->targets[i];
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
@@ -346,6 +383,22 @@ static int cxl_region_decode_commit(struct cxl_region *cxlr)
cxled->cxld.reset(&cxled->cxld);
goto err;
}
+
+ /*
+ * A reset after the check above wipes BI Enable, but
+ * cxlds->bi is only refreshed from .reset_done, which may
+ * not have run. Ask the hardware.
+ */
+ if (hdm_db && !cxl_bi_decoder_enabled(cxled_to_port(cxled))) {
+ dev_err(&cxlr->dev,
+ "%s:%s BI disabled by reset during commit\n",
+ dev_name(&cxlmd->dev),
+ dev_name(&cxled->cxld.dev));
+ WRITE_ONCE(cxlmd->cxlds->bi, false);
+ rc = -ENXIO;
+ i++; /* this target committed, so undo it too */
+ goto err;
+ }
}
return 0;
@@ -493,11 +546,9 @@ static int set_interleave_ways(struct cxl_region *cxlr, int val)
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
int save, rc;
- u8 iw;
- rc = ways_to_eiw(val, &iw);
- if (rc)
- return rc;
+ if (!ways_valid(val))
+ return -EINVAL;
/*
* Even for x3, x6, and x12 interleaves the region interleave must be a
@@ -566,12 +617,9 @@ static int set_interleave_granularity(struct cxl_region *cxlr, int val)
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
- int rc;
- u16 ig;
- rc = granularity_to_eig(val, &ig);
- if (rc)
- return rc;
+ if (!granularity_valid(val))
+ return -EINVAL;
/*
* When the host-bridge is interleaved, disallow region granularity !=
@@ -1132,16 +1180,11 @@ static int cxl_rr_assign_decoder(struct cxl_port *port, struct cxl_region *cxlr,
}
/*
- * Endpoints should already match the region type, but backstop that
- * assumption with an assertion. Switch-decoders change mapping-type
- * based on what is mapped when they are assigned to a region.
+ * Endpoint decoders inherit their type from cxlr->type; broken
+ * pairings were already rejected by the coherency checks in
+ * cxl_region_attach(). Switch-decoders change mapping-type based
+ * on what is mapped when they are assigned to a region.
*/
- dev_WARN_ONCE(&cxlr->dev,
- port == cxled_to_port(cxled) &&
- cxld->target_type != cxlr->type,
- "%s:%s mismatch decoder type %d -> %d\n",
- dev_name(&cxled_to_memdev(cxled)->dev),
- dev_name(&cxld->dev), cxld->target_type, cxlr->type);
cxld->target_type = cxlr->type;
cxl_rr->decoder = cxld;
return 0;
@@ -1437,66 +1480,48 @@ static int check_interleave_cap(struct cxl_decoder *cxld, int iw, int ig)
return 0;
}
-static int cxl_port_setup_targets(struct cxl_port *port,
- struct cxl_region *cxlr,
- struct cxl_endpoint_decoder *cxled)
+/*
+ * The "distance" between peer downstream ports represents which
+ * endpoint positions in the region interleave a given port can
+ * host.
+ *
+ * For example, at the root of a hierarchy the distance is
+ * always 1 as every index targets a different host-bridge. At
+ * each subsequent switch level those ports map every Nth region
+ * position where N is the width of the switch == distance.
+ */
+static int cxl_port_peer_distance(struct cxl_port *port, struct cxl_region *cxlr)
{
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
- int parent_iw, parent_ig, ig, iw, rc, pos = cxled->pos;
- struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
- struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
- struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
- struct cxl_ep *ep = cxl_ep_load(port, cxlmd);
- struct cxl_region_params *p = &cxlr->params;
- struct cxl_decoder *cxld = cxl_rr->decoder;
- struct cxl_switch_decoder *cxlsd;
+ struct cxl_region_ref *cxl_rr_iter;
struct cxl_port *iter = port;
- u16 eig, peig;
- u8 eiw, peiw;
+ int distance = 1;
- /*
- * While root level decoders support x3, x6, x12, switch level
- * decoders only support powers of 2 up to x16.
- */
- if (!is_power_of_2(cxl_rr->nr_targets)) {
- dev_dbg(&cxlr->dev, "%s:%s: invalid target count %d\n",
- dev_name(port->uport_dev), dev_name(&port->dev),
- cxl_rr->nr_targets);
- return -EINVAL;
- }
+ do {
+ cxl_rr_iter = cxl_rr_load(iter, cxlr);
+ distance *= cxl_rr_iter->nr_targets;
+ iter = to_cxl_port(iter->dev.parent);
+ } while (!is_cxl_root(iter));
+ distance *= cxlrd->cxlsd.cxld.interleave_ways;
- cxlsd = to_cxl_switch_decoder(&cxld->dev);
- if (cxl_rr->nr_targets_set) {
- int i, distance = 1;
- struct cxl_region_ref *cxl_rr_iter;
+ return distance;
+}
- /*
- * The "distance" between peer downstream ports represents which
- * endpoint positions in the region interleave a given port can
- * host.
- *
- * For example, at the root of a hierarchy the distance is
- * always 1 as every index targets a different host-bridge. At
- * each subsequent switch level those ports map every Nth region
- * position where N is the width of the switch == distance.
- */
- do {
- cxl_rr_iter = cxl_rr_load(iter, cxlr);
- distance *= cxl_rr_iter->nr_targets;
- iter = to_cxl_port(iter->dev.parent);
- } while (!is_cxl_root(iter));
- distance *= cxlrd->cxlsd.cxld.interleave_ways;
-
- for (i = 0; i < cxl_rr->nr_targets_set; i++)
- if (ep->dport == cxlsd->target[i]) {
- rc = check_last_peer(cxled, ep, cxl_rr,
- distance);
- if (rc)
- return rc;
- goto out_target_set;
- }
- goto add_target;
- }
+/*
+ * Get the parent interleave ways and granularity.
+ *
+ * For address bit routing, use power-of-2 math for switch ports.
+ * Root interleave ways of 3, 6, or 12 are normalized to 1, 2, or 4
+ * in @out_iw.
+ */
+static int cxl_port_parent_interleave(struct cxl_port *port,
+ struct cxl_region *cxlr,
+ int *out_iw, int *out_ig)
+{
+ struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
+ struct cxl_region_params *p = &cxlr->params;
+ int parent_iw, parent_ig;
if (is_cxl_root(parent_port)) {
/*
@@ -1508,10 +1533,6 @@ static int cxl_port_setup_targets(struct cxl_port *port,
*/
parent_ig = p->interleave_granularity;
parent_iw = cxlrd->cxlsd.cxld.interleave_ways;
- /*
- * For purposes of address bit routing, use power-of-2 math for
- * switch ports.
- */
if (!is_power_of_2(parent_iw))
parent_iw /= 3;
} else {
@@ -1524,50 +1545,60 @@ static int cxl_port_setup_targets(struct cxl_port *port,
parent_iw = parent_cxld->interleave_ways;
}
- rc = granularity_to_eig(parent_ig, &peig);
- if (rc) {
+ if (!granularity_valid(parent_ig)) {
dev_dbg(&cxlr->dev, "%s:%s: invalid parent granularity: %d\n",
dev_name(parent_port->uport_dev),
dev_name(&parent_port->dev), parent_ig);
- return rc;
+ return -EINVAL;
}
- rc = ways_to_eiw(parent_iw, &peiw);
- if (rc) {
+ if (!ways_valid(parent_iw)) {
dev_dbg(&cxlr->dev, "%s:%s: invalid parent interleave: %d\n",
dev_name(parent_port->uport_dev),
dev_name(&parent_port->dev), parent_iw);
- return rc;
+ return -EINVAL;
}
+ *out_iw = parent_iw;
+ *out_ig = parent_ig;
+
+ return 0;
+}
+
+static int cxl_port_calc_interleave(struct cxl_port *port, struct cxl_region *cxlr,
+ struct cxl_region_ref *cxl_rr, int *out_iw,
+ int *out_ig)
+{
+ struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
+ struct cxl_decoder *cxld = cxl_rr->decoder;
+ int parent_iw, parent_ig, iw, ig, rc;
+ struct cxl_switch_decoder *cxlsd;
+
+ rc = cxl_port_parent_interleave(port, cxlr, &parent_iw, &parent_ig);
+ if (rc)
+ return rc;
+
iw = cxl_rr->nr_targets;
- rc = ways_to_eiw(iw, &eiw);
- if (rc) {
+ if (!ways_valid(iw)) {
dev_dbg(&cxlr->dev, "%s:%s: invalid port interleave: %d\n",
dev_name(port->uport_dev), dev_name(&port->dev), iw);
- return rc;
+ return -EINVAL;
}
/*
* Interleave granularity is a multiple of @parent_port granularity.
* Multiplier is the parent port interleave ways.
*/
- rc = granularity_to_eig(parent_ig * parent_iw, &eig);
- if (rc) {
+ ig = parent_ig * parent_iw;
+
+ if (!granularity_valid(ig)) {
dev_dbg(&cxlr->dev,
"%s: invalid granularity calculation (%d * %d)\n",
dev_name(&parent_port->dev), parent_ig, parent_iw);
- return rc;
- }
-
- rc = eig_to_granularity(eig, &ig);
- if (rc) {
- dev_dbg(&cxlr->dev, "%s:%s: invalid interleave: %d\n",
- dev_name(port->uport_dev), dev_name(&port->dev),
- 256 << eig);
- return rc;
+ return -EINVAL;
}
+ cxlsd = to_cxl_switch_decoder(&cxld->dev);
if (iw > 8 || iw > cxlsd->nr_targets) {
dev_dbg(&cxlr->dev,
"%s:%s:%s: ways: %d overflows targets: %d\n",
@@ -1576,6 +1607,18 @@ static int cxl_port_setup_targets(struct cxl_port *port,
return -ENXIO;
}
+ *out_iw = iw;
+ *out_ig = ig;
+
+ return 0;
+}
+
+static int cxl_port_setup_decoder(struct cxl_port *port, struct cxl_region *cxlr,
+ struct cxl_decoder *cxld, int iw, int ig)
+{
+ struct cxl_region_params *p = &cxlr->params;
+ int rc;
+
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
if (cxld->interleave_ways != iw ||
(iw > 1 && cxld->interleave_granularity != ig) ||
@@ -1613,7 +1656,20 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
dev_dbg(&cxlr->dev, "%s:%s iw: %d ig: %d\n", dev_name(port->uport_dev),
dev_name(&port->dev), iw, ig);
-add_target:
+
+ return 0;
+}
+
+static int cxl_switch_port_add_target(struct cxl_switch_decoder *cxlsd,
+ struct cxl_region *cxlr,
+ struct cxl_region_ref *cxl_rr,
+ struct cxl_endpoint_decoder *cxled,
+ struct cxl_ep *ep)
+{
+ struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
+ struct cxl_port *port = cxl_rr->port;
+ int pos = cxled->pos;
+
if (cxl_rr->nr_targets_set == cxl_rr->nr_targets) {
dev_dbg(&cxlr->dev,
"%s:%s: targets full trying to add %s:%s at %d\n",
@@ -1635,7 +1691,54 @@ add_target:
cxlsd->cxld.target_map[cxl_rr->nr_targets_set] = ep->dport->port_id;
}
cxl_rr->nr_targets_set++;
-out_target_set:
+
+ return 0;
+}
+
+static int cxl_switch_port_setup_targets(struct cxl_switch_decoder *cxlsd,
+ struct cxl_region *cxlr,
+ struct cxl_endpoint_decoder *cxled)
+{
+ struct cxl_port *port = to_cxl_port(cxlsd->cxld.dev.parent);
+ int iw, ig, rc, pos = cxled->pos;
+ struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
+ struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
+ struct cxl_ep *ep = cxl_ep_load(port, cxlmd);
+ struct cxl_decoder *cxld = &cxlsd->cxld;
+
+ /*
+ * While root level decoders support x3, x6, x12, switch level
+ * decoders only support powers of 2 up to x16.
+ */
+ if (!is_power_of_2(cxl_rr->nr_targets)) {
+ dev_dbg(&cxlr->dev, "%s:%s: invalid target count %d\n",
+ dev_name(port->uport_dev), dev_name(&port->dev),
+ cxl_rr->nr_targets);
+ return -EINVAL;
+ }
+
+ if (cxl_rr->nr_targets_set) {
+ for (int i = 0; i < cxl_rr->nr_targets_set; i++) {
+ if (ep->dport != cxlsd->target[i])
+ continue;
+
+ return check_last_peer(cxled, ep, cxl_rr,
+ cxl_port_peer_distance(port, cxlr));
+ }
+ } else {
+ rc = cxl_port_calc_interleave(port, cxlr, cxl_rr, &iw, &ig);
+ if (rc)
+ return rc;
+
+ rc = cxl_port_setup_decoder(port, cxlr, cxld, iw, ig);
+ if (rc)
+ return rc;
+ }
+
+ rc = cxl_switch_port_add_target(cxlsd, cxlr, cxl_rr, cxled, ep);
+ if (rc)
+ return rc;
+
dev_dbg(&cxlr->dev, "%s:%s target[%d] = %s for %s:%s @ %d\n",
dev_name(port->uport_dev), dev_name(&port->dev),
cxl_rr->nr_targets_set - 1, dev_name(ep->dport->dport_dev),
@@ -1733,7 +1836,11 @@ static int cxl_region_setup_targets(struct cxl_region *cxlr)
*/
for (ep = cxl_ep_load(iter, cxlmd); iter;
iter = ep->next, ep = cxl_ep_load(iter, cxlmd)) {
- rc = cxl_port_setup_targets(iter, cxlr, cxled);
+ struct cxl_region_ref *cxl_rr = cxl_rr_load(iter, cxlr);
+ struct cxl_switch_decoder *cxlsd;
+
+ cxlsd = to_cxl_switch_decoder(&cxl_rr->decoder->dev);
+ rc = cxl_switch_port_setup_targets(cxlsd, cxlr, cxled);
if (rc) {
cxl_region_teardown_targets(cxlr);
return rc;
@@ -1805,6 +1912,7 @@ static int cxl_region_attach_position(struct cxl_region *cxlr,
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd;
+ enum cxl_decoder_type type = cxled->cxld.target_type;
struct cxl_decoder *cxld = &cxlsd->cxld;
int iw = cxld->interleave_ways;
struct cxl_port *iter;
@@ -1830,9 +1938,18 @@ err:
for (iter = cxled_to_port(cxled); !is_cxl_root(iter);
iter = to_cxl_port(iter->dev.parent))
cxl_port_detach_region(iter, cxlr, cxled);
+ /* undo cxl_rr_assign_decoder() type inheritance */
+ cxled->cxld.target_type = type;
return rc;
}
+static const char *cxl_coherency_name(enum cxl_decoder_type type, bool bi)
+{
+ if (type == CXL_DECODER_HOSTONLYMEM)
+ return "HDM-H";
+ return bi ? "HDM-DB" : "HDM-D";
+}
+
static int cxl_region_attach_auto(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled, int pos)
{
@@ -2049,6 +2166,68 @@ static int cxl_region_sort_targets(struct cxl_region *cxlr)
return rc;
}
+/*
+ * Verify the device and HDM are capable of the region's flavor. The
+ * endpoint decoder's target_type is then inherited from cxlr->type
+ * later in cxl_rr_assign_decoder().
+ */
+static int cxl_region_check_coherency(struct cxl_region *cxlr,
+ struct cxl_endpoint_decoder *cxled)
+{
+ struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ bool root_bi = cxl_root_decoder_is_bi(cxlr->cxlrd);
+ struct cxl_hdm *cxlhdm;
+
+ if (cxlr->type == CXL_DECODER_DEVMEM && root_bi &&
+ !READ_ONCE(cxlds->bi)) {
+ dev_dbg(&cxlr->dev, "%s:%s BI not enabled on device\n",
+ dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev));
+ return -ENXIO;
+ }
+
+ if (cxled->state != CXL_DECODER_STATE_AUTO &&
+ cxlr->type == CXL_DECODER_HOSTONLYMEM &&
+ cxlds->type == CXL_DEVTYPE_DEVMEM) {
+ dev_dbg(&cxlr->dev, "%s:%s HDM-H requires a Type 3 device\n",
+ dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev));
+ return -ENXIO;
+ }
+
+ cxlhdm = dev_get_drvdata(&cxled_to_port(cxled)->dev);
+ if (!cxlhdm)
+ return -ENXIO;
+ if (cxlr->type == CXL_DECODER_HOSTONLYMEM &&
+ cxlhdm->supported_coherency == CXL_HDM_DECODER_COHERENCY_DEV) {
+ dev_dbg(&cxlr->dev, "%s:%s HDM is device-coherent only\n",
+ dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev));
+ return -ENXIO;
+ }
+ if (cxlr->type == CXL_DECODER_DEVMEM &&
+ cxlhdm->supported_coherency == CXL_HDM_DECODER_COHERENCY_HOST) {
+ dev_dbg(&cxlr->dev, "%s:%s HDM is host-only coherent\n",
+ dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev));
+ return -ENXIO;
+ }
+
+ /* a committed decoder cannot inherit the region's flavor */
+ if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
+ bool bi = cxled->cxld.flags & CXL_DECODER_F_BI;
+
+ if (cxled->cxld.target_type != cxlr->type || bi != root_bi) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s coherency model mismatch: %s vs %s\n",
+ dev_name(&cxlmd->dev),
+ dev_name(&cxled->cxld.dev),
+ cxl_coherency_name(cxled->cxld.target_type, bi),
+ cxl_coherency_name(cxlr->type, root_bi));
+ return -ENXIO;
+ }
+ }
+
+ return 0;
+}
+
static int cxl_region_attach(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled, int pos)
{
@@ -2107,12 +2286,9 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
- if (cxled->cxld.target_type != cxlr->type) {
- dev_dbg(&cxlr->dev, "%s:%s type mismatch: %d vs %d\n",
- dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
- cxled->cxld.target_type, cxlr->type);
- return -ENXIO;
- }
+ rc = cxl_region_check_coherency(cxlr, cxled);
+ if (rc)
+ return rc;
if (cxled_empty(cxled)) {
dev_dbg(&cxlr->dev, "%s:%s: missing DPA allocation.\n",
@@ -2130,8 +2306,6 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -EINVAL;
}
- cxl_region_perf_data_calculate(cxlr, cxled);
-
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
int i;
@@ -2171,7 +2345,7 @@ static int cxl_region_attach(struct cxl_region *cxlr,
* then the region is already committed.
*/
p->state = CXL_CONFIG_COMMIT;
- cxl_region_shared_upstream_bandwidth_update(cxlr);
+ cxl_region_perf_data_complete(cxlr);
return 0;
}
@@ -2193,7 +2367,7 @@ static int cxl_region_attach(struct cxl_region *cxlr,
if (rc)
return rc;
p->state = CXL_CONFIG_ACTIVE;
- cxl_region_shared_upstream_bandwidth_update(cxlr);
+ cxl_region_perf_data_complete(cxlr);
}
cxled->cxld.interleave_ways = p->interleave_ways;
@@ -2327,6 +2501,12 @@ __cxl_decoder_detach(struct cxl_region *cxlr,
.start = 0,
.end = -1,
};
+ /*
+ * Undo cxl_rr_assign_decoder() type inheritance, unless the
+ * decoder is still committed and its type is what it decodes.
+ */
+ if (!(cxled->cxld.flags & CXL_DECODER_F_ENABLE))
+ cxled->cxld.target_type = cxled_default_type(cxled);
get_device(&cxlr->dev);
return cxlr;
@@ -2629,7 +2809,6 @@ static struct cxl_region *cxl_region_alloc(struct cxl_root_decoder *cxlrd, int i
static bool cxl_region_update_coordinates(struct cxl_region *cxlr, int nid)
{
int cset = 0;
- int rc;
for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) {
if (cxlr->coord[i].read_bandwidth) {
@@ -2650,18 +2829,7 @@ static bool cxl_region_update_coordinates(struct cxl_region *cxlr, int nid)
}
}
- if (!cset)
- return false;
-
- rc = sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_access0_group());
- if (rc)
- dev_dbg(&cxlr->dev, "Failed to update access0 group\n");
-
- rc = sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_access1_group());
- if (rc)
- dev_dbg(&cxlr->dev, "Failed to update access1 group\n");
-
- return true;
+ return cset > 0;
}
static int cxl_region_perf_attrs_callback(struct notifier_block *nb,
@@ -2776,8 +2944,10 @@ static struct cxl_region *devm_cxl_add_region(struct cxl_root_decoder *cxlrd,
return ERR_PTR(rc);
}
- dev_dbg(port->uport_dev, "%s: created %s\n",
- dev_name(&cxlrd->cxlsd.cxld.dev), dev_name(dev));
+ dev_dbg(port->uport_dev, "%s: created %s %s\n",
+ dev_name(&cxlrd->cxlsd.cxld.dev),
+ cxl_coherency_name(cxlr->type, cxl_root_decoder_is_bi(cxlrd)),
+ dev_name(dev));
return cxlr;
err:
put_device(dev);
@@ -2835,6 +3005,7 @@ static ssize_t create_region_store(struct device *dev, const char *buf,
size_t len, enum cxl_partition_mode mode)
{
struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev);
+ enum cxl_decoder_type target_type;
struct cxl_region *cxlr;
int rc, id;
@@ -2846,7 +3017,15 @@ static ssize_t create_region_store(struct device *dev, const char *buf,
if ((rc = ACQUIRE_ERR(mutex_intr, &regions_lock)))
return rc;
- cxlr = __create_region(cxlrd, mode, id, CXL_DECODER_HOSTONLYMEM);
+ /*
+ * The CFMWS dictates endpoint coherency: a BI-restricted Window
+ * produces an HDM-DB region; otherwise HDM-H. HDM-D is not an
+ * option here, type2 regions are created by their driver.
+ */
+ target_type = cxl_root_decoder_is_bi(cxlrd) ?
+ CXL_DECODER_DEVMEM : CXL_DECODER_HOSTONLYMEM;
+
+ cxlr = __create_region(cxlrd, mode, id, target_type);
if (IS_ERR(cxlr))
return PTR_ERR(cxlr);
@@ -3131,13 +3310,13 @@ static bool cxl_is_hpa_in_chunk(u64 hpa, struct cxl_region *cxlr, int pos)
*/
int cxl_validate_translation_params(u8 eiw, u16 eig, int pos)
{
- int ways, gran;
+ int ways;
if (eiw_to_ways(eiw, &ways)) {
pr_debug("%s: invalid eiw=%u\n", __func__, eiw);
return -EINVAL;
}
- if (eig_to_granularity(eig, &gran)) {
+ if (!eig_valid(eig)) {
pr_debug("%s: invalid eig=%u\n", __func__, eig);
return -EINVAL;
}
@@ -3792,12 +3971,30 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd,
struct cxl_endpoint_decoder *cxled = ctx->cxled;
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ bool bi = cxled->cxld.flags & CXL_DECODER_F_BI;
int rc, part = READ_ONCE(cxled->part);
struct cxl_region *cxlr;
+ unsigned long need;
if (part < 0)
return ERR_PTR(-EBUSY);
+ /*
+ * A committed decoder defines the region built from it, so no
+ * attach check can find its coherency model wrong. Only the
+ * window's restrictions can, if they do not permit its BI bit or
+ * range type.
+ */
+ need = cxled->cxld.target_type == CXL_DECODER_DEVMEM ?
+ CXL_DECODER_F_DEVMEM : CXL_DECODER_F_HOSTONLY;
+ if (bi != cxl_root_decoder_is_bi(cxlrd) ||
+ !(cxlrd->cxlsd.cxld.flags & need)) {
+ dev_err(&cxlrd->cxlsd.cxld.dev,
+ "%s:%s coherency model not permitted by the window\n",
+ dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev));
+ return ERR_PTR(-ENXIO);
+ }
+
do {
cxlr = __create_region(cxlrd, cxlds->part[part].mode,
atomic_read(&cxlrd->region_id),
diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c
index 41416fa2ce4a..76bcc316a85c 100644
--- a/drivers/cxl/core/regs.c
+++ b/drivers/cxl/core/regs.c
@@ -93,6 +93,18 @@ void cxl_probe_component_regs(struct device *dev, void __iomem *base,
length = CXL_RAS_CAPABILITY_LENGTH;
rmap = &map->ras;
break;
+ case CXL_CM_CAP_CAP_ID_BI_RT:
+ dev_dbg(dev, "found BI RT capability (0x%x)\n",
+ offset);
+ length = CXL_BI_RT_CAPABILITY_LENGTH;
+ rmap = &map->bi_rt;
+ break;
+ case CXL_CM_CAP_CAP_ID_BI_DECODER:
+ dev_dbg(dev, "found BI Decoder capability (0x%x)\n",
+ offset);
+ length = CXL_BI_DECODER_CAPABILITY_LENGTH;
+ rmap = &map->bi_decoder;
+ break;
default:
dev_dbg(dev, "Unknown CM cap ID: %d (0x%x)\n", cap_id,
offset);
@@ -212,6 +224,8 @@ int cxl_map_component_regs(const struct cxl_register_map *map,
} mapinfo[] = {
{ &map->component_map.hdm_decoder, &regs->hdm_decoder },
{ &map->component_map.ras, &regs->ras },
+ { &map->component_map.bi_rt, &regs->bi_rt },
+ { &map->component_map.bi_decoder, &regs->bi_decoder },
};
int i;
diff --git a/drivers/cxl/core/tests/Makefile b/drivers/cxl/core/tests/Makefile
new file mode 100644
index 000000000000..08d97303d0d1
--- /dev/null
+++ b/drivers/cxl/core/tests/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+ccflags-y += -I$(srctree)/drivers/cxl
+
+obj-$(CONFIG_CXL_KUNIT_TEST) += cxl_core_mbox_kunit.o
+cxl_core_mbox_kunit-y := mbox_kunit.o
diff --git a/drivers/cxl/core/tests/mbox_kunit.c b/drivers/cxl/core/tests/mbox_kunit.c
new file mode 100644
index 000000000000..9c0f3355ff16
--- /dev/null
+++ b/drivers/cxl/core/tests/mbox_kunit.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for cxl_internal_send_cmd() using a stub mailbox
+ * transport in place of hardware.
+ */
+#include <kunit/test.h>
+#include <cxl/mailbox.h>
+#include "cxlmem.h"
+
+#define TEST_PAYLOAD_SIZE SZ_4K
+
+/*
+ * Stub transport state: what the "device" returns, and a record of
+ * what the core passed down.
+ */
+struct mock_mbox {
+ struct cxl_mailbox cxl_mbox;
+ int send_rc;
+ u16 return_code;
+ size_t size_out;
+ int calls;
+ struct cxl_mbox_cmd seen;
+};
+
+static int mock_mbox_send(struct cxl_mailbox *cxl_mbox,
+ struct cxl_mbox_cmd *cmd)
+{
+ struct mock_mbox *mock;
+
+ mock = container_of(cxl_mbox, struct mock_mbox, cxl_mbox);
+ mock->calls++;
+ mock->seen = *cmd;
+ if (mock->send_rc)
+ return mock->send_rc;
+
+ cmd->return_code = mock->return_code;
+ cmd->size_out = mock->size_out;
+ return 0;
+}
+
+static int cxl_mbox_test_init(struct kunit *test)
+{
+ struct mock_mbox *mock;
+
+ mock = kunit_kzalloc(test, sizeof(*mock), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, mock);
+
+ mock->cxl_mbox.payload_size = TEST_PAYLOAD_SIZE;
+ mock->cxl_mbox.mbox_send = mock_mbox_send;
+ mock->return_code = CXL_MBOX_CMD_RC_SUCCESS;
+ test->priv = mock;
+
+ return 0;
+}
+
+static void send_success(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ u8 in[16], out[64];
+ struct cxl_mbox_cmd cmd = {
+ .opcode = CXL_MBOX_OP_IDENTIFY,
+ .payload_in = in,
+ .size_in = sizeof(in),
+ .payload_out = out,
+ .size_out = sizeof(out),
+ };
+
+ mock->size_out = sizeof(out);
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd), 0);
+ KUNIT_EXPECT_EQ(test, mock->calls, 1);
+
+ /* The command reaches the transport as the caller built it */
+ KUNIT_EXPECT_EQ(test, mock->seen.opcode, CXL_MBOX_OP_IDENTIFY);
+ KUNIT_EXPECT_PTR_EQ(test, mock->seen.payload_in, (void *)in);
+ KUNIT_EXPECT_EQ(test, mock->seen.size_in, sizeof(in));
+ KUNIT_EXPECT_PTR_EQ(test, mock->seen.payload_out, (void *)out);
+ KUNIT_EXPECT_EQ(test, mock->seen.size_out, sizeof(out));
+}
+
+static void send_no_output(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd = {};
+
+ /* No output was requested, so the returned size is not checked */
+ mock->size_out = 0;
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd), 0);
+}
+
+static void send_size_in_too_big(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd = {
+ .size_in = TEST_PAYLOAD_SIZE + 1,
+ };
+
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd),
+ -E2BIG);
+ KUNIT_EXPECT_EQ(test, mock->calls, 0);
+}
+
+static void send_size_out_too_big(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd = {
+ .size_out = TEST_PAYLOAD_SIZE + 1,
+ };
+
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd),
+ -E2BIG);
+ KUNIT_EXPECT_EQ(test, mock->calls, 0);
+}
+
+static void send_full_payload(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd = {
+ .size_in = TEST_PAYLOAD_SIZE,
+ .size_out = TEST_PAYLOAD_SIZE,
+ };
+
+ mock->size_out = TEST_PAYLOAD_SIZE;
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd), 0);
+}
+
+static void send_transport_error(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd = {};
+
+ mock->send_rc = -ETIMEDOUT;
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd),
+ -ETIMEDOUT);
+}
+
+static void send_device_error(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+
+ /* Every defined error code fails the command */
+ for (u16 code = CXL_MBOX_CMD_RC_INPUT;
+ code < ARRAY_SIZE(cxl_mbox_cmd_rctable); code++) {
+ struct cxl_mbox_cmd cmd = {};
+ int expect, rc;
+
+ switch (code) {
+ case CXL_MBOX_CMD_RC_PADDR:
+ expect = -EFAULT;
+ break;
+ case CXL_MBOX_CMD_RC_POISONLMT:
+ expect = -EBUSY;
+ break;
+ default:
+ expect = -ENXIO;
+ break;
+ }
+
+ mock->return_code = code;
+ rc = cxl_internal_send_cmd(&mock->cxl_mbox, &cmd);
+ KUNIT_EXPECT_EQ_MSG(test, rc, expect, "return_code=%#x", code);
+ }
+}
+
+static void send_unknown_return_code(struct kunit *test)
+{
+ static const u16 codes[] = { ARRAY_SIZE(cxl_mbox_cmd_rctable), 0x100,
+ 0xffff };
+ struct mock_mbox *mock = test->priv;
+
+ /* Codes the driver does not know are still device errors */
+ for (int i = 0; i < ARRAY_SIZE(codes); i++) {
+ struct cxl_mbox_cmd cmd = {};
+ int rc;
+
+ mock->return_code = codes[i];
+ rc = cxl_internal_send_cmd(&mock->cxl_mbox, &cmd);
+ KUNIT_EXPECT_EQ_MSG(test, rc, -ENXIO, "return_code=%#x",
+ codes[i]);
+ KUNIT_EXPECT_STREQ_MSG(test, cxl_mbox_cmd_rc2str(&cmd),
+ "unknown return code", "return_code=%#x",
+ codes[i]);
+ }
+}
+
+static void send_background_started(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd = {};
+
+ /* A background command that started is not an error */
+ mock->return_code = CXL_MBOX_CMD_RC_BACKGROUND;
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd), 0);
+}
+
+static void send_short_fixed_output(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd = {
+ .size_out = 64,
+ };
+
+ /* Without min_out, the full requested size is required */
+ mock->size_out = 63;
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd),
+ -EIO);
+}
+
+static void send_variable_output(struct kunit *test)
+{
+ struct mock_mbox *mock = test->priv;
+ struct cxl_mbox_cmd cmd;
+
+ /* Output at or above min_out is accepted */
+ cmd = (struct cxl_mbox_cmd){ .size_out = 64, .min_out = 8 };
+ mock->size_out = 8;
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd), 0);
+
+ /* Output below min_out is rejected */
+ cmd = (struct cxl_mbox_cmd){ .size_out = 64, .min_out = 8 };
+ mock->size_out = 7;
+ KUNIT_EXPECT_EQ(test, cxl_internal_send_cmd(&mock->cxl_mbox, &cmd),
+ -EIO);
+}
+
+static struct kunit_case cxl_core_mbox_cases[] = {
+ KUNIT_CASE(send_success),
+ KUNIT_CASE(send_no_output),
+ KUNIT_CASE(send_size_in_too_big),
+ KUNIT_CASE(send_size_out_too_big),
+ KUNIT_CASE(send_full_payload),
+ KUNIT_CASE(send_transport_error),
+ KUNIT_CASE(send_device_error),
+ KUNIT_CASE(send_unknown_return_code),
+ KUNIT_CASE(send_background_started),
+ KUNIT_CASE(send_short_fixed_output),
+ KUNIT_CASE(send_variable_output),
+ {}
+};
+
+static struct kunit_suite cxl_core_mbox_suite = {
+ .name = "cxl_core_mbox",
+ .init = cxl_mbox_test_init,
+ .test_cases = cxl_core_mbox_cases,
+};
+
+kunit_test_suite(cxl_core_mbox_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the CXL mailbox command core");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CXL");
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index baa6df2d362a..fb62480acbe6 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -39,11 +39,21 @@ extern const struct nvdimm_security_ops *cxl_security_ops;
#define CXL_CM_CAP_HDR_ARRAY_SIZE_MASK GENMASK(31, 24)
#define CXL_CM_CAP_PTR_MASK GENMASK(31, 20)
+/* CXL 4.0 8.2.4 Table 8-74 */
#define CXL_CM_CAP_CAP_ID_RAS 0x2
#define CXL_CM_CAP_CAP_ID_HDM 0x5
#define CXL_CM_CAP_CAP_HDM_VERSION 1
+#define CXL_CM_CAP_CAP_ID_BI_RT 0xB
+#define CXL_CM_CAP_CAP_ID_BI_DECODER 0xC
-/* HDM decoders CXL 2.0 8.2.5.12 CXL HDM Decoder Capability Structure */
+/*
+ * CXL 4.0 8.2.4.26 / 8.2.4.27 BI Capability Structure lengths (Tables
+ * 8-151 and 8-155), unrelated to the capability IDs above.
+ */
+#define CXL_BI_RT_CAPABILITY_LENGTH 0xC
+#define CXL_BI_DECODER_CAPABILITY_LENGTH 0xC
+
+/* HDM decoders CXL 4.0 8.2.4.20 CXL HDM Decoder Capability Structure */
#define CXL_HDM_DECODER_CAP_OFFSET 0x0
#define CXL_HDM_DECODER_COUNT_MASK GENMASK(3, 0)
#define CXL_HDM_DECODER_TARGET_COUNT_MASK GENMASK(7, 4)
@@ -51,6 +61,11 @@ extern const struct nvdimm_security_ops *cxl_security_ops;
#define CXL_HDM_DECODER_INTERLEAVE_14_12 BIT(9)
#define CXL_HDM_DECODER_INTERLEAVE_3_6_12_WAY BIT(11)
#define CXL_HDM_DECODER_INTERLEAVE_16_WAY BIT(12)
+#define CXL_HDM_DECODER_SUPPORTED_COHERENCY_MASK GENMASK(22, 21)
+#define CXL_HDM_DECODER_COHERENCY_UNKNOWN 0x0
+#define CXL_HDM_DECODER_COHERENCY_DEV 0x1
+#define CXL_HDM_DECODER_COHERENCY_HOST 0x2
+#define CXL_HDM_DECODER_COHERENCY_BOTH 0x3
#define CXL_HDM_DECODER_CTRL_OFFSET 0x4
#define CXL_HDM_DECODER_ENABLE BIT(1)
#define CXL_HDM_DECODER0_BASE_LOW_OFFSET(i) (0x20 * (i) + 0x10)
@@ -65,6 +80,8 @@ extern const struct nvdimm_security_ops *cxl_security_ops;
#define CXL_HDM_DECODER0_CTRL_COMMITTED BIT(10)
#define CXL_HDM_DECODER0_CTRL_COMMIT_ERROR BIT(11)
#define CXL_HDM_DECODER0_CTRL_HOSTONLY BIT(12)
+#define CXL_HDM_DECODER0_CTRL_BI BIT(13)
+#define CXL_HDM_DECODER0_CTRL_ISP_MASK GENMASK(27, 24)
#define CXL_HDM_DECODER0_TL_LOW(i) (0x20 * (i) + 0x24)
#define CXL_HDM_DECODER0_TL_HIGH(i) (0x20 * (i) + 0x28)
/* Target list capacity, i.e. max interleave ways for a non-endpoint decoder */
@@ -146,6 +163,27 @@ static inline int ways_to_eiw(unsigned int ways, u8 *eiw)
return 0;
}
+static inline bool eig_valid(u16 eig)
+{
+ int granularity;
+
+ return eig_to_granularity(eig, &granularity) == 0;
+}
+
+static inline bool granularity_valid(int granularity)
+{
+ u16 eig;
+
+ return granularity_to_eig(granularity, &eig) == 0;
+}
+
+static inline bool ways_valid(unsigned int ways)
+{
+ u8 eiw;
+
+ return ways_to_eiw(ways, &eiw) == 0;
+}
+
/* RAS Registers CXL 2.0 8.2.5.9 CXL RAS Capability Structure */
#define CXL_RAS_UNCORRECTABLE_STATUS_OFFSET 0x0
#define CXL_RAS_UNCORRECTABLE_STATUS_MASK (GENMASK(16, 14) | GENMASK(11, 0))
@@ -175,6 +213,31 @@ static inline int ways_to_eiw(unsigned int ways, u8 *eiw)
#define CXL_HEADERLOG_TRACE_SIZE SZ_512
#define CXL_HEADERLOG_TRACE_SIZE_U32 (CXL_HEADERLOG_TRACE_SIZE / sizeof(u32))
+/* CXL 4.0 8.2.4.26 CXL BI Route Table Capability Structure */
+#define CXL_BI_RT_CAPS_OFFSET 0x0
+#define CXL_BI_RT_CAPS_EXPLICIT_COMMIT_REQ BIT(0)
+#define CXL_BI_RT_CTRL_OFFSET 0x4
+#define CXL_BI_RT_CTRL_BI_COMMIT BIT(0)
+#define CXL_BI_RT_STATUS_OFFSET 0x8
+#define CXL_BI_RT_STATUS_BI_COMMITTED BIT(0)
+#define CXL_BI_RT_STATUS_BI_ERR_NOT_COMMITTED BIT(1)
+#define CXL_BI_RT_STATUS_BI_COMMIT_TM_SCALE GENMASK(11, 8)
+#define CXL_BI_RT_STATUS_BI_COMMIT_TM_BASE GENMASK(15, 12)
+
+/* CXL 4.0 8.2.4.27 CXL BI Decoder Capability Structure */
+#define CXL_BI_DECODER_CAPS_OFFSET 0x0
+#define CXL_BI_DECODER_CAPS_HDMD_CAP BIT(0)
+#define CXL_BI_DECODER_CAPS_EXPLICIT_COMMIT_REQ BIT(1)
+#define CXL_BI_DECODER_CTRL_OFFSET 0x4
+#define CXL_BI_DECODER_CTRL_BI_FW BIT(0)
+#define CXL_BI_DECODER_CTRL_BI_ENABLE BIT(1)
+#define CXL_BI_DECODER_CTRL_BI_COMMIT BIT(2)
+#define CXL_BI_DECODER_STATUS_OFFSET 0x8
+#define CXL_BI_DECODER_STATUS_BI_COMMITTED BIT(0)
+#define CXL_BI_DECODER_STATUS_BI_ERR_NOT_COMMITTED BIT(1)
+#define CXL_BI_DECODER_STATUS_BI_COMMIT_TM_SCALE GENMASK(11, 8)
+#define CXL_BI_DECODER_STATUS_BI_COMMIT_TM_BASE GENMASK(15, 12)
+
/* CXL 2.0 8.2.8.1 Device Capabilities Array Register */
#define CXLDEV_CAP_ARRAY_OFFSET 0x0
#define CXLDEV_CAP_ARRAY_CAP_ID 0
@@ -263,12 +326,14 @@ int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
*/
#define CXL_DECODER_F_RAM BIT(0)
#define CXL_DECODER_F_PMEM BIT(1)
-#define CXL_DECODER_F_TYPE2 BIT(2)
-#define CXL_DECODER_F_TYPE3 BIT(3)
+#define CXL_DECODER_F_DEVMEM BIT(2)
+#define CXL_DECODER_F_HOSTONLY BIT(3)
#define CXL_DECODER_F_LOCK BIT(4)
#define CXL_DECODER_F_ENABLE BIT(5)
#define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6)
-#define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK)
+#define CXL_DECODER_F_BI BIT(7)
+#define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK | \
+ CXL_DECODER_F_BI)
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
@@ -574,6 +639,7 @@ struct cxl_dax_region {
* @decoder_ida: allocator for decoder ids
* @reg_map: component and ras register mapping parameters
* @regs: mapped component registers
+ * @bi_lock: serializes BI Decoder/RT state of this port's dports
* @nr_dports: number of entries in @dports
* @hdm_end: track last allocated HDM decoder instance for allocation ordering
* @commit_end: cursor to track highest committed decoder for commit ordering
@@ -596,6 +662,7 @@ struct cxl_port {
struct ida decoder_ida;
struct cxl_register_map reg_map;
struct cxl_component_regs regs;
+ struct mutex bi_lock; /* dport BI state shared below this port */
int nr_dports;
int hdm_end;
int commit_end;
@@ -659,6 +726,8 @@ struct cxl_rcrb_info {
* @coord: access coordinates (bandwidth and latency performance attributes)
* @link_latency: calculated PCIe downstream latency
* @gpf_dvsec: Cached GPF port DVSEC
+ * @nr_bi: number of BI-enabled endpoints below this dport
+ * @bi_held: found up on first use, or kept up for a device below: never cleared
*/
struct cxl_dport {
struct device *dport_dev;
@@ -671,6 +740,8 @@ struct cxl_dport {
struct access_coordinate coord[ACCESS_COORDINATE_MAX];
long link_latency;
int gpf_dvsec;
+ int nr_bi;
+ bool bi_held;
};
/**
@@ -817,6 +888,11 @@ static inline int cxl_root_decoder_autoremove(struct device *host,
{
return cxl_decoder_autoremove(host, &cxlrd->cxlsd.cxld);
}
+
+static inline bool cxl_root_decoder_is_bi(struct cxl_root_decoder *cxlrd)
+{
+ return cxlrd->cxlsd.cxld.flags & CXL_DECODER_F_BI;
+}
int cxl_endpoint_autoremove(struct cxl_memdev *cxlmd, struct cxl_port *endpoint);
/**
@@ -932,8 +1008,7 @@ void cxl_switch_parse_cdat(struct cxl_dport *dport);
int cxl_endpoint_get_perf_coordinates(struct cxl_port *port,
struct access_coordinate *coord);
-void cxl_region_perf_data_calculate(struct cxl_region *cxlr,
- struct cxl_endpoint_decoder *cxled);
+void cxl_region_perf_data_update(struct cxl_region *cxlr);
void cxl_region_shared_upstream_bandwidth_update(struct cxl_region *cxlr);
void cxl_memdev_update_perf(struct cxl_memdev *cxlmd);
@@ -943,6 +1018,9 @@ void cxl_coordinates_combine(struct access_coordinate *out,
struct access_coordinate *c2);
bool cxl_endpoint_decoder_reset_detected(struct cxl_port *port);
+void cxl_bi_probe_capable(struct cxl_port *endpoint);
+int cxl_bi_setup(struct cxl_port *endpoint);
+void cxl_bi_reset_detected(struct cxl_port *endpoint);
struct cxl_dport *devm_cxl_add_dport_by_dev(struct cxl_port *port,
struct device *dport_dev);
diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h
index 35268d1c387c..d58e620823e1 100644
--- a/drivers/cxl/cxlmem.h
+++ b/drivers/cxl/cxlmem.h
@@ -6,6 +6,7 @@
#include <linux/pci.h>
#include <linux/cdev.h>
#include <linux/uuid.h>
+#include <linux/array_size.h>
#include <linux/node.h>
#include <cxl/event.h>
#include <cxl/mailbox.h>
@@ -206,13 +207,18 @@ static const
struct cxl_mbox_cmd_rc cxl_mbox_cmd_rctable[] ={ CMD_CMD_RC_TABLE };
#undef C
+/* The return code comes from the device and may be one this table lacks */
static inline const char *cxl_mbox_cmd_rc2str(struct cxl_mbox_cmd *mbox_cmd)
{
+ if (mbox_cmd->return_code >= ARRAY_SIZE(cxl_mbox_cmd_rctable))
+ return "unknown return code";
return cxl_mbox_cmd_rctable[mbox_cmd->return_code].desc;
}
static inline int cxl_mbox_cmd_rc2errno(struct cxl_mbox_cmd *mbox_cmd)
{
+ if (mbox_cmd->return_code >= ARRAY_SIZE(cxl_mbox_cmd_rctable))
+ return -ENXIO;
return cxl_mbox_cmd_rctable[mbox_cmd->return_code].err;
}
@@ -933,6 +939,7 @@ int cxl_mem_sanitize(struct cxl_memdev *cxlmd, u16 cmd);
* @target_count: for switch decoders, max downstream port targets
* @interleave_mask: interleave granularity capability, see check_interleave_cap()
* @iw_cap_mask: bitmask of supported interleave ways, see check_interleave_cap()
+ * @supported_coherency: HDM Decoder Capability supported coherency models
* @port: mapped cxl_port, see devm_cxl_setup_hdm()
*/
struct cxl_hdm {
@@ -941,6 +948,7 @@ struct cxl_hdm {
unsigned int target_count;
unsigned int interleave_mask;
unsigned long iw_cap_mask;
+ unsigned int supported_coherency;
struct cxl_port *port;
};
diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c
index 8ce58917c32d..f974bd63b775 100644
--- a/drivers/cxl/pci.c
+++ b/drivers/cxl/pci.c
@@ -1117,8 +1117,12 @@ static void cxl_reset_done(struct pci_dev *pdev)
if (!cxlmd->dev.driver)
return;
- if (cxlmd->endpoint &&
- cxl_endpoint_decoder_reset_detected(cxlmd->endpoint)) {
+ if (!cxlmd->endpoint)
+ return;
+
+ cxl_bi_reset_detected(cxlmd->endpoint);
+
+ if (cxl_endpoint_decoder_reset_detected(cxlmd->endpoint)) {
device_for_each_child(&cxlmd->endpoint->dev, NULL,
cxl_endpoint_decoder_clear_reset_flags);
diff --git a/drivers/cxl/port.c b/drivers/cxl/port.c
index c12fd0b89883..58256417fd44 100644
--- a/drivers/cxl/port.c
+++ b/drivers/cxl/port.c
@@ -61,6 +61,47 @@ static int discover_region(struct device *dev, void *unused)
return 0;
}
+static void devm_cxl_port_bi_setup(struct cxl_port *port)
+{
+ struct cxl_register_map *map = &port->reg_map;
+ struct cxl_dport *parent_dport = port->parent_dport;
+ struct device *udev;
+ int cap_id;
+
+ /* no upstream BI registers above host bridges or the cxl_root */
+ if (!parent_dport || is_cxl_root(parent_dport->port))
+ return;
+
+ udev = is_cxl_endpoint(port) ?
+ port->uport_dev->parent : port->uport_dev;
+ if (!dev_is_pci(udev))
+ return;
+
+ /* BI requires 256B Flit on the upstream link */
+ if (!cxl_pci_flit_256(to_pci_dev(udev)))
+ return;
+
+ /* map this port's own BI capability */
+ if (is_cxl_endpoint(port)) {
+ if (!map->component_map.bi_decoder.valid) {
+ dev_dbg(&port->dev, "BI Decoder registers not found\n");
+ return;
+ }
+ cap_id = CXL_CM_CAP_CAP_ID_BI_DECODER;
+ } else {
+ if (!map->component_map.bi_rt.valid) {
+ dev_dbg(&port->dev, "BI RT registers not found\n");
+ return;
+ }
+ cap_id = CXL_CM_CAP_CAP_ID_BI_RT;
+ }
+
+ map->host = &port->dev;
+ if (cxl_map_component_regs(map, &port->regs, BIT(cap_id)))
+ dev_dbg(&port->dev, "Failed to map BI capability 0x%x\n",
+ cap_id);
+}
+
static int cxl_switch_port_probe(struct cxl_port *port)
{
/* Reset nr_dports for rebind of driver */
@@ -72,19 +113,21 @@ static int cxl_switch_port_probe(struct cxl_port *port)
return 0;
}
-static int cxl_ras_unmask(struct cxl_port *port)
+static int cxl_endpoint_ras_unmask(struct cxl_port *endpoint)
{
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct device *parent = cxlmd->dev.parent;
struct pci_dev *pdev;
void __iomem *addr;
u32 orig_val, val, mask;
u16 cap;
int rc;
- if (!dev_is_pci(port->uport_dev))
+ if (!dev_is_pci(parent))
return 0;
- pdev = to_pci_dev(port->uport_dev);
+ pdev = to_pci_dev(parent);
- if (!port->regs.ras) {
+ if (!endpoint->regs.ras) {
pci_dbg(pdev, "No RAS registers.\n");
return 0;
}
@@ -98,7 +141,7 @@ static int cxl_ras_unmask(struct cxl_port *port)
return rc;
if (cap & PCI_EXP_DEVCTL_URRE) {
- addr = port->regs.ras + CXL_RAS_UNCORRECTABLE_MASK_OFFSET;
+ addr = endpoint->regs.ras + CXL_RAS_UNCORRECTABLE_MASK_OFFSET;
orig_val = readl(addr);
mask = CXL_RAS_UNCORRECTABLE_MASK_MASK |
@@ -110,7 +153,7 @@ static int cxl_ras_unmask(struct cxl_port *port)
}
if (cap & PCI_EXP_DEVCTL_CERE) {
- addr = port->regs.ras + CXL_RAS_CORRECTABLE_MASK_OFFSET;
+ addr = endpoint->regs.ras + CXL_RAS_CORRECTABLE_MASK_OFFSET;
orig_val = readl(addr);
val = orig_val & ~CXL_RAS_CORRECTABLE_MASK_MASK;
writel(val, addr);
@@ -131,6 +174,9 @@ static int cxl_endpoint_port_probe(struct cxl_port *port)
read_cdat_data(port);
cxl_endpoint_parse_cdat(port);
+ devm_cxl_port_bi_setup(port);
+ cxl_bi_probe_capable(port);
+
get_device(&cxlmd->dev);
rc = devm_add_action_or_reset(&port->dev, schedule_detach, cxlmd);
if (rc)
@@ -152,7 +198,7 @@ static int cxl_endpoint_port_probe(struct cxl_port *port)
devm_cxl_dport_rch_ras_setup(dport);
devm_cxl_port_ras_setup(port);
- if (cxl_ras_unmask(port))
+ if (cxl_endpoint_ras_unmask(port))
dev_dbg(&port->dev, "failed to unmask RAS interrupts\n");
/*
@@ -255,6 +301,8 @@ static struct cxl_dport *cxl_port_add_dport(struct cxl_port *port,
* on failure, or the device does not implement RAS registers.
*/
devm_cxl_port_ras_setup(port);
+
+ devm_cxl_port_bi_setup(port);
}
dport = devm_cxl_add_dport_by_dev(port, dport_dev);
diff --git a/drivers/cxl/tests/Makefile b/drivers/cxl/tests/Makefile
new file mode 100644
index 000000000000..aa5e4f8e3638
--- /dev/null
+++ b/drivers/cxl/tests/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+ccflags-y += -I$(srctree)/drivers/cxl
+
+obj-$(CONFIG_CXL_KUNIT_TEST) += cxl_hdm_encoding_kunit.o
+cxl_hdm_encoding_kunit-y := hdm_encoding_kunit.o
diff --git a/drivers/cxl/tests/hdm_encoding_kunit.c b/drivers/cxl/tests/hdm_encoding_kunit.c
new file mode 100644
index 000000000000..68db36ea3890
--- /dev/null
+++ b/drivers/cxl/tests/hdm_encoding_kunit.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for the HDM decoder interleave encode/decode helpers
+ * in drivers/cxl/cxl.h
+ */
+#include <kunit/test.h>
+#include "cxl.h"
+
+struct eiw_case {
+ u8 eiw;
+ int ways;
+};
+
+/* Encodings of the IW field in the HDM Decoder n Control Register */
+static const struct eiw_case valid_eiw[] = {
+ { 0, 1 }, { 1, 2 }, { 2, 4 }, { 3, 8 }, { 4, 16 },
+ { 8, 3 }, { 9, 6 }, { 10, 12 },
+};
+
+static const u8 invalid_eiw[] = { 5, 6, 7, 11, 12, 13, 14, 15, 0xff };
+
+static const unsigned int invalid_ways[] = {
+ 0, 5, 7, 9, 10, 11, 13, 14, 15, 17, 24, 32,
+};
+
+static void eiw_to_ways_valid(struct kunit *test)
+{
+ for (int i = 0; i < ARRAY_SIZE(valid_eiw); i++) {
+ int ways = 0;
+
+ KUNIT_EXPECT_EQ_MSG(test, eiw_to_ways(valid_eiw[i].eiw, &ways),
+ 0, "eiw=%u", valid_eiw[i].eiw);
+ KUNIT_EXPECT_EQ_MSG(test, ways, valid_eiw[i].ways, "eiw=%u",
+ valid_eiw[i].eiw);
+ }
+}
+
+static void eiw_to_ways_invalid(struct kunit *test)
+{
+ for (int i = 0; i < ARRAY_SIZE(invalid_eiw); i++) {
+ int ways;
+
+ KUNIT_EXPECT_EQ_MSG(test, eiw_to_ways(invalid_eiw[i], &ways),
+ -EINVAL, "eiw=%u", invalid_eiw[i]);
+ }
+}
+
+static void ways_to_eiw_valid(struct kunit *test)
+{
+ for (int i = 0; i < ARRAY_SIZE(valid_eiw); i++) {
+ u8 eiw = 0xff;
+
+ KUNIT_EXPECT_EQ_MSG(test, ways_to_eiw(valid_eiw[i].ways, &eiw),
+ 0, "ways=%d", valid_eiw[i].ways);
+ KUNIT_EXPECT_EQ_MSG(test, eiw, valid_eiw[i].eiw, "ways=%d",
+ valid_eiw[i].ways);
+ }
+}
+
+static void ways_to_eiw_invalid(struct kunit *test)
+{
+ for (int i = 0; i < ARRAY_SIZE(invalid_ways); i++) {
+ u8 eiw;
+
+ KUNIT_EXPECT_EQ_MSG(test, ways_to_eiw(invalid_ways[i], &eiw),
+ -EINVAL, "ways=%u", invalid_ways[i]);
+ }
+}
+
+static void eig_to_granularity_valid(struct kunit *test)
+{
+ for (u16 eig = 0; eig <= CXL_DECODER_MAX_ENCODED_IG; eig++) {
+ int gran = 0;
+ u16 back = 0xffff;
+
+ KUNIT_EXPECT_EQ_MSG(test, eig_to_granularity(eig, &gran), 0,
+ "eig=%u", eig);
+ KUNIT_EXPECT_EQ_MSG(test, gran, 256 << eig, "eig=%u", eig);
+
+ /* Round trip back to the encoding */
+ KUNIT_EXPECT_EQ_MSG(test, granularity_to_eig(gran, &back), 0,
+ "gran=%d", gran);
+ KUNIT_EXPECT_EQ_MSG(test, back, eig, "gran=%d", gran);
+ }
+}
+
+static void eig_to_granularity_invalid(struct kunit *test)
+{
+ static const u16 invalid_eig[] = { 7, 8, 15, 0xffff };
+
+ for (int i = 0; i < ARRAY_SIZE(invalid_eig); i++) {
+ int gran;
+
+ KUNIT_EXPECT_EQ_MSG(test,
+ eig_to_granularity(invalid_eig[i], &gran),
+ -EINVAL, "eig=%u", invalid_eig[i]);
+ }
+}
+
+static void granularity_to_eig_invalid(struct kunit *test)
+{
+ static const int invalid_gran[] = {
+ 0, 1, 128, 255, 257, 384, 768, SZ_32K, -256,
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(invalid_gran); i++) {
+ u16 eig;
+
+ KUNIT_EXPECT_EQ_MSG(test,
+ granularity_to_eig(invalid_gran[i], &eig),
+ -EINVAL, "gran=%d", invalid_gran[i]);
+ }
+}
+
+/* Decoder Count field of the CXL HDM Decoder Capability Register */
+static void hdm_decoder_count(struct kunit *test)
+{
+ static const int expect[16] = {
+ [0x0] = 1,
+ [0x1] = 2, [0x2] = 4, [0x3] = 6, [0x4] = 8,
+ [0x5] = 10, [0x6] = 12, [0x7] = 14, [0x8] = 16,
+ [0x9] = 20, [0xA] = 24, [0xB] = 28, [0xC] = 32,
+ [0xD ... 0xF] = -ENXIO,
+ };
+
+ for (u32 val = 0; val < ARRAY_SIZE(expect); val++) {
+ u32 cap = FIELD_PREP(CXL_HDM_DECODER_COUNT_MASK, val);
+
+ KUNIT_EXPECT_EQ_MSG(test, cxl_hdm_decoder_count(cap),
+ expect[val], "count field=%u", val);
+ }
+
+ /* Bits outside the count field are ignored */
+ KUNIT_EXPECT_EQ(test,
+ cxl_hdm_decoder_count((u32)~CXL_HDM_DECODER_COUNT_MASK),
+ 1);
+}
+
+static struct kunit_case cxl_hdm_encoding_cases[] = {
+ KUNIT_CASE(eiw_to_ways_valid),
+ KUNIT_CASE(eiw_to_ways_invalid),
+ KUNIT_CASE(ways_to_eiw_valid),
+ KUNIT_CASE(ways_to_eiw_invalid),
+ KUNIT_CASE(eig_to_granularity_valid),
+ KUNIT_CASE(eig_to_granularity_invalid),
+ KUNIT_CASE(granularity_to_eig_invalid),
+ KUNIT_CASE(hdm_decoder_count),
+ {}
+};
+
+static struct kunit_suite cxl_hdm_encoding_suite = {
+ .name = "cxl_hdm_encoding",
+ .test_cases = cxl_hdm_encoding_cases,
+};
+
+kunit_test_suite(cxl_hdm_encoding_suite);
+
+MODULE_DESCRIPTION("KUnit tests for CXL HDM decoder encoding helpers");
+MODULE_LICENSE("GPL");
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 1c04a7823f64..6fa9d913ccdb 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -16,7 +16,8 @@
* mailbox, or other memory-device-standard manageability
* flows.
* @CXL_DEVTYPE_CLASSMEM: Common class definition of a CXL Type-3 device with
- * HDM-H and class-mandatory memory device registers
+ * HDM-H or HDM-DB, and class-mandatory memory device
+ * registers
*/
enum cxl_devtype {
CXL_DEVTYPE_DEVMEM,
@@ -34,10 +35,14 @@ struct cxl_regs {
* Common set of CXL Component register block base pointers
* @hdm_decoder: CXL 2.0 8.2.5.12 CXL HDM Decoder Capability Structure
* @ras: CXL 2.0 8.2.5.9 CXL RAS Capability Structure
+ * @bi_rt: CXL 4.0 8.2.4.26 CXL BI Route Table Capability Structure
+ * @bi_decoder: CXL 4.0 8.2.4.27 CXL BI Decoder Capability Structure
*/
struct_group_tagged(cxl_component_regs, component,
void __iomem *hdm_decoder;
void __iomem *ras;
+ void __iomem *bi_rt;
+ void __iomem *bi_decoder;
);
/*
* Common set of CXL Device register block base pointers
@@ -80,6 +85,8 @@ struct cxl_reg_map {
struct cxl_component_reg_map {
struct cxl_reg_map hdm_decoder;
struct cxl_reg_map ras;
+ struct cxl_reg_map bi_rt;
+ struct cxl_reg_map bi_decoder;
};
struct cxl_device_reg_map {
@@ -189,6 +196,8 @@ struct cxl_dpa_partition {
* @regs: Parsed register blocks
* @cxl_dvsec: Offset to the PCIe device DVSEC
* @rcd: operating in RCD mode (CXL 3.0 9.11.8 CXL Devices Attached to an RCH)
+ * @bi: device is BI (Back-Invalidate) enabled
+ * @bi_capable: device and topology path are BI capable
* @media_ready: Indicate whether the device media is usable
* @dpa_res: Overall DPA resource tree for the device
* @part: DPA partition array
@@ -208,6 +217,8 @@ struct cxl_dev_state {
struct cxl_device_regs regs;
int cxl_dvsec;
bool rcd;
+ bool bi;
+ bool bi_capable;
bool media_ready;
struct resource dpa_res;
struct cxl_dpa_partition part[CXL_NR_PARTITIONS_MAX];
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index e49ce644b4b1..39b4bb2e0404 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -14,6 +14,7 @@ wrap_cxl_core += --wrap=devm_cxl_add_dport_by_dev
wrap_cxl_core += --wrap=devm_cxl_switch_port_decoders_setup
wrap_cxl_core += --wrap=devm_cxl_endpoint_decoders_setup
wrap_cxl_core += --wrap=cxl_endpoint_parse_cdat
+wrap_cxl_core += --wrap=cxl_bi_probe_capable
LDFLAGS_cxl_acpi.o += $(wrap_cxl_core)
LDFLAGS_cxl_pmem.o += $(wrap_cxl_core)
LDFLAGS_cxl_port.o += $(wrap_cxl_core)
diff --git a/tools/testing/cxl/test/accel.c b/tools/testing/cxl/test/accel.c
index 8e6f4687ca02..f3800083ea27 100644
--- a/tools/testing/cxl/test/accel.c
+++ b/tools/testing/cxl/test/accel.c
@@ -31,7 +31,7 @@ static int cxl_mock_accel_probe(struct platform_device *pdev)
cxlds = &cxl_accel->cxlds;
cxlds->media_ready = true;
- rc = cxl_set_capacity(cxlds, SZ_512M);
+ rc = cxl_set_capacity(cxlds, SZ_1G);
if (rc)
return rc;
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index 3eb1051b1ee8..4724297446de 100644
--- a/tools/testing/cxl/test/cxl.c
+++ b/tools/testing/cxl/test/cxl.c
@@ -185,13 +185,25 @@ static struct {
} cfmws6;
struct {
struct acpi_cedt_cfmws cfmws;
- u32 target[2];
+ u32 target[1];
} cfmws7;
struct {
struct acpi_cedt_cfmws cfmws;
- u32 target[3];
+ u32 target[1];
} cfmws8;
struct {
+ struct acpi_cedt_cfmws cfmws;
+ u32 target[2];
+ } cfmws9;
+ struct {
+ struct acpi_cedt_cfmws cfmws;
+ u32 target[3];
+ } cfmws10;
+ struct {
+ struct acpi_cedt_cfmws cfmws;
+ u32 target[1];
+ } cfmws_bi;
+ struct {
struct acpi_cedt_cxims cxims;
u64 xormap_list[2];
} cxims0;
@@ -325,13 +337,44 @@ static struct {
},
.target = { 3 },
},
- /* .cfmws6,7,8 use ACPI_CEDT_CFMWS_ARITHMETIC_XOR */
.cfmws6 = {
.cfmws = {
.header = {
.type = ACPI_CEDT_TYPE_CFMWS,
.length = sizeof(mock_cedt.cfmws6),
},
+ .interleave_ways = 0,
+ .granularity = 4,
+ .restrictions = ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM |
+ ACPI_CEDT_CFMWS_RESTRICT_PMEM,
+ .qtg_id = FAKE_QTG_ID,
+ .window_size = SZ_256M * 4UL,
+ },
+ .target = { 2 },
+ },
+ .cfmws7 = {
+ .cfmws = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CFMWS,
+ .length = sizeof(mock_cedt.cfmws7),
+ },
+ .interleave_ways = 0,
+ .granularity = 4,
+ .restrictions = ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM |
+ ACPI_CEDT_CFMWS_RESTRICT_VOLATILE |
+ ACPI_CEDT_CFMWS_RESTRICT_PMEM,
+ .qtg_id = FAKE_QTG_ID,
+ .window_size = SZ_256M * 4UL,
+ },
+ .target = { 1 },
+ },
+ /* .cfmws8,9,10 use ACPI_CEDT_CFMWS_ARITHMETIC_XOR */
+ .cfmws8 = {
+ .cfmws = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CFMWS,
+ .length = sizeof(mock_cedt.cfmws8),
+ },
.interleave_arithmetic = ACPI_CEDT_CFMWS_ARITHMETIC_XOR,
.interleave_ways = 0,
.granularity = 4,
@@ -342,11 +385,11 @@ static struct {
},
.target = { 0, },
},
- .cfmws7 = {
+ .cfmws9 = {
.cfmws = {
.header = {
.type = ACPI_CEDT_TYPE_CFMWS,
- .length = sizeof(mock_cedt.cfmws7),
+ .length = sizeof(mock_cedt.cfmws9),
},
.interleave_arithmetic = ACPI_CEDT_CFMWS_ARITHMETIC_XOR,
.interleave_ways = 1,
@@ -358,11 +401,11 @@ static struct {
},
.target = { 0, 1, },
},
- .cfmws8 = {
+ .cfmws10 = {
.cfmws = {
.header = {
.type = ACPI_CEDT_TYPE_CFMWS,
- .length = sizeof(mock_cedt.cfmws8),
+ .length = sizeof(mock_cedt.cfmws10),
},
.interleave_arithmetic = ACPI_CEDT_CFMWS_ARITHMETIC_XOR,
.interleave_ways = 8,
@@ -374,6 +417,22 @@ static struct {
},
.target = { 0, 1, 2, },
},
+ .cfmws_bi = {
+ .cfmws = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CFMWS,
+ .length = sizeof(mock_cedt.cfmws_bi),
+ },
+ .interleave_ways = 0,
+ .granularity = 4,
+ .restrictions = ACPI_CEDT_CFMWS_RESTRICT_BI |
+ ACPI_CEDT_CFMWS_RESTRICT_DEVMEM |
+ ACPI_CEDT_CFMWS_RESTRICT_VOLATILE,
+ .qtg_id = FAKE_QTG_ID,
+ .window_size = SZ_256M * 4UL,
+ },
+ .target = { 0 },
+ },
.cxims0 = {
.cxims = {
.header = {
@@ -407,23 +466,37 @@ struct acpi_cedt_cfmws *mock_cfmws[] = {
[3] = &mock_cedt.cfmws3.cfmws,
[4] = &mock_cedt.cfmws4.cfmws,
[5] = &mock_cedt.cfmws5.cfmws,
- /* Modulo Math above, XOR Math below */
[6] = &mock_cedt.cfmws6.cfmws,
[7] = &mock_cedt.cfmws7.cfmws,
+ /* Modulo Math above, XOR Math below */
[8] = &mock_cedt.cfmws8.cfmws,
+ [9] = &mock_cedt.cfmws9.cfmws,
+ [10] = &mock_cedt.cfmws10.cfmws,
};
static int cfmws_start;
static int cfmws_end;
#define CFMWS_MOD_ARRAY_START 0
-#define CFMWS_MOD_ARRAY_END 5
-#define CFMWS_XOR_ARRAY_START 6
-#define CFMWS_XOR_ARRAY_END 8
+#define CFMWS_MOD_ARRAY_END 7
+#define CFMWS_XOR_ARRAY_START 8
+#define CFMWS_XOR_ARRAY_END 10
struct acpi_cedt_cxims *mock_cxims[1] = {
[0] = &mock_cedt.cxims0.cxims,
};
+static struct acpi_cedt_cfmws *mock_auto_region_cfmws(void)
+{
+ for (int i = cfmws_start; i <= cfmws_end; i++) {
+ struct acpi_cedt_cfmws *window = mock_cfmws[i];
+
+ if (window->restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE)
+ return window;
+ }
+
+ return NULL;
+}
+
struct cxl_mock_res {
struct list_head list;
struct range range;
@@ -535,6 +608,12 @@ static int populate_cedt(void)
window->base_hpa = res->range.start;
}
+ res = alloc_mock_res(mock_cedt.cfmws_bi.cfmws.window_size,
+ max_t(int, SZ_256M, PMD_SIZE));
+ if (!res)
+ return -ENOMEM;
+ mock_cedt.cfmws_bi.cfmws.base_hpa = res->range.start;
+
return 0;
}
@@ -570,12 +649,17 @@ static int mock_acpi_table_parse_cedt(enum acpi_cedt_type id,
handler_arg(h, arg, end);
}
- if (id == ACPI_CEDT_TYPE_CFMWS)
+ if (id == ACPI_CEDT_TYPE_CFMWS) {
for (i = cfmws_start; i <= cfmws_end; i++) {
h = (union acpi_subtable_headers *) mock_cfmws[i];
end = (unsigned long) h + mock_cfmws[i]->header.length;
handler_arg(h, arg, end);
}
+ /* one HDM-DB window in every topology */
+ h = (union acpi_subtable_headers *)&mock_cedt.cfmws_bi.cfmws;
+ end = (unsigned long)h + mock_cedt.cfmws_bi.cfmws.header.length;
+ handler_arg(h, arg, end);
+ }
if (id == ACPI_CEDT_TYPE_CXIMS)
for (i = 0; i < ARRAY_SIZE(mock_cxims); i++) {
@@ -737,10 +821,43 @@ static struct cxl_hdm *mock_cxl_setup_hdm(struct cxl_port *port,
cxlhdm->port = port;
cxlhdm->interleave_mask = ~0U;
cxlhdm->iw_cap_mask = ~0UL;
+
+ /* a page stands in for the endpoint's BI Decoder block, Enable set */
+ if (is_cxl_endpoint(port)) {
+ void __iomem *bi = (void __iomem *)
+ devm_get_free_pages(dev, GFP_KERNEL | __GFP_ZERO, 0);
+
+ if (!bi)
+ return ERR_PTR(-ENOMEM);
+ writel(CXL_BI_DECODER_CTRL_BI_ENABLE,
+ bi + CXL_BI_DECODER_CTRL_OFFSET);
+ port->regs.bi_decoder = bi;
+ }
+
dev_set_drvdata(dev, cxlhdm);
return cxlhdm;
}
+/* HPA-based, so replay after cxl_acpi rebind can re-derive it */
+static bool mock_hpa_is_bi(u64 hpa)
+{
+ struct acpi_cedt_cfmws *bi = &mock_cedt.cfmws_bi.cfmws;
+
+ return hpa >= bi->base_hpa && hpa < bi->base_hpa + bi->window_size;
+}
+
+/* committed BI state is a decoder flag, so no register to emulate */
+static void mock_decoder_set_bi(struct cxl_decoder *cxld, bool bi)
+{
+ if (!is_endpoint_decoder(&cxld->dev))
+ return;
+
+ if (bi)
+ cxld->flags |= CXL_DECODER_F_BI;
+ else
+ cxld->flags &= ~CXL_DECODER_F_BI;
+}
+
struct target_map_ctx {
u32 *target_map;
int index;
@@ -913,15 +1030,13 @@ static void __cxld_registry_save(struct cxl_test_decoder *td,
cxld_copy(&td->cxled.cxld, cxld);
td->cxled.state = cxled->state;
- td->cxled.skip = cxled->skip;
- if (!(cxld->flags & CXL_DECODER_F_ENABLE)) {
- td->dpa_range.start = 0;
- td->dpa_range.end = -1;
- } else if (cxled->dpa_res) {
+ if ((cxld->flags & CXL_DECODER_F_ENABLE) && cxled->dpa_res) {
+ td->cxled.skip = cxled->skip;
td->dpa_range.start = cxled->dpa_res->start;
td->dpa_range.end = cxled->dpa_res->end;
} else {
+ td->cxled.skip = 0;
td->dpa_range.start = 0;
td->dpa_range.end = -1;
}
@@ -974,6 +1089,7 @@ static int mock_decoder_commit(struct cxl_decoder *cxld)
cxled->state = CXL_DECODER_STATE_AUTO;
}
+ mock_decoder_set_bi(cxld, mock_hpa_is_bi(cxld->hpa_range.start));
cxld_registry_update(cxld);
return 0;
@@ -1001,8 +1117,8 @@ static void mock_decoder_reset(struct cxl_decoder *cxld)
to_cxl_endpoint_decoder(&cxld->dev);
cxled->state = CXL_DECODER_STATE_MANUAL;
- cxled->skip = 0;
}
+ mock_decoder_set_bi(cxld, false);
if (decoder_reset_preserve_registry)
dev_dbg(port->uport_dev, "decoder%d: skip registry update\n",
cxld->id);
@@ -1127,7 +1243,8 @@ enum cxld_init_type {
static enum cxld_init_type get_decoder_init_type(struct cxl_decoder *cxld,
struct platform_device *pdev,
bool hb0,
- struct cxl_test_decoder **td)
+ struct cxl_test_decoder **td,
+ struct acpi_cedt_cfmws **window)
{
struct cxl_test_decoder *found_td = cxld_registry_find(cxld);
@@ -1137,6 +1254,7 @@ static enum cxld_init_type get_decoder_init_type(struct cxl_decoder *cxld,
}
*td = NULL;
+ *window = NULL;
/*
* The first decoder on the first 2 devices on the first switch
@@ -1150,6 +1268,11 @@ static enum cxld_init_type get_decoder_init_type(struct cxl_decoder *cxld,
pdev->id > 4 || cxld->id > (mock_zero_size_decoders ? 2 : 0))
return MOCK_DECODER_INIT_DEFAULT;
+ /* XOR windows are all pmem, no window to host the RAM region */
+ *window = mock_auto_region_cfmws();
+ if (!*window)
+ return MOCK_DECODER_INIT_DEFAULT;
+
return type2_test ? MOCK_DECODER_INIT_TYPE2_AUTO :
MOCK_DECODER_INIT_TYPE3_AUTO;
}
@@ -1163,17 +1286,22 @@ static bool mock_decoder_handle_saved(struct cxl_decoder *cxld, struct cxl_test_
else
enabled = td->cxled.cxld.flags & CXL_DECODER_F_ENABLE;
- if (enabled)
- return !cxld_registry_restore(cxld, td);
+ if (enabled) {
+ if (cxld_registry_restore(cxld, td))
+ return false;
+ mock_decoder_set_bi(cxld,
+ mock_hpa_is_bi(cxld->hpa_range.start));
+ return true;
+ }
init_disabled_mock_decoder(cxld);
return false;
}
static void mock_init_hdm_type2_cxled(struct cxl_endpoint_decoder *cxled,
- struct cxl_port *port)
+ struct cxl_port *port,
+ struct acpi_cedt_cfmws *window)
{
- struct acpi_cedt_cfmws *window = mock_cfmws[0];
struct cxl_decoder *cxld = &cxled->cxld;
struct cxl_switch_decoder *cxlsd;
struct cxl_dport *dport;
@@ -1246,9 +1374,9 @@ static void mock_init_hdm_type2_cxled(struct cxl_endpoint_decoder *cxled,
static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled,
struct cxl_port *port,
struct platform_device *pdev,
- bool hb0)
+ bool hb0,
+ struct acpi_cedt_cfmws *window)
{
- struct acpi_cedt_cfmws *window = mock_cfmws[0];
struct cxl_decoder *cxld = &cxled->cxld;
struct cxl_switch_decoder *cxlsd;
struct cxl_dport *dport;
@@ -1399,6 +1527,7 @@ static bool mock_init_hdm_decoder(struct cxl_decoder *cxld)
{
struct cxl_endpoint_decoder *cxled = NULL;
struct platform_device *pdev = NULL;
+ struct acpi_cedt_cfmws *window;
struct cxl_test_decoder *td;
struct cxl_memdev *cxlmd;
struct cxl_port *port;
@@ -1427,7 +1556,7 @@ static bool mock_init_hdm_decoder(struct cxl_decoder *cxld)
port = to_cxl_port(cxld->dev.parent);
}
- switch (get_decoder_init_type(cxld, pdev, hb0, &td)) {
+ switch (get_decoder_init_type(cxld, pdev, hb0, &td, &window)) {
case MOCK_DECODER_INIT_SAVED:
if (WARN_ON(!td))
return false;
@@ -1440,10 +1569,10 @@ static bool mock_init_hdm_decoder(struct cxl_decoder *cxld)
default_mock_decoder(cxld);
return false;
case MOCK_DECODER_INIT_TYPE3_AUTO:
- mock_init_hdm_type3_cxled(cxled, port, pdev, hb0);
+ mock_init_hdm_type3_cxled(cxled, port, pdev, hb0, window);
return false;
case MOCK_DECODER_INIT_TYPE2_AUTO:
- mock_init_hdm_type2_cxled(cxled, port);
+ mock_init_hdm_type2_cxled(cxled, port, window);
return false;
default:
return false;
@@ -1523,9 +1652,12 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
return 0;
}
+static int mock_cxl_bi_setup(struct cxl_port *endpoint);
+
static int __mock_cxl_decoders_setup(struct cxl_port *port)
{
struct cxl_hdm *cxlhdm;
+ int rc;
cxlhdm = mock_cxl_setup_hdm(port, NULL);
if (IS_ERR(cxlhdm)) {
@@ -1534,6 +1666,13 @@ static int __mock_cxl_decoders_setup(struct cxl_port *port)
return PTR_ERR(cxlhdm);
}
+ /* as the real setup: BI between the HDM state and the decoders */
+ if (is_cxl_endpoint(port)) {
+ rc = mock_cxl_bi_setup(port);
+ if (rc)
+ dev_dbg(&port->dev, "BI setup failed rc=%d\n", rc);
+ }
+
return mock_cxl_enumerate_decoders(cxlhdm, NULL);
}
@@ -1629,18 +1768,29 @@ static struct cxl_dport *mock_cxl_add_dport_by_dev(struct cxl_port *port,
}
/*
- * Faking the cxl_dpa_perf for the memdev when appropriate.
+ * Derive mock performance data from the memdev id so tests can identify
+ * each memdev's contribution to region performance.
+ *
+ * latency = 100 + 10 * id (nanoseconds)
+ * bandwidth = 1000 + 100 * id (MB/s)
+ *
+ * Keep the values nonzero so the region access attributes are visible.
+ * The ndctl cxl-perf-data.sh test relies on these formulas.
*/
static void dpa_perf_setup(struct cxl_port *endpoint, struct range *range,
struct cxl_dpa_perf *dpa_perf)
{
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ unsigned int bandwidth = 1000 + 100 * cxlmd->id;
+ unsigned int latency = 100 + 10 * cxlmd->id;
+
dpa_perf->qos_class = FAKE_QTG_ID;
dpa_perf->dpa_range = *range;
for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) {
- dpa_perf->coord[i].read_latency = 500;
- dpa_perf->coord[i].write_latency = 500;
- dpa_perf->coord[i].read_bandwidth = 1000;
- dpa_perf->coord[i].write_bandwidth = 1000;
+ dpa_perf->coord[i].read_latency = latency;
+ dpa_perf->coord[i].write_latency = latency;
+ dpa_perf->coord[i].read_bandwidth = bandwidth;
+ dpa_perf->coord[i].write_bandwidth = bandwidth;
}
}
@@ -1722,6 +1872,67 @@ mock_region_intersects_soft_reserve(resource_size_t start, size_t size)
return -1;
}
+/*
+ * All-software mirror of the BI enable path: no BI RT registers exist
+ * on mock devices, so capability and enablement are asserted directly
+ * while the dport nr_bi accounting - the part with driver-visible
+ * semantics (shared transit dports, teardown order) - follows the same
+ * walk the real cxl_bi_enable_path() takes.
+ */
+static void mock_cxl_bi_probe_capable(struct cxl_port *endpoint)
+{
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+
+ /* BI is VH-only, mirroring cxl_bi_probe_capable() */
+ cxlds->bi_capable = !cxlds->rcd;
+}
+
+static void mock_cxl_bi_dealloc(void *data)
+{
+ struct cxl_port *endpoint = data;
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ struct cxl_dport *dport_iter;
+ struct cxl_port *port_iter;
+
+ cxlds->bi = false;
+ dport_iter = endpoint->parent_dport;
+ port_iter = dport_iter->port;
+ while (port_iter->parent_dport) {
+ scoped_guard(mutex, &port_iter->bi_lock) {
+ if (!WARN_ON_ONCE(dport_iter->nr_bi == 0))
+ dport_iter->nr_bi--;
+ }
+ dport_iter = port_iter->parent_dport;
+ port_iter = dport_iter->port;
+ }
+}
+
+static int mock_cxl_bi_setup(struct cxl_port *endpoint)
+{
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ struct cxl_dport *dport_iter;
+ struct cxl_port *port_iter;
+
+ if (!cxlds->bi_capable)
+ return 0;
+
+ dport_iter = endpoint->parent_dport;
+ port_iter = dport_iter->port;
+ while (port_iter->parent_dport) {
+ scoped_guard(mutex, &port_iter->bi_lock)
+ dport_iter->nr_bi++;
+ dport_iter = port_iter->parent_dport;
+ port_iter = dport_iter->port;
+ }
+ cxlds->bi = true;
+
+ return devm_add_action_or_reset(&endpoint->dev, mock_cxl_bi_dealloc,
+ endpoint);
+}
+
static struct cxl_mock_ops cxl_mock_ops = {
.is_mock_adev = is_mock_adev,
.is_mock_bridge = is_mock_bridge,
@@ -1740,6 +1951,7 @@ static struct cxl_mock_ops cxl_mock_ops = {
.walk_hmem_resources = mock_walk_hmem_resources,
.region_intersects = mock_region_intersects,
.region_intersects_soft_reserve = mock_region_intersects_soft_reserve,
+ .cxl_bi_probe_capable = mock_cxl_bi_probe_capable,
.list = LIST_HEAD_INIT(cxl_mock_ops.list),
};
diff --git a/tools/testing/cxl/test/mock.c b/tools/testing/cxl/test/mock.c
index 6454b868b122..eabc9bba0fac 100644
--- a/tools/testing/cxl/test/mock.c
+++ b/tools/testing/cxl/test/mock.c
@@ -181,6 +181,19 @@ int __wrap_devm_cxl_endpoint_decoders_setup(struct cxl_port *port)
}
EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_endpoint_decoders_setup, "CXL");
+void __wrap_cxl_bi_probe_capable(struct cxl_port *endpoint)
+{
+ int index;
+ struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
+
+ if (ops && ops->is_mock_port(endpoint->uport_dev))
+ ops->cxl_bi_probe_capable(endpoint);
+ else
+ cxl_bi_probe_capable(endpoint);
+ put_cxl_mock_ops(index);
+}
+EXPORT_SYMBOL_NS_GPL(__wrap_cxl_bi_probe_capable, "CXL");
+
int __wrap_cxl_await_media_ready(struct cxl_dev_state *cxlds)
{
int rc, index;
diff --git a/tools/testing/cxl/test/mock.h b/tools/testing/cxl/test/mock.h
index 846d7c5d6eaa..c7919c3eb875 100644
--- a/tools/testing/cxl/test/mock.h
+++ b/tools/testing/cxl/test/mock.h
@@ -35,6 +35,7 @@ struct cxl_mock_ops {
unsigned long flags, unsigned long desc);
int (*region_intersects_soft_reserve)(resource_size_t start,
size_t size);
+ void (*cxl_bi_probe_capable)(struct cxl_port *endpoint);
};
int hmem_test_init(void);