1 /*
2 * Copyright (C) 2021 BlueKitchen GmbH
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the copyright holders nor the names of
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 * 4. Any redistribution, use, or modification is done solely for
17 * personal benefit and not for any commercial purpose or for
18 * monetary gain.
19 *
20 * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
24 * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 * Please inquire about commercial licensing options at
34 * [email protected]
35 *
36 */
37
38 #define BTSTACK_FILE__ "btstack_sco_transport_posix_i2s_test_bridge.c"
39
40 /*
41 * Implementation of btstack_sco_transport interface for posix systems with an i2s test bridge connected via UART
42 * https://github.com/bluekitchen/i2s-test-bridge
43 *
44 * Assumptions: UART is faster (230400 baud) than I2S Data Rate (left channel only = 128 kbit/s)
45 * This allows to let received block trigger new send. As sending is faster than I2S, there's no need for storing more
46 * than the current block.
47 *
48 * If there is significant delay in higher layers, e.g. opening PortAudio on Mac takes about 50 ms, this would cause
49 * a burst of received packets that get queued in the OS UART buffers. To work around, the first BRIDGE_RX_BLOCKS_DROP
50 * are dropped. Expected period for SCO packets: 7.5 ms
51 */
52
53 #include "btstack_sco_transport_posix_i2s_test_bridge.h"
54
55 #include "btstack_run_loop.h"
56 #include "btstack_debug.h"
57 #include "btstack_util.h"
58 #include <stdint.h>
59 #include <termios.h> /* POSIX terminal control definitions */
60 #include <fcntl.h> /* File control definitions */
61 #include <unistd.h> /* UNIX standard function definitions */
62 #include <string.h>
63 #include <errno.h>
64 #ifdef __APPLE__
65 #include <sys/ioctl.h>
66 #include <IOKit/serial/ioss.h>
67 #endif
68
69 #define BRIDGE_BLOCK_SIZE_BYTES 120
70 #define BRIDGE_RX_BLOCKS_DROP 10
71
72 // data source for integration with BTstack Runloop
73 static btstack_data_source_t sco_data_source;
74 static sco_format_t sco_format;
75 static hci_con_handle_t sco_handle;
76
77 // RX
78 static uint8_t sco_rx_buffer[BRIDGE_BLOCK_SIZE_BYTES];
79 static uint16_t sco_rx_bytes_read;
80 static uint16_t sco_rx_counter;
81
82 // TX
83 static uint8_t sco_tx_packet[BRIDGE_BLOCK_SIZE_BYTES];
84 static uint16_t sco_tx_packet_pos;
85 static uint16_t sco_tx_counter;
86
87 static void (*posix_i2s_test_bridge_packet_handler)(uint8_t packet_type, uint8_t *packet, uint16_t size);
88
posix_i2s_test_bridge_process_write(btstack_data_source_t * ds)89 static void posix_i2s_test_bridge_process_write(btstack_data_source_t *ds) {
90 ssize_t bytes_to_write = BRIDGE_BLOCK_SIZE_BYTES - sco_tx_packet_pos;
91 ssize_t bytes_written = write(ds->source.fd, &sco_tx_packet[sco_tx_packet_pos], bytes_to_write);
92 if (bytes_written < 0) {
93 log_error("write returned error");
94 return;
95 }
96
97 sco_tx_packet_pos += bytes_written;
98 if (sco_tx_packet_pos < BRIDGE_BLOCK_SIZE_BYTES) {
99 return;
100 }
101
102 // block sent
103 btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE);
104 }
105
posix_i2s_test_bride_send_packet(const uint8_t * packet,uint16_t length)106 static void posix_i2s_test_bride_send_packet(const uint8_t *packet, uint16_t length){
107 // ignore con handle
108 btstack_assert( (packet[2]+3) == length);
109 uint16_t i;
110 switch (sco_format){
111 case SCO_FORMAT_8_BIT:
112 btstack_assert(length == 63);
113 for (i=0;i<60;i++){
114 big_endian_store_16(sco_tx_packet, i * 2, packet[ 3 + i ]);
115 }
116 break;
117 case SCO_FORMAT_16_BIT:
118 btstack_assert(length == 123);
119 for (i=0;i<60;i++){
120 sco_tx_packet[ i * 2 ] = packet[4 + i * 2];
121 sco_tx_packet[ i * 2 + 1] = packet[3 + i * 2];
122 }
123 break;
124 default:
125 btstack_assert(false);
126 break;
127 }
128
129 // start sending
130 sco_tx_packet_pos = 0;
131 btstack_run_loop_enable_data_source_callbacks(&sco_data_source, DATA_SOURCE_CALLBACK_WRITE);
132 }
133
posix_i2s_test_bridge_process_read(btstack_data_source_t * ds)134 static void posix_i2s_test_bridge_process_read(btstack_data_source_t *ds) {
135
136 // read up to bytes_to_read data in
137 ssize_t bytes_to_read = BRIDGE_BLOCK_SIZE_BYTES - sco_rx_bytes_read;
138 ssize_t bytes_read = read(ds->source.fd, &sco_rx_buffer[sco_rx_bytes_read], bytes_to_read);
139 if (bytes_read == 0){
140 log_error("read zero bytes of %d bytes", (int) bytes_to_read);
141 return;
142 }
143 if (bytes_read < 0) {
144 log_error("read returned error");
145 return;
146 }
147 sco_rx_bytes_read += bytes_read;
148 if (sco_rx_bytes_read < BRIDGE_BLOCK_SIZE_BYTES) {
149 return;
150 }
151
152 // block received
153 sco_rx_bytes_read = 0;
154
155 // already active
156 if (sco_handle == HCI_CON_HANDLE_INVALID) {
157 log_info("drop SCO packet, no con Handle yet");
158 return;
159 }
160
161 // drop first packets
162 if (sco_rx_counter < BRIDGE_RX_BLOCKS_DROP) {
163 sco_rx_counter++;
164 log_info("drop packet %u/%u\n", sco_rx_counter, BRIDGE_RX_BLOCKS_DROP);
165 return;
166 }
167
168 // setup SCO header
169 uint8_t packet[BRIDGE_BLOCK_SIZE_BYTES + 3];
170 little_endian_store_16(packet,0, sco_handle);
171 uint16_t index;
172 switch (sco_format) {
173 case SCO_FORMAT_8_BIT:
174 // data is received big endian and transparent data is in lower byte
175 packet[2] = BRIDGE_BLOCK_SIZE_BYTES / 2;
176 for (index= 0 ; index < (BRIDGE_BLOCK_SIZE_BYTES / 2) ; index++) {
177 packet[3+index] = sco_rx_buffer[2 * index + 1];
178 }
179 break;
180 case SCO_FORMAT_16_BIT:
181 // data is received big endian but sco packet contains little endian data -> swap bytes
182 packet[2] = BRIDGE_BLOCK_SIZE_BYTES;
183 for (index = 0 ; index < (BRIDGE_BLOCK_SIZE_BYTES / 2) ; index++) {
184 packet[3 + 2 * index] = sco_rx_buffer[2 * index + 1];
185 packet[4 + 2 * index] = sco_rx_buffer[2 * index];
186 }
187 break;
188 default:
189 btstack_assert(false);
190 break;
191 }
192 (*posix_i2s_test_bridge_packet_handler)(HCI_SCO_DATA_PACKET, packet, 3 + packet[2]);
193 }
194
hci_uart_posix_process(btstack_data_source_t * ds,btstack_data_source_callback_type_t callback_type)195 static void hci_uart_posix_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type) {
196 if (ds->source.fd < 0) return;
197 switch (callback_type){
198 case DATA_SOURCE_CALLBACK_READ:
199 posix_i2s_test_bridge_process_read(ds);
200 break;
201 case DATA_SOURCE_CALLBACK_WRITE:
202 posix_i2s_test_bridge_process_write(ds);
203 break;
204 default:
205 break;
206 }
207 }
208
posix_i2s_test_bridge_set_baudrate(int fd,uint32_t baudrate)209 static int posix_i2s_test_bridge_set_baudrate(int fd, uint32_t baudrate){
210
211 #ifdef __APPLE__
212
213 // From https://developer.apple.com/library/content/samplecode/SerialPortSample/Listings/SerialPortSample_SerialPortSample_c.html
214
215 // The IOSSIOSPEED ioctl can be used to set arbitrary baud rates
216 // other than those specified by POSIX. The driver for the underlying serial hardware
217 // ultimately determines which baud rates can be used. This ioctl sets both the input
218 // and output speed.
219
220 speed_t speed = baudrate;
221 if (ioctl(fd, IOSSIOSPEED, &speed) == -1) {
222 log_error("set baud: error calling ioctl(..., IOSSIOSPEED, %u) - %s(%d).\n", baudrate, strerror(errno), errno);
223 return -1;
224 }
225
226 #else
227 struct termios toptions;
228
229 if (tcgetattr(fd, &toptions) < 0) {
230 log_error("set baud: Couldn't get term attributes");
231 return -1;
232 }
233
234 speed_t brate = baudrate; // let you override switch below if needed
235 switch(baudrate) {
236 case 9600: brate=B9600; break;
237 case 19200: brate=B19200; break;
238 case 38400: brate=B38400; break;
239 case 57600: brate=B57600; break;
240 case 115200: brate=B115200; break;
241 #ifdef B230400
242 case 230400: brate=B230400; break;
243 #endif
244 #ifdef B460800
245 case 460800: brate=B460800; break;
246 #endif
247 #ifdef B500000
248 case 500000: brate=B500000; break;
249 #endif
250 #ifdef B576000
251 case 576000: brate=B576000; break;
252 #endif
253 #ifdef B921600
254 case 921600: brate=B921600; break;
255 #endif
256 #ifdef B1000000
257 case 1000000: brate=B1000000; break;
258 #endif
259 #ifdef B1152000
260 case 1152000: brate=B1152000; break;
261 #endif
262 #ifdef B1500000
263 case 1500000: brate=B1500000; break;
264 #endif
265 #ifdef B2000000
266 case 2000000: brate=B2000000; break;
267 #endif
268 #ifdef B2500000
269 case 2500000: brate=B2500000; break;
270 #endif
271 #ifdef B3000000
272 case 3000000: brate=B3000000; break;
273 #endif
274 #ifdef B3500000
275 case 3500000: brate=B3500000; break;
276 #endif
277 #ifdef B400000
278 case 4000000: brate=B4000000; break;
279 #endif
280 default:
281 log_error("can't set baudrate %dn", baudrate );
282 return -1;
283 }
284 cfsetospeed(&toptions, brate);
285 cfsetispeed(&toptions, brate);
286
287 if( tcsetattr(fd, TCSANOW, &toptions) < 0) {
288 log_error("Couldn't set term attributes");
289 return -1;
290 }
291 #endif
292
293 return 0;
294 }
295
posix_i2s_test_bridge_init(const char * device_path)296 static int posix_i2s_test_bridge_init(const char * device_path){
297
298 const uint32_t baudrate = 230400;
299
300 struct termios toptions;
301 int flags = O_RDWR | O_NOCTTY | O_NONBLOCK;
302 int fd = open(device_path, flags);
303 if (fd == -1) {
304 log_error("Unable to open port %s", device_path);
305 return -1;
306 }
307
308 if (tcgetattr(fd, &toptions) < 0) {
309 log_error("Couldn't get term attributes");
310 return -1;
311 }
312
313 cfmakeraw(&toptions); // make raw
314
315 // 8N1
316 toptions.c_cflag &= ~CSTOPB;
317 toptions.c_cflag |= CS8;
318
319 toptions.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
320 toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
321
322 // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
323 toptions.c_cc[VMIN] = 1;
324 toptions.c_cc[VTIME] = 0;
325
326 if(tcsetattr(fd, TCSANOW, &toptions) < 0) {
327 log_error("Couldn't set term attributes");
328 return -1;
329 }
330
331 // also set baudrate
332 if (posix_i2s_test_bridge_set_baudrate(fd, baudrate) < 0){
333 return -1;
334 }
335
336 log_info("I2S Test Bridge initialized for %u baud", baudrate);
337
338 // set up data_source
339 btstack_run_loop_set_data_source_fd(&sco_data_source, fd);
340 btstack_run_loop_set_data_source_handler(&sco_data_source, &hci_uart_posix_process);
341 btstack_run_loop_add_data_source(&sco_data_source);
342
343 // reset and enable rx
344 sco_rx_bytes_read = 0;
345 btstack_run_loop_enable_data_source_callbacks(&sco_data_source, DATA_SOURCE_CALLBACK_READ);
346
347 // wait a bit - at least cheap FTDI232 clones might send the first byte out incorrectly
348 usleep(100000);
349
350 return 0;
351 }
352
posix_i2s_test_bridge_open(hci_con_handle_t con_handle,sco_format_t format)353 static void posix_i2s_test_bridge_open(hci_con_handle_t con_handle, sco_format_t format){
354 log_info("open: handle 0x%04x, format %s", con_handle, (format == SCO_FORMAT_16_BIT) ? "16 bit" : "8 bit");
355 // store config
356 sco_format = format;
357 sco_handle = con_handle;
358 // reset rx
359 sco_rx_counter = 0;
360 // reset tx
361 sco_tx_counter = 0;
362 }
363
posix_i2s_test_bridge_close(hci_con_handle_t con_handle)364 static void posix_i2s_test_bridge_close(hci_con_handle_t con_handle){
365 log_info("close: handle 0x%04x", con_handle);
366 sco_handle = HCI_CON_HANDLE_INVALID;
367 }
368
posix_i2s_test_bridge_register_packet_handler(void (* handler)(uint8_t packet_type,uint8_t * packet,uint16_t size))369 static void posix_i2s_test_bridge_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)){
370 posix_i2s_test_bridge_packet_handler = handler;
371 }
372
btstack_sco_transport_posix_i2s_test_bridge_init_instance(const char * device_path)373 const btstack_sco_transport_t * btstack_sco_transport_posix_i2s_test_bridge_init_instance(const char * device_path){
374 static const btstack_sco_transport_t transport = {
375 .open = posix_i2s_test_bridge_open,
376 .close = posix_i2s_test_bridge_close,
377 .register_packet_handler = posix_i2s_test_bridge_register_packet_handler,
378 .send_packet = posix_i2s_test_bride_send_packet,
379 };
380 int err = posix_i2s_test_bridge_init(device_path);
381 if (err > 0) {
382 return NULL;
383 }
384 sco_format = SCO_FORMAT_8_BIT;
385 sco_handle = HCI_CON_HANDLE_INVALID;
386 return &transport;
387 }
388