kvm/x86: Hyper-V SynIC timers
authorAndrey Smetanin <asmetanin@virtuozzo.com>
Mon, 30 Nov 2015 16:22:21 +0000 (19:22 +0300)
committerPaolo Bonzini <pbonzini@redhat.com>
Wed, 16 Dec 2015 17:49:45 +0000 (18:49 +0100)
Per Hyper-V specification (and as required by Hyper-V-aware guests),
SynIC provides 4 per-vCPU timers.  Each timer is programmed via a pair
of MSRs, and signals expiration by delivering a special format message
to the configured SynIC message slot and triggering the corresponding
synthetic interrupt.

Note: as implemented by this patch, all periodic timers are "lazy"
(i.e. if the vCPU wasn't scheduled for more than the timer period the
timer events are lost), regardless of the corresponding configuration
MSR.  If deemed necessary, the "catch up" mode (the timer period is
shortened until the timer catches up) will be implemented later.

Changes v2:
* Use remainder to calculate periodic timer expiration time

Signed-off-by: Andrey Smetanin <asmetanin@virtuozzo.com>
Reviewed-by: Roman Kagan <rkagan@virtuozzo.com>
CC: Gleb Natapov <gleb@kernel.org>
CC: Paolo Bonzini <pbonzini@redhat.com>
CC: "K. Y. Srinivasan" <kys@microsoft.com>
CC: Haiyang Zhang <haiyangz@microsoft.com>
CC: Vitaly Kuznetsov <vkuznets@redhat.com>
CC: Roman Kagan <rkagan@virtuozzo.com>
CC: Denis V. Lunev <den@openvz.org>
CC: qemu-devel@nongnu.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/include/asm/kvm_host.h
arch/x86/include/uapi/asm/hyperv.h
arch/x86/kvm/hyperv.c
arch/x86/kvm/hyperv.h
arch/x86/kvm/x86.c
include/linux/kvm_host.h

index 814007701f8b233e83c39135cbf88ac61300ebbc..a7c89876698be48abe5edc6dccc5783a9cce659c 100644 (file)
@@ -379,6 +379,17 @@ struct kvm_mtrr {
        struct list_head head;
 };
 
+/* Hyper-V SynIC timer */
+struct kvm_vcpu_hv_stimer {
+       struct hrtimer timer;
+       int index;
+       u64 config;
+       u64 count;
+       u64 exp_time;
+       struct hv_message msg;
+       bool msg_pending;
+};
+
 /* Hyper-V synthetic interrupt controller (SynIC)*/
 struct kvm_vcpu_hv_synic {
        u64 version;
@@ -398,6 +409,8 @@ struct kvm_vcpu_hv {
        s64 runtime_offset;
        struct kvm_vcpu_hv_synic synic;
        struct kvm_hyperv_exit exit;
+       struct kvm_vcpu_hv_stimer stimer[HV_SYNIC_STIMER_COUNT];
+       DECLARE_BITMAP(stimer_pending_bitmap, HV_SYNIC_STIMER_COUNT);
 };
 
 struct kvm_vcpu_arch {
index 2a5629eac0b165b56f77cbc903247cb949b17003..7956412d09bd8b340a41660d41aed4fac3947a6e 100644 (file)
@@ -355,4 +355,10 @@ struct hv_timer_message_payload {
        __u64 delivery_time;    /* When the message was delivered */
 };
 
+#define HV_STIMER_ENABLE               (1ULL << 0)
+#define HV_STIMER_PERIODIC             (1ULL << 1)
+#define HV_STIMER_LAZY                 (1ULL << 2)
+#define HV_STIMER_AUTOENABLE           (1ULL << 3)
+#define HV_STIMER_SINT(config)         (__u8)(((config) >> 16) & 0x0F)
+
 #endif
index 6412b6b504b5cbdb05f629cf9767d8a5886dfce3..8ff88293b906a7e90000c40e0840812b571e2710 100644 (file)
@@ -147,15 +147,32 @@ static void kvm_hv_notify_acked_sint(struct kvm_vcpu *vcpu, u32 sint)
 {
        struct kvm *kvm = vcpu->kvm;
        struct kvm_vcpu_hv_synic *synic = vcpu_to_synic(vcpu);
-       int gsi, idx;
+       struct kvm_vcpu_hv *hv_vcpu = vcpu_to_hv_vcpu(vcpu);
+       struct kvm_vcpu_hv_stimer *stimer;
+       int gsi, idx, stimers_pending;
 
        vcpu_debug(vcpu, "Hyper-V SynIC acked sint %d\n", sint);
 
        if (synic->msg_page & HV_SYNIC_SIMP_ENABLE)
                synic_clear_sint_msg_pending(synic, sint);
 
+       /* Try to deliver pending Hyper-V SynIC timers messages */
+       stimers_pending = 0;
+       for (idx = 0; idx < ARRAY_SIZE(hv_vcpu->stimer); idx++) {
+               stimer = &hv_vcpu->stimer[idx];
+               if (stimer->msg_pending &&
+                   (stimer->config & HV_STIMER_ENABLE) &&
+                   HV_STIMER_SINT(stimer->config) == sint) {
+                       set_bit(stimer->index,
+                               hv_vcpu->stimer_pending_bitmap);
+                       stimers_pending++;
+               }
+       }
+       if (stimers_pending)
+               kvm_make_request(KVM_REQ_HV_STIMER, vcpu);
+
        idx = srcu_read_lock(&kvm->irq_srcu);
-       gsi = atomic_read(&vcpu_to_synic(vcpu)->sint_to_gsi[sint]);
+       gsi = atomic_read(&synic->sint_to_gsi[sint]);
        if (gsi != -1)
                kvm_notify_acked_gsi(kvm, gsi);
        srcu_read_unlock(&kvm->irq_srcu, idx);
@@ -371,9 +388,268 @@ static u64 get_time_ref_counter(struct kvm *kvm)
        return div_u64(get_kernel_ns() + kvm->arch.kvmclock_offset, 100);
 }
 
+static void stimer_mark_expired(struct kvm_vcpu_hv_stimer *stimer,
+                               bool vcpu_kick)
+{
+       struct kvm_vcpu *vcpu = stimer_to_vcpu(stimer);
+
+       set_bit(stimer->index,
+               vcpu_to_hv_vcpu(vcpu)->stimer_pending_bitmap);
+       kvm_make_request(KVM_REQ_HV_STIMER, vcpu);
+       if (vcpu_kick)
+               kvm_vcpu_kick(vcpu);
+}
+
+static void stimer_stop(struct kvm_vcpu_hv_stimer *stimer)
+{
+       hrtimer_cancel(&stimer->timer);
+}
+
+static void stimer_cleanup(struct kvm_vcpu_hv_stimer *stimer)
+{
+       struct kvm_vcpu *vcpu = stimer_to_vcpu(stimer);
+
+       stimer_stop(stimer);
+       clear_bit(stimer->index,
+                 vcpu_to_hv_vcpu(vcpu)->stimer_pending_bitmap);
+       stimer->msg_pending = false;
+}
+
+static enum hrtimer_restart stimer_timer_callback(struct hrtimer *timer)
+{
+       struct kvm_vcpu_hv_stimer *stimer;
+
+       stimer = container_of(timer, struct kvm_vcpu_hv_stimer, timer);
+       stimer_mark_expired(stimer, true);
+
+       return HRTIMER_NORESTART;
+}
+
+static void stimer_restart(struct kvm_vcpu_hv_stimer *stimer)
+{
+       u64 time_now;
+       ktime_t ktime_now;
+       u64 remainder;
+
+       time_now = get_time_ref_counter(stimer_to_vcpu(stimer)->kvm);
+       ktime_now = ktime_get();
+
+       div64_u64_rem(time_now - stimer->exp_time, stimer->count, &remainder);
+       stimer->exp_time = time_now + (stimer->count - remainder);
+
+       hrtimer_start(&stimer->timer,
+                     ktime_add_ns(ktime_now,
+                                  100 * (stimer->exp_time - time_now)),
+                     HRTIMER_MODE_ABS);
+}
+
+static int stimer_start(struct kvm_vcpu_hv_stimer *stimer)
+{
+       u64 time_now;
+       ktime_t ktime_now;
+
+       time_now = get_time_ref_counter(stimer_to_vcpu(stimer)->kvm);
+       ktime_now = ktime_get();
+
+       if (stimer->config & HV_STIMER_PERIODIC) {
+               if (stimer->count == 0)
+                       return -EINVAL;
+
+               stimer->exp_time = time_now + stimer->count;
+               hrtimer_start(&stimer->timer,
+                             ktime_add_ns(ktime_now, 100 * stimer->count),
+                             HRTIMER_MODE_ABS);
+               return 0;
+       }
+       stimer->exp_time = stimer->count;
+       if (time_now >= stimer->count) {
+               /*
+                * Expire timer according to Hypervisor Top-Level Functional
+                * specification v4(15.3.1):
+                * "If a one shot is enabled and the specified count is in
+                * the past, it will expire immediately."
+                */
+               stimer_mark_expired(stimer, false);
+               return 0;
+       }
+
+       hrtimer_start(&stimer->timer,
+                     ktime_add_ns(ktime_now, 100 * (stimer->count - time_now)),
+                     HRTIMER_MODE_ABS);
+       return 0;
+}
+
+static int stimer_set_config(struct kvm_vcpu_hv_stimer *stimer, u64 config,
+                            bool host)
+{
+       if (stimer->count == 0 || HV_STIMER_SINT(config) == 0)
+               config &= ~HV_STIMER_ENABLE;
+       stimer->config = config;
+       stimer_cleanup(stimer);
+       if (stimer->config & HV_STIMER_ENABLE)
+               if (stimer_start(stimer))
+                       return 1;
+       return 0;
+}
+
+static int stimer_set_count(struct kvm_vcpu_hv_stimer *stimer, u64 count,
+                           bool host)
+{
+       stimer->count = count;
+
+       stimer_cleanup(stimer);
+       if (stimer->count == 0)
+               stimer->config &= ~HV_STIMER_ENABLE;
+       else if (stimer->config & HV_STIMER_AUTOENABLE) {
+               stimer->config |= HV_STIMER_ENABLE;
+               if (stimer_start(stimer))
+                       return 1;
+       }
+
+       return 0;
+}
+
+static int stimer_get_config(struct kvm_vcpu_hv_stimer *stimer, u64 *pconfig)
+{
+       *pconfig = stimer->config;
+       return 0;
+}
+
+static int stimer_get_count(struct kvm_vcpu_hv_stimer *stimer, u64 *pcount)
+{
+       *pcount = stimer->count;
+       return 0;
+}
+
+static int synic_deliver_msg(struct kvm_vcpu_hv_synic *synic, u32 sint,
+                            struct hv_message *src_msg)
+{
+       struct kvm_vcpu *vcpu = synic_to_vcpu(synic);
+       struct page *page;
+       gpa_t gpa;
+       struct hv_message *dst_msg;
+       int r;
+       struct hv_message_page *msg_page;
+
+       if (!(synic->msg_page & HV_SYNIC_SIMP_ENABLE))
+               return -ENOENT;
+
+       gpa = synic->msg_page & PAGE_MASK;
+       page = kvm_vcpu_gfn_to_page(vcpu, gpa >> PAGE_SHIFT);
+       if (is_error_page(page))
+               return -EFAULT;
+
+       msg_page = kmap_atomic(page);
+       dst_msg = &msg_page->sint_message[sint];
+       if (sync_cmpxchg(&dst_msg->header.message_type, HVMSG_NONE,
+                        src_msg->header.message_type) != HVMSG_NONE) {
+               dst_msg->header.message_flags.msg_pending = 1;
+               r = -EAGAIN;
+       } else {
+               memcpy(&dst_msg->u.payload, &src_msg->u.payload,
+                      src_msg->header.payload_size);
+               dst_msg->header.message_type = src_msg->header.message_type;
+               dst_msg->header.payload_size = src_msg->header.payload_size;
+               r = synic_set_irq(synic, sint);
+               if (r >= 1)
+                       r = 0;
+               else if (r == 0)
+                       r = -EFAULT;
+       }
+       kunmap_atomic(msg_page);
+       kvm_release_page_dirty(page);
+       kvm_vcpu_mark_page_dirty(vcpu, gpa >> PAGE_SHIFT);
+       return r;
+}
+
+static void stimer_send_msg(struct kvm_vcpu_hv_stimer *stimer)
+{
+       struct kvm_vcpu *vcpu = stimer_to_vcpu(stimer);
+       struct hv_message *msg = &stimer->msg;
+       struct hv_timer_message_payload *payload =
+                       (struct hv_timer_message_payload *)&msg->u.payload;
+       int r;
+
+       stimer->msg_pending = true;
+       payload->expiration_time = stimer->exp_time;
+       payload->delivery_time = get_time_ref_counter(vcpu->kvm);
+       r = synic_deliver_msg(vcpu_to_synic(vcpu),
+                             HV_STIMER_SINT(stimer->config), msg);
+       if (!r)
+               stimer->msg_pending = false;
+}
+
+static void stimer_expiration(struct kvm_vcpu_hv_stimer *stimer)
+{
+       stimer_send_msg(stimer);
+       if (!(stimer->config & HV_STIMER_PERIODIC))
+               stimer->config |= ~HV_STIMER_ENABLE;
+       else
+               stimer_restart(stimer);
+}
+
+void kvm_hv_process_stimers(struct kvm_vcpu *vcpu)
+{
+       struct kvm_vcpu_hv *hv_vcpu = vcpu_to_hv_vcpu(vcpu);
+       struct kvm_vcpu_hv_stimer *stimer;
+       u64 time_now;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(hv_vcpu->stimer); i++)
+               if (test_and_clear_bit(i, hv_vcpu->stimer_pending_bitmap)) {
+                       stimer = &hv_vcpu->stimer[i];
+                       stimer_stop(stimer);
+                       if (stimer->config & HV_STIMER_ENABLE) {
+                               time_now = get_time_ref_counter(vcpu->kvm);
+                               if (time_now >= stimer->exp_time)
+                                       stimer_expiration(stimer);
+                       }
+               }
+}
+
+void kvm_hv_vcpu_uninit(struct kvm_vcpu *vcpu)
+{
+       struct kvm_vcpu_hv *hv_vcpu = vcpu_to_hv_vcpu(vcpu);
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(hv_vcpu->stimer); i++)
+               stimer_cleanup(&hv_vcpu->stimer[i]);
+}
+
+static void stimer_prepare_msg(struct kvm_vcpu_hv_stimer *stimer)
+{
+       struct hv_message *msg = &stimer->msg;
+       struct hv_timer_message_payload *payload =
+                       (struct hv_timer_message_payload *)&msg->u.payload;
+
+       memset(&msg->header, 0, sizeof(msg->header));
+       msg->header.message_type = HVMSG_TIMER_EXPIRED;
+       msg->header.payload_size = sizeof(*payload);
+
+       payload->timer_index = stimer->index;
+       payload->expiration_time = 0;
+       payload->delivery_time = 0;
+}
+
+static void stimer_init(struct kvm_vcpu_hv_stimer *stimer, int timer_index)
+{
+       memset(stimer, 0, sizeof(*stimer));
+       stimer->index = timer_index;
+       hrtimer_init(&stimer->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+       stimer->timer.function = stimer_timer_callback;
+       stimer_prepare_msg(stimer);
+}
+
 void kvm_hv_vcpu_init(struct kvm_vcpu *vcpu)
 {
-       synic_init(vcpu_to_synic(vcpu));
+       struct kvm_vcpu_hv *hv_vcpu = vcpu_to_hv_vcpu(vcpu);
+       int i;
+
+       synic_init(&hv_vcpu->synic);
+
+       bitmap_zero(hv_vcpu->stimer_pending_bitmap, HV_SYNIC_STIMER_COUNT);
+       for (i = 0; i < ARRAY_SIZE(hv_vcpu->stimer); i++)
+               stimer_init(&hv_vcpu->stimer[i], i);
 }
 
 int kvm_hv_activate_synic(struct kvm_vcpu *vcpu)
@@ -590,6 +866,24 @@ static int kvm_hv_set_msr(struct kvm_vcpu *vcpu, u32 msr, u64 data, bool host)
        case HV_X64_MSR_EOM:
        case HV_X64_MSR_SINT0 ... HV_X64_MSR_SINT15:
                return synic_set_msr(vcpu_to_synic(vcpu), msr, data, host);
+       case HV_X64_MSR_STIMER0_CONFIG:
+       case HV_X64_MSR_STIMER1_CONFIG:
+       case HV_X64_MSR_STIMER2_CONFIG:
+       case HV_X64_MSR_STIMER3_CONFIG: {
+               int timer_index = (msr - HV_X64_MSR_STIMER0_CONFIG)/2;
+
+               return stimer_set_config(vcpu_to_stimer(vcpu, timer_index),
+                                        data, host);
+       }
+       case HV_X64_MSR_STIMER0_COUNT:
+       case HV_X64_MSR_STIMER1_COUNT:
+       case HV_X64_MSR_STIMER2_COUNT:
+       case HV_X64_MSR_STIMER3_COUNT: {
+               int timer_index = (msr - HV_X64_MSR_STIMER0_COUNT)/2;
+
+               return stimer_set_count(vcpu_to_stimer(vcpu, timer_index),
+                                       data, host);
+       }
        default:
                vcpu_unimpl(vcpu, "Hyper-V uhandled wrmsr: 0x%x data 0x%llx\n",
                            msr, data);
@@ -673,6 +967,24 @@ static int kvm_hv_get_msr(struct kvm_vcpu *vcpu, u32 msr, u64 *pdata)
        case HV_X64_MSR_EOM:
        case HV_X64_MSR_SINT0 ... HV_X64_MSR_SINT15:
                return synic_get_msr(vcpu_to_synic(vcpu), msr, pdata);
+       case HV_X64_MSR_STIMER0_CONFIG:
+       case HV_X64_MSR_STIMER1_CONFIG:
+       case HV_X64_MSR_STIMER2_CONFIG:
+       case HV_X64_MSR_STIMER3_CONFIG: {
+               int timer_index = (msr - HV_X64_MSR_STIMER0_CONFIG)/2;
+
+               return stimer_get_config(vcpu_to_stimer(vcpu, timer_index),
+                                        pdata);
+       }
+       case HV_X64_MSR_STIMER0_COUNT:
+       case HV_X64_MSR_STIMER1_COUNT:
+       case HV_X64_MSR_STIMER2_COUNT:
+       case HV_X64_MSR_STIMER3_COUNT: {
+               int timer_index = (msr - HV_X64_MSR_STIMER0_COUNT)/2;
+
+               return stimer_get_count(vcpu_to_stimer(vcpu, timer_index),
+                                       pdata);
+       }
        default:
                vcpu_unimpl(vcpu, "Hyper-V unhandled rdmsr: 0x%x\n", msr);
                return 1;
index d5d8217c59bba5b7999f0c3d0612a2bb70752501..60eccd4bd1d365f45a700a08f84e7935b8461634 100644 (file)
@@ -59,5 +59,29 @@ void kvm_hv_synic_send_eoi(struct kvm_vcpu *vcpu, int vector);
 int kvm_hv_activate_synic(struct kvm_vcpu *vcpu);
 
 void kvm_hv_vcpu_init(struct kvm_vcpu *vcpu);
+void kvm_hv_vcpu_uninit(struct kvm_vcpu *vcpu);
+
+static inline struct kvm_vcpu_hv_stimer *vcpu_to_stimer(struct kvm_vcpu *vcpu,
+                                                       int timer_index)
+{
+       return &vcpu_to_hv_vcpu(vcpu)->stimer[timer_index];
+}
+
+static inline struct kvm_vcpu *stimer_to_vcpu(struct kvm_vcpu_hv_stimer *stimer)
+{
+       struct kvm_vcpu_hv *hv_vcpu;
+
+       hv_vcpu = container_of(stimer - stimer->index, struct kvm_vcpu_hv,
+                              stimer[0]);
+       return hv_vcpu_to_vcpu(hv_vcpu);
+}
+
+static inline bool kvm_hv_has_stimer_pending(struct kvm_vcpu *vcpu)
+{
+       return !bitmap_empty(vcpu->arch.hyperv.stimer_pending_bitmap,
+                            HV_SYNIC_STIMER_COUNT);
+}
+
+void kvm_hv_process_stimers(struct kvm_vcpu *vcpu);
 
 #endif
index f1d6501180ec8e294bbe1359bacb7020496e6229..b6102c1eb3b1f6da8f3c218dc89a9ceff7ff5aa2 100644 (file)
@@ -967,6 +967,7 @@ static u32 emulated_msrs[] = {
        HV_X64_MSR_VP_INDEX,
        HV_X64_MSR_VP_RUNTIME,
        HV_X64_MSR_SCONTROL,
+       HV_X64_MSR_STIMER0_CONFIG,
        HV_X64_MSR_APIC_ASSIST_PAGE, MSR_KVM_ASYNC_PF_EN, MSR_KVM_STEAL_TIME,
        MSR_KVM_PV_EOI_EN,
 
@@ -2199,6 +2200,7 @@ int kvm_set_msr_common(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
        case HV_X64_MSR_GUEST_OS_ID ... HV_X64_MSR_SINT15:
        case HV_X64_MSR_CRASH_P0 ... HV_X64_MSR_CRASH_P4:
        case HV_X64_MSR_CRASH_CTL:
+       case HV_X64_MSR_STIMER0_CONFIG ... HV_X64_MSR_STIMER3_COUNT:
                return kvm_hv_set_msr_common(vcpu, msr, data,
                                             msr_info->host_initiated);
        case MSR_IA32_BBL_CR_CTL3:
@@ -2403,6 +2405,7 @@ int kvm_get_msr_common(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
        case HV_X64_MSR_GUEST_OS_ID ... HV_X64_MSR_SINT15:
        case HV_X64_MSR_CRASH_P0 ... HV_X64_MSR_CRASH_P4:
        case HV_X64_MSR_CRASH_CTL:
+       case HV_X64_MSR_STIMER0_CONFIG ... HV_X64_MSR_STIMER3_COUNT:
                return kvm_hv_get_msr_common(vcpu,
                                             msr_info->index, &msr_info->data);
                break;
@@ -6489,6 +6492,8 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
                        r = 0;
                        goto out;
                }
+               if (kvm_check_request(KVM_REQ_HV_STIMER, vcpu))
+                       kvm_hv_process_stimers(vcpu);
        }
 
        /*
@@ -7649,6 +7654,7 @@ void kvm_arch_vcpu_uninit(struct kvm_vcpu *vcpu)
 {
        int idx;
 
+       kvm_hv_vcpu_uninit(vcpu);
        kvm_pmu_destroy(vcpu);
        kfree(vcpu->arch.mce_banks);
        kvm_free_lapic(vcpu);
@@ -8043,6 +8049,9 @@ static inline bool kvm_vcpu_has_events(struct kvm_vcpu *vcpu)
            kvm_cpu_has_interrupt(vcpu))
                return true;
 
+       if (kvm_hv_has_stimer_pending(vcpu))
+               return true;
+
        return false;
 }
 
index f44c24b81b178e7c858356807c0612cfd172d5c4..2969c474a3990e643ca526d76496839a9106d628 100644 (file)
@@ -144,6 +144,7 @@ static inline bool is_error_page(struct page *page)
 #define KVM_REQ_IOAPIC_EOI_EXIT   28
 #define KVM_REQ_HV_RESET          29
 #define KVM_REQ_HV_EXIT           30
+#define KVM_REQ_HV_STIMER         31
 
 #define KVM_USERSPACE_IRQ_SOURCE_ID            0
 #define KVM_IRQFD_RESAMPLE_IRQ_SOURCE_ID       1