| /** |
| * 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_usb.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 |