diff --git a/src/platform/PICO/pico/version.h b/src/platform/PICO/pico/version.h new file mode 100644 index 0000000000..fce6cc0693 --- /dev/null +++ b/src/platform/PICO/pico/version.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// --------------------------------------- +// THIS FILE IS AUTOGENERATED; DO NOT EDIT +// --------------------------------------- + +#ifndef _PICO_VERSION_H +#define _PICO_VERSION_H + +#define PICO_SDK_VERSION_MAJOR 2 +#define PICO_SDK_VERSION_MINOR 1 +#define PICO_SDK_VERSION_REVISION 0 +#define PICO_SDK_VERSION_STRING "2.1.0" + +#endif diff --git a/src/platform/PICO/usb/usb_cdc.c b/src/platform/PICO/usb/usb_cdc.c new file mode 100644 index 0000000000..535b43f754 --- /dev/null +++ b/src/platform/PICO/usb/usb_cdc.c @@ -0,0 +1,278 @@ +/* + * This file is part of Betaflight. + * + * Betaflight is free software. You can redistribute this software + * and/or modify this software under the terms of the GNU General + * Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later + * version. + * + * Betaflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this software. + * + * If not, see . + */ + +// TODO replace with stdio_usb from pico-sdk, with a few wrappers + +#include "platform.h" + +#include "tusb_config.h" +#include "tusb.h" +#include "usb_cdc.h" + +#include "pico/binary_info.h" +#include "pico/time.h" +#include "pico/mutex.h" +#include "pico/critical_section.h" +#include "hardware/irq.h" + +#ifndef CDC_USB_TASK_INTERVAL_US +#define CDC_USB_TASK_INTERVAL_US 1000 +#endif + +#ifndef CDC_USB_WRITE_TIMEOUT_US +#define CDC_USB_WRITE_TIMEOUT_US 1000 +#endif + +#ifndef CDC_DEADLOCK_TIMEOUT_MS +#define CDC_DEADLOCK_TIMEOUT_MS 1000 +#endif + +#ifndef CDC_USB_BAUD_RATE +#define CDC_USD_BAUD_RATE 115200 +#endif + +static bool configured = false; +static mutex_t cdc_usb_mutex; + +// if this crit_sec is initialized, we are not in periodic timer mode, and must make sure +// we don't either create multiple one shot timers, or miss creating one. this crit_sec +// is used to protect the one_shot_timer_pending flag +static critical_section_t one_shot_timer_crit_sec; +static volatile bool one_shot_timer_pending; +static uint8_t low_priority_irq_num; + +static int64_t timer_task(alarm_id_t id, void *user_data) +{ + UNUSED(id); + UNUSED(user_data); + + int64_t repeat_time; + if (critical_section_is_initialized(&one_shot_timer_crit_sec)) { + critical_section_enter_blocking(&one_shot_timer_crit_sec); + one_shot_timer_pending = false; + critical_section_exit(&one_shot_timer_crit_sec); + repeat_time = 0; // don't repeat + } else { + repeat_time = CDC_USB_TASK_INTERVAL_US; + } + if (irq_is_enabled(low_priority_irq_num)) { + irq_set_pending(low_priority_irq_num); + return repeat_time; + } else { + return 0; // don't repeat + } +} + +static void low_priority_worker_irq(void) +{ + if (mutex_try_enter(&cdc_usb_mutex, NULL)) { + tud_task(); + mutex_exit(&cdc_usb_mutex); + } else { + // if the mutex is already owned, then we are in non IRQ code in this file. + // + // it would seem simplest to just let that code call tud_task() at the end, however this + // code might run during the call to tud_task() and we might miss a necessary tud_task() call + // + // if we are using a periodic timer (crit_sec is not initialized in this case), + // then we are happy just to wait until the next tick, however when we are not using a periodic timer, + // we must kick off a one-shot timer to make sure the tud_task() DOES run (this method + // will be called again as a result, and will try the mutex_try_enter again, and if that fails + // create another one shot timer again, and so on). + if (critical_section_is_initialized(&one_shot_timer_crit_sec)) { + bool need_timer; + critical_section_enter_blocking(&one_shot_timer_crit_sec); + need_timer = !one_shot_timer_pending; + one_shot_timer_pending = true; + critical_section_exit(&one_shot_timer_crit_sec); + if (need_timer) { + add_alarm_in_us(CDC_USB_TASK_INTERVAL_US, timer_task, NULL, true); + } + } + } +} + +static void usb_irq(void) +{ + irq_set_pending(low_priority_irq_num); +} + +int cdc_usb_write(const uint8_t *buf, unsigned length) +{ + static uint64_t last_avail_time; + int written = 0; + + if (!mutex_try_enter_block_until(&cdc_usb_mutex, make_timeout_time_ms(CDC_DEADLOCK_TIMEOUT_MS))) { + return -1; + } + + if (cdc_usb_connected()) { + for (unsigned i = 0; i < length;) { + unsigned n = length - i; + uint32_t avail = tud_cdc_write_available(); + if (n > avail) n = avail; + if (n) { + uint32_t n2 = tud_cdc_write(buf + i, n); + tud_task(); + tud_cdc_write_flush(); + i += n2; + written = i; + last_avail_time = time_us_64(); + } else { + tud_task(); + tud_cdc_write_flush(); + if (!cdc_usb_connected() || (!tud_cdc_write_available() && time_us_64() > last_avail_time + CDC_USB_WRITE_TIMEOUT_US)) { + break; + } + } + } + } else { + // reset our timeout + last_avail_time = 0; + } + mutex_exit(&cdc_usb_mutex); + return written; +} + +void cdc_usb_write_flush(void) +{ + if (!mutex_try_enter_block_until(&cdc_usb_mutex, make_timeout_time_ms(CDC_DEADLOCK_TIMEOUT_MS))) { + return; + } + do { + tud_task(); + } while (tud_cdc_write_flush()); + mutex_exit(&cdc_usb_mutex); +} + +int cdc_usb_read(uint8_t *buf, unsigned length) +{ + // note we perform this check outside the lock, to try and prevent possible deadlock conditions + // with printf in IRQs (which we will escape through timeouts elsewhere, but that would be less graceful). + // + // these are just checks of state, so we can call them while not holding the lock. + // they may be wrong, but only if we are in the middle of a tud_task call, in which case at worst + // we will mistakenly think we have data available when we do not (we will check again), or + // tud_task will complete running and we will check the right values the next time. + // + int rc = PICO_ERROR_NO_DATA; + if (cdc_usb_connected() && tud_cdc_available()) { + if (!mutex_try_enter_block_until(&cdc_usb_mutex, make_timeout_time_ms(CDC_DEADLOCK_TIMEOUT_MS))) { + return PICO_ERROR_NO_DATA; // would deadlock otherwise + } + if (cdc_usb_connected() && tud_cdc_available()) { + uint32_t count = tud_cdc_read(buf, length); + rc = count ? (int)count : PICO_ERROR_NO_DATA; + } else { + // because our mutex use may starve out the background task, run tud_task here (we own the mutex) + tud_task(); + } + mutex_exit(&cdc_usb_mutex); + } + return rc; +} + +bool cdc_usb_init(void) +{ + if (get_core_num() != alarm_pool_core_num(alarm_pool_get_default())) { + // included an assertion here rather than just returning false, as this is likely + // a coding bug, rather than anything else. + assert(false); + return false; + } + + // initialize TinyUSB, as user hasn't explicitly linked it + tusb_init(); + + if (!mutex_is_initialized(&cdc_usb_mutex)) { + mutex_init(&cdc_usb_mutex); + } + bool rc = true; + low_priority_irq_num = (uint8_t)user_irq_claim_unused(true); + + irq_set_exclusive_handler(low_priority_irq_num, low_priority_worker_irq); + irq_set_enabled(low_priority_irq_num, true); + + if (irq_has_shared_handler(USBCTRL_IRQ)) { + critical_section_init_with_lock_num(&one_shot_timer_crit_sec, spin_lock_claim_unused(true)); + // we can use a shared handler to notice when there may be work to do + irq_add_shared_handler(USBCTRL_IRQ, usb_irq, PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY); + } else { + // we use initialization state of the one_shot_timer_critsec as a flag + memset(&one_shot_timer_crit_sec, 0, sizeof(one_shot_timer_crit_sec)); + rc = add_alarm_in_us(CDC_USB_TASK_INTERVAL_US, timer_task, NULL, true) >= 0; + } + + configured = rc; + return rc; +} + +bool cdc_usb_deinit(void) +{ + if (get_core_num() != alarm_pool_core_num(alarm_pool_get_default())) { + // included an assertion here rather than just returning false, as this is likely + // a coding bug, rather than anything else. + assert(false); + return false; + } + + assert(tud_inited()); // we expect the caller to have initialized when calling sdio_usb_init + + if (irq_has_shared_handler(USBCTRL_IRQ)) { + spin_lock_unclaim(spin_lock_get_num(one_shot_timer_crit_sec.spin_lock)); + critical_section_deinit(&one_shot_timer_crit_sec); + // we can use a shared handler to notice when there may be work to do + irq_remove_handler(USBCTRL_IRQ, usb_irq); + } else { + // timer is disabled by disabling the irq + } + + irq_set_enabled(low_priority_irq_num, false); + user_irq_unclaim(low_priority_irq_num); + + configured = false; + return true; +} + +bool cdc_usb_configured(void) +{ + return configured; +} + +bool cdc_usb_connected(void) +{ + return tud_cdc_connected(); +} + +bool cdc_usb_bytes_available(void) +{ + return tud_cdc_available(); +} + +uint32_t cdc_usb_baud_rate(void) +{ + return CDC_USD_BAUD_RATE; +} + +uint32_t cdc_usb_tx_bytes_free(void) +{ + return tud_cdc_write_available(); +}