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