KVM: x86: XSAVE/XRSTOR live migration support
[firefly-linux-kernel-4.4.55.git] / arch / x86 / kvm / x86.c
index 795999e1ac196fb4b664d25c181478c13b7a9c50..0c8dc9614e7d30790c3c5365541b0251d9f75452 100644 (file)
@@ -1680,6 +1680,7 @@ int kvm_dev_ioctl_check_extension(long ext)
        case KVM_CAP_PCI_SEGMENT:
        case KVM_CAP_DEBUGREGS:
        case KVM_CAP_X86_ROBUST_SINGLESTEP:
+       case KVM_CAP_XSAVE:
                r = 1;
                break;
        case KVM_CAP_COALESCED_MMIO:
@@ -1703,6 +1704,9 @@ int kvm_dev_ioctl_check_extension(long ext)
        case KVM_CAP_MCE:
                r = KVM_MAX_MCE_BANKS;
                break;
+       case KVM_CAP_XCRS:
+               r = cpu_has_xsave;
+               break;
        default:
                r = 0;
                break;
@@ -2355,6 +2359,77 @@ static int kvm_vcpu_ioctl_x86_set_debugregs(struct kvm_vcpu *vcpu,
        return 0;
 }
 
+static void kvm_vcpu_ioctl_x86_get_xsave(struct kvm_vcpu *vcpu,
+                                        struct kvm_xsave *guest_xsave)
+{
+       if (cpu_has_xsave)
+               memcpy(guest_xsave->region,
+                       &vcpu->arch.guest_fpu.state->xsave,
+                       sizeof(struct xsave_struct));
+       else {
+               memcpy(guest_xsave->region,
+                       &vcpu->arch.guest_fpu.state->fxsave,
+                       sizeof(struct i387_fxsave_struct));
+               *(u64 *)&guest_xsave->region[XSAVE_HDR_OFFSET / sizeof(u32)] =
+                       XSTATE_FPSSE;
+       }
+}
+
+static int kvm_vcpu_ioctl_x86_set_xsave(struct kvm_vcpu *vcpu,
+                                       struct kvm_xsave *guest_xsave)
+{
+       u64 xstate_bv =
+               *(u64 *)&guest_xsave->region[XSAVE_HDR_OFFSET / sizeof(u32)];
+
+       if (cpu_has_xsave)
+               memcpy(&vcpu->arch.guest_fpu.state->xsave,
+                       guest_xsave->region, sizeof(struct xsave_struct));
+       else {
+               if (xstate_bv & ~XSTATE_FPSSE)
+                       return -EINVAL;
+               memcpy(&vcpu->arch.guest_fpu.state->fxsave,
+                       guest_xsave->region, sizeof(struct i387_fxsave_struct));
+       }
+       return 0;
+}
+
+static void kvm_vcpu_ioctl_x86_get_xcrs(struct kvm_vcpu *vcpu,
+                                       struct kvm_xcrs *guest_xcrs)
+{
+       if (!cpu_has_xsave) {
+               guest_xcrs->nr_xcrs = 0;
+               return;
+       }
+
+       guest_xcrs->nr_xcrs = 1;
+       guest_xcrs->flags = 0;
+       guest_xcrs->xcrs[0].xcr = XCR_XFEATURE_ENABLED_MASK;
+       guest_xcrs->xcrs[0].value = vcpu->arch.xcr0;
+}
+
+static int kvm_vcpu_ioctl_x86_set_xcrs(struct kvm_vcpu *vcpu,
+                                      struct kvm_xcrs *guest_xcrs)
+{
+       int i, r = 0;
+
+       if (!cpu_has_xsave)
+               return -EINVAL;
+
+       if (guest_xcrs->nr_xcrs > KVM_MAX_XCRS || guest_xcrs->flags)
+               return -EINVAL;
+
+       for (i = 0; i < guest_xcrs->nr_xcrs; i++)
+               /* Only support XCR0 currently */
+               if (guest_xcrs->xcrs[0].xcr == XCR_XFEATURE_ENABLED_MASK) {
+                       r = __kvm_set_xcr(vcpu, XCR_XFEATURE_ENABLED_MASK,
+                               guest_xcrs->xcrs[0].value);
+                       break;
+               }
+       if (r)
+               r = -EINVAL;
+       return r;
+}
+
 long kvm_arch_vcpu_ioctl(struct file *filp,
                         unsigned int ioctl, unsigned long arg)
 {
@@ -2556,6 +2631,70 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
                r = kvm_vcpu_ioctl_x86_set_debugregs(vcpu, &dbgregs);
                break;
        }
+       case KVM_GET_XSAVE: {
+               struct kvm_xsave *xsave;
+
+               xsave = kzalloc(sizeof(struct kvm_xsave), GFP_KERNEL);
+               r = -ENOMEM;
+               if (!xsave)
+                       break;
+
+               kvm_vcpu_ioctl_x86_get_xsave(vcpu, xsave);
+
+               r = -EFAULT;
+               if (copy_to_user(argp, xsave, sizeof(struct kvm_xsave)))
+                       break;
+               r = 0;
+               break;
+       }
+       case KVM_SET_XSAVE: {
+               struct kvm_xsave *xsave;
+
+               xsave = kzalloc(sizeof(struct kvm_xsave), GFP_KERNEL);
+               r = -ENOMEM;
+               if (!xsave)
+                       break;
+
+               r = -EFAULT;
+               if (copy_from_user(xsave, argp, sizeof(struct kvm_xsave)))
+                       break;
+
+               r = kvm_vcpu_ioctl_x86_set_xsave(vcpu, xsave);
+               break;
+       }
+       case KVM_GET_XCRS: {
+               struct kvm_xcrs *xcrs;
+
+               xcrs = kzalloc(sizeof(struct kvm_xcrs), GFP_KERNEL);
+               r = -ENOMEM;
+               if (!xcrs)
+                       break;
+
+               kvm_vcpu_ioctl_x86_get_xcrs(vcpu, xcrs);
+
+               r = -EFAULT;
+               if (copy_to_user(argp, xcrs,
+                                sizeof(struct kvm_xcrs)))
+                       break;
+               r = 0;
+               break;
+       }
+       case KVM_SET_XCRS: {
+               struct kvm_xcrs *xcrs;
+
+               xcrs = kzalloc(sizeof(struct kvm_xcrs), GFP_KERNEL);
+               r = -ENOMEM;
+               if (!xcrs)
+                       break;
+
+               r = -EFAULT;
+               if (copy_from_user(xcrs, argp,
+                                  sizeof(struct kvm_xcrs)))
+                       break;
+
+               r = kvm_vcpu_ioctl_x86_set_xcrs(vcpu, xcrs);
+               break;
+       }
        default:
                r = -EINVAL;
        }