xref: /openbsd-src/sys/dev/pv/vmmci.c (revision 4b70baf6e17fc8b27fc1f7fa7929335753fa94c3)
1 /*	$OpenBSD: vmmci.c,v 1.4 2019/03/24 18:21:12 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/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 
29 #include <machine/bus.h>
30 
31 #include <dev/pv/virtioreg.h>
32 #include <dev/pv/virtiovar.h>
33 #include <dev/pv/pvvar.h>
34 #include <dev/rndvar.h>
35 
36 enum vmmci_cmd {
37 	VMMCI_NONE = 0,
38 	VMMCI_SHUTDOWN,
39 	VMMCI_REBOOT,
40 	VMMCI_SYNCRTC,
41 };
42 
43 struct vmmci_softc {
44 	struct device		 sc_dev;
45 	struct virtio_softc	*sc_virtio;
46 	enum vmmci_cmd		 sc_cmd;
47 	unsigned int		 sc_interval;
48 	struct ksensordev	 sc_sensordev;
49 	struct ksensor		 sc_sensor;
50 	struct timeout		 sc_tick;
51 };
52 
53 int	vmmci_match(struct device *, void *, void *);
54 void	vmmci_attach(struct device *, struct device *, void *);
55 int	vmmci_activate(struct device *, int);
56 
57 int	vmmci_config_change(struct virtio_softc *);
58 void	vmmci_tick(void *);
59 void	vmmci_tick_hook(struct device *);
60 
61 struct cfattach vmmci_ca = {
62 	sizeof(struct vmmci_softc),
63 	vmmci_match,
64 	vmmci_attach,
65 	NULL,
66 	vmmci_activate
67 };
68 
69 /* Configuration registers */
70 #define VMMCI_CONFIG_COMMAND	0
71 #define VMMCI_CONFIG_TIME_SEC	4
72 #define VMMCI_CONFIG_TIME_USEC	12
73 
74 /* Feature bits */
75 #define VMMCI_F_TIMESYNC	(1ULL<<0)
76 #define VMMCI_F_ACK		(1ULL<<1)
77 #define VMMCI_F_SYNCRTC		(1ULL<<2)
78 
79 struct cfdriver vmmci_cd = {
80 	NULL, "vmmci", DV_DULL
81 };
82 
83 int
84 vmmci_match(struct device *parent, void *match, void *aux)
85 {
86 	struct virtio_softc *va = aux;
87 	if (va->sc_childdevid == PCI_PRODUCT_VIRTIO_VMMCI)
88 		return (1);
89 	return (0);
90 }
91 
92 void
93 vmmci_attach(struct device *parent, struct device *self, void *aux)
94 {
95 	struct vmmci_softc *sc = (struct vmmci_softc *)self;
96 	struct virtio_softc *vsc = (struct virtio_softc *)parent;
97 	uint64_t features;
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 	features = VMMCI_F_TIMESYNC | VMMCI_F_ACK | VMMCI_F_SYNCRTC;
109 	features = virtio_negotiate_features(vsc, features, NULL);
110 
111 	if (features & VMMCI_F_TIMESYNC) {
112 		strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
113 		    sizeof(sc->sc_sensordev.xname));
114 		sc->sc_sensor.type = SENSOR_TIMEDELTA;
115 		sc->sc_sensor.status = SENSOR_S_UNKNOWN;
116 		sensor_attach(&sc->sc_sensordev, &sc->sc_sensor);
117 		sensordev_install(&sc->sc_sensordev);
118 
119 		config_mountroot(self, vmmci_tick_hook);
120 	}
121 
122 	printf("\n");
123 }
124 
125 int
126 vmmci_activate(struct device *self, int act)
127 {
128 	struct vmmci_softc	*sc = (struct vmmci_softc *)self;
129 	struct virtio_softc	*vsc = sc->sc_virtio;
130 
131 	if ((vsc->sc_features & VMMCI_F_ACK) == 0)
132 		return (0);
133 
134 	switch (act) {
135 	case DVACT_POWERDOWN:
136 		printf("%s: powerdown\n", sc->sc_dev.dv_xname);
137 
138 		/*
139 		 * Tell the host that we are shutting down.  The host will
140 		 * start a timer and kill our VM if we didn't reboot before
141 		 * expiration.  This avoids being stuck in the
142 		 * "Please press any key to reboot" handler on RB_HALT;
143 		 * without hooking into the MD code directly.
144 		 */
145 		virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND,
146 		    VMMCI_SHUTDOWN);
147 		break;
148 	default:
149 		break;
150 	}
151 	return (0);
152 }
153 
154 int
155 vmmci_config_change(struct virtio_softc *vsc)
156 {
157 	struct vmmci_softc	*sc = (struct vmmci_softc *)vsc->sc_child;
158 	uint32_t		 cmd;
159 
160 	/* Check for command */
161 	cmd = virtio_read_device_config_4(vsc, VMMCI_CONFIG_COMMAND);
162 	if (cmd == sc->sc_cmd)
163 		return (0);
164 	sc->sc_cmd = cmd;
165 
166 	switch (cmd) {
167 	case VMMCI_NONE:
168 		/* no action */
169 		break;
170 	case VMMCI_SHUTDOWN:
171 		pvbus_shutdown(&sc->sc_dev);
172 		break;
173 	case VMMCI_REBOOT:
174 		pvbus_reboot(&sc->sc_dev);
175 		break;
176 	case VMMCI_SYNCRTC:
177 		inittodr(time_second);
178 		sc->sc_cmd = VMMCI_NONE;
179 		break;
180 	default:
181 		printf("%s: invalid command %d\n", sc->sc_dev.dv_xname, cmd);
182 		cmd = VMMCI_NONE;
183 		break;
184 	}
185 
186 	if ((cmd != VMMCI_NONE) &&
187 	    (vsc->sc_features & 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