summaryrefslogtreecommitdiffstats
path: root/kernel/main.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 12:37:46 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 12:37:46 -0400
commit5d4d26f8e773f2871c1b5c9266b82495d390970e (patch)
tree544214bfabdcd76ee0b75de879e3798c6ab06334 /kernel/main.c
parent85084a8fc6a95dd88f81bbec31cffa6b4aba56b7 (diff)
downloadopen-iov-5d4d26f8e773f2871c1b5c9266b82495d390970e.tar.gz
open-iov-5d4d26f8e773f2871c1b5c9266b82495d390970e.zip
kernel: Fix guest-hang root cause (VFIO mmap garbage PFN) + rebuild mdev remove path
VFIO mmap (main.c): virt_to_phys() was used on vmalloc-family addresses (vzalloc'd BAR0 shadow, ioremap'd BAR2/3), producing garbage PFNs. Guest BAR0 access then hit a KVM make_spte reserved-bits WARN and an EPT-violation livelock, hard-wedging the guest on every nvidia driver probe (confirmed via QEMU GDB stub). BAR0 now maps per-page via vmalloc_to_pfn(); BAR2/3/4/5 map bars[N].gpa directly (like BAR1); config space uses new nv_vgpu_mmap_virt_to_pfn() helper. mdev remove path (main.c): old path never list_del'd the device (available_instances leaked), never ran cleanup (leaked gsp_emu kthread), never unregistered VFIO (leaked /dev/vfio/N + iommu group), never put pdev, never freed the struct. New kvmgt-style path: cancel init_work -> wait fds -> vfio_unregister_group_dev -> cleanup work -> list_del -> pci_dev_put -> vfio_put_device. gsp_rpc_client.c: fix dma_map_page dma_addr_t->int truncation in gsp_rpc_alloc_emul (corrupts >=4GB IOVAs); export gsp_client_init_emulation. gsp_emulation.c: export gsp_emu_process_rpc and gsp_emu_get_stats. main.c: remove wrong MODULE_SOFTDEP("pre: nvidia") - module has zero nvidia imports, loads standalone, and must load BEFORE nvidia.ko (it exports nvidia_vgpu_vfio_* for nvidia.ko to import). nvidia-vgpu-vfio.h: NULL-safe nv_vgpu_{dbg,info,warn,err} macros. tests/gsp_emulation_test.c: rewritten against current API - drives gsp_emu_process_rpc() with crafted shared-memory messages (protocol validation, SCHEDULE/YIELD/NOTIFY/DEBUG dispatch, stress). Builds with KBUILD_EXTRA_SYMBOLS; depends: open-iov-vfio. stack_manager.sh: vfio_mdev doesn't exist on >=6.4 (probe per-module); load-nvidia detects foreign nvidia via /sys/module/nvidia/version and errors with guidance instead of silently skipping.
Diffstat (limited to 'kernel/main.c')
-rw-r--r--kernel/main.c97
1 files changed, 69 insertions, 28 deletions
diff --git a/kernel/main.c b/kernel/main.c
index c96357b..e0e823b 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -489,7 +489,10 @@ void nv_vgpu_mdev_unregister_parent(struct pci_dev *pdev)
}
EXPORT_SYMBOL(nv_vgpu_mdev_unregister_parent);
-MODULE_SOFTDEP("pre: nvidia");
+/* NOTE: No MODULE_SOFTDEP on nvidia - this module is fully standalone.
+ * It exports the nvidia_vgpu_vfio_* interface (nvidia.ko imports it when
+ * present) and registers the mdev parent on the physical GPU directly.
+ * Load order for the matched stack: open-iov-vfio.ko BEFORE nvidia.ko. */
MODULE_AUTHOR("Open-IOV Project");
MODULE_DESCRIPTION("Open-IOV vGPU VFIO Mediated Device Driver");
@@ -507,7 +510,6 @@ static int nv_vgpu_mdev_create(struct mdev_device *mdev)
struct nv_vgpu_device *vgpu;
struct pci_dev *pdev;
struct mdev_parent *parent;
- struct iommu_group *iommu_group;
int ret;
vgpu = vfio_alloc_device(nv_vgpu_device, vfio_dev, &mdev->dev,
@@ -595,18 +597,17 @@ static void nv_vgpu_mdev_remove(struct mdev_device *mdev)
pr_info("nvidia-vgpu-vfio: mdev remove started for %s\n", dev_name(&mdev->dev));
- /* Cancel init work first */
+ /* Cancel pending/running init work first */
cancel_work_sync(&vgpu->init_work);
- /* Cancel cleanup work to prevent race */
- cancel_work_sync(&vgpu->cleanup_work);
-
/* Wait for any open file descriptors to close before proceeding.
* The VFIO device must not be unregistered while userspace has it open,
- * otherwise vfio_device_group_unregister can crash with NULL pointer deref. */
+ * otherwise vfio_device_group_unregister can crash with NULL pointer deref.
+ * vfio_unregister_group_dev() itself blocks until fds drain, so this
+ * pre-wait exists to surface a clear warning if userspace is stuck. */
pr_info("nvidia-vgpu-vfio: waiting for vfio_dev_open_count to reach 0 (current: %d)\n",
atomic_read(&vgpu->vfio_dev_open_count));
-
+
timeout = wait_event_timeout(vgpu->vfio_dev_close_wait,
atomic_read(&vgpu->vfio_dev_open_count) == 0,
msecs_to_jiffies(5000));
@@ -617,10 +618,31 @@ static void nv_vgpu_mdev_remove(struct mdev_device *mdev)
pr_info("nvidia-vgpu-vfio: all vfio device file descriptors closed\n");
}
- /* Mark device as being removed - actual resource cleanup happens in
- * vfio_device_release callback which is called when refcount reaches 0
- * after mdev framework unregisters the vfio_device. */
- vgpu->vfio_dev_registered = false;
+ /* Unregister the VFIO device. This removes it from its IOMMU group,
+ * deletes the cdev, and waits for any remaining userspace fds to
+ * close. After this returns, no userspace can access the device. */
+ if (vgpu->vfio_dev_registered) {
+ vfio_unregister_group_dev(&vgpu->vfio_dev);
+ vgpu->vfio_dev_registered = false;
+ }
+
+ /* Now that no userspace can touch the device, release all per-device
+ * resources (GSP emulation thread, memory, IRQs, BARs, migration).
+ * cleanup_work is never scheduled elsewhere, so invoke it directly. */
+ nv_vgpu_device_cleanup_work(&vgpu->cleanup_work);
+
+ /* Remove from driver device list (get_available accounts per-type) */
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_del(&vgpu->list);
+ nv_vgpu_driver.device_count--;
+ mutex_unlock(&nv_vgpu_driver.lock);
+
+ pci_dev_put(vgpu->pdev);
+ vgpu->pdev = NULL;
+
+ /* Balance vfio_alloc_device() in create; frees the vgpu struct once
+ * the vfio_device refcount drops to zero (ops->release fires first). */
+ vfio_put_device(&vgpu->vfio_dev);
pr_info("nvidia-vgpu-vfio: mdev remove callback completed for %s\n", dev_name(&mdev->dev));
}
@@ -1001,6 +1023,19 @@ static long nv_vgpu_vfio_dev_ioctl(struct vfio_device *vdev, unsigned int cmd,
return -ENOTTY;
}
+/* Convert any kernel virtual address (lowmem or vmalloc) to a PFN for mmap.
+ * virt_to_phys() is only valid for direct-mapped lowmem; vmalloc/vmap
+ * addresses (vzalloc, ioremap, kvzalloc fallback) must go through
+ * vmalloc_to_pfn(). Using virt_to_phys() on a vmalloc address produces a
+ * garbage PFN, which KVM then refuses to map (EPT reserved-bit WARN) and
+ * the guest faults/hangs touching the BAR. */
+static unsigned long nv_vgpu_mmap_virt_to_pfn(const void *vaddr)
+{
+ if (is_vmalloc_addr(vaddr))
+ return vmalloc_to_pfn(vaddr);
+ return virt_to_phys((void *)vaddr) >> PAGE_SHIFT;
+}
+
static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct *vma)
{
struct nv_vgpu_device *vgpu = container_of(vdev, struct nv_vgpu_device, vfio_dev);
@@ -1021,7 +1056,7 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
if (offset + size > region_base + region_size)
return -EINVAL;
vaddr = vgpu->pci_config + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = nv_vgpu_mmap_virt_to_pfn(vaddr);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
@@ -1030,14 +1065,24 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR0_REGION_INDEX);
region_size = bar_ctx ? bar_ctx->bar0_shadow_size : (256 * 1024 * 1024);
if (offset >= region_base && offset < region_base + region_size) {
+ unsigned long shadow_off = offset - region_base;
+ unsigned long pos;
+
if (!bar_ctx || !bar_ctx->bar0_shadow)
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = bar_ctx->bar0_shadow + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ /* bar0_shadow is vzalloc'd: physically non-contiguous pages, so
+ * map one page at a time via vmalloc_to_pfn(). */
+ vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
- return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
+ for (pos = 0; pos < size; pos += PAGE_SIZE) {
+ pfn = vmalloc_to_pfn(bar_ctx->bar0_shadow + shadow_off + pos);
+ if (remap_pfn_range(vma, vma->vm_start + pos, pfn, PAGE_SIZE,
+ vma->vm_page_prot))
+ return -EAGAIN;
+ }
+ return 0;
}
/* BAR1 region - VRAM (use physical address directly) */
@@ -1054,7 +1099,7 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR2 region */
+ /* BAR2 region - ioremap'd host BAR; map backing physical address */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR2_REGION_INDEX);
region_size = vgpu->bars[2].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1062,13 +1107,12 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[2].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[2].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR3 region */
+ /* BAR3 region - ioremap'd host BAR; map backing physical address */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR3_REGION_INDEX);
region_size = vgpu->bars[3].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1076,13 +1120,12 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[3].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[3].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR4 region */
+ /* BAR4 region - map backing physical address if present */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR4_REGION_INDEX);
region_size = vgpu->bars[4].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1090,13 +1133,12 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[4].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[4].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}
- /* BAR5 region */
+ /* BAR5 region - map backing physical address if present */
region_base = VFIO_PCI_INDEX_TO_OFFSET(VFIO_PCI_BAR5_REGION_INDEX);
region_size = vgpu->bars[5].size;
if (offset >= region_base && offset < region_base + region_size) {
@@ -1104,8 +1146,7 @@ static int nv_vgpu_vfio_dev_mmap(struct vfio_device *vdev, struct vm_area_struct
return -ENODEV;
if (offset + size > region_base + region_size)
return -EINVAL;
- vaddr = vgpu->bars[5].vaddr + (offset - region_base);
- pfn = virt_to_phys(vaddr) >> PAGE_SHIFT;
+ pfn = (vgpu->bars[5].gpa + (offset - region_base)) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
}