Merge pull request #1627 from atoktoto/midihost

RP2040 MIDI Host
This commit is contained in:
Ha Thach
2025-03-09 19:01:10 +07:00
committed by GitHub
30 changed files with 1415 additions and 169 deletions

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
{

View File

@@ -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
//--------------------------------------------------------------------+

View File

@@ -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.

View File

@@ -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
/** @} */

View File

@@ -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

595
src/class/midi/midi_host.c Normal file
View File

@@ -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

189
src/class/midi/midi_host.h Normal file
View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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
//--------------------------------------------------------------------+

View File

@@ -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 \

View File

@@ -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;

View File

@@ -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