/** * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #if !defined(LIB_TINYUSB_HOST) && !defined(LIB_TINYUSB_DEVICE) #include "tusb.h" #include "pico/time.h" #include "pico/stdio/driver.h" #include "pico/binary_info.h" #include "pico/mutex.h" #include "hardware/irq.h" static_assert(PICO_STDIO_USB_LOW_PRIORITY_IRQ > RTC_IRQ, ""); // note RTC_IRQ is currently the last one static mutex_t stdio_usb_mutex; static void low_priority_worker_irq(void) { // if the mutex is already owned, then we are in user code // in this file which will do a tud_task itself, so we'll just do nothing // until the next tick; we won't starve if (mutex_try_enter(&stdio_usb_mutex, NULL)) { tud_task(); mutex_exit(&stdio_usb_mutex); } } static int64_t timer_task(__unused alarm_id_t id, __unused void *user_data) { irq_set_pending(PICO_STDIO_USB_LOW_PRIORITY_IRQ); return PICO_STDIO_USB_TASK_INTERVAL_US; } static void stdio_usb_out_chars(const char *buf, int length) { static uint64_t last_avail_time; uint32_t owner; if (!mutex_try_enter(&stdio_usb_mutex, &owner)) { if (owner == get_core_num()) return; // would deadlock otherwise mutex_enter_blocking(&stdio_usb_mutex); } if (tud_cdc_connected()) { for (int i = 0; i < length;) { int n = length - i; int avail = tud_cdc_write_available(); if (n > avail) n = avail; if (n) { int n2 = tud_cdc_write(buf + i, n); tud_task(); tud_cdc_write_flush(); i += n2; last_avail_time = time_us_64(); } else { tud_task(); tud_cdc_write_flush(); if (!tud_cdc_connected() || (!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) { break; } } } } else { // reset our timeout last_avail_time = 0; } mutex_exit(&stdio_usb_mutex); } int stdio_usb_in_chars(char *buf, int length) { uint32_t owner; if (!mutex_try_enter(&stdio_usb_mutex, &owner)) { if (owner == get_core_num()) return PICO_ERROR_NO_DATA; // would deadlock otherwise mutex_enter_blocking(&stdio_usb_mutex); } int rc = PICO_ERROR_NO_DATA; if (tud_cdc_connected() && tud_cdc_available()) { int count = tud_cdc_read(buf, length); rc = count ? count : PICO_ERROR_NO_DATA; } mutex_exit(&stdio_usb_mutex); return rc; } stdio_driver_t stdio_usb = { .out_chars = stdio_usb_out_chars, .in_chars = stdio_usb_in_chars, #if PICO_STDIO_ENABLE_CRLF_SUPPORT .crlf_enabled = PICO_STDIO_USB_DEFAULT_CRLF #endif }; bool stdio_usb_init(void) { #if !PICO_NO_BI_STDIO_USB bi_decl_if_func_used(bi_program_feature("USB stdin / stdout")); #endif // initialize TinyUSB tusb_init(); irq_set_exclusive_handler(PICO_STDIO_USB_LOW_PRIORITY_IRQ, low_priority_worker_irq); irq_set_enabled(PICO_STDIO_USB_LOW_PRIORITY_IRQ, true); mutex_init(&stdio_usb_mutex); bool rc = add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true); if (rc) { stdio_set_driver_enabled(&stdio_usb, true); #if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS #if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS > 0 absolute_time_t until = make_timeout_time_ms(PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS); #else absolute_time_t until = at_the_end_of_time; #endif do { if (stdio_usb_connected()) { #if PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS != 0 sleep_ms(PICO_STDIO_USB_POST_CONNECT_WAIT_DELAY_MS); #endif break; } sleep_ms(10); } while (!time_reached(until)); #endif } return rc; } bool stdio_usb_connected(void) { return tud_cdc_connected(); } #else #include "pico/stdio_usb.h" #warning stdio USB was configured, but is being disabled as TinyUSB is explicitly linked bool stdio_usb_init(void) { return false; } #endif