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) { 102 RTE_LOG(ERR, EAL, "Failed to hotplug add device on primary\n"); 103 if (ret != -EEXIST) 104 goto finish; 105 } 106 ret = eal_dev_hotplug_request_to_secondary(&tmp_req); 107 if (ret != 0) { 108 RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n"); 109 ret = -ENOMSG; 110 goto rollback; 111 } 112 if (tmp_req.result != 0) { 113 ret = tmp_req.result; 114 RTE_LOG(ERR, EAL, "Failed to hotplug add device on secondary\n"); 115 if (ret != -EEXIST) 116 goto rollback; 117 } 118 } else if (req->t == EAL_DEV_REQ_TYPE_DETACH) { 119 ret = rte_devargs_parse(&da, req->devargs); 120 if (ret != 0) 121 goto finish; 122 123 ret = eal_dev_hotplug_request_to_secondary(&tmp_req); 124 if (ret != 0) { 125 RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n"); 126 ret = -ENOMSG; 127 goto rollback; 128 } 129 130 bus = rte_bus_find_by_name(da.bus->name); 131 if (bus == NULL) { 132 RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", da.bus->name); 133 ret = -ENOENT; 134 goto finish; 135 } 136 137 dev = bus->find_device(NULL, cmp_dev_name, da.name); 138 if (dev == NULL) { 139 RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da.name); 140 ret = -ENOENT; 141 goto finish; 142 } 143 144 if (tmp_req.result != 0) { 145 RTE_LOG(ERR, EAL, "Failed to hotplug remove device on secondary\n"); 146 ret = tmp_req.result; 147 if (ret != -ENOENT) 148 goto rollback; 149 } 150 151 ret = local_dev_remove(dev); 152 if (ret != 0) { 153 RTE_LOG(ERR, EAL, "Failed to hotplug remove device on primary\n"); 154 if (ret != -ENOENT) 155 goto rollback; 156 } 157 } else { 158 RTE_LOG(ERR, EAL, "unsupported secondary to primary request\n"); 159 ret = -ENOTSUP; 160 } 161 goto finish; 162 163 rollback: 164 if (req->t == EAL_DEV_REQ_TYPE_ATTACH) { 165 tmp_req.t = EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK; 166 eal_dev_hotplug_request_to_secondary(&tmp_req); 167 local_dev_remove(dev); 168 } else { 169 tmp_req.t = EAL_DEV_REQ_TYPE_DETACH_ROLLBACK; 170 eal_dev_hotplug_request_to_secondary(&tmp_req); 171 } 172 173 finish: 174 ret = send_response_to_secondary(&tmp_req, ret, bundle->peer); 175 if (ret) 176 RTE_LOG(ERR, EAL, "failed to send response to secondary\n"); 177 178 rte_devargs_reset(&da); 179 free(bundle->peer); 180 free(bundle); 181 } 182 183 static int 184 handle_secondary_request(const struct rte_mp_msg *msg, const void *peer) 185 { 186 struct mp_reply_bundle *bundle; 187 const struct eal_dev_mp_req *req = 188 (const struct eal_dev_mp_req *)msg->param; 189 int ret = 0; 190 191 bundle = malloc(sizeof(*bundle)); 192 if (bundle == NULL) { 193 RTE_LOG(ERR, EAL, "not enough memory\n"); 194 return send_response_to_secondary(req, -ENOMEM, peer); 195 } 196 197 bundle->msg = *msg; 198 /** 199 * We need to send reply on interrupt thread, but peer can't be 200 * parsed directly, so this is a temporal hack, need to be fixed 201 * when it is ready. 202 */ 203 bundle->peer = strdup(peer); 204 if (bundle->peer == NULL) { 205 free(bundle); 206 RTE_LOG(ERR, EAL, "not enough memory\n"); 207 return send_response_to_secondary(req, -ENOMEM, peer); 208 } 209 210 /** 211 * We are at IPC callback thread, sync IPC is not allowed due to 212 * dead lock, so we delegate the task to interrupt thread. 213 */ 214 ret = rte_eal_alarm_set(1, __handle_secondary_request, bundle); 215 if (ret != 0) { 216 RTE_LOG(ERR, EAL, "failed to add mp task\n"); 217 free(bundle->peer); 218 free(bundle); 219 return send_response_to_secondary(req, ret, peer); 220 } 221 return 0; 222 } 223 224 static void __handle_primary_request(void *param) 225 { 226 struct mp_reply_bundle *bundle = param; 227 struct rte_mp_msg *msg = &bundle->msg; 228 const struct eal_dev_mp_req *req = 229 (const struct eal_dev_mp_req *)msg->param; 230 struct rte_mp_msg mp_resp; 231 struct eal_dev_mp_req *resp = 232 (struct eal_dev_mp_req *)mp_resp.param; 233 struct rte_devargs *da; 234 struct rte_device *dev; 235 struct rte_bus *bus; 236 int ret = 0; 237 238 memset(&mp_resp, 0, sizeof(mp_resp)); 239 240 switch (req->t) { 241 case EAL_DEV_REQ_TYPE_ATTACH: 242 case EAL_DEV_REQ_TYPE_DETACH_ROLLBACK: 243 ret = local_dev_probe(req->devargs, &dev); 244 break; 245 case EAL_DEV_REQ_TYPE_DETACH: 246 case EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK: 247 da = calloc(1, sizeof(*da)); 248 if (da == NULL) { 249 ret = -ENOMEM; 250 break; 251 } 252 253 ret = rte_devargs_parse(da, req->devargs); 254 if (ret != 0) 255 goto quit; 256 257 bus = rte_bus_find_by_name(da->bus->name); 258 if (bus == NULL) { 259 RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", da->bus->name); 260 ret = -ENOENT; 261 goto quit; 262 } 263 264 dev = bus->find_device(NULL, cmp_dev_name, da->name); 265 if (dev == NULL) { 266 RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da->name); 267 ret = -ENOENT; 268 goto quit; 269 } 270 271 if (!rte_dev_is_probed(dev)) { 272 if (req->t == EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK) { 273 /** 274 * Don't fail the rollback just because there's 275 * nothing to do. 276 */ 277 ret = 0; 278 } else 279 ret = -ENODEV; 280 281 goto quit; 282 } 283 284 ret = local_dev_remove(dev); 285 quit: 286 rte_devargs_reset(da); 287 free(da); 288 break; 289 default: 290 ret = -EINVAL; 291 } 292 293 strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name)); 294 mp_resp.len_param = sizeof(*req); 295 memcpy(resp, req, sizeof(*resp)); 296 resp->result = ret; 297 if (rte_mp_reply(&mp_resp, bundle->peer) < 0) 298 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 299 300 free(bundle->peer); 301 free(bundle); 302 } 303 304 static int 305 handle_primary_request(const struct rte_mp_msg *msg, const void *peer) 306 { 307 struct rte_mp_msg mp_resp; 308 const struct eal_dev_mp_req *req = 309 (const struct eal_dev_mp_req *)msg->param; 310 struct eal_dev_mp_req *resp = 311 (struct eal_dev_mp_req *)mp_resp.param; 312 struct mp_reply_bundle *bundle; 313 int ret = 0; 314 315 memset(&mp_resp, 0, sizeof(mp_resp)); 316 strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name)); 317 mp_resp.len_param = sizeof(*req); 318 memcpy(resp, req, sizeof(*resp)); 319 320 bundle = calloc(1, sizeof(*bundle)); 321 if (bundle == NULL) { 322 RTE_LOG(ERR, EAL, "not enough memory\n"); 323 resp->result = -ENOMEM; 324 ret = rte_mp_reply(&mp_resp, peer); 325 if (ret) 326 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 327 return ret; 328 } 329 330 bundle->msg = *msg; 331 /** 332 * We need to send reply on interrupt thread, but peer can't be 333 * parsed directly, so this is a temporal hack, need to be fixed 334 * when it is ready. 335 */ 336 bundle->peer = (void *)strdup(peer); 337 if (bundle->peer == NULL) { 338 RTE_LOG(ERR, EAL, "not enough memory\n"); 339 free(bundle); 340 resp->result = -ENOMEM; 341 ret = rte_mp_reply(&mp_resp, peer); 342 if (ret) 343 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 344 return ret; 345 } 346 347 /** 348 * We are at IPC callback thread, sync IPC is not allowed due to 349 * dead lock, so we delegate the task to interrupt thread. 350 */ 351 ret = rte_eal_alarm_set(1, __handle_primary_request, bundle); 352 if (ret != 0) { 353 free(bundle->peer); 354 free(bundle); 355 resp->result = ret; 356 ret = rte_mp_reply(&mp_resp, peer); 357 if (ret != 0) { 358 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n"); 359 return ret; 360 } 361 } 362 return 0; 363 } 364 365 int eal_dev_hotplug_request_to_primary(struct eal_dev_mp_req *req) 366 { 367 struct rte_mp_msg mp_req; 368 struct rte_mp_reply mp_reply; 369 struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0}; 370 struct eal_dev_mp_req *resp; 371 int ret; 372 373 memset(&mp_req, 0, sizeof(mp_req)); 374 memcpy(mp_req.param, req, sizeof(*req)); 375 mp_req.len_param = sizeof(*req); 376 strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name)); 377 378 ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts); 379 if (ret || mp_reply.nb_received != 1) { 380 RTE_LOG(ERR, EAL, "Cannot send request to primary\n"); 381 if (!ret) 382 return -1; 383 return ret; 384 } 385 386 resp = (struct eal_dev_mp_req *)mp_reply.msgs[0].param; 387 req->result = resp->result; 388 389 free(mp_reply.msgs); 390 return ret; 391 } 392 393 int eal_dev_hotplug_request_to_secondary(struct eal_dev_mp_req *req) 394 { 395 struct rte_mp_msg mp_req; 396 struct rte_mp_reply mp_reply; 397 struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0}; 398 int ret; 399 int i; 400 401 memset(&mp_req, 0, sizeof(mp_req)); 402 memcpy(mp_req.param, req, sizeof(*req)); 403 mp_req.len_param = sizeof(*req); 404 strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name)); 405 406 ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts); 407 if (ret != 0) { 408 /* if IPC is not supported, behave as if the call succeeded */ 409 if (rte_errno != ENOTSUP) 410 RTE_LOG(ERR, EAL, "rte_mp_request_sync failed\n"); 411 else 412 ret = 0; 413 return ret; 414 } 415 416 if (mp_reply.nb_sent != mp_reply.nb_received) { 417 RTE_LOG(ERR, EAL, "not all secondary reply\n"); 418 free(mp_reply.msgs); 419 return -1; 420 } 421 422 req->result = 0; 423 for (i = 0; i < mp_reply.nb_received; i++) { 424 struct eal_dev_mp_req *resp = 425 (struct eal_dev_mp_req *)mp_reply.msgs[i].param; 426 if (resp->result != 0) { 427 if (req->t == EAL_DEV_REQ_TYPE_ATTACH && 428 resp->result == -EEXIST) 429 continue; 430 if (req->t == EAL_DEV_REQ_TYPE_DETACH && 431 resp->result == -ENOENT) 432 continue; 433 req->result = resp->result; 434 } 435 } 436 437 free(mp_reply.msgs); 438 return 0; 439 } 440 441 int eal_mp_dev_hotplug_init(void) 442 { 443 int ret; 444 445 if (rte_eal_process_type() == RTE_PROC_PRIMARY) { 446 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST, 447 handle_secondary_request); 448 /* primary is allowed to not support IPC */ 449 if (ret != 0 && rte_errno != ENOTSUP) { 450 RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n", 451 EAL_DEV_MP_ACTION_REQUEST); 452 return ret; 453 } 454 } else { 455 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST, 456 handle_primary_request); 457 if (ret != 0) { 458 RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n", 459 EAL_DEV_MP_ACTION_REQUEST); 460 return ret; 461 } 462 } 463 464 return 0; 465 } 466