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     if (irq_is_enabled(low_priority_irq_num)) {
54         irq_set_pending(low_priority_irq_num);
55         return repeat_time;
56     } else {
57         return 0; // don't repeat
58     }
59 }
60 
low_priority_worker_irq(void)61 static void low_priority_worker_irq(void) {
62     if (mutex_try_enter(&stdio_usb_mutex, NULL)) {
63         tud_task();
64 #if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
65         uint32_t chars_avail = tud_cdc_available();
66 #endif
67         mutex_exit(&stdio_usb_mutex);
68 #if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
69         if (chars_avail && chars_available_callback) chars_available_callback(chars_available_param);
70 #endif
71     } else {
72         // if the mutex is already owned, then we are in non IRQ code in this file.
73         //
74         // it would seem simplest to just let that code call tud_task() at the end, however this
75         // code might run during the call to tud_task() and we might miss a necessary tud_task() call
76         //
77         // if we are using a periodic timer (crit_sec is not initialized in this case),
78         // then we are happy just to wait until the next tick, however when we are not using a periodic timer,
79         // we must kick off a one-shot timer to make sure the tud_task() DOES run (this method
80         // will be called again as a result, and will try the mutex_try_enter again, and if that fails
81         // create another one shot timer again, and so on).
82         if (critical_section_is_initialized(&one_shot_timer_crit_sec)) {
83             bool need_timer;
84             critical_section_enter_blocking(&one_shot_timer_crit_sec);
85             need_timer = !one_shot_timer_pending;
86             one_shot_timer_pending = true;
87             critical_section_exit(&one_shot_timer_crit_sec);
88             if (need_timer) {
89                 add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true);
90             }
91         }
92     }
93 }
94 
usb_irq(void)95 static void usb_irq(void) {
96     irq_set_pending(low_priority_irq_num);
97 }
98 
99 #endif
100 
stdio_usb_out_chars(const char * buf,int length)101 static void stdio_usb_out_chars(const char *buf, int length) {
102     static uint64_t last_avail_time;
103     if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
104         return;
105     }
106     if (stdio_usb_connected()) {
107         for (int i = 0; i < length;) {
108             int n = length - i;
109             int avail = (int) tud_cdc_write_available();
110             if (n > avail) n = avail;
111             if (n) {
112                 int n2 = (int) tud_cdc_write(buf + i, (uint32_t)n);
113                 tud_task();
114                 tud_cdc_write_flush();
115                 i += n2;
116                 last_avail_time = time_us_64();
117             } else {
118                 tud_task();
119                 tud_cdc_write_flush();
120                 if (!stdio_usb_connected() ||
121                     (!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) {
122                     break;
123                 }
124             }
125         }
126     } else {
127         // reset our timeout
128         last_avail_time = 0;
129     }
130     mutex_exit(&stdio_usb_mutex);
131 }
132 
stdio_usb_out_flush(void)133 static void stdio_usb_out_flush(void) {
134     if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
135         return;
136     }
137     do {
138         tud_task();
139     } while (tud_cdc_write_flush());
140     mutex_exit(&stdio_usb_mutex);
141 }
142 
stdio_usb_in_chars(char * buf,int length)143 int stdio_usb_in_chars(char *buf, int length) {
144     // note we perform this check outside the lock, to try and prevent possible deadlock conditions
145     // with printf in IRQs (which we will escape through timeouts elsewhere, but that would be less graceful).
146     //
147     // these are just checks of state, so we can call them while not holding the lock.
148     // they may be wrong, but only if we are in the middle of a tud_task call, in which case at worst
149     // we will mistakenly think we have data available when we do not (we will check again), or
150     // tud_task will complete running and we will check the right values the next time.
151     //
152     int rc = PICO_ERROR_NO_DATA;
153     if (stdio_usb_connected() && tud_cdc_available()) {
154         if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
155             return PICO_ERROR_NO_DATA; // would deadlock otherwise
156         }
157         if (stdio_usb_connected() && tud_cdc_available()) {
158             int count = (int) tud_cdc_read(buf, (uint32_t) length);
159             rc = count ? count : PICO_ERROR_NO_DATA;
160         } else {
161             // because our mutex use may starve out the background task, run tud_task here (we own the mutex)
162             tud_task();
163         }
164         mutex_exit(&stdio_usb_mutex);
165     }
166     return rc;
167 }
168 
169 #if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
stdio_usb_set_chars_available_callback(void (* fn)(void *),void * param)170 void stdio_usb_set_chars_available_callback(void (*fn)(void*), void *param) {
171     chars_available_callback = fn;
172     chars_available_param = param;
173 }
174 #endif
175 
176 stdio_driver_t stdio_usb = {
177     .out_chars = stdio_usb_out_chars,
178     .out_flush = stdio_usb_out_flush,
179     .in_chars = stdio_usb_in_chars,
180 #if PICO_STDIO_USB_SUPPORT_CHARS_AVAILABLE_CALLBACK
181     .set_chars_available_callback = stdio_usb_set_chars_available_callback,
182 #endif
183 #if PICO_STDIO_ENABLE_CRLF_SUPPORT
184     .crlf_enabled = PICO_STDIO_USB_DEFAULT_CRLF
185 #endif
186 
187 };
188 
stdio_usb_init(void)189 bool stdio_usb_init(void) {
190     if (get_core_num() != alarm_pool_core_num(alarm_pool_get_default())) {
191         // included an assertion here rather than just returning false, as this is likely
192         // a coding bug, rather than anything else.
193         assert(false);
194         return false;
195     }
196 #if !PICO_NO_BI_STDIO_USB
197     bi_decl_if_func_used(bi_program_feature("USB stdin / stdout"));
198 #endif
199 
200 #if !defined(LIB_TINYUSB_DEVICE)
201     // initialize TinyUSB, as user hasn't explicitly linked it
202     tusb_init();
203 #else
204     assert(tud_inited()); // we expect the caller to have initialized if they are using TinyUSB
205 #endif
206 
207     if (!mutex_is_initialized(&stdio_usb_mutex)) mutex_init(&stdio_usb_mutex);
208     bool rc = true;
209 #if !LIB_TINYUSB_DEVICE
210 #ifdef PICO_STDIO_USB_LOW_PRIORITY_IRQ
211     user_irq_claim(PICO_STDIO_USB_LOW_PRIORITY_IRQ);
212 #else
213     low_priority_irq_num = (uint8_t) user_irq_claim_unused(true);
214 #endif
215     irq_set_exclusive_handler(low_priority_irq_num, low_priority_worker_irq);
216     irq_set_enabled(low_priority_irq_num, true);
217 
218     if (irq_has_shared_handler(USBCTRL_IRQ)) {
219         critical_section_init_with_lock_num(&one_shot_timer_crit_sec, spin_lock_claim_unused(true));
220         // we can use a shared handler to notice when there may be work to do
221         irq_add_shared_handler(USBCTRL_IRQ, usb_irq, PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY);
222     } else {
223         // we use initialization state of the one_shot_timer_critsec as a flag
224         memset(&one_shot_timer_crit_sec, 0, sizeof(one_shot_timer_crit_sec));
225         rc = add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true) >= 0;
226     }
227 #endif
228     if (rc) {
229         stdio_set_driver_enabled(&stdio_usb, true);
230 #if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS
231 #if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS > 0
232         absolute_time_t until = make_timeout_time_ms(PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS);
233 #else
234         absolute_time_t until = at_the_end_of_time;
235 #endif
236         do {
237             if (stdio_usb_connected()) {
238 #if PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS != 0
239                 sleep_ms(PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS);
240 #endif
241                 break;
242             }
243             sleep_ms(10);
244         } while (!time_reached(until));
245 #endif
246     }
247     return rc;
248 }
249 
stdio_usb_deinit(void)250 bool stdio_usb_deinit(void) {
251     if (get_core_num() != alarm_pool_core_num(alarm_pool_get_default())) {
252         // included an assertion here rather than just returning false, as this is likely
253         // a coding bug, rather than anything else.
254         assert(false);
255         return false;
256     }
257 
258     assert(tud_inited()); // we expect the caller to have initialized when calling sdio_usb_init
259 
260     bool rc = true;
261 
262     stdio_set_driver_enabled(&stdio_usb, false);
263 
264 #if PICO_STDIO_USB_DEINIT_DELAY_MS != 0
265     sleep_ms(PICO_STDIO_USB_DEINIT_DELAY_MS);
266 #endif
267 
268 #if !LIB_TINYUSB_DEVICE
269     if (irq_has_shared_handler(USBCTRL_IRQ)) {
270         spin_lock_unclaim(spin_lock_get_num(one_shot_timer_crit_sec.spin_lock));
271         critical_section_deinit(&one_shot_timer_crit_sec);
272         // we can use a shared handler to notice when there may be work to do
273         irq_remove_handler(USBCTRL_IRQ, usb_irq);
274     } else {
275         // timer is disabled by disabling the irq
276     }
277 
278     irq_set_enabled(low_priority_irq_num, false);
279     user_irq_unclaim(low_priority_irq_num);
280 #endif
281     return rc;
282 }
283 
stdio_usb_connected(void)284 bool stdio_usb_connected(void) {
285 #if PICO_STDIO_USB_CONNECTION_WITHOUT_DTR
286     return tud_ready();
287 #else
288     // this actually checks DTR
289     return tud_cdc_connected();
290 #endif
291 }
292 
293 #else
294 #warning stdio USB was configured along with user use of TinyUSB device mode, but CDC is not enabled
stdio_usb_init(void)295 bool stdio_usb_init(void) {
296     return false;
297 }
298 #endif // CFG_TUD_ENABLED && CFG_TUD_CDC
299 #else
300 #warning stdio USB was configured, but is being disabled as TinyUSB host is explicitly linked
stdio_usb_init(void)301 bool stdio_usb_init(void) {
302     return false;
303 }
304 #endif // !LIB_TINYUSB_HOST
305 
306