1 /*
2  * Copyright (c) 2020 Intel Corporation.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(net_txtime_sample, LOG_LEVEL_DBG);
9 
10 #include <zephyr/kernel.h>
11 #include <errno.h>
12 #include <stdio.h>
13 #include <inttypes.h>
14 #include <zephyr/drivers/ptp_clock.h>
15 #include <zephyr/shell/shell.h>
16 
17 #include <zephyr/net/net_mgmt.h>
18 #include <zephyr/net/net_event.h>
19 #include <zephyr/net/conn_mgr_monitor.h>
20 
21 #include <zephyr/net/socket.h>
22 #include <zephyr/net/ethernet.h>
23 #include <zephyr/net/ethernet_mgmt.h>
24 
25 #define APP_BANNER "Run SO_TXTIME client"
26 
27 #define DHCPV4_MASK (NET_EVENT_IPV4_DHCP_BOUND | \
28 		     NET_EVENT_IPV4_DHCP_STOP)
29 #define EVENT_MASK (NET_EVENT_L4_CONNECTED | \
30 		    NET_EVENT_L4_DISCONNECTED)
31 
32 #define STACK_SIZE 2048
33 #define THREAD_PRIORITY K_PRIO_COOP(8)
34 #define WAIT_PERIOD (1 * MSEC_PER_SEC)
35 #define MAX_MSG_LEN 64
36 
37 static char txtime_str[MAX_MSG_LEN];
38 
39 static struct k_sem quit_lock;
40 static struct net_mgmt_event_callback mgmt_cb;
41 static struct net_mgmt_event_callback dhcpv4_cb;
42 
43 struct app_data {
44 	const struct device *clk;
45 	struct sockaddr peer;
46 	socklen_t peer_addr_len;
47 	int sock;
48 };
49 
50 static struct app_data peer_data = {
51 	.sock = -1,
52 };
53 
54 static k_tid_t tx_tid;
55 static K_THREAD_STACK_DEFINE(tx_stack, STACK_SIZE);
56 static struct k_thread tx_thread;
57 
58 static k_tid_t rx_tid;
59 static K_THREAD_STACK_DEFINE(rx_stack, STACK_SIZE);
60 static struct k_thread rx_thread;
61 
62 K_SEM_DEFINE(run_app, 0, 1);
63 static bool want_to_quit;
64 static bool connected;
65 
66 extern int init_vlan(void);
67 
quit(void)68 static void quit(void)
69 {
70 	k_sem_give(&quit_lock);
71 }
72 
event_handler(struct net_mgmt_event_callback * cb,uint32_t mgmt_event,struct net_if * iface)73 static void event_handler(struct net_mgmt_event_callback *cb,
74 			  uint32_t mgmt_event, struct net_if *iface)
75 {
76 	static bool dhcpv4_done;
77 
78 	if (want_to_quit) {
79 		k_sem_give(&run_app);
80 		want_to_quit = false;
81 	}
82 
83 	if (IS_ENABLED(CONFIG_NET_DHCPV4)) {
84 		if (mgmt_event == NET_EVENT_IPV4_DHCP_BOUND) {
85 			LOG_INF("DHCPv4 bound");
86 			dhcpv4_done = true;
87 
88 			if (connected) {
89 				k_sem_give(&run_app);
90 			}
91 
92 			return;
93 		}
94 
95 		if (mgmt_event == NET_EVENT_IPV4_DHCP_STOP) {
96 			dhcpv4_done = false;
97 			return;
98 		}
99 	}
100 
101 	if (mgmt_event == NET_EVENT_L4_CONNECTED) {
102 		if (!connected) {
103 			LOG_INF("Network connected");
104 		}
105 
106 		connected = true;
107 
108 		/* Go to connected state only after DHCPv4 is done */
109 		if (!IS_ENABLED(CONFIG_NET_DHCPV4) || dhcpv4_done) {
110 			k_sem_give(&run_app);
111 		}
112 
113 		return;
114 	}
115 
116 	if (mgmt_event == NET_EVENT_L4_DISCONNECTED) {
117 		if (connected == false) {
118 			LOG_INF("Waiting network to be connected");
119 		} else {
120 			LOG_INF("Network disconnected");
121 			connected = false;
122 		}
123 
124 		k_sem_reset(&run_app);
125 
126 		return;
127 	}
128 }
129 
rx(void * p1,void * p2,void * p3)130 static void rx(void *p1, void *p2, void *p3)
131 {
132 	ARG_UNUSED(p2);
133 	ARG_UNUSED(p3);
134 
135 	struct app_data *data = p1;
136 	static uint8_t recv_buf[sizeof(txtime_str)];
137 	struct sockaddr src;
138 	socklen_t addr_len = data->peer_addr_len;
139 	ssize_t len = 0;
140 
141 	while (true) {
142 		len += recvfrom(data->sock, recv_buf, sizeof(recv_buf), 0,
143 				&src, &addr_len);
144 		if (!(len % (100 * 1024))) {
145 			LOG_DBG("Received %zd kb data", len / 1024);
146 		}
147 	}
148 }
149 
tx(void * p1,void * p2,void * p3)150 static void tx(void *p1, void *p2, void *p3)
151 {
152 	ARG_UNUSED(p2);
153 	ARG_UNUSED(p3);
154 
155 	struct app_data *data = p1;
156 	struct net_ptp_time time;
157 	struct msghdr msg;
158 	struct cmsghdr *cmsg;
159 	struct iovec io_vector[1];
160 	union {
161 		struct cmsghdr hdr;
162 		unsigned char  buf[CMSG_SPACE(sizeof(uint64_t))];
163 	} cmsgbuf;
164 	net_time_t txtime, delay, interval;
165 	int ret;
166 	int print_offset;
167 
168 	print_offset = IS_ENABLED(CONFIG_NET_SAMPLE_PACKET_SOCKET) ?
169 		sizeof(struct net_eth_hdr) : 0;
170 
171 	interval = CONFIG_NET_SAMPLE_PACKET_INTERVAL * NSEC_PER_MSEC;
172 	delay = CONFIG_NET_SAMPLE_PACKET_TXTIME * NSEC_PER_USEC;
173 
174 	io_vector[0].iov_base = (void *)txtime_str;
175 
176 	memset(&msg, 0, sizeof(msg));
177 	msg.msg_control = &cmsgbuf.buf;
178 	msg.msg_controllen = sizeof(cmsgbuf.buf);
179 	msg.msg_iov = io_vector;
180 	msg.msg_iovlen = 1;
181 	msg.msg_name = &data->peer;
182 	msg.msg_namelen = data->peer_addr_len;
183 
184 	cmsg = CMSG_FIRSTHDR(&msg);
185 	cmsg->cmsg_len = CMSG_LEN(sizeof(txtime));
186 	cmsg->cmsg_level = SOL_SOCKET;
187 	cmsg->cmsg_type = SCM_TXTIME;
188 
189 	LOG_DBG("Sending network packets with SO_TXTIME");
190 
191 	ptp_clock_get(data->clk, &time);
192 	txtime = net_ptp_time_to_ns(&time);
193 
194 	snprintk(txtime_str + print_offset,
195 		 sizeof(txtime_str) - print_offset, "%"PRIx64, (uint64_t)txtime);
196 	io_vector[0].iov_len = sizeof(txtime_str);
197 
198 	while (1) {
199 		*(net_time_t *)CMSG_DATA(cmsg) = txtime + delay;
200 
201 		ret = sendmsg(data->sock, &msg, 0);
202 		if (ret < 0) {
203 			if (errno != ENOMEM) {
204 				LOG_DBG("Message send failed (%d)", -errno);
205 				quit();
206 				break;
207 			}
208 		}
209 
210 		txtime += interval;
211 		snprintk(txtime_str + print_offset,
212 			 sizeof(txtime_str) - print_offset, "%"PRIx64, (uint64_t)txtime);
213 
214 		k_sleep(K_NSEC(interval));
215 	}
216 }
217 
get_local_ipv6(struct net_if * iface,struct sockaddr * peer,struct sockaddr * local,socklen_t * addrlen)218 static int get_local_ipv6(struct net_if *iface, struct sockaddr *peer,
219 			  struct sockaddr *local, socklen_t *addrlen)
220 {
221 	const struct in6_addr *addr;
222 
223 	if (peer->sa_family != AF_INET6) {
224 		return 0;
225 	}
226 
227 	addr = net_if_ipv6_select_src_addr(iface, &net_sin6(peer)->sin6_addr);
228 	if (!addr) {
229 		LOG_ERR("Cannot get local %s address", "IPv6");
230 		return -EINVAL;
231 	}
232 
233 	memcpy(&net_sin6(local)->sin6_addr, addr, sizeof(*addr));
234 	local->sa_family = AF_INET6;
235 	*addrlen = sizeof(struct sockaddr_in6);
236 
237 	return 0;
238 }
239 
get_local_ipv4(struct net_if * iface,struct sockaddr * peer,struct sockaddr * local,socklen_t * addrlen)240 static int get_local_ipv4(struct net_if *iface, struct sockaddr *peer,
241 			  struct sockaddr *local, socklen_t *addrlen)
242 {
243 	const struct in_addr *addr;
244 
245 	if (peer->sa_family != AF_INET) {
246 		return 0;
247 	}
248 
249 	addr = net_if_ipv4_select_src_addr(iface, &net_sin(peer)->sin_addr);
250 	if (!addr) {
251 		LOG_ERR("Cannot get local %s address", "IPv4");
252 		return -EINVAL;
253 	}
254 
255 	memcpy(&net_sin(local)->sin_addr, addr, sizeof(*addr));
256 	local->sa_family = AF_INET;
257 	*addrlen = sizeof(struct sockaddr_in);
258 
259 	return 0;
260 }
261 
create_socket(struct net_if * iface,struct sockaddr * peer)262 static int create_socket(struct net_if *iface, struct sockaddr *peer)
263 {
264 	struct sockaddr local;
265 	socklen_t addrlen;
266 	int optval;
267 	uint8_t priority;
268 	int sock;
269 	int ret;
270 
271 	memset(&local, 0, sizeof(local));
272 
273 	if (IS_ENABLED(CONFIG_NET_SAMPLE_PACKET_SOCKET)) {
274 		struct sockaddr_ll *addr;
275 
276 		sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
277 		if (sock < 0) {
278 			LOG_ERR("Cannot create %s socket (%d)", "packet",
279 				-errno);
280 			return -errno;
281 		}
282 
283 		addr = (struct sockaddr_ll *)&local;
284 		addr->sll_ifindex = net_if_get_by_iface(net_if_get_default());
285 		addr->sll_family = AF_PACKET;
286 		addrlen = sizeof(struct sockaddr_ll);
287 
288 		LOG_DBG("Binding to interface %d (%p)", addr->sll_ifindex,
289 			net_if_get_by_index(addr->sll_ifindex));
290 	}
291 
292 	if (IS_ENABLED(CONFIG_NET_SAMPLE_UDP_SOCKET)) {
293 		char addr_str[INET6_ADDRSTRLEN];
294 
295 		sock = socket(peer->sa_family, SOCK_DGRAM, IPPROTO_UDP);
296 		if (sock < 0) {
297 			LOG_ERR("Cannot create %s socket (%d)", "UDP", -errno);
298 			return -errno;
299 		}
300 
301 		if (IS_ENABLED(CONFIG_NET_IPV6) &&
302 		    peer->sa_family == AF_INET6) {
303 			ret = get_local_ipv6(iface, peer, &local, &addrlen);
304 			if (ret < 0) {
305 				return ret;
306 			}
307 
308 			net_addr_ntop(AF_INET6, &net_sin6(&local)->sin6_addr,
309 				      addr_str, sizeof(addr_str));
310 		} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
311 			   peer->sa_family == AF_INET) {
312 			ret = get_local_ipv4(iface, peer, &local, &addrlen);
313 			if (ret < 0) {
314 				return ret;
315 			}
316 
317 			net_addr_ntop(AF_INET, &net_sin(&local)->sin_addr,
318 				      addr_str, sizeof(addr_str));
319 		} else {
320 			LOG_ERR("Invalid socket family %d", peer->sa_family);
321 			return -EINVAL;
322 		}
323 
324 		LOG_DBG("Binding to %s", addr_str);
325 	}
326 
327 	ret = bind(sock, &local, addrlen);
328 	if (ret < 0) {
329 		LOG_ERR("Cannot bind socket (%d)", -errno);
330 		return -errno;
331 	}
332 
333 	optval = true;
334 	ret = setsockopt(sock, SOL_SOCKET, SO_TXTIME, &optval, sizeof(optval));
335 	if (ret < 0) {
336 		LOG_ERR("Cannot set SO_TXTIME (%d)", -errno);
337 		return -errno;
338 	}
339 
340 	priority = NET_PRIORITY_CA;
341 	ret = setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &priority,
342 			 sizeof(priority));
343 	if (ret < 0) {
344 		LOG_ERR("Cannot set SO_PRIORITY (%d)", -errno);
345 		return -errno;
346 	}
347 
348 	return sock;
349 }
350 
get_peer_address(struct net_if ** iface,char * addr_str,int addr_str_len)351 static int get_peer_address(struct net_if **iface, char *addr_str,
352 			    int addr_str_len)
353 {
354 	int ret;
355 
356 	ret = net_ipaddr_parse(CONFIG_NET_SAMPLE_PEER,
357 			       strlen(CONFIG_NET_SAMPLE_PEER),
358 			       &peer_data.peer);
359 	if (!ret) {
360 		LOG_ERR("Cannot parse '%s'", CONFIG_NET_SAMPLE_PEER);
361 		return -EINVAL;
362 	}
363 
364 	if (net_sin(&peer_data.peer)->sin_port == 0) {
365 		net_sin(&peer_data.peer)->sin_port = htons(4242);
366 	}
367 
368 	if (IS_ENABLED(CONFIG_NET_IPV6) &&
369 					peer_data.peer.sa_family == AF_INET6) {
370 		*iface = net_if_ipv6_select_src_iface(
371 					&net_sin6(&peer_data.peer)->sin6_addr);
372 
373 		net_addr_ntop(peer_data.peer.sa_family,
374 			      &net_sin6(&peer_data.peer)->sin6_addr, addr_str,
375 			      addr_str_len);
376 		peer_data.peer_addr_len = sizeof(struct sockaddr_in6);
377 
378 	} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
379 					peer_data.peer.sa_family == AF_INET) {
380 		*iface = net_if_ipv4_select_src_iface(
381 					&net_sin(&peer_data.peer)->sin_addr);
382 
383 		net_addr_ntop(peer_data.peer.sa_family,
384 			      &net_sin(&peer_data.peer)->sin_addr, addr_str,
385 			      addr_str_len);
386 		peer_data.peer_addr_len = sizeof(struct sockaddr_in);
387 	}
388 
389 	return 0;
390 }
391 
enable_txtime_for_queues(struct net_if * iface)392 static void enable_txtime_for_queues(struct net_if *iface)
393 {
394 	struct ethernet_req_params params = { 0 };
395 	int i;
396 
397 	params.txtime_param.type = ETHERNET_TXTIME_PARAM_TYPE_ENABLE_QUEUES;
398 	params.txtime_param.enable_txtime = true;
399 
400 	for (i = 0; i < NET_TC_TX_COUNT; i++) {
401 		params.txtime_param.queue_id = i;
402 
403 		(void)net_mgmt(NET_REQUEST_ETHERNET_SET_TXTIME_PARAM,
404 			       iface, &params, sizeof(params));
405 	}
406 }
407 
set_qbv_params(struct net_if * iface)408 static void set_qbv_params(struct net_if *iface)
409 {
410 	struct ethernet_req_params params = { 0 };
411 	int i, ret;
412 	int ports_count = 1, row;
413 
414 	/* Assume only one port atm, the amount of ports could be
415 	 * queried from controller by ETHERNET_CONFIG_TYPE_PORTS_NUM
416 	 */
417 
418 	/* Set some defaults */
419 	LOG_DBG("Setting Qbv parameters to %d port%s", ports_count,
420 		ports_count > 1 ? "s" : "");
421 
422 	/* One Qbv setting example:
423 	 *
424 	 *    Start time: after 20s of current configuring base time
425 	 *    Cycle time: 20ms
426 	 *    Number GCL list: 2
427 	 *    GCL list 0 cycle time: 10ms
428 	 *    GCL list 0 'set' gate open: Txq1 (default queue),
429 	 *                                Txq3 (highest priority queue)
430 	 *    GCL list 1 cycle time: 10ms
431 	 *    GCL list 1 'set' gate open: Txq0 (background queue)
432 	 */
433 
434 	for (i = 0; i < ports_count; ++i) {
435 		/* Turn on the gate control to first two gates (just for demo
436 		 * purposes)
437 		 */
438 		for (row = 0; row < 2; row++) {
439 			memset(&params, 0, sizeof(params));
440 
441 			params.qbv_param.port_id = i;
442 			params.qbv_param.type =
443 				ETHERNET_QBV_PARAM_TYPE_GATE_CONTROL_LIST;
444 			params.qbv_param.gate_control.operation = ETHERNET_SET_GATE_STATE;
445 			params.qbv_param.gate_control.time_interval = 10000000UL;
446 			params.qbv_param.gate_control.row = row;
447 
448 			if (row == 0) {
449 				params.qbv_param.gate_control.
450 					gate_status[net_tx_priority2tc(NET_PRIORITY_CA)] = true;
451 				params.qbv_param.gate_control.
452 					gate_status[net_tx_priority2tc(NET_PRIORITY_BE)] = true;
453 			} else if (row == 1) {
454 				params.qbv_param.gate_control.
455 					gate_status[net_tx_priority2tc(NET_PRIORITY_BK)] = true;
456 			}
457 
458 			ret = net_mgmt(NET_REQUEST_ETHERNET_SET_QBV_PARAM,
459 				       iface, &params,
460 				       sizeof(struct ethernet_req_params));
461 			if (ret) {
462 				LOG_ERR("Could not set %s%s (%d) to port %d",
463 					"gate control list", "", ret, i);
464 			}
465 		}
466 
467 		memset(&params, 0, sizeof(params));
468 
469 		params.qbv_param.port_id = i;
470 		params.qbv_param.type =
471 				ETHERNET_QBV_PARAM_TYPE_GATE_CONTROL_LIST_LEN;
472 		params.qbv_param.gate_control_list_len = MIN(NET_TC_TX_COUNT, 2);
473 
474 		ret = net_mgmt(NET_REQUEST_ETHERNET_SET_QBV_PARAM, iface,
475 			       &params, sizeof(struct ethernet_req_params));
476 		if (ret) {
477 			LOG_ERR("Could not set %s%s (%d) to port %d",
478 				"gate control list", " len", ret, i);
479 		}
480 
481 		memset(&params, 0, sizeof(params));
482 
483 		params.qbv_param.port_id = i;
484 		params.qbv_param.type = ETHERNET_QBV_PARAM_TYPE_TIME;
485 		params.qbv_param.base_time.second = 20ULL;
486 		params.qbv_param.base_time.fract_nsecond = 0ULL;
487 		params.qbv_param.cycle_time.second = 0ULL;
488 		params.qbv_param.cycle_time.nanosecond = 20000000UL;
489 		params.qbv_param.extension_time = 0UL;
490 
491 		ret = net_mgmt(NET_REQUEST_ETHERNET_SET_QBV_PARAM, iface,
492 			       &params, sizeof(struct ethernet_req_params));
493 		if (ret) {
494 			LOG_ERR("Could not set %s%s (%d) to port %d",
495 				"base time", "", ret, i);
496 		}
497 	}
498 }
499 
cmd_sample_quit(const struct shell * sh,size_t argc,char * argv[])500 static int cmd_sample_quit(const struct shell *sh,
501 			  size_t argc, char *argv[])
502 {
503 	want_to_quit = true;
504 
505 	quit();
506 
507 	conn_mgr_mon_resend_status();
508 
509 	return 0;
510 }
511 
512 SHELL_STATIC_SUBCMD_SET_CREATE(sample_commands,
513 	SHELL_CMD(quit, NULL,
514 		  "Quit the sample application\n",
515 		  cmd_sample_quit),
516 	SHELL_SUBCMD_SET_END
517 );
518 
519 SHELL_CMD_REGISTER(sample, &sample_commands,
520 		   "Sample application commands", NULL);
521 
main(void)522 int main(void)
523 {
524 	struct net_if *iface = NULL;
525 	char addr_str[INET6_ADDRSTRLEN];
526 	enum ethernet_hw_caps caps;
527 	int ret, if_index;
528 
529 	LOG_INF(APP_BANNER);
530 
531 	k_sem_init(&quit_lock, 0, UINT_MAX);
532 
533 	if (IS_ENABLED(CONFIG_NET_CONNECTION_MANAGER)) {
534 		net_mgmt_init_event_callback(&mgmt_cb,
535 					     event_handler, EVENT_MASK);
536 		net_mgmt_add_event_callback(&mgmt_cb);
537 
538 		if (IS_ENABLED(CONFIG_NET_DHCPV4)) {
539 			net_mgmt_init_event_callback(&dhcpv4_cb,
540 						     event_handler,
541 						     DHCPV4_MASK);
542 			net_mgmt_add_event_callback(&dhcpv4_cb);
543 		}
544 
545 		conn_mgr_mon_resend_status();
546 	}
547 
548 	/* The VLAN in this example is created for demonstration purposes.
549 	 */
550 	if (IS_ENABLED(CONFIG_NET_VLAN)) {
551 		ret = init_vlan();
552 		if (ret < 0) {
553 			LOG_WRN("Cannot setup VLAN (%d)", ret);
554 		}
555 	}
556 
557 	/* Wait for the connection. */
558 	k_sem_take(&run_app, K_FOREVER);
559 
560 	if (IS_ENABLED(CONFIG_NET_SAMPLE_UDP_SOCKET)) {
561 		ret = get_peer_address(&iface, addr_str, sizeof(addr_str));
562 		if (ret < 0) {
563 			return 0;
564 		}
565 	} else {
566 		struct sockaddr_ll *addr = (struct sockaddr_ll *)&peer_data.peer;
567 
568 		addr->sll_ifindex = net_if_get_by_iface(net_if_get_default());
569 		addr->sll_family = AF_PACKET;
570 		peer_data.peer_addr_len = sizeof(struct sockaddr_ll);
571 		iface = net_if_get_by_index(addr->sll_ifindex);
572 	}
573 
574 	if (!iface) {
575 		LOG_ERR("Cannot get local network interface!");
576 		return 0;
577 	}
578 
579 	if_index = net_if_get_by_iface(iface);
580 
581 	caps = net_eth_get_hw_capabilities(iface);
582 	if (!(caps & ETHERNET_PTP)) {
583 		LOG_ERR("Interface %p does not support %s", iface, "PTP");
584 		return 0;
585 	}
586 
587 	if (!(caps & ETHERNET_TXTIME)) {
588 		LOG_ERR("Interface %p does not support %s", iface, "TXTIME");
589 		return 0;
590 	}
591 
592 	peer_data.clk = net_eth_get_ptp_clock_by_index(if_index);
593 	if (!peer_data.clk) {
594 		LOG_ERR("Interface %p does not support %s", iface,
595 			"PTP clock");
596 		return 0;
597 	}
598 
599 	/* Make sure the queues are enabled */
600 	if (IS_ENABLED(CONFIG_NET_L2_ETHERNET_MGMT) && (NET_TC_TX_COUNT > 0)) {
601 		enable_txtime_for_queues(iface);
602 
603 		/* Set Qbv options if they are available */
604 		if (caps & ETHERNET_QBV) {
605 			set_qbv_params(iface);
606 		}
607 	}
608 
609 	if (IS_ENABLED(CONFIG_NET_SAMPLE_UDP_SOCKET)) {
610 		LOG_INF("Socket SO_TXTIME sample to %s port %d using "
611 			"interface %d (%p) and PTP clock %p",
612 			addr_str,
613 			ntohs(net_sin(&peer_data.peer)->sin_port),
614 			if_index, iface, peer_data.clk);
615 	}
616 
617 	if (IS_ENABLED(CONFIG_NET_SAMPLE_PACKET_SOCKET)) {
618 		LOG_INF("Socket SO_TXTIME sample using AF_PACKET and "
619 			"interface %d (%p) and PTP clock %p",
620 			if_index, iface, peer_data.clk);
621 	}
622 
623 	peer_data.sock = create_socket(iface, &peer_data.peer);
624 	if (peer_data.sock < 0) {
625 		LOG_ERR("Cannot create socket (%d)", peer_data.sock);
626 		return 0;
627 	}
628 
629 	tx_tid = k_thread_create(&tx_thread, tx_stack,
630 				 K_THREAD_STACK_SIZEOF(tx_stack),
631 				 tx, &peer_data,
632 				 NULL, NULL, THREAD_PRIORITY, 0,
633 				 K_FOREVER);
634 	if (!tx_tid) {
635 		LOG_ERR("Cannot create TX thread!");
636 		return 0;
637 	}
638 
639 	k_thread_name_set(tx_tid, "TX");
640 
641 	rx_tid = k_thread_create(&rx_thread, rx_stack,
642 				 K_THREAD_STACK_SIZEOF(rx_stack),
643 				 rx, &peer_data,
644 				 NULL, NULL, THREAD_PRIORITY, 0,
645 				 K_FOREVER);
646 	if (!rx_tid) {
647 		LOG_ERR("Cannot create RX thread!");
648 		return 0;
649 	}
650 
651 	k_thread_name_set(rx_tid, "RX");
652 
653 	k_thread_start(rx_tid);
654 	k_thread_start(tx_tid);
655 
656 	k_sem_take(&quit_lock, K_FOREVER);
657 
658 	LOG_INF("Stopping...");
659 
660 	k_thread_abort(tx_tid);
661 	k_thread_abort(rx_tid);
662 
663 	if (peer_data.sock >= 0) {
664 		(void)close(peer_data.sock);
665 	}
666 	return 0;
667 }
668