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 if (req->t == EAL_DEV_REQ_TYPE_ATTACH) {
99 ret = local_dev_probe(req->devargs, &dev);
100 if (ret != 0) {
101 RTE_LOG(ERR, EAL, "Failed to hotplug add device on primary\n");
102 if (ret != -EEXIST)
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 free(da.args); /* we don't need those */
122 da.args = NULL;
123
124 ret = eal_dev_hotplug_request_to_secondary(&tmp_req);
125 if (ret != 0) {
126 RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n");
127 ret = -ENOMSG;
128 goto rollback;
129 }
130
131 bus = rte_bus_find_by_name(da.bus->name);
132 if (bus == NULL) {
133 RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", 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 RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da.name);
141 ret = -ENOENT;
142 goto finish;
143 }
144
145 if (tmp_req.result != 0) {
146 RTE_LOG(ERR, EAL, "Failed to hotplug remove device on secondary\n");
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 RTE_LOG(ERR, EAL, "Failed to hotplug remove device on primary\n");
155 if (ret != -ENOENT)
156 goto rollback;
157 }
158 } else {
159 RTE_LOG(ERR, EAL, "unsupported secondary to primary request\n");
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 RTE_LOG(ERR, EAL, "failed to send response to secondary\n");
178
179 free(bundle->peer);
180 free(bundle);
181 }
182
183 static int
handle_secondary_request(const struct rte_mp_msg * msg,const void * peer)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
__handle_primary_request(void * param)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 free(da->args);
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
handle_primary_request(const struct rte_mp_msg * msg,const void * peer)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
eal_dev_hotplug_request_to_primary(struct eal_dev_mp_req * req)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
eal_dev_hotplug_request_to_secondary(struct eal_dev_mp_req * req)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
eal_mp_dev_hotplug_init(void)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