1 /*
2  * Copyright (c) 2024-2025 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <autoconf.h>
8 #include <errno.h>
9 #include <stdbool.h>
10 #include <stddef.h>
11 #include <stdint.h>
12 
13 #include <zephyr/bluetooth/audio/cap.h>
14 #include <zephyr/bluetooth/audio/bap.h>
15 #include <zephyr/bluetooth/bluetooth.h>
16 #include <zephyr/bluetooth/hci_types.h>
17 #include <zephyr/bluetooth/iso.h>
18 #include <zephyr/kernel.h>
19 #include <zephyr/kernel/thread_stack.h>
20 #include <zephyr/logging/log.h>
21 #include <zephyr/logging/log_core.h>
22 #include <zephyr/net_buf.h>
23 #include <zephyr/sys/__assert.h>
24 #include <zephyr/sys/byteorder.h>
25 #include <zephyr/sys/util.h>
26 #include <zephyr/sys/util_macro.h>
27 #include <zephyr/types.h>
28 
29 #include "stream_lc3.h"
30 #include "stream_tx.h"
31 
32 LOG_MODULE_REGISTER(stream_tx, LOG_LEVEL_INF);
33 
34 static struct tx_stream tx_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT];
35 
stream_is_streaming(const struct bt_bap_stream * bap_stream)36 static bool stream_is_streaming(const struct bt_bap_stream *bap_stream)
37 {
38 	struct bt_bap_ep_info ep_info;
39 	int err;
40 
41 	if (bap_stream == NULL) {
42 		return false;
43 	}
44 
45 	/* No-op if stream is not configured */
46 	if (bap_stream->ep == NULL) {
47 		return false;
48 	}
49 
50 	err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
51 	if (err != 0) {
52 		return false;
53 	}
54 
55 	return ep_info.state == BT_BAP_EP_STATE_STREAMING;
56 }
57 
tx_thread_func(void * arg1,void * arg2,void * arg3)58 static void tx_thread_func(void *arg1, void *arg2, void *arg3)
59 {
60 	NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT,
61 				  BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
62 				  CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
63 	static uint8_t mock_data[CONFIG_BT_ISO_TX_MTU];
64 
65 	for (size_t i = 0U; i < ARRAY_SIZE(mock_data); i++) {
66 		mock_data[i] = (uint8_t)i;
67 	}
68 
69 	/* This loop will attempt to send on all streams in the streaming state in a round robin
70 	 * fashion.
71 	 * The TX is controlled by the number of buffers configured, and increasing
72 	 * CONFIG_BT_ISO_TX_BUF_COUNT will allow for more streams in parallel, or to submit more
73 	 * buffers per stream.
74 	 * Once a buffer has been freed by the stack, it triggers the next TX.
75 	 */
76 	while (true) {
77 		int err = -ENOEXEC;
78 
79 		for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) {
80 			struct bt_bap_stream *bap_stream = tx_streams[i].bap_stream;
81 
82 			if (stream_is_streaming(bap_stream)) {
83 				uint16_t sdu_len;
84 				struct net_buf *buf;
85 
86 				buf = net_buf_alloc(&tx_pool, K_FOREVER);
87 				net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
88 
89 				if (IS_ENABLED(CONFIG_LIBLC3) &&
90 				    bap_stream->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
91 					stream_lc3_add_data(&tx_streams[i], buf);
92 				} else {
93 					__ASSERT(bap_stream->qos->sdu <= ARRAY_SIZE(mock_data),
94 						 "Configured codec SDU len %u does not match mock "
95 						 "data size %zu",
96 						 bap_stream->qos->sdu, ARRAY_SIZE(mock_data));
97 					net_buf_add_mem(buf, mock_data, bap_stream->qos->sdu);
98 				}
99 
100 				sdu_len = buf->len;
101 
102 				err = bt_bap_stream_send(bap_stream, buf, tx_streams[i].seq_num);
103 				if (err == 0) {
104 					tx_streams[i].seq_num++;
105 
106 					if (CONFIG_INFO_REPORTING_INTERVAL > 0 &&
107 					    (tx_streams[i].seq_num %
108 					     CONFIG_INFO_REPORTING_INTERVAL) == 0U) {
109 						LOG_INF("Stream %p: Sent %u total SDUs of size %u",
110 							bap_stream, tx_streams[i].seq_num, sdu_len);
111 					}
112 				} else {
113 					LOG_ERR("Unable to send: %d", err);
114 					net_buf_unref(buf);
115 				}
116 			} /* No-op if stream is not streaming */
117 		}
118 
119 		if (err != 0) {
120 			/* In case of any errors, retry with a delay */
121 			k_sleep(K_MSEC(10));
122 		}
123 	}
124 }
125 
stream_tx_register(struct bt_bap_stream * bap_stream)126 int stream_tx_register(struct bt_bap_stream *bap_stream)
127 {
128 	if (bap_stream == NULL) {
129 		return -EINVAL;
130 	}
131 
132 	for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) {
133 		if (tx_streams[i].bap_stream == NULL) {
134 			tx_streams[i].bap_stream = bap_stream;
135 			tx_streams[i].seq_num = 0U;
136 
137 			if (IS_ENABLED(CONFIG_LIBLC3) &&
138 			    bap_stream->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
139 				const int err = stream_lc3_init(&tx_streams[i]);
140 
141 				if (err != 0) {
142 					tx_streams[i].bap_stream = NULL;
143 
144 					return err;
145 				}
146 			}
147 
148 			LOG_INF("Registered %p for TX", bap_stream);
149 			if (bap_stream->qos->sdu > CONFIG_BT_ISO_TX_MTU) {
150 				LOG_WRN("Stream configured for SDUs larger (%u) than "
151 					"CONFIG_BT_ISO_TX_MTU (%d)",
152 					bap_stream->qos->sdu, CONFIG_BT_ISO_TX_MTU);
153 			}
154 
155 			return 0;
156 		}
157 	}
158 
159 	return -ENOMEM;
160 }
161 
stream_tx_unregister(struct bt_bap_stream * bap_stream)162 int stream_tx_unregister(struct bt_bap_stream *bap_stream)
163 {
164 	if (bap_stream == NULL) {
165 		return -EINVAL;
166 	}
167 
168 	for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) {
169 		if (tx_streams[i].bap_stream == bap_stream) {
170 			tx_streams[i].bap_stream = NULL;
171 
172 			LOG_INF("Unregistered %p for TX", bap_stream);
173 
174 			return 0;
175 		}
176 	}
177 
178 	return -ENODATA;
179 }
180 
stream_tx_init(void)181 void stream_tx_init(void)
182 {
183 	static bool thread_started;
184 
185 	if (!thread_started) {
186 		static K_KERNEL_STACK_DEFINE(tx_thread_stack,
187 					     IS_ENABLED(CONFIG_LIBLC3) ? 4096U : 1024U);
188 		const int tx_thread_prio = K_PRIO_PREEMPT(5);
189 		static struct k_thread tx_thread;
190 
191 		k_thread_create(&tx_thread, tx_thread_stack, K_KERNEL_STACK_SIZEOF(tx_thread_stack),
192 				tx_thread_func, NULL, NULL, NULL, tx_thread_prio, 0, K_NO_WAIT);
193 		k_thread_name_set(&tx_thread, "TX thread");
194 		thread_started = true;
195 	}
196 }
197