aboutsummaryrefslogtreecommitdiffstats
path: root/lib/librte_eal/linuxapp/eal/eal_vfio_mp_sync.c
blob: 7cc3c1527c78c2520892cd275a3b5e3ab12a6f42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2014 Intel Corporation
 */

#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <pthread.h>

/* sys/un.h with __USE_MISC uses strlen, which is unsafe */
#ifdef __USE_MISC
#define REMOVED_USE_MISC
#undef __USE_MISC
#endif
#include <sys/un.h>
/* make sure we redefine __USE_MISC only if it was previously undefined */
#ifdef REMOVED_USE_MISC
#define __USE_MISC
#undef REMOVED_USE_MISC
#endif

#include <rte_log.h>
#include <rte_eal_memconfig.h>
#include <rte_malloc.h>
#include <rte_vfio.h>

#include "eal_filesystem.h"
#include "eal_vfio.h"
#include "eal_thread.h"

/**
 * @file
 * VFIO socket for communication between primary and secondary processes.
 *
 * This file is only compiled if CONFIG_RTE_EAL_VFIO is set to "y".
 */

#ifdef VFIO_PRESENT

#define SOCKET_PATH_FMT "%s/.%s_mp_socket"
#define CMSGLEN (CMSG_LEN(sizeof(int)))
#define FD_TO_CMSGHDR(fd, chdr) \
		do {\
			(chdr).cmsg_len = CMSGLEN;\
			(chdr).cmsg_level = SOL_SOCKET;\
			(chdr).cmsg_type = SCM_RIGHTS;\
			memcpy((chdr).__cmsg_data, &(fd), sizeof(fd));\
		} while (0)
#define CMSGHDR_TO_FD(chdr, fd) \
			memcpy(&(fd), (chdr).__cmsg_data, sizeof(fd))

static pthread_t socket_thread;
static int mp_socket_fd;


/* get socket path (/var/run if root, $HOME otherwise) */
static void
get_socket_path(char *buffer, int bufsz)
{
	const char *dir = "/var/run";
	const char *home_dir = getenv("HOME");

	if (getuid() != 0 && home_dir != NULL)
		dir = home_dir;

	/* use current prefix as file path */
	snprintf(buffer, bufsz, SOCKET_PATH_FMT, dir,
			internal_config.hugefile_prefix);
}



/*
 * data flow for socket comm protocol:
 * 1. client sends SOCKET_REQ_CONTAINER or SOCKET_REQ_GROUP
 * 1a. in case of SOCKET_REQ_GROUP, client also then sends group number
 * 2. server receives message
 * 2a. in case of invalid group, SOCKET_ERR is sent back to client
 * 2b. in case of unbound group, SOCKET_NO_FD is sent back to client
 * 2c. in case of valid group, SOCKET_OK is sent and immediately followed by fd
 *
 * in case of any error, socket is closed.
 */

/* send a request, return -1 on error */
int
vfio_mp_sync_send_request(int socket, int req)
{
	struct msghdr hdr;
	struct iovec iov;
	int buf;
	int ret;

	memset(&hdr, 0, sizeof(hdr));

	buf = req;

	hdr.msg_iov = &iov;
	hdr.msg_iovlen = 1;
	iov.iov_base = (char *) &buf;
	iov.iov_len = sizeof(buf);

	ret = sendmsg(socket, &hdr, 0);
	if (ret < 0)
		return -1;
	return 0;
}

/* receive a request and return it */
int
vfio_mp_sync_receive_request(int socket)
{
	int buf;
	struct msghdr hdr;
	struct iovec iov;
	int ret, req;

	memset(&hdr, 0, sizeof(hdr));

	buf = SOCKET_ERR;

	hdr.msg_iov = &iov;
	hdr.msg_iovlen = 1;
	iov.iov_base = (char *) &buf;
	iov.iov_len = sizeof(buf);

	ret = recvmsg(socket, &hdr, 0);
	if (ret < 0)
		return -1;

	req = buf;

	return req;
}

/* send OK in message, fd in control message */
int
vfio_mp_sync_send_fd(int socket, int fd)
{
	int buf;
	struct msghdr hdr;
	struct cmsghdr *chdr;
	char chdr_buf[CMSGLEN];
	struct iovec iov;
	int ret;

	chdr = (struct cmsghdr *) chdr_buf;
	memset(chdr, 0, sizeof(chdr_buf));
	memset(&hdr, 0, sizeof(hdr));

	hdr.msg_iov = &iov;
	hdr.msg_iovlen = 1;
	iov.iov_base = (char *) &buf;
	iov.iov_len = sizeof(buf);
	hdr.msg_control = chdr;
	hdr.msg_controllen = CMSGLEN;

	buf = SOCKET_OK;
	FD_TO_CMSGHDR(fd, *chdr);

	ret = sendmsg(socket, &hdr, 0);
	if (ret < 0)
		return -1;
	return 0;
}

/* receive OK in message, fd in control message */
int
vfio_mp_sync_receive_fd(int socket)
{
	int buf;
	struct msghdr hdr;
	struct cmsghdr *chdr;
	char chdr_buf[CMSGLEN];
	struct iovec iov;
	int ret, req, fd;

	buf = SOCKET_ERR;

	chdr = (struct cmsghdr *) chdr_buf;
	memset(chdr, 0, sizeof(chdr_buf));
	memset(&hdr, 0, sizeof(hdr));

	hdr.msg_iov = &iov;
	hdr.msg_iovlen = 1;
	iov.iov_base = (char *) &buf;
	iov.iov_len = sizeof(buf);
	hdr.msg_control = chdr;
	hdr.msg_controllen = CMSGLEN;

	ret = recvmsg(socket, &hdr, 0);
	if (ret < 0)
		return -1;

	req = buf;

	if (req != SOCKET_OK)
		return -1;

	CMSGHDR_TO_FD(*chdr, fd);

	return fd;
}

/* connect socket_fd in secondary process to the primary process's socket */
int
vfio_mp_sync_connect_to_primary(void)
{
	struct sockaddr_un addr;
	socklen_t sockaddr_len;
	int socket_fd;

	/* set up a socket */
	socket_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
	if (socket_fd < 0) {
		RTE_LOG(ERR, EAL, "Failed to create socket!\n");
		return -1;
	}

	get_socket_path(addr.sun_path, sizeof(addr.sun_path));
	addr.sun_family = AF_UNIX;

	sockaddr_len = sizeof(struct sockaddr_un);

	if (connect(socket_fd, (struct sockaddr *) &addr, sockaddr_len) == 0)
		return socket_fd;

	/* if connect failed */
	close(socket_fd);
	return -1;
}



/*
 * socket listening thread for primary process
 */
static __attribute__((noreturn)) void *
vfio_mp_sync_thread(void __rte_unused * arg)
{
	int ret, fd, vfio_data;

	/* wait for requests on the socket */
	for (;;) {
		int conn_sock;
		struct sockaddr_un addr;
		socklen_t sockaddr_len = sizeof(addr);

		/* this is a blocking call */
		conn_sock = accept(mp_socket_fd, (struct sockaddr *) &addr,
				&sockaddr_len);

		/* just restart on error */
		if (conn_sock == -1)
			continue;

		/* set socket to linger after close */
		struct linger l;
		l.l_onoff = 1;
		l.l_linger = 60;

		if (setsockopt(conn_sock, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0)
			RTE_LOG(WARNING, EAL, "Cannot set SO_LINGER option "
					"on listen socket (%s)\n", strerror(errno));

		ret = vfio_mp_sync_receive_request(conn_sock);

		switch (ret) {
		case SOCKET_REQ_CONTAINER:
			fd = vfio_get_container_fd();
			if (fd < 0)
				vfio_mp_sync_send_request(conn_sock, SOCKET_ERR);
			else
				vfio_mp_sync_send_fd(conn_sock, fd);
			if (fd >= 0)
				close(fd);
			break;
		case SOCKET_REQ_GROUP:
			/* wait for group number */
			vfio_data = vfio_mp_sync_receive_request(conn_sock);
			if (vfio_data < 0) {
				close(conn_sock);
				continue;
			}

			fd = vfio_get_group_fd(vfio_data);

			if (fd < 0)
				vfio_mp_sync_send_request(conn_sock, SOCKET_ERR);
			/* if VFIO group exists but isn't bound to VFIO driver */
			else if (fd == 0)
				vfio_mp_sync_send_request(conn_sock, SOCKET_NO_FD);
			/* if group exists and is bound to VFIO driver */
			else {
				vfio_mp_sync_send_request(conn_sock, SOCKET_OK);
				vfio_mp_sync_send_fd(conn_sock, fd);
			}
			break;
		case SOCKET_CLR_GROUP:
			/* wait for group fd */
			vfio_data = vfio_mp_sync_receive_request(conn_sock);
			if (vfio_data < 0) {
				close(conn_sock);
				continue;
			}

			ret = rte_vfio_clear_group(vfio_data);

			if (ret < 0)
				vfio_mp_sync_send_request(conn_sock, SOCKET_NO_FD);
			else
				vfio_mp_sync_send_request(conn_sock, SOCKET_OK);
			break;
		default:
			vfio_mp_sync_send_request(conn_sock, SOCKET_ERR);
			break;
		}
		close(conn_sock);
	}
}

static int
vfio_mp_sync_socket_setup(void)
{
	int ret, socket_fd;
	struct sockaddr_un addr;
	socklen_t sockaddr_len;

	/* set up a socket */
	socket_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
	if (socket_fd < 0) {
		RTE_LOG(ERR, EAL, "Failed to create socket!\n");
		return -1;
	}

	get_socket_path(addr.sun_path, sizeof(addr.sun_path));
	addr.sun_family = AF_UNIX;

	sockaddr_len = sizeof(struct sockaddr_un);

	unlink(addr.sun_path);

	ret = bind(socket_fd, (struct sockaddr *) &addr, sockaddr_len);
	if (ret) {
		RTE_LOG(ERR, EAL, "Failed to bind socket: %s!\n", strerror(errno));
		close(socket_fd);
		return -1;
	}

	ret = listen(socket_fd, 50);
	if (ret) {
		RTE_LOG(ERR, EAL, "Failed to listen: %s!\n", strerror(errno));
		close(socket_fd);
		return -1;
	}

	/* save the socket in local configuration */
	mp_socket_fd = socket_fd;

	return 0;
}

/*
 * set up a local socket and tell it to listen for incoming connections
 */
int
vfio_mp_sync_setup(void)
{
	int ret;
	char thread_name[RTE_MAX_THREAD_NAME_LEN];

	if (vfio_mp_sync_socket_setup() < 0) {
		RTE_LOG(ERR, EAL, "Failed to set up local socket!\n");
		return -1;
	}

	ret = pthread_create(&socket_thread, NULL,
			vfio_mp_sync_thread, NULL);
	if (ret) {
		RTE_LOG(ERR, EAL,
			"Failed to create thread for communication with secondary processes!\n");
		close(mp_socket_fd);
		return -1;
	}

	/* Set thread_name for aid in debugging. */
	snprintf(thread_name, RTE_MAX_THREAD_NAME_LEN, "vfio-sync");
	ret = rte_thread_setname(socket_thread, thread_name);
	if (ret)
		RTE_LOG(DEBUG, EAL,
			"Failed to set thread name for secondary processes!\n");

	return 0;
}

#endif