xref: /spdk/lib/env_dpdk/pci_event.c (revision ba8f1a9e5d59b6dfa9fd07c1300891b4e03722d9)
1 /*-
2  *   BSD LICENSE
3  *
4  *   Copyright (c) Intel Corporation.
5  *   All rights reserved.
6  *
7  *   Redistribution and use in source and binary forms, with or without
8  *   modification, are permitted provided that the following conditions
9  *   are met:
10  *
11  *     * Redistributions of source code must retain the above copyright
12  *       notice, this list of conditions and the following disclaimer.
13  *     * Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in
15  *       the documentation and/or other materials provided with the
16  *       distribution.
17  *     * Neither the name of Intel Corporation nor the names of its
18  *       contributors may be used to endorse or promote products derived
19  *       from this software without specific prior written permission.
20  *
21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include "spdk/stdinc.h"
35 #include "spdk/string.h"
36 
37 #include "spdk/log.h"
38 #include "spdk/env.h"
39 
40 #ifdef __linux__
41 
42 #include <linux/netlink.h>
43 
44 #define SPDK_UEVENT_MSG_LEN 4096
45 #define SPDK_UEVENT_RECVBUF_SIZE 1024 * 1024
46 
47 int
48 spdk_pci_event_listen(void)
49 {
50 	struct sockaddr_nl addr;
51 	int netlink_fd;
52 	int size = SPDK_UEVENT_RECVBUF_SIZE;
53 	int buf_size;
54 	socklen_t opt_size;
55 	int flag, rc;
56 
57 	memset(&addr, 0, sizeof(addr));
58 	addr.nl_family = AF_NETLINK;
59 	addr.nl_pid = 0;
60 	addr.nl_groups = 0xffffffff;
61 
62 	netlink_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
63 	if (netlink_fd < 0) {
64 		SPDK_ERRLOG("Failed to create netlink socket\n");
65 		return netlink_fd;
66 	}
67 
68 	if (setsockopt(netlink_fd, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size)) < 0) {
69 		if (setsockopt(netlink_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) < 0) {
70 			rc = errno;
71 			SPDK_ERRLOG("Failed to set socket option SO_RCVBUF\n");
72 			goto error;
73 		}
74 		opt_size = sizeof(buf_size);
75 		if (getsockopt(netlink_fd, SOL_SOCKET, SO_RCVBUF, &buf_size, &opt_size) < 0) {
76 			rc = errno;
77 			SPDK_ERRLOG("Failed to get socket option SO_RCVBUF\n");
78 			goto error;
79 		}
80 		if (buf_size < SPDK_UEVENT_RECVBUF_SIZE) {
81 			SPDK_ERRLOG("Socket recv buffer is too small (< %d), see SO_RCVBUF "
82 				    "section in socket(7) man page for specifics on how to "
83 				    "adjust the system setting.", SPDK_UEVENT_RECVBUF_SIZE);
84 			rc = ENOSPC;
85 			goto error;
86 		}
87 	}
88 
89 	flag = fcntl(netlink_fd, F_GETFL);
90 	if (flag < 0) {
91 		rc = errno;
92 		SPDK_ERRLOG("Failed to get socket flag, fd: %d\n", netlink_fd);
93 		goto error;
94 	}
95 
96 	if (fcntl(netlink_fd, F_SETFL, flag | O_NONBLOCK) < 0) {
97 		rc = errno;
98 		SPDK_ERRLOG("Fcntl can't set nonblocking mode for socket, fd: %d\n", netlink_fd);
99 		goto error;
100 	}
101 
102 	if (bind(netlink_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
103 		rc = errno;
104 		SPDK_ERRLOG("Failed to bind the netlink\n");
105 		goto error;
106 	}
107 
108 	return netlink_fd;
109 error:
110 	close(netlink_fd);
111 	return -rc;
112 }
113 
114 /* Note: We parse the event from uio and vfio subsystem and will ignore
115  *       all the event from other subsystem. the event from uio subsystem
116  *       as below:
117  *       action: "add" or "remove"
118  *       subsystem: "uio"
119  *       dev_path: "/devices/pci0000:80/0000:80:01.0/0000:81:00.0/uio/uio0"
120  *       VFIO subsystem add event:
121  *       ACTION=bind
122  *       DRIVER=vfio-pci
123  *       PCI_SLOT_NAME=0000:d8:00.0
124  */
125 static int
126 parse_subsystem_event(const char *buf, struct spdk_pci_event *event)
127 {
128 	char subsystem[SPDK_UEVENT_MSG_LEN];
129 	char action[SPDK_UEVENT_MSG_LEN];
130 	char dev_path[SPDK_UEVENT_MSG_LEN];
131 	char driver[SPDK_UEVENT_MSG_LEN];
132 	char vfio_pci_addr[SPDK_UEVENT_MSG_LEN];
133 	char *pci_address, *tmp;
134 	int rc;
135 
136 	memset(subsystem, 0, SPDK_UEVENT_MSG_LEN);
137 	memset(action, 0, SPDK_UEVENT_MSG_LEN);
138 	memset(dev_path, 0, SPDK_UEVENT_MSG_LEN);
139 	memset(driver, 0, SPDK_UEVENT_MSG_LEN);
140 	memset(vfio_pci_addr, 0, SPDK_UEVENT_MSG_LEN);
141 
142 	while (*buf) {
143 		if (!strncmp(buf, "SUBSYSTEM=", 10)) {
144 			buf += 10;
145 			snprintf(subsystem, sizeof(subsystem), "%s", buf);
146 		} else if (!strncmp(buf, "ACTION=", 7)) {
147 			buf += 7;
148 			snprintf(action, sizeof(action), "%s", buf);
149 		} else if (!strncmp(buf, "DEVPATH=", 8)) {
150 			buf += 8;
151 			snprintf(dev_path, sizeof(dev_path), "%s", buf);
152 		} else if (!strncmp(buf, "DRIVER=", 7)) {
153 			buf += 7;
154 			snprintf(driver, sizeof(driver), "%s", buf);
155 		} else if (!strncmp(buf, "PCI_SLOT_NAME=", 14)) {
156 			buf += 14;
157 			snprintf(vfio_pci_addr, sizeof(vfio_pci_addr), "%s", buf);
158 		}
159 
160 		while (*buf++)
161 			;
162 	}
163 
164 	if (!strncmp(subsystem, "uio", 3)) {
165 		if (!strncmp(action, "remove", 6)) {
166 			event->action = SPDK_UEVENT_REMOVE;
167 		} else if (!strncmp(action, "add", 3)) {
168 			/* Support the ADD UEVENT for the device allow */
169 			event->action = SPDK_UEVENT_ADD;
170 		} else {
171 			return 0;
172 		}
173 
174 		tmp = strstr(dev_path, "/uio/");
175 		if (!tmp) {
176 			SPDK_ERRLOG("Invalid format of uevent: %s\n", dev_path);
177 			return -EBADMSG;
178 		}
179 		memset(tmp, 0, SPDK_UEVENT_MSG_LEN - (tmp - dev_path));
180 
181 		pci_address = strrchr(dev_path, '/');
182 		if (!pci_address) {
183 			SPDK_ERRLOG("Not found PCI device BDF in uevent: %s\n", dev_path);
184 			return -EBADMSG;
185 		}
186 		pci_address++;
187 
188 		rc = spdk_pci_addr_parse(&event->traddr, pci_address);
189 		if (rc != 0) {
190 			SPDK_ERRLOG("Invalid format for PCI device BDF: %s\n", pci_address);
191 			return rc;
192 		}
193 
194 		return 1;
195 	}
196 
197 	if (!strncmp(driver, "vfio-pci", 8)) {
198 		if (!strncmp(action, "bind", 4)) {
199 			/* Support the ADD UEVENT for the device allow */
200 			event->action = SPDK_UEVENT_ADD;
201 		} else {
202 			/* Only need to support add event.
203 			 * VFIO hotplug interface is "pci.c:pci_device_rte_dev_event".
204 			 * VFIO informs the userspace hotplug through vfio req notifier interrupt.
205 			 * The app needs to free the device userspace driver resource first then
206 			 * the OS remove the device VFIO driver and broadcast the VFIO uevent.
207 			 */
208 			return 0;
209 		}
210 
211 		rc = spdk_pci_addr_parse(&event->traddr, vfio_pci_addr);
212 		if (rc != 0) {
213 			SPDK_ERRLOG("Invalid format for PCI device BDF: %s\n", vfio_pci_addr);
214 			return rc;
215 		}
216 
217 		return 1;
218 	}
219 
220 	return 0;
221 }
222 
223 int
224 spdk_pci_get_event(int fd, struct spdk_pci_event *event)
225 {
226 	int ret;
227 	char buf[SPDK_UEVENT_MSG_LEN];
228 
229 	memset(buf, 0, SPDK_UEVENT_MSG_LEN);
230 	memset(event, 0, sizeof(*event));
231 
232 	ret = recv(fd, buf, SPDK_UEVENT_MSG_LEN - 1, MSG_DONTWAIT);
233 	if (ret > 0) {
234 		return parse_subsystem_event(buf, event);
235 	} else if (ret < 0) {
236 		if (errno == EAGAIN || errno == EWOULDBLOCK) {
237 			return 0;
238 		} else {
239 			ret = errno;
240 			SPDK_ERRLOG("Socket read error %d\n", errno);
241 			return -ret;
242 		}
243 	} else {
244 		/* connection closed */
245 		return -ENOTCONN;
246 	}
247 
248 	return 0;
249 }
250 
251 #else /* Not Linux */
252 
253 int
254 spdk_pci_event_listen(void)
255 {
256 	SPDK_ERRLOG("Non-Linux does not support this operation\n");
257 	return -ENOTSUP;
258 }
259 
260 int
261 spdk_pci_get_event(int fd, struct spdk_pci_event *event)
262 {
263 	SPDK_ERRLOG("Non-Linux does not support this operation\n");
264 	return -ENOTSUP;
265 }
266 #endif
267