ipc: handle unsupported IPC in action register
[dpdk.git] / lib / librte_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         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
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                 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
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                 RTE_LOG(ERR, EAL, "rte_mp_request_sync failed\n");
409                 return ret;
410         }
411
412         if (mp_reply.nb_sent != mp_reply.nb_received) {
413                 RTE_LOG(ERR, EAL, "not all secondary reply\n");
414                 free(mp_reply.msgs);
415                 return -1;
416         }
417
418         req->result = 0;
419         for (i = 0; i < mp_reply.nb_received; i++) {
420                 struct eal_dev_mp_req *resp =
421                         (struct eal_dev_mp_req *)mp_reply.msgs[i].param;
422                 if (resp->result != 0) {
423                         if (req->t == EAL_DEV_REQ_TYPE_ATTACH &&
424                                 resp->result == -EEXIST)
425                                 continue;
426                         if (req->t == EAL_DEV_REQ_TYPE_DETACH &&
427                                 resp->result == -ENOENT)
428                                 continue;
429                         req->result = resp->result;
430                 }
431         }
432
433         free(mp_reply.msgs);
434         return 0;
435 }
436
437 int rte_mp_dev_hotplug_init(void)
438 {
439         int ret;
440
441         if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
442                 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST,
443                                         handle_secondary_request);
444                 /* primary is allowed to not support IPC */
445                 if (ret != 0 && rte_errno != ENOTSUP) {
446                         RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n",
447                                 EAL_DEV_MP_ACTION_REQUEST);
448                         return ret;
449                 }
450         } else {
451                 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST,
452                                         handle_primary_request);
453                 if (ret != 0) {
454                         RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n",
455                                 EAL_DEV_MP_ACTION_REQUEST);
456                         return ret;
457                 }
458         }
459
460         return 0;
461 }