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