1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #ifndef LIB_TINYUSB_HOST
8 #include "tusb.h"
9 #include "pico/stdio_usb.h"
10
11 // these may not be set if the user is providing tud support (i.e. LIB_TINYUSB_DEVICE is 1 because
12 // the user linked in tinyusb_device) but they haven't selected CDC
13 #if (CFG_TUD_ENABLED | TUSB_OPT_DEVICE_ENABLED) && CFG_TUD_CDC
14
15 #include "pico/binary_info.h"
16 #include "pico/time.h"
17 #include "pico/stdio/driver.h"
18 #include "pico/mutex.h"
19 #include "hardware/irq.h"
20 #include "device/usbd_pvt.h" // for usbd_defer_func
21
22 static mutex_t stdio_usb_mutex;
23
24 #if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
25 static void (*chars_available_callback)(void*);
26 static void *chars_available_param;
27 #endif
28
29 // when tinyusb_device is explicitly linked we do no background tud processing
30 #if !LIB_TINYUSB_DEVICE
31 // if this crit_sec is initialized, we are not in periodic timer mode, and must make sure
32 // we don't either create multiple one shot timers, or miss creating one. this crit_sec
33 // is used to protect the one_shot_timer_pending flag
34 static critical_section_t one_shot_timer_crit_sec;
35 static volatile bool one_shot_timer_pending;
36 #ifdef PICO_STDIO_USB_LOW_PRIORITY_IRQ
37 static_assert(PICO_STDIO_USB_LOW_PRIORITY_IRQ >= NUM_IRQS - NUM_USER_IRQS, "");
38 #define low_priority_irq_num PICO_STDIO_USB_LOW_PRIORITY_IRQ
39 #else
40 static uint8_t low_priority_irq_num;
41 #endif
42
timer_task(__unused alarm_id_t id,__unused void * user_data)43 static int64_t timer_task(__unused alarm_id_t id, __unused void *user_data) {
44 int64_t repeat_time;
45 if (critical_section_is_initialized(&one_shot_timer_crit_sec)) {
46 critical_section_enter_blocking(&one_shot_timer_crit_sec);
47 one_shot_timer_pending = false;
48 critical_section_exit(&one_shot_timer_crit_sec);
49 repeat_time = 0; // don't repeat
50 } else {
51 repeat_time = PICO_STDIO_USB_TASK_INTERVAL_US;
52 }
53 irq_set_pending(low_priority_irq_num);
54 return repeat_time;
55 }
56
low_priority_worker_irq(void)57 static void low_priority_worker_irq(void) {
58 if (mutex_try_enter(&stdio_usb_mutex, NULL)) {
59 tud_task();
60 mutex_exit(&stdio_usb_mutex);
61 } else {
62 // if the mutex is already owned, then we are in non IRQ code in this file.
63 //
64 // it would seem simplest to just let that code call tud_task() at the end, however this
65 // code might run during the call to tud_task() and we might miss a necessary tud_task() call
66 //
67 // if we are using a periodic timer (crit_sec is not initialized in this case),
68 // then we are happy just to wait until the next tick, however when we are not using a periodic timer,
69 // we must kick off a one-shot timer to make sure the tud_task() DOES run (this method
70 // will be called again as a result, and will try the mutex_try_enter again, and if that fails
71 // create another one shot timer again, and so on).
72 if (critical_section_is_initialized(&one_shot_timer_crit_sec)) {
73 bool need_timer;
74 critical_section_enter_blocking(&one_shot_timer_crit_sec);
75 need_timer = !one_shot_timer_pending;
76 one_shot_timer_pending = true;
77 critical_section_exit(&one_shot_timer_crit_sec);
78 if (need_timer) {
79 add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true);
80 }
81 }
82 }
83 }
84
usb_irq(void)85 static void usb_irq(void) {
86 irq_set_pending(low_priority_irq_num);
87 }
88
89 #endif
90
stdio_usb_out_chars(const char * buf,int length)91 static void stdio_usb_out_chars(const char *buf, int length) {
92 static uint64_t last_avail_time;
93 if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
94 return;
95 }
96 if (stdio_usb_connected()) {
97 for (int i = 0; i < length;) {
98 int n = length - i;
99 int avail = (int) tud_cdc_write_available();
100 if (n > avail) n = avail;
101 if (n) {
102 int n2 = (int) tud_cdc_write(buf + i, (uint32_t)n);
103 tud_task();
104 tud_cdc_write_flush();
105 i += n2;
106 last_avail_time = time_us_64();
107 } else {
108 tud_task();
109 tud_cdc_write_flush();
110 if (!stdio_usb_connected() ||
111 (!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) {
112 break;
113 }
114 }
115 }
116 } else {
117 // reset our timeout
118 last_avail_time = 0;
119 }
120 mutex_exit(&stdio_usb_mutex);
121 }
122
stdio_usb_in_chars(char * buf,int length)123 int stdio_usb_in_chars(char *buf, int length) {
124 // note we perform this check outside the lock, to try and prevent possible deadlock conditions
125 // with printf in IRQs (which we will escape through timeouts elsewhere, but that would be less graceful).
126 //
127 // these are just checks of state, so we can call them while not holding the lock.
128 // they may be wrong, but only if we are in the middle of a tud_task call, in which case at worst
129 // we will mistakenly think we have data available when we do not (we will check again), or
130 // tud_task will complete running and we will check the right values the next time.
131 //
132 int rc = PICO_ERROR_NO_DATA;
133 if (stdio_usb_connected() && tud_cdc_available()) {
134 if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
135 return PICO_ERROR_NO_DATA; // would deadlock otherwise
136 }
137 if (stdio_usb_connected() && tud_cdc_available()) {
138 int count = (int) tud_cdc_read(buf, (uint32_t) length);
139 rc = count ? count : PICO_ERROR_NO_DATA;
140 } else {
141 // because our mutex use may starve out the background task, run tud_task here (we own the mutex)
142 tud_task();
143 }
144 mutex_exit(&stdio_usb_mutex);
145 }
146 return rc;
147 }
148
149 #if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
tud_cdc_rx_cb(__unused uint8_t itf)150 void tud_cdc_rx_cb(__unused uint8_t itf) {
151 if (chars_available_callback) {
152 usbd_defer_func(chars_available_callback, chars_available_param, false);
153 }
154 }
155
stdio_usb_set_chars_available_callback(void (* fn)(void *),void * param)156 void stdio_usb_set_chars_available_callback(void (*fn)(void*), void *param) {
157 chars_available_callback = fn;
158 chars_available_param = param;
159 }
160 #endif
161
162 stdio_driver_t stdio_usb = {
163 .out_chars = stdio_usb_out_chars,
164 .in_chars = stdio_usb_in_chars,
165 #if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
166 .set_chars_available_callback = stdio_usb_set_chars_available_callback,
167 #endif
168 #if PICO_STDIO_ENABLE_CRLF_SUPPORT
169 .crlf_enabled = PICO_STDIO_USB_DEFAULT_CRLF
170 #endif
171
172 };
173
stdio_usb_init(void)174 bool stdio_usb_init(void) {
175 if (get_core_num() != alarm_pool_core_num(alarm_pool_get_default())) {
176 // included an assertion here rather than just returning false, as this is likely
177 // a coding bug, rather than anything else.
178 assert(false);
179 return false;
180 }
181 #if !PICO_NO_BI_STDIO_USB
182 bi_decl_if_func_used(bi_program_feature("USB stdin / stdout"));
183 #endif
184
185 #if !defined(LIB_TINYUSB_DEVICE)
186 // initialize TinyUSB, as user hasn't explicitly linked it
187 tusb_init();
188 #else
189 assert(tud_inited()); // we expect the caller to have initialized if they are using TinyUSB
190 #endif
191
192 mutex_init(&stdio_usb_mutex);
193 bool rc = true;
194 #if !LIB_TINYUSB_DEVICE
195 #ifdef PICO_STDIO_USB_LOW_PRIORITY_IRQ
196 user_irq_claim(PICO_STDIO_USB_LOW_PRIORITY_IRQ);
197 #else
198 low_priority_irq_num = (uint8_t) user_irq_claim_unused(true);
199 #endif
200 irq_set_exclusive_handler(low_priority_irq_num, low_priority_worker_irq);
201 irq_set_enabled(low_priority_irq_num, true);
202
203 if (irq_has_shared_handler(USBCTRL_IRQ)) {
204 // we can use a shared handler to notice when there may be work to do
205 irq_add_shared_handler(USBCTRL_IRQ, usb_irq, PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY);
206 critical_section_init_with_lock_num(&one_shot_timer_crit_sec, next_striped_spin_lock_num());
207 } else {
208 rc = add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true) >= 0;
209 // we use initialization state of the one_shot_timer_critsec as a flag
210 memset(&one_shot_timer_crit_sec, 0, sizeof(one_shot_timer_crit_sec));
211 }
212 #endif
213 if (rc) {
214 stdio_set_driver_enabled(&stdio_usb, true);
215 #if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS
216 #if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS > 0
217 absolute_time_t until = make_timeout_time_ms(PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS);
218 #else
219 absolute_time_t until = at_the_end_of_time;
220 #endif
221 do {
222 if (stdio_usb_connected()) {
223 #if PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS != 0
224 sleep_ms(PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS);
225 #endif
226 break;
227 }
228 sleep_ms(10);
229 } while (!time_reached(until));
230 #endif
231 }
232 return rc;
233 }
234
stdio_usb_connected(void)235 bool stdio_usb_connected(void) {
236 #if PICO_STDIO_USB_CONNECTION_WITHOUT_DTR
237 return tud_ready();
238 #else
239 // this actually checks DTR
240 return tud_cdc_connected();
241 #endif
242 }
243
244 #else
245 #warning stdio USB was configured along with user use of TinyUSB device mode, but CDC is not enabled
stdio_usb_init(void)246 bool stdio_usb_init(void) {
247 return false;
248 }
249 #endif // CFG_TUD_ENABLED && CFG_TUD_CDC
250 #else
251 #warning stdio USB was configured, but is being disabled as TinyUSB host is explicitly linked
stdio_usb_init(void)252 bool stdio_usb_init(void) {
253 return false;
254 }
255 #endif // !LIB_TINYUSB_HOST
256
257