1 /* $OpenBSD: vmmci.c,v 1.6 2019/12/31 01:26:56 jsg Exp $ */ 2 3 /* 4 * Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> 20 #include <sys/systm.h> 21 #include <sys/kernel.h> 22 #include <sys/timeout.h> 23 #include <sys/signalvar.h> 24 #include <sys/syslog.h> 25 #include <sys/device.h> 26 #include <sys/pool.h> 27 #include <sys/proc.h> 28 #include <sys/sensors.h> 29 30 #include <machine/bus.h> 31 32 #include <dev/pv/virtioreg.h> 33 #include <dev/pv/virtiovar.h> 34 #include <dev/pv/pvvar.h> 35 #include <dev/rndvar.h> 36 37 enum vmmci_cmd { 38 VMMCI_NONE = 0, 39 VMMCI_SHUTDOWN, 40 VMMCI_REBOOT, 41 VMMCI_SYNCRTC, 42 }; 43 44 struct vmmci_softc { 45 struct device sc_dev; 46 struct virtio_softc *sc_virtio; 47 enum vmmci_cmd sc_cmd; 48 unsigned int sc_interval; 49 struct ksensordev sc_sensordev; 50 struct ksensor sc_sensor; 51 struct timeout sc_tick; 52 }; 53 54 int vmmci_match(struct device *, void *, void *); 55 void vmmci_attach(struct device *, struct device *, void *); 56 int vmmci_activate(struct device *, int); 57 58 int vmmci_config_change(struct virtio_softc *); 59 void vmmci_tick(void *); 60 void vmmci_tick_hook(struct device *); 61 62 struct cfattach vmmci_ca = { 63 sizeof(struct vmmci_softc), 64 vmmci_match, 65 vmmci_attach, 66 NULL, 67 vmmci_activate 68 }; 69 70 /* Configuration registers */ 71 #define VMMCI_CONFIG_COMMAND 0 72 #define VMMCI_CONFIG_TIME_SEC 4 73 #define VMMCI_CONFIG_TIME_USEC 12 74 75 /* Feature bits */ 76 #define VMMCI_F_TIMESYNC (1ULL<<0) 77 #define VMMCI_F_ACK (1ULL<<1) 78 #define VMMCI_F_SYNCRTC (1ULL<<2) 79 80 struct cfdriver vmmci_cd = { 81 NULL, "vmmci", DV_DULL 82 }; 83 84 int 85 vmmci_match(struct device *parent, void *match, void *aux) 86 { 87 struct virtio_softc *va = aux; 88 if (va->sc_childdevid == PCI_PRODUCT_VIRTIO_VMMCI) 89 return (1); 90 return (0); 91 } 92 93 void 94 vmmci_attach(struct device *parent, struct device *self, void *aux) 95 { 96 struct vmmci_softc *sc = (struct vmmci_softc *)self; 97 struct virtio_softc *vsc = (struct virtio_softc *)parent; 98 99 if (vsc->sc_child != NULL) 100 panic("already attached to something else"); 101 102 vsc->sc_child = self; 103 vsc->sc_nvqs = 0; 104 vsc->sc_config_change = vmmci_config_change; 105 vsc->sc_ipl = IPL_NET; 106 sc->sc_virtio = vsc; 107 108 vsc->sc_driver_features = VMMCI_F_TIMESYNC | VMMCI_F_ACK | 109 VMMCI_F_SYNCRTC; 110 virtio_negotiate_features(vsc, NULL); 111 112 if (virtio_has_feature(vsc, VMMCI_F_TIMESYNC)) { 113 strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, 114 sizeof(sc->sc_sensordev.xname)); 115 sc->sc_sensor.type = SENSOR_TIMEDELTA; 116 sc->sc_sensor.status = SENSOR_S_UNKNOWN; 117 sensor_attach(&sc->sc_sensordev, &sc->sc_sensor); 118 sensordev_install(&sc->sc_sensordev); 119 120 config_mountroot(self, vmmci_tick_hook); 121 } 122 123 printf("\n"); 124 } 125 126 int 127 vmmci_activate(struct device *self, int act) 128 { 129 struct vmmci_softc *sc = (struct vmmci_softc *)self; 130 struct virtio_softc *vsc = sc->sc_virtio; 131 132 if (virtio_has_feature(vsc, VMMCI_F_ACK) == 0) 133 return (0); 134 135 switch (act) { 136 case DVACT_POWERDOWN: 137 printf("%s: powerdown\n", sc->sc_dev.dv_xname); 138 139 /* 140 * Tell the host that we are shutting down. The host will 141 * start a timer and kill our VM if we didn't reboot before 142 * expiration. This avoids being stuck in the 143 * "Please press any key to reboot" handler on RB_HALT; 144 * without hooking into the MD code directly. 145 */ 146 virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, 147 VMMCI_SHUTDOWN); 148 break; 149 default: 150 break; 151 } 152 return (0); 153 } 154 155 int 156 vmmci_config_change(struct virtio_softc *vsc) 157 { 158 struct vmmci_softc *sc = (struct vmmci_softc *)vsc->sc_child; 159 uint32_t cmd; 160 161 /* Check for command */ 162 cmd = virtio_read_device_config_4(vsc, VMMCI_CONFIG_COMMAND); 163 if (cmd == sc->sc_cmd) 164 return (0); 165 sc->sc_cmd = cmd; 166 167 switch (cmd) { 168 case VMMCI_NONE: 169 /* no action */ 170 break; 171 case VMMCI_SHUTDOWN: 172 pvbus_shutdown(&sc->sc_dev); 173 break; 174 case VMMCI_REBOOT: 175 pvbus_reboot(&sc->sc_dev); 176 break; 177 case VMMCI_SYNCRTC: 178 inittodr(time_second); 179 sc->sc_cmd = VMMCI_NONE; 180 break; 181 default: 182 printf("%s: invalid command %d\n", sc->sc_dev.dv_xname, cmd); 183 cmd = VMMCI_NONE; 184 break; 185 } 186 187 if ((cmd != VMMCI_NONE) && virtio_has_feature(vsc, VMMCI_F_ACK)) 188 virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, cmd); 189 190 return (1); 191 } 192 193 void 194 vmmci_tick(void *arg) 195 { 196 struct vmmci_softc *sc = arg; 197 struct virtio_softc *vsc = sc->sc_virtio; 198 struct timeval *guest = &sc->sc_sensor.tv; 199 struct timeval host, diff; 200 201 microtime(guest); 202 203 /* Update time delta sensor */ 204 host.tv_sec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_SEC); 205 host.tv_usec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_USEC); 206 207 if (host.tv_usec > 0) { 208 timersub(guest, &host, &diff); 209 210 sc->sc_sensor.value = (uint64_t)diff.tv_sec * 1000000000LL + 211 (uint64_t)diff.tv_usec * 1000LL; 212 sc->sc_sensor.status = SENSOR_S_OK; 213 } else 214 sc->sc_sensor.status = SENSOR_S_UNKNOWN; 215 216 timeout_add_sec(&sc->sc_tick, 15); 217 } 218 219 void 220 vmmci_tick_hook(struct device *self) 221 { 222 struct vmmci_softc *sc = (struct vmmci_softc *)self; 223 224 timeout_set(&sc->sc_tick, vmmci_tick, sc); 225 vmmci_tick(sc); 226 } 227