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