-
Notifications
You must be signed in to change notification settings - Fork 2
/
stdio_usb_cdc.c
156 lines (138 loc) · 5.05 KB
/
stdio_usb_cdc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "tusb.h"
#include "stdio_usb_cdc.h"
// these may not be set if the user is providing tud support (i.e. LIB_TINYUSB_DEVICE is 1 because
// the user linked in tinyusb_device) but they haven't selected CDC
#if (CFG_TUD_ENABLED | TUSB_OPT_DEVICE_ENABLED) && CFG_TUD_CDC
#include "pico/binary_info.h"
#include "pico/time.h"
#include "pico/stdio/driver.h"
#include "pico/mutex.h"
#include "hardware/irq.h"
#include "device/usbd_pvt.h" // for usbd_defer_func
static mutex_t stdio_usb_mutex;
#if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
static void (*chars_available_callback)(void*);
static void *chars_available_param;
#endif
bool stdio_usb_cdc_connected(void);
static void stdio_usb_cdc_out_chars(const char *buf, int length) {
static uint64_t last_avail_time;
if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
return;
}
if (stdio_usb_cdc_connected()) {
for (int i = 0; i < length;) {
int n = length - i;
int avail = (int) tud_cdc_write_available();
if (n > avail) n = avail;
if (n) {
int n2 = (int) tud_cdc_write(buf + i, (uint32_t)n);
tud_task();
tud_cdc_write_flush();
i += n2;
last_avail_time = time_us_64();
} else {
tud_task();
tud_cdc_write_flush();
if (!stdio_usb_cdc_connected() ||
(!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) {
break;
}
}
}
} else {
// reset our timeout
last_avail_time = 0;
}
mutex_exit(&stdio_usb_mutex);
}
int stdio_usb_cdc_in_chars(char *buf, int length) {
// note we perform this check outside the lock, to try and prevent possible deadlock conditions
// with printf in IRQs (which we will escape through timeouts elsewhere, but that would be less graceful).
//
// these are just checks of state, so we can call them while not holding the lock.
// they may be wrong, but only if we are in the middle of a tud_task call, in which case at worst
// we will mistakenly think we have data available when we do not (we will check again), or
// tud_task will complete running and we will check the right values the next time.
//
int rc = PICO_ERROR_NO_DATA;
if (stdio_usb_cdc_connected() && tud_cdc_available()) {
if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
return PICO_ERROR_NO_DATA; // would deadlock otherwise
}
if (stdio_usb_cdc_connected() && tud_cdc_available()) {
int count = (int) tud_cdc_read(buf, (uint32_t) length);
rc = count ? count : PICO_ERROR_NO_DATA;
} else {
// because our mutex use may starve out the background task, run tud_task here (we own the mutex)
tud_task();
}
mutex_exit(&stdio_usb_mutex);
}
return rc;
}
#if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
void tud_cdc_rx_cb(__unused uint8_t itf) {
if (chars_available_callback) {
usbd_defer_func(chars_available_callback, chars_available_param, false);
}
}
void stdio_usb_cdc_set_chars_available_callback(void (*fn)(void*), void *param) {
chars_available_callback = fn;
chars_available_param = param;
}
#endif
stdio_driver_t stdio_usb_cdc = {
.out_chars = stdio_usb_cdc_out_chars,
.in_chars = stdio_usb_cdc_in_chars,
#if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
.set_chars_available_callback = stdio_usb_cdc_set_chars_available_callback,
#endif
#if PICO_STDIO_ENABLE_CRLF_SUPPORT
.crlf_enabled = PICO_STDIO_USB_DEFAULT_CRLF
#endif
};
bool stdio_usb_cdc_init(void) {
assert(tud_inited()); // we expect the caller to have initialized if they are using TinyUSB
mutex_init(&stdio_usb_mutex);
bool rc = true;
if (rc) {
stdio_set_driver_enabled(&stdio_usb_cdc, true);
#if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS
#if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS > 0
absolute_time_t until = make_timeout_time_ms(PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS);
#else
absolute_time_t until = at_the_end_of_time;
#endif
do {
if (stdio_usb_cdc_connected()) {
#if PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS != 0
sleep_ms(PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS);
#endif
break;
}
sleep_ms(10);
} while (!time_reached(until));
#endif
}
return rc;
}
bool stdio_usb_cdc_connected(void) {
#if PICO_STDIO_USB_CONNECTION_WITHOUT_DTR
return tud_ready();
#else
// this actually checks DTR
return tud_cdc_connected();
#endif
}
#else
#warning stdio USB was configured along with user use of TinyUSB device mode, but CDC is not enabled
bool stdio_usb_init(void) {
return false;
}
#endif // CFG_TUD_ENABLED && CFG_TUD_CDC