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
cmp_dev_name(const struct rte_device * dev,const void * _name)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
send_response_to_secondary(const struct eal_dev_mp_req * req,int result,const void * peer)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
__handle_secondary_request(void * param)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
handle_secondary_request(const struct rte_mp_msg * msg,const void * peer)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
__handle_primary_request(void * param)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
handle_primary_request(const struct rte_mp_msg * msg,const void * peer)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
eal_dev_hotplug_request_to_primary(struct eal_dev_mp_req * req)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
eal_dev_hotplug_request_to_secondary(struct eal_dev_mp_req * req)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
eal_mp_dev_hotplug_init(void)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
eal_mp_dev_hotplug_cleanup(void)466 void eal_mp_dev_hotplug_cleanup(void)
467 {
468 rte_mp_action_unregister(EAL_DEV_MP_ACTION_REQUEST);
469 }
470