KVM: x86: emulator: introduce emulator_recalc_and_set_mode
[platform/kernel/linux-starfive.git] / arch / x86 / kvm / emulate.c
index 3b27622..e5522a2 100644 (file)
@@ -791,8 +791,7 @@ static int linearize(struct x86_emulate_ctxt *ctxt,
                           ctxt->mode, linear);
 }
 
-static inline int assign_eip(struct x86_emulate_ctxt *ctxt, ulong dst,
-                            enum x86emul_mode mode)
+static inline int assign_eip(struct x86_emulate_ctxt *ctxt, ulong dst)
 {
        ulong linear;
        int rc;
@@ -802,41 +801,71 @@ static inline int assign_eip(struct x86_emulate_ctxt *ctxt, ulong dst,
 
        if (ctxt->op_bytes != sizeof(unsigned long))
                addr.ea = dst & ((1UL << (ctxt->op_bytes << 3)) - 1);
-       rc = __linearize(ctxt, addr, &max_size, 1, false, true, mode, &linear);
+       rc = __linearize(ctxt, addr, &max_size, 1, false, true, ctxt->mode, &linear);
        if (rc == X86EMUL_CONTINUE)
                ctxt->_eip = addr.ea;
        return rc;
 }
 
+static inline int emulator_recalc_and_set_mode(struct x86_emulate_ctxt *ctxt)
+{
+       u64 efer;
+       struct desc_struct cs;
+       u16 selector;
+       u32 base3;
+
+       ctxt->ops->get_msr(ctxt, MSR_EFER, &efer);
+
+       if (!(ctxt->ops->get_cr(ctxt, 0) & X86_CR0_PE)) {
+               /* Real mode. cpu must not have long mode active */
+               if (efer & EFER_LMA)
+                       return X86EMUL_UNHANDLEABLE;
+               ctxt->mode = X86EMUL_MODE_REAL;
+               return X86EMUL_CONTINUE;
+       }
+
+       if (ctxt->eflags & X86_EFLAGS_VM) {
+               /* Protected/VM86 mode. cpu must not have long mode active */
+               if (efer & EFER_LMA)
+                       return X86EMUL_UNHANDLEABLE;
+               ctxt->mode = X86EMUL_MODE_VM86;
+               return X86EMUL_CONTINUE;
+       }
+
+       if (!ctxt->ops->get_segment(ctxt, &selector, &cs, &base3, VCPU_SREG_CS))
+               return X86EMUL_UNHANDLEABLE;
+
+       if (efer & EFER_LMA) {
+               if (cs.l) {
+                       /* Proper long mode */
+                       ctxt->mode = X86EMUL_MODE_PROT64;
+               } else if (cs.d) {
+                       /* 32 bit compatibility mode*/
+                       ctxt->mode = X86EMUL_MODE_PROT32;
+               } else {
+                       ctxt->mode = X86EMUL_MODE_PROT16;
+               }
+       } else {
+               /* Legacy 32 bit / 16 bit mode */
+               ctxt->mode = cs.d ? X86EMUL_MODE_PROT32 : X86EMUL_MODE_PROT16;
+       }
+
+       return X86EMUL_CONTINUE;
+}
+
 static inline int assign_eip_near(struct x86_emulate_ctxt *ctxt, ulong dst)
 {
-       return assign_eip(ctxt, dst, ctxt->mode);
+       return assign_eip(ctxt, dst);
 }
 
-static int assign_eip_far(struct x86_emulate_ctxt *ctxt, ulong dst,
-                         const struct desc_struct *cs_desc)
+static int assign_eip_far(struct x86_emulate_ctxt *ctxt, ulong dst)
 {
-       enum x86emul_mode mode = ctxt->mode;
-       int rc;
+       int rc = emulator_recalc_and_set_mode(ctxt);
 
-#ifdef CONFIG_X86_64
-       if (ctxt->mode >= X86EMUL_MODE_PROT16) {
-               if (cs_desc->l) {
-                       u64 efer = 0;
+       if (rc != X86EMUL_CONTINUE)
+               return rc;
 
-                       ctxt->ops->get_msr(ctxt, MSR_EFER, &efer);
-                       if (efer & EFER_LMA)
-                               mode = X86EMUL_MODE_PROT64;
-               } else
-                       mode = X86EMUL_MODE_PROT32; /* temporary value */
-       }
-#endif
-       if (mode == X86EMUL_MODE_PROT16 || mode == X86EMUL_MODE_PROT32)
-               mode = cs_desc->d ? X86EMUL_MODE_PROT32 : X86EMUL_MODE_PROT16;
-       rc = assign_eip(ctxt, dst, mode);
-       if (rc == X86EMUL_CONTINUE)
-               ctxt->mode = mode;
-       return rc;
+       return assign_eip(ctxt, dst);
 }
 
 static inline int jmp_rel(struct x86_emulate_ctxt *ctxt, int rel)
@@ -2172,7 +2201,7 @@ static int em_jmp_far(struct x86_emulate_ctxt *ctxt)
        if (rc != X86EMUL_CONTINUE)
                return rc;
 
-       rc = assign_eip_far(ctxt, ctxt->src.val, &new_desc);
+       rc = assign_eip_far(ctxt, ctxt->src.val);
        /* Error handling is not implemented. */
        if (rc != X86EMUL_CONTINUE)
                return X86EMUL_UNHANDLEABLE;
@@ -2250,7 +2279,7 @@ static int em_ret_far(struct x86_emulate_ctxt *ctxt)
                                       &new_desc);
        if (rc != X86EMUL_CONTINUE)
                return rc;
-       rc = assign_eip_far(ctxt, eip, &new_desc);
+       rc = assign_eip_far(ctxt, eip);
        /* Error handling is not implemented. */
        if (rc != X86EMUL_CONTINUE)
                return X86EMUL_UNHANDLEABLE;
@@ -2876,6 +2905,7 @@ static int em_sysexit(struct x86_emulate_ctxt *ctxt)
        ops->set_segment(ctxt, ss_sel, &ss, 0, VCPU_SREG_SS);
 
        ctxt->_eip = rdx;
+       ctxt->mode = usermode;
        *reg_write(ctxt, VCPU_REGS_RSP) = rcx;
 
        return X86EMUL_CONTINUE;
@@ -3469,7 +3499,7 @@ static int em_call_far(struct x86_emulate_ctxt *ctxt)
        if (rc != X86EMUL_CONTINUE)
                return rc;
 
-       rc = assign_eip_far(ctxt, ctxt->src.val, &new_desc);
+       rc = assign_eip_far(ctxt, ctxt->src.val);
        if (rc != X86EMUL_CONTINUE)
                goto fail;