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 
cmp_dev_name(const struct rte_device * dev,const void * _name)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
send_response_to_secondary(const struct eal_dev_mp_req * req,int result,const void * peer)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
__handle_secondary_request(void * param)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
handle_secondary_request(const struct rte_mp_msg * msg,const void * peer)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 
__handle_primary_request(void * param)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
handle_primary_request(const struct rte_mp_msg * msg,const void * peer)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 
eal_dev_hotplug_request_to_primary(struct eal_dev_mp_req * req)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 
eal_dev_hotplug_request_to_secondary(struct eal_dev_mp_req * req)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 
eal_mp_dev_hotplug_init(void)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