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 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 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 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 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 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