1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright (C) 2020 Intel Corporation.
3 * All rights reserved.
4 */
5
6 #include "spdk/stdinc.h"
7 #include "spdk/string.h"
8
9 #include "spdk/log.h"
10 #include "spdk/env.h"
11
12 #ifdef __linux__
13
14 #include <linux/netlink.h>
15
16 #define SPDK_UEVENT_MSG_LEN 4096
17 #define SPDK_UEVENT_RECVBUF_SIZE 1024 * 1024
18
19 int
spdk_pci_event_listen(void)20 spdk_pci_event_listen(void)
21 {
22 struct sockaddr_nl addr;
23 int netlink_fd;
24 int size = SPDK_UEVENT_RECVBUF_SIZE;
25 int buf_size;
26 socklen_t opt_size;
27 int flag, rc;
28
29 memset(&addr, 0, sizeof(addr));
30 addr.nl_family = AF_NETLINK;
31 addr.nl_pid = 0;
32 addr.nl_groups = 0xffffffff;
33
34 netlink_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
35 if (netlink_fd < 0) {
36 SPDK_ERRLOG("Failed to create netlink socket\n");
37 return netlink_fd;
38 }
39
40 if (setsockopt(netlink_fd, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size)) < 0) {
41 if (setsockopt(netlink_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) < 0) {
42 rc = errno;
43 SPDK_ERRLOG("Failed to set socket option SO_RCVBUF\n");
44 goto error;
45 }
46 opt_size = sizeof(buf_size);
47 if (getsockopt(netlink_fd, SOL_SOCKET, SO_RCVBUF, &buf_size, &opt_size) < 0) {
48 rc = errno;
49 SPDK_ERRLOG("Failed to get socket option SO_RCVBUF\n");
50 goto error;
51 }
52 if (buf_size < SPDK_UEVENT_RECVBUF_SIZE) {
53 SPDK_ERRLOG("Socket recv buffer is too small (< %d), see SO_RCVBUF "
54 "section in socket(7) man page for specifics on how to "
55 "adjust the system setting.", SPDK_UEVENT_RECVBUF_SIZE);
56 rc = ENOSPC;
57 goto error;
58 }
59 }
60
61 flag = fcntl(netlink_fd, F_GETFL);
62 if (flag < 0) {
63 rc = errno;
64 SPDK_ERRLOG("Failed to get socket flag, fd: %d\n", netlink_fd);
65 goto error;
66 }
67
68 if (fcntl(netlink_fd, F_SETFL, flag | O_NONBLOCK) < 0) {
69 rc = errno;
70 SPDK_ERRLOG("Fcntl can't set nonblocking mode for socket, fd: %d\n", netlink_fd);
71 goto error;
72 }
73
74 if (bind(netlink_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
75 rc = errno;
76 SPDK_ERRLOG("Failed to bind the netlink\n");
77 goto error;
78 }
79
80 return netlink_fd;
81 error:
82 close(netlink_fd);
83 return -rc;
84 }
85
86 /* Note: We parse the event from uio and vfio subsystem and will ignore
87 * all the event from other subsystem. the event from uio subsystem
88 * as below:
89 * action: "add" or "remove"
90 * subsystem: "uio"
91 * dev_path: "/devices/pci0000:80/0000:80:01.0/0000:81:00.0/uio/uio0"
92 * VFIO subsystem add event:
93 * ACTION=bind
94 * DRIVER=vfio-pci
95 * PCI_SLOT_NAME=0000:d8:00.0
96 */
97 static int
parse_subsystem_event(const char * buf,struct spdk_pci_event * event)98 parse_subsystem_event(const char *buf, struct spdk_pci_event *event)
99 {
100 char subsystem[SPDK_UEVENT_MSG_LEN];
101 char action[SPDK_UEVENT_MSG_LEN];
102 char dev_path[SPDK_UEVENT_MSG_LEN];
103 char driver[SPDK_UEVENT_MSG_LEN];
104 char vfio_pci_addr[SPDK_UEVENT_MSG_LEN];
105 char *pci_address, *tmp;
106 int rc;
107
108 memset(subsystem, 0, SPDK_UEVENT_MSG_LEN);
109 memset(action, 0, SPDK_UEVENT_MSG_LEN);
110 memset(dev_path, 0, SPDK_UEVENT_MSG_LEN);
111 memset(driver, 0, SPDK_UEVENT_MSG_LEN);
112 memset(vfio_pci_addr, 0, SPDK_UEVENT_MSG_LEN);
113
114 while (*buf) {
115 if (!strncmp(buf, "SUBSYSTEM=", 10)) {
116 buf += 10;
117 snprintf(subsystem, sizeof(subsystem), "%s", buf);
118 } else if (!strncmp(buf, "ACTION=", 7)) {
119 buf += 7;
120 snprintf(action, sizeof(action), "%s", buf);
121 } else if (!strncmp(buf, "DEVPATH=", 8)) {
122 buf += 8;
123 snprintf(dev_path, sizeof(dev_path), "%s", buf);
124 } else if (!strncmp(buf, "DRIVER=", 7)) {
125 buf += 7;
126 snprintf(driver, sizeof(driver), "%s", buf);
127 } else if (!strncmp(buf, "PCI_SLOT_NAME=", 14)) {
128 buf += 14;
129 snprintf(vfio_pci_addr, sizeof(vfio_pci_addr), "%s", buf);
130 }
131
132 while (*buf++)
133 ;
134 }
135
136 if (!strncmp(subsystem, "uio", 3)) {
137 if (!strncmp(action, "remove", 6)) {
138 event->action = SPDK_UEVENT_REMOVE;
139 } else if (!strncmp(action, "add", 3)) {
140 /* Support the ADD UEVENT for the device allow */
141 event->action = SPDK_UEVENT_ADD;
142 } else {
143 return 0;
144 }
145
146 tmp = strstr(dev_path, "/uio/");
147 if (!tmp) {
148 SPDK_ERRLOG("Invalid format of uevent: %s\n", dev_path);
149 return -EBADMSG;
150 }
151 memset(tmp, 0, SPDK_UEVENT_MSG_LEN - (tmp - dev_path));
152
153 pci_address = strrchr(dev_path, '/');
154 if (!pci_address) {
155 SPDK_ERRLOG("Not found PCI device BDF in uevent: %s\n", dev_path);
156 return -EBADMSG;
157 }
158 pci_address++;
159
160 rc = spdk_pci_addr_parse(&event->traddr, pci_address);
161 if (rc != 0) {
162 SPDK_ERRLOG("Invalid format for PCI device BDF: %s\n", pci_address);
163 return rc;
164 }
165
166 return 1;
167 }
168
169 if (!strncmp(driver, "vfio-pci", 8)) {
170 if (!strncmp(action, "bind", 4)) {
171 /* Support the ADD UEVENT for the device allow */
172 event->action = SPDK_UEVENT_ADD;
173 } else {
174 /* Only need to support add event.
175 * VFIO hotplug interface is "pci.c:pci_device_rte_dev_event".
176 * VFIO informs the userspace hotplug through vfio req notifier interrupt.
177 * The app needs to free the device userspace driver resource first then
178 * the OS remove the device VFIO driver and broadcast the VFIO uevent.
179 */
180 return 0;
181 }
182
183 rc = spdk_pci_addr_parse(&event->traddr, vfio_pci_addr);
184 if (rc != 0) {
185 SPDK_ERRLOG("Invalid format for PCI device BDF: %s\n", vfio_pci_addr);
186 return rc;
187 }
188
189 return 1;
190 }
191
192 return 0;
193 }
194
195 int
spdk_pci_get_event(int fd,struct spdk_pci_event * event)196 spdk_pci_get_event(int fd, struct spdk_pci_event *event)
197 {
198 int ret;
199 char buf[SPDK_UEVENT_MSG_LEN];
200
201 memset(buf, 0, SPDK_UEVENT_MSG_LEN);
202 memset(event, 0, sizeof(*event));
203
204 ret = recv(fd, buf, SPDK_UEVENT_MSG_LEN - 1, MSG_DONTWAIT);
205 if (ret > 0) {
206 return parse_subsystem_event(buf, event);
207 } else if (ret < 0) {
208 if (errno == EAGAIN || errno == EWOULDBLOCK) {
209 return 0;
210 } else {
211 ret = errno;
212 SPDK_ERRLOG("Socket read error %d\n", errno);
213 return -ret;
214 }
215 } else {
216 /* connection closed */
217 return -ENOTCONN;
218 }
219
220 return 0;
221 }
222
223 #else /* Not Linux */
224
225 int
spdk_pci_event_listen(void)226 spdk_pci_event_listen(void)
227 {
228 SPDK_ERRLOG("Non-Linux does not support this operation\n");
229 return -ENOTSUP;
230 }
231
232 int
spdk_pci_get_event(int fd,struct spdk_pci_event * event)233 spdk_pci_get_event(int fd, struct spdk_pci_event *event)
234 {
235 SPDK_ERRLOG("Non-Linux does not support this operation\n");
236 return -ENOTSUP;
237 }
238 #endif
239