Linux Documentation
 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 v6 26/31] vfio/cxl: Restore the HDM decoder after a hot reset
Date: Fri, 16 Oct 2026 21:48:33 +0530	[thread overview]
Message-ID: <20261016161839.2765592-27-mhonap@nvidia.com> (raw)
In-Reply-To: <20261016161839.2765592-1-mhonap@nvidia.com>

From: Manish Honap <mhonap@nvidia.com>

The HDM memory fault inserts a PFN only when the decoder is in a
known-good state (cxl->hdm_valid), so the host CPU never faults the
coherent range into a disabled decoder.

VFIO_DEVICE_PCI_HOT_RESET drives a plain secondary bus reset rather than
the CXL reset sequence. A secondary bus reset only reaches a CXL
endpoint when the upstream port has SBR unmasked (CXL r4.0 sec 8.1.5.2).
With SBR masked the PCI core now refuses the bus reset, so the hot reset
fails instead of doing nothing. With SBR unmasked the reset is a Hot
Reset, which returns the decoder's RWL fields, Commit included, to their
defaults and decommits it (CXL r4.0 sec 8.2.4.20.7, Table 8-1).

Take the HDM memory region out of use in reset_prepare. In reset_done,
when the port has SBR unmasked and the decoder is no longer committed,
restore it with cxl_restore_state_after_pci_reset(), the helper the PCI
core's cxl_bus reset method uses, and put the region back. A decoder
that is still committed, after an FLR or a cxl_bus reset that restored
it itself, is left alone. If the restore fails, reset_done returns the
error, the hot reset ioctl fails with it, and the region stays out of
use until a reset succeeds. reset_done also runs after a reset method
that failed and after resets that do not reach the decoder; then put the
region back only if it was in use when reset_prepare ran. Before putting
the region back, reset_done waits for Memory_Active as power_up does, and
returns -ETIMEDOUT if the memory does not come back.

AER and DPC recovery can reset the link without reset_done. The HDM
region is taken out of use when the error is reported and put back when
recovery resumes the device only if it was in use before and the decoder
is still committed; recovery does not restore config space, so otherwise
the region stays out of use until the user resets the device. The
resume path also waits for Memory_Active before it puts the region back.

vfio_cxl_sbr_unmasked() mirrors the cxl_sbr_masked() check the PCI core
uses (drivers/pci/pci.c) for its own CXL bus reset. power_up now also
skips the restore when the decoder is still committed, as after a D3
request that left the device in D0.

Assisted-by: LLM
Signed-off-by: Manish Honap <mhonap@nvidia.com>
---
 drivers/vfio/pci/cxl/vfio_cxl_core.c | 156 +++++++++++++++++++++++++--
 1 file changed, 147 insertions(+), 9 deletions(-)

diff --git a/drivers/vfio/pci/cxl/vfio_cxl_core.c b/drivers/vfio/pci/cxl/vfio_cxl_core.c
index 68e898ee1985..3cbb9fbc8328 100644
--- a/drivers/vfio/pci/cxl/vfio_cxl_core.c
+++ b/drivers/vfio/pci/cxl/vfio_cxl_core.c
@@ -18,6 +18,7 @@
 #include <linux/uaccess.h>
 #include <linux/vfio_pci_core.h>
 #include <cxl/cxl.h>
+#include <cxl/cxl_regs.h>
 #include <cxl/hdm.h>
 #include <cxl/pci.h>
 
@@ -35,6 +36,7 @@
  * @await_mem_active: the device reported its memory active at bind, so wait
  *	for Memory_Active before host access reopens after a reset or low power
  * @mem_region_index: vfio region index of the HDM memory region while open, else 0
+ * @hdm_open_at_prepare: @hdm_valid as reset_prepare found it; under memory_lock
  */
 struct vfio_cxl_state {
 	struct cxl_dev_state cxlds;
@@ -48,6 +50,7 @@ struct vfio_cxl_state {
 	bool hdm_valid;
 	bool await_mem_active;
 	unsigned int mem_region_index;
+	bool hdm_open_at_prepare;
 };
 
 static unsigned long vfio_cxl_mem_pgoff(struct vm_area_struct *vma,
@@ -684,15 +687,6 @@ static void vfio_cxl_close_device(struct vfio_pci_core_device *vdev)
 	cxl->mem_region_index = 0;
 }
 
-static void vfio_cxl_reset_prepare(struct vfio_pci_core_device *vdev)
-{
-}
-
-static int vfio_cxl_reset_done(struct vfio_pci_core_device *vdev)
-{
-	return 0;
-}
-
 /*
  * Take the HDM memory region out of use: zap its mmaps and revoke its
  * dma-bufs. A fault then gets SIGBUS from vfio_cxl_mem_huge_fault() until
@@ -756,6 +750,33 @@ static int vfio_cxl_reset(struct vfio_pci_core_device *vdev)
 	return pci_cxl_reset_owned(vdev->pdev, vdev->cxl->hpa_res);
 }
 
+/*
+ * Report whether the device still decodes its HDM range: memory decode on,
+ * CXL.mem enabled in the CXL Device DVSEC and the decoder committed. A
+ * secondary bus reset or a link down clears all three. Config space is read
+ * first; the decoder registers sit in a BAR and are read only while memory
+ * decode is on.
+ */
+static bool vfio_cxl_hdm_intact(struct vfio_pci_core_device *vdev)
+{
+	struct vfio_cxl_state *cxl = vdev->cxl;
+	struct pci_dev *pdev = vdev->pdev;
+	u16 cmd, ctrl;
+	u32 dctrl;
+
+	if (pci_read_config_word(pdev, PCI_COMMAND, &cmd) ||
+	    PCI_POSSIBLE_ERROR(cmd) || !(cmd & PCI_COMMAND_MEMORY))
+		return false;
+
+	if (pci_read_config_word(pdev, cxl->hdm->dvsec + PCI_DVSEC_CXL_CTRL,
+				 &ctrl) ||
+	    PCI_POSSIBLE_ERROR(ctrl) || !(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
+		return false;
+
+	dctrl = readl(cxl->hdm_regs + CXL_HDM_DECODER0_CTRL_OFFSET(0));
+	return dctrl != U32_MAX && (dctrl & CXL_HDM_DECODER0_CTRL_COMMITTED);
+}
+
 /* Low power loses the committed decoder: close host access until restored. */
 static void vfio_cxl_power_down(struct vfio_pci_core_device *vdev)
 {
@@ -770,6 +791,16 @@ static void vfio_cxl_power_up(struct vfio_pci_core_device *vdev)
 
 	lockdep_assert_held_write(&vdev->memory_lock);
 
+	/*
+	 * A D3 request that failed, or a quirk that kept the device in D0,
+	 * lost nothing: reopen without decommitting and recommitting.
+	 */
+	if (vfio_cxl_hdm_intact(vdev)) {
+		if (!vfio_cxl_await_mem_active(vdev))
+			vfio_cxl_rearm_hdm(vdev);
+		return;
+	}
+
 	/*
 	 * Restore the decoder that low power lost. The restore needs the device
 	 * lock; if it is contended, keep host access closed until a reset
@@ -793,6 +824,112 @@ static void vfio_cxl_power_up(struct vfio_pci_core_device *vdev)
 	}
 }
 
+/* A PCI core reset may clear the decoder: take the HDM region out of use. */
+static void vfio_cxl_reset_prepare(struct vfio_pci_core_device *vdev)
+{
+	vdev->cxl->hdm_open_at_prepare = vdev->cxl->hdm_valid;
+	vfio_cxl_revoke_hdm(vdev);
+}
+
+/*
+ * A secondary bus reset only reaches a CXL endpoint when the upstream port
+ * has SBR unmasked (CXL r4.0 sec 8.1.5.2); otherwise the Bridge Control SBR
+ * bit is ignored and the decoder is left intact. Mirrors the cxl_sbr_masked()
+ * check the PCI core uses for its own CXL bus reset.
+ */
+static bool vfio_cxl_sbr_unmasked(struct pci_dev *pdev)
+{
+	struct pci_dev *bridge = pci_upstream_bridge(pdev);
+	u16 dvsec, ctl;
+
+	if (!bridge)
+		return false;
+
+	dvsec = pci_find_dvsec_capability(bridge, PCI_VENDOR_ID_CXL,
+					  PCI_DVSEC_CXL_PORT);
+	if (!dvsec)
+		return false;
+
+	if (pci_read_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL, &ctl))
+		return false;
+
+	return ctl & PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR;
+}
+
+static int vfio_cxl_reset_done(struct vfio_pci_core_device *vdev)
+{
+	struct vfio_cxl_state *cxl = vdev->cxl;
+	struct pci_dev *pdev = vdev->pdev;
+	int ret;
+
+	lockdep_assert_held_write(&vdev->memory_lock);
+
+	/*
+	 * VFIO_DEVICE_PCI_HOT_RESET drives a plain secondary bus reset, not the
+	 * CXL-aware cxl_reset_bus_function(). When the upstream port has SBR
+	 * unmasked the reset decommits the decoder. The PCI core has restored
+	 * config space by now; restore the decoder with the same helper its
+	 * cxl_bus reset method uses. A masked port refuses the bus reset. Other
+	 * resets (FLR, the cxl_reset and cxl_bus methods) leave the decoder
+	 * committed or restore it themselves; restore only a decoder that is
+	 * gone, so a committed one is not decommitted and committed again.
+	 */
+	if (vfio_cxl_sbr_unmasked(pdev) && !vfio_cxl_hdm_intact(vdev)) {
+		ret = pci_save_state(pdev);
+		if (!ret)
+			ret = cxl_restore_state_after_pci_reset(pdev, cxl->hdm);
+		if (ret) {
+			pci_warn(pdev, "vfio-cxl: HDM decoder restore failed: %d\n",
+				 ret);
+			vfio_cxl_sync_mem_enable(vdev);
+			return ret;
+		}
+	} else if (!cxl->hdm_open_at_prepare) {
+		/*
+		 * reset_done runs even when the reset method failed. Nothing
+		 * restored the decoder, and it was not known good before this
+		 * reset (a failed reset or a skipped restore closed the gate),
+		 * so keep host access closed.
+		 */
+		return 0;
+	}
+
+	ret = vfio_cxl_await_mem_active(vdev);
+	if (ret)
+		return ret;
+
+	vfio_cxl_rearm_hdm(vdev);
+	return 0;
+}
+
+/*
+ * AER or DPC recovery resumes the device without restoring its config space,
+ * so do not restore the decoder here. Put the HDM region back only if the
+ * device still decodes it; otherwise the user's reset restores it.
+ */
+static void vfio_cxl_error_resume(struct vfio_pci_core_device *vdev)
+{
+	struct pci_dev *pdev = vdev->pdev;
+
+	lockdep_assert_held_write(&vdev->memory_lock);
+
+	/* Low power closed the region; vfio_cxl_power_up() reopens it. */
+	if (pdev->current_state != PCI_D0)
+		return;
+
+	/* The region was closed before the error; keep it that way. */
+	if (!vdev->cxl->hdm_open_at_prepare)
+		return;
+
+	if (!vfio_cxl_hdm_intact(vdev)) {
+		pci_warn(pdev, "vfio-cxl: HDM decoder lost in error recovery, reset the device to restore it\n");
+		return;
+	}
+
+	if (!vfio_cxl_await_mem_active(vdev))
+		vfio_cxl_rearm_hdm(vdev);
+}
+
 static const struct vfio_cxl_ops vfio_cxl_ops = {
 	.init		= vfio_cxl_init_device,
 	.release	= vfio_cxl_release_device,
@@ -800,6 +937,7 @@ static const struct vfio_cxl_ops vfio_cxl_ops = {
 	.close_device	= vfio_cxl_close_device,
 	.reset_prepare	= vfio_cxl_reset_prepare,
 	.reset_done	= vfio_cxl_reset_done,
+	.error_resume	= vfio_cxl_error_resume,
 	.reset		= vfio_cxl_reset,
 	.power_down	= vfio_cxl_power_down,
 	.power_up	= vfio_cxl_power_up,
-- 
2.53.0


  parent reply	other threads:[~2026-10-16 16:23 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-16 16:18 [PATCH v6 00/31] vfio/pci: Add CXL Type-2 device passthrough support mhonap
2026-10-16 16:18 ` [PATCH v6 01/31] cxl/regs: Split the BAR block request and ioremap helpers mhonap
2026-10-16 16:18 ` [PATCH v6 02/31] cxl/regs: Let a BAR-owning driver own the component register block mhonap
2026-10-16 16:18 ` [PATCH v6 03/31] cxl: Move component register defines to uapi/cxl/cxl_regs.h mhonap
2026-10-16 16:18 ` [PATCH v6 04/31] PCI/CXL: Let the driver that owns the HDM range run CXL Reset mhonap
2026-10-16 16:18 ` [PATCH v6 05/31] PCI: Refuse bus and slot reset below a CXL port with SBR masked mhonap
2026-10-16 16:18 ` [PATCH v6 06/31] vfio/pci: Add the CXL provider ops registration interface mhonap
2026-10-16 16:18 ` [PATCH v6 07/31] vfio/pci: Detect CXL devices and load the CXL provider on demand mhonap
2026-10-16 16:18 ` [PATCH v6 08/31] vfio/pci: Add a generic excluded-range list mhonap
2026-10-16 16:18 ` [PATCH v6 09/31] vfio/pci: Advertise the mmap areas around excluded ranges mhonap
2026-10-16 16:18 ` [PATCH v6 10/31] vfio/pci: Refuse ioeventfds and dma-buf exports over " mhonap
2026-10-16 16:18 ` [PATCH v6 11/31] vfio/pci: Virtualize the CXL DVSEC in vfio_pci_config.c mhonap
2026-10-16 16:18 ` [PATCH v6 12/31] vfio/pci: Call the CXL open and close hooks around device use mhonap
2026-10-16 16:18 ` [PATCH v6 13/31] vfio/pci: Bracket PCI resets with the CXL reset hooks mhonap
2026-10-16 16:18 ` [PATCH v6 14/31] vfio/pci: Provide an opt-out for the CXL Type-2 extensions mhonap
2026-10-16 16:18 ` [PATCH v6 15/31] vfio/pci: Route every reset point through a try-reset helper mhonap
2026-10-16 16:18 ` [PATCH v6 16/31] vfio/pci: Revoke and re-arm one device-specific region mhonap
2026-10-16 16:18 ` [PATCH v6 17/31] vfio/pci: Call the device provider across low-power transitions mhonap
2026-10-16 16:18 ` [PATCH v6 18/31] vfio/cxl: Add the vfio-cxl provider module skeleton mhonap
2026-10-16 16:18 ` [PATCH v6 19/31] vfio/cxl: Create the CXL memdev and set media ready at bind mhonap
2026-10-16 16:18 ` [PATCH v6 20/31] vfio/cxl: Own the whole component register BAR mhonap
2026-10-16 16:18 ` [PATCH v6 21/31] vfio/cxl: Expose the HDM memory region to the guest mhonap
2026-10-16 16:18 ` [PATCH v6 22/31] vfio/cxl: Allow mmap of the HDM memory region mhonap
2026-10-16 16:18 ` [PATCH v6 23/31] vfio/cxl: Contain HDM memory errors with memory_failure() mhonap
2026-10-16 16:18 ` [PATCH v6 24/31] vfio/cxl: Expose the HDM decoder registers read-only to the guest mhonap
2026-10-16 16:18 ` [PATCH v6 25/31] vfio/cxl: Exclude the HDM decoder registers from direct BAR access mhonap
2026-10-16 16:18 ` mhonap [this message]
2026-10-16 16:18 ` [PATCH v6 27/31] vfio/cxl: Describe the CXL device and decoder geometry to userspace mhonap
2026-10-16 16:18 ` [PATCH v6 28/31] vfio/cxl: Export the HDM memory region as a dma-buf mhonap
2026-10-16 16:18 ` [PATCH v6 29/31] vfio/cxl: Run the CXL reset at the vfio reset points mhonap
2026-10-16 16:18 ` [PATCH v6 30/31] Documentation: vfio-pci: Document CXL Type-2 device passthrough mhonap
2026-10-16 16:18 ` [PATCH v6 31/31] selftests/vfio: Add CXL Type-2 passthrough tests mhonap

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=20261016161839.2765592-27-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