1 /**
2  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #if !defined(TINYUSB_HOST_LINKED) && !defined(TINYUSB_DEVICE_LINKED)
8 #include "tusb.h"
9 
10 #include "pico/time.h"
11 #include "pico/stdio_usb.h"
12 #include "pico/stdio/driver.h"
13 #include "pico/binary_info.h"
14 #include "hardware/irq.h"
15 
16 static_assert(PICO_STDIO_USB_LOW_PRIORITY_IRQ > RTC_IRQ, ""); // note RTC_IRQ is currently the last one
17 static mutex_t stdio_usb_mutex;
18 
low_priority_worker_irq()19 static void low_priority_worker_irq() {
20     // if the mutex is already owned, then we are in user code
21     // in this file which will do a tud_task itself, so we'll just do nothing
22     // until the next tick; we won't starve
23     if (mutex_try_enter(&stdio_usb_mutex, NULL)) {
24         tud_task();
25         mutex_exit(&stdio_usb_mutex);
26     }
27 }
28 
timer_task(__unused alarm_id_t id,__unused void * user_data)29 static int64_t timer_task(__unused alarm_id_t id, __unused void *user_data) {
30     irq_set_pending(PICO_STDIO_USB_LOW_PRIORITY_IRQ);
31     return PICO_STDIO_USB_TASK_INTERVAL_US;
32 }
33 
stdio_usb_out_chars(const char * buf,int length)34 static void stdio_usb_out_chars(const char *buf, int length) {
35     static uint64_t last_avail_time;
36     uint32_t owner;
37     if (!mutex_try_enter(&stdio_usb_mutex, &owner)) {
38         if (owner == get_core_num()) return; // would deadlock otherwise
39         mutex_enter_blocking(&stdio_usb_mutex);
40     }
41     if (tud_cdc_connected()) {
42         for (int i = 0; i < length;) {
43             int n = length - i;
44             int avail = tud_cdc_write_available();
45             if (n > avail) n = avail;
46             if (n) {
47                 int n2 = tud_cdc_write(buf + i, n);
48                 tud_task();
49                 tud_cdc_write_flush();
50                 i += n2;
51                 last_avail_time = time_us_64();
52             } else {
53                 tud_task();
54                 tud_cdc_write_flush();
55                 if (!tud_cdc_connected() ||
56                     (!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) {
57                     break;
58                 }
59             }
60         }
61     } else {
62         // reset our timeout
63         last_avail_time = 0;
64     }
65     mutex_exit(&stdio_usb_mutex);
66 }
67 
stdio_usb_in_chars(char * buf,int length)68 int stdio_usb_in_chars(char *buf, int length) {
69     uint32_t owner;
70     if (!mutex_try_enter(&stdio_usb_mutex, &owner)) {
71         if (owner == get_core_num()) return PICO_ERROR_NO_DATA; // would deadlock otherwise
72         mutex_enter_blocking(&stdio_usb_mutex);
73     }
74     int rc = PICO_ERROR_NO_DATA;
75     if (tud_cdc_connected() && tud_cdc_available()) {
76         int count = tud_cdc_read(buf, length);
77         rc =  count ? count : PICO_ERROR_NO_DATA;
78     }
79     mutex_exit(&stdio_usb_mutex);
80     return rc;
81 }
82 
83 stdio_driver_t stdio_usb = {
84     .out_chars = stdio_usb_out_chars,
85     .in_chars = stdio_usb_in_chars,
86 #if PICO_STDIO_ENABLE_CRLF_SUPPORT
87     .crlf_enabled = PICO_STDIO_USB_DEFAULT_CRLF
88 #endif
89 };
90 
stdio_usb_init(void)91 bool stdio_usb_init(void) {
92 #if !PICO_NO_BI_STDIO_USB
93     bi_decl_if_func_used(bi_program_feature("USB stdin / stdout"));
94 #endif
95 
96     // initialize TinyUSB
97     tusb_init();
98 
99     irq_set_exclusive_handler(PICO_STDIO_USB_LOW_PRIORITY_IRQ, low_priority_worker_irq);
100     irq_set_enabled(PICO_STDIO_USB_LOW_PRIORITY_IRQ, true);
101 
102     mutex_init(&stdio_usb_mutex);
103     bool rc = add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true);
104     if (rc) {
105         stdio_set_driver_enabled(&stdio_usb, true);
106     }
107     return rc;
108 }
109 #else
110 #include "pico/stdio_usb.h"
111 #warning stdio USB was configured, but is being disabled as TinyUSB is explicitly linked
stdio_usb_init(void)112 bool stdio_usb_init(void) {
113     return false;
114 }
115 #endif
116