From 5d4d26f8e773f2871c1b5c9266b82495d390970e Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 21 Sep 2026 12:37:46 -0400 Subject: 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. --- kernel/main.c | 97 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 28 deletions(-) (limited to 'kernel/main.c') 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); } -- cgit v1.2.3