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