1 /*
2  * Copyright (C) 2018-2022 Intel Corporation.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  *
6  */
7 
8 /*
9  * The interface of virtio_coreu is obsoleted.
10  * We reserve these code for future.
11  */
12 
13 /*
14  * CoreU Virtualization
15  *
16  * CoreU is for PAVP session management. CoreU contains two parts, the daemon
17  * and library used by libpavp. When playback premium content which should be
18  * protected in GPU memory, media application calls libpavp which uses MEI to
19  * create PAVP session. CoreU daemon has the capability to read GPU registers
20  * to know whether the PAVP session status is valid. For now, CoreU is not open
21  * source.
22  *
23  *              +---------------+
24  *              |               |
25  *   +----------+----------+    |    +------------------+
26  *   |       ACRN DM       |    |    |     Media APP    |
27  *   | +-----------------+ |    |    +------------------+
28  *   | |  CoreU Backend  | |    |              |
29  *   | +-----------------+ |    |    +------------------+
30  *   +---------------------+    |    |      LibPAVP     |
31  *              |               |    +------------------+
32  *              |               |              |
33  *     +------------------+     |    +------------------+
34  *     | CoreU Service VM |     |    | CoreU User VM    |
35  *     |      Daemon      |     |    |      Daemon      |
36  *     +------------------+     |    +------------------+
37  *                              |
38  *    Service VM User Space     |     User VM User Space
39  *                              |
40  *  --------------------------  |  ---------------------------
41  *                              |
42  *   Service VM Kernel Space    |    User VM Kernel Space
43  *                              |
44  *                              |    +------------------+
45  *                              |    |  CoreU Frontend  |
46  *                              |    +------------------+
47  *                              |             |
48  *                              +-------------+
49  *
50  * Above diagram illustrates the CoreU architecture in ACRN. In Service VM, CoreU
51  * daemon starts upon the system boots. In User VM, CoreU daemon gets the PAVP
52  * session status by open/read/write /dev/coreu0 which is created by CoreU
53  * frontend, instead of accessing GPU. Then the CoreU frontend sends the
54  * requests to the CoreU backend thru virtio mechanism. CoreU backend talks to
55  * CoreU Service VM daemon to get the PAVP session status.
56  *
57  */
58 
59 #include <sys/cdefs.h>
60 #include <sys/param.h>
61 #include <sys/uio.h>
62 #include <err.h>
63 #include <errno.h>
64 #include <fcntl.h>
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <unistd.h>
69 #include <pthread.h>
70 #include <sysexits.h>
71 #include <dlfcn.h>
72 #include <sys/socket.h>
73 #include <sys/un.h>
74 
75 #include "dm.h"
76 #include "pci_core.h"
77 #include "virtio.h"
78 
79 #define VIRTIO_COREU_RINGSZ	64
80 #define COREU_MSG_SIZE		72
81 #define VIRTIO_COREU_NUMQ	1
82 #define COREU_SERVICE_NAME	"/var/run/coreu"
83 
84 /* CoreU raw message */
85 struct coreu_msg {
86 	uint8_t bytes[COREU_MSG_SIZE];
87 };
88 
89 /* Per-device struct - VBS-U */
90 struct virtio_coreu {
91 	struct virtio_base		base;
92 	struct virtio_vq_info		queues[VIRTIO_COREU_NUMQ];
93 	pthread_mutex_t			mtx;
94 
95 	pthread_t rx_tid;
96 	pthread_mutex_t	rx_mtx;
97 	pthread_cond_t rx_cond;
98 
99 	/* socket handle to CoreU daemon */
100 	int fd;
101 };
102 
103 /* VBS-U virtio_ops */
104 static void virtio_coreu_reset(void *);
105 
106 static struct virtio_ops virtio_coreu_ops = {
107 	"virtio_coreu",			/* our name */
108 	VIRTIO_COREU_NUMQ,		/* we support one virtqueue */
109 	0,				/* config reg size */
110 	virtio_coreu_reset,		/* reset */
111 	NULL,				/* device-wide qnotify */
112 	NULL,				/* read virtio config */
113 	NULL,				/* write virtio config */
114 	NULL,				/* apply negotiated features */
115 	NULL,				/* called on guest set status */
116 };
117 
118 /* Debug printf */
119 static int virtio_coreu_debug;
120 #define DPRINTF(params) do { if (virtio_coreu_debug) pr_dbg params; } while (0)
121 #define WPRINTF(params) (pr_err params)
122 
123 static void
virtio_coreu_reset(void * vdev)124 virtio_coreu_reset(void *vdev)
125 {
126 	struct virtio_coreu *vcoreu = vdev;
127 
128 	DPRINTF(("virtio_coreu: device reset requested\n"));
129 	virtio_reset_dev(&vcoreu->base);
130 }
131 
132 static int
send_and_receive(int fd,struct coreu_msg * msg)133 send_and_receive(int fd, struct coreu_msg *msg)
134 {
135 	uint32_t msg_size = sizeof(struct coreu_msg);
136 	int ret;
137 
138 	ret = send(fd, (void *)msg, msg_size, 0);
139 	if (ret < 0) {
140 		WPRINTF(("send error\n"));
141 		return ret;
142 	}
143 
144 	ret = recv(fd, (void *)msg, msg_size, 0);
145 	if (ret < 0) {
146 		WPRINTF(("recv error\n"));
147 		return ret;
148 	}
149 
150 	if (ret < msg_size) {
151 		WPRINTF(("received part of the data, %d instead of %d\n",
152 			ret,
153 			msg_size));
154 		return ret;
155 	}
156 
157 	return 0;
158 }
159 
160 static void *
virtio_coreu_thread(void * param)161 virtio_coreu_thread(void *param)
162 {
163 	struct virtio_coreu *vcoreu = param;
164 	struct virtio_vq_info *rvq = &vcoreu->queues[0];
165 	struct iovec iov;
166 	uint16_t idx;
167 	int ret;
168 	struct coreu_msg *msg;
169 
170 	for (;;) {
171 		ret = 0;
172 		pthread_mutex_lock(&vcoreu->rx_mtx);
173 
174 		/*
175 		 * Checking the avail ring here serves two purposes:
176 		 *  - avoid vring processing due to spurious wakeups
177 		 *  - catch missing notifications before acquiring rx_mtx
178 		 */
179 		while (!ret && !vq_has_descs(rvq))
180 			ret = pthread_cond_wait(&vcoreu->rx_cond, &vcoreu->rx_mtx);
181 
182 		pthread_mutex_unlock(&vcoreu->rx_mtx);
183 
184 		if (ret)
185 			break;
186 
187 		do {
188 			ret = vq_getchain(rvq, &idx, &iov, 1, NULL);
189 			if (ret < 1) {
190 				pr_err("%s: fail to getchain!\n", __func__);
191 				return NULL;
192 			}
193 			if (ret != 1) {
194 				pr_warn("%s: invalid chain!\n", __func__);
195 				vq_relchain(rvq, idx, 0);
196 				continue;
197 			}
198 
199 			msg = (struct coreu_msg *)(iov.iov_base);
200 
201 			ret = send_and_receive(vcoreu->fd, msg);
202 			if (ret < 0) {
203 				close(vcoreu->fd);
204 				vcoreu->fd = -1;
205 			}
206 
207 			/* release this chain and handle more */
208 			vq_relchain(rvq, idx, sizeof(struct coreu_msg));
209 		} while (vq_has_descs(rvq));
210 
211 		/* at least one avail ring element has been processed */
212 		vq_endchains(rvq, 1);
213 	}
214 
215 	pthread_exit(NULL);
216 }
217 
218 static int
connect_coreu_daemon()219 connect_coreu_daemon()
220 {
221 	struct sockaddr_un addr;
222 	int msg_size;
223 	int fd;
224 	int ret;
225 
226 	fd = socket(AF_UNIX, SOCK_STREAM, 0);
227 	if (fd < 0) {
228 		WPRINTF(("socket error %d\n", errno));
229 		return -1;
230 	}
231 
232 	memset(&addr, 0, sizeof(addr));
233 	addr.sun_family = AF_UNIX;
234 	strncpy(addr.sun_path, COREU_SERVICE_NAME, sizeof(addr.sun_path));
235 
236 	ret = connect(fd, &addr, sizeof(struct sockaddr_un));
237 	if (ret < 0) {
238 		WPRINTF(("connect error %d\n", errno));
239 		close(fd);
240 		return -1;
241 	}
242 
243 	msg_size = sizeof(struct coreu_msg);
244 	ret = setsockopt(fd, SOL_SOCKET,
245 		SO_RCVLOWAT, &msg_size, sizeof(msg_size));
246 	if (ret < 0) {
247 		WPRINTF(("setsockopt error\n"));
248 		close(fd);
249 		return -1;
250 	}
251 	return fd;
252 }
253 
254 static void
virtio_coreu_notify(void * vdev,struct virtio_vq_info * vq)255 virtio_coreu_notify(void *vdev, struct virtio_vq_info *vq)
256 {
257 	struct virtio_coreu *vcoreu = vdev;
258 
259 	/* Any ring entries to process */
260 	if (!vq_has_descs(vq))
261 		return;
262 
263 	vcoreu->fd = (vcoreu->fd < 0) ? connect_coreu_daemon() : vcoreu->fd;
264 	if (vcoreu->fd < 0)
265 	{
266 		WPRINTF(("Invalid CoreU daemon file descriptor\n"));
267 		return;
268 	}
269 
270 	/* Signal the thread for processing */
271 	pthread_mutex_lock(&vcoreu->rx_mtx);
272 	pthread_cond_signal(&vcoreu->rx_cond);
273 	pthread_mutex_unlock(&vcoreu->rx_mtx);
274 }
275 
276 static int
virtio_coreu_init(struct vmctx * ctx,struct pci_vdev * dev,char * opts)277 virtio_coreu_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
278 {
279 	struct virtio_coreu *vcoreu;
280 	pthread_mutexattr_t attr;
281 	char tname[MAXCOMLEN + 1];
282 	int rc;
283 
284 	vcoreu = calloc(1, sizeof(struct virtio_coreu));
285 	if (!vcoreu) {
286 		WPRINTF(("vcoreu init: fail to alloc virtio_coreu\n"));
287 		return -1;
288 	}
289 
290 	/* init mutex attribute properly */
291 	int mutexattr_type = virtio_uses_msix()
292 			? PTHREAD_MUTEX_DEFAULT
293 			: PTHREAD_MUTEX_RECURSIVE;
294 
295 	rc = pthread_mutexattr_init(&attr);
296 	if (rc)
297 		WPRINTF(("vcoreu init: mutexattr init fail, erro %d\n", rc));
298 	rc = pthread_mutexattr_settype(&attr, mutexattr_type);
299 	if (rc)
300 		WPRINTF(("vcoreu init: mutexattr_settype fail, erro %d\n", rc));
301 	rc = pthread_mutex_init(&vcoreu->mtx, &attr);
302 	if (rc)
303 		WPRINTF(("vcoreu init: mutexattr_settype fail, erro %d\n", rc));
304 
305 	DPRINTF(("vcoreu init: using VBS-U...\n"));
306 	virtio_linkup(&vcoreu->base, &virtio_coreu_ops,
307 			vcoreu, dev, vcoreu->queues, BACKEND_VBSU);
308 	vcoreu->base.mtx = &vcoreu->mtx;
309 
310 	vcoreu->queues[0].qsize  = VIRTIO_COREU_RINGSZ;
311 	vcoreu->queues[0].notify = virtio_coreu_notify;
312 
313 	/* initialize config space */
314 	pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_COREU);
315 	pci_set_cfgdata16(dev, PCIR_VENDOR, INTEL_VENDOR_ID);
316 	pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_CRYPTO);
317 	pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_SIMPLECOMM_OTHER);
318 	pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_COREU);
319 	pci_set_cfgdata16(dev, PCIR_SUBVEND_0, INTEL_VENDOR_ID);
320 
321 	if (virtio_interrupt_init(&vcoreu->base, virtio_uses_msix())) {
322 		WPRINTF(("vcoreu init: interrupt init fail\n"));
323 		free(vcoreu);
324 		return -1;
325 	}
326 
327 	virtio_set_io_bar(&vcoreu->base, 0);
328 
329 	/*
330 	 * connect to coreu daemon in init phase
331 	 *
332 	 * @FIXME if failed connecting to CoreU daemon, the return value should
333 	 * be set appropriately for Service VM not exposing the CoreU PCI device to User VM
334 	 */
335 	vcoreu->fd = connect_coreu_daemon();
336 	if (vcoreu->fd < 0) {
337 		WPRINTF(("connection to server failed\n"));
338 		pthread_mutex_destroy(&vcoreu->mtx);
339 		free(vcoreu);
340 		return -errno;
341 	}
342 
343 	pthread_mutex_init(&vcoreu->rx_mtx, NULL);
344 	pthread_cond_init(&vcoreu->rx_cond, NULL);
345 	pthread_create(&vcoreu->rx_tid, NULL,
346 			virtio_coreu_thread, (void *)vcoreu);
347 	snprintf(tname, sizeof(tname), "vtcoreu-%d:%d tx",
348 			dev->slot, dev->func);
349 	pthread_setname_np(vcoreu->rx_tid, tname);
350 
351 	return 0;
352 }
353 
354 static void
virtio_coreu_deinit(struct vmctx * ctx,struct pci_vdev * dev,char * opts)355 virtio_coreu_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
356 {
357 	struct virtio_coreu *vcoreu = (struct virtio_coreu *)dev->arg;
358 
359 	if (!vcoreu)
360 		return;
361 
362 	pthread_mutex_destroy(&vcoreu->mtx);
363 	pthread_mutex_destroy(&vcoreu->rx_mtx);
364 	pthread_cond_destroy(&vcoreu->rx_cond);
365 	pthread_join(vcoreu->rx_tid, NULL);
366 
367 	virtio_coreu_reset(vcoreu);
368 	free(vcoreu);
369 }
370 
371 struct pci_vdev_ops pci_ops_virtio_coreu = {
372 	.class_name		= "virtio-coreu",
373 	.vdev_init		= virtio_coreu_init,
374 	.vdev_deinit		= virtio_coreu_deinit,
375 	.vdev_barwrite		= virtio_pci_write,
376 	.vdev_barread		= virtio_pci_read
377 };
378 
379 DEFINE_PCI_DEVTYPE(pci_ops_virtio_coreu);
380