diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt index 175646830..2783dd84e 100644 --- a/examples/host/CMakeLists.txt +++ b/examples/host/CMakeLists.txt @@ -11,4 +11,5 @@ family_add_subdirectory(cdc_msc_hid) family_add_subdirectory(cdc_msc_hid_freertos) family_add_subdirectory(device_info) family_add_subdirectory(hid_controller) +family_add_subdirectory(midi_rx) family_add_subdirectory(msc_file_explorer) diff --git a/examples/host/cdc_msc_hid/src/main.c b/examples/host/cdc_msc_hid/src/main.c index 71257c0fe..2d5df23f9 100644 --- a/examples/host/cdc_msc_hid/src/main.c +++ b/examples/host/cdc_msc_hid/src/main.c @@ -82,12 +82,12 @@ int main(void) { void tuh_mount_cb(uint8_t dev_addr) { // application set-up - printf("A device with address %d is mounted\r\n", dev_addr); + printf("A device with address %u is mounted\r\n", dev_addr); } void tuh_umount_cb(uint8_t dev_addr) { // application tear-down - printf("A device with address %d is unmounted \r\n", dev_addr); + printf("A device with address %u is unmounted \r\n", dev_addr); } diff --git a/examples/host/device_info/src/tusb_config.h b/examples/host/device_info/src/tusb_config.h index db02a97db..e12970c12 100644 --- a/examples/host/device_info/src/tusb_config.h +++ b/examples/host/device_info/src/tusb_config.h @@ -110,8 +110,7 @@ // only hub class is enabled #define CFG_TUH_HUB 1 -// max device support (excluding hub device) -// 1 hub typically has 4 ports +// max device support (excluding hub device): 1 hub typically has 4 ports #define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) #ifdef __cplusplus diff --git a/examples/host/midi_rx/CMakeLists.txt b/examples/host/midi_rx/CMakeLists.txt new file mode 100644 index 000000000..33953233d --- /dev/null +++ b/examples/host/midi_rx/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +# gets PROJECT name for the example +family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR}) + +project(${PROJECT} C CXX ASM) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}) + +# Espressif has its own cmake build system +if(FAMILY STREQUAL "espressif") + return() +endif() + +add_executable(${PROJECT}) + +# Example source +target_sources(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ) + +# Example include +target_include_directories(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +# Configure compilation flags and libraries for the example without RTOS. +# See the corresponding function in hw/bsp/FAMILY/family.cmake for details. +family_configure_host_example(${PROJECT} noos) diff --git a/examples/host/midi_rx/Makefile b/examples/host/midi_rx/Makefile new file mode 100644 index 000000000..0235e08c3 --- /dev/null +++ b/examples/host/midi_rx/Makefile @@ -0,0 +1,13 @@ +include ../../build_system/make/make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += \ + src/main.c + +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../build_system/make/rules.mk diff --git a/examples/host/midi_rx/only.txt b/examples/host/midi_rx/only.txt new file mode 100644 index 000000000..b6f87f423 --- /dev/null +++ b/examples/host/midi_rx/only.txt @@ -0,0 +1,20 @@ +mcu:ESP32S2 +mcu:ESP32S3 +mcu:ESP32P4 +mcu:KINETIS_KL +mcu:LPC175X_6X +mcu:LPC177X_8X +mcu:LPC18XX +mcu:LPC40XX +mcu:LPC43XX +mcu:MAX3421 +mcu:MIMXRT1XXX +mcu:MIMXRT10XX +mcu:MIMXRT11XX +mcu:MSP432E4 +mcu:RP2040 +mcu:RX65X +mcu:RAXXX +mcu:STM32F4 +mcu:STM32F7 +mcu:STM32H7 diff --git a/examples/host/midi_rx/src/main.c b/examples/host/midi_rx/src/main.c new file mode 100644 index 000000000..78b1a11b9 --- /dev/null +++ b/examples/host/midi_rx/src/main.c @@ -0,0 +1,123 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include +#include + +#include "bsp/board_api.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// STATIC GLOBALS DECLARATION +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTOTYPES +//--------------------------------------------------------------------+ +void led_blinking_task(void); +void midi_host_rx_task(void); + +/*------------- MAIN -------------*/ +int main(void) { + board_init(); + + printf("TinyUSB Host MIDI Example\r\n"); + + // init host stack on configured roothub port + tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUSB_SPEED_AUTO + }; + tusb_init(BOARD_TUH_RHPORT, &host_init); + + while (1) { + tuh_task(); + led_blinking_task(); + midi_host_rx_task(); + } + + return 0; +} + +//--------------------------------------------------------------------+ +// Blinking Task +//--------------------------------------------------------------------+ +void led_blinking_task(void) { + const uint32_t interval_ms = 1000; + static uint32_t start_ms = 0; + + static bool led_state = false; + + // Blink every interval ms + if (board_millis() - start_ms < interval_ms) return;// not enough time + start_ms += interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state;// toggle +} + +//--------------------------------------------------------------------+ +// MIDI host receive task +//--------------------------------------------------------------------+ +void midi_host_rx_task(void) { + // nothing to do, we just print out received data in callback +} + +//--------------------------------------------------------------------+ +// TinyUSB Callbacks +//--------------------------------------------------------------------+ + +// Invoked when device with MIDI interface is mounted. +void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) { + printf("MIDI Interface Index = %u, Address = %u, Number of RX cables = %u, Number of TX cables = %u\r\n", + idx, mount_cb_data->daddr, mount_cb_data->rx_cable_count, mount_cb_data->tx_cable_count); +} + +// Invoked when device with hid interface is un-mounted +void tuh_midi_umount_cb(uint8_t idx) { + printf("MIDI Interface Index = %u is unmounted\r\n", idx); +} + +void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes) { + if (xferred_bytes == 0) { + return; + } + + uint8_t buffer[48]; + uint8_t cable_num = 0; + uint32_t bytes_read = tuh_midi_stream_read(idx, &cable_num, buffer, sizeof(buffer)); + + printf("Cable %u rx: ", cable_num); + for (uint32_t i = 0; i < bytes_read; i++) { + printf("%02X ", buffer[i]); + } + printf("\r\n"); +} + +void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void) idx; + (void) xferred_bytes; +} diff --git a/examples/host/midi_rx/src/tusb_config.h b/examples/host/midi_rx/src/tusb_config.h new file mode 100644 index 000000000..c9b430388 --- /dev/null +++ b/examples/host/midi_rx/src/tusb_config.h @@ -0,0 +1,118 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// Common Configuration +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +// Espressif IDF requires "freertos/" prefix in include path +#if TUSB_MCU_VENDOR_ESPRESSIF +#define CFG_TUSB_OS_INC_PATH freertos/ +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUH_MEM_SECTION +#define CFG_TUH_MEM_SECTION +#endif + +#ifndef CFG_TUH_MEM_ALIGN +#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Enable Host stack +#define CFG_TUH_ENABLED 1 + +#if CFG_TUSB_MCU == OPT_MCU_RP2040 + // #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller + // #define CFG_TUH_MAX3421 1 // use max3421 as host controller + + // host roothub port is 1 if using either pio-usb or max3421 + #if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421) + #define BOARD_TUH_RHPORT 1 + #endif +#endif + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED + +//------------------------- Board Specific -------------------------- + +// RHPort number used for host can be defined by board.mk, default to port 0 +#ifndef BOARD_TUH_RHPORT +#define BOARD_TUH_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUH_MAX_SPEED +#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// Driver Configuration +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +#define CFG_TUH_HUB 1 +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) +#define CFG_TUH_MIDI CFG_TUH_DEVICE_MAX + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hw/bsp/rp2040/family.c b/hw/bsp/rp2040/family.c index a6e176308..8c85c5cc8 100644 --- a/hw/bsp/rp2040/family.c +++ b/hw/bsp/rp2040/family.c @@ -294,7 +294,9 @@ int board_getchar(void) { #if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 void max3421_int_handler(uint gpio, uint32_t event_mask) { - if (!(gpio == MAX3421_INTR_PIN && event_mask & GPIO_IRQ_EDGE_FALL)) return; + if (!(gpio == MAX3421_INTR_PIN && event_mask & GPIO_IRQ_EDGE_FALL)) { + return; + } tuh_int_handler(BOARD_TUH_RHPORT, true); } diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake index 384b06933..2182e9ad1 100644 --- a/hw/bsp/rp2040/family.cmake +++ b/hw/bsp/rp2040/family.cmake @@ -113,6 +113,7 @@ target_sources(tinyusb_host_base INTERFACE ${TOP}/src/host/hub.c ${TOP}/src/class/cdc/cdc_host.c ${TOP}/src/class/hid/hid_host.c + ${TOP}/src/class/midi/midi_host.c ${TOP}/src/class/msc/msc_host.c ${TOP}/src/class/vendor/vendor_host.c ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b3ab4d42..55c52033c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ function(tinyusb_target_add TARGET) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/host/hub.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_host.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_host.c # typec diff --git a/src/class/audio/audio.h b/src/class/audio/audio.h index 2f97c0f23..0d1acadcc 100644 --- a/src/class/audio/audio.h +++ b/src/class/audio/audio.h @@ -661,6 +661,7 @@ typedef struct TU_ATTR_PACKED uint16_t wTotalLength ; ///< Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes the combined length of this descriptor header and all Clock Source, Unit and Terminal descriptors. uint8_t bmControls ; ///< See: audio_cs_ac_interface_control_pos_t. } audio_desc_cs_ac_interface_t; +TU_VERIFY_STATIC(sizeof(audio_desc_cs_ac_interface_t) == 9, "size is not correct"); /// AUDIO Clock Source Descriptor (4.7.2.1) typedef struct TU_ATTR_PACKED diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 8a130b7e2..388168424 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -628,10 +628,6 @@ static uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio); #if (CFG_TUD_AUDIO_ENABLE_EP_IN && (CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL || CFG_TUD_AUDIO_ENABLE_ENCODING)) || (CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING) static void audiod_parse_for_AS_params(audiod_function_t *audio, uint8_t const *p_desc, uint8_t const *p_desc_end, uint8_t const as_itf); - -static inline uint8_t tu_desc_subtype(void const *desc) { - return ((uint8_t const *) desc)[2]; -} #endif #if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index e817ebc7e..4058857c5 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -691,10 +691,10 @@ bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t } } else if ( ep_addr == p_cdc->stream.rx.ep_addr ) { #if CFG_TUH_CDC_FTDI - if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI) { + if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI && xferred_bytes > 2) { // FTDI reserve 2 bytes for status // uint8_t status[2] = {p_cdc->stream.rx.ep_buf[0], p_cdc->stream.rx.ep_buf[1]}; - tu_edpt_stream_read_xfer_complete_offset(&p_cdc->stream.rx, xferred_bytes, 2); + tu_edpt_stream_read_xfer_complete_with_buf(&p_cdc->stream.rx, p_cdc->stream.rx.ep_buf+2, xferred_bytes-2); }else #endif { diff --git a/src/class/cdc/cdc_host.h b/src/class/cdc/cdc_host.h index df975b2f0..8c5399534 100644 --- a/src/class/cdc/cdc_host.h +++ b/src/class/cdc/cdc_host.h @@ -49,22 +49,22 @@ // RX FIFO size #ifndef CFG_TUH_CDC_RX_BUFSIZE -#define CFG_TUH_CDC_RX_BUFSIZE USBH_EPSIZE_BULK_MAX +#define CFG_TUH_CDC_RX_BUFSIZE TUH_EPSIZE_BULK_MPS #endif // RX Endpoint size #ifndef CFG_TUH_CDC_RX_EPSIZE -#define CFG_TUH_CDC_RX_EPSIZE USBH_EPSIZE_BULK_MAX +#define CFG_TUH_CDC_RX_EPSIZE TUH_EPSIZE_BULK_MPS #endif // TX FIFO size #ifndef CFG_TUH_CDC_TX_BUFSIZE -#define CFG_TUH_CDC_TX_BUFSIZE USBH_EPSIZE_BULK_MAX +#define CFG_TUH_CDC_TX_BUFSIZE TUH_EPSIZE_BULK_MPS #endif // TX Endpoint size #ifndef CFG_TUH_CDC_TX_EPSIZE -#define CFG_TUH_CDC_TX_EPSIZE USBH_EPSIZE_BULK_MAX +#define CFG_TUH_CDC_TX_EPSIZE TUH_EPSIZE_BULK_MPS #endif //--------------------------------------------------------------------+ diff --git a/src/class/midi/README_midi_host.md b/src/class/midi/README_midi_host.md new file mode 100644 index 000000000..efaf7a13d --- /dev/null +++ b/src/class/midi/README_midi_host.md @@ -0,0 +1,111 @@ +# MIDI HOST DRIVER +This README file contains the design notes and limitations of the +MIDI host driver. + +# MAXIMUM NUMBER OF MIDI DEVICES ATTACHED TO HOST +In this version of the driver, only one MIDI device is supported. This +constraint may change in the future. + +# MAXIMUM NUMBER OF ENDPOINTS +Although the USB MIDI 1.0 Class specification allows an arbitrary number +of endpoints, this driver supports at most one USB BULK DATA IN endpoint +and one USB BULK DATA OUT endpoint. Each endpoint can support up to 16 +virtual cables. If a device has multiple IN endpoints or multiple OUT +endpoints, it will fail to enumerate. + +Most USB MIDI devices contain both an IN endpoint and an OUT endpoint, +but not all do. For example, some USB pedals only support an OUT endpoint. +This driver allows that. + +# PUBLIC API +Applications interact with this driver via 8-bit buffers of MIDI messages +formed using the rules for sending bytes on a 5-pin DIN cable per the +original MIDI 1.0 specification. + +To send a message to a device, the Host application composes a sequence +of status and data bytes in a byte array and calls the API function. +The arguments of the function are a pointer to the byte array, the number +of bytes in the array, and the target virtual cable number 0-15. + +When the host driver receives a message from the device, the host driver +will call a callback function that the host application registers. This +callback function contains a pointer to a message buffer, a message length, +and the virtual cable number of the message buffer. One complete bulk IN +endpoint transfer might contain multiple messages targeted to different +virtual cables. + +# SUBCLASS AUDIO CONTROL +A MIDI device does not absolutely need to have an Audio Control Interface, +unless it adheres to the USB Audio Class 2 spec, but many devices +have them even if the devices do not have an audio streaming interface. +Because this driver does not support audio streaming, the descriptor parser +will skip past any audio control interface and audio streaming interface +and open only the MIDI interface. + +An audio streaming host driver can use this driver by passing a pointer +to the MIDI interface descriptor that is found after the audio streaming +interface to the midih_open() function. That is, an audio streaming host +driver would parse the audio control interface descriptor and then the +audio streaming interface and endpoint descriptors. When the next descriptor +pointer points to a MIDI interface descriptor, call midih_open() with that +descriptor pointer. + +# CLASS SPECIFIC INTERFACE AND REQUESTS +The host driver does not make use of the information in the class specific +interface descriptors. In the future, a public API could be created to +retrieve the string descriptors for the names of each ELEMENT, +IN JACK and OUT JACK, and how the device describes the connections. + +This driver also does not support class specific requests to control +ELEMENT items, nor does it support non-MIDI Streaming bulk endpoints. + +# MIDI CLASS SPECIFIC DESCRIPTOR TOTAL LENGTH FIELD IGNORED +I have observed at least one keyboard by a leading manufacturer that +sets the wTotalLength field of the Class-Specific MS Interface Header +Descriptor to include the length of the MIDIStreaming Endpoint +Descriptors. This is wrong per my reading of the specification. + +# MESSAGE BUFFER DETAILS +Messages buffers composed from USB data received on the IN endpoint will never contain +running status because USB MIDI 1.0 class does not support that. Messages +buffers to be sent to the device on the OUT endpoint may contain running status +(the message might come from a UART data stream from a 5-pin DIN MIDI IN +cable on the host, for example). The driver may in the future correctly compose +4-byte USB MIDI Class packets using the running status if need be. However, +it does not currently do that. Also, use of running status is not a good idea +overall because a single byte error can really mess up the data stream with no +way to recover until the next non-real time status byte is in the message buffer. + +Message buffers to be sent to the device may contain Real time messages +such as MIDI clock. Real time messages may be inserted in the message +byte stream between status and data bytes of another message without disrupting +the running status. However, because MIDI 1.0 class messages are sent +as four byte packets, a real-time message so inserted will be re-ordered +to be sent to the device in a new 4-byte packet immediately before the +interrupted data stream. + +Real time messages the device sends to the host can only appear between +the status byte and data bytes of the message in System Exclusive messages +that are longer than 3 bytes. + +# POORLY FORMED USB MIDI DATA PACKETS FROM THE DEVICE +Some devices do not properly encode the code index number (CIN) for the +MIDI message status byte even though the 3-byte data payload correctly encodes +the MIDI message. This driver looks to the byte after the CIN byte to decide +how many bytes to place in the message buffer. + +Some devices do not properly encode the virtual cable number. If the virtual +cable number in the CIN data byte of the packet is not less than bNumEmbMIDIJack +for that endpoint, then the host driver assumes virtual cable 0 and does not +report an error. + +Some MIDI devices will always send back exactly wMaxPacketSize bytes on +every endpoint even if only one 4-byte packet is required (e.g., NOTE ON). +These devices send packets with 4 packet bytes 0. This driver ignores all +zero packets without reporting an error. + +# ENUMERATION FAILURES +The host may fail to enumerate a device if it has too many endpoints, if it has +if it has a Standard MS Transfer Bulk Data Endpoint Descriptor (not supported), +if it has a poorly formed descriptor, or if the descriptor is too long for +the host to read the whole thing. diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h index 8ddcdfda2..cd67640e4 100644 --- a/src/class/midi/midi.h +++ b/src/class/midi/midi.h @@ -24,13 +24,8 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup group_class - * \defgroup ClassDriver_CDC Communication Device Class (CDC) - * Currently only Abstract Control Model subclass is supported - * @{ */ - -#ifndef _TUSB_MIDI_H__ -#define _TUSB_MIDI_H__ +#ifndef TUSB_MIDI_H_ +#define TUSB_MIDI_H_ #include "common/tusb_common.h" @@ -39,30 +34,31 @@ #endif //--------------------------------------------------------------------+ -// Class Specific Descriptor +// Constants //--------------------------------------------------------------------+ +enum { + MIDI_VERSION_1_0 = 0x0100, + MIDI_VERSION_2_0 = 0x0200, +}; -typedef enum -{ +typedef enum { MIDI_CS_INTERFACE_HEADER = 0x01, MIDI_CS_INTERFACE_IN_JACK = 0x02, MIDI_CS_INTERFACE_OUT_JACK = 0x03, MIDI_CS_INTERFACE_ELEMENT = 0x04, } midi_cs_interface_subtype_t; -typedef enum -{ - MIDI_CS_ENDPOINT_GENERAL = 0x01 +typedef enum { + MIDI_CS_ENDPOINT_GENERAL = 0x01, + MIDI_CS_ENDPOINT_GENERAL_2_0 = 0x02, } midi_cs_endpoint_subtype_t; -typedef enum -{ +typedef enum { MIDI_JACK_EMBEDDED = 0x01, MIDI_JACK_EXTERNAL = 0x02 } midi_jack_type_t; -typedef enum -{ +typedef enum { MIDI_CIN_MISC = 0, MIDI_CIN_CABLE_EVENT = 1, MIDI_CIN_SYSCOM_2BYTE = 2, // 2 byte system common message e.g MTC, SongSelect @@ -82,8 +78,7 @@ typedef enum } midi_code_index_number_t; // MIDI 1.0 status byte -enum -{ +enum { //------------- System Exclusive -------------// MIDI_STATUS_SYSEX_START = 0xF0, MIDI_STATUS_SYSEX_END = 0xF7, @@ -106,80 +101,54 @@ enum MIDI_STATUS_SYSREAL_SYSTEM_RESET = 0xFF, }; +enum { + MIDI_MAX_DATA_VAL = 0x7F, +}; + +//--------------------------------------------------------------------+ +// Class Specific Descriptor +//--------------------------------------------------------------------+ + /// MIDI Interface Header Descriptor -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint16_t bcdMSC ; ///< MidiStreaming SubClass release number in Binary-Coded Decimal - uint16_t wTotalLength ; +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< must be TUSB_DESC_CS_INTERFACE + uint8_t bDescriptorSubType;///< Descriptor SubType + uint16_t bcdMSC; ///< MidiStreaming SubClass release number in Binary-Coded Decimal + uint16_t wTotalLength; } midi_desc_header_t; +TU_VERIFY_STATIC(sizeof(midi_desc_header_t) == 7, "size is not correct"); /// MIDI In Jack Descriptor -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint8_t bJackType ; ///< Embedded or External - uint8_t bJackID ; ///< Unique ID for MIDI IN Jack - uint8_t iJack ; ///< string descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType;///< Descriptor SubType + uint8_t bJackType; ///< Embedded or External + uint8_t bJackID; ///< Unique ID for MIDI IN Jack + uint8_t iJack; ///< string descriptor } midi_desc_in_jack_t; +TU_VERIFY_STATIC(sizeof(midi_desc_in_jack_t) == 6, "size is not correct"); - -/// MIDI Out Jack Descriptor with single pin -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint8_t bJackType ; ///< Embedded or External - uint8_t bJackID ; ///< Unique ID for MIDI IN Jack - uint8_t bNrInputPins; - - uint8_t baSourceID; - uint8_t baSourcePin; - - uint8_t iJack ; ///< string descriptor -} midi_desc_out_jack_t ; - -/// MIDI Out Jack Descriptor with multiple pins +/// MIDI Out Jack Descriptor with multiple input pins #define midi_desc_out_jack_n_t(input_num) \ - struct TU_ATTR_PACKED { \ - uint8_t bLength ; \ - uint8_t bDescriptorType ; \ - uint8_t bDescriptorSubType ; \ - uint8_t bJackType ; \ - uint8_t bJackID ; \ - uint8_t bNrInputPins ; \ - struct TU_ATTR_PACKED { \ - uint8_t baSourceID; \ - uint8_t baSourcePin; \ - } pins[input_num]; \ - uint8_t iJack ; \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bJackType; \ + uint8_t bJackID; \ + uint8_t bNrInputPins; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID; \ + uint8_t baSourcePin; \ + } input[input_num]; \ + uint8_t iJack; \ } -/// MIDI Element Descriptor -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint8_t bElementID; - - uint8_t bNrInputPins; - uint8_t baSourceID; - uint8_t baSourcePin; - - uint8_t bNrOutputPins; - uint8_t bInTerminalLink; - uint8_t bOutTerminalLink; - uint8_t bElCapsSize; - - uint16_t bmElementCaps; - uint8_t iElement; -} midi_desc_element_t; +typedef midi_desc_out_jack_n_t(1) midi_desc_out_jack_1in_t; // 1 input +typedef midi_desc_out_jack_1in_t midi_desc_out_jack_t; // backward compatible +TU_VERIFY_STATIC(sizeof(midi_desc_out_jack_1in_t) == 7 + 2 * 1, "size is not correct"); /// MIDI Element Descriptor with multiple pins #define midi_desc_element_n_t(input_num) \ @@ -201,12 +170,32 @@ typedef struct TU_ATTR_PACKED uint8_t iElement; \ } -/** @} */ +// This descriptor follows the standard bulk data endpoint descriptor +#define midi_desc_cs_endpoint_n_t(jack_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bNumEmbMIDIJack; \ + uint8_t baAssocJackID[jack_num]; \ + } + +typedef midi_desc_cs_endpoint_n_t() midi_desc_cs_endpoint_t; // empty/flexible jack list +typedef midi_desc_cs_endpoint_n_t(1) midi_desc_cs_endpoint_1jack_t; + +TU_VERIFY_STATIC(sizeof(midi_desc_cs_endpoint_1jack_t) == 4+1, "size is not correct"); + +//--------------------------------------------------------------------+ +// For Internal Driver Use +//--------------------------------------------------------------------+ +typedef struct { + uint8_t buffer[4]; + uint8_t index; + uint8_t total; +} midi_driver_stream_t; #ifdef __cplusplus } #endif #endif - -/** @} */ diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c index c545f8287..0bbb3caf4 100644 --- a/src/class/midi/midi_device.c +++ b/src/class/midi/midi_device.c @@ -39,13 +39,6 @@ //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ - -typedef struct { - uint8_t buffer[4]; - uint8_t index; - uint8_t total; -} midid_stream_t; - typedef struct { uint8_t itf_num; uint8_t ep_in; @@ -54,8 +47,8 @@ typedef struct { // For Stream read()/write() API // Messages are always 4 bytes long, queue them for reading and writing so the // callers can use the Stream interface with single-byte read/write calls. - midid_stream_t stream_write; - midid_stream_t stream_read; + midi_driver_stream_t stream_write; + midi_driver_stream_t stream_read; /*------------- From this point, data is not cleared by bus reset -------------*/ // FIFO @@ -122,7 +115,7 @@ uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num) (void) cable_num; midid_interface_t* midi = &_midid_itf[itf]; - const midid_stream_t* stream = &midi->stream_read; + const midi_driver_stream_t* stream = &midi->stream_read; // when using with packet API stream total & index are both zero return tu_fifo_count(&midi->rx_ff) + (uint8_t) (stream->total - stream->index); @@ -136,7 +129,7 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void* buffer, ui uint8_t* buf8 = (uint8_t*) buffer; midid_interface_t* midi = &_midid_itf[itf]; - midid_stream_t* stream = &midi->stream_read; + midi_driver_stream_t* stream = &midi->stream_read; uint32_t total_read = 0; while( bufsize ) @@ -241,7 +234,7 @@ uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t* midid_interface_t* midi = &_midid_itf[itf]; TU_VERIFY(midi->ep_in, 0); - midid_stream_t* stream = &midi->stream_write; + midi_driver_stream_t* stream = &midi->stream_write; uint32_t i = 0; while ( (i < bufsize) && (tu_fifo_remaining(&midi->tx_ff) >= 4) ) @@ -429,21 +422,21 @@ void midid_reset(uint8_t rhport) } } -uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) -{ - // 1st Interface is Audio Control v1 - TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && - AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && - AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol, 0); +uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) { + uint16_t drv_len = 0; + uint8_t const * p_desc = (uint8_t const *)desc_itf; - uint16_t drv_len = tu_desc_len(desc_itf); - const uint8_t* p_desc = tu_desc_next(desc_itf); - - // Skip Class Specific descriptors - while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) - { - drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); + // 1st Interface is Audio Control v1 (optional) + if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { + drv_len = tu_desc_len(desc_itf); + p_desc = tu_desc_next(desc_itf); + // Skip Class Specific descriptors + while (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len) { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } } // 2nd Interface is MIDI Streaming diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c new file mode 100644 index 000000000..03144eb45 --- /dev/null +++ b/src/class/midi/midi_host.c @@ -0,0 +1,595 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_MIDI) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "midi_host.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_MIDI_LOG_LEVEL + #define CFG_TUH_MIDI_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t * desc_cb_data) { (void) idx; (void) desc_cb_data; } +TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) { (void) idx; (void) mount_cb_data; } +TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t idx) { (void) idx; } +TU_ATTR_WEAK void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; } +TU_ATTR_WEAK void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; } + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; // interface number of MIDI streaming + uint8_t itf_count; // number of interface including Audio Control + MIDI streaming + + uint8_t ep_in; // IN endpoint address + uint8_t ep_out; // OUT endpoint address + + uint8_t rx_cable_count; // IN endpoint CS descriptor bNumEmbMIDIJack value + uint8_t tx_cable_count; // OUT endpoint CS descriptor bNumEmbMIDIJack value + + #if CFG_TUH_MIDI_STREAM_API + // For Stream read()/write() API + // Messages are always 4 bytes long, queue them for reading and writing so the + // callers can use the Stream interface with single-byte read/write calls. + midi_driver_stream_t stream_write; + midi_driver_stream_t stream_read; + #endif + + // Endpoint stream + struct { + tu_edpt_stream_t tx; + tu_edpt_stream_t rx; + + uint8_t rx_ff_buf[CFG_TUH_MIDI_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUH_MIDI_TX_BUFSIZE]; + } ep_stream; + + bool mounted; +}midih_interface_t; + +typedef struct { + TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MPS); + TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MPS); +} midih_epbuf_t; + +static midih_interface_t _midi_host[CFG_TUH_MIDI]; +CFG_TUH_MEM_SECTION static midih_epbuf_t _midi_epbuf[CFG_TUH_MIDI]; + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint8_t find_new_midi_index(void) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + if (_midi_host[idx].daddr == 0) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + const midih_interface_t *p_midi = &_midi_host[idx]; + if ((p_midi->daddr == daddr) && + (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr)) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +//--------------------------------------------------------------------+ +// USBH API +//--------------------------------------------------------------------+ +bool midih_init(void) { + tu_memclr(&_midi_host, sizeof(_midi_host)); + for (int inst = 0; inst < CFG_TUH_MIDI; inst++) { + midih_interface_t *p_midi_host = &_midi_host[inst]; + tu_edpt_stream_init(&p_midi_host->ep_stream.rx, true, false, false, + p_midi_host->ep_stream.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, _midi_epbuf->rx, TUH_EPSIZE_BULK_MPS); + tu_edpt_stream_init(&p_midi_host->ep_stream.tx, true, true, false, + p_midi_host->ep_stream.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, _midi_epbuf->tx, TUH_EPSIZE_BULK_MPS); + } + return true; +} + +bool midih_deinit(void) { + for (size_t i = 0; i < CFG_TUH_MIDI; i++) { + midih_interface_t* p_midi = &_midi_host[i]; + tu_edpt_stream_deinit(&p_midi->ep_stream.rx); + tu_edpt_stream_deinit(&p_midi->ep_stream.tx); + } + return true; +} + +void midih_close(uint8_t daddr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + midih_interface_t* p_midi = &_midi_host[idx]; + if (p_midi->daddr == daddr) { + TU_LOG_DRV(" MIDI close addr = %u index = %u\r\n", daddr, idx); + tuh_midi_umount_cb(idx); + + p_midi->ep_in = 0; + p_midi->ep_out = 0; + p_midi->bInterfaceNumber = 0; + p_midi->rx_cable_count = 0; + p_midi->tx_cable_count = 0; + p_midi->daddr = 0; + p_midi->mounted = false; + tu_memclr(&p_midi->stream_read, sizeof(p_midi->stream_read)); + tu_memclr(&p_midi->stream_write, sizeof(p_midi->stream_write)); + + tu_edpt_stream_close(&p_midi->ep_stream.rx); + tu_edpt_stream_close(&p_midi->ep_stream.tx); + } + } +} + +bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + const uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr); + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + + if (ep_addr == p_midi->ep_stream.rx.ep_addr) { + // receive new data, put it into FIFO and invoke callback if available + // Note: some devices send back all zero packets even if there is no data ready + if (xferred_bytes && !tu_mem_is_zero(p_midi->ep_stream.rx.ep_buf, xferred_bytes)) { + tu_edpt_stream_read_xfer_complete(&p_midi->ep_stream.rx, xferred_bytes); + tuh_midi_rx_cb(idx, xferred_bytes); + } + + tu_edpt_stream_read_xfer(dev_addr, &p_midi->ep_stream.rx); // prepare for next transfer + } else if (ep_addr == p_midi->ep_stream.tx.ep_addr) { + tuh_midi_tx_cb(idx, xferred_bytes); + + if (0 == tu_edpt_stream_write_xfer(dev_addr, &p_midi->ep_stream.tx)) { + // If there is no data left, a ZLP should be sent if + // xferred_bytes is multiple of EP size and not zero + tu_edpt_stream_write_zlp_if_needed(dev_addr, &p_midi->ep_stream.tx, xferred_bytes); + } + } + + return true; +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ +bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) { + (void) rhport; + + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); + const uint8_t *p_end = ((const uint8_t *) desc_itf) + max_len; + const uint8_t *p_desc = (const uint8_t *) desc_itf; + + const uint8_t idx = find_new_midi_index(); + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + p_midi->itf_count = 0; + + tuh_midi_descriptor_cb_t desc_cb = { 0 }; + desc_cb.jack_num = 0; + + // There can be just a MIDI or an Audio + MIDI interface + // If there is Audio Control Interface + Audio Header descriptor, skip it + if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) { + TU_VERIFY(max_len > 2*sizeof(tusb_desc_interface_t) + sizeof(audio_desc_cs_ac_interface_t)); + + p_desc = tu_desc_next(p_desc); + TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && + tu_desc_subtype(p_desc) == AUDIO_CS_AC_INTERFACE_HEADER); + desc_cb.desc_audio_control = desc_itf; + + p_desc = tu_desc_next(p_desc); + desc_itf = (const tusb_desc_interface_t *)p_desc; + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); + p_midi->itf_count = 1; + } + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass); + + TU_LOG_DRV("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr); + p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber; + p_midi->itf_count++; + desc_cb.desc_midi = desc_itf; + + p_desc = tu_desc_next(p_desc); // next to CS Header + + bool found_new_interface = false; + while ((p_desc < p_end) && (tu_desc_next(p_desc) <= p_end) && !found_new_interface) { + switch (tu_desc_type(p_desc)) { + case TUSB_DESC_INTERFACE: + found_new_interface = true; + break; + + case TUSB_DESC_CS_INTERFACE: + switch (tu_desc_subtype(p_desc)) { + case MIDI_CS_INTERFACE_HEADER: + TU_LOG_DRV(" Interface Header descriptor\r\n"); + desc_cb.desc_header = p_desc; + break; + + case MIDI_CS_INTERFACE_IN_JACK: + case MIDI_CS_INTERFACE_OUT_JACK: { + TU_LOG_DRV(" Jack %s %s descriptor \r\n", + tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_IN_JACK ? "IN" : "OUT", + p_desc[3] == MIDI_JACK_EXTERNAL ? "External" : "Embedded"); + desc_cb.desc_jack[desc_cb.jack_num++] = p_desc; + break; + } + + case MIDI_CS_INTERFACE_ELEMENT: + TU_LOG_DRV(" Element descriptor\r\n"); + desc_cb.desc_element = p_desc; + break; + + default: + TU_LOG_DRV(" Unknown CS Interface sub-type %u\r\n", tu_desc_subtype(p_desc)); + break; + } + break; + + case TUSB_DESC_ENDPOINT: { + const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; + p_desc = tu_desc_next(p_desc); // next to CS endpoint + TU_VERIFY(p_desc < p_end && tu_desc_next(p_desc) <= p_end); + const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc; + + TU_LOG_DRV(" Endpoint and CS_Endpoint descriptor %02x\r\n", p_ep->bEndpointAddress); + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { + p_midi->ep_out = p_ep->bEndpointAddress; + p_midi->tx_cable_count = p_csep->bNumEmbMIDIJack; + desc_cb.desc_epout = p_ep; + + TU_ASSERT(tuh_edpt_open(dev_addr, p_ep)); + tu_edpt_stream_open(&p_midi->ep_stream.tx, p_ep); + } else { + p_midi->ep_in = p_ep->bEndpointAddress; + p_midi->rx_cable_count = p_csep->bNumEmbMIDIJack; + desc_cb.desc_epin = p_ep; + + TU_ASSERT(tuh_edpt_open(dev_addr, p_ep)); + tu_edpt_stream_open(&p_midi->ep_stream.rx, p_ep); + } + break; + } + + default: break; // skip unknown descriptor + } + p_desc = tu_desc_next(p_desc); + } + desc_cb.desc_midi_total_len = (uint16_t) ((uintptr_t)p_desc - (uintptr_t) desc_itf); + + p_midi->daddr = dev_addr; + tuh_midi_descriptor_cb(idx, &desc_cb); + + return true; +} + +bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) { + uint8_t idx = tuh_midi_itf_get_index(dev_addr, itf_num); + TU_ASSERT(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + p_midi->mounted = true; + + const tuh_midi_mount_cb_t mount_cb_data = { + .daddr = dev_addr, + .bInterfaceNumber = itf_num, + .rx_cable_count = p_midi->rx_cable_count, + .tx_cable_count = p_midi->tx_cable_count, + }; + tuh_midi_mount_cb(idx, &mount_cb_data); + + tu_edpt_stream_read_xfer(dev_addr, &p_midi->ep_stream.rx); // prepare for incoming data + + // No special config things to do for MIDI + usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber); + return true; +} + +//--------------------------------------------------------------------+ +// API +//--------------------------------------------------------------------+ +bool tuh_midi_mounted(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return p_midi->mounted; +} + +uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + const midih_interface_t *p_midi = &_midi_host[idx]; + if (p_midi->daddr == daddr && + (p_midi->bInterfaceNumber == itf_num || + p_midi->bInterfaceNumber == (uint8_t) (itf_num + p_midi->itf_count - 1))) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +uint8_t tuh_midi_get_tx_cable_count (uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi->ep_stream.tx.ep_addr != 0, 0); + return p_midi->tx_cable_count; +} + +uint8_t tuh_midi_get_rx_cable_count (uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi->ep_stream.rx.ep_addr != 0, 0); + return p_midi->rx_cable_count; +} + +uint32_t tuh_midi_read_available(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return tu_edpt_stream_read_available(&p_midi->ep_stream.rx); +} + +uint32_t tuh_midi_write_flush(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return tu_edpt_stream_write_xfer(p_midi->daddr, &p_midi->ep_stream.tx); +} + +//--------------------------------------------------------------------+ +// Packet API +//--------------------------------------------------------------------+ +uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0); + midih_interface_t *p_midi = &_midi_host[idx]; + + uint32_t count4 = tu_min32(bufsize, tu_edpt_stream_read_available(&p_midi->ep_stream.rx)); + count4 = tu_align4(count4); // round down to multiple of 4 + TU_VERIFY(count4 > 0, 0); + return tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, buffer, count4); +} + +uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0); + midih_interface_t *p_midi = &_midi_host[idx]; + + const uint32_t bufsize4 = tu_align4(bufsize); + TU_VERIFY(bufsize4 > 0, 0); + return tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, buffer, bufsize4); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ +#if CFG_TUH_MIDI_STREAM_API +uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(cable_num < p_midi->tx_cable_count); + midi_driver_stream_t *stream = &p_midi->stream_write; + + uint32_t byte_count = 0; + while ((byte_count < bufsize) && (tu_edpt_stream_write_available(p_midi->daddr, &p_midi->ep_stream.tx) >= 4)) { + uint8_t const data = buffer[byte_count]; + byte_count++; + if (data >= MIDI_STATUS_SYSREAL_TIMING_CLOCK) { + // real-time messages need to be sent right away + midi_driver_stream_t streamrt; + streamrt.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + streamrt.buffer[1] = data; + streamrt.index = 2; + streamrt.total = 2; + uint32_t const count = tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, streamrt.buffer, 4); + TU_ASSERT(count == 4, byte_count); // Check FIFO overflown, since we already check fifo remaining. It is probably race condition + } else if (stream->index == 0) { + //------------- New event packet -------------// + + uint8_t const msg = data >> 4; + + stream->index = 2; + stream->buffer[1] = data; + + // Check to see if we're still in a SysEx transmit. + if (stream->buffer[0] == MIDI_CIN_SYSEX_START) { + if (data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } else { + stream->total = 4; + } + } else if ((msg >= 0x8 && msg <= 0xB) || msg == 0xE) { + // Channel Voice Messages + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 4; + } else if (msg == 0xC || msg == 0xD) { + // Channel Voice Messages, two-byte variants (Program Change and Channel Pressure) + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 3; + } else if (msg == 0xf) { + // System message + if (data == MIDI_STATUS_SYSEX_START) { + stream->buffer[0] = MIDI_CIN_SYSEX_START; + stream->total = 4; + } else if (data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT) { + stream->buffer[0] = MIDI_CIN_SYSCOM_2BYTE; + stream->total = 3; + } else if (data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER) { + stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE; + stream->total = 4; + } else { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } + } else { + // Pack individual bytes if we don't support packing them into words. + stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf); + stream->buffer[2] = 0; + stream->buffer[3] = 0; + stream->index = 2; + stream->total = 2; + } + } else { + //------------- On-going (buffering) packet -------------// + TU_ASSERT(stream->index < 4, byte_count); + stream->buffer[stream->index] = data; + stream->index++; + // See if this byte ends a SysEx. + if (stream->buffer[0] == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = MIDI_CIN_SYSEX_START + (stream->index - 1); + stream->total = stream->index; + } + } + + // Send out packet + if (stream->index >= 2 && stream->index == stream->total) { + // zeroes unused bytes + for (uint8_t i = stream->total; i < 4; i++) { + stream->buffer[i] = 0; + } + TU_LOG3_MEM(stream->buffer, 4, 2); + + const uint32_t count = tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, stream->buffer, 4); + + // complete current event packet, reset stream + stream->index = 0; + stream->total = 0; + + // FIFO overflown, since we already check fifo remaining. It is probably race condition + TU_ASSERT(count == 4, byte_count); + } + } + return byte_count; +} + +uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && p_cable_num && p_buffer && bufsize > 0); + midih_interface_t *p_midi = &_midi_host[idx]; + uint32_t bytes_buffered = 0; + uint8_t one_byte; + if (!tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) { + return 0; + } + *p_cable_num = (one_byte >> 4) & 0xf; + uint32_t nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + static uint16_t cable_sysex_in_progress;// bit i is set if received MIDI_STATUS_SYSEX_START but not MIDI_STATUS_SYSEX_END + while (nread == 4 && bytes_buffered < bufsize) { + *p_cable_num = (p_midi->stream_read.buffer[0] >> 4) & 0x0f; + uint8_t bytes_to_add_to_stream = 0; + if (*p_cable_num < p_midi->rx_cable_count) { + // ignore the CIN field; too many devices out there encode this wrong + uint8_t status = p_midi->stream_read.buffer[1]; + uint16_t cable_mask = (uint16_t) (1 << *p_cable_num); + if (status <= MIDI_MAX_DATA_VAL || status == MIDI_STATUS_SYSEX_START) { + if (status == MIDI_STATUS_SYSEX_START) { + cable_sysex_in_progress |= cable_mask; + } + // only add the packet if a sysex message is in progress + if (cable_sysex_in_progress & cable_mask) { + ++bytes_to_add_to_stream; + for (uint8_t i = 2; i < 4; i++) { + if (p_midi->stream_read.buffer[i] <= MIDI_MAX_DATA_VAL) { + ++bytes_to_add_to_stream; + } else if (p_midi->stream_read.buffer[i] == MIDI_STATUS_SYSEX_END) { + ++bytes_to_add_to_stream; + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + i = 4;// force the loop to exit; I hate break statements in loops + } + } + } + } else if (status < MIDI_STATUS_SYSEX_START) { + // then it is a channel message either three bytes or two + uint8_t fake_cin = (status & 0xf0) >> 4; + switch (fake_cin) { + case MIDI_CIN_NOTE_OFF: + case MIDI_CIN_NOTE_ON: + case MIDI_CIN_POLY_KEYPRESS: + case MIDI_CIN_CONTROL_CHANGE: + case MIDI_CIN_PITCH_BEND_CHANGE: + bytes_to_add_to_stream = 3; + break; + case MIDI_CIN_PROGRAM_CHANGE: + case MIDI_CIN_CHANNEL_PRESSURE: + bytes_to_add_to_stream = 2; + break; + default: + break;// Should not get this + } + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + } else if (status < MIDI_STATUS_SYSREAL_TIMING_CLOCK) { + switch (status) { + case MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME: + case MIDI_STATUS_SYSCOM_SONG_SELECT: + bytes_to_add_to_stream = 2; + break; + case MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER: + bytes_to_add_to_stream = 3; + break; + case MIDI_STATUS_SYSCOM_TUNE_REQUEST: + case MIDI_STATUS_SYSEX_END: + bytes_to_add_to_stream = 1; + break; + default: + break; + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + } + } else { + // Real-time message: can be inserted into a sysex message, + // so do don't clear cable_sysex_in_progress bit + bytes_to_add_to_stream = 1; + } + } + + for (uint8_t i = 1; i <= bytes_to_add_to_stream; i++) { + *p_buffer++ = p_midi->stream_read.buffer[i]; + } + bytes_buffered += bytes_to_add_to_stream; + nread = 0; + if (tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) { + uint8_t new_cable = (one_byte >> 4) & 0xf; + if (new_cable == *p_cable_num) { + // still on the same cable. Continue reading the stream + nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + } + } + } + + return bytes_buffered; +} +#endif + +#endif diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h new file mode 100644 index 000000000..b06bb43d7 --- /dev/null +++ b/src/class/midi/midi_host.h @@ -0,0 +1,189 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MIDI_HOST_H_ +#define TUSB_MIDI_HOST_H_ + +#include "class/audio/audio.h" +#include "midi.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUH_MIDI_RX_BUFSIZE +#define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +#ifndef CFG_TUH_MIDI_TX_BUFSIZE +#define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +#ifndef CFG_TUH_MIDI_EP_BUFSIZE +#define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +// Enable the MIDI stream read/write API. Some library can work with raw USB MIDI packet +// Disable this can save driver footprint. +#ifndef CFG_TUH_MIDI_STREAM_API +#define CFG_TUH_MIDI_STREAM_API 1 +#endif + +//--------------------------------------------------------------------+ +// Application Types +//--------------------------------------------------------------------+ +typedef struct { + const tusb_desc_interface_t* desc_audio_control; + const tusb_desc_interface_t* desc_midi; // start of whole midi interface descriptor + uint16_t desc_midi_total_len; + + const uint8_t* desc_header; + const uint8_t* desc_element; + const tusb_desc_endpoint_t* desc_epin; // endpoint IN descriptor, CS_ENDPOINT is right after + const tusb_desc_endpoint_t* desc_epout; // endpoint OUT descriptor, CS_ENDPOINT is right after + + uint8_t jack_num; + const uint8_t* desc_jack[16]; // list of jack descriptors (embedded + external) +} tuh_midi_descriptor_cb_t; + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; // interface number of MIDI streaming + uint8_t rx_cable_count; + uint8_t tx_cable_count; +} tuh_midi_mount_cb_t; + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Check if MIDI interface is mounted +bool tuh_midi_mounted(uint8_t idx); + +// Get Interface index from device address + interface number +// return TUSB_INDEX_INVALID_8 (0xFF) if not found +uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num); + +// return the number of virtual midi cables on the device's IN endpoint +uint8_t tuh_midi_get_rx_cable_count(uint8_t idx); + +// return the number of virtual midi cables on the device's OUT endpoint +uint8_t tuh_midi_get_tx_cable_count(uint8_t idx); + +// return the raw number of bytes available. +// Note: this is related but not the same as number of stream bytes available. +uint32_t tuh_midi_read_available(uint8_t idx); + +// Send any queued packets to the device if the host hardware is able to do it +// Returns the number of bytes flushed to the host hardware or 0 if +// the host hardware is busy or there is nothing in queue to send. +uint32_t tuh_midi_write_flush(uint8_t idx); + +//--------------------------------------------------------------------+ +// Packet API +//--------------------------------------------------------------------+ + +// Read all available MIDI packets from the connected device +// Return number of bytes read (always multiple of 4) +uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize); + +// Read a raw MIDI packet from the connected device +// Return true if a packet was returned +TU_ATTR_ALWAYS_INLINE static inline +bool tuh_midi_packet_read (uint8_t idx, uint8_t packet[4]) { + return 4 == tuh_midi_packet_read_n(idx, packet, 4); +} + +// Write all 4-byte packets, data is locally buffered and only transferred when buffered bytes +// reach the endpoint packet size or tuh_midi_write_flush() is called +uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize); + +// Write a 4-bytes packet to the device. +// Returns true if the packet was successfully queued. +TU_ATTR_ALWAYS_INLINE static inline +bool tuh_midi_packet_write (uint8_t idx, uint8_t const packet[4]) { + return 4 == tuh_midi_packet_write_n(idx, packet, 4); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ +#if CFG_TUH_MIDI_STREAM_API + +// Queue a message to the device using stream API. data is locally buffered and only transferred when buffered bytes +// reach the endpoint packet size or tuh_midi_write_flush() is called +// Returns number of bytes was successfully queued. +uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *p_buffer, uint32_t bufsize); + +// Get the MIDI stream from the device. Set the value pointed +// to by p_cable_num to the MIDI cable number intended to receive it. +// The MIDI stream will be stored in the buffer pointed to by p_buffer. +// Return the number of bytes added to the buffer. +// Note that this function ignores the CIN field of the MIDI packet +// because a number of commercial devices out there do not encode +// it properly. +uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize); + +#endif + +//--------------------------------------------------------------------+ +// Callbacks (Weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when MIDI interface is detected in enumeration. Application can copy/parse descriptor if needed. +// Note: may be fired before tuh_midi_mount_cb(), therefore midi interface is not mounted/ready. +void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t * desc_cb_data); + +// Invoked when device with MIDI interface is mounted. +void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data); + +// Invoked when device with MIDI interface is un-mounted +void tuh_midi_umount_cb(uint8_t idx); + +// Invoked when received new data +void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes); + +// Invoked when a TX is complete and therefore space becomes available in TX buffer +void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool midih_init (void); +bool midih_deinit (void); +bool midih_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len); +bool midih_set_config (uint8_t dev_addr, uint8_t itf_num); +bool midih_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void midih_close (uint8_t daddr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/common/tusb_common.h b/src/common/tusb_common.h index 74cac965d..0351a3d8f 100644 --- a/src/common/tusb_common.h +++ b/src/common/tusb_common.h @@ -120,6 +120,22 @@ TU_ATTR_ALWAYS_INLINE static inline int tu_memcpy_s(void *dest, size_t destsz, c return 0; } +TU_ATTR_ALWAYS_INLINE static inline bool tu_mem_is_zero(const void *buffer, size_t size) { + const uint8_t* buf8 = (const uint8_t*) buffer; + for (size_t i = 0; i < size; i++) { + if (buf8[i] != 0) { return false; } + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_mem_is_ff(const void *buffer, size_t size) { + const uint8_t* buf8 = (const uint8_t*) buffer; + for (size_t i = 0; i < size; i++) { + if (buf8[i] != 0xff) { return false; } + } + return true; +} + //------------- Bytes -------------// TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_u32(uint8_t b3, uint8_t b2, uint8_t b1, uint8_t b0) { @@ -181,8 +197,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_round_up(uint32_t v, uint32_t f) // log2 of a value is its MSB's position // TODO use clz TODO remove -static inline uint8_t tu_log2(uint32_t value) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_log2(uint32_t value) { uint8_t result = 0; while (value >>= 1) { result++; } return result; @@ -193,8 +208,7 @@ static inline uint8_t tu_log2(uint32_t value) // return sizeof(uint32_t) * CHAR_BIT - __builtin_clz(x) - 1; //} -static inline bool tu_is_power_of_two(uint32_t value) -{ +TU_ATTR_ALWAYS_INLINE static inline bool tu_is_power_of_two(uint32_t value) { return (value != 0) && ((value & (value - 1)) == 0); } @@ -205,27 +219,23 @@ static inline bool tu_is_power_of_two(uint32_t value) typedef struct { uint16_t val; } TU_ATTR_PACKED tu_unaligned_uint16_t; typedef struct { uint32_t val; } TU_ATTR_PACKED tu_unaligned_uint32_t; -TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void* mem) -{ - tu_unaligned_uint32_t const* ua32 = (tu_unaligned_uint32_t const*) mem; +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void *mem) { + tu_unaligned_uint32_t const *ua32 = (tu_unaligned_uint32_t const *) mem; return ua32->val; } -TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void* mem, uint32_t value) -{ - tu_unaligned_uint32_t* ua32 = (tu_unaligned_uint32_t*) mem; +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void *mem, uint32_t value) { + tu_unaligned_uint32_t *ua32 = (tu_unaligned_uint32_t *) mem; ua32->val = value; } -TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void* mem) -{ - tu_unaligned_uint16_t const* ua16 = (tu_unaligned_uint16_t const*) mem; +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void *mem) { + tu_unaligned_uint16_t const *ua16 = (tu_unaligned_uint16_t const *) mem; return ua16->val; } -TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void* mem, uint16_t value) -{ - tu_unaligned_uint16_t* ua16 = (tu_unaligned_uint16_t*) mem; +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void *mem, uint16_t value) { + tu_unaligned_uint16_t *ua16 = (tu_unaligned_uint16_t *) mem; ua16->val = value; } diff --git a/src/common/tusb_private.h b/src/common/tusb_private.h index c71775abb..31aca8a31 100644 --- a/src/common/tusb_private.h +++ b/src/common/tusb_private.h @@ -67,7 +67,7 @@ typedef struct { //--------------------------------------------------------------------+ // Check if endpoint descriptor is valid per USB specs -bool tu_edpt_validate(tusb_desc_endpoint_t const * desc_ep, tusb_speed_t speed); +bool tu_edpt_validate(tusb_desc_endpoint_t const * desc_ep, tusb_speed_t speed, bool is_host); // Bind all endpoint of a interface descriptor to class driver void tu_edpt_bind_driver(uint8_t ep2drv[][2], tusb_desc_interface_t const* p_desc, uint16_t desc_len, uint8_t driver_id); @@ -138,7 +138,7 @@ uint32_t tu_edpt_stream_read(uint8_t hwid, tu_edpt_stream_t* s, void* buffer, ui // Start an usb transfer if endpoint is not busy uint32_t tu_edpt_stream_read_xfer(uint8_t hwid, tu_edpt_stream_t* s); -// Must be called in the transfer complete callback +// Complete read transfer by writing EP -> FIFO. Must be called in the transfer complete callback TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_read_xfer_complete(tu_edpt_stream_t* s, uint32_t xferred_bytes) { if (tu_fifo_depth(&s->ff)) { @@ -146,11 +146,11 @@ void tu_edpt_stream_read_xfer_complete(tu_edpt_stream_t* s, uint32_t xferred_byt } } -// Same as tu_edpt_stream_read_xfer_complete but skip the first n bytes +// Complete read transfer with provided buffer TU_ATTR_ALWAYS_INLINE static inline -void tu_edpt_stream_read_xfer_complete_offset(tu_edpt_stream_t* s, uint32_t xferred_bytes, uint32_t skip_offset) { - if (tu_fifo_depth(&s->ff) && (skip_offset < xferred_bytes)) { - tu_fifo_write_n(&s->ff, s->ep_buf + skip_offset, (uint16_t) (xferred_bytes - skip_offset)); +void tu_edpt_stream_read_xfer_complete_with_buf(tu_edpt_stream_t* s, const void * buf, uint32_t xferred_bytes) { + if (tu_fifo_depth(&s->ff)) { + tu_fifo_write_n(&s->ff, buf, (uint16_t) xferred_bytes); } } diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index 71af132af..e000a4bd3 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -281,7 +281,8 @@ typedef enum { // TODO remove enum { DESC_OFFSET_LEN = 0, - DESC_OFFSET_TYPE = 1 + DESC_OFFSET_TYPE = 1, + DESC_OFFSET_SUBTYPE = 2 }; enum { @@ -570,14 +571,19 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t const * tu_desc_next(void const* des return desc8 + desc8[DESC_OFFSET_LEN]; } +// get descriptor length +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; +} + // get descriptor type TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_type(void const* desc) { return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; } -// get descriptor length -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) { - return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; +// get descriptor subtype +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_subtype(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_SUBTYPE]; } // find descriptor that match byte1 (type) diff --git a/src/device/usbd.c b/src/device/usbd.c index 2a6081673..fb5cec49d 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -1032,7 +1032,14 @@ static bool process_set_config(uint8_t rhport, uint8_t cfg_num) #endif #if CFG_TUD_MIDI - if ( driver->open == midid_open ) assoc_itf_count = 2; + if (driver->open == midid_open) { + // If there is a class-compliant Audio Control Class, then 2 interfaces. Otherwise, only one + if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { + assoc_itf_count = 2; + } + } #endif #if CFG_TUD_BTH && CFG_TUD_BTH_ISO_ALT_COUNT @@ -1289,7 +1296,7 @@ bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { rhport = _usbd_rhport; TU_ASSERT(tu_edpt_number(desc_ep->bEndpointAddress) < CFG_TUD_ENDPPOINT_MAX); - TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) _usbd_dev.speed)); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) _usbd_dev.speed, false)); return dcd_edpt_open(rhport, desc_ep); } @@ -1490,7 +1497,7 @@ bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) uint8_t const dir = tu_edpt_dir(desc_ep->bEndpointAddress); TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX); - TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) _usbd_dev.speed)); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) _usbd_dev.speed, false)); _usbd_dev.ep_status[epnum][dir].stalled = 0; _usbd_dev.ep_status[epnum][dir].busy = 0; diff --git a/src/host/usbh.c b/src/host/usbh.c index dcd22ee31..c87f058cd 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -192,6 +192,18 @@ static usbh_class_driver_t const usbh_class_drivers[] = { }, #endif + #if CFG_TUH_MIDI + { + .name = DRIVER_NAME("MIDI"), + .init = midih_init, + .deinit = midih_deinit, + .open = midih_open, + .set_config = midih_set_config, + .xfer_cb = midih_xfer_cb, + .close = midih_close + }, + #endif + #if CFG_TUH_HUB { .name = DRIVER_NAME("HUB"), @@ -974,7 +986,7 @@ static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) { } bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const* desc_ep) { - TU_ASSERT(tu_edpt_validate(desc_ep, tuh_speed_get(dev_addr))); + TU_ASSERT(tu_edpt_validate(desc_ep, tuh_speed_get(dev_addr), true)); return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, desc_ep); } @@ -1588,6 +1600,8 @@ static void process_enumeration(tuh_xfer_t* xfer) { } case ENUM_SET_CONFIG: + // tuh_desc_configuration_cb(daddr, CONFIG_NUM-1, (const tusb_desc_configuration_t*) _usbh_epbuf.ctrl); + TU_ASSERT(tuh_configuration_set(daddr, CONFIG_NUM, process_enumeration, ENUM_CONFIG_DRIVER),); break; @@ -1740,7 +1754,7 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur if ( 0 == tu_desc_len(p_desc) ) { // A zero length descriptor indicates that the device is off spec (e.g. wrong wTotalLength). // Parsed interfaces should still be usable - TU_LOG_USBH("Encountered a zero-length descriptor after %" PRIu32 "bytes\r\n", (uint32_t)p_desc - (uint32_t)desc_cfg); + TU_LOG_USBH("Encountered a zero-length descriptor after %" PRIu32 " bytes\r\n", (uint32_t)p_desc - (uint32_t)desc_cfg); break; } diff --git a/src/host/usbh.h b/src/host/usbh.h index 0fbe39743..52b4f478c 100644 --- a/src/host/usbh.h +++ b/src/host/usbh.h @@ -37,6 +37,9 @@ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ +// Endpoint Bulk size depending on host mx speed +#define TUH_EPSIZE_BULK_MPS (TUD_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS) + // forward declaration struct tuh_xfer_s; typedef struct tuh_xfer_s tuh_xfer_t; @@ -96,6 +99,12 @@ typedef union { // APPLICATION CALLBACK //--------------------------------------------------------------------+ +// Invoked when enumeration get device descriptor +// TU_ATTR_WEAK void tuh_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device); + +// Invoked when enumeration get configuration descriptor +// TU_ATTR_WEAK void tuh_desc_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config); + // Invoked when a device is mounted (configured) TU_ATTR_WEAK void tuh_mount_cb (uint8_t daddr); diff --git a/src/host/usbh_pvt.h b/src/host/usbh_pvt.h index d27c4baa2..bfa1fb2ba 100644 --- a/src/host/usbh_pvt.h +++ b/src/host/usbh_pvt.h @@ -41,10 +41,6 @@ #define TU_LOG_INT_USBH(...) TU_LOG_INT(CFG_TUH_LOG_LEVEL, __VA_ARGS__) #define TU_LOG_HEX_USBH(...) TU_LOG_HEX(CFG_TUH_LOG_LEVEL, __VA_ARGS__) -enum { - USBH_EPSIZE_BULK_MAX = (TUH_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS) -}; - //--------------------------------------------------------------------+ // Class Driver API //--------------------------------------------------------------------+ diff --git a/src/tinyusb.mk b/src/tinyusb.mk index 89ea0212c..a9f623c24 100644 --- a/src/tinyusb.mk +++ b/src/tinyusb.mk @@ -21,6 +21,7 @@ TINYUSB_SRC_C += \ src/host/hub.c \ src/class/cdc/cdc_host.c \ src/class/hid/hid_host.c \ + src/class/midi/midi_host.c \ src/class/msc/msc_host.c \ src/class/vendor/vendor_host.c \ src/typec/usbc.c \ diff --git a/src/tusb.c b/src/tusb.c index 85ab1d6ae..13b89997c 100644 --- a/src/tusb.c +++ b/src/tusb.c @@ -142,7 +142,9 @@ void tusb_int_handler(uint8_t rhport, bool in_isr) { uint8_t const* tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1) { while (desc + 1 < end) { - if (desc[1] == byte1) return desc; + if (desc[1] == byte1) { + return desc; + } desc += desc[DESC_OFFSET_LEN]; } return NULL; @@ -150,7 +152,9 @@ uint8_t const* tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byt uint8_t const* tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2) { while (desc + 2 < end) { - if (desc[1] == byte1 && desc[2] == byte2) return desc; + if (desc[1] == byte1 && desc[2] == byte2) { + return desc; + } desc += desc[DESC_OFFSET_LEN]; } return NULL; @@ -158,7 +162,9 @@ uint8_t const* tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t by uint8_t const* tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3) { while (desc + 3 < end) { - if (desc[1] == byte1 && desc[2] == byte2 && desc[3] == byte3) return desc; + if (desc[1] == byte1 && desc[2] == byte2 && desc[3] == byte3) { + return desc; + } desc += desc[DESC_OFFSET_LEN]; } return NULL; @@ -199,7 +205,7 @@ bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { return ret; } -bool tu_edpt_validate(tusb_desc_endpoint_t const* desc_ep, tusb_speed_t speed) { +bool tu_edpt_validate(tusb_desc_endpoint_t const* desc_ep, tusb_speed_t speed, bool is_host) { uint16_t const max_packet_size = tu_edpt_packet_size(desc_ep); TU_LOG2(" Open EP %02X with Size = %u\r\n", desc_ep->bEndpointAddress, max_packet_size); @@ -215,8 +221,17 @@ bool tu_edpt_validate(tusb_desc_endpoint_t const* desc_ep, tusb_speed_t speed) { // Bulk highspeed must be EXACTLY 512 TU_ASSERT(max_packet_size == 512); } else { - // TODO Bulk fullspeed can only be 8, 16, 32, 64 - TU_ASSERT(max_packet_size <= 64); + // Bulk fullspeed can only be 8, 16, 32, 64 + if (is_host && max_packet_size == 512) { + // HACK: while in host mode, some device incorrectly always report 512 regardless of link speed + // overwrite descriptor to force 64 + TU_LOG1(" WARN: EP max packet size is 512 in fullspeed, force to 64\r\n"); + tusb_desc_endpoint_t* hacked_ep = (tusb_desc_endpoint_t*) (uintptr_t) desc_ep; + hacked_ep->wMaxPacketSize = tu_htole16(64); + } else { + TU_ASSERT(max_packet_size == 8 || max_packet_size == 16 || + max_packet_size == 32 || max_packet_size == 64); + } } break; diff --git a/src/tusb.h b/src/tusb.h index cb6021b33..dfba21ddf 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -59,6 +59,10 @@ #include "class/cdc/cdc_host.h" #endif + #if CFG_TUH_MIDI + #include "class/midi/midi_host.h" + #endif + #if CFG_TUH_VENDOR #include "class/vendor/vendor_host.h" #endif