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
vmmci_match(struct device * parent,void * match,void * aux)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
vmmci_attach(struct device * parent,struct device * self,void * aux)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
vmmci_activate(struct device * self,int act)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
vmmci_config_change(struct virtio_softc * vsc)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
vmmci_tick(void * arg)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
vmmci_tick_hook(struct device * self)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