1 /* $OpenBSD: vmmci.c,v 1.13 2024/12/20 22:18:27 sf 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/timeout.h> 22 #include <sys/device.h> 23 #include <sys/sensors.h> 24 25 #include <machine/bus.h> 26 27 #include <dev/pv/virtioreg.h> 28 #include <dev/pv/virtiovar.h> 29 #include <dev/pv/pvvar.h> 30 31 enum vmmci_cmd { 32 VMMCI_NONE = 0, 33 VMMCI_SHUTDOWN, 34 VMMCI_REBOOT, 35 VMMCI_SYNCRTC, 36 }; 37 38 struct vmmci_softc { 39 struct device sc_dev; 40 struct virtio_softc *sc_virtio; 41 enum vmmci_cmd sc_cmd; 42 unsigned int sc_interval; 43 struct ksensordev sc_sensordev; 44 struct ksensor sc_sensor; 45 struct timeout sc_tick; 46 }; 47 48 int vmmci_match(struct device *, void *, void *); 49 void vmmci_attach(struct device *, struct device *, void *); 50 int vmmci_activate(struct device *, int); 51 52 int vmmci_config_change(struct virtio_softc *); 53 void vmmci_tick(void *); 54 void vmmci_tick_hook(struct device *); 55 56 const struct cfattach vmmci_ca = { 57 sizeof(struct vmmci_softc), 58 vmmci_match, 59 vmmci_attach, 60 NULL, 61 vmmci_activate 62 }; 63 64 /* Configuration registers */ 65 #define VMMCI_CONFIG_COMMAND 0 66 #define VMMCI_CONFIG_TIME_SEC 4 67 #define VMMCI_CONFIG_TIME_USEC 12 68 69 /* Feature bits */ 70 #define VMMCI_F_TIMESYNC (1ULL<<0) 71 #define VMMCI_F_ACK (1ULL<<1) 72 #define VMMCI_F_SYNCRTC (1ULL<<2) 73 74 struct cfdriver vmmci_cd = { 75 NULL, "vmmci", DV_DULL 76 }; 77 78 int 79 vmmci_match(struct device *parent, void *match, void *aux) 80 { 81 struct virtio_attach_args *va = aux; 82 if (va->va_devid == PCI_PRODUCT_VIRTIO_VMMCI) 83 return (1); 84 return (0); 85 } 86 87 void 88 vmmci_attach(struct device *parent, struct device *self, void *aux) 89 { 90 struct vmmci_softc *sc = (struct vmmci_softc *)self; 91 struct virtio_softc *vsc = (struct virtio_softc *)parent; 92 struct virtio_attach_args *va = aux; 93 94 if (vsc->sc_child != NULL) 95 panic("already attached to something else"); 96 97 vsc->sc_child = self; 98 vsc->sc_nvqs = 0; 99 vsc->sc_config_change = vmmci_config_change; 100 vsc->sc_ipl = IPL_NET; 101 sc->sc_virtio = vsc; 102 103 vsc->sc_driver_features = VMMCI_F_TIMESYNC | VMMCI_F_ACK | 104 VMMCI_F_SYNCRTC; 105 if (virtio_negotiate_features(vsc, NULL) != 0) 106 goto err; 107 108 if (virtio_has_feature(vsc, VMMCI_F_TIMESYNC)) { 109 strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, 110 sizeof(sc->sc_sensordev.xname)); 111 sc->sc_sensor.type = SENSOR_TIMEDELTA; 112 sc->sc_sensor.status = SENSOR_S_UNKNOWN; 113 sensor_attach(&sc->sc_sensordev, &sc->sc_sensor); 114 sensordev_install(&sc->sc_sensordev); 115 116 config_mountroot(self, vmmci_tick_hook); 117 } 118 119 printf("\n"); 120 if (virtio_attach_finish(vsc, va) != 0) 121 goto err; 122 return; 123 124 err: 125 vsc->sc_child = VIRTIO_CHILD_ERROR; 126 } 127 128 int 129 vmmci_activate(struct device *self, int act) 130 { 131 struct vmmci_softc *sc = (struct vmmci_softc *)self; 132 struct virtio_softc *vsc = sc->sc_virtio; 133 134 if (virtio_has_feature(vsc, VMMCI_F_ACK) == 0) 135 return (0); 136 137 switch (act) { 138 case DVACT_POWERDOWN: 139 printf("%s: powerdown\n", sc->sc_dev.dv_xname); 140 141 /* 142 * Tell the host that we are shutting down. The host will 143 * start a timer and kill our VM if we didn't reboot before 144 * expiration. This avoids being stuck in the 145 * "Please press any key to reboot" handler on RB_HALT; 146 * without hooking into the MD code directly. 147 */ 148 virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, 149 VMMCI_SHUTDOWN); 150 break; 151 default: 152 break; 153 } 154 return (0); 155 } 156 157 int 158 vmmci_config_change(struct virtio_softc *vsc) 159 { 160 struct vmmci_softc *sc = (struct vmmci_softc *)vsc->sc_child; 161 uint32_t cmd; 162 163 /* Check for command */ 164 cmd = virtio_read_device_config_4(vsc, VMMCI_CONFIG_COMMAND); 165 if (cmd == sc->sc_cmd) 166 return (0); 167 sc->sc_cmd = cmd; 168 169 switch (cmd) { 170 case VMMCI_NONE: 171 /* no action */ 172 break; 173 case VMMCI_SHUTDOWN: 174 pvbus_shutdown(&sc->sc_dev); 175 break; 176 case VMMCI_REBOOT: 177 pvbus_reboot(&sc->sc_dev); 178 break; 179 case VMMCI_SYNCRTC: 180 inittodr(gettime()); 181 sc->sc_cmd = VMMCI_NONE; 182 break; 183 default: 184 printf("%s: invalid command %d\n", sc->sc_dev.dv_xname, cmd); 185 cmd = VMMCI_NONE; 186 break; 187 } 188 189 if ((cmd != VMMCI_NONE) && virtio_has_feature(vsc, VMMCI_F_ACK)) 190 virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, cmd); 191 192 return (1); 193 } 194 195 void 196 vmmci_tick(void *arg) 197 { 198 struct vmmci_softc *sc = arg; 199 struct virtio_softc *vsc = sc->sc_virtio; 200 struct timeval *guest = &sc->sc_sensor.tv; 201 struct timeval host, diff; 202 203 microtime(guest); 204 205 /* Update time delta sensor */ 206 host.tv_sec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_SEC); 207 host.tv_usec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_USEC); 208 209 if (host.tv_usec > 0) { 210 timersub(guest, &host, &diff); 211 212 sc->sc_sensor.value = (uint64_t)diff.tv_sec * 1000000000LL + 213 (uint64_t)diff.tv_usec * 1000LL; 214 sc->sc_sensor.status = SENSOR_S_OK; 215 } else 216 sc->sc_sensor.status = SENSOR_S_UNKNOWN; 217 218 timeout_add_sec(&sc->sc_tick, 15); 219 } 220 221 void 222 vmmci_tick_hook(struct device *self) 223 { 224 struct vmmci_softc *sc = (struct vmmci_softc *)self; 225 226 timeout_set(&sc->sc_tick, vmmci_tick, sc); 227 vmmci_tick(sc); 228 } 229