From: Manish Honap <[email protected]>

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 <[email protected]>
---
 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


Reply via email to