test/mem: disable ASan when accessing unallocated memory
[dpdk.git] / lib / eal / common / hotplug_mp.c
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 && 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
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
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
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
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
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
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
466 void eal_mp_dev_hotplug_cleanup(void)
467 {
468         rte_mp_action_unregister(EAL_DEV_MP_ACTION_REQUEST);
469 }