xref: /btstack/platform/posix/btstack_sco_transport_posix_i2s_test_bridge.c (revision 2fca4dad957cd7b88f4657ed51e89c12615dda72)
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