xref: /openbsd/sys/dev/pv/vmmci.c (revision 73471bf0)
1 /*	$OpenBSD: vmmci.c,v 1.9 2021/11/05 11:38:29 mpi 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 
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 const 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 
98 	if (vsc->sc_child != NULL)
99 		panic("already attached to something else");
100 
101 	vsc->sc_child = self;
102 	vsc->sc_nvqs = 0;
103 	vsc->sc_config_change = vmmci_config_change;
104 	vsc->sc_ipl = IPL_NET;
105 	sc->sc_virtio = vsc;
106 
107 	vsc->sc_driver_features = VMMCI_F_TIMESYNC | VMMCI_F_ACK |
108 	    VMMCI_F_SYNCRTC;
109 	virtio_negotiate_features(vsc, NULL);
110 
111 	if (virtio_has_feature(vsc, 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 (virtio_has_feature(vsc, 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(gettime());
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) && virtio_has_feature(vsc, VMMCI_F_ACK))
187 		virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, cmd);
188 
189 	return (1);
190 }
191 
192 void
193 vmmci_tick(void *arg)
194 {
195 	struct vmmci_softc	*sc = arg;
196 	struct virtio_softc	*vsc = sc->sc_virtio;
197 	struct timeval		*guest = &sc->sc_sensor.tv;
198 	struct timeval		 host, diff;
199 
200 	microtime(guest);
201 
202 	/* Update time delta sensor */
203 	host.tv_sec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_SEC);
204 	host.tv_usec = virtio_read_device_config_8(vsc, VMMCI_CONFIG_TIME_USEC);
205 
206 	if (host.tv_usec > 0) {
207 		timersub(guest, &host, &diff);
208 
209 		sc->sc_sensor.value = (uint64_t)diff.tv_sec * 1000000000LL +
210 		    (uint64_t)diff.tv_usec * 1000LL;
211 		sc->sc_sensor.status = SENSOR_S_OK;
212 	} else
213 		sc->sc_sensor.status = SENSOR_S_UNKNOWN;
214 
215 	timeout_add_sec(&sc->sc_tick, 15);
216 }
217 
218 void
219 vmmci_tick_hook(struct device *self)
220 {
221 	struct vmmci_softc	*sc = (struct vmmci_softc *)self;
222 
223 	timeout_set(&sc->sc_tick, vmmci_tick, sc);
224 	vmmci_tick(sc);
225 }
226