Linux PCI subsystem development
 help / color / mirror / Atom feed
From: <mhonap@nvidia.com>
To: <alex@shazbot.org>, <jgg@ziepe.ca>, <ankita@nvidia.com>,
	<jic23@kernel.org>, <dave.jiang@intel.com>,
	<alejandro.lucero-palau@amd.com>, <smadhavan@nvidia.com>,
	<corbet@lwn.net>, <skhan@linuxfoundation.org>,
	<dave@stgolabs.net>, <alison.schofield@intel.com>,
	<vishal.l.verma@intel.com>, <iweiny@kernel.org>,
	<ming.li@zohomail.com>, <yishaih@nvidia.com>,
	<skolothumtho@nvidia.com>, <kevin.tian@intel.com>,
	<bhelgaas@google.com>, <dmatlack@google.com>, <kees@kernel.org>,
	<gustavoars@kernel.org>
Cc: <cjia@nvidia.com>, <kjaju@nvidia.com>, <vsethi@nvidia.com>,
	<zhiw@nvidia.com>, <mhonap@nvidia.com>,
	<linux-doc@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<kvm@vger.kernel.org>, <linux-cxl@vger.kernel.org>,
	<linux-pci@vger.kernel.org>, <linux-kselftest@vger.kernel.org>,
	<linux-hardening@vger.kernel.org>
Subject: [PATCH v4 24/27] vfio/cxl: Service a guest-triggered CXL reset
Date: Thu, 13 Aug 2026 15:06:28 +0530	[thread overview]
Message-ID: <20260813093631.2288172-25-mhonap@nvidia.com> (raw)
In-Reply-To: <20260813093631.2288172-1-mhonap@nvidia.com>

From: Manish Honap <mhonap@nvidia.com>

When the guest sets Initiate_CXL_Reset in the CXL DVSEC, run the reset on
its behalf. The bit is not forwarded to hardware; the CXL core drives the
state machine through cxl_reset_dvsec_sequence(). The memory_lock, mapping
revoke and dma-buf quiesce are core-internal, so the core exposes a
wrapper that runs them around the CXL-specific work.

A CXL reset can clear config like an FLR, so the sequence is bracketed by
pci_save_state() and pci_restore_state(), with Bus Master dropped for the
window and kept dropped if a step failed, so the function cannot DMA over
decoders that were not restored. The guest owns Mem_Clr_Enable, read from
the shadow, and the outcome is stamped into STATUS2 for it to poll.

Signed-off-by: Manish Honap <mhonap@nvidia.com>
---
 drivers/vfio/pci/cxl/vfio_cxl_core.c | 167 ++++++++++++++++++++-
 drivers/vfio/pci/vfio_pci_config.c   |   4 +-
 drivers/vfio/pci/vfio_pci_core.c     | 215 +++++++++++++++++++++++++--
 drivers/vfio/pci/vfio_pci_priv.h     |   2 +
 include/linux/vfio_pci_core.h        |   3 +
 5 files changed, 374 insertions(+), 17 deletions(-)

diff --git a/drivers/vfio/pci/cxl/vfio_cxl_core.c b/drivers/vfio/pci/cxl/vfio_cxl_core.c
index f45eaa60bad2..b3eaefa03479 100644
--- a/drivers/vfio/pci/cxl/vfio_cxl_core.c
+++ b/drivers/vfio/pci/cxl/vfio_cxl_core.c
@@ -7,6 +7,7 @@
 
 #include <linux/cleanup.h>
 #include <linux/memory-failure.h>
+#include <linux/memregion.h>
 #include <linux/mm.h>
 #include <linux/module.h>
 #include <linux/pci.h>
@@ -761,6 +762,16 @@ static u16 vfio_cxl_dvsec16(struct vfio_cxl_state *cxl, u32 off)
 	return (dw >> (8 * (off % sizeof(u32)))) & 0xffff;
 }
 
+/* Write a 16-bit DVSEC field into the shadow; the field must not straddle a dword. */
+static void vfio_cxl_dvsec_write16(struct vfio_cxl_state *cxl, u32 off, u16 val)
+{
+	u32 shift = 8 * (off % sizeof(u32));
+	u32 idx = off / sizeof(u32);
+
+	cxl->dvsec_shadow[idx] &= ~(0xffffU << shift);
+	cxl->dvsec_shadow[idx] |= (u32)val << shift;
+}
+
 /*
  * Apply the CXL r4.0 8.1.3 write class for the 16-bit DVSEC register at @off.
  * Control is programmable, Status is write-1-to-clear, and Capability, Lock and
@@ -815,12 +826,14 @@ static int vfio_cxl_config_write(struct vfio_pci_core_device *vdev, int pos,
 	int boff = (pos - cxl->dvsec) % sizeof(u32);
 	u32 off = idx * sizeof(u32);
 	__le32 le_wval = 0, le_wmask = 0;
+	u16 before, after, lo, hi;
 	u32 old, wval, wmask;
-	u16 lo, hi;
 
 	if (pos < cxl->dvsec || pos >= cxl->dvsec + cxl->dvsec_len)
 		return -ENODEV;
 
+	before = vfio_cxl_dvsec16(cxl, PCI_DVSEC_CXL_CTRL2);
+
 	/*
 	 * Place the guest bytes and a matching byte mask at the write offset,
 	 * then let the per-field class decide what actually lands in the shadow.
@@ -836,9 +849,159 @@ static int vfio_cxl_config_write(struct vfio_pci_core_device *vdev, int pos,
 	hi = vfio_cxl_dvsec_field(off + 2, old >> 16, wval >> 16, wmask >> 16);
 	cxl->dvsec_shadow[idx] = lo | ((u32)hi << 16);
 
+	/*
+	 * A 0->1 write of Initiate_CXL_Reset asks for a CXL reset. It is not
+	 * forwarded to hardware; cxl_reset_dvsec_sequence() drives the state
+	 * machine, and the outcome comes back through STATUS2.
+	 */
+	after = vfio_cxl_dvsec16(cxl, PCI_DVSEC_CXL_CTRL2);
+	if (!(before & PCI_DVSEC_CXL_INIT_CXL_RST) &&
+	    (after & PCI_DVSEC_CXL_INIT_CXL_RST) &&
+	    cxl_reset_capable(vdev->pdev))
+		vfio_pci_core_cxl_reset(vdev);
+
 	return count;
 }
 
+static int vfio_cxl_reset(struct vfio_pci_core_device *vdev)
+{
+	struct vfio_cxl_state *cxl = vdev->cxl;
+	struct pci_dev *pdev = vdev->pdev;
+	struct pci_saved_state *saved_state;
+	bool mem_clr;
+	u16 ctrl2, status2, cmd;
+	int ret;
+
+	lockdep_assert_held_write(&vdev->memory_lock);
+
+	/*
+	 * The host cxl_reset PCI method cannot run for a vfio-owned device: it
+	 * requests the HDM range that this driver already holds exclusively, so
+	 * it always fails busy. Drive the DVSEC reset directly here. Report
+	 * not-capable so the core reset path can fall back to a standard PCI
+	 * reset for a device with no CXL reset, such as a multifunction device.
+	 */
+	if (!cxl_reset_capable(pdev))
+		return -ENOTTY;
+
+	/*
+	 * Mem_Clr_En comes from the guest shadow CTRL2 while the device is open.
+	 * The core reset path also drives this reset at VM power on and off with
+	 * no shadow present, so read it from the live DVSEC then.
+	 */
+	if (cxl->dvsec_shadow)
+		ctrl2 = vfio_cxl_dvsec16(cxl, PCI_DVSEC_CXL_CTRL2);
+	else
+		pci_read_config_word(pdev, cxl->dvsec + PCI_DVSEC_CXL_CTRL2,
+				     &ctrl2);
+	mem_clr = ctrl2 & PCI_DVSEC_CXL_RST_MEM_CLR_EN;
+
+	/*
+	 * Mem_Clr is guest-controlled (Mem_Clr_En in the DVSEC CTRL2), so honor it
+	 * and pass it to cxl_reset_dvsec_sequence(). It zeroes the device memory,
+	 * but that sequence does not write back host CPU caches over the HDM range
+	 * the way the host cxl_reset method does. The range is mapped write-back,
+	 * so a dirty host line could survive the clear and overwrite it; invalidate
+	 * it first when the platform can.
+	 *
+	 * When cpu_cache_has_invalidate_memregion() is false proceed rather
+	 * than abort. Unlike native CXL region invalidation, which the host
+	 * manages and must fail safe, the host CPU never writes passthrough
+	 * HDM range: the guest owns it through its stage-2 mapping, so there
+	 * are no dirty host lines for the clear to lose.
+	 * Warn once so the case is visible rather than silent.
+	 */
+	if (mem_clr) {
+		if (cpu_cache_has_invalidate_memregion()) {
+			ret = cpu_cache_invalidate_memregion(cxl->hpa_range.start,
+							     range_len(&cxl->hpa_range));
+			if (ret) {
+				pci_err(pdev, "vfio-cxl: reset: CPU cache invalidate failed (%d), aborting reset\n",
+					ret);
+				return ret;
+			}
+		} else {
+			pci_warn_once(pdev, "vfio-cxl: reset: no CPU cache invalidation available; proceeding with Mem_Clr (host does not cache the HDM range)\n");
+		}
+	}
+
+	/*
+	 * A CXL reset can clear config like an FLR, so save state and drop Bus
+	 * Master for the reset window; the function masters the bus again only
+	 * once the decoders are restored.
+	 *
+	 * Sample the guest's Bus Master intent from live config before the reset
+	 * perturbs it, so it can be reapplied after the decoder is known good with
+	 * no window in which pci_restore_state() leaves Bus Master enabled.
+	 */
+	pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+
+	saved_state = pci_store_saved_state(pdev);
+	if (!saved_state && pdev->state_saved) {
+		pci_err(pdev, "vfio-cxl: reset: saved-state stash failed (-ENOMEM), aborting reset\n");
+		return -ENOMEM;
+	}
+	if (saved_state)
+		pci_save_state(pdev);
+	pci_clear_master(pdev);
+	ret = cxl_reset_dvsec_sequence(pdev, mem_clr);
+	pci_restore_state(pdev);
+	pci_clear_master(pdev);
+	/*
+	 * pci_restore_state() reinstated the guest's PCI_COMMAND, which may have
+	 * re-enabled Bus Master while the reset outcome, and thus decoder
+	 * validity, is not yet known. The pci_clear_master() above is its
+	 * immediate next statement, so there is no window in which a failed reset
+	 * could DMA over decoders that were not restored.
+	 */
+	if (saved_state)
+		pci_load_and_free_saved_state(pdev, &saved_state);
+	/*
+	 * Re-enable Bus Master only for a clean reset or -EBUSY (the reset never
+	 * ran, so the firmware-committed decoder is intact), and only if the guest
+	 * had it enabled (sampled before the reset).
+	 */
+	if ((!ret || ret == -EBUSY) && (cmd & PCI_COMMAND_MASTER))
+		pci_set_master(pdev);
+
+	vfio_cxl_post_reset(vdev);
+
+	/*
+	 * A clean reset restored the decoder, and -EBUSY means the reset never
+	 * ran so the firmware-committed decoder is intact: both are known-good.
+	 * Any other error may have left the decoder unrestored, so close the gate
+	 * until the next open or restore. Faults cannot race this: the caller
+	 * holds memory_lock for write across the whole reset.
+	 */
+	if (cxl->hdm_shadow) {
+		if (!ret || ret == -EBUSY)
+			cxl->hdm_valid = true;
+		else
+			cxl->hdm_valid = false;
+	}
+
+	/*
+	 * The guest-facing DVSEC bookkeeping only applies while the device is
+	 * open. Initiate_CXL_Reset self-clears in hardware; mirror that and
+	 * stamp the outcome onto a fresh hardware STATUS2 read for the polling
+	 * guest. A contended -EBUSY reports as an error so the guest can reissue
+	 * rather than poll a result that never comes.
+	 */
+	if (cxl->dvsec_shadow) {
+		vfio_cxl_dvsec_write16(cxl, PCI_DVSEC_CXL_CTRL2,
+				       vfio_cxl_dvsec16(cxl, PCI_DVSEC_CXL_CTRL2) &
+				       ~PCI_DVSEC_CXL_INIT_CXL_RST);
+
+		pci_read_config_word(pdev, cxl->dvsec + PCI_DVSEC_CXL_STATUS2,
+				     &status2);
+		status2 &= ~(PCI_DVSEC_CXL_RST_DONE | PCI_DVSEC_CXL_RST_ERR);
+		status2 |= ret ? PCI_DVSEC_CXL_RST_ERR : PCI_DVSEC_CXL_RST_DONE;
+		vfio_cxl_dvsec_write16(cxl, PCI_DVSEC_CXL_STATUS2, status2);
+	}
+
+	return ret;
+}
+
 static const struct vfio_cxl_ops vfio_cxl_ops = {
 	.init_device	= vfio_cxl_init_device,
 	.release_device	= vfio_cxl_release_device,
@@ -849,6 +1012,7 @@ static const struct vfio_cxl_ops vfio_cxl_ops = {
 	.zap		= vfio_cxl_zap,
 	.post_reset	= vfio_cxl_post_reset,
 	.pm_restore	= vfio_cxl_pm_restore,
+	.reset		= vfio_cxl_reset,
 	.owner		= THIS_MODULE,
 };
 
@@ -869,3 +1033,4 @@ MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("VFIO support for CXL Type-2 devices");
 MODULE_ALIAS("vfio-cxl");
 MODULE_IMPORT_NS("CXL");
+MODULE_IMPORT_NS("DEVMEM");
diff --git a/drivers/vfio/pci/vfio_pci_config.c b/drivers/vfio/pci/vfio_pci_config.c
index 01d808546a4c..f6509824988c 100644
--- a/drivers/vfio/pci/vfio_pci_config.c
+++ b/drivers/vfio/pci/vfio_pci_config.c
@@ -910,7 +910,7 @@ static int vfio_exp_config_write(struct vfio_pci_core_device *vdev, int pos,
 		if (!ret && (cap & PCI_EXP_DEVCAP_FLR)) {
 			vfio_pci_zap_and_down_write_memory_lock(vdev);
 			vfio_pci_dma_buf_move(vdev, true);
-			pci_try_reset_function(vdev->pdev);
+			vfio_pci_reset_function(vdev);
 			vfio_pci_cxl_post_reset(vdev);
 			if (__vfio_pci_memory_enabled(vdev))
 				vfio_pci_dma_buf_move(vdev, false);
@@ -996,7 +996,7 @@ static int vfio_af_config_write(struct vfio_pci_core_device *vdev, int pos,
 		if (!ret && (cap & PCI_AF_CAP_FLR) && (cap & PCI_AF_CAP_TP)) {
 			vfio_pci_zap_and_down_write_memory_lock(vdev);
 			vfio_pci_dma_buf_move(vdev, true);
-			pci_try_reset_function(vdev->pdev);
+			vfio_pci_reset_function(vdev);
 			vfio_pci_cxl_post_reset(vdev);
 			if (__vfio_pci_memory_enabled(vdev))
 				vfio_pci_dma_buf_move(vdev, false);
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index fc8235c8b4fc..0fed8e00bc1d 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -635,8 +635,26 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
 		goto out_power;
 
 	/* If reset fails because of the device lock, fail this path entirely */
-	ret = pci_try_reset_function(pdev);
-	if (ret == -EAGAIN)
+	if (vdev->cxl_ops && vdev->cxl_ops->reset) {
+		/*
+		 * VM power-on resets a CXL Type-2 device through its DVSEC
+		 * sequence. vconfig is not built yet here, so take memory_lock
+		 * and call the op directly rather than the wrapper.
+		 */
+		down_write(&vdev->memory_lock);
+		ret = vdev->cxl_ops->reset(vdev);
+		up_write(&vdev->memory_lock);
+	} else {
+		ret = pci_try_reset_function(pdev);
+	}
+	/*
+	 * -EAGAIN means the reset could not run. For a CXL device any reset
+	 * error must also fail the open: a failed DVSEC reset can leave the HDM
+	 * decoder cleared or unrestored, and continuing would expose the HDM
+	 * region for host access through a decoder in an unknown state.
+	 */
+	if (ret == -EAGAIN ||
+	    (vdev->cxl_ops && vdev->cxl_ops->reset && ret))
 		goto out_disable_device;
 
 	vdev->reset_works = !ret;
@@ -824,16 +842,30 @@ void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
 	 * overwrite the previously restored configuration information.
 	 */
 	if (vdev->reset_works) {
-		bridge = pci_upstream_bridge(pdev);
-		if (bridge && !pci_dev_trylock(bridge))
-			goto out_restore_state;
-		if (pci_dev_trylock(pdev)) {
-			if (!__pci_reset_function_locked(pdev))
+		if (vdev->cxl_ops && vdev->cxl_ops->reset) {
+			/*
+			 * VM power-off resets a CXL Type-2 device through its
+			 * DVSEC sequence. The sequence takes its own device lock,
+			 * so run it outside the lock below.
+			 * vconfig is already freed here, so call the op directly
+			 * under memory_lock rather than the wrapper.
+			 */
+			down_write(&vdev->memory_lock);
+			if (!vdev->cxl_ops->reset(vdev))
 				vdev->needs_reset = false;
-			pci_dev_unlock(pdev);
+			up_write(&vdev->memory_lock);
+		} else {
+			bridge = pci_upstream_bridge(pdev);
+			if (bridge && !pci_dev_trylock(bridge))
+				goto out_restore_state;
+			if (pci_dev_trylock(pdev)) {
+				if (!__pci_reset_function_locked(pdev))
+					vdev->needs_reset = false;
+				pci_dev_unlock(pdev);
+			}
+			if (bridge)
+				pci_dev_unlock(bridge);
 		}
-		if (bridge)
-			pci_dev_unlock(bridge);
 	}
 
 out_restore_state:
@@ -1441,6 +1473,20 @@ static int vfio_pci_ioctl_set_irqs(struct vfio_pci_core_device *vdev,
 	return ret;
 }
 
+/*
+ * Reset a function the way a guest asked for. A CXL Type-2 device resets
+ * through its DVSEC sequence: the host cxl_reset method would collide
+ * with the exclusive HDM range this driver holds and fail busy. Everything else
+ * takes a standard PCI function reset. The caller holds memory_lock, which the
+ * DVSEC sequence requires.
+ */
+int vfio_pci_reset_function(struct vfio_pci_core_device *vdev)
+{
+	if (vdev->cxl_ops && vdev->cxl_ops->reset)
+		return vdev->cxl_ops->reset(vdev);
+	return pci_try_reset_function(vdev->pdev);
+}
+
 static int vfio_pci_ioctl_reset(struct vfio_pci_core_device *vdev,
 				void __user *arg)
 {
@@ -1463,7 +1509,7 @@ static int vfio_pci_ioctl_reset(struct vfio_pci_core_device *vdev,
 	vfio_pci_set_power_state(vdev, PCI_D0);
 
 	vfio_pci_dma_buf_move(vdev, true);
-	ret = pci_try_reset_function(vdev->pdev);
+	ret = vfio_pci_reset_function(vdev);
 	vfio_pci_cxl_post_reset(vdev);
 	if (__vfio_pci_memory_enabled(vdev))
 		vfio_pci_dma_buf_move(vdev, false);
@@ -2736,6 +2782,21 @@ static int vfio_pci_dev_set_hot_reset(struct vfio_device_set *dev_set,
 		goto err_unlock;
 	}
 
+	/*
+	 * A multifunction CXL Type-2 device cannot be bus reset: its DVSEC
+	 * reset acts per function, so a shared secondary bus reset would reset
+	 * sibling functions out from under their own state. Reject it, matching
+	 * the cxl_reset bus method. A single-function device is quiesced through
+	 * its DVSEC sequence just before the reset below.
+	 */
+	list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list) {
+		if (vdev->cxl_ops && vdev->cxl_ops->reset &&
+		    vdev->pdev->multifunction) {
+			ret = -ENOTTY;
+			goto err_unlock;
+		}
+	}
+
 	/*
 	 * Some of the devices in the dev_set can be in the runtime suspended
 	 * state. Increment the usage count for all the devices in the dev_set
@@ -2817,11 +2878,57 @@ static int vfio_pci_dev_set_hot_reset(struct vfio_device_set *dev_set,
 	list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list)
 		vfio_pci_set_power_state(vdev, PCI_D0);
 
+	/*
+	 * Quiesce each CXL Type-2 device through its DVSEC sequence before the
+	 * secondary bus reset: the bus reset alone does not write back the
+	 * device cache or tear down the HDM decoders. memory_lock is held. If a
+	 * quiesce fails, abort before the bus reset: resetting an unquiesced CXL
+	 * device risks data loss or a fabric error. Every device is locked here,
+	 * so unwind from the last one.
+	 */
+	list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list) {
+		if (vdev->cxl_ops && vdev->cxl_ops->reset) {
+			ret = vdev->cxl_ops->reset(vdev);
+			if (ret) {
+				pci_warn(vdev->pdev, "vfio-cxl: hot reset: DVSEC quiesce failed (%d), aborting before bus reset\n",
+					 ret);
+				vdev = list_last_entry(&dev_set->device_list,
+						       struct vfio_pci_core_device,
+						       vdev.dev_set_list);
+				goto err_undo;
+			}
+		}
+	}
+
 	ret = pci_reset_bus(pdev);
 
-	/* Re-sample decoder state for any CXL device the bus reset touched. */
-	list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list)
-		vfio_pci_cxl_post_reset(vdev);
+	/*
+	 * pci_reset_bus() restored each device's PCI_COMMAND, which can re-enable
+	 * Bus Master, but the secondary bus reset cleared the physical HDM decoder
+	 * that the CXL quiesce above restored. Until it is restored a CXL device
+	 * could DMA over cleared decode, so for each CXL device drop Bus Master,
+	 * restore and re-sample the decoder, then re-enable Bus Master to the
+	 * guest's intent only once the decoder is known good. Re-sampling alone
+	 * would leave hdm_valid true over a cleared decoder. Keep the first restore
+	 * error so a failed restore is reported instead of the bus reset's success.
+	 */
+	list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list) {
+		u16 cmd;
+		int rret;
+
+		if (!(vdev->cxl_ops && vdev->cxl_ops->reset))
+			continue;
+
+		pci_read_config_word(vdev->pdev, PCI_COMMAND, &cmd);
+		pci_clear_master(vdev->pdev);
+		rret = vfio_pci_cxl_pm_restore(vdev);
+		if (rret) {
+			if (!ret)
+				ret = rret;
+		} else if (cmd & PCI_COMMAND_MASTER) {
+			pci_set_master(vdev->pdev);
+		}
+	}
 
 	vdev = list_last_entry(&dev_set->device_list,
 			       struct vfio_pci_core_device, vdev.dev_set_list);
@@ -2876,6 +2983,16 @@ static void vfio_pci_dev_set_try_reset(struct vfio_device_set *dev_set)
 	if (!pdev)
 		return;
 
+	/*
+	 * A multifunction CXL Type-2 device cannot be bus reset (its DVSEC
+	 * reset is per function), so skip the automatic reset rather than reset
+	 * sibling functions out from under their state.
+	 */
+	list_for_each_entry(cur, &dev_set->device_list, vdev.dev_set_list)
+		if (cur->cxl_ops && cur->cxl_ops->reset &&
+		    cur->pdev->multifunction)
+			return;
+
 	/*
 	 * Some of the devices in the bus can be in the runtime suspended
 	 * state. Increment the usage count for all the devices in the dev_set
@@ -2884,9 +3001,56 @@ static void vfio_pci_dev_set_try_reset(struct vfio_device_set *dev_set)
 	if (vfio_pci_dev_set_pm_runtime_get(dev_set))
 		return;
 
+	/*
+	 * Quiesce each CXL Type-2 device through its DVSEC sequence before the
+	 * bus reset, which alone does not write back the device cache or tear
+	 * down the HDM decoders. Take memory_lock and zap the HDM window as the
+	 * explicit hot reset does. On lock contention or a failed quiesce, skip
+	 * the bus reset and leave needs_reset set for a later retry.
+	 */
+	list_for_each_entry(cur, &dev_set->device_list, vdev.dev_set_list) {
+		if (!(cur->cxl_ops && cur->cxl_ops->reset))
+			continue;
+		if (!down_write_trylock(&cur->memory_lock))
+			goto unwind;
+		vfio_pci_cxl_zap(cur);
+		if (cur->cxl_ops->reset(cur)) {
+			up_write(&cur->memory_lock);
+			goto unwind;
+		}
+	}
+
 	if (!pci_reset_bus(pdev))
 		reset_done = true;
 
+	list_for_each_entry(cur, &dev_set->device_list, vdev.dev_set_list) {
+		u16 cmd;
+
+		if (!(cur->cxl_ops && cur->cxl_ops->reset))
+			continue;
+		/*
+		 * pci_reset_bus() restored PCI_COMMAND, which can re-enable Bus
+		 * Master, but the bus reset cleared the physical HDM decoder. Drop
+		 * Bus Master, restore and re-sample the decoder, then re-enable Bus
+		 * Master to the guest's intent once the decoder is known good.
+		 * Re-sampling alone would leave hdm_valid true over a cleared
+		 * decoder.
+		 */
+		pci_read_config_word(cur->pdev, PCI_COMMAND, &cmd);
+		pci_clear_master(cur->pdev);
+		if (!vfio_pci_cxl_pm_restore(cur) && (cmd & PCI_COMMAND_MASTER))
+			pci_set_master(cur->pdev);
+		up_write(&cur->memory_lock);
+	}
+	goto out;
+
+unwind:
+	list_for_each_entry_continue_reverse(cur, &dev_set->device_list,
+					     vdev.dev_set_list)
+		if (cur->cxl_ops && cur->cxl_ops->reset)
+			up_write(&cur->memory_lock);
+
+out:
 	list_for_each_entry(cur, &dev_set->device_list, vdev.dev_set_list) {
 		if (reset_done)
 			cur->needs_reset = false;
@@ -2919,6 +3083,29 @@ void vfio_pci_core_unregister_cxl_ops(const struct vfio_cxl_ops *ops)
 }
 EXPORT_SYMBOL_GPL(vfio_pci_core_unregister_cxl_ops);
 
+/*
+ * Drive a guest-requested CXL reset. The memory_lock, mapping revoke and
+ * dma-buf quiesce are core-internal, so vfio-cxl calls in here to run them
+ * around its DVSEC reset sequence.
+ */
+int vfio_pci_core_cxl_reset(struct vfio_pci_core_device *vdev)
+{
+	int ret;
+
+	if (!vdev->cxl_ops || !vdev->cxl_ops->reset)
+		return -ENOTTY;
+
+	vfio_pci_zap_and_down_write_memory_lock(vdev);
+	vfio_pci_dma_buf_move(vdev, true);
+	ret = vdev->cxl_ops->reset(vdev);
+	if (__vfio_pci_memory_enabled(vdev))
+		vfio_pci_dma_buf_move(vdev, false);
+	up_write(&vdev->memory_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(vfio_pci_core_cxl_reset);
+
 static void vfio_pci_core_cleanup(void)
 {
 	vfio_pci_uninit_perm_bits();
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index 46e67573d264..8055099cab03 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -102,6 +102,8 @@ static inline int vfio_pci_cxl_pm_restore(struct vfio_pci_core_device *vdev)
 	return 0;
 }
 
+int vfio_pci_reset_function(struct vfio_pci_core_device *vdev);
+
 u16 vfio_pci_memory_lock_and_enable(struct vfio_pci_core_device *vdev);
 void vfio_pci_memory_unlock_and_restore(struct vfio_pci_core_device *vdev,
 					u16 cmd);
diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
index c438d968dc59..18e206a35d8c 100644
--- a/include/linux/vfio_pci_core.h
+++ b/include/linux/vfio_pci_core.h
@@ -82,6 +82,8 @@ struct vfio_cxl_ops {
 	void    (*post_reset)(struct vfio_pci_core_device *vdev);
 	/* Restore the HDM decoder after a D3hot->D0 soft reset */
 	int     (*pm_restore)(struct vfio_pci_core_device *vdev);
+	/* Run the CXL reset sequence; the core holds memory_lock across it */
+	int     (*reset)(struct vfio_pci_core_device *vdev);
 
 	/* Pinned per bound CXL device so vfio-cxl cannot unload under usage */
 	struct module *owner;
@@ -89,6 +91,7 @@ struct vfio_cxl_ops {
 
 int vfio_pci_core_register_cxl_ops(const struct vfio_cxl_ops *ops);
 void vfio_pci_core_unregister_cxl_ops(const struct vfio_cxl_ops *ops);
+int vfio_pci_core_cxl_reset(struct vfio_pci_core_device *vdev);
 
 #if IS_ENABLED(CONFIG_VFIO_PCI_DMABUF)
 int vfio_pci_core_fill_phys_vec(struct phys_vec *phys_vec,
-- 
2.25.1


  parent reply	other threads:[~2026-08-13  9:41 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-13  9:36 [PATCH v4 00/27] vfio/pci: Add CXL Type-2 device passthrough support mhonap
2026-08-13  9:36 ` [PATCH v4 01/27] cxl: Fix resource.c include path and export cxl_restore_hdm_after_pci_reset mhonap
2026-08-21 22:52   ` Jonathan Cameron
2026-08-22  1:22     ` Manish Honap
2026-08-13  9:36 ` [PATCH v4 02/27] cxl/regs: Skip sub-block region request for BAR-owning drivers mhonap
2026-08-25 21:26   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 03/27] cxl: Move component register defines to uapi/cxl/cxl_regs.h mhonap
2026-08-13  9:36 ` [PATCH v4 04/27] cxl: Establish media readiness in cxl_mem_probe() mhonap
2026-08-25 22:18   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 05/27] cxl: Add a function-scoped reset entry for vfio-pci mhonap
2026-08-25 23:11   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 06/27] vfio/pci: Add CXL ops registration interface mhonap
2026-08-26 21:11   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 07/27] vfio/pci: Detect CXL devices and load vfio-cxl on demand mhonap
2026-08-26 22:17   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 08/27] vfio/cxl: Add the vfio-cxl module skeleton mhonap
2026-08-26 22:50   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 09/27] vfio/cxl: Create the CXL memory device at bind mhonap
2026-08-27 20:43   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 10/27] vfio/cxl: Reject unsupported decoder topologies " mhonap
2026-08-27 20:58   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 11/27] vfio/cxl: Own the whole component register BAR mhonap
2026-08-27 21:13   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 12/27] vfio/pci: Let a provider exclude a BAR sub-range from mmap mhonap
2026-08-27 22:37   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 13/27] vfio/pci: Refuse read/write to an excluded BAR sub-range mhonap
2026-08-13  9:36 ` [PATCH v4 14/27] vfio: Add CXL region type for the HDM region mhonap
2026-08-27 22:43   ` Alex Williamson
2026-08-13  9:36 ` [PATCH v4 15/27] vfio/pci: Call CXL open and close hooks around device use mhonap
2026-08-13  9:36 ` [PATCH v4 16/27] vfio/cxl: Shadow the CXL DVSEC body at open mhonap
2026-08-13  9:36 ` [PATCH v4 17/27] vfio/cxl: Virtualize the CXL DVSEC mhonap
2026-08-13  9:36 ` [PATCH v4 18/27] vfio/cxl: Expose the HDM memory and trap the decoder registers mhonap
2026-08-13  9:36 ` [PATCH v4 19/27] vfio/cxl: Keep the HDM decoder block off the direct BAR mapping mhonap
2026-08-13  9:36 ` [PATCH v4 20/27] vfio/cxl: Emulate the HDM decoder commit handshake mhonap
2026-08-13  9:36 ` [PATCH v4 21/27] vfio/cxl: Describe the CXL device and decoder geometry to userspace mhonap
2026-08-13  9:36 ` [PATCH v4 22/27] vfio/cxl: Revoke the HDM mapping on reset and power transitions mhonap
2026-08-13  9:36 ` [PATCH v4 23/27] vfio/cxl: Refresh the decoder snapshot after a device reset mhonap
2026-08-13  9:36 ` mhonap [this message]
2026-08-13  9:36 ` [PATCH v4 25/27] vfio/pci: Provide an opt-out for the CXL Type-2 extensions mhonap
2026-08-13  9:36 ` [PATCH v4 26/27] Documentation: vfio-pci: Document CXL Type-2 device passthrough mhonap
2026-08-13  9:36 ` [PATCH v4 27/27] selftests/vfio: Add CXL Type-2 passthrough corner-case tests mhonap
2026-08-26  7:28   ` Shuai Xue
2026-08-26 16:17     ` Manish Honap

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260813093631.2288172-25-mhonap@nvidia.com \
    --to=mhonap@nvidia.com \
    --cc=alejandro.lucero-palau@amd.com \
    --cc=alex@shazbot.org \
    --cc=alison.schofield@intel.com \
    --cc=ankita@nvidia.com \
    --cc=bhelgaas@google.com \
    --cc=cjia@nvidia.com \
    --cc=corbet@lwn.net \
    --cc=dave.jiang@intel.com \
    --cc=dave@stgolabs.net \
    --cc=dmatlack@google.com \
    --cc=gustavoars@kernel.org \
    --cc=iweiny@kernel.org \
    --cc=jgg@ziepe.ca \
    --cc=jic23@kernel.org \
    --cc=kees@kernel.org \
    --cc=kevin.tian@intel.com \
    --cc=kjaju@nvidia.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-cxl@vger.kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=ming.li@zohomail.com \
    --cc=skhan@linuxfoundation.org \
    --cc=skolothumtho@nvidia.com \
    --cc=smadhavan@nvidia.com \
    --cc=vishal.l.verma@intel.com \
    --cc=vsethi@nvidia.com \
    --cc=yishaih@nvidia.com \
    --cc=zhiw@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox