summaryrefslogtreecommitdiffstats
path: root/sys/dev/hyperv/utilities/hv_timesync.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/hyperv/utilities/hv_timesync.c')
-rw-r--r--sys/dev/hyperv/utilities/hv_timesync.c216
1 files changed, 216 insertions, 0 deletions
diff --git a/sys/dev/hyperv/utilities/hv_timesync.c b/sys/dev/hyperv/utilities/hv_timesync.c
new file mode 100644
index 0000000..d1ea904
--- /dev/null
+++ b/sys/dev/hyperv/utilities/hv_timesync.c
@@ -0,0 +1,216 @@
+/*-
+ * Copyright (c) 2014 Microsoft Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * A common driver for all hyper-V util services.
+ */
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/reboot.h>
+#include <sys/timetc.h>
+#include <sys/syscallsubr.h>
+
+#include <dev/hyperv/include/hyperv.h>
+#include "hv_util.h"
+
+#define HV_WLTIMEDELTA 116444736000000000L /* in 100ns unit */
+#define HV_ICTIMESYNCFLAG_PROBE 0
+#define HV_ICTIMESYNCFLAG_SYNC 1
+#define HV_ICTIMESYNCFLAG_SAMPLE 2
+#define HV_NANO_SEC_PER_SEC 1000000000
+
+/* Time Sync data */
+typedef struct {
+ uint64_t data;
+} time_sync_data;
+
+ /* Time Synch Service */
+static hv_guid service_guid = {.data =
+ {0x30, 0xe6, 0x27, 0x95, 0xae, 0xd0, 0x7b, 0x49,
+ 0xad, 0xce, 0xe8, 0x0a, 0xb0, 0x17, 0x5c, 0xaf } };
+
+struct hv_ictimesync_data {
+ uint64_t parenttime;
+ uint64_t childtime;
+ uint64_t roundtriptime;
+ uint8_t flags;
+} __packed;
+
+typedef struct hv_timesync_sc {
+ hv_util_sc util_sc;
+ struct task task;
+ time_sync_data time_msg;
+} hv_timesync_sc;
+
+/**
+ * Set host time based on time sync message from host
+ */
+static void
+hv_set_host_time(void *context, int pending)
+{
+ hv_timesync_sc *softc = (hv_timesync_sc*)context;
+ uint64_t hosttime = softc->time_msg.data;
+ struct timespec guest_ts, host_ts;
+ uint64_t host_tns;
+ int64_t diff;
+ int error;
+
+ host_tns = (hosttime - HV_WLTIMEDELTA) * 100;
+ host_ts.tv_sec = (time_t)(host_tns/HV_NANO_SEC_PER_SEC);
+ host_ts.tv_nsec = (long)(host_tns%HV_NANO_SEC_PER_SEC);
+
+ nanotime(&guest_ts);
+
+ diff = (int64_t)host_ts.tv_sec - (int64_t)guest_ts.tv_sec;
+
+ /*
+ * If host differs by 5 seconds then make the guest catch up
+ */
+ if (diff > 5 || diff < -5) {
+ error = kern_clock_settime(curthread, CLOCK_REALTIME,
+ &host_ts);
+ }
+}
+
+/**
+ * @brief Synchronize time with host after reboot, restore, etc.
+ *
+ * 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.
+ */
+static inline
+void hv_adj_guesttime(hv_timesync_sc *sc, uint64_t hosttime, uint8_t flags)
+{
+ sc->time_msg.data = hosttime;
+
+ if (((flags & HV_ICTIMESYNCFLAG_SYNC) != 0) ||
+ ((flags & HV_ICTIMESYNCFLAG_SAMPLE) != 0)) {
+ taskqueue_enqueue(taskqueue_thread, &sc->task);
+ }
+}
+
+/**
+ * Time Sync Channel message handler
+ */
+static void
+hv_timesync_cb(void *context)
+{
+ hv_vmbus_channel* channel;
+ hv_vmbus_icmsg_hdr* icmsghdrp;
+ uint32_t recvlen;
+ uint64_t requestId;
+ int ret;
+ uint8_t* time_buf;
+ struct hv_ictimesync_data* timedatap;
+ hv_timesync_sc *softc;
+
+ softc = (hv_timesync_sc*)context;
+ channel = softc->util_sc.hv_dev->channel;
+ time_buf = softc->util_sc.receive_buffer;
+
+ ret = hv_vmbus_channel_recv_packet(channel, time_buf,
+ PAGE_SIZE, &recvlen, &requestId);
+
+ if ((ret == 0) && recvlen > 0) {
+ icmsghdrp = (struct hv_vmbus_icmsg_hdr *) &time_buf[
+ sizeof(struct hv_vmbus_pipe_hdr)];
+
+ if (icmsghdrp->icmsgtype == HV_ICMSGTYPE_NEGOTIATE) {
+ hv_negotiate_version(icmsghdrp, NULL, time_buf);
+ } else {
+ timedatap = (struct hv_ictimesync_data *) &time_buf[
+ sizeof(struct hv_vmbus_pipe_hdr) +
+ sizeof(struct hv_vmbus_icmsg_hdr)];
+ hv_adj_guesttime(softc, timedatap->parenttime, timedatap->flags);
+ }
+
+ icmsghdrp->icflags = HV_ICMSGHDRFLAG_TRANSACTION
+ | HV_ICMSGHDRFLAG_RESPONSE;
+
+ hv_vmbus_channel_send_packet(channel, time_buf,
+ recvlen, requestId,
+ HV_VMBUS_PACKET_TYPE_DATA_IN_BAND, 0);
+ }
+}
+
+static int
+hv_timesync_probe(device_t dev)
+{
+ const char *p = vmbus_get_type(dev);
+ if (!memcmp(p, &service_guid, sizeof(hv_guid))) {
+ device_set_desc(dev, "Hyper-V Time Synch Service");
+ return BUS_PROBE_DEFAULT;
+ }
+
+ return ENXIO;
+}
+
+static int
+hv_timesync_attach(device_t dev)
+{
+ hv_timesync_sc *softc = device_get_softc(dev);
+
+ softc->util_sc.callback = hv_timesync_cb;
+ TASK_INIT(&softc->task, 1, hv_set_host_time, softc);
+
+ return hv_util_attach(dev);
+}
+
+static int
+hv_timesync_detach(device_t dev)
+{
+ hv_timesync_sc *softc = device_get_softc(dev);
+ taskqueue_drain(taskqueue_thread, &softc->task);
+
+ return hv_util_detach(dev);
+}
+
+static device_method_t timesync_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, hv_timesync_probe),
+ DEVMETHOD(device_attach, hv_timesync_attach),
+ DEVMETHOD(device_detach, hv_timesync_detach),
+ { 0, 0 }
+};
+
+static driver_t timesync_driver = { "hvtimesync", timesync_methods, sizeof(hv_timesync_sc)};
+
+static devclass_t timesync_devclass;
+
+DRIVER_MODULE(hv_timesync, vmbus, timesync_driver, timesync_devclass, NULL, NULL);
+MODULE_VERSION(hv_timesync, 1);
+MODULE_DEPEND(hv_timesync, vmbus, 1, 1, 1);
OpenPOWER on IntegriCloud