New upstream version 18.11
[deb_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_alarm.h>
8 #include <rte_string_fns.h>
9 #include <rte_devargs.h>
10
11 #include "hotplug_mp.h"
12 #include "eal_private.h"
13
14 #define MP_TIMEOUT_S 5 /**< 5 seconds timeouts */
15
16 struct mp_reply_bundle {
17         struct rte_mp_msg msg;
18         void *peer;
19 };
20
21 static int cmp_dev_name(const struct rte_device *dev, const void *_name)
22 {
23         const char *name = _name;
24
25         return strcmp(dev->name, name);
26 }
27
28 /**
29  * Secondary to primary request.
30  * start from function eal_dev_hotplug_request_to_primary.
31  *
32  * device attach on secondary:
33  * a) secondary send sync request to the primary.
34  * b) primary receive the request and attach the new device if
35  *    failed goto i).
36  * c) primary forward attach sync request to all secondary.
37  * d) secondary receive the request and attach the device and send a reply.
38  * e) primary check the reply if all success goes to j).
39  * f) primary send attach rollback sync request to all secondary.
40  * g) secondary receive the request and detach the device and send a reply.
41  * h) primary receive the reply and detach device as rollback action.
42  * i) send attach fail to secondary as a reply of step a), goto k).
43  * j) send attach success to secondary as a reply of step a).
44  * k) secondary receive reply and return.
45  *
46  * device detach on secondary:
47  * a) secondary send sync request to the primary.
48  * b) primary send detach sync request to all secondary.
49  * c) secondary detach the device and send a reply.
50  * d) primary check the reply if all success goes to g).
51  * e) primary send detach rollback sync request to all secondary.
52  * f) secondary receive the request and attach back device. goto h).
53  * g) primary detach the device if success goto i), else goto e).
54  * h) primary send detach fail to secondary as a reply of step a), goto j).
55  * i) primary send detach success to secondary as a reply of step a).
56  * j) secondary receive reply and return.
57  */
58
59 static int
60 send_response_to_secondary(const struct eal_dev_mp_req *req,
61                         int result,
62                         const void *peer)
63 {
64         struct rte_mp_msg mp_resp;
65         struct eal_dev_mp_req *resp =
66                 (struct eal_dev_mp_req *)mp_resp.param;
67         int ret;
68
69         memset(&mp_resp, 0, sizeof(mp_resp));
70         mp_resp.len_param = sizeof(*resp);
71         strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name));
72         memcpy(resp, req, sizeof(*req));
73         resp->result = result;
74
75         ret = rte_mp_reply(&mp_resp, peer);
76         if (ret != 0)
77                 RTE_LOG(ERR, EAL, "failed to send response to secondary\n");
78
79         return ret;
80 }
81
82 static void
83 __handle_secondary_request(void *param)
84 {
85         struct mp_reply_bundle *bundle = param;
86                 const struct rte_mp_msg *msg = &bundle->msg;
87         const struct eal_dev_mp_req *req =
88                 (const struct eal_dev_mp_req *)msg->param;
89         struct eal_dev_mp_req tmp_req;
90         struct rte_devargs da;
91         struct rte_device *dev;
92         struct rte_bus *bus;
93         int ret = 0;
94
95         tmp_req = *req;
96
97         if (req->t == EAL_DEV_REQ_TYPE_ATTACH) {
98                 ret = local_dev_probe(req->devargs, &dev);
99                 if (ret != 0) {
100                         RTE_LOG(ERR, EAL, "Failed to hotplug add device on primary\n");
101                         if (ret != -EEXIST)
102                                 goto finish;
103                 }
104                 ret = eal_dev_hotplug_request_to_secondary(&tmp_req);
105                 if (ret != 0) {
106                         RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n");
107                         ret = -ENOMSG;
108                         goto rollback;
109                 }
110                 if (tmp_req.result != 0) {
111                         ret = tmp_req.result;
112                         RTE_LOG(ERR, EAL, "Failed to hotplug add device on secondary\n");
113                         if (ret != -EEXIST)
114                                 goto rollback;
115                 }
116         } else if (req->t == EAL_DEV_REQ_TYPE_DETACH) {
117                 ret = rte_devargs_parse(&da, req->devargs);
118                 if (ret != 0)
119                         goto finish;
120                 free(da.args); /* we don't need those */
121                 da.args = NULL;
122
123                 ret = eal_dev_hotplug_request_to_secondary(&tmp_req);
124                 if (ret != 0) {
125                         RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n");
126                         ret = -ENOMSG;
127                         goto rollback;
128                 }
129
130                 bus = rte_bus_find_by_name(da.bus->name);
131                 if (bus == NULL) {
132                         RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", da.bus->name);
133                         ret = -ENOENT;
134                         goto finish;
135                 }
136
137                 dev = bus->find_device(NULL, cmp_dev_name, da.name);
138                 if (dev == NULL) {
139                         RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da.name);
140                         ret = -ENOENT;
141                         goto finish;
142                 }
143
144                 if (tmp_req.result != 0) {
145                         RTE_LOG(ERR, EAL, "Failed to hotplug remove device on secondary\n");
146                         ret = tmp_req.result;
147                         if (ret != -ENOENT)
148                                 goto rollback;
149                 }
150
151                 ret = local_dev_remove(dev);
152                 if (ret != 0) {
153                         RTE_LOG(ERR, EAL, "Failed to hotplug remove device on primary\n");
154                         if (ret != -ENOENT)
155                                 goto rollback;
156                 }
157         } else {
158                 RTE_LOG(ERR, EAL, "unsupported secondary to primary request\n");
159                 ret = -ENOTSUP;
160         }
161         goto finish;
162
163 rollback:
164         if (req->t == EAL_DEV_REQ_TYPE_ATTACH) {
165                 tmp_req.t = EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK;
166                 eal_dev_hotplug_request_to_secondary(&tmp_req);
167                 local_dev_remove(dev);
168         } else {
169                 tmp_req.t = EAL_DEV_REQ_TYPE_DETACH_ROLLBACK;
170                 eal_dev_hotplug_request_to_secondary(&tmp_req);
171         }
172
173 finish:
174         ret = send_response_to_secondary(&tmp_req, ret, bundle->peer);
175         if (ret)
176                 RTE_LOG(ERR, EAL, "failed to send response to secondary\n");
177
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
204         /**
205          * We are at IPC callback thread, sync IPC is not allowed due to
206          * dead lock, so we delegate the task to interrupt thread.
207          */
208         ret = rte_eal_alarm_set(1, __handle_secondary_request, bundle);
209         if (ret != 0) {
210                 RTE_LOG(ERR, EAL, "failed to add mp task\n");
211                 return send_response_to_secondary(req, ret, peer);
212         }
213         return 0;
214 }
215
216 static void __handle_primary_request(void *param)
217 {
218         struct mp_reply_bundle *bundle = param;
219         struct rte_mp_msg *msg = &bundle->msg;
220         const struct eal_dev_mp_req *req =
221                 (const struct eal_dev_mp_req *)msg->param;
222         struct rte_mp_msg mp_resp;
223         struct eal_dev_mp_req *resp =
224                 (struct eal_dev_mp_req *)mp_resp.param;
225         struct rte_devargs *da;
226         struct rte_device *dev;
227         struct rte_bus *bus;
228         int ret = 0;
229
230         memset(&mp_resp, 0, sizeof(mp_resp));
231
232         switch (req->t) {
233         case EAL_DEV_REQ_TYPE_ATTACH:
234         case EAL_DEV_REQ_TYPE_DETACH_ROLLBACK:
235                 ret = local_dev_probe(req->devargs, &dev);
236                 break;
237         case EAL_DEV_REQ_TYPE_DETACH:
238         case EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK:
239                 da = calloc(1, sizeof(*da));
240                 if (da == NULL) {
241                         ret = -ENOMEM;
242                         break;
243                 }
244
245                 ret = rte_devargs_parse(da, req->devargs);
246                 if (ret != 0)
247                         goto quit;
248
249                 bus = rte_bus_find_by_name(da->bus->name);
250                 if (bus == NULL) {
251                         RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", da->bus->name);
252                         ret = -ENOENT;
253                         goto quit;
254                 }
255
256                 dev = bus->find_device(NULL, cmp_dev_name, da->name);
257                 if (dev == NULL) {
258                         RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da->name);
259                         ret = -ENOENT;
260                         goto quit;
261                 }
262
263                 if (!rte_dev_is_probed(dev)) {
264                         if (req->t == EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK) {
265                                 /**
266                                  * Don't fail the rollback just because there's
267                                  * nothing to do.
268                                  */
269                                 ret = 0;
270                         } else
271                                 ret = -ENODEV;
272
273                         goto quit;
274                 }
275
276                 ret = local_dev_remove(dev);
277 quit:
278                 free(da->args);
279                 free(da);
280                 break;
281         default:
282                 ret = -EINVAL;
283         }
284
285         strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name));
286         mp_resp.len_param = sizeof(*req);
287         memcpy(resp, req, sizeof(*resp));
288         resp->result = ret;
289         if (rte_mp_reply(&mp_resp, bundle->peer) < 0)
290                 RTE_LOG(ERR, EAL, "failed to send reply to primary request\n");
291
292         free(bundle->peer);
293         free(bundle);
294 }
295
296 static int
297 handle_primary_request(const struct rte_mp_msg *msg, const void *peer)
298 {
299         struct rte_mp_msg mp_resp;
300         const struct eal_dev_mp_req *req =
301                 (const struct eal_dev_mp_req *)msg->param;
302         struct eal_dev_mp_req *resp =
303                 (struct eal_dev_mp_req *)mp_resp.param;
304         struct mp_reply_bundle *bundle;
305         int ret = 0;
306
307         memset(&mp_resp, 0, sizeof(mp_resp));
308         strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name));
309         mp_resp.len_param = sizeof(*req);
310         memcpy(resp, req, sizeof(*resp));
311
312         bundle = calloc(1, sizeof(*bundle));
313         if (bundle == NULL) {
314                 resp->result = -ENOMEM;
315                 ret = rte_mp_reply(&mp_resp, peer);
316                 if (ret)
317                         RTE_LOG(ERR, EAL, "failed to send reply to primary request\n");
318                 return ret;
319         }
320
321         bundle->msg = *msg;
322         /**
323          * We need to send reply on interrupt thread, but peer can't be
324          * parsed directly, so this is a temporal hack, need to be fixed
325          * when it is ready.
326          */
327         bundle->peer = (void *)strdup(peer);
328
329         /**
330          * We are at IPC callback thread, sync IPC is not allowed due to
331          * dead lock, so we delegate the task to interrupt thread.
332          */
333         ret = rte_eal_alarm_set(1, __handle_primary_request, bundle);
334         if (ret != 0) {
335                 resp->result = ret;
336                 ret = rte_mp_reply(&mp_resp, peer);
337                 if  (ret != 0) {
338                         RTE_LOG(ERR, EAL, "failed to send reply to primary request\n");
339                         return ret;
340                 }
341         }
342         return 0;
343 }
344
345 int eal_dev_hotplug_request_to_primary(struct eal_dev_mp_req *req)
346 {
347         struct rte_mp_msg mp_req;
348         struct rte_mp_reply mp_reply;
349         struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0};
350         struct eal_dev_mp_req *resp;
351         int ret;
352
353         memset(&mp_req, 0, sizeof(mp_req));
354         memcpy(mp_req.param, req, sizeof(*req));
355         mp_req.len_param = sizeof(*req);
356         strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name));
357
358         ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts);
359         if (ret || mp_reply.nb_received != 1) {
360                 RTE_LOG(ERR, EAL, "cannot send request to primary");
361                 if (!ret)
362                         return -1;
363                 return ret;
364         }
365
366         resp = (struct eal_dev_mp_req *)mp_reply.msgs[0].param;
367         req->result = resp->result;
368
369         free(mp_reply.msgs);
370         return ret;
371 }
372
373 int eal_dev_hotplug_request_to_secondary(struct eal_dev_mp_req *req)
374 {
375         struct rte_mp_msg mp_req;
376         struct rte_mp_reply mp_reply;
377         struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0};
378         int ret;
379         int i;
380
381         memset(&mp_req, 0, sizeof(mp_req));
382         memcpy(mp_req.param, req, sizeof(*req));
383         mp_req.len_param = sizeof(*req);
384         strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name));
385
386         ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts);
387         if (ret != 0) {
388                 RTE_LOG(ERR, EAL, "rte_mp_request_sync failed\n");
389                 return ret;
390         }
391
392         if (mp_reply.nb_sent != mp_reply.nb_received) {
393                 RTE_LOG(ERR, EAL, "not all secondary reply\n");
394                 free(mp_reply.msgs);
395                 return -1;
396         }
397
398         req->result = 0;
399         for (i = 0; i < mp_reply.nb_received; i++) {
400                 struct eal_dev_mp_req *resp =
401                         (struct eal_dev_mp_req *)mp_reply.msgs[i].param;
402                 if (resp->result != 0) {
403                         if (req->t == EAL_DEV_REQ_TYPE_ATTACH &&
404                                 resp->result == -EEXIST)
405                                 continue;
406                         if (req->t == EAL_DEV_REQ_TYPE_DETACH &&
407                                 resp->result == -ENOENT)
408                                 continue;
409                         req->result = resp->result;
410                 }
411         }
412
413         free(mp_reply.msgs);
414         return 0;
415 }
416
417 int rte_mp_dev_hotplug_init(void)
418 {
419         int ret;
420
421         if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
422                 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST,
423                                         handle_secondary_request);
424                 if (ret != 0) {
425                         RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n",
426                                 EAL_DEV_MP_ACTION_REQUEST);
427                         return ret;
428                 }
429         } else {
430                 ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST,
431                                         handle_primary_request);
432                 if (ret != 0) {
433                         RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n",
434                                 EAL_DEV_MP_ACTION_REQUEST);
435                         return ret;
436                 }
437         }
438
439         return 0;
440 }