xref: /openbsd/sys/dev/pv/vmmci.c (revision 77d0f823)
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