1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2018 Intel Corporation 3 */ 4 #include <string.h> 5 6 #include <rte_eal.h> 7 #include <rte_errno.h> 8 #include <rte_alarm.h> 9 #include <rte_string_fns.h> 10 #include <rte_devargs.h> 11 12 #include "hotplug_mp.h" 13 #include "eal_private.h" 14 15 #define MP_TIMEOUT_S 5 /**< 5 seconds timeouts */ 16 17 struct mp_reply_bundle { 18 struct rte_mp_msg msg; 19 void *peer; 20 }; 21 22 static int cmp_dev_name(const struct rte_device *dev, const void *_name) 23 { 24 const char *name = _name; 25 26 return strcmp(dev->name, name); 27 } 28 29 /** 30 * Secondary to primary request. 31 * start from function eal_dev_hotplug_request_to_primary. 32 * 33 * device attach on secondary: 34 * a) secondary send sync request to the primary. 35 * b) primary receive the request and attach the new device if 36 * failed goto i). 37 * c) primary forward attach sync request to all secondary. 38 * d) secondary receive the request and attach the device and send a reply. 39 * e) primary check the reply if all success goes to j). 40 * f) primary send attach rollback sync request to all secondary. 41 * g) secondary receive the request and detach the device and send a reply. 42 * h) primary receive the reply and detach device as rollback action. 43 * i) send attach fail to secondary as a reply of step a), goto k). 44 * j) send attach success to secondary as a reply of step a). 45 * k) secondary receive reply and return. 46 * 47 * device detach on secondary: 48 * a) secondary send sync request to the primary. 49 * b) primary send detach sync request to all secondary. 50 * c) secondary detach the device and send a reply. 51 * d) primary check the reply if all success goes to g). 52 * e) primary send detach rollback sync request to all secondary. 53 * f) secondary receive the request and attach back device. goto h). 54 * g) primary detach the device if success goto i), else goto e). 55 * h) primary send detach fail to secondary as a reply of step a), goto j). 56 * i) primary send detach success to secondary as a reply of step a). 57 * j) secondary receive reply and return. 58 */ 59 60 static int 61 send_response_to_secondary(const struct eal_dev_mp_req *req, 62 int result, 63 const void *peer) 64 { 65 struct rte_mp_msg mp_resp; 66 struct eal_dev_mp_req *resp = 67 (struct eal_dev_mp_req *)mp_resp.param; 68 int ret; 69 70 memset(&mp_resp, 0, sizeof(mp_resp)); 71 mp_resp.len_param = sizeof(*resp); 72 strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name)); 73 memcpy(resp, req, sizeof(*req)); 74 resp->result = result; 75 76 ret = rte_mp_reply(&mp_resp, peer); 77 if (ret != 0) 78 RTE_LOG(ERR, EAL, "failed to send response to secondary\n"); 79 80 return ret; 81 } 82 83 static void 84 __handle_secondary_request(void *param) 85 { 86 struct mp_reply_bundle *bundle = param; 87 const struct rte_mp_msg *msg = &bundle->msg; 88 const struct eal_dev_mp_req *req = 89 (const struct eal_dev_mp_req *)msg->param; 90 struct eal_dev_mp_req tmp_req; 91 struct rte_devargs da; 92 struct rte_device *dev; 93 struct rte_bus *bus; 94 int ret = 0; 95 96 tmp_req = *req; 97 98 memset(&da, 0, sizeof(da)); 99 if (req->t == EAL_DEV_REQ_TYPE_ATTACH) { 100 ret = local_dev_probe(req->devargs, &dev); 101 if (ret != 0 && ret != -EEXIST) { 102 RTE_LOG(ERR, EAL, "Failed to hotplug add device on primary\n"); 103 goto finish; 104 } 105 ret = eal_dev_hotplug_request_to_secondary(&tmp_req); 106 if (ret != 0) { 107 RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n"); 108 ret = -ENOMSG; 109 goto rollback; 110 } 111 if (tmp_req.result != 0) { 112 ret = tmp_req.result; 113 RTE_LOG(ERR, EAL, "Failed to hotplug add device on secondary\n"); 114 if (ret != -EEXIST) 115 goto rollback; 116 } 117 } else if (req->t == EAL_DEV_REQ_TYPE_DETACH) { 118 ret = rte_devargs_parse(&da, req->devargs); 119 if (ret != 0) 120 goto finish; 121 122 ret = eal_dev_hotplug_request_to_secondary(&tmp_req); 123 if (ret != 0) { 124 RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n"); 125 ret = -ENOMSG; 126 goto rollback; 127 } 128 129 bus = rte_bus_find_by_name(da.bus->name); 130 if (bus == NULL) { 131 RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", da.bus->name); 132 ret = -ENOENT; 133 goto finish; 134 } 135 136 dev = bus->find_device(NULL, cmp_dev_name, da.name); 137 if (dev == NULL) { 138 RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da.name); 139 ret = -ENOENT; 140 goto finish; 141 } 142 143 if (tmp_req.result != 0) { 144 RTE_LOG(ERR, EAL, "Failed to hotplug remove device on secondary\n"); 145 ret = tmp_req.result; 146 if (ret != -ENOENT) 147 goto rollback; 148 } 149 150 ret = local_dev_remove(dev); 151 if (ret != 0) { 152 RTE_LOG(ERR, EAL, "Failed to hotplug remove device on primary\n"); 153 if (ret != -ENOENT) 154 goto rollback; 155 } 156 } else { 157 RTE_LOG(ERR, EAL, "unsupported secondary to primary request\n"); 158 ret = -ENOTSUP; 159 } 160 goto finish; 161 162 rollback: 163 if (req->t == EAL_DEV_REQ_TYPE_ATTACH) { 164 tmp_req.t = EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK; 165 eal_dev_hotplug_request_to_secondary(&tmp_req); 166 local_dev_remove(dev); 167 } else { 168 tmp_req.t = EAL_DEV_REQ_TYPE_DETACH_ROLLBACK; 169 eal_dev_hotplug_request_to_secondary(&tmp_req); 170 } 171 172 finish: 173 ret = send_response_to_secondary(&tmp_req, ret, bundle->peer); 174 if (ret) 175 RTE_LOG(ERR, EAL, "failed to send response to secondary\n"); 176 177 rte_devargs_reset(&da); 178 free(bundle->peer); 179 free(bundle); 180 } 181 182 static int 183 handle_secondary_request(const struct rte_mp_msg *msg, const void *peer) 184 { 185 struct mp_reply_bundle *bundle; 186 const struct eal_dev_mp_req *req = 187 (const struct eal_dev_mp_req *)msg->param; 188 int ret = 0; 189 190 bundle = malloc(sizeof(*bundle)); 191 if (bundle == NULL) { 192 RTE_LOG(ERR, EAL, "not enough memory\n"); 193 return send_response_to_secondary(req, -ENOMEM, peer); 194 } 195 196 bundle->msg = *msg; 197 /** 198 * We need to send reply on interrupt thread, but peer can't be 199 * parsed directly, so this is a temporal hack, need to be fixed 200 * when it is ready. 201 */ 202 bundle->peer = strdup(peer); 203 if (bundle->peer == NULL) { 204 free(bundle); 205 RTE_LOG(ERR, EAL, "not enough memory\n"); 206 return send_response_to_secondary(req, -ENOMEM, peer); 207 } 208 209 /** 210 * We are at IPC callback thread, sync IPC is not allowed due to 211 * dead lock, so we delegate the task to interrupt thread. 212 */ 213 ret = rte_eal_alarm_set(1, __handle_secondary_request, bundle); 214 if (ret != 0) { 215 RTE_LOG(ERR, EAL, "failed to add mp task\n"); 216 free(bundle->peer); 217 free(bundle); 218 return send_response_to_secondary(req, ret, peer); 219 } 220 return 0; 221 } 222 223 static void __handle_primary_request(void *param) 224 { 225 struct mp_reply_bundle *bundle = param; 226 struct rte_mp_msg *msg = &bundle->msg; 227 const struct eal_dev_mp_req *req = 228 (const struct eal_dev_mp_req *)msg->param; 229 struct rte_mp_msg mp_resp; 230 struct eal_dev_mp_req *resp = 231 (struct eal_dev_mp_req *)mp_resp.param; 232 struct rte_devargs *da; 233 struct rte_device *dev; 234 struct rte_bus *bus; 235 int ret = 0; 236 237 memset(&mp_resp, 0, sizeof(mp_resp)); 238 239 switch (req->t) { 240 case EAL_DEV_REQ_TYPE_ATTACH: 241 case EAL_DEV_REQ_TYPE_DETACH_ROLLBACK: 242 ret = local_dev_probe(req->devargs, &dev); 243 break; 244 case EAL_DEV_REQ_TYPE_DETACH: 245 case EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK: 246 da = calloc(1, sizeof(*da)); 247 if (da == NULL) { 248 ret = -ENOMEM; 249 break; 250 } 251 252 ret = rte_devargs_parse(da, req->devargs); 253 if (ret != 0) 254 goto quit; 255 256 bus = rte_bus_find_by_name(da->bus->name); 257 if (bus == NULL) { 258 RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", da->bus->name); 259 ret = -ENOENT; 260 goto quit; 261 } 262 263 dev = bus->find_device(NULL, cmp_dev_name, da->name); 264 if (dev == NULL) { 265 RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da->name); 266 ret = -ENOENT; 267 goto quit; 268 } 269 270 if (!rte_dev_is_probed(dev)) { 271 if (req->t == EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK) { 272 /** 273 * Don't fail the rollback just because there's 274 * nothing to do. 275 */ 276 ret = 0; 277 } else 278 ret = -ENODEV; 279 280 goto quit; 281 } 282 283 ret = local_dev_remove(dev); 284 quit: 285 rte_devargs_reset(da); 286 free(da); 287 break; 288 default: 289 ret = -EINVAL; 290 } 291 292 strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name)); 293 mp_resp.len_param = sizeof(*req); 294 memcpy(resp, req, sizeof(*resp)); 295 resp->result = ret; 296 if (rte_mp_reply(&mp_resp, bundle->peer) < 0) 297 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 298 299 free(bundle->peer); 300 free(bundle); 301 } 302 303 static int 304 handle_primary_request(const struct rte_mp_msg *msg, const void *peer) 305 { 306 struct rte_mp_msg mp_resp; 307 const struct eal_dev_mp_req *req = 308 (const struct eal_dev_mp_req *)msg->param; 309 struct eal_dev_mp_req *resp = 310 (struct eal_dev_mp_req *)mp_resp.param; 311 struct mp_reply_bundle *bundle; 312 int ret = 0; 313 314 memset(&mp_resp, 0, sizeof(mp_resp)); 315 strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name)); 316 mp_resp.len_param = sizeof(*req); 317 memcpy(resp, req, sizeof(*resp)); 318 319 bundle = calloc(1, sizeof(*bundle)); 320 if (bundle == NULL) { 321 RTE_LOG(ERR, EAL, "not enough memory\n"); 322 resp->result = -ENOMEM; 323 ret = rte_mp_reply(&mp_resp, peer); 324 if (ret) 325 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 326 return ret; 327 } 328 329 bundle->msg = *msg; 330 /** 331 * We need to send reply on interrupt thread, but peer can't be 332 * parsed directly, so this is a temporal hack, need to be fixed 333 * when it is ready. 334 */ 335 bundle->peer = (void *)strdup(peer); 336 if (bundle->peer == NULL) { 337 RTE_LOG(ERR, EAL, "not enough memory\n"); 338 free(bundle); 339 resp->result = -ENOMEM; 340 ret = rte_mp_reply(&mp_resp, peer); 341 if (ret) 342 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 343 return ret; 344 } 345 346 /** 347 * We are at IPC callback thread, sync IPC is not allowed due to 348 * dead lock, so we delegate the task to interrupt thread. 349 */ 350 ret = rte_eal_alarm_set(1, __handle_primary_request, bundle); 351 if (ret != 0) { 352 free(bundle->peer); 353 free(bundle); 354 resp->result = ret; 355 ret = rte_mp_reply(&mp_resp, peer); 356 if (ret != 0) { 357 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 358 return ret; 359 } 360 } 361 return 0; 362 } 363 364 int eal_dev_hotplug_request_to_primary(struct eal_dev_mp_req *req) 365 { 366 struct rte_mp_msg mp_req; 367 struct rte_mp_reply mp_reply; 368 struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0}; 369 struct eal_dev_mp_req *resp; 370 int ret; 371 372 memset(&mp_req, 0, sizeof(mp_req)); 373 memcpy(mp_req.param, req, sizeof(*req)); 374 mp_req.len_param = sizeof(*req); 375 strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name)); 376 377 ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts); 378 if (ret || mp_reply.nb_received != 1) { 379 RTE_LOG(ERR, EAL, "Cannot send request to primary\n"); 380 if (!ret) 381 return -1; 382 return ret; 383 } 384 385 resp = (struct eal_dev_mp_req *)mp_reply.msgs[0].param; 386 req->result = resp->result; 387 388 free(mp_reply.msgs); 389 return ret; 390 } 391 392 int eal_dev_hotplug_request_to_secondary(struct eal_dev_mp_req *req) 393 { 394 struct rte_mp_msg mp_req; 395 struct rte_mp_reply mp_reply; 396 struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0}; 397 int ret; 398 int i; 399 400 memset(&mp_req, 0, sizeof(mp_req)); 401 memcpy(mp_req.param, req, sizeof(*req)); 402 mp_req.len_param = sizeof(*req); 403 strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name)); 404 405 ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts); 406 if (ret != 0) { 407 /* if IPC is not supported, behave as if the call succeeded */ 408 if (rte_errno != ENOTSUP) 409 RTE_LOG(ERR, EAL, "rte_mp_request_sync failed\n"); 410 else 411 ret = 0; 412 return ret; 413 } 414 415 if (mp_reply.nb_sent != mp_reply.nb_received) { 416 RTE_LOG(ERR, EAL, "not all secondary reply\n"); 417 free(mp_reply.msgs); 418 return -1; 419 } 420 421 req->result = 0; 422 for (i = 0; i < mp_reply.nb_received; i++) { 423 struct eal_dev_mp_req *resp = 424 (struct eal_dev_mp_req *)mp_reply.msgs[i].param; 425 if (resp->result != 0) { 426 if (req->t == EAL_DEV_REQ_TYPE_ATTACH && 427 resp->result == -EEXIST) 428 continue; 429 if (req->t == EAL_DEV_REQ_TYPE_DETACH && 430 resp->result == -ENOENT) 431 continue; 432 req->result = resp->result; 433 } 434 } 435 436 free(mp_reply.msgs); 437 return 0; 438 } 439 440 int eal_mp_dev_hotplug_init(void) 441 { 442 int ret; 443 444 if (rte_eal_process_type() == RTE_PROC_PRIMARY) { 445 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST, 446 handle_secondary_request); 447 /* primary is allowed to not support IPC */ 448 if (ret != 0 && rte_errno != ENOTSUP) { 449 RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n", 450 EAL_DEV_MP_ACTION_REQUEST); 451 return ret; 452 } 453 } else { 454 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST, 455 handle_primary_request); 456 if (ret != 0) { 457 RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n", 458 EAL_DEV_MP_ACTION_REQUEST); 459 return ret; 460 } 461 } 462 463 return 0; 464 } 465