/* $OpenBSD: vmmci.c,v 1.11 2024/05/24 10:05:55 jsg Exp $ */ /* * Copyright (c) 2017 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include enum vmmci_cmd { VMMCI_NONE = 0, VMMCI_SHUTDOWN, VMMCI_REBOOT, VMMCI_SYNCRTC, }; struct vmmci_softc { struct device sc_dev; struct virtio_softc *sc_virtio; enum vmmci_cmd sc_cmd; unsigned int sc_interval; struct ksensordev sc_sensordev; struct ksensor sc_sensor; struct timeout sc_tick; }; int vmmci_match(struct device *, void *, void *); void vmmci_attach(struct device *, struct device *, void *); int vmmci_activate(struct device *, int); int vmmci_config_change(struct virtio_softc *); void vmmci_tick(void *); void vmmci_tick_hook(struct device *); const struct cfattach vmmci_ca = { sizeof(struct vmmci_softc), vmmci_match, vmmci_attach, NULL, vmmci_activate }; /* Configuration registers */ #define VMMCI_CONFIG_COMMAND 0 #define VMMCI_CONFIG_TIME_SEC 4 #define VMMCI_CONFIG_TIME_USEC 12 /* Feature bits */ #define VMMCI_F_TIMESYNC (1ULL<<0) #define VMMCI_F_ACK (1ULL<<1) #define VMMCI_F_SYNCRTC (1ULL<<2) struct cfdriver vmmci_cd = { NULL, "vmmci", DV_DULL }; int vmmci_match(struct device *parent, void *match, void *aux) { struct virtio_softc *va = aux; if (va->sc_childdevid == PCI_PRODUCT_VIRTIO_VMMCI) return (1); return (0); } void vmmci_attach(struct device *parent, struct device *self, void *aux) { struct vmmci_softc *sc = (struct vmmci_softc *)self; struct virtio_softc *vsc = (struct virtio_softc *)parent; if (vsc->sc_child != NULL) panic("already attached to something else"); vsc->sc_child = self; vsc->sc_nvqs = 0; vsc->sc_config_change = vmmci_config_change; vsc->sc_ipl = IPL_NET; sc->sc_virtio = vsc; vsc->sc_driver_features = VMMCI_F_TIMESYNC | VMMCI_F_ACK | VMMCI_F_SYNCRTC; virtio_negotiate_features(vsc, NULL); if (virtio_has_feature(vsc, VMMCI_F_TIMESYNC)) { strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, sizeof(sc->sc_sensordev.xname)); sc->sc_sensor.type = SENSOR_TIMEDELTA; sc->sc_sensor.status = SENSOR_S_UNKNOWN; sensor_attach(&sc->sc_sensordev, &sc->sc_sensor); sensordev_install(&sc->sc_sensordev); config_mountroot(self, vmmci_tick_hook); } printf("\n"); virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK); } int vmmci_activate(struct device *self, int act) { struct vmmci_softc *sc = (struct vmmci_softc *)self; struct virtio_softc *vsc = sc->sc_virtio; if (virtio_has_feature(vsc, VMMCI_F_ACK) == 0) return (0); switch (act) { case DVACT_POWERDOWN: printf("%s: powerdown\n", sc->sc_dev.dv_xname); /* * Tell the host that we are shutting down. The host will * start a timer and kill our VM if we didn't reboot before * expiration. This avoids being stuck in the * "Please press any key to reboot" handler on RB_HALT; * without hooking into the MD code directly. */ virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, VMMCI_SHUTDOWN); break; default: break; } return (0); } int vmmci_config_change(struct virtio_softc *vsc) { struct vmmci_softc *sc = (struct vmmci_softc *)vsc->sc_child; uint32_t cmd; /* Check for command */ cmd = virtio_read_device_config_4(vsc, VMMCI_CONFIG_COMMAND); if (cmd == sc->sc_cmd) return (0); sc->sc_cmd = cmd; switch (cmd) { case VMMCI_NONE: /* no action */ break; case VMMCI_SHUTDOWN: pvbus_shutdown(&sc->sc_dev); break; case VMMCI_REBOOT: pvbus_reboot(&sc->sc_dev); break; case VMMCI_SYNCRTC: inittodr(gettime()); sc->sc_cmd = VMMCI_NONE; break; default: printf("%s: invalid command %d\n", sc->sc_dev.dv_xname, cmd); cmd = VMMCI_NONE; break; } if ((cmd != VMMCI_NONE) && virtio_has_feature(vsc, VMMCI_F_ACK)) virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, cmd); return (1); } void vmmci_tick(void *arg) { struct vmmci_softc *sc = arg; struct virtio_softc *vsc = sc->sc_virtio; struct timeval *guest = &sc->sc_sensor.tv; struct timeval host, diff; microtime(guest); /* Update time delta sensor */ host.tv_sec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_SEC); host.tv_usec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_USEC); if (host.tv_usec > 0) { timersub(guest, &host, &diff); sc->sc_sensor.value = (uint64_t)diff.tv_sec * 1000000000LL + (uint64_t)diff.tv_usec * 1000LL; sc->sc_sensor.status = SENSOR_S_OK; } else sc->sc_sensor.status = SENSOR_S_UNKNOWN; timeout_add_sec(&sc->sc_tick, 15); } void vmmci_tick_hook(struct device *self) { struct vmmci_softc *sc = (struct vmmci_softc *)self; timeout_set(&sc->sc_tick, vmmci_tick, sc); vmmci_tick(sc); }