xref: /dpdk/lib/eal/common/hotplug_mp.c (revision daa02b5cddbb8e11b31d41e2bf7bb1ae64dcae2f)
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