/* step-by-step approximation to mitigate fluctuation */
 #define LAPIC_TIMER_ADVANCE_ADJUST_STEP 8
 
+static __always_inline u64 __kvm_lapic_get_reg64(char *regs, int reg)
+{
+       BUILD_BUG_ON(reg != APIC_ICR);
+       return *((u64 *) (regs + reg));
+}
+
+static __always_inline u64 kvm_lapic_get_reg64(struct kvm_lapic *apic, int reg)
+{
+       return __kvm_lapic_get_reg64(apic->regs, reg);
+}
+
+static __always_inline void __kvm_lapic_set_reg64(char *regs, int reg, u64 val)
+{
+       BUILD_BUG_ON(reg != APIC_ICR);
+       *((u64 *) (regs + reg)) = val;
+}
+
+static __always_inline void kvm_lapic_set_reg64(struct kvm_lapic *apic,
+                                               int reg, u64 val)
+{
+       __kvm_lapic_set_reg64(apic->regs, reg, val);
+}
+
 static inline int apic_test_vector(int vec, void *bitmap)
 {
        return test_bit(VEC_POS(vec), (bitmap) + REG_POS(vec));
                APIC_REGS_MASK(APIC_IRR, APIC_ISR_NR) |
                APIC_REG_MASK(APIC_ESR) |
                APIC_REG_MASK(APIC_ICR) |
-               APIC_REG_MASK(APIC_ICR2) |
                APIC_REG_MASK(APIC_LVTT) |
                APIC_REG_MASK(APIC_LVTTHMR) |
                APIC_REG_MASK(APIC_LVTPC) |
                APIC_REG_MASK(APIC_TMCCT) |
                APIC_REG_MASK(APIC_TDCR);
 
-       /* ARBPRI is not valid on x2APIC */
+       /*
+        * ARBPRI and ICR2 are not valid in x2APIC mode.  WARN if KVM reads ICR
+        * in x2APIC mode as it's an 8-byte register in x2APIC and needs to be
+        * manually handled by the caller.
+        */
        if (!apic_x2apic_mode(apic))
-               valid_reg_mask |= APIC_REG_MASK(APIC_ARBPRI);
+               valid_reg_mask |= APIC_REG_MASK(APIC_ARBPRI) |
+                                 APIC_REG_MASK(APIC_ICR2);
+       else
+               WARN_ON_ONCE(offset == APIC_ICR);
 
        if (alignment + len > 4)
                return 1;
                break;
        }
        case APIC_ICR:
+               WARN_ON_ONCE(apic_x2apic_mode(apic));
+
                /* No delay here, so we always clear the pending bit */
                val &= ~APIC_ICR_BUSY;
                kvm_apic_send_ipi(apic, val, kvm_lapic_get_reg(apic, APIC_ICR2));
                kvm_lapic_set_reg(apic, APIC_ICR, val);
                break;
-
        case APIC_ICR2:
-               if (!apic_x2apic_mode(apic))
-                       val &= 0xff000000;
-               kvm_lapic_set_reg(apic, APIC_ICR2, val);
+               if (apic_x2apic_mode(apic))
+                       ret = 1;
+               else
+                       kvm_lapic_set_reg(apic, APIC_ICR2, val & 0xff000000);
                break;
 
        case APIC_LVT0:
        if (!apic_x2apic_mode(apic))
                kvm_apic_set_ldr(apic, 0);
        kvm_lapic_set_reg(apic, APIC_ESR, 0);
-       kvm_lapic_set_reg(apic, APIC_ICR, 0);
-       kvm_lapic_set_reg(apic, APIC_ICR2, 0);
+       if (!apic_x2apic_mode(apic)) {
+               kvm_lapic_set_reg(apic, APIC_ICR, 0);
+               kvm_lapic_set_reg(apic, APIC_ICR2, 0);
+       } else {
+               kvm_lapic_set_reg64(apic, APIC_ICR, 0);
+       }
        kvm_lapic_set_reg(apic, APIC_TDCR, 0);
        kvm_lapic_set_reg(apic, APIC_TMICT, 0);
        for (i = 0; i < 8; i++) {
        if (apic_x2apic_mode(vcpu->arch.apic)) {
                u32 *id = (u32 *)(s->regs + APIC_ID);
                u32 *ldr = (u32 *)(s->regs + APIC_LDR);
+               u64 icr;
 
                if (vcpu->kvm->arch.x2apic_format) {
                        if (*id != vcpu->vcpu_id)
                                *id <<= 24;
                }
 
-               /* In x2APIC mode, the LDR is fixed and based on the id */
-               if (set)
+               /*
+                * In x2APIC mode, the LDR is fixed and based on the id.  And
+                * ICR is internally a single 64-bit register, but needs to be
+                * split to ICR+ICR2 in userspace for backwards compatibility.
+                */
+               if (set) {
                        *ldr = kvm_apic_calc_x2apic_ldr(*id);
+
+                       icr = __kvm_lapic_get_reg(s->regs, APIC_ICR) |
+                             (u64)__kvm_lapic_get_reg(s->regs, APIC_ICR2) << 32;
+                       __kvm_lapic_set_reg64(s->regs, APIC_ICR, icr);
+               } else {
+                       icr = __kvm_lapic_get_reg64(s->regs, APIC_ICR);
+                       __kvm_lapic_set_reg(s->regs, APIC_ICR2, icr >> 32);
+               }
        }
 
        return 0;
        return 0;
 }
 
+int kvm_x2apic_icr_write(struct kvm_lapic *apic, u64 data)
+{
+       data &= ~APIC_ICR_BUSY;
+
+       kvm_apic_send_ipi(apic, (u32)data, (u32)(data >> 32));
+       kvm_lapic_set_reg64(apic, APIC_ICR, data);
+       trace_kvm_apic_write(APIC_ICR, data);
+       return 0;
+}
+
 static int kvm_lapic_msr_read(struct kvm_lapic *apic, u32 reg, u64 *data)
 {
-       u32 low, high = 0;
+       u32 low;
 
-       if (kvm_lapic_reg_read(apic, reg, 4, &low))
-               return 1;
+       if (reg == APIC_ICR) {
+               *data = kvm_lapic_get_reg64(apic, APIC_ICR);
+               return 0;
+       }
 
-       if (reg == APIC_ICR &&
-           WARN_ON_ONCE(kvm_lapic_reg_read(apic, APIC_ICR2, 4, &high)))
+       if (kvm_lapic_reg_read(apic, reg, 4, &low))
                return 1;
 
-       *data = (((u64)high) << 32) | low;
+       *data = low;
 
        return 0;
 }
 
 static int kvm_lapic_msr_write(struct kvm_lapic *apic, u32 reg, u64 data)
 {
-       /* For 64-bit ICR writes, set ICR2 (dest) before ICR (command). */
+       /*
+        * ICR is a 64-bit register in x2APIC mode (and Hyper'v PV vAPIC) and
+        * can be written as such, all other registers remain accessible only
+        * through 32-bit reads/writes.
+        */
        if (reg == APIC_ICR)
-               kvm_lapic_reg_write(apic, APIC_ICR2, (u32)(data >> 32));
+               return kvm_x2apic_icr_write(apic, data);
+
        return kvm_lapic_reg_write(apic, reg, (u32)data);
 }
 
        if (!lapic_in_kernel(vcpu) || !apic_x2apic_mode(apic))
                return 1;
 
-       if (reg == APIC_ICR2)
-               return 1;
-
        return kvm_lapic_msr_write(apic, reg, data);
 }
 
        if (!lapic_in_kernel(vcpu) || !apic_x2apic_mode(apic))
                return 1;
 
-       if (reg == APIC_DFR || reg == APIC_ICR2)
+       if (reg == APIC_DFR)
                return 1;
 
        return kvm_lapic_msr_read(apic, reg, data);