diff options
| author | Mark Brown <broonie@kernel.org> | 2026-11-13 17:23:45 +0000 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-11-13 17:23:45 +0000 |
| commit | 91e174e86ee7b4c636fea03752d43aa0ded8df4e (patch) | |
| tree | ac7af2d6e5ab445c4100a968ea9a4a4e4a42a4b3 | |
| parent | 4507970404e9dde2a3cb6b799d36d44f03131879 (diff) | |
| parent | d4780f61df995bef6b7b97958b9f1856593740d7 (diff) | |
| download | linux-next-91e174e86ee7b4c636fea03752d43aa0ded8df4e.tar.gz linux-next-91e174e86ee7b4c636fea03752d43aa0ded8df4e.zip | |
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git
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, ®ions_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, ®s->hdm_decoder }, { &map->component_map.ras, ®s->ras }, + { &map->component_map.bi_rt, ®s->bi_rt }, + { &map->component_map.bi_decoder, ®s->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); |
