xref: /netbsd-src/sys/dev/hyperv/hvtimesync.c (revision 25af6d8fb297d2a7df9be9e33978d55fea6c5dd2)
1*25af6d8fSandvar /*	$NetBSD: hvtimesync.c,v 1.3 2022/11/02 18:18:44 andvar Exp $	*/
250517e57Snonaka 
350517e57Snonaka /*-
450517e57Snonaka  * Copyright (c) 2014,2016-2017 Microsoft Corp.
550517e57Snonaka  * All rights reserved.
650517e57Snonaka  *
750517e57Snonaka  * Redistribution and use in source and binary forms, with or without
850517e57Snonaka  * modification, are permitted provided that the following conditions
950517e57Snonaka  * are met:
1050517e57Snonaka  * 1. Redistributions of source code must retain the above copyright
1150517e57Snonaka  *    notice unmodified, this list of conditions, and the following
1250517e57Snonaka  *    disclaimer.
1350517e57Snonaka  * 2. Redistributions in binary form must reproduce the above copyright
1450517e57Snonaka  *    notice, this list of conditions and the following disclaimer in the
1550517e57Snonaka  *    documentation and/or other materials provided with the distribution.
1650517e57Snonaka  *
1750517e57Snonaka  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1850517e57Snonaka  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1950517e57Snonaka  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2050517e57Snonaka  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2150517e57Snonaka  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2250517e57Snonaka  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2350517e57Snonaka  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2450517e57Snonaka  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2550517e57Snonaka  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2650517e57Snonaka  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2750517e57Snonaka  */
2850517e57Snonaka 
2950517e57Snonaka #include <sys/cdefs.h>
3050517e57Snonaka #ifdef __KERNEL_RCSID
31*25af6d8fSandvar __KERNEL_RCSID(0, "$NetBSD: hvtimesync.c,v 1.3 2022/11/02 18:18:44 andvar Exp $");
3250517e57Snonaka #endif
3350517e57Snonaka #ifdef __FBSDID
3450517e57Snonaka __FBSDID("$FreeBSD: head/sys/dev/hyperv/utilities/vmbus_timesync.c 322488 2017-08-14 06:00:50Z sephe $");
3550517e57Snonaka #endif
3650517e57Snonaka 
3750517e57Snonaka #include <sys/param.h>
3850517e57Snonaka #include <sys/systm.h>
3950517e57Snonaka #include <sys/device.h>
4050517e57Snonaka #include <sys/module.h>
4150517e57Snonaka #include <sys/pmf.h>
4250517e57Snonaka #include <sys/sysctl.h>
4350517e57Snonaka #include <sys/timetc.h>
4450517e57Snonaka 
4550517e57Snonaka #include <dev/hyperv/vmbusvar.h>
4650517e57Snonaka #include <dev/hyperv/vmbusicreg.h>
4750517e57Snonaka #include <dev/hyperv/vmbusicvar.h>
4850517e57Snonaka 
4950517e57Snonaka #define VMBUS_TIMESYNC_FWVER_MAJOR	3
5050517e57Snonaka #define VMBUS_TIMESYNC_FWVER		\
5150517e57Snonaka 	    VMBUS_IC_VERSION(VMBUS_TIMESYNC_FWVER_MAJOR, 0)
5250517e57Snonaka 
5350517e57Snonaka #define VMBUS_TIMESYNC_MSGVER_MAJOR	4
5450517e57Snonaka #define VMBUS_TIMESYNC_MSGVER		\
5550517e57Snonaka 	    VMBUS_IC_VERSION(VMBUS_TIMESYNC_MSGVER_MAJOR, 0)
5650517e57Snonaka 
5750517e57Snonaka #define VMBUS_TIMESYNC_MSGVER4(sc)	\
5850517e57Snonaka 	    VMBUS_ICVER_LE(VMBUS_IC_VERSION(4, 0), (sc)->sc_vmbusic.sc_msgver)
5950517e57Snonaka 
6050517e57Snonaka #define VMBUS_TIMESYNC_DORTT(sc)	\
6150517e57Snonaka 	    (VMBUS_TIMESYNC_MSGVER4((sc)) && (hyperv_tc64 != NULL))
6250517e57Snonaka 
6350517e57Snonaka static int	hvtimesync_match(device_t, cfdata_t, void *);
6450517e57Snonaka static void	hvtimesync_attach(device_t, device_t, void *);
6550517e57Snonaka static int	hvtimesync_detach(device_t, int);
6650517e57Snonaka 
6750517e57Snonaka static void	hvtimesync_channel_cb(void *);
6850517e57Snonaka static int	hvtimesync_sysctl_setup(device_t);
6950517e57Snonaka 
7050517e57Snonaka struct hvtimesync_softc {
7150517e57Snonaka 	struct vmbusic_softc	sc_vmbusic;
7250517e57Snonaka };
7350517e57Snonaka 
7450517e57Snonaka CFATTACH_DECL_NEW(hvtimesync, sizeof(struct hvtimesync_softc),
7550517e57Snonaka     hvtimesync_match, hvtimesync_attach, hvtimesync_detach, NULL);
7650517e57Snonaka 
7750517e57Snonaka static int hvtimesync_ignore_sync;
78*25af6d8fSandvar static int hvtimesync_sample_verbose;
7950517e57Snonaka static int hvtimesync_sample_thresh = -1;
8050517e57Snonaka 
8150517e57Snonaka static int
hvtimesync_match(device_t parent,cfdata_t cf,void * aux)8250517e57Snonaka hvtimesync_match(device_t parent, cfdata_t cf, void *aux)
8350517e57Snonaka {
8450517e57Snonaka 	struct vmbus_attach_args *aa = aux;
8550517e57Snonaka 
8650517e57Snonaka 	return vmbusic_probe(aa, &hyperv_guid_timesync);
8750517e57Snonaka }
8850517e57Snonaka 
8950517e57Snonaka static void
hvtimesync_attach(device_t parent,device_t self,void * aux)9050517e57Snonaka hvtimesync_attach(device_t parent, device_t self, void *aux)
9150517e57Snonaka {
9250517e57Snonaka 	struct vmbus_attach_args *aa = aux;
9350517e57Snonaka 	int error;
9450517e57Snonaka 
9550517e57Snonaka 	aprint_naive("\n");
9638df7c85Snonaka 	aprint_normal(": Hyper-V Time Synchronization Service\n");
9750517e57Snonaka 
9850517e57Snonaka 	error = vmbusic_attach(self, aa, hvtimesync_channel_cb);
9950517e57Snonaka 	if (error)
10050517e57Snonaka 		return;
10150517e57Snonaka 
10250517e57Snonaka 	(void) pmf_device_register(self, NULL, NULL);
10350517e57Snonaka 
10450517e57Snonaka 	(void) hvtimesync_sysctl_setup(self);
10550517e57Snonaka }
10650517e57Snonaka 
10750517e57Snonaka static int
hvtimesync_detach(device_t self,int flags)10850517e57Snonaka hvtimesync_detach(device_t self, int flags)
10950517e57Snonaka {
11050517e57Snonaka 	int error;
11150517e57Snonaka 
11250517e57Snonaka 	error = vmbusic_detach(self, flags);
11350517e57Snonaka 	if (error)
11450517e57Snonaka 		return error;
11550517e57Snonaka 
11650517e57Snonaka 	pmf_device_deregister(self);
11750517e57Snonaka 
11850517e57Snonaka 	return 0;
11950517e57Snonaka }
12050517e57Snonaka 
12150517e57Snonaka static void
do_timesync(struct hvtimesync_softc * sc,uint64_t hvtime,uint64_t sent_tc,uint8_t tsflags)12250517e57Snonaka do_timesync(struct hvtimesync_softc *sc, uint64_t hvtime, uint64_t sent_tc,
12350517e57Snonaka     uint8_t tsflags)
12450517e57Snonaka {
12550517e57Snonaka 	struct vmbusic_softc *vsc = &sc->sc_vmbusic;
12650517e57Snonaka 	struct timespec vm_ts, hv_ts;
12750517e57Snonaka 	uint64_t hv_ns, vm_ns, rtt = 0;
12850517e57Snonaka 	int64_t diff;
12950517e57Snonaka 
13050517e57Snonaka 	if (VMBUS_TIMESYNC_DORTT(sc))
13150517e57Snonaka 		rtt = hyperv_tc64() - sent_tc;
13250517e57Snonaka 
13350517e57Snonaka 	hv_ns = (hvtime - VMBUS_ICMSG_TS_BASE + rtt) * HYPERV_TIMER_NS_FACTOR;
13450517e57Snonaka 	nanotime(&vm_ts);
13550517e57Snonaka 	vm_ns = (vm_ts.tv_sec * NANOSECOND) + vm_ts.tv_nsec;
13650517e57Snonaka 
13750517e57Snonaka 	if ((tsflags & VMBUS_ICMSG_TS_FLAG_SYNC) && !hvtimesync_ignore_sync) {
13850517e57Snonaka #if 0
13950517e57Snonaka 		device_printf(vsc->sc_dev,
14050517e57Snonaka 		    "apply sync request, hv: %ju, vm: %ju\n",
14150517e57Snonaka 		    (uintmax_t)hv_ns, (uintmax_t)vm_ns);
14250517e57Snonaka #endif
14350517e57Snonaka 		hv_ts.tv_sec = hv_ns / NANOSECOND;
14450517e57Snonaka 		hv_ts.tv_nsec = hv_ns % NANOSECOND;
14550517e57Snonaka 		tc_setclock(&hv_ts);
14650517e57Snonaka 		/* Done! */
14750517e57Snonaka 		return;
14850517e57Snonaka 	}
14950517e57Snonaka 
15050517e57Snonaka 	if ((tsflags & VMBUS_ICMSG_TS_FLAG_SAMPLE) &&
15150517e57Snonaka 	    hvtimesync_sample_thresh >= 0) {
152*25af6d8fSandvar 		if (hvtimesync_sample_verbose) {
15350517e57Snonaka 			device_printf(vsc->sc_dev,
15450517e57Snonaka 			    "sample request, hv: %ju, vm: %ju\n",
15550517e57Snonaka 			    (uintmax_t)hv_ns, (uintmax_t)vm_ns);
15650517e57Snonaka 		}
15750517e57Snonaka 
15850517e57Snonaka 		if (hv_ns > vm_ns)
15950517e57Snonaka 			diff = hv_ns - vm_ns;
16050517e57Snonaka 		else
16150517e57Snonaka 			diff = vm_ns - hv_ns;
16250517e57Snonaka 		/* nanosec -> millisec */
16350517e57Snonaka 		diff /= 1000000;
16450517e57Snonaka 
16550517e57Snonaka 		if (diff > hvtimesync_sample_thresh) {
16650517e57Snonaka 			device_printf(vsc->sc_dev,
16750517e57Snonaka 			    "apply sample request, hv: %ju, vm: %ju\n",
16850517e57Snonaka 			    (uintmax_t)hv_ns, (uintmax_t)vm_ns);
16950517e57Snonaka 			hv_ts.tv_sec = hv_ns / NANOSECOND;
17050517e57Snonaka 			hv_ts.tv_nsec = hv_ns % NANOSECOND;
17150517e57Snonaka 			tc_setclock(&hv_ts);
17250517e57Snonaka 		}
17350517e57Snonaka 		/* Done */
17450517e57Snonaka 		return;
17550517e57Snonaka 	}
17650517e57Snonaka }
17750517e57Snonaka 
17850517e57Snonaka static void
hvtimesync_channel_cb(void * arg)17950517e57Snonaka hvtimesync_channel_cb(void *arg)
18050517e57Snonaka {
18150517e57Snonaka 	struct hvtimesync_softc *sc = arg;
18250517e57Snonaka 	struct vmbusic_softc *vsc = &sc->sc_vmbusic;
18350517e57Snonaka 	struct vmbus_channel *ch = vsc->sc_chan;
18450517e57Snonaka 	struct vmbus_icmsg_hdr *hdr;
18550517e57Snonaka 	uint64_t rid;
18650517e57Snonaka 	uint32_t rlen;
18750517e57Snonaka 	int error;
18850517e57Snonaka 
18950517e57Snonaka 	error = vmbus_channel_recv(ch, vsc->sc_buf, vsc->sc_buflen,
19050517e57Snonaka 	    &rlen, &rid, 0);
19150517e57Snonaka 	if (error || rlen == 0) {
19250517e57Snonaka 		if (error != EAGAIN) {
19350517e57Snonaka 			DPRINTF("%s: timesync error=%d len=%u\n",
19450517e57Snonaka 			    device_xname(vsc->sc_dev), error, rlen);
19550517e57Snonaka 		}
19650517e57Snonaka 		return;
19750517e57Snonaka 	}
19850517e57Snonaka 	if (rlen < sizeof(*hdr)) {
19950517e57Snonaka 		DPRINTF("%s: hvtimesync short read len=%u\n",
20050517e57Snonaka 		    device_xname(vsc->sc_dev), rlen);
20150517e57Snonaka 		return;
20250517e57Snonaka 	}
20350517e57Snonaka 
20450517e57Snonaka 	hdr = (struct vmbus_icmsg_hdr *)vsc->sc_buf;
20550517e57Snonaka 	switch (hdr->ic_type) {
20650517e57Snonaka 	case VMBUS_ICMSG_TYPE_NEGOTIATE:
20750517e57Snonaka 		error = vmbusic_negotiate(vsc, hdr, &rlen, VMBUS_TIMESYNC_FWVER,
20850517e57Snonaka 		    VMBUS_TIMESYNC_MSGVER);
20950517e57Snonaka 		if (error)
21050517e57Snonaka 			return;
21150517e57Snonaka 		if (VMBUS_TIMESYNC_DORTT(sc)) {
21250517e57Snonaka 			DPRINTF("%s: RTT\n", device_xname(vsc->sc_dev));
21350517e57Snonaka 		}
21450517e57Snonaka 		break;
21550517e57Snonaka 
21650517e57Snonaka 	case VMBUS_ICMSG_TYPE_TIMESYNC:
21750517e57Snonaka 		if (VMBUS_TIMESYNC_MSGVER4(sc)) {
21850517e57Snonaka 			struct vmbus_icmsg_timesync4 *msg4;
21950517e57Snonaka 
22050517e57Snonaka 			if (rlen < sizeof(*msg4)) {
22150517e57Snonaka 				DPRINTF("%s: invalid timesync4 len=%u\n",
22250517e57Snonaka 				    device_xname(vsc->sc_dev), rlen);
22350517e57Snonaka 				return;
22450517e57Snonaka 			}
22550517e57Snonaka 
22650517e57Snonaka 			msg4 = (struct vmbus_icmsg_timesync4 *)hdr;
22750517e57Snonaka 			do_timesync(sc, msg4->ic_hvtime, msg4->ic_sent_tc,
22850517e57Snonaka 			    msg4->ic_tsflags);
22950517e57Snonaka 		} else {
23050517e57Snonaka 			struct vmbus_icmsg_timesync *msg;
23150517e57Snonaka 
23250517e57Snonaka 			if (rlen < sizeof(*msg)) {
23350517e57Snonaka 				DPRINTF("%s: invalid timesync len=%u\n",
23450517e57Snonaka 				    device_xname(vsc->sc_dev), rlen);
23550517e57Snonaka 				return;
23650517e57Snonaka 			}
23750517e57Snonaka 
23850517e57Snonaka 			msg = (struct vmbus_icmsg_timesync *)hdr;
23950517e57Snonaka 			do_timesync(sc, msg->ic_hvtime, 0, msg->ic_tsflags);
24050517e57Snonaka 		}
24150517e57Snonaka 		break;
24250517e57Snonaka 
24350517e57Snonaka 	default:
24450517e57Snonaka 		device_printf(vsc->sc_dev,
24550517e57Snonaka 		    "unhandled _timesync message type %u\n", hdr->ic_type);
24650517e57Snonaka 		return;
24750517e57Snonaka 	}
24850517e57Snonaka 
24950517e57Snonaka 	(void) vmbusic_sendresp(vsc, ch, vsc->sc_buf, rlen, rid);
25050517e57Snonaka }
25150517e57Snonaka 
25250517e57Snonaka static int
hvtimesync_sysctl_setup(device_t self)25350517e57Snonaka hvtimesync_sysctl_setup(device_t self)
25450517e57Snonaka {
25550517e57Snonaka 	struct hvtimesync_softc *sc = device_private(self);
25650517e57Snonaka 	struct vmbusic_softc *vsc = &sc->sc_vmbusic;
25750517e57Snonaka 	const struct sysctlnode *node;
25850517e57Snonaka 	int error;
25950517e57Snonaka 
26050517e57Snonaka 	error = sysctl_createv(NULL, 0, NULL, &node,
26150517e57Snonaka 	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
26250517e57Snonaka 	    NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
26350517e57Snonaka 	if (error)
26450517e57Snonaka 		return error;
26550517e57Snonaka 	error = sysctl_createv(NULL, 0, &node, &node,
26650517e57Snonaka 	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "hyperv", NULL,
26750517e57Snonaka 	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
26850517e57Snonaka 	if (error)
26950517e57Snonaka 		return error;
27050517e57Snonaka 
27150517e57Snonaka 	error = sysctl_createv(&vsc->sc_log, 0, &node, &node,
27250517e57Snonaka 	    0, CTLTYPE_NODE, "timesync", NULL,
27350517e57Snonaka 	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
27450517e57Snonaka 	if (error)
27550517e57Snonaka 		return error;
27650517e57Snonaka 
27750517e57Snonaka 	error = sysctl_createv(&vsc->sc_log, 0, &node, NULL,
27850517e57Snonaka 	    CTLFLAG_READWRITE,
27950517e57Snonaka 	    CTLTYPE_INT, "ignore_sync", NULL,
28050517e57Snonaka 	    NULL, 0, &hvtimesync_ignore_sync, 0,
28150517e57Snonaka 	    CTL_CREATE, CTL_EOL);
28250517e57Snonaka 	if (error)
28350517e57Snonaka 		return error;
28450517e57Snonaka 	error = sysctl_createv(&vsc->sc_log, 0, &node, NULL,
28550517e57Snonaka 	    CTLFLAG_READWRITE,
28650517e57Snonaka 	    CTLTYPE_INT, "sample_verbose", NULL,
287*25af6d8fSandvar 	    NULL, 0, &hvtimesync_sample_verbose, 0,
28850517e57Snonaka 	    CTL_CREATE, CTL_EOL);
28950517e57Snonaka 	if (error)
29050517e57Snonaka 		return error;
29150517e57Snonaka 	error = sysctl_createv(&vsc->sc_log, 0, &node, NULL,
29250517e57Snonaka 	    CTLFLAG_READWRITE,
29350517e57Snonaka 	    CTLTYPE_INT, "sample_thresh", NULL,
29450517e57Snonaka 	    NULL, 0, &hvtimesync_sample_thresh, 0,
29550517e57Snonaka 	    CTL_CREATE, CTL_EOL);
29650517e57Snonaka 	if (error)
29750517e57Snonaka 		return error;
29850517e57Snonaka 
29950517e57Snonaka 	return 0;
30050517e57Snonaka }
30150517e57Snonaka 
30250517e57Snonaka MODULE(MODULE_CLASS_DRIVER, hvtimesync, "vmbus");
30350517e57Snonaka 
30450517e57Snonaka #ifdef _MODULE
30550517e57Snonaka #include "ioconf.c"
30650517e57Snonaka #endif
30750517e57Snonaka 
30850517e57Snonaka static int
hvtimesync_modcmd(modcmd_t cmd,void * aux)30950517e57Snonaka hvtimesync_modcmd(modcmd_t cmd, void *aux)
31050517e57Snonaka {
31150517e57Snonaka 	int error = 0;
31250517e57Snonaka 
31350517e57Snonaka 	switch (cmd) {
31450517e57Snonaka 	case MODULE_CMD_INIT:
31550517e57Snonaka #ifdef _MODULE
31650517e57Snonaka 		error = config_init_component(cfdriver_ioconf_hvtimesync,
31750517e57Snonaka 		    cfattach_ioconf_hvtimesync, cfdata_ioconf_hvtimesync);
31850517e57Snonaka #endif
31950517e57Snonaka 		break;
32050517e57Snonaka 
32150517e57Snonaka 	case MODULE_CMD_FINI:
32250517e57Snonaka #ifdef _MODULE
32350517e57Snonaka 		error = config_fini_component(cfdriver_ioconf_hvtimesync,
32450517e57Snonaka 		    cfattach_ioconf_hvtimesync, cfdata_ioconf_hvtimesync);
32550517e57Snonaka #endif
32650517e57Snonaka 		break;
32750517e57Snonaka 
32850517e57Snonaka 	case MODULE_CMD_AUTOUNLOAD:
32950517e57Snonaka 		error = EBUSY;
33050517e57Snonaka 		break;
33150517e57Snonaka 
33250517e57Snonaka 	default:
33350517e57Snonaka 		error = ENOTTY;
33450517e57Snonaka 		break;
33550517e57Snonaka 	}
33650517e57Snonaka 
33750517e57Snonaka 	return error;
33850517e57Snonaka }
339