1 /*
2  * Copyright (c) 2022 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/ipc/icmsg.h>
8 
9 #include <string.h>
10 #include <zephyr/drivers/mbox.h>
11 #include <zephyr/sys/atomic.h>
12 #include <zephyr/ipc/pbuf.h>
13 #include <zephyr/init.h>
14 
15 
16 #define UNBOUND_DISABLED IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_DISABLED_ALLOWED)
17 #define UNBOUND_ENABLED IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_ENABLED_ALLOWED)
18 #define UNBOUND_DETECT IS_ENABLED(CONFIG_IPC_SERVICE_ICMSG_UNBOUND_DETECT_ALLOWED)
19 
20 /** Get local session id request from RX handshake word.
21  */
22 #define LOCAL_SID_REQ_FROM_RX(rx_handshake) ((rx_handshake) & 0xFFFF)
23 
24 /** Get remote session id request from TX handshake word.
25  */
26 #define REMOTE_SID_REQ_FROM_TX(tx_handshake) ((tx_handshake) & 0xFFFF)
27 
28 /** Get local session id acknowledge from TX handshake word.
29  */
30 #define LOCAL_SID_ACK_FROM_TX(tx_handshake) ((tx_handshake) >> 16)
31 
32 /** Create RX handshake word from local session id request
33  * and remote session id acknowledge.
34  */
35 #define MAKE_RX_HANDSHAKE(local_sid_req, remote_sid_ack) \
36 	((local_sid_req) | ((remote_sid_ack) << 16))
37 
38 /** Create TX handshake word from remote session id request
39  * and local session id acknowledge.
40  */
41 #define MAKE_TX_HANDSHAKE(remote_sid_req, local_sid_ack) \
42 	((remote_sid_req) | ((local_sid_ack) << 16))
43 
44 /** Special session id indicating that peers are disconnected.
45  */
46 #define SID_DISCONNECTED 0
47 
48 #define SHMEM_ACCESS_TO		K_MSEC(CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_TO_MS)
49 
50 static const uint8_t magic[] = {0x45, 0x6d, 0x31, 0x6c, 0x31, 0x4b,
51 				0x30, 0x72, 0x6e, 0x33, 0x6c, 0x69, 0x34};
52 
53 #ifdef CONFIG_MULTITHREADING
54 #if defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE)
55 static K_THREAD_STACK_DEFINE(icmsg_stack, CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_STACK_SIZE);
56 static struct k_work_q icmsg_workq;
57 static struct k_work_q *const workq = &icmsg_workq;
58 #else /* defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE) */
59 static struct k_work_q *const workq = &k_sys_work_q;
60 #endif /* defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE) */
61 #endif /* def CONFIG_MULTITHREADING */
62 
mbox_deinit(const struct icmsg_config_t * conf,struct icmsg_data_t * dev_data)63 static int mbox_deinit(const struct icmsg_config_t *conf,
64 		       struct icmsg_data_t *dev_data)
65 {
66 	int err;
67 
68 	err = mbox_set_enabled_dt(&conf->mbox_rx, 0);
69 	if (err != 0) {
70 		return err;
71 	}
72 
73 	err = mbox_register_callback_dt(&conf->mbox_rx, NULL, NULL);
74 	if (err != 0) {
75 		return err;
76 	}
77 
78 #ifdef CONFIG_MULTITHREADING
79 	(void)k_work_cancel(&dev_data->mbox_work);
80 #endif
81 
82 	return 0;
83 }
84 
is_endpoint_ready(atomic_t state)85 static bool is_endpoint_ready(atomic_t state)
86 {
87 	return state >= MIN(ICMSG_STATE_CONNECTED_SID_DISABLED, ICMSG_STATE_CONNECTED_SID_ENABLED);
88 }
89 
reserve_tx_buffer_if_unused(struct icmsg_data_t * dev_data)90 static inline int reserve_tx_buffer_if_unused(struct icmsg_data_t *dev_data)
91 {
92 #ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
93 	return k_mutex_lock(&dev_data->tx_lock, SHMEM_ACCESS_TO);
94 #else
95 	return 0;
96 #endif
97 }
98 
release_tx_buffer(struct icmsg_data_t * dev_data)99 static inline int release_tx_buffer(struct icmsg_data_t *dev_data)
100 {
101 #ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
102 	return k_mutex_unlock(&dev_data->tx_lock);
103 #else
104 	return 0;
105 #endif
106 }
107 
data_available(struct icmsg_data_t * dev_data)108 static uint32_t data_available(struct icmsg_data_t *dev_data)
109 {
110 	return pbuf_read(dev_data->rx_pb, NULL, 0);
111 }
112 
113 #ifdef CONFIG_MULTITHREADING
submit_mbox_work(struct icmsg_data_t * dev_data)114 static void submit_mbox_work(struct icmsg_data_t *dev_data)
115 {
116 	if (k_work_submit_to_queue(workq, &dev_data->mbox_work) < 0) {
117 		/* The mbox processing work is never canceled.
118 		 * The negative error code should never be seen.
119 		 */
120 		__ASSERT_NO_MSG(false);
121 	}
122 }
123 
124 #endif
125 
initialize_tx_with_sid_disabled(struct icmsg_data_t * dev_data)126 static int initialize_tx_with_sid_disabled(struct icmsg_data_t *dev_data)
127 {
128 	int ret;
129 
130 	ret = pbuf_tx_init(dev_data->tx_pb);
131 
132 	if (ret < 0) {
133 		__ASSERT(false, "Incorrect Tx configuration");
134 		return ret;
135 	}
136 
137 	ret = pbuf_write(dev_data->tx_pb, magic, sizeof(magic));
138 
139 	if (ret < 0) {
140 		__ASSERT_NO_MSG(false);
141 		return ret;
142 	}
143 
144 	if (ret < (int)sizeof(magic)) {
145 		__ASSERT_NO_MSG(ret == sizeof(magic));
146 		return -EINVAL;
147 	}
148 
149 	return 0;
150 }
151 
callback_process(struct icmsg_data_t * dev_data)152 static bool callback_process(struct icmsg_data_t *dev_data)
153 {
154 	int ret;
155 	uint8_t rx_buffer[CONFIG_PBUF_RX_READ_BUF_SIZE] __aligned(4);
156 	uint32_t len = 0;
157 	uint32_t len_available;
158 	bool rerun = false;
159 	bool notify_remote = false;
160 	atomic_t state = atomic_get(&dev_data->state);
161 
162 	switch (state) {
163 
164 #if UNBOUND_DETECT
165 	case ICMSG_STATE_INITIALIZING_SID_DETECT: {
166 		/* Initialization with detection of remote session awareness */
167 		volatile char *magic_buf;
168 		uint16_t magic_len;
169 
170 		ret = pbuf_get_initial_buf(dev_data->rx_pb, &magic_buf, &magic_len);
171 
172 		if (ret == 0 && magic_len == sizeof(magic) &&
173 		    memcmp((void *)magic_buf, magic, sizeof(magic)) == 0) {
174 			/* Remote initialized in session-unaware mode, so we do old way of
175 			 * initialization.
176 			 */
177 			ret = initialize_tx_with_sid_disabled(dev_data);
178 			if (ret < 0) {
179 				if (dev_data->cb->error) {
180 					dev_data->cb->error("Incorrect Tx configuration",
181 							    dev_data->ctx);
182 				}
183 				__ASSERT(false, "Incorrect Tx configuration");
184 				atomic_set(&dev_data->state, ICMSG_STATE_OFF);
185 				return false;
186 			}
187 			/* We got magic data, so we can handle it later. */
188 			notify_remote = true;
189 			rerun = true;
190 			atomic_set(&dev_data->state, ICMSG_STATE_INITIALIZING_SID_DISABLED);
191 			break;
192 		}
193 		/* If remote did not initialize the RX in session-unaware mode, we can try
194 		 * with session-aware initialization.
195 		 */
196 		__fallthrough;
197 	}
198 #endif /* UNBOUND_DETECT */
199 
200 #if UNBOUND_ENABLED || UNBOUND_DETECT
201 	case ICMSG_STATE_INITIALIZING_SID_ENABLED: {
202 		uint32_t tx_handshake = pbuf_handshake_read(dev_data->tx_pb);
203 		uint32_t remote_sid_req = REMOTE_SID_REQ_FROM_TX(tx_handshake);
204 		uint32_t local_sid_ack = LOCAL_SID_ACK_FROM_TX(tx_handshake);
205 
206 		if (remote_sid_req != dev_data->remote_sid && remote_sid_req != SID_DISCONNECTED) {
207 			/* We can now initialize TX, since we know that remote, during receiving,
208 			 * will first read FIFO indexes and later, it will check if session has
209 			 * changed before using indexes to receive the message. Additionally,
210 			 * we know that remote after session request change will no try to receive
211 			 * more data.
212 			 */
213 			ret = pbuf_tx_init(dev_data->tx_pb);
214 			if (ret < 0) {
215 				if (dev_data->cb->error) {
216 					dev_data->cb->error("Incorrect Tx configuration",
217 						dev_data->ctx);
218 				}
219 				__ASSERT(false, "Incorrect Tx configuration");
220 				atomic_set(&dev_data->state, ICMSG_STATE_DISCONNECTED);
221 				return false;
222 			}
223 			/* Acknowledge the remote session. */
224 			dev_data->remote_sid = remote_sid_req;
225 			pbuf_handshake_write(dev_data->rx_pb,
226 				MAKE_RX_HANDSHAKE(dev_data->local_sid, dev_data->remote_sid));
227 			notify_remote = true;
228 		}
229 
230 		if (local_sid_ack == dev_data->local_sid &&
231 		    dev_data->remote_sid != SID_DISCONNECTED) {
232 			/* We send acknowledge to remote, receive acknowledge from remote,
233 			 * so we are ready.
234 			 */
235 			atomic_set(&dev_data->state, ICMSG_STATE_CONNECTED_SID_ENABLED);
236 
237 			if (dev_data->cb->bound) {
238 				dev_data->cb->bound(dev_data->ctx);
239 			}
240 			/* Re-run this handler, because remote may already send something. */
241 			rerun = true;
242 			notify_remote = true;
243 		}
244 
245 		break;
246 	}
247 #endif /* UNBOUND_ENABLED || UNBOUND_DETECT */
248 
249 #if UNBOUND_ENABLED || UNBOUND_DETECT
250 	case ICMSG_STATE_CONNECTED_SID_ENABLED:
251 #endif
252 #if UNBOUND_DISABLED || UNBOUND_DETECT
253 	case ICMSG_STATE_CONNECTED_SID_DISABLED:
254 #endif
255 #if UNBOUND_DISABLED
256 	case ICMSG_STATE_INITIALIZING_SID_DISABLED:
257 #endif
258 
259 		len_available = data_available(dev_data);
260 
261 		if (len_available > 0 && sizeof(rx_buffer) >= len_available) {
262 			len = pbuf_read(dev_data->rx_pb, rx_buffer, sizeof(rx_buffer));
263 		}
264 
265 		if (state == ICMSG_STATE_CONNECTED_SID_ENABLED &&
266 		    (UNBOUND_ENABLED || UNBOUND_DETECT)) {
267 			/* The incoming message is valid only if remote session is as expected,
268 			 * so we need to check remote session now.
269 			 */
270 			uint32_t remote_sid_req = REMOTE_SID_REQ_FROM_TX(
271 				pbuf_handshake_read(dev_data->tx_pb));
272 
273 			if (remote_sid_req != dev_data->remote_sid) {
274 				atomic_set(&dev_data->state, ICMSG_STATE_DISCONNECTED);
275 				if (dev_data->cb->unbound) {
276 					dev_data->cb->unbound(dev_data->ctx);
277 				}
278 				return false;
279 			}
280 		}
281 
282 		if (len_available == 0) {
283 			/* Unlikely, no data in buffer. */
284 			return false;
285 		}
286 
287 		__ASSERT_NO_MSG(len_available <= sizeof(rx_buffer));
288 
289 		if (sizeof(rx_buffer) < len_available) {
290 			return false;
291 		}
292 
293 		if (state != ICMSG_STATE_INITIALIZING_SID_DISABLED || !UNBOUND_DISABLED) {
294 			if (dev_data->cb->received) {
295 				dev_data->cb->received(rx_buffer, len, dev_data->ctx);
296 			}
297 		} else {
298 			/* Allow magic number longer than sizeof(magic) for future protocol
299 			 * version.
300 			 */
301 			bool endpoint_invalid = (len < sizeof(magic) ||
302 						memcmp(magic, rx_buffer, sizeof(magic)));
303 
304 			if (endpoint_invalid) {
305 				__ASSERT_NO_MSG(false);
306 				return false;
307 			}
308 
309 			if (dev_data->cb->bound) {
310 				dev_data->cb->bound(dev_data->ctx);
311 			}
312 
313 			atomic_set(&dev_data->state, ICMSG_STATE_CONNECTED_SID_DISABLED);
314 			notify_remote = true;
315 		}
316 
317 		rerun = (data_available(dev_data) > 0);
318 		break;
319 
320 	case ICMSG_STATE_OFF:
321 	case ICMSG_STATE_DISCONNECTED:
322 	default:
323 		/* Nothing to do in this state. */
324 		return false;
325 	}
326 
327 	if (notify_remote) {
328 		(void)mbox_send_dt(&dev_data->cfg->mbox_tx, NULL);
329 	}
330 
331 	return rerun;
332 }
333 
334 #ifdef CONFIG_MULTITHREADING
workq_callback_process(struct k_work * item)335 static void workq_callback_process(struct k_work *item)
336 {
337 	bool rerun;
338 	struct icmsg_data_t *dev_data = CONTAINER_OF(item, struct icmsg_data_t, mbox_work);
339 
340 	rerun = callback_process(dev_data);
341 	if (rerun) {
342 		submit_mbox_work(dev_data);
343 	}
344 }
345 #endif /* def CONFIG_MULTITHREADING */
346 
mbox_callback(const struct device * instance,uint32_t channel,void * user_data,struct mbox_msg * msg_data)347 static void mbox_callback(const struct device *instance, uint32_t channel,
348 			  void *user_data, struct mbox_msg *msg_data)
349 {
350 	bool rerun;
351 	struct icmsg_data_t *dev_data = user_data;
352 
353 #ifdef CONFIG_MULTITHREADING
354 	ARG_UNUSED(rerun);
355 	submit_mbox_work(dev_data);
356 #else
357 	do {
358 		rerun = callback_process(dev_data);
359 	} while (rerun);
360 #endif
361 }
362 
mbox_init(const struct icmsg_config_t * conf,struct icmsg_data_t * dev_data)363 static int mbox_init(const struct icmsg_config_t *conf,
364 		     struct icmsg_data_t *dev_data)
365 {
366 	int err;
367 
368 #ifdef CONFIG_MULTITHREADING
369 	k_work_init(&dev_data->mbox_work, workq_callback_process);
370 #endif
371 
372 	err = mbox_register_callback_dt(&conf->mbox_rx, mbox_callback, dev_data);
373 	if (err != 0) {
374 		return err;
375 	}
376 
377 	return mbox_set_enabled_dt(&conf->mbox_rx, 1);
378 }
379 
icmsg_open(const struct icmsg_config_t * conf,struct icmsg_data_t * dev_data,const struct ipc_service_cb * cb,void * ctx)380 int icmsg_open(const struct icmsg_config_t *conf,
381 	       struct icmsg_data_t *dev_data,
382 	       const struct ipc_service_cb *cb, void *ctx)
383 {
384 	int ret;
385 	enum icmsg_state old_state;
386 
387 	__ASSERT(conf->unbound_mode != ICMSG_UNBOUND_MODE_DISABLE || UNBOUND_DISABLED,
388 		 "Unbound mode \"disabled\" is was forbidden in Kconfig.");
389 	__ASSERT(conf->unbound_mode != ICMSG_UNBOUND_MODE_ENABLE || UNBOUND_ENABLED,
390 		 "Unbound mode \"enabled\" is was forbidden in Kconfig.");
391 	__ASSERT(conf->unbound_mode != ICMSG_UNBOUND_MODE_DETECT || UNBOUND_DETECT,
392 		 "Unbound mode \"detect\" is was forbidden in Kconfig.");
393 
394 	if (conf->unbound_mode == ICMSG_UNBOUND_MODE_DISABLE ||
395 	    !(UNBOUND_ENABLED || UNBOUND_DETECT)) {
396 		if (!atomic_cas(&dev_data->state, ICMSG_STATE_OFF,
397 				ICMSG_STATE_INITIALIZING_SID_DISABLED)) {
398 			/* Already opened. */
399 			return -EALREADY;
400 		}
401 		old_state = ICMSG_STATE_OFF;
402 	} else {
403 		/* Unbound mode has the same values as ICMSG_STATE_INITIALIZING_* */
404 		old_state = atomic_set(&dev_data->state, conf->unbound_mode);
405 	}
406 
407 	dev_data->cb = cb;
408 	dev_data->ctx = ctx;
409 	dev_data->cfg = conf;
410 
411 #ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
412 	k_mutex_init(&dev_data->tx_lock);
413 #endif
414 
415 	ret = pbuf_rx_init(dev_data->rx_pb);
416 
417 	if (ret < 0) {
418 		__ASSERT(false, "Incorrect Rx configuration");
419 		goto cleanup_and_exit;
420 	}
421 
422 	if (conf->unbound_mode != ICMSG_UNBOUND_MODE_DISABLE &&
423 	    (UNBOUND_ENABLED || UNBOUND_DETECT)) {
424 		/* Increment local session id without conflicts with forbidden values. */
425 		uint32_t local_sid_ack =
426 			LOCAL_SID_ACK_FROM_TX(pbuf_handshake_read(dev_data->tx_pb));
427 		dev_data->local_sid =
428 			LOCAL_SID_REQ_FROM_RX(pbuf_handshake_read(dev_data->tx_pb));
429 		dev_data->remote_sid = SID_DISCONNECTED;
430 		do {
431 			dev_data->local_sid = (dev_data->local_sid + 1) & 0xFFFF;
432 		} while (dev_data->local_sid == local_sid_ack ||
433 			 dev_data->local_sid == SID_DISCONNECTED);
434 		/* Write local session id request without remote acknowledge */
435 		pbuf_handshake_write(dev_data->rx_pb,
436 			MAKE_RX_HANDSHAKE(dev_data->local_sid, SID_DISCONNECTED));
437 	} else if (UNBOUND_DISABLED) {
438 		ret = initialize_tx_with_sid_disabled(dev_data);
439 	}
440 
441 	if (old_state == ICMSG_STATE_OFF && (UNBOUND_ENABLED || UNBOUND_DETECT)) {
442 		/* Initialize mbox only if we are doing first-time open (not re-open
443 		 * after unbound)
444 		 */
445 		ret = mbox_init(conf, dev_data);
446 		if (ret) {
447 			goto cleanup_and_exit;
448 		}
449 	}
450 
451 	/* We need to send a notification to remote, it may not be delivered
452 	 * since it may be uninitialized yet, but when it finishes the initialization
453 	 * we get a notification from it. We need to send this notification in callback
454 	 * again to make sure that it arrived.
455 	 */
456 	ret = mbox_send_dt(&conf->mbox_tx, NULL);
457 
458 	if (ret < 0) {
459 		__ASSERT(false, "Cannot send mbox notification");
460 		goto cleanup_and_exit;
461 	}
462 
463 	return ret;
464 
465 cleanup_and_exit:
466 	atomic_set(&dev_data->state, ICMSG_STATE_OFF);
467 	return ret;
468 }
469 
icmsg_close(const struct icmsg_config_t * conf,struct icmsg_data_t * dev_data)470 int icmsg_close(const struct icmsg_config_t *conf,
471 		struct icmsg_data_t *dev_data)
472 {
473 	int ret = 0;
474 	enum icmsg_state old_state;
475 
476 	if (conf->unbound_mode != ICMSG_UNBOUND_MODE_DISABLE &&
477 	    (UNBOUND_ENABLED || UNBOUND_DETECT)) {
478 		pbuf_handshake_write(dev_data->rx_pb,
479 			MAKE_RX_HANDSHAKE(SID_DISCONNECTED, SID_DISCONNECTED));
480 	}
481 
482 	(void)mbox_send_dt(&conf->mbox_tx, NULL);
483 
484 	old_state = atomic_set(&dev_data->state, ICMSG_STATE_OFF);
485 
486 	if (old_state != ICMSG_STATE_OFF) {
487 		ret = mbox_deinit(conf, dev_data);
488 	}
489 
490 	return ret;
491 }
492 
icmsg_send(const struct icmsg_config_t * conf,struct icmsg_data_t * dev_data,const void * msg,size_t len)493 int icmsg_send(const struct icmsg_config_t *conf,
494 	       struct icmsg_data_t *dev_data,
495 	       const void *msg, size_t len)
496 {
497 	int ret;
498 	int write_ret;
499 	int release_ret;
500 	int sent_bytes;
501 	uint32_t state = atomic_get(&dev_data->state);
502 
503 	if (!is_endpoint_ready(state)) {
504 		/* If instance was disconnected on the remote side, some threads may still
505 		 * don't know it yet and still may try to send messages.
506 		 */
507 		return (state == ICMSG_STATE_DISCONNECTED) ? len : -EBUSY;
508 	}
509 
510 	/* Empty message is not allowed */
511 	if (len == 0) {
512 		return -ENODATA;
513 	}
514 
515 	ret = reserve_tx_buffer_if_unused(dev_data);
516 	if (ret < 0) {
517 		return -ENOBUFS;
518 	}
519 
520 	write_ret = pbuf_write(dev_data->tx_pb, msg, len);
521 
522 	release_ret = release_tx_buffer(dev_data);
523 	__ASSERT_NO_MSG(!release_ret);
524 
525 	if (write_ret < 0) {
526 		return write_ret;
527 	} else if (write_ret < len) {
528 		return -EBADMSG;
529 	}
530 	sent_bytes = write_ret;
531 
532 	__ASSERT_NO_MSG(conf->mbox_tx.dev != NULL);
533 
534 	ret = mbox_send_dt(&conf->mbox_tx, NULL);
535 	if (ret) {
536 		return ret;
537 	}
538 
539 	return sent_bytes;
540 }
541 
542 #if defined(CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_ENABLE)
543 
work_q_init(void)544 static int work_q_init(void)
545 {
546 	struct k_work_queue_config cfg = {
547 		.name = "icmsg_workq",
548 	};
549 
550 	k_work_queue_start(&icmsg_workq,
551 			    icmsg_stack,
552 			    K_KERNEL_STACK_SIZEOF(icmsg_stack),
553 			    CONFIG_IPC_SERVICE_BACKEND_ICMSG_WQ_PRIORITY, &cfg);
554 	return 0;
555 }
556 
557 SYS_INIT(work_q_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
558 
559 #endif
560