From 7504438b3ff1b3f619ea49a9d0878db842cc7020 Mon Sep 17 00:00:00 2001 From: rechrtb Date: Wed, 24 Apr 2024 20:56:39 +0800 Subject: [PATCH 1/4] Support switching between USB host and device --- src/SerialCDC_tusb.cpp | 91 +++++++++++++++++++----- src/TinyUsbInterface.cpp | 145 ++++++++++++++++++++++++++++++++++++--- src/TinyUsbInterface.h | 15 +++- 3 files changed, 223 insertions(+), 28 deletions(-) diff --git a/src/SerialCDC_tusb.cpp b/src/SerialCDC_tusb.cpp index c1eabba39..5fab1436c 100644 --- a/src/SerialCDC_tusb.cpp +++ b/src/SerialCDC_tusb.cpp @@ -9,6 +9,7 @@ #if SUPPORT_USB +#include "TinyUsbInterface.h" #include "SerialCDC_tusb.h" #if CORE_USES_TINYUSB @@ -27,18 +28,35 @@ SerialCDC::SerialCDC() noexcept void SerialCDC::Start(Pin p) noexcept { +#if CFG_TUH_ENABLED + if (CoreUsbIsHostMode()) + { + return; + } +#endif + vBusPin = p; while (!tud_inited()) { delay(10); } running = true; } void SerialCDC::end() noexcept { +#if CFG_TUH_ENABLED + if (CoreUsbIsHostMode()) + { + return; + } +#endif running = false; } bool SerialCDC::IsConnected() const noexcept { - return tud_cdc_connected(); + return +#if CFG_TUH_ENABLED + !CoreUsbIsHostMode() && +#endif + tud_cdc_connected(); } // Overridden virtual functions @@ -49,36 +67,57 @@ bool SerialCDC::IsConnected() const noexcept // available() returned nonzero bit read() never read it. Now we check neither when reading. int SerialCDC::read() noexcept { - if (!running) - { - return -1; - } - - if (tud_cdc_available()) - { - return tud_cdc_read_char(); - } - return -1; + if (!running +#if CFG_TUH_ENABLED + || CoreUsbIsHostMode() +#endif + ) + { + return -1; + } + + if (tud_cdc_available()) + { + return tud_cdc_read_char(); + } + return -1; } int SerialCDC::available() noexcept { - if (!running) - { - return 0; - } + if (!running +#if CFG_TUH_ENABLED + || CoreUsbIsHostMode() +#endif + ) + { + return 0; + } - return tud_cdc_available(); + return tud_cdc_available(); } size_t SerialCDC::readBytes(char * _ecv_array buffer, size_t length) noexcept { + if (!running +#if CFG_TUH_ENABLED + || CoreUsbIsHostMode() +#endif + ) + { + return 0; + } + return tud_cdc_read (buffer, length); } void SerialCDC::flush() noexcept { - if (!running) + if (!running +#if CFG_TUH_ENABLED + || CoreUsbIsHostMode() +#endif + ) { return; } @@ -88,7 +127,11 @@ void SerialCDC::flush() noexcept size_t SerialCDC::canWrite() noexcept { - if (!running) + if (!running +#if CFG_TUH_ENABLED + || CoreUsbIsHostMode() +#endif + ) { return 0; } @@ -99,13 +142,23 @@ size_t SerialCDC::canWrite() noexcept // Write single character, blocking size_t SerialCDC::write(uint8_t c) noexcept { +#if CFG_TUH_ENABLED + if (CoreUsbIsHostMode()) + { + return 0; + } +#endif return write(&c, 1); } // Blocking write block size_t SerialCDC::write(const uint8_t *buf, size_t length) noexcept { - if (!running) + if (!running +#if CFG_TUH_ENABLED + || CoreUsbIsHostMode() +#endif + ) { return 0; } diff --git a/src/TinyUsbInterface.cpp b/src/TinyUsbInterface.cpp index 8914e0817..81e80076e 100644 --- a/src/TinyUsbInterface.cpp +++ b/src/TinyUsbInterface.cpp @@ -29,6 +29,8 @@ #include "class/hid/hid_device.h" #include "class/audio/audio.h" #include "class/midi/midi.h" +#include "host/hcd.h" +#include "device/dcd.h" #if SAME70 @@ -266,11 +268,32 @@ extern "C" const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t lang return desc_str; } +#if CFG_TUH_ENABLED +static volatile bool isHostMode = false; +static volatile bool changingMode = false; + +static Pin UsbVbusDetect; +static Pin UsbVbusOn; +static Pin UsbModeSwitch; +static Pin UsbModeDetect; +#endif + // Call this to initialise the hardware +#if CFG_TUH_ENABLED +void CoreUsbInit(NvicPriority priority, Pin usbVbusDetect, Pin usbVbusOn, Pin usbModeSwitch, Pin usbModeDetect) noexcept +#else void CoreUsbInit(NvicPriority priority) noexcept +#endif { -#if SAME70 +#if CFG_TUH_ENABLED + UsbVbusDetect = usbVbusDetect; + UsbVbusOn = usbVbusOn; + UsbModeSwitch = usbModeSwitch; + UsbModeDetect = usbModeDetect; +#endif + +#if SAME70 // Set the USB interrupt priority to a level that is allowed to make FreeRTOS calls NVIC_SetPriority(USBHS_IRQn, priority); @@ -323,24 +346,125 @@ void CoreUsbInit(NvicPriority priority) noexcept #endif } +#if CFG_TUH_ENABLED +bool CoreUsbSetHostMode(bool hostMode, const StringRef& reply) +{ + if (changingMode) + { + reply.printf("Previous USB mode change still in progress"); + return false; + } + + if (hostMode && digitalRead(UsbVbusDetect)) + { + reply.printf("Unable to change to host mode, board plugged in to computer\n"); + return false; + } + + if (hostMode == CoreUsbIsHostMode()) + { + reply.printf("Already in %s mode\n", hostMode ? "host" : "device"); + } + else + { + CoreUsbStop(); + changingMode = true; + } + + return true; +} + +bool CoreUsbIsHostMode() +{ + return isHostMode; +} + // USB Device Driver task // This top level thread process all usb events and invoke callbacks extern "C" void CoreUsbDeviceTask(void* param) noexcept { (void)param; - // This should be called after scheduler/kernel is started. - // Otherwise it could cause kernel issue since USB IRQ handler does use RTOS queue API. - tusb_init(); + while (true) + { + auto tusb_init = isHostMode ? tuh_init : tud_init; + auto tusb_int_enable = isHostMode ? hcd_int_enable : dcd_int_enable; + auto tusb_task = isHostMode ? tuh_task_ext : tud_task_ext; + + digitalWrite(UsbVbusOn, isHostMode); + tusb_init(0); + if (changingMode) + { + if (isHostMode) + { + if (tuh_inited()) + { + hcd_init(0); + } + } + else + { + tud_connect(); + } + } + tusb_int_enable(0); + + changingMode = false; + while (!changingMode) + { + tusb_task(100, false); + } + + // Deinit current tinyUSB context. + if (isHostMode) + { + hcd_event_device_remove(0, false); + } + else + { + tud_disconnect(); + } + tusb_task(100, false); + + // Reset USB hardware. + USBHS->USBHS_CTRL &= ~USBHS_CTRL_USBE; + for (int i = 9; i >= 0; i--) + { + if (isHostMode) + { + USBHS->USBHS_HSTPIPCFG[i] &= ~(USBHS_HSTPIPCFG_ALLOC); + } + else + { + USBHS->USBHS_DEVEPTCFG[i] &= ~(USBHS_DEVEPTCFG_ALLOC); + } + } + + // Complete the mode change. + isHostMode = !isHostMode; + } +} +#else +extern "C" void CoreUsbDeviceTask(void* param) noexcept +{ + (void)param; - // RTOS forever loop - while (1) + tud_init(0); + while (true) { - // tinyusb device task tud_task(); -// tud_cdc_write_flush(); } } +#endif + +void CoreUsbStop() +{ +#if CFG_TUH_ENABLED + digitalWrite(UsbVbusOn, false); +#endif + NVIC_DisableIRQ((IRQn_Type)ID_USBHS); + USBHS->USBHS_CTRL &= ~USBHS_CTRL_USBE; +} #if RP2040 // RP2040 USB configuration has HID enabled by default @@ -390,7 +514,12 @@ uint32_t numUsbInterrupts = 0; extern "C" void USBHS_Handler() noexcept { ++numUsbInterrupts; +#if CFG_TUH_ENABLED + auto tusb_handler = isHostMode ? tuh_int_handler : tud_int_handler; + tusb_handler(0); +#else tud_int_handler(0); +#endif } #elif SAME5x diff --git a/src/TinyUsbInterface.h b/src/TinyUsbInterface.h index b5dd7f5f3..ba219cb09 100644 --- a/src/TinyUsbInterface.h +++ b/src/TinyUsbInterface.h @@ -23,12 +23,25 @@ #define TINYUSBINTERFACE_H_INCLUDED #include +#include #if SUPPORT_USB && CORE_USES_TINYUSB +#include "tusb_option.h" + +#if CFG_TUH_ENABLED +void CoreUsbInit(NvicPriority priority, Pin usbVbusDetect = NoPin, Pin usbVbusOn = NoPin, Pin usbModeSwitch = NoPin, Pin usbModeDetect = NoPin) noexcept; // call this to initialise the hardware +bool CoreUsbSetHostMode(bool hostMode, const StringRef& reply); +bool CoreUsbIsHostMode(); +#else +void CoreUsbInit(NvicPriority priority) noexcept; // call this to initialise the hardware +#endif -void CoreUsbInit(NvicPriority priority) noexcept; // call this to initialise the hardware extern "C" [[noreturn]] void CoreUsbDeviceTask(void* param) noexcept; // this must be called by the USB task +void CoreUsbStop(); +#else +#define CFG_TUH_ENABLED 0 #endif + #endif // TINYUSBINTERFACE_H_INCLUDED From 86ff0f046d77afbccf2f8d3cc945b807870aecd9 Mon Sep 17 00:00:00 2001 From: rechrtb Date: Mon, 29 Apr 2024 14:27:45 +0800 Subject: [PATCH 2/4] Fully reset USB hardware when changing mode Resetting the USB DMA registers fixes a crash when changing between host and device mode. --- src/TinyUsbInterface.cpp | 78 ++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/src/TinyUsbInterface.cpp b/src/TinyUsbInterface.cpp index 81e80076e..2bba49a95 100644 --- a/src/TinyUsbInterface.cpp +++ b/src/TinyUsbInterface.cpp @@ -387,27 +387,38 @@ extern "C" void CoreUsbDeviceTask(void* param) noexcept while (true) { - auto tusb_init = isHostMode ? tuh_init : tud_init; - auto tusb_int_enable = isHostMode ? hcd_int_enable : dcd_int_enable; - auto tusb_task = isHostMode ? tuh_task_ext : tud_task_ext; - digitalWrite(UsbVbusOn, isHostMode); - tusb_init(0); + + auto tusb_init = isHostMode ? tuh_init : tud_init; if (changingMode) { - if (isHostMode) + auto tusb_inited = isHostMode ? tuh_inited : tud_inited; + if (tusb_inited()) { - if (tuh_inited()) + // TinyUSB class drivers already inited previously, just call + // the functions to initialize the USB peripheral. + if (isHostMode) { hcd_init(0); + hcd_int_enable(0); + } + else + { + dcd_init(0); + dcd_int_enable(0); } } else { - tud_connect(); + tusb_init(0); } } - tusb_int_enable(0); + else + { + tusb_init(0); + } + + auto tusb_task = isHostMode ? tuh_task_ext : tud_task_ext; changingMode = false; while (!changingMode) @@ -415,32 +426,53 @@ extern "C" void CoreUsbDeviceTask(void* param) noexcept tusb_task(100, false); } - // Deinit current tinyUSB context. - if (isHostMode) - { - hcd_event_device_remove(0, false); - } - else - { - tud_disconnect(); - } - tusb_task(100, false); - - // Reset USB hardware. - USBHS->USBHS_CTRL &= ~USBHS_CTRL_USBE; + // Disable pipes, deallocate DPRAM for (int i = 9; i >= 0; i--) { if (isHostMode) { + USBHS->USBHS_HSTPIP &= ~(USBHS_HSTPIP_PEN0 << i); USBHS->USBHS_HSTPIPCFG[i] &= ~(USBHS_HSTPIPCFG_ALLOC); } else { + USBHS->USBHS_DEVEPT &= ~(USBHS_DEVEPT_EPEN0 << i); USBHS->USBHS_DEVEPTCFG[i] &= ~(USBHS_DEVEPTCFG_ALLOC); } } - // Complete the mode change. + // Reset DMA registers + for (int i = 0; i < 7; i++) + { + if (isHostMode) + { + USBHS->USBHS_HSTDMA[i].USBHS_HSTDMAADDRESS = 0; + USBHS->USBHS_HSTDMA[i].USBHS_HSTDMACONTROL = 0; + USBHS->USBHS_HSTDMA[i].USBHS_HSTDMASTATUS = 0; + } + else + { + USBHS->USBHS_DEVDMA[i].USBHS_DEVDMAADDRESS = 0; + USBHS->USBHS_DEVDMA[i].USBHS_DEVDMACONTROL = 0; + USBHS->USBHS_DEVDMA[i].USBHS_DEVDMASTATUS = 0; + } + } + + // Deinit current tinyUSB host context. Not needed for device + // since changing mode requires the board to not be connected + // to a host, so if we are changing mode then the DCD_EVENT_UNPLUGGED + // must have been handled in the past already. + if (isHostMode) + { + hcd_event_device_remove(0, false); + tusb_task(100, false); + } + + // Reset USB hardware + USBHS->USBHS_CTRL &= ~(USBHS_CTRL_USBE | USBHS_CTRL_UIMOD); + USBHS->USBHS_CTRL |= USBHS_CTRL_FRZCLK; + + // Toggle mode for next loop isHostMode = !isHostMode; } } From 8e5fd2e4790f4abef4651169e5b8e437107818f7 Mon Sep 17 00:00:00 2001 From: rechrtb Date: Mon, 29 Apr 2024 20:52:55 +0800 Subject: [PATCH 3/4] Fix build for SAME5x --- src/TinyUsbInterface.cpp | 14 +++++++++----- src/TinyUsbInterface.h | 10 ++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/TinyUsbInterface.cpp b/src/TinyUsbInterface.cpp index 2bba49a95..761551b9e 100644 --- a/src/TinyUsbInterface.cpp +++ b/src/TinyUsbInterface.cpp @@ -279,13 +279,8 @@ static Pin UsbModeDetect; #endif // Call this to initialise the hardware -#if CFG_TUH_ENABLED void CoreUsbInit(NvicPriority priority, Pin usbVbusDetect, Pin usbVbusOn, Pin usbModeSwitch, Pin usbModeDetect) noexcept -#else -void CoreUsbInit(NvicPriority priority) noexcept -#endif { - #if CFG_TUH_ENABLED UsbVbusDetect = usbVbusDetect; UsbVbusOn = usbVbusOn; @@ -494,8 +489,17 @@ void CoreUsbStop() #if CFG_TUH_ENABLED digitalWrite(UsbVbusOn, false); #endif + +#if SAME5x + NVIC_DisableIRQ(USB_0_IRQn); + NVIC_DisableIRQ(USB_1_IRQn); + NVIC_DisableIRQ(USB_2_IRQn); + NVIC_DisableIRQ(USB_3_IRQn); + USB->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE; +#elif SAME70 NVIC_DisableIRQ((IRQn_Type)ID_USBHS); USBHS->USBHS_CTRL &= ~USBHS_CTRL_USBE; +#endif } #if RP2040 // RP2040 USB configuration has HID enabled by default diff --git a/src/TinyUsbInterface.h b/src/TinyUsbInterface.h index ba219cb09..da99ee8fc 100644 --- a/src/TinyUsbInterface.h +++ b/src/TinyUsbInterface.h @@ -28,17 +28,15 @@ #if SUPPORT_USB && CORE_USES_TINYUSB #include "tusb_option.h" -#if CFG_TUH_ENABLED void CoreUsbInit(NvicPriority priority, Pin usbVbusDetect = NoPin, Pin usbVbusOn = NoPin, Pin usbModeSwitch = NoPin, Pin usbModeDetect = NoPin) noexcept; // call this to initialise the hardware +extern "C" [[noreturn]] void CoreUsbDeviceTask(void* param) noexcept; // this must be called by the USB task +void CoreUsbStop(); + +#if CFG_TUH_ENABLED bool CoreUsbSetHostMode(bool hostMode, const StringRef& reply); bool CoreUsbIsHostMode(); -#else -void CoreUsbInit(NvicPriority priority) noexcept; // call this to initialise the hardware #endif -extern "C" [[noreturn]] void CoreUsbDeviceTask(void* param) noexcept; // this must be called by the USB task -void CoreUsbStop(); - #else #define CFG_TUH_ENABLED 0 #endif From d837026763a7f52eb4d67371d1d4c9f972f699b1 Mon Sep 17 00:00:00 2001 From: rechrtb Date: Thu, 18 Jul 2024 00:07:30 +0900 Subject: [PATCH 4/4] Fix issue with checking if already in host mode --- src/TinyUsbInterface.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/TinyUsbInterface.cpp b/src/TinyUsbInterface.cpp index 761551b9e..1df7f6dd7 100644 --- a/src/TinyUsbInterface.cpp +++ b/src/TinyUsbInterface.cpp @@ -350,17 +350,13 @@ bool CoreUsbSetHostMode(bool hostMode, const StringRef& reply) return false; } - if (hostMode && digitalRead(UsbVbusDetect)) + if (!CoreUsbIsHostMode() && hostMode && digitalRead(UsbVbusDetect)) { reply.printf("Unable to change to host mode, board plugged in to computer\n"); return false; } - if (hostMode == CoreUsbIsHostMode()) - { - reply.printf("Already in %s mode\n", hostMode ? "host" : "device"); - } - else + if (hostMode != CoreUsbIsHostMode()) { CoreUsbStop(); changingMode = true;