Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi...
[cascardo/linux.git] / drivers / hv / hv_util.c
index d5acaa2..4aa3cb6 100644 (file)
 #define SD_MINOR       0
 #define SD_VERSION     (SD_MAJOR << 16 | SD_MINOR)
 
-#define SD_WS2008_MAJOR                1
-#define SD_WS2008_VERSION      (SD_WS2008_MAJOR << 16 | SD_MINOR)
+#define SD_MAJOR_1     1
+#define SD_VERSION_1   (SD_MAJOR_1 << 16 | SD_MINOR)
 
-#define TS_MAJOR       3
+#define TS_MAJOR       4
 #define TS_MINOR       0
 #define TS_VERSION     (TS_MAJOR << 16 | TS_MINOR)
 
-#define TS_WS2008_MAJOR                1
-#define TS_WS2008_VERSION      (TS_WS2008_MAJOR << 16 | TS_MINOR)
+#define TS_MAJOR_1     1
+#define TS_VERSION_1   (TS_MAJOR_1 << 16 | TS_MINOR)
+
+#define TS_MAJOR_3     3
+#define TS_VERSION_3   (TS_MAJOR_3 << 16 | TS_MINOR)
 
 #define HB_MAJOR       3
-#define HB_MINOR 0
+#define HB_MINOR       0
 #define HB_VERSION     (HB_MAJOR << 16 | HB_MINOR)
 
-#define HB_WS2008_MAJOR        1
-#define HB_WS2008_VERSION      (HB_WS2008_MAJOR << 16 | HB_MINOR)
+#define HB_MAJOR_1     1
+#define HB_VERSION_1   (HB_MAJOR_1 << 16 | HB_MINOR)
 
 static int sd_srv_version;
 static int ts_srv_version;
@@ -61,9 +64,14 @@ static struct hv_util_service util_shutdown = {
        .util_cb = shutdown_onchannelcallback,
 };
 
+static int hv_timesync_init(struct hv_util_service *srv);
+static void hv_timesync_deinit(void);
+
 static void timesync_onchannelcallback(void *context);
 static struct hv_util_service util_timesynch = {
        .util_cb = timesync_onchannelcallback,
+       .util_init = hv_timesync_init,
+       .util_deinit = hv_timesync_deinit,
 };
 
 static void heartbeat_onchannelcallback(void *context);
@@ -160,20 +168,6 @@ static void shutdown_onchannelcallback(void *context)
                schedule_work(&shutdown_work);
 }
 
-/*
- * Set guest time to host UTC time.
- */
-static inline void do_adj_guesttime(u64 hosttime)
-{
-       s64 host_tns;
-       struct timespec host_ts;
-
-       host_tns = (hosttime - WLTIMEDELTA) * 100;
-       host_ts = ns_to_timespec(host_tns);
-
-       do_settimeofday(&host_ts);
-}
-
 /*
  * Set the host time in a process context.
  */
@@ -181,15 +175,37 @@ static inline void do_adj_guesttime(u64 hosttime)
 struct adj_time_work {
        struct work_struct work;
        u64     host_time;
+       u64     ref_time;
+       u8      flags;
 };
 
 static void hv_set_host_time(struct work_struct *work)
 {
        struct adj_time_work    *wrk;
+       s64 host_tns;
+       u64 newtime;
+       struct timespec host_ts;
 
        wrk = container_of(work, struct adj_time_work, work);
-       do_adj_guesttime(wrk->host_time);
-       kfree(wrk);
+
+       newtime = wrk->host_time;
+       if (ts_srv_version > TS_VERSION_3) {
+               /*
+                * Some latency has been introduced since Hyper-V generated
+                * its time sample. Take that latency into account before
+                * using TSC reference time sample from Hyper-V.
+                *
+                * This sample is given by TimeSync v4 and above hosts.
+                */
+               u64 current_tick;
+
+               rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
+               newtime += (current_tick - wrk->ref_time);
+       }
+       host_tns = (newtime - WLTIMEDELTA) * 100;
+       host_ts = ns_to_timespec(host_tns);
+
+       do_settimeofday(&host_ts);
 }
 
 /*
@@ -198,33 +214,31 @@ static void hv_set_host_time(struct work_struct *work)
  * ICTIMESYNCFLAG_SYNC flag bit indicates reboot, restore events of the VM.
  * After reboot the flag ICTIMESYNCFLAG_SYNC is included in the first time
  * message after the timesync channel is opened. Since the hv_utils module is
- * loaded after hv_vmbus, the first message is usually missed. The other
- * thing is, systime is automatically set to emulated hardware clock which may
- * not be UTC time or in the same time zone. So, to override these effects, we
- * use the first 50 time samples for initial system time setting.
+ * loaded after hv_vmbus, the first message is usually missed. This bit is
+ * considered a hard request to discipline the clock.
+ *
+ * ICTIMESYNCFLAG_SAMPLE bit indicates a time sample from host. This is
+ * typically used as a hint to the guest. The guest is under no obligation
+ * to discipline the clock.
  */
-static inline void adj_guesttime(u64 hosttime, u8 flags)
+static struct adj_time_work  wrk;
+static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 flags)
 {
-       struct adj_time_work    *wrk;
-       static s32 scnt = 50;
 
-       wrk = kmalloc(sizeof(struct adj_time_work), GFP_ATOMIC);
-       if (wrk == NULL)
+       /*
+        * This check is safe since we are executing in the
+        * interrupt context and time synch messages arre always
+        * delivered on the same CPU.
+        */
+       if (work_pending(&wrk.work))
                return;
 
-       wrk->host_time = hosttime;
-       if ((flags & ICTIMESYNCFLAG_SYNC) != 0) {
-               INIT_WORK(&wrk->work, hv_set_host_time);
-               schedule_work(&wrk->work);
-               return;
+       wrk.host_time = hosttime;
+       wrk.ref_time = reftime;
+       wrk.flags = flags;
+       if ((flags & (ICTIMESYNCFLAG_SYNC | ICTIMESYNCFLAG_SAMPLE)) != 0) {
+               schedule_work(&wrk.work);
        }
-
-       if ((flags & ICTIMESYNCFLAG_SAMPLE) != 0 && scnt > 0) {
-               scnt--;
-               INIT_WORK(&wrk->work, hv_set_host_time);
-               schedule_work(&wrk->work);
-       } else
-               kfree(wrk);
 }
 
 /*
@@ -237,6 +251,7 @@ static void timesync_onchannelcallback(void *context)
        u64 requestid;
        struct icmsg_hdr *icmsghdrp;
        struct ictimesync_data *timedatap;
+       struct ictimesync_ref_data *refdata;
        u8 *time_txf_buf = util_timesynch.recv_buffer;
        struct icmsg_negotiate *negop = NULL;
 
@@ -252,11 +267,27 @@ static void timesync_onchannelcallback(void *context)
                                                time_txf_buf,
                                                util_fw_version,
                                                ts_srv_version);
+                       pr_info("Using TimeSync version %d.%d\n",
+                               ts_srv_version >> 16, ts_srv_version & 0xFFFF);
                } else {
-                       timedatap = (struct ictimesync_data *)&time_txf_buf[
-                               sizeof(struct vmbuspipe_hdr) +
-                               sizeof(struct icmsg_hdr)];
-                       adj_guesttime(timedatap->parenttime, timedatap->flags);
+                       if (ts_srv_version > TS_VERSION_3) {
+                               refdata = (struct ictimesync_ref_data *)
+                                       &time_txf_buf[
+                                       sizeof(struct vmbuspipe_hdr) +
+                                       sizeof(struct icmsg_hdr)];
+
+                               adj_guesttime(refdata->parenttime,
+                                               refdata->vmreferencetime,
+                                               refdata->flags);
+                       } else {
+                               timedatap = (struct ictimesync_data *)
+                                       &time_txf_buf[
+                                       sizeof(struct vmbuspipe_hdr) +
+                                       sizeof(struct icmsg_hdr)];
+                               adj_guesttime(timedatap->parenttime,
+                                               0,
+                                               timedatap->flags);
+                       }
                }
 
                icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
@@ -350,16 +381,21 @@ static int util_probe(struct hv_device *dev,
        switch (vmbus_proto_version) {
        case (VERSION_WS2008):
                util_fw_version = UTIL_WS2K8_FW_VERSION;
-               sd_srv_version = SD_WS2008_VERSION;
-               ts_srv_version = TS_WS2008_VERSION;
-               hb_srv_version = HB_WS2008_VERSION;
+               sd_srv_version = SD_VERSION_1;
+               ts_srv_version = TS_VERSION_1;
+               hb_srv_version = HB_VERSION_1;
                break;
-
-       default:
+       case(VERSION_WIN10):
                util_fw_version = UTIL_FW_VERSION;
                sd_srv_version = SD_VERSION;
                ts_srv_version = TS_VERSION;
                hb_srv_version = HB_VERSION;
+               break;
+       default:
+               util_fw_version = UTIL_FW_VERSION;
+               sd_srv_version = SD_VERSION;
+               ts_srv_version = TS_VERSION_3;
+               hb_srv_version = HB_VERSION;
        }
 
        ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0,
@@ -427,6 +463,17 @@ static  struct hv_driver util_drv = {
        .remove =  util_remove,
 };
 
+static int hv_timesync_init(struct hv_util_service *srv)
+{
+       INIT_WORK(&wrk.work, hv_set_host_time);
+       return 0;
+}
+
+static void hv_timesync_deinit(void)
+{
+       cancel_work_sync(&wrk.work);
+}
+
 static int __init init_hyperv_utils(void)
 {
        pr_info("Registering HyperV Utility Driver\n");