1 /* $NetBSD: hvtimesync.c,v 1.2 2019/03/01 08:17:51 nonaka Exp $ */ 2 3 /*- 4 * Copyright (c) 2014,2016-2017 Microsoft Corp. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice unmodified, this list of conditions, and the following 12 * disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 #ifdef __KERNEL_RCSID 31 __KERNEL_RCSID(0, "$NetBSD: hvtimesync.c,v 1.2 2019/03/01 08:17:51 nonaka Exp $"); 32 #endif 33 #ifdef __FBSDID 34 __FBSDID("$FreeBSD: head/sys/dev/hyperv/utilities/vmbus_timesync.c 322488 2017-08-14 06:00:50Z sephe $"); 35 #endif 36 37 #include <sys/param.h> 38 #include <sys/systm.h> 39 #include <sys/device.h> 40 #include <sys/module.h> 41 #include <sys/pmf.h> 42 #include <sys/sysctl.h> 43 #include <sys/timetc.h> 44 45 #include <dev/hyperv/vmbusvar.h> 46 #include <dev/hyperv/vmbusicreg.h> 47 #include <dev/hyperv/vmbusicvar.h> 48 49 #define VMBUS_TIMESYNC_FWVER_MAJOR 3 50 #define VMBUS_TIMESYNC_FWVER \ 51 VMBUS_IC_VERSION(VMBUS_TIMESYNC_FWVER_MAJOR, 0) 52 53 #define VMBUS_TIMESYNC_MSGVER_MAJOR 4 54 #define VMBUS_TIMESYNC_MSGVER \ 55 VMBUS_IC_VERSION(VMBUS_TIMESYNC_MSGVER_MAJOR, 0) 56 57 #define VMBUS_TIMESYNC_MSGVER4(sc) \ 58 VMBUS_ICVER_LE(VMBUS_IC_VERSION(4, 0), (sc)->sc_vmbusic.sc_msgver) 59 60 #define VMBUS_TIMESYNC_DORTT(sc) \ 61 (VMBUS_TIMESYNC_MSGVER4((sc)) && (hyperv_tc64 != NULL)) 62 63 static int hvtimesync_match(device_t, cfdata_t, void *); 64 static void hvtimesync_attach(device_t, device_t, void *); 65 static int hvtimesync_detach(device_t, int); 66 67 static void hvtimesync_channel_cb(void *); 68 static int hvtimesync_sysctl_setup(device_t); 69 70 struct hvtimesync_softc { 71 struct vmbusic_softc sc_vmbusic; 72 }; 73 74 CFATTACH_DECL_NEW(hvtimesync, sizeof(struct hvtimesync_softc), 75 hvtimesync_match, hvtimesync_attach, hvtimesync_detach, NULL); 76 77 static int hvtimesync_ignore_sync; 78 static int hvtimesnyc_sample_verbose; 79 static int hvtimesync_sample_thresh = -1; 80 81 static int 82 hvtimesync_match(device_t parent, cfdata_t cf, void *aux) 83 { 84 struct vmbus_attach_args *aa = aux; 85 86 return vmbusic_probe(aa, &hyperv_guid_timesync); 87 } 88 89 static void 90 hvtimesync_attach(device_t parent, device_t self, void *aux) 91 { 92 struct vmbus_attach_args *aa = aux; 93 int error; 94 95 aprint_naive("\n"); 96 aprint_normal(": Hyper-V Time Synchronization Service\n"); 97 98 error = vmbusic_attach(self, aa, hvtimesync_channel_cb); 99 if (error) 100 return; 101 102 (void) pmf_device_register(self, NULL, NULL); 103 104 (void) hvtimesync_sysctl_setup(self); 105 } 106 107 static int 108 hvtimesync_detach(device_t self, int flags) 109 { 110 int error; 111 112 error = vmbusic_detach(self, flags); 113 if (error) 114 return error; 115 116 pmf_device_deregister(self); 117 118 return 0; 119 } 120 121 static void 122 do_timesync(struct hvtimesync_softc *sc, uint64_t hvtime, uint64_t sent_tc, 123 uint8_t tsflags) 124 { 125 struct vmbusic_softc *vsc = &sc->sc_vmbusic; 126 struct timespec vm_ts, hv_ts; 127 uint64_t hv_ns, vm_ns, rtt = 0; 128 int64_t diff; 129 130 if (VMBUS_TIMESYNC_DORTT(sc)) 131 rtt = hyperv_tc64() - sent_tc; 132 133 hv_ns = (hvtime - VMBUS_ICMSG_TS_BASE + rtt) * HYPERV_TIMER_NS_FACTOR; 134 nanotime(&vm_ts); 135 vm_ns = (vm_ts.tv_sec * NANOSECOND) + vm_ts.tv_nsec; 136 137 if ((tsflags & VMBUS_ICMSG_TS_FLAG_SYNC) && !hvtimesync_ignore_sync) { 138 #if 0 139 device_printf(vsc->sc_dev, 140 "apply sync request, hv: %ju, vm: %ju\n", 141 (uintmax_t)hv_ns, (uintmax_t)vm_ns); 142 #endif 143 hv_ts.tv_sec = hv_ns / NANOSECOND; 144 hv_ts.tv_nsec = hv_ns % NANOSECOND; 145 tc_setclock(&hv_ts); 146 /* Done! */ 147 return; 148 } 149 150 if ((tsflags & VMBUS_ICMSG_TS_FLAG_SAMPLE) && 151 hvtimesync_sample_thresh >= 0) { 152 if (hvtimesnyc_sample_verbose) { 153 device_printf(vsc->sc_dev, 154 "sample request, hv: %ju, vm: %ju\n", 155 (uintmax_t)hv_ns, (uintmax_t)vm_ns); 156 } 157 158 if (hv_ns > vm_ns) 159 diff = hv_ns - vm_ns; 160 else 161 diff = vm_ns - hv_ns; 162 /* nanosec -> millisec */ 163 diff /= 1000000; 164 165 if (diff > hvtimesync_sample_thresh) { 166 device_printf(vsc->sc_dev, 167 "apply sample request, hv: %ju, vm: %ju\n", 168 (uintmax_t)hv_ns, (uintmax_t)vm_ns); 169 hv_ts.tv_sec = hv_ns / NANOSECOND; 170 hv_ts.tv_nsec = hv_ns % NANOSECOND; 171 tc_setclock(&hv_ts); 172 } 173 /* Done */ 174 return; 175 } 176 } 177 178 static void 179 hvtimesync_channel_cb(void *arg) 180 { 181 struct hvtimesync_softc *sc = arg; 182 struct vmbusic_softc *vsc = &sc->sc_vmbusic; 183 struct vmbus_channel *ch = vsc->sc_chan; 184 struct vmbus_icmsg_hdr *hdr; 185 uint64_t rid; 186 uint32_t rlen; 187 int error; 188 189 error = vmbus_channel_recv(ch, vsc->sc_buf, vsc->sc_buflen, 190 &rlen, &rid, 0); 191 if (error || rlen == 0) { 192 if (error != EAGAIN) { 193 DPRINTF("%s: timesync error=%d len=%u\n", 194 device_xname(vsc->sc_dev), error, rlen); 195 } 196 return; 197 } 198 if (rlen < sizeof(*hdr)) { 199 DPRINTF("%s: hvtimesync short read len=%u\n", 200 device_xname(vsc->sc_dev), rlen); 201 return; 202 } 203 204 hdr = (struct vmbus_icmsg_hdr *)vsc->sc_buf; 205 switch (hdr->ic_type) { 206 case VMBUS_ICMSG_TYPE_NEGOTIATE: 207 error = vmbusic_negotiate(vsc, hdr, &rlen, VMBUS_TIMESYNC_FWVER, 208 VMBUS_TIMESYNC_MSGVER); 209 if (error) 210 return; 211 if (VMBUS_TIMESYNC_DORTT(sc)) { 212 DPRINTF("%s: RTT\n", device_xname(vsc->sc_dev)); 213 } 214 break; 215 216 case VMBUS_ICMSG_TYPE_TIMESYNC: 217 if (VMBUS_TIMESYNC_MSGVER4(sc)) { 218 struct vmbus_icmsg_timesync4 *msg4; 219 220 if (rlen < sizeof(*msg4)) { 221 DPRINTF("%s: invalid timesync4 len=%u\n", 222 device_xname(vsc->sc_dev), rlen); 223 return; 224 } 225 226 msg4 = (struct vmbus_icmsg_timesync4 *)hdr; 227 do_timesync(sc, msg4->ic_hvtime, msg4->ic_sent_tc, 228 msg4->ic_tsflags); 229 } else { 230 struct vmbus_icmsg_timesync *msg; 231 232 if (rlen < sizeof(*msg)) { 233 DPRINTF("%s: invalid timesync len=%u\n", 234 device_xname(vsc->sc_dev), rlen); 235 return; 236 } 237 238 msg = (struct vmbus_icmsg_timesync *)hdr; 239 do_timesync(sc, msg->ic_hvtime, 0, msg->ic_tsflags); 240 } 241 break; 242 243 default: 244 device_printf(vsc->sc_dev, 245 "unhandled _timesync message type %u\n", hdr->ic_type); 246 return; 247 } 248 249 (void) vmbusic_sendresp(vsc, ch, vsc->sc_buf, rlen, rid); 250 } 251 252 static int 253 hvtimesync_sysctl_setup(device_t self) 254 { 255 struct hvtimesync_softc *sc = device_private(self); 256 struct vmbusic_softc *vsc = &sc->sc_vmbusic; 257 const struct sysctlnode *node; 258 int error; 259 260 error = sysctl_createv(NULL, 0, NULL, &node, 261 CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL, 262 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL); 263 if (error) 264 return error; 265 error = sysctl_createv(NULL, 0, &node, &node, 266 CTLFLAG_PERMANENT, CTLTYPE_NODE, "hyperv", NULL, 267 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); 268 if (error) 269 return error; 270 271 error = sysctl_createv(&vsc->sc_log, 0, &node, &node, 272 0, CTLTYPE_NODE, "timesync", NULL, 273 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); 274 if (error) 275 return error; 276 277 error = sysctl_createv(&vsc->sc_log, 0, &node, NULL, 278 CTLFLAG_READWRITE, 279 CTLTYPE_INT, "ignore_sync", NULL, 280 NULL, 0, &hvtimesync_ignore_sync, 0, 281 CTL_CREATE, CTL_EOL); 282 if (error) 283 return error; 284 error = sysctl_createv(&vsc->sc_log, 0, &node, NULL, 285 CTLFLAG_READWRITE, 286 CTLTYPE_INT, "sample_verbose", NULL, 287 NULL, 0, &hvtimesnyc_sample_verbose, 0, 288 CTL_CREATE, CTL_EOL); 289 if (error) 290 return error; 291 error = sysctl_createv(&vsc->sc_log, 0, &node, NULL, 292 CTLFLAG_READWRITE, 293 CTLTYPE_INT, "sample_thresh", NULL, 294 NULL, 0, &hvtimesync_sample_thresh, 0, 295 CTL_CREATE, CTL_EOL); 296 if (error) 297 return error; 298 299 return 0; 300 } 301 302 MODULE(MODULE_CLASS_DRIVER, hvtimesync, "vmbus"); 303 304 #ifdef _MODULE 305 #include "ioconf.c" 306 #endif 307 308 static int 309 hvtimesync_modcmd(modcmd_t cmd, void *aux) 310 { 311 int error = 0; 312 313 switch (cmd) { 314 case MODULE_CMD_INIT: 315 #ifdef _MODULE 316 error = config_init_component(cfdriver_ioconf_hvtimesync, 317 cfattach_ioconf_hvtimesync, cfdata_ioconf_hvtimesync); 318 #endif 319 break; 320 321 case MODULE_CMD_FINI: 322 #ifdef _MODULE 323 error = config_fini_component(cfdriver_ioconf_hvtimesync, 324 cfattach_ioconf_hvtimesync, cfdata_ioconf_hvtimesync); 325 #endif 326 break; 327 328 case MODULE_CMD_AUTOUNLOAD: 329 error = EBUSY; 330 break; 331 332 default: 333 error = ENOTTY; 334 break; 335 } 336 337 return error; 338 } 339