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