Re: [PATCH v8 07/10] PCI/CXL: Discover the CXL reset scope

From: Richard Cheng

Date: Mon Jul 06 2026 - 21:46:03 EST


On Fri, Jul 03, 2026 at 10:05:05PM +0800, Srirangan Madhavan wrote:
> Add reset context support to validate that CXL Reset is function-scoped
> before advertising it as a PCI reset method. Use the Non-CXL Function
> Map, ARI/devfn rules, and CXL.cache/mem capability bits to reject reset
> when another same-scope function would also be affected.
>
> If the Function Map cannot be read, warn and conservatively treat all
> candidate same-scope functions as CXL-capable for scope validation.
>
> Signed-off-by: Srirangan Madhavan <smadhavan@xxxxxxxxxx>
> ---
> drivers/cxl/core/reset.c | 153 +++++++++++++++++++++++++++++++++-
> include/uapi/linux/pci_regs.h | 1 +
> 2 files changed, 152 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
> index fb741b66e502..8e80e7588046 100644
> --- a/drivers/cxl/core/reset.c
> +++ b/drivers/cxl/core/reset.c
> @@ -1,5 +1,6 @@
> // SPDX-License-Identifier: GPL-2.0-only
> /* Copyright(c) 2026 NVIDIA Corporation. All rights reserved. */
> +#include <linux/bitmap.h>
> #include <linux/delay.h>
> #include <linux/bug.h>
> #include <linux/bitfield.h>
> @@ -469,6 +470,21 @@ static const u32 cxl_reset_timeout_ms[] = {
> #define CXL_CACHE_WBI_TIMEOUT_US 100000
> #define CXL_CACHE_WBI_POLL_US 100
>
> +/* CXL r4.0 sec 8.1.4 defines 256 bits of Non-CXL Function Map. */
> +#define CXL_RESET_MAX_FUNCTIONS 256
> +#define CXL_RESET_FUNCTION_MAP_REGS (CXL_RESET_MAX_FUNCTIONS / 32)
> +
> +struct cxl_reset_context {
> + struct pci_dev *target;
> +};
> +
> +struct cxl_reset_walk_context {
> + struct cxl_reset_context *ctx;
> + DECLARE_BITMAP(non_cxl_func_map, CXL_RESET_MAX_FUNCTIONS);
> + bool ari;
> + int rc;
> +};
> +
> struct cxl_hdm_range {
> struct list_head list;
> struct pci_dev *pdev;
> @@ -480,6 +496,137 @@ struct cxl_hdm_range_context {
> struct list_head ranges;
> };
>
> +static void cxl_reset_context_init(struct cxl_reset_context *ctx,
> + struct pci_dev *pdev)
> +{
> + *ctx = (struct cxl_reset_context) {
> + .target = pdev,
> + };
> +}
> +
> +static void cxl_reset_read_non_cxl_func_map(struct pci_dev *pdev,
> + unsigned long *map)
> +{
> + u32 words[CXL_RESET_FUNCTION_MAP_REGS];
> + int dvsec, reg;
> +
> + bitmap_zero(map, CXL_RESET_MAX_FUNCTIONS);
> +
> + dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
> + PCI_DVSEC_CXL_FUNCTION_MAP);
> + if (!dvsec)
> + return;
> +
> + for (reg = 0; reg < CXL_RESET_FUNCTION_MAP_REGS; reg++) {
> + int rc;
> +
> + rc = pci_read_config_dword(pdev,
> + dvsec + PCI_DVSEC_CXL_FUNCTION_MAP_REG +
> + reg * sizeof(u32), &words[reg]);
> + if (rc) {
> + pci_warn(pdev,
> + "failed to read Non-CXL Function Map; treating same-scope functions as CXL\n");
> + bitmap_zero(map, CXL_RESET_MAX_FUNCTIONS);
> + return;
> + }
> + }
> +
> + bitmap_from_arr32(map, words, CXL_RESET_MAX_FUNCTIONS);
> +}
> +
> +static int cxl_reset_func_map_bit(struct pci_dev *sibling, bool ari)
> +{
> + if (ari)
> + return sibling->devfn;
> +
> + /*
> + * Without ARI, the Function Map is organized as 32 device slots per
> + * conventional 3-bit function number.
> + */
> + return PCI_FUNC(sibling->devfn) * 32 + PCI_SLOT(sibling->devfn);
> +}
> +
> +static int cxl_reset_read_cxl_cap(struct pci_dev *pdev, u16 *cap)
> +{
> + int dvsec, rc;
> +
> + dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
> + PCI_DVSEC_CXL_DEVICE);
> + if (!dvsec)
> + return -ENODEV;
> +
> + rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, cap);
> + if (rc) {
> + rc = pcibios_err_to_errno(rc);
> + pci_warn(pdev, "failed to read CXL capability: %d\n", rc);
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int cxl_reset_has_cache_or_mem(struct pci_dev *pdev)
> +{
> + u16 cap;
> + int rc;
> +
> + rc = cxl_reset_read_cxl_cap(pdev, &cap);
> + if (rc == -ENODEV)
> + return 0;
> + if (rc)
> + return rc;
> +
> + return !!(cap & (PCI_DVSEC_CXL_CACHE_CAPABLE |
> + PCI_DVSEC_CXL_MEM_CAPABLE));
> +}
> +
> +static int cxl_reset_validate_function_scope(struct pci_dev *sibling,
> + void *data)
> +{
> + struct cxl_reset_walk_context *wctx = data;
> + struct cxl_reset_context *ctx = wctx->ctx;
> + struct pci_dev *pdev = ctx->target;
> + int fn, rc;
> +
> + if (sibling == pdev)
> + return 0;
> +
> + if (sibling->bus != pdev->bus)
> + return 0;
> +
> + if (!wctx->ari && PCI_SLOT(sibling->devfn) != PCI_SLOT(pdev->devfn))
> + return 0;
> +
> + fn = cxl_reset_func_map_bit(sibling, wctx->ari);
> + if (test_bit(fn, wctx->non_cxl_func_map))
> + return 0;
> +
> + rc = cxl_reset_has_cache_or_mem(sibling);
> + if (rc < 0) {
> + wctx->rc = rc;
> + return rc;
> + }
> + if (!rc)
> + return 0;
> +

cxl_reset_has_cache_or_mem() treats a sibling with no CXL device DVSEC as non-CXL, but according to
spec, it places the DVSEC only on Function 0, the sibling function can participate under fn0's DVSEC
with no DVSEC of their own. The non-CXL Function Map should be authoritative for classifying siblings
rather than a per-sibling DVSEC read.

What do you think ?

--Richard

> + wctx->rc = -ENOTTY;
> + return wctx->rc;
> +}
> +
> +static int cxl_reset_validate_function_scoped(struct cxl_reset_context *ctx)
> +{
> + struct pci_dev *pdev = ctx->target;
> + struct cxl_reset_walk_context wctx = {
> + .ctx = ctx,
> + .ari = pci_ari_enabled(pdev->bus),
> + };
> +
> + cxl_reset_read_non_cxl_func_map(pdev, wctx.non_cxl_func_map);
> + pci_walk_bus(pdev->bus, cxl_reset_validate_function_scope, &wctx);
> +
> + return wctx.rc;
> +}
> +
> static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
> {
> INIT_LIST_HEAD(&ctx->ranges);
> @@ -890,6 +1037,7 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
> int cxl_reset_function(struct pci_dev *pdev, bool probe)
> {
> struct cxl_hdm_range_context range_ctx;
> + struct cxl_reset_context ctx;
> int dvsec;
> int rc;
>
> @@ -897,8 +1045,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
> if (dvsec < 0)
> return dvsec;
>
> + cxl_reset_context_init(&ctx, pdev);
> if (probe)
> - return 0;
> + return cxl_reset_validate_function_scoped(&ctx);
>

This only runs on the PCI_RESET_PROBE path, but pci_init_reset_methods() runs during pci_device_add(),
before sibling functions of a multi-function device are on pdev->bus.
Thus pci_walk_bus() at probe time walks on an incomplete sibling set and passes, the DO_RESET path never
re-validates.
A multi-function Type-2 device would advertise and perform cxl_reset even with a live CXL sibling.
Can the scope be recheck at reset time or gate on siblings actually being present ?


> cxl_hdm_range_context_init(&range_ctx);
>
> @@ -907,7 +1056,7 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
> if (!rc)
> rc = cxl_reset_execute(pdev, dvsec);
> }
> -
> +out:
> cxl_hdm_range_context_destroy(&range_ctx);
> return rc;
> }
> diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
> index 194ae56b4404..7fc1d34fcce7 100644
> --- a/include/uapi/linux/pci_regs.h
> +++ b/include/uapi/linux/pci_regs.h
> @@ -1380,6 +1380,7 @@
>
> /* CXL r4.0, 8.1.4: Non-CXL Function Map DVSEC */
> #define PCI_DVSEC_CXL_FUNCTION_MAP 2
> +#define PCI_DVSEC_CXL_FUNCTION_MAP_REG 0x0C
>
> /* CXL r4.0, 8.1.5: Extensions DVSEC for Ports */
> #define PCI_DVSEC_CXL_PORT 3
> --
> 2.43.0
>