xref: /dpdk/lib/eal/common/hotplug_mp.c (revision 99f9d799ce21ab22e922ffec8aad51d56e24d04d)
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) {
102 			RTE_LOG(ERR, EAL, "Failed to hotplug add device on primary\n");
103 			if (ret != -EEXIST)
104 				goto finish;
105 		}
106 		ret = eal_dev_hotplug_request_to_secondary(&tmp_req);
107 		if (ret != 0) {
108 			RTE_LOG(ERR, EAL, "Failed to send hotplug request to secondary\n");
109 			ret = -ENOMSG;
110 			goto rollback;
111 		}
112 		if (tmp_req.result != 0) {
113 			ret = tmp_req.result;
114 			RTE_LOG(ERR, EAL, "Failed to hotplug add device on secondary\n");
115 			if (ret != -EEXIST)
116 				goto rollback;
117 		}
118 	} else if (req->t == EAL_DEV_REQ_TYPE_DETACH) {
119 		ret = rte_devargs_parse(&da, req->devargs);
120 		if (ret != 0)
121 			goto finish;
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 	rte_devargs_reset(&da);
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 		rte_devargs_reset(da);
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 		/* 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 
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