1/*
2 * This file is part of the MicroPython project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2020 Jim Mussared
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
27#include "py/runtime.h"
28#include "py/mperrno.h"
29#include "py/mphal.h"
30
31#if MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4))
32
33#if !MICROPY_PY_THREAD
34#error Unix HCI UART requires MICROPY_PY_THREAD
35#endif
36
37#include "extmod/modbluetooth.h"
38#include "extmod/mpbthci.h"
39
40#include <pthread.h>
41#include <unistd.h>
42
43#include <termios.h>
44#include <fcntl.h>
45#include <stdlib.h>
46#include <string.h>
47
48#define DEBUG_printf(...) // printf(__VA_ARGS__)
49#define DEBUG_HCI_DUMP (0)
50
51uint8_t mp_bluetooth_hci_cmd_buf[4 + 256];
52
53STATIC int uart_fd = -1;
54
55// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c).
56extern bool mp_bluetooth_hci_poll(void);
57
58#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS
59
60// For synchronous mode, we run all BLE stack code inside a scheduled task.
61// This task is scheduled periodically (every 1ms) by a background thread.
62
63// Allows the stack to tell us that we should stop trying to schedule.
64extern bool mp_bluetooth_hci_active(void);
65
66// Prevent double-enqueuing of the scheduled task.
67STATIC volatile bool events_task_is_scheduled = false;
68
69STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) {
70 (void)none_in;
71 MICROPY_PY_BLUETOOTH_ENTER
72 events_task_is_scheduled = false;
73 MICROPY_PY_BLUETOOTH_EXIT
74 mp_bluetooth_hci_poll();
75 return mp_const_none;
76}
77STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_events_scheduled_task_obj, run_events_scheduled_task);
78
79#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS
80
81STATIC const useconds_t UART_POLL_INTERVAL_US = 1000;
82STATIC pthread_t hci_poll_thread_id;
83
84STATIC void *hci_poll_thread(void *arg) {
85 (void)arg;
86
87 DEBUG_printf("hci_poll_thread: starting\n");
88
89 #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS
90
91 events_task_is_scheduled = false;
92
93 while (mp_bluetooth_hci_active()) {
94 MICROPY_PY_BLUETOOTH_ENTER
95 if (!events_task_is_scheduled) {
96 events_task_is_scheduled = mp_sched_schedule(MP_OBJ_FROM_PTR(&run_events_scheduled_task_obj), mp_const_none);
97 }
98 MICROPY_PY_BLUETOOTH_EXIT
99 usleep(UART_POLL_INTERVAL_US);
100 }
101
102 #else
103
104 // In asynchronous (i.e. ringbuffer) mode, we run the BLE stack directly from the thread.
105 // This will return false when the stack is shutdown.
106 while (mp_bluetooth_hci_poll()) {
107 usleep(UART_POLL_INTERVAL_US);
108 }
109
110 #endif
111
112 DEBUG_printf("hci_poll_thread: stopped\n");
113
114 return NULL;
115}
116
117STATIC int configure_uart(void) {
118 struct termios toptions;
119
120 // Get existing config.
121 if (tcgetattr(uart_fd, &toptions) < 0) {
122 DEBUG_printf("Couldn't get term attributes");
123 return -1;
124 }
125
126 // Raw mode (disable all processing).
127 cfmakeraw(&toptions);
128
129 // 8N1, no parity.
130 toptions.c_cflag &= ~CSTOPB;
131 toptions.c_cflag |= CS8;
132 toptions.c_cflag &= ~PARENB;
133
134 // Enable receiver, ignore modem control lines
135 toptions.c_cflag |= CREAD | CLOCAL;
136
137 // Blocking, single-byte reads.
138 toptions.c_cc[VMIN] = 1;
139 toptions.c_cc[VTIME] = 0;
140
141 // Enable HW RTS/CTS flow control.
142 toptions.c_iflag &= ~(IXON | IXOFF | IXANY);
143 toptions.c_cflag |= CRTSCTS;
144
145 // 1Mbit (TODO: make this configurable).
146 speed_t brate = B1000000;
147 cfsetospeed(&toptions, brate);
148 cfsetispeed(&toptions, brate);
149
150 // Apply immediately.
151 if (tcsetattr(uart_fd, TCSANOW, &toptions) < 0) {
152 DEBUG_printf("Couldn't set term attributes");
153
154 close(uart_fd);
155 uart_fd = -1;
156
157 return -1;
158 }
159
160 return 0;
161}
162
163// HCI UART bindings.
164int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) {
165 (void)port;
166 (void)baudrate;
167
168 DEBUG_printf("mp_bluetooth_hci_uart_init (unix)\n");
169
170 if (uart_fd != -1) {
171 DEBUG_printf("mp_bluetooth_hci_uart_init: already active\n");
172 return 0;
173 }
174
175 char uart_device_name[256] = "/dev/ttyUSB0";
176
177 char *path = getenv("MICROPYBTUART");
178 if (path != NULL) {
179 strcpy(uart_device_name, path);
180 }
181 DEBUG_printf("mp_bluetooth_hci_uart_init: Using HCI UART: %s\n", uart_device_name);
182
183 int flags = O_RDWR | O_NOCTTY | O_NONBLOCK;
184 uart_fd = open(uart_device_name, flags);
185 if (uart_fd == -1) {
186 printf("mp_bluetooth_hci_uart_init: Unable to open port %s\n", uart_device_name);
187 return -1;
188 }
189
190 if (configure_uart()) {
191 return -1;
192 }
193
194 // Create a thread to run the polling loop.
195 pthread_attr_t attr;
196 pthread_attr_init(&attr);
197 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
198 pthread_create(&hci_poll_thread_id, &attr, &hci_poll_thread, NULL);
199
200 return 0;
201}
202
203int mp_bluetooth_hci_uart_deinit(void) {
204 DEBUG_printf("mp_bluetooth_hci_uart_deinit\n");
205
206 if (uart_fd == -1) {
207 return 0;
208 }
209
210 // Wait for the poll loop to terminate when the state is set to OFF.
211 pthread_join(hci_poll_thread_id, NULL);
212
213 // Close the UART.
214 close(uart_fd);
215 uart_fd = -1;
216
217 return 0;
218}
219
220int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) {
221 (void)baudrate;
222 DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate\n");
223 return 0;
224}
225
226int mp_bluetooth_hci_uart_readchar(void) {
227 // DEBUG_printf("mp_bluetooth_hci_uart_readchar\n");
228
229 if (uart_fd == -1) {
230 return -1;
231 }
232
233 uint8_t c;
234 ssize_t bytes_read = read(uart_fd, &c, 1);
235
236 if (bytes_read == 1) {
237 #if DEBUG_HCI_DUMP
238 printf("[% 8ld] RX: %02x\n", mp_hal_ticks_ms(), c);
239 #endif
240 return c;
241 } else {
242 return -1;
243 }
244}
245
246int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) {
247 // DEBUG_printf("mp_bluetooth_hci_uart_write\n");
248
249 if (uart_fd == -1) {
250 return 0;
251 }
252
253 #if DEBUG_HCI_DUMP
254 printf("[% 8ld] TX: %02x", mp_hal_ticks_ms(), buf[0]);
255 for (size_t i = 1; i < len; ++i) {
256 printf(":%02x", buf[i]);
257 }
258 printf("\n");
259 #endif
260
261 return write(uart_fd, buf, len);
262}
263
264// No-op implementations of HCI controller interface.
265int mp_bluetooth_hci_controller_init(void) {
266 return 0;
267}
268
269int mp_bluetooth_hci_controller_deinit(void) {
270 return 0;
271}
272
273int mp_bluetooth_hci_controller_sleep_maybe(void) {
274 return 0;
275}
276
277bool mp_bluetooth_hci_controller_woken(void) {
278 return true;
279}
280
281int mp_bluetooth_hci_controller_wakeup(void) {
282 return 0;
283}
284
285#endif // MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4))
286