From: Manish Honap <[email protected]>

The BARs of a vfio PCI device follow the memory enable bit: clearing it
zaps their mmaps and revokes their dma-bufs, and setting it re-arms the
dma-bufs. A device-specific region does not have to follow that bit. The
HDM memory region of a CXL Type-2 device follows the committed HDM
decoder, which changes across reset, bus reset and low power, not with
Memory Space Enable. Other device-specific regions, such as nvgrace
coherent memory or the IGD OpRegion, do not depend on it either.

Record the region index of each dma-buf at export. vfio_pci_dma_buf_move()
now moves only the BAR dma-bufs, and vfio_pci_dma_buf_cleanup() still
drains every dma-buf of the device.

Export vfio_pci_core_revoke_region(), which zaps the user mmaps of one
device-specific region and revokes its dma-bufs, and
vfio_pci_core_rearm_region(), which re-arms them. A provider calls them
under memory_lock when the state its region depends on changes. Also
export __vfio_pci_vmf_insert_pfn(), the pfn insert of
vfio_pci_vmf_insert_pfn() without its memory enable and runtime PM
checks, for the fault handler of such a region. Export
vfio_pci_core_clear_memory_enable() for a provider whose failed
operation left Memory Space off in the device: it zaps the BAR mmaps,
revokes the BAR dma-bufs and clears vfio's record of the bit.

Suggested-by: Alex Williamson <[email protected]>
Assisted-by: LLM
Signed-off-by: Manish Honap <[email protected]>
---
 drivers/vfio/pci/vfio_pci_core.c   | 109 +++++++++++++++++++++++++++--
 drivers/vfio/pci/vfio_pci_dmabuf.c |  98 +++++++++++++++++---------
 drivers/vfio/pci/vfio_pci_priv.h   |   7 ++
 include/linux/vfio_pci_core.h      |   8 +++
 4 files changed, 180 insertions(+), 42 deletions(-)

diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 6676c561465b..9d0c22a4f3c6 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -1982,6 +1982,77 @@ static void vfio_pci_zap_bars(struct 
vfio_pci_core_device *vdev)
        unmap_mapping_range(core_vdev->inode->i_mapping, start, len, true);
 }
 
+/**
+ * vfio_pci_core_revoke_region() - take a device-specific region out of use
+ * @vdev: vfio PCI device, with memory_lock held for write
+ * @index: vfio region index of a device-specific region
+ *
+ * Zap the user mmaps of the region and revoke its dma-bufs, for a provider
+ * whose region depends on device state that is about to change. The BARs
+ * follow the memory enable bit instead; a device-specific region may not.
+ * A later fault on the region goes back to the region's own fault handler.
+ */
+void vfio_pci_core_revoke_region(struct vfio_pci_core_device *vdev,
+                                unsigned int index)
+{
+       struct vfio_pci_region *region;
+
+       lockdep_assert_held_write(&vdev->memory_lock);
+
+       if (WARN_ON(index < VFIO_PCI_NUM_REGIONS ||
+                   index - VFIO_PCI_NUM_REGIONS >= vdev->num_regions))
+               return;
+
+       region = &vdev->region[index - VFIO_PCI_NUM_REGIONS];
+       unmap_mapping_range(vdev->vdev.inode->i_mapping,
+                           VFIO_PCI_INDEX_TO_OFFSET(index), region->size, 
true);
+       vfio_pci_dma_buf_move_region(vdev, index, true);
+}
+EXPORT_SYMBOL_GPL(vfio_pci_core_revoke_region);
+
+/**
+ * vfio_pci_core_rearm_region() - put a revoked device-specific region back
+ * @vdev: vfio PCI device, with memory_lock held for write
+ * @index: vfio region index passed to vfio_pci_core_revoke_region()
+ */
+void vfio_pci_core_rearm_region(struct vfio_pci_core_device *vdev,
+                               unsigned int index)
+{
+       lockdep_assert_held_write(&vdev->memory_lock);
+
+       vfio_pci_dma_buf_move_region(vdev, index, false);
+}
+EXPORT_SYMBOL_GPL(vfio_pci_core_rearm_region);
+
+/**
+ * vfio_pci_core_clear_memory_enable() - follow a device that lost Memory Space
+ * @vdev: vfio PCI device, with memory_lock held for write
+ *
+ * For a provider whose failed operation left Memory Space disabled in the
+ * device. Zap the BAR mmaps, revoke the BAR dma-bufs and clear vfio's record
+ * of the Memory Space bit, as a user write that clears the bit would. The
+ * user's next Memory Space write enables it again.
+ */
+void vfio_pci_core_clear_memory_enable(struct vfio_pci_core_device *vdev)
+{
+       __le16 *virt_cmd;
+
+       lockdep_assert_held_write(&vdev->memory_lock);
+
+       /* Closed, or not open yet: vfio_config_init() reads the device. */
+       if (!vdev->vconfig)
+               return;
+
+       virt_cmd = (__le16 *)&vdev->vconfig[PCI_COMMAND];
+       if (!(*virt_cmd & cpu_to_le16(PCI_COMMAND_MEMORY)))
+               return;
+
+       vfio_pci_zap_bars(vdev);
+       vfio_pci_dma_buf_move(vdev, true);
+       *virt_cmd &= cpu_to_le16(~PCI_COMMAND_MEMORY);
+}
+EXPORT_SYMBOL_GPL(vfio_pci_core_clear_memory_enable);
+
 void vfio_pci_zap_and_down_write_memory_lock(struct vfio_pci_core_device *vdev)
 {
        down_write(&vdev->memory_lock);
@@ -2019,16 +2090,26 @@ static unsigned long vma_to_pfn(struct vm_area_struct 
*vma)
        return (pci_resource_start(vdev->pdev, index) >> PAGE_SHIFT) + pgoff;
 }
 
-vm_fault_t vfio_pci_vmf_insert_pfn(struct vfio_pci_core_device *vdev,
-                                  struct vm_fault *vmf,
-                                  unsigned long pfn,
-                                  unsigned int order)
+/**
+ * __vfio_pci_vmf_insert_pfn() - insert a pfn without the memory enable and
+ *     runtime PM checks
+ * @vdev: vfio PCI device, with memory_lock held for read
+ * @vmf: the fault being served
+ * @pfn: first pfn to insert
+ * @order: page order of the mapping
+ *
+ * For a device-specific region that does not follow the memory enable bit.
+ * The caller checks the state its region depends on under memory_lock, and
+ * revokes the region with vfio_pci_core_revoke_region() when it changes or
+ * before the device leaves D0.
+ */
+vm_fault_t __vfio_pci_vmf_insert_pfn(struct vfio_pci_core_device *vdev,
+                                    struct vm_fault *vmf,
+                                    unsigned long pfn,
+                                    unsigned int order)
 {
        lockdep_assert_held_read(&vdev->memory_lock);
 
-       if (vdev->pm_runtime_engaged || !__vfio_pci_memory_enabled(vdev))
-               return VM_FAULT_SIGBUS;
-
        if (!order)
                return vmf_insert_pfn(vmf->vma, vmf->address, pfn);
 
@@ -2040,6 +2121,20 @@ vm_fault_t vfio_pci_vmf_insert_pfn(struct 
vfio_pci_core_device *vdev,
 
        return VM_FAULT_FALLBACK;
 }
+EXPORT_SYMBOL_GPL(__vfio_pci_vmf_insert_pfn);
+
+vm_fault_t vfio_pci_vmf_insert_pfn(struct vfio_pci_core_device *vdev,
+                                  struct vm_fault *vmf,
+                                  unsigned long pfn,
+                                  unsigned int order)
+{
+       lockdep_assert_held_read(&vdev->memory_lock);
+
+       if (vdev->pm_runtime_engaged || !__vfio_pci_memory_enabled(vdev))
+               return VM_FAULT_SIGBUS;
+
+       return __vfio_pci_vmf_insert_pfn(vdev, vmf, pfn, order);
+}
 EXPORT_SYMBOL_GPL(vfio_pci_vmf_insert_pfn);
 
 static vm_fault_t vfio_pci_mmap_huge_fault(struct vm_fault *vmf,
diff --git a/drivers/vfio/pci/vfio_pci_dmabuf.c 
b/drivers/vfio/pci/vfio_pci_dmabuf.c
index 8606c15ab2fe..d0f7ec5487de 100644
--- a/drivers/vfio/pci/vfio_pci_dmabuf.c
+++ b/drivers/vfio/pci/vfio_pci_dmabuf.c
@@ -19,6 +19,7 @@ struct vfio_pci_dma_buf {
        u32 nr_ranges;
        struct kref kref;
        struct completion comp;
+       u32 region_index;
        u8 revoked : 1;
 };
 
@@ -288,6 +289,7 @@ int vfio_pci_core_feature_dma_buf(struct 
vfio_pci_core_device *vdev, u32 flags,
        priv->vdev = vdev;
        priv->nr_ranges = get_dma_buf.nr_ranges;
        priv->size = length;
+       priv->region_index = get_dma_buf.region_index;
        ret = vdev->pci_ops->get_dmabuf_phys(vdev, &priv->provider,
                                             get_dma_buf.region_index,
                                             priv->phys_vec, dma_ranges,
@@ -347,6 +349,49 @@ int vfio_pci_core_feature_dma_buf(struct 
vfio_pci_core_device *vdev, u32 flags,
        return ret;
 }
 
+static void vfio_pci_dma_buf_move_one(struct vfio_pci_dma_buf *priv,
+                                     bool revoked)
+{
+       if (!get_file_active(&priv->dmabuf->file))
+               return;
+
+       if (priv->revoked != revoked) {
+               dma_resv_lock(priv->dmabuf->resv, NULL);
+               if (revoked)
+                       priv->revoked = true;
+               dma_buf_invalidate_mappings(priv->dmabuf);
+               dma_resv_wait_timeout(priv->dmabuf->resv,
+                                     DMA_RESV_USAGE_BOOKKEEP, false,
+                                     MAX_SCHEDULE_TIMEOUT);
+               dma_resv_unlock(priv->dmabuf->resv);
+               if (revoked) {
+                       kref_put(&priv->kref, vfio_pci_dma_buf_done);
+                       wait_for_completion(&priv->comp);
+                       /*
+                        * Re-arm the registered kref reference and the
+                        * completion so the post-revoke state matches the
+                        * post-creation state.  An un-revoke followed by a
+                        * new mapping needs the kref to be non-zero before
+                        * kref_get(), and vfio_pci_dma_buf_cleanup()
+                        * delegates its drain back through this revoke
+                        * path on a possibly-already-revoked dma-buf.
+                        */
+                       kref_init(&priv->kref);
+                       reinit_completion(&priv->comp);
+               } else {
+                       dma_resv_lock(priv->dmabuf->resv, NULL);
+                       priv->revoked = false;
+                       dma_resv_unlock(priv->dmabuf->resv);
+               }
+       }
+       fput(priv->dmabuf->file);
+}
+
+/*
+ * Revoke or re-arm the dma-bufs of the BARs, which follow the memory enable
+ * bit. A device-specific region is moved by its provider through
+ * vfio_pci_dma_buf_move_region().
+ */
 void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked)
 {
        struct vfio_pci_dma_buf *priv;
@@ -354,41 +399,23 @@ void vfio_pci_dma_buf_move(struct vfio_pci_core_device 
*vdev, bool revoked)
 
        lockdep_assert_held_write(&vdev->memory_lock);
 
-       list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm) {
-               if (!get_file_active(&priv->dmabuf->file))
-                       continue;
+       list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm)
+               if (priv->region_index < VFIO_PCI_NUM_REGIONS)
+                       vfio_pci_dma_buf_move_one(priv, revoked);
+}
 
-               if (priv->revoked != revoked) {
-                       dma_resv_lock(priv->dmabuf->resv, NULL);
-                       if (revoked)
-                               priv->revoked = true;
-                       dma_buf_invalidate_mappings(priv->dmabuf);
-                       dma_resv_wait_timeout(priv->dmabuf->resv,
-                                             DMA_RESV_USAGE_BOOKKEEP, false,
-                                             MAX_SCHEDULE_TIMEOUT);
-                       dma_resv_unlock(priv->dmabuf->resv);
-                       if (revoked) {
-                               kref_put(&priv->kref, vfio_pci_dma_buf_done);
-                               wait_for_completion(&priv->comp);
-                               /*
-                                * Re-arm the registered kref reference and the
-                                * completion so the post-revoke state matches 
the
-                                * post-creation state.  An un-revoke followed 
by a
-                                * new mapping needs the kref to be non-zero 
before
-                                * kref_get(), and vfio_pci_dma_buf_cleanup()
-                                * delegates its drain back through this revoke
-                                * path on a possibly-already-revoked dma-buf.
-                                */
-                               kref_init(&priv->kref);
-                               reinit_completion(&priv->comp);
-                       } else {
-                               dma_resv_lock(priv->dmabuf->resv, NULL);
-                               priv->revoked = false;
-                               dma_resv_unlock(priv->dmabuf->resv);
-                       }
-               }
-               fput(priv->dmabuf->file);
-       }
+/* Revoke or re-arm the dma-bufs exported from one device-specific region. */
+void vfio_pci_dma_buf_move_region(struct vfio_pci_core_device *vdev,
+                                 unsigned int index, bool revoked)
+{
+       struct vfio_pci_dma_buf *priv;
+       struct vfio_pci_dma_buf *tmp;
+
+       lockdep_assert_held_write(&vdev->memory_lock);
+
+       list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm)
+               if (priv->region_index == index)
+                       vfio_pci_dma_buf_move_one(priv, revoked);
 }
 
 void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev)
@@ -405,7 +432,8 @@ void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device 
*vdev)
         * ready, so cleanup itself does not need to participate in kref
         * bookkeeping.
         */
-       vfio_pci_dma_buf_move(vdev, true);
+       list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm)
+               vfio_pci_dma_buf_move_one(priv, true);
 
        list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm) {
                if (!get_file_active(&priv->dmabuf->file))
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index dbede6e24fa9..23bccfbb7eb7 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -141,6 +141,8 @@ int vfio_pci_core_feature_dma_buf(struct 
vfio_pci_core_device *vdev, u32 flags,
                                  size_t argsz);
 void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev);
 void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked);
+void vfio_pci_dma_buf_move_region(struct vfio_pci_core_device *vdev,
+                                 unsigned int index, bool revoked);
 #else
 static inline int
 vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags,
@@ -156,6 +158,11 @@ static inline void vfio_pci_dma_buf_move(struct 
vfio_pci_core_device *vdev,
                                         bool revoked)
 {
 }
+static inline void
+vfio_pci_dma_buf_move_region(struct vfio_pci_core_device *vdev,
+                            unsigned int index, bool revoked)
+{
+}
 #endif
 
 #endif
diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
index cb9ba92b696a..7dd93ef103cb 100644
--- a/include/linux/vfio_pci_core.h
+++ b/include/linux/vfio_pci_core.h
@@ -227,6 +227,11 @@ int vfio_pci_core_register_dev_region(struct 
vfio_pci_core_device *vdev,
                                      unsigned int type, unsigned int subtype,
                                      const struct vfio_pci_regops *ops,
                                      size_t size, u32 flags, void *data);
+void vfio_pci_core_revoke_region(struct vfio_pci_core_device *vdev,
+                                unsigned int index);
+void vfio_pci_core_rearm_region(struct vfio_pci_core_device *vdev,
+                               unsigned int index);
+void vfio_pci_core_clear_memory_enable(struct vfio_pci_core_device *vdev);
 void vfio_pci_core_close_device(struct vfio_device *core_vdev);
 int vfio_pci_core_init_dev(struct vfio_device *core_vdev);
 void vfio_pci_core_release_dev(struct vfio_device *core_vdev);
@@ -250,6 +255,9 @@ ssize_t vfio_pci_core_read(struct vfio_device *core_vdev, 
char __user *buf,
                size_t count, loff_t *ppos);
 ssize_t vfio_pci_core_write(struct vfio_device *core_vdev, const char __user 
*buf,
                size_t count, loff_t *ppos);
+vm_fault_t __vfio_pci_vmf_insert_pfn(struct vfio_pci_core_device *vdev,
+                                    struct vm_fault *vmf, unsigned long pfn,
+                                    unsigned int order);
 vm_fault_t vfio_pci_vmf_insert_pfn(struct vfio_pci_core_device *vdev,
                                   struct vm_fault *vmf, unsigned long pfn,
                                   unsigned int order);
-- 
2.53.0


Reply via email to