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 		free(bundle->peer);
212 		free(bundle);
213 		return send_response_to_secondary(req, ret, peer);
214 	}
215 	return 0;
216 }
217 
218 static void __handle_primary_request(void *param)
219 {
220 	struct mp_reply_bundle *bundle = param;
221 	struct rte_mp_msg *msg = &bundle->msg;
222 	const struct eal_dev_mp_req *req =
223 		(const struct eal_dev_mp_req *)msg->param;
224 	struct rte_mp_msg mp_resp;
225 	struct eal_dev_mp_req *resp =
226 		(struct eal_dev_mp_req *)mp_resp.param;
227 	struct rte_devargs *da;
228 	struct rte_device *dev;
229 	struct rte_bus *bus;
230 	int ret = 0;
231 
232 	memset(&mp_resp, 0, sizeof(mp_resp));
233 
234 	switch (req->t) {
235 	case EAL_DEV_REQ_TYPE_ATTACH:
236 	case EAL_DEV_REQ_TYPE_DETACH_ROLLBACK:
237 		ret = local_dev_probe(req->devargs, &dev);
238 		break;
239 	case EAL_DEV_REQ_TYPE_DETACH:
240 	case EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK:
241 		da = calloc(1, sizeof(*da));
242 		if (da == NULL) {
243 			ret = -ENOMEM;
244 			break;
245 		}
246 
247 		ret = rte_devargs_parse(da, req->devargs);
248 		if (ret != 0)
249 			goto quit;
250 
251 		bus = rte_bus_find_by_name(da->bus->name);
252 		if (bus == NULL) {
253 			RTE_LOG(ERR, EAL, "Cannot find bus (%s)\n", da->bus->name);
254 			ret = -ENOENT;
255 			goto quit;
256 		}
257 
258 		dev = bus->find_device(NULL, cmp_dev_name, da->name);
259 		if (dev == NULL) {
260 			RTE_LOG(ERR, EAL, "Cannot find plugged device (%s)\n", da->name);
261 			ret = -ENOENT;
262 			goto quit;
263 		}
264 
265 		if (!rte_dev_is_probed(dev)) {
266 			if (req->t == EAL_DEV_REQ_TYPE_ATTACH_ROLLBACK) {
267 				/**
268 				 * Don't fail the rollback just because there's
269 				 * nothing to do.
270 				 */
271 				ret = 0;
272 			} else
273 				ret = -ENODEV;
274 
275 			goto quit;
276 		}
277 
278 		ret = local_dev_remove(dev);
279 quit:
280 		free(da->args);
281 		free(da);
282 		break;
283 	default:
284 		ret = -EINVAL;
285 	}
286 
287 	strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name));
288 	mp_resp.len_param = sizeof(*req);
289 	memcpy(resp, req, sizeof(*resp));
290 	resp->result = ret;
291 	if (rte_mp_reply(&mp_resp, bundle->peer) < 0)
292 		RTE_LOG(ERR, EAL, "failed to send reply to primary request\n");
293 
294 	free(bundle->peer);
295 	free(bundle);
296 }
297 
298 static int
299 handle_primary_request(const struct rte_mp_msg *msg, const void *peer)
300 {
301 	struct rte_mp_msg mp_resp;
302 	const struct eal_dev_mp_req *req =
303 		(const struct eal_dev_mp_req *)msg->param;
304 	struct eal_dev_mp_req *resp =
305 		(struct eal_dev_mp_req *)mp_resp.param;
306 	struct mp_reply_bundle *bundle;
307 	int ret = 0;
308 
309 	memset(&mp_resp, 0, sizeof(mp_resp));
310 	strlcpy(mp_resp.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_resp.name));
311 	mp_resp.len_param = sizeof(*req);
312 	memcpy(resp, req, sizeof(*resp));
313 
314 	bundle = calloc(1, sizeof(*bundle));
315 	if (bundle == NULL) {
316 		resp->result = -ENOMEM;
317 		ret = rte_mp_reply(&mp_resp, peer);
318 		if (ret)
319 			RTE_LOG(ERR, EAL, "failed to send reply to primary request\n");
320 		return ret;
321 	}
322 
323 	bundle->msg = *msg;
324 	/**
325 	 * We need to send reply on interrupt thread, but peer can't be
326 	 * parsed directly, so this is a temporal hack, need to be fixed
327 	 * when it is ready.
328 	 */
329 	bundle->peer = (void *)strdup(peer);
330 
331 	/**
332 	 * We are at IPC callback thread, sync IPC is not allowed due to
333 	 * dead lock, so we delegate the task to interrupt thread.
334 	 */
335 	ret = rte_eal_alarm_set(1, __handle_primary_request, bundle);
336 	if (ret != 0) {
337 		free(bundle->peer);
338 		free(bundle);
339 		resp->result = ret;
340 		ret = rte_mp_reply(&mp_resp, peer);
341 		if  (ret != 0) {
342 			RTE_LOG(ERR, EAL, "failed to send reply to primary request\n");
343 			return ret;
344 		}
345 	}
346 	return 0;
347 }
348 
349 int eal_dev_hotplug_request_to_primary(struct eal_dev_mp_req *req)
350 {
351 	struct rte_mp_msg mp_req;
352 	struct rte_mp_reply mp_reply;
353 	struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0};
354 	struct eal_dev_mp_req *resp;
355 	int ret;
356 
357 	memset(&mp_req, 0, sizeof(mp_req));
358 	memcpy(mp_req.param, req, sizeof(*req));
359 	mp_req.len_param = sizeof(*req);
360 	strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name));
361 
362 	ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts);
363 	if (ret || mp_reply.nb_received != 1) {
364 		RTE_LOG(ERR, EAL, "Cannot send request to primary\n");
365 		if (!ret)
366 			return -1;
367 		return ret;
368 	}
369 
370 	resp = (struct eal_dev_mp_req *)mp_reply.msgs[0].param;
371 	req->result = resp->result;
372 
373 	free(mp_reply.msgs);
374 	return ret;
375 }
376 
377 int eal_dev_hotplug_request_to_secondary(struct eal_dev_mp_req *req)
378 {
379 	struct rte_mp_msg mp_req;
380 	struct rte_mp_reply mp_reply;
381 	struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0};
382 	int ret;
383 	int i;
384 
385 	memset(&mp_req, 0, sizeof(mp_req));
386 	memcpy(mp_req.param, req, sizeof(*req));
387 	mp_req.len_param = sizeof(*req);
388 	strlcpy(mp_req.name, EAL_DEV_MP_ACTION_REQUEST, sizeof(mp_req.name));
389 
390 	ret = rte_mp_request_sync(&mp_req, &mp_reply, &ts);
391 	if (ret != 0) {
392 		RTE_LOG(ERR, EAL, "rte_mp_request_sync failed\n");
393 		return ret;
394 	}
395 
396 	if (mp_reply.nb_sent != mp_reply.nb_received) {
397 		RTE_LOG(ERR, EAL, "not all secondary reply\n");
398 		free(mp_reply.msgs);
399 		return -1;
400 	}
401 
402 	req->result = 0;
403 	for (i = 0; i < mp_reply.nb_received; i++) {
404 		struct eal_dev_mp_req *resp =
405 			(struct eal_dev_mp_req *)mp_reply.msgs[i].param;
406 		if (resp->result != 0) {
407 			if (req->t == EAL_DEV_REQ_TYPE_ATTACH &&
408 				resp->result == -EEXIST)
409 				continue;
410 			if (req->t == EAL_DEV_REQ_TYPE_DETACH &&
411 				resp->result == -ENOENT)
412 				continue;
413 			req->result = resp->result;
414 		}
415 	}
416 
417 	free(mp_reply.msgs);
418 	return 0;
419 }
420 
421 int rte_mp_dev_hotplug_init(void)
422 {
423 	int ret;
424 
425 	if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
426 		ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST,
427 					handle_secondary_request);
428 		if (ret != 0) {
429 			RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n",
430 				EAL_DEV_MP_ACTION_REQUEST);
431 			return ret;
432 		}
433 	} else {
434 		ret = rte_mp_action_register(EAL_DEV_MP_ACTION_REQUEST,
435 					handle_primary_request);
436 		if (ret != 0) {
437 			RTE_LOG(ERR, EAL, "Couldn't register '%s' action\n",
438 				EAL_DEV_MP_ACTION_REQUEST);
439 			return ret;
440 		}
441 	}
442 
443 	return 0;
444 }
445