]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blobdiff - virt/kvm/kvm_main.c
KVM: Add coalesced MMIO support (common part)
[mirror_ubuntu-jammy-kernel.git] / virt / kvm / kvm_main.c
index e89338e2b04379d7c29e816de44e8b6824b7885e..7d10dfa0d3886ac4884e06099085cd142622cee5 100644 (file)
 #include <asm/uaccess.h>
 #include <asm/pgtable.h>
 
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+#include "coalesced_mmio.h"
+#endif
+
 MODULE_AUTHOR("Qumranet");
 MODULE_LICENSE("GPL");
 
@@ -65,6 +69,8 @@ struct dentry *kvm_debugfs_dir;
 static long kvm_vcpu_ioctl(struct file *file, unsigned int ioctl,
                           unsigned long arg);
 
+bool kvm_rebooting;
+
 static inline int valid_vcpu(int n)
 {
        return likely(n >= 0 && n < KVM_MAX_VCPUS);
@@ -183,10 +189,23 @@ EXPORT_SYMBOL_GPL(kvm_vcpu_uninit);
 static struct kvm *kvm_create_vm(void)
 {
        struct kvm *kvm = kvm_arch_create_vm();
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+       struct page *page;
+#endif
 
        if (IS_ERR(kvm))
                goto out;
 
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+       page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+       if (!page) {
+               kfree(kvm);
+               return ERR_PTR(-ENOMEM);
+       }
+       kvm->coalesced_mmio_ring =
+                       (struct kvm_coalesced_mmio_ring *)page_address(page);
+#endif
+
        kvm->mm = current->mm;
        atomic_inc(&kvm->mm->mm_count);
        spin_lock_init(&kvm->mmu_lock);
@@ -198,6 +217,9 @@ static struct kvm *kvm_create_vm(void)
        spin_lock(&kvm_lock);
        list_add(&kvm->vm_list, &vm_list);
        spin_unlock(&kvm_lock);
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+       kvm_coalesced_mmio_init(kvm);
+#endif
 out:
        return kvm;
 }
@@ -240,6 +262,10 @@ static void kvm_destroy_vm(struct kvm *kvm)
        spin_unlock(&kvm_lock);
        kvm_io_bus_destroy(&kvm->pio_bus);
        kvm_io_bus_destroy(&kvm->mmio_bus);
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+       if (kvm->coalesced_mmio_ring != NULL)
+               free_page((unsigned long)kvm->coalesced_mmio_ring);
+#endif
        kvm_arch_destroy_vm(kvm);
        mmdrop(mm);
 }
@@ -522,6 +548,7 @@ unsigned long gfn_to_hva(struct kvm *kvm, gfn_t gfn)
                return bad_hva();
        return (slot->userspace_addr + (gfn - slot->base_gfn) * PAGE_SIZE);
 }
+EXPORT_SYMBOL_GPL(gfn_to_hva);
 
 /*
  * Requires current->mm->mmap_sem to be held
@@ -531,6 +558,7 @@ pfn_t gfn_to_pfn(struct kvm *kvm, gfn_t gfn)
        struct page *page[1];
        unsigned long addr;
        int npages;
+       pfn_t pfn;
 
        might_sleep();
 
@@ -543,19 +571,38 @@ pfn_t gfn_to_pfn(struct kvm *kvm, gfn_t gfn)
        npages = get_user_pages(current, current->mm, addr, 1, 1, 1, page,
                                NULL);
 
-       if (npages != 1) {
-               get_page(bad_page);
-               return page_to_pfn(bad_page);
-       }
+       if (unlikely(npages != 1)) {
+               struct vm_area_struct *vma;
 
-       return page_to_pfn(page[0]);
+               vma = find_vma(current->mm, addr);
+               if (vma == NULL || addr < vma->vm_start ||
+                   !(vma->vm_flags & VM_PFNMAP)) {
+                       get_page(bad_page);
+                       return page_to_pfn(bad_page);
+               }
+
+               pfn = ((addr - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;
+               BUG_ON(pfn_valid(pfn));
+       } else
+               pfn = page_to_pfn(page[0]);
+
+       return pfn;
 }
 
 EXPORT_SYMBOL_GPL(gfn_to_pfn);
 
 struct page *gfn_to_page(struct kvm *kvm, gfn_t gfn)
 {
-       return pfn_to_page(gfn_to_pfn(kvm, gfn));
+       pfn_t pfn;
+
+       pfn = gfn_to_pfn(kvm, gfn);
+       if (pfn_valid(pfn))
+               return pfn_to_page(pfn);
+
+       WARN_ON(!pfn_valid(pfn));
+
+       get_page(bad_page);
+       return bad_page;
 }
 
 EXPORT_SYMBOL_GPL(gfn_to_page);
@@ -568,7 +615,8 @@ EXPORT_SYMBOL_GPL(kvm_release_page_clean);
 
 void kvm_release_pfn_clean(pfn_t pfn)
 {
-       put_page(pfn_to_page(pfn));
+       if (pfn_valid(pfn))
+               put_page(pfn_to_page(pfn));
 }
 EXPORT_SYMBOL_GPL(kvm_release_pfn_clean);
 
@@ -593,21 +641,25 @@ EXPORT_SYMBOL_GPL(kvm_set_page_dirty);
 
 void kvm_set_pfn_dirty(pfn_t pfn)
 {
-       struct page *page = pfn_to_page(pfn);
-       if (!PageReserved(page))
-               SetPageDirty(page);
+       if (pfn_valid(pfn)) {
+               struct page *page = pfn_to_page(pfn);
+               if (!PageReserved(page))
+                       SetPageDirty(page);
+       }
 }
 EXPORT_SYMBOL_GPL(kvm_set_pfn_dirty);
 
 void kvm_set_pfn_accessed(pfn_t pfn)
 {
-       mark_page_accessed(pfn_to_page(pfn));
+       if (pfn_valid(pfn))
+               mark_page_accessed(pfn_to_page(pfn));
 }
 EXPORT_SYMBOL_GPL(kvm_set_pfn_accessed);
 
 void kvm_get_pfn(pfn_t pfn)
 {
-       get_page(pfn_to_page(pfn));
+       if (pfn_valid(pfn))
+               get_page(pfn_to_page(pfn));
 }
 EXPORT_SYMBOL_GPL(kvm_get_pfn);
 
@@ -757,25 +809,26 @@ void mark_page_dirty(struct kvm *kvm, gfn_t gfn)
  */
 void kvm_vcpu_block(struct kvm_vcpu *vcpu)
 {
-       DECLARE_WAITQUEUE(wait, current);
+       DEFINE_WAIT(wait);
 
-       add_wait_queue(&vcpu->wq, &wait);
+       for (;;) {
+               prepare_to_wait(&vcpu->wq, &wait, TASK_INTERRUPTIBLE);
+
+               if (kvm_cpu_has_interrupt(vcpu))
+                       break;
+               if (kvm_cpu_has_pending_timer(vcpu))
+                       break;
+               if (kvm_arch_vcpu_runnable(vcpu))
+                       break;
+               if (signal_pending(current))
+                       break;
 
-       /*
-        * We will block until either an interrupt or a signal wakes us up
-        */
-       while (!kvm_cpu_has_interrupt(vcpu)
-              && !kvm_cpu_has_pending_timer(vcpu)
-              && !signal_pending(current)
-              && !kvm_arch_vcpu_runnable(vcpu)) {
-               set_current_state(TASK_INTERRUPTIBLE);
                vcpu_put(vcpu);
                schedule();
                vcpu_load(vcpu);
        }
 
-       __set_current_state(TASK_RUNNING);
-       remove_wait_queue(&vcpu->wq, &wait);
+       finish_wait(&vcpu->wq, &wait);
 }
 
 void kvm_resched(struct kvm_vcpu *vcpu)
@@ -796,6 +849,10 @@ static int kvm_vcpu_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
 #ifdef CONFIG_X86
        else if (vmf->pgoff == KVM_PIO_PAGE_OFFSET)
                page = virt_to_page(vcpu->arch.pio_data);
+#endif
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+       else if (vmf->pgoff == KVM_COALESCED_MMIO_PAGE_OFFSET)
+               page = virt_to_page(vcpu->kvm->coalesced_mmio_ring);
 #endif
        else
                return VM_FAULT_SIGBUS;
@@ -1119,6 +1176,32 @@ static long kvm_vm_ioctl(struct file *filp,
                        goto out;
                break;
        }
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+       case KVM_REGISTER_COALESCED_MMIO: {
+               struct kvm_coalesced_mmio_zone zone;
+               r = -EFAULT;
+               if (copy_from_user(&zone, argp, sizeof zone))
+                       goto out;
+               r = -ENXIO;
+               r = kvm_vm_ioctl_register_coalesced_mmio(kvm, &zone);
+               if (r)
+                       goto out;
+               r = 0;
+               break;
+       }
+       case KVM_UNREGISTER_COALESCED_MMIO: {
+               struct kvm_coalesced_mmio_zone zone;
+               r = -EFAULT;
+               if (copy_from_user(&zone, argp, sizeof zone))
+                       goto out;
+               r = -ENXIO;
+               r = kvm_vm_ioctl_unregister_coalesced_mmio(kvm, &zone);
+               if (r)
+                       goto out;
+               r = 0;
+               break;
+       }
+#endif
        default:
                r = kvm_arch_vm_ioctl(filp, ioctl, arg);
        }
@@ -1177,7 +1260,6 @@ static int kvm_dev_ioctl_create_vm(void)
 static long kvm_dev_ioctl(struct file *filp,
                          unsigned int ioctl, unsigned long arg)
 {
-       void __user *argp = (void __user *)arg;
        long r = -EINVAL;
 
        switch (ioctl) {
@@ -1194,7 +1276,7 @@ static long kvm_dev_ioctl(struct file *filp,
                r = kvm_dev_ioctl_create_vm();
                break;
        case KVM_CHECK_EXTENSION:
-               r = kvm_dev_ioctl_check_extension((long)argp);
+               r = kvm_dev_ioctl_check_extension(arg);
                break;
        case KVM_GET_VCPU_MMAP_SIZE:
                r = -EINVAL;
@@ -1203,6 +1285,9 @@ static long kvm_dev_ioctl(struct file *filp,
                r = PAGE_SIZE;     /* struct kvm_run */
 #ifdef CONFIG_X86
                r += PAGE_SIZE;    /* pio data page */
+#endif
+#ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
+               r += PAGE_SIZE;    /* coalesced mmio ring page */
 #endif
                break;
        case KVM_TRACE_ENABLE:
@@ -1245,7 +1330,6 @@ static void hardware_disable(void *junk)
        if (!cpu_isset(cpu, cpus_hardware_enabled))
                return;
        cpu_clear(cpu, cpus_hardware_enabled);
-       decache_vcpus_on_cpu(cpu);
        kvm_arch_hardware_disable(NULL);
 }
 
@@ -1264,17 +1348,29 @@ static int kvm_cpu_hotplug(struct notifier_block *notifier, unsigned long val,
        case CPU_UP_CANCELED:
                printk(KERN_INFO "kvm: disabling virtualization on CPU%d\n",
                       cpu);
-               smp_call_function_single(cpu, hardware_disable, NULL, 0, 1);
+               smp_call_function_single(cpu, hardware_disable, NULL, 1);
                break;
        case CPU_ONLINE:
                printk(KERN_INFO "kvm: enabling virtualization on CPU%d\n",
                       cpu);
-               smp_call_function_single(cpu, hardware_enable, NULL, 0, 1);
+               smp_call_function_single(cpu, hardware_enable, NULL, 1);
                break;
        }
        return NOTIFY_OK;
 }
 
+
+asmlinkage void kvm_handle_fault_on_reboot(void)
+{
+       if (kvm_rebooting)
+               /* spin while reset goes on */
+               while (true)
+                       ;
+       /* Fault while not rebooting.  We want the trace. */
+       BUG();
+}
+EXPORT_SYMBOL_GPL(kvm_handle_fault_on_reboot);
+
 static int kvm_reboot(struct notifier_block *notifier, unsigned long val,
                      void *v)
 {
@@ -1284,7 +1380,8 @@ static int kvm_reboot(struct notifier_block *notifier, unsigned long val,
                 * in vmx root mode.
                 */
                printk(KERN_INFO "kvm: exiting hardware virtualization\n");
-               on_each_cpu(hardware_disable, NULL, 0, 1);
+               kvm_rebooting = true;
+               on_each_cpu(hardware_disable, NULL, 1);
        }
        return NOTIFY_OK;
 }
@@ -1310,14 +1407,15 @@ void kvm_io_bus_destroy(struct kvm_io_bus *bus)
        }
 }
 
-struct kvm_io_device *kvm_io_bus_find_dev(struct kvm_io_bus *bus, gpa_t addr)
+struct kvm_io_device *kvm_io_bus_find_dev(struct kvm_io_bus *bus,
+                                         gpa_t addr, int len, int is_write)
 {
        int i;
 
        for (i = 0; i < bus->dev_count; i++) {
                struct kvm_io_device *pos = bus->devs[i];
 
-               if (pos->in_range(pos, addr))
+               if (pos->in_range(pos, addr, len, is_write))
                        return pos;
        }
 
@@ -1472,12 +1570,12 @@ int kvm_init(void *opaque, unsigned int vcpu_size,
        for_each_online_cpu(cpu) {
                smp_call_function_single(cpu,
                                kvm_arch_check_processor_compat,
-                               &r, 0, 1);
+                               &r, 1);
                if (r < 0)
                        goto out_free_1;
        }
 
-       on_each_cpu(hardware_enable, NULL, 0, 1);
+       on_each_cpu(hardware_enable, NULL, 1);
        r = register_cpu_notifier(&kvm_cpu_notifier);
        if (r)
                goto out_free_2;
@@ -1523,7 +1621,7 @@ out_free_3:
        unregister_reboot_notifier(&kvm_reboot_notifier);
        unregister_cpu_notifier(&kvm_cpu_notifier);
 out_free_2:
-       on_each_cpu(hardware_disable, NULL, 0, 1);
+       on_each_cpu(hardware_disable, NULL, 1);
 out_free_1:
        kvm_arch_hardware_unsetup();
 out_free_0:
@@ -1545,7 +1643,7 @@ void kvm_exit(void)
        sysdev_class_unregister(&kvm_sysdev_class);
        unregister_reboot_notifier(&kvm_reboot_notifier);
        unregister_cpu_notifier(&kvm_cpu_notifier);
-       on_each_cpu(hardware_disable, NULL, 0, 1);
+       on_each_cpu(hardware_disable, NULL, 1);
        kvm_arch_hardware_unsetup();
        kvm_arch_exit();
        kvm_exit_debug();