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