/** * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #if !defined(TINYUSB_HOST_LINKED) && !defined(TINYUSB_DEVICE_LINKED) #include "tusb.h" #include "pico/time.h" #include "pico/stdio/driver.h" #include "pico/binary_info.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() { // 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); } return rc; } #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