From a252bb83859fdc66e3d405eff5c48189c42f663a Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 20:46:46 -0800 Subject: [PATCH 01/35] create README_midi_host.md --- src/class/midi/README_midi_host.md | 111 +++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/class/midi/README_midi_host.md diff --git a/src/class/midi/README_midi_host.md b/src/class/midi/README_midi_host.md new file mode 100644 index 0000000000..9df2249b39 --- /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 informaton 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 endpont 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. From 17bcd39d5f80bcb884859ab4086c7e671accec45 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 20:50:48 -0800 Subject: [PATCH 02/35] Make pedantic change to MS ENDPOINT --- src/class/midi/midi.h | 4 ++-- src/device/usbd.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h index 74dc41749b..efce052517 100644 --- a/src/class/midi/midi.h +++ b/src/class/midi/midi.h @@ -52,8 +52,8 @@ typedef enum typedef enum { - MIDI_CS_ENDPOINT_GENERAL = 0x01 -} midi_cs_endpoint_subtype_t; + MIDI_MS_ENDPOINT_GENERAL = 0x01 +} midi_ms_endpoint_subtype_t; typedef enum { diff --git a/src/device/usbd.h b/src/device/usbd.h index ec34817fa4..99c3ce1818 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -311,7 +311,7 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb /* Endpoint: Note Audio v1.0's endpoint has 9 bytes instead of 7 */\ 9, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, 0, 0, \ /* MS Endpoint (connected to embedded jack) */\ - (uint8_t)(4 + (_numcables)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL, _numcables + (uint8_t)(4 + (_numcables)), TUSB_DESC_CS_ENDPOINT, MIDI_MS_ENDPOINT_GENERAL, _numcables // Length of template descriptor (88 bytes) #define TUD_MIDI_DESC_LEN (TUD_MIDI_DESC_HEAD_LEN + TUD_MIDI_DESC_JACK_LEN + TUD_MIDI_DESC_EP_LEN(1) * 2) From 9f928b05485cea61b17aa6a9de8f06b734343df4 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 20:51:35 -0800 Subject: [PATCH 03/35] Fix encoding of the CIN field for note on and note off --- src/class/midi/midi.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h index efce052517..6ac32a7a56 100644 --- a/src/class/midi/midi.h +++ b/src/class/midi/midi.h @@ -71,8 +71,8 @@ typedef enum MIDI_CIN_SYSEX_END_1BYTE = 5, // SysEx ends with 1 data, or 1 byte system common message MIDI_CIN_SYSEX_END_2BYTE = 6, // SysEx ends with 2 data MIDI_CIN_SYSEX_END_3BYTE = 7, // SysEx ends with 3 data - MIDI_CIN_NOTE_ON = 8, - MIDI_CIN_NOTE_OFF = 9, + MIDI_CIN_NOTE_ON = 9, + MIDI_CIN_NOTE_OFF = 8, MIDI_CIN_POLY_KEYPRESS = 10, MIDI_CIN_CONTROL_CHANGE = 11, MIDI_CIN_PROGRAM_CHANGE = 12, From 326a8ef9867f483865567ea69cecf38b81fc07d7 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 20:52:39 -0800 Subject: [PATCH 04/35] Create midi_cs_desc_endpoint_t structure --- src/class/midi/midi.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h index 6ac32a7a56..57668e326c 100644 --- a/src/class/midi/midi.h +++ b/src/class/midi/midi.h @@ -201,6 +201,17 @@ typedef struct TU_ATTR_PACKED uint8_t iElement; \ } +// This descriptor follows the standard bulk data endpoint descriptor +typedef struct +{ + uint8_t bLength ; ///< Size of this descriptor in bytes (4+bNumEmbMIDIJack) + uint8_t bDescriptorType ; ///< Descriptor Type, must be CS_ENDPOINT + uint8_t bDescriptorSubType ; ///< Descriptor SubType, must be MS_GENERAL + uint8_t bNumEmbMIDIJack; ; ///< Number of embedded MIDI jacks associated with this endpoint + uint8_t baAssocJackID[]; ; ///< A list of associated jacks +} midi_cs_desc_endpoint_t; + + /** @} */ #ifdef __cplusplus From 319c19469528b9a5f4a5bae5ef59966151897fa2 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 20:58:23 -0800 Subject: [PATCH 05/35] Add support for bulk endpoints --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 110 +++++++++++++++++-- 1 file changed, 101 insertions(+), 9 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 3e80dd8720..4ef2f0c841 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -55,6 +55,9 @@ static_assert(PICO_USB_HOST_INTERRUPT_ENDPOINTS <= USB_MAX_ENDPOINTS, ""); static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS]; #define epx (ep_pool[0]) +// The epx endpoint is shared among control, bulk and isochronous endpoints +struct hw_endpoint *_current_epx_endpoint = &epx; + #define usb_hw_set hw_set_alias(usb_hw) #define usb_hw_clear hw_clear_alias(usb_hw) @@ -83,6 +86,16 @@ static inline uint8_t dev_speed(void) return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB; } +static inline void clear_nak_received(void) +{ + usb_hw_clear->sie_status = USB_SIE_STATUS_NAK_REC_BITS; +} + +static inline bool nak_received() +{ + return (usb_hw->sie_status & USB_SIE_STATUS_NAK_REC_BITS) != 0; +} + static bool need_pre(uint8_t dev_addr) { // If this device is different to the speed of the root device @@ -97,6 +110,12 @@ static void hw_xfer_complete(struct hw_endpoint *ep, xfer_result_t xfer_result) uint8_t ep_addr = ep->ep_addr; uint xferred_len = ep->xferred_len; hw_endpoint_reset_transfer(ep); + if (tu_edpt_number(ep_addr) != 0 && nak_received()) + { + TU_LOG2("NAK Received EP=%x\r\n", ep_addr); + clear_nak_received(); + xferred_len = 0; + } hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); } @@ -120,7 +139,7 @@ static void hw_handle_buff_status(void) if (remaining_buffers & bit) { remaining_buffers &= ~bit; - struct hw_endpoint *ep = &epx; + struct hw_endpoint *ep = _current_epx_endpoint; uint32_t ep_ctrl = *ep->endpoint_control; if (ep_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) @@ -213,7 +232,7 @@ static void hcd_rp2040_irq(void) if (status & USB_INTS_STALL_BITS) { - // We have rx'd a stall from the device + // We have rx'd a stall or a NAK from the device pico_trace("Stall REC\n"); handled |= USB_INTS_STALL_BITS; usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS; @@ -255,6 +274,21 @@ static struct hw_endpoint *_next_free_interrupt_ep(void) return ep; } +static struct hw_endpoint *_next_free_non_interrupt_ep(void) +{ + struct hw_endpoint *ep = NULL; + for (uint i = TU_ARRAY_SIZE(ep_pool)-1; i > 0 ; i--) + { + ep = &ep_pool[i]; + if (!ep->configured) + { + // Will be configured by _hw_endpoint_init / _hw_endpoint_allocate + return ep; + } + } + return ep; +} + static struct hw_endpoint *_hw_endpoint_allocate(uint8_t transfer_type) { struct hw_endpoint *ep = NULL; @@ -272,6 +306,15 @@ static struct hw_endpoint *_hw_endpoint_allocate(uint8_t transfer_type) // etc ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)]; } + else if (transfer_type == TUSB_XFER_BULK || transfer_type == TUSB_XFER_ISOCHRONOUS) + { + ep = _next_free_non_interrupt_ep(); + pico_info("Allocated non-interrupt ep\n"); + assert(ep); + ep->buffer_control = &usbh_dpram->epx_buf_ctrl; + ep->endpoint_control = &usbh_dpram->epx_ctrl; + ep->hw_data_buf = &usbh_dpram->epx_data[0]; + } else { ep = &epx; @@ -399,6 +442,9 @@ tusb_speed_t hcd_port_speed_get(uint8_t rhport) // TODO: Should enumval this register switch (dev_speed()) { + case 0: + // device is disconnected; we shouldn't panic + return TUSB_SPEED_INVALID; case 1: return TUSB_SPEED_LOW; case 2: @@ -478,12 +524,36 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const return true; } +// The epx register is shared among endpoints. Set it up with the parameters stored in epx register +static void _hw_endpoint_reinit_epx(struct hw_endpoint *ep) +{ + // Already has data buffer, endpoint control, and buffer control allocated at this point + assert(ep->endpoint_control); + assert(ep->buffer_control); + assert(ep->hw_data_buf); + assert(ep->configured); + pico_trace("hw_endpoint_init dev %d ep %d %s xfer %d\n", ep->dev_addr, tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)], ep->transfer_type); + pico_trace("dev %d ep %d %s setup buffer @ 0x%p\n", ep->dev_addr, tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)], ep->hw_data_buf); + uint dpram_offset = hw_data_offset(ep->hw_data_buf); + // Bits 0-5 should be 0 + assert(!(dpram_offset & 0b111111)); + + // Fill in endpoint control register with buffer offset + uint32_t ep_reg = EP_CTRL_ENABLE_BITS + | EP_CTRL_INTERRUPT_PER_BUFFER + | (ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) + | dpram_offset; + + *ep->endpoint_control = ep_reg; + pico_trace("endpoint control (0x%p) <- 0x%x\n", ep->endpoint_control, ep_reg); +} + bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { (void) rhport; pico_trace("hcd_edpt_xfer dev_addr %d, ep_addr 0x%x, len %d\n", dev_addr, ep_addr, buflen); - + uint8_t const ep_num = tu_edpt_number(ep_addr); tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); @@ -491,15 +561,37 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr); assert(ep); - // Control endpoint can change direction 0x00 <-> 0x80 - if ( ep_addr != ep->ep_addr ) + if ( ep_num == 0 ) { - assert(ep_num == 0); + // exp buffers might not have been used for endpoint 0 or control endpoint might have changed direction 0x00 <-> 0x80 + if ( _current_epx_endpoint != &epx || ep_addr != ep->ep_addr ) + { + // Re-init endpoint + _hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, ep->transfer_type, 0); + } + _current_epx_endpoint = &epx; + } - // Direction has flipped on endpoint control so re init it but with same properties - _hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, ep->transfer_type, 0); - } + bool is_bulk_or_isochronous = (ep != &epx && (ep->transfer_type == TUSB_XFER_BULK || ep->transfer_type == TUSB_XFER_ISOCHRONOUS)); + // the epx endpoint is shared among control, bulk and isochronous endpoints + if ( is_bulk_or_isochronous ) + { + if (ep != _current_epx_endpoint) + _hw_endpoint_reinit_epx(ep); + _current_epx_endpoint = ep; + hw_endpoint_xfer_start(ep, buffer, buflen); + // That has set up buffer control, endpoint control etc + // for host we have to initiate the transfer + usb_hw->dev_addr_ctrl = dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB); + + uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE | + (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); + // Set pre if we are a low speed device on full speed hub + flags |= need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0; + + usb_hw->sie_ctrl = flags; + }else // If a normal transfer (non-interrupt) then initiate using // sie ctrl registers. Otherwise interrupt ep registers should // already be configured From e2aaca6022e5697bfc8cf0e6ec129386b8dca77b Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 20:59:02 -0800 Subject: [PATCH 06/35] Create midi_host.c/h --- src/class/midi/midi_host.c | 798 +++++++++++++++++++++++++++++++++++++ src/class/midi/midi_host.h | 113 ++++++ 2 files changed, 911 insertions(+) create mode 100644 src/class/midi/midi_host.c create mode 100644 src/class/midi/midi_host.h diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c new file mode 100644 index 0000000000..2521142f64 --- /dev/null +++ b/src/class/midi/midi_host.c @@ -0,0 +1,798 @@ +/* + * 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 (TUSB_OPT_HOST_ENABLED && CFG_TUH_MIDI) + +#include "host/usbh.h" +#include "host/usbh_classdriver.h" + +#include "midi_host.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +#ifndef CFG_TUH_MAX_CABLES + #define CFG_TUH_MAX_CABLES 16 +#endif +#define CFG_TUH_MIDI_RX_BUFSIZE 64 +#define CFG_TUH_MIDI_TX_BUFSIZE 64 +#ifndef CFG_TUH_MIDI_EP_BUFSIZE + #define CFG_TUH_MIDI_EP_BUFSIZE 64 +#endif + +// TODO: refactor to share code with the MIDI Device driver +typedef struct +{ + uint8_t buffer[4]; + uint8_t index; + uint8_t total; +}midi_stream_t; + +typedef struct +{ + uint8_t dev_addr; + uint8_t itf_num; + + uint8_t ep_in; // IN endpoint address + uint8_t ep_out; // OUT endpoint address + uint16_t ep_in_max; // min( CFG_TUH_MIDI_RX_BUFSIZE, wMaxPacketSize of the IN endpoint) + uint16_t ep_out_max; // min( CFG_TUH_MIDI_TX_BUFSIZE, wMaxPacketSize of the OUT endpoint) + + uint8_t num_cables_rx; // IN endpoint CS descriptor bNumEmbMIDIJack value + uint8_t num_cables_tx; // OUT endpoint CS descriptor bNumEmbMIDIJack value + + // 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_stream_t stream_write; + midi_stream_t stream_read; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + // Endpoint FIFOs + tu_fifo_t rx_ff; + tu_fifo_t tx_ff; + + + uint8_t rx_ff_buf[CFG_TUH_MIDI_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUH_MIDI_TX_BUFSIZE]; + + #if CFG_FIFO_MUTEX + osal_mutex_def_t rx_ff_mutex; + osal_mutex_def_t tx_ff_mutex; + #endif + + // Endpoint Transfer buffer + CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUH_MIDI_EP_BUFSIZE]; + CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUH_MIDI_EP_BUFSIZE]; + + bool configured; +}midih_interface_t; + +static midih_interface_t _midi_host; + +//------------- Internal prototypes -------------// +static uint32_t write_flush(uint8_t dev_addr, midih_interface_t* midi); +#if 0 +typedef struct +{ + uint8_t inst_count; + hidh_interface_t instances[CFG_TUH_HID]; +} hidh_device_t; + +static hidh_device_t _hidh_dev[CFG_TUH_DEVICE_MAX]; + +//------------- Internal prototypes -------------// + +// Get HID device & interface +TU_ATTR_ALWAYS_INLINE static inline hidh_device_t* get_dev(uint8_t dev_addr); +TU_ATTR_ALWAYS_INLINE static inline hidh_interface_t* get_instance(uint8_t dev_addr, uint8_t instance); +static uint8_t get_instance_id_by_itfnum(uint8_t dev_addr, uint8_t itf); +static uint8_t get_instance_id_by_epaddr(uint8_t dev_addr, uint8_t ep_addr); + +//--------------------------------------------------------------------+ +// Interface API +//--------------------------------------------------------------------+ + +uint8_t tuh_hid_instance_count(uint8_t dev_addr) +{ + return get_dev(dev_addr)->inst_count; +} + +bool tuh_hid_mounted(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + return (hid_itf->ep_in != 0) || (hid_itf->ep_out != 0); +} + +uint8_t tuh_hid_interface_protocol(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + return hid_itf->itf_protocol; +} + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ + +uint8_t tuh_hid_get_protocol(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + return hid_itf->protocol_mode; +} + +static bool set_protocol_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + uint8_t const itf_num = (uint8_t) request->wIndex; + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + if (XFER_RESULT_SUCCESS == result) hid_itf->protocol_mode = (uint8_t) request->wValue; + + if (tuh_hid_set_protocol_complete_cb) + { + tuh_hid_set_protocol_complete_cb(dev_addr, instance, hid_itf->protocol_mode); + } + + return true; +} + +bool tuh_hid_set_protocol(uint8_t dev_addr, uint8_t instance, uint8_t protocol) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + TU_VERIFY(hid_itf->itf_protocol != HID_ITF_PROTOCOL_NONE); + + TU_LOG2("HID Set Protocol = %d\r\n", protocol); + + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_PROTOCOL, + .wValue = protocol, + .wIndex = hid_itf->itf_num, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, NULL, set_protocol_complete) ); + return true; +} + +static bool set_report_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_LOG2("HID Set Report complete\r\n"); + + if (tuh_hid_set_report_complete_cb) + { + uint8_t const itf_num = (uint8_t) request->wIndex; + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); + + tuh_hid_set_report_complete_cb(dev_addr, instance, report_id, report_type, (result == XFER_RESULT_SUCCESS) ? request->wLength : 0); + } + + return true; +} + +bool tuh_hid_set_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + TU_LOG2("HID Set Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); + + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_REPORT, + .wValue = tu_u16(report_type, report_id), + .wIndex = hid_itf->itf_num, + .wLength = len + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, report, set_report_complete) ); + return true; +} + +//--------------------------------------------------------------------+ +// Interrupt Endpoint API +//--------------------------------------------------------------------+ + +bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + // claim endpoint + TU_VERIFY( usbh_edpt_claim(dev_addr, hid_itf->ep_in) ); + + return usbh_edpt_xfer(dev_addr, hid_itf->ep_in, hid_itf->epin_buf, hid_itf->epin_size); +} + +//bool tuh_n_hid_n_ready(uint8_t dev_addr, uint8_t instance) +//{ +// TU_VERIFY(tuh_n_hid_n_mounted(dev_addr, instance)); +// +// hidh_interface_t* hid_itf = get_instance(dev_addr, instance); +// return !usbh_edpt_busy(dev_addr, hid_itf->ep_in); +//} + +//void tuh_hid_send_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t const* report, uint16_t len); +#endif +//--------------------------------------------------------------------+ +// USBH API +//--------------------------------------------------------------------+ +void midih_init(void) +{ + tu_memclr(&_midi_host, sizeof(_midi_host)); + + // config fifo + tu_fifo_config(&_midi_host.rx_ff, _midi_host.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, 1, false); // true, true + tu_fifo_config(&_midi_host.tx_ff, _midi_host.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, 1, false); // OBVS. + + #if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&_midi_host.rx_ff, NULL, osal_mutex_create(&_midi_host.rx_ff_mutex)); + tu_fifo_config_mutex(&_midi_host.tx_ff, osal_mutex_create(&_midi_host.tx_ff_mutex), NULL); + #endif +} + +bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + + if ( ep_addr == _midi_host.ep_in) + { + if (0 == xferred_bytes) + { + return true; // No data to handle + } + + // receive new data if available + TU_LOG0("Data Received Result=%u #bytes=%u\r\n", result, xferred_bytes); + if (xferred_bytes) + { + // put in the RX FIFO only non-zero MIDI IN 4-byte packets + uint8_t* buf = _midi_host.epin_buf; + uint32_t npackets = xferred_bytes / 4; + uint32_t packet_num; + uint32_t num_packets_queued = 0; + for (packet_num = 0; packet_num < npackets; packet_num++) + { + // some devices send back all zero packets even if there is no data ready + uint32_t packet = (uint32_t)(*buf) | ((uint32_t)(*(buf+1))<<8) | ((uint32_t)(*(buf+2))<<16) | ((uint32_t)(*(buf+3))<<24); + if (packet != 0) + { + tu_fifo_write_n(&_midi_host.rx_ff, buf, 4); + ++num_packets_queued; + TU_LOG1("MIDI RX=%08x\r\n", packet); + } + buf += 4; + } + #if 0 // TODO + // invoke receive callback if available + if (tuh_midi_rx_cb) + { + tuh_midi_rx_cb(itf); + } + #endif + } + #if 0 // TODO + tu_fifo_write_n(&p_midi->rx_ff, p_midi->epin_buf, xferred_bytes); + + + + // prepare for next + // TODO for now ep_out is not used by public API therefore there is no race condition, + // and does not need to claim like ep_in + _prep_out_transaction(p_midi); + #endif + } + else if ( ep_addr == _midi_host.ep_out ) + { + if (0 == write_flush(dev_addr, &_midi_host)) + { + // If there is no data left, a ZLP should be sent if + // xferred_bytes is multiple of EP size and not zero + if ( !tu_fifo_count(&_midi_host.tx_ff) && xferred_bytes && (0 == (xferred_bytes % _midi_host.ep_out_max)) ) + { + if ( usbh_edpt_claim(dev_addr, _midi_host.ep_out) ) + { + usbh_edpt_xfer(dev_addr, _midi_host.ep_out, XFER_RESULT_SUCCESS, 0); + } + } + } + } + + return true; +} + +void midih_close(uint8_t dev_addr) +{ + if (dev_addr == _midi_host.dev_addr) + { + if (tuh_midi_umount_cb) + tuh_midi_umount_cb(dev_addr, 0); + tu_fifo_clear(&_midi_host.rx_ff); + tu_fifo_clear(&_midi_host.tx_ff); + _midi_host.ep_in = 0; + _midi_host.ep_in_max = 0; + _midi_host.ep_out = 0; + _midi_host.ep_out_max = 0; + _midi_host.itf_num = 0; + _midi_host.num_cables_rx = 0; + _midi_host.num_cables_tx = 0; + _midi_host.dev_addr = 255; // invalid + _midi_host.configured = false; + tu_memclr(&_midi_host.stream_read, sizeof(_midi_host.stream_read)); + tu_memclr(&_midi_host.stream_read, sizeof(_midi_host.stream_write)); + } +} + +#if 0 +// Invoked when device with midi interface is un-mounted +void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) +{ + printf("MIDI device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); + +} +#endif + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ +#if 0 +static bool config_set_protocol (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool config_get_report_desc (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool config_get_report_desc_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); + +static void config_driver_mount_complete(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len); +#endif +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); + // There can be just a MIDI interface or an audio and a MIDI interface. Only open the MIDI interface + uint8_t const *p_desc = (uint8_t const *) desc_itf; + uint16_t len_parsed = 0; + if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) + { + // This driver does not support audio streaming. However, if this is the audio control interface + // there might be a MIDI interface following it. Search through every descriptor until a MIDI + // interface is found or the end of the descriptor is found + while (len_parsed < max_len && (desc_itf->bInterfaceClass != TUSB_CLASS_AUDIO || desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING)) + { + len_parsed += desc_itf->bLength; + p_desc = tu_desc_next(p_desc); + desc_itf = (tusb_desc_interface_t const *)p_desc; + } + #if 0 + p_desc = tu_desc_next(p_desc); + // p_desc now should point to the class-specific audio interface descriptor header + audio_desc_cs_ac_interface_t const *p_cs_ac = (audio_desc_cs_ac_interface_t const *)p_desc; + TU_VERIFY(p_cs_ac->bDescriptorType == TUSB_DESC_CS_INTERFACE); + TU_VERIFY(p_cs_ac->bDescriptorSubType == AUDIO_CS_AC_INTERFACE_HEADER); + if (p_cs_ac->bcdADC == 0x0200) + { + // skip the audio interface header + p_desc += p_cs_ac->wTotalLength; + len_parsed += p_cs_ac->wTotalLength; + } + else if (p_cs_ac->bcdADC == 0x0100) + { + // it's audio class 1.0 + audio_desc_cs_ac1_interface_t const *p_cs_ac1 = (audio_desc_cs_ac1_interface_t const *)p_desc; + // skip the audio interface header + p_desc += p_cs_ac1->wTotalLength; + len_parsed += p_cs_ac1->wTotalLength; + } + else + { + return false; + } + desc_itf = (tusb_desc_interface_t const *)p_desc; + #endif + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); + } + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass); + len_parsed += desc_itf->bLength; + + p_desc = tu_desc_next(p_desc); + TU_LOG1("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr); + // Find out if getting the MIDI class specific interface header or an endpoint descriptor + // or a class-specific endpoint descriptor + // Jack descriptors or element descriptors must follow the cs interface header, + // but this driver does not support devices that contain element descriptors + + // assume it is an interface header + midi_desc_header_t const *p_mdh = (midi_desc_header_t const *)p_desc; + TU_VERIFY((p_mdh->bDescriptorType == TUSB_DESC_CS_INTERFACE && p_mdh->bDescriptorSubType == MIDI_CS_INTERFACE_HEADER) || + (p_mdh->bDescriptorType == TUSB_DESC_CS_ENDPOINT && p_mdh->bDescriptorSubType == MIDI_MS_ENDPOINT_GENERAL) || + p_mdh->bDescriptorType == TUSB_DESC_ENDPOINT); + + uint8_t prev_ep_addr = 0; // the CS endpoint descriptor is associated with the previous endpoint descrptor + _midi_host.itf_num = desc_itf->bInterfaceNumber; + tusb_desc_endpoint_t const* in_desc = NULL; + tusb_desc_endpoint_t const* out_desc = NULL; + while (len_parsed < max_len) + { + TU_VERIFY((p_mdh->bDescriptorType == TUSB_DESC_CS_INTERFACE) || + (p_mdh->bDescriptorType == TUSB_DESC_CS_ENDPOINT && p_mdh->bDescriptorSubType == MIDI_MS_ENDPOINT_GENERAL) || + p_mdh->bDescriptorType == TUSB_DESC_ENDPOINT); + + if (p_mdh->bDescriptorType == TUSB_DESC_CS_INTERFACE) { + // The USB host doesn't really need this information unless it uses + // the string descriptor for a jack or Element + + // assume it is an input jack + midi_desc_in_jack_t const *p_mdij = (midi_desc_in_jack_t const *)p_desc; + if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_HEADER) + { + TU_LOG2("Found MIDI Interface Header\r\b"); + } + else if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_IN_JACK) + { + // Then it is an in jack. + TU_LOG2("Found in jack\r\n"); + } + else if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_OUT_JACK) + { + // then it is an out jack + TU_LOG2("Found out jack\r\n"); + } + else if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_ELEMENT) + { + // the it is an element; + TU_LOG2("Found element\r\n"); + } + else + { + TU_LOG2("Unknown CS Interface sub-type %u\r\n", p_mdij->bDescriptorSubType); + TU_VERIFY(false); // unknown CS Interface sub-type + } + len_parsed += p_mdij->bLength; + } + else if (p_mdh->bDescriptorType == TUSB_DESC_CS_ENDPOINT) + { + TU_LOG2("found CS_ENDPOINT Descriptor for %u\r\n", prev_ep_addr); + TU_VERIFY(prev_ep_addr != 0); + // parse out the mapping between the device's embedded jacks and the endpoints + // Each embedded IN jack is assocated with an OUT endpoint + midi_cs_desc_endpoint_t const* p_csep = (midi_cs_desc_endpoint_t const*)p_mdh; + if (tu_edpt_dir(prev_ep_addr) == TUSB_DIR_OUT) + { + TU_VERIFY(_midi_host.ep_out == prev_ep_addr); + TU_VERIFY(_midi_host.num_cables_tx == 0); + _midi_host.num_cables_tx = p_csep->bNumEmbMIDIJack; + } + else + { + TU_VERIFY(_midi_host.ep_in == prev_ep_addr); + TU_VERIFY(_midi_host.num_cables_rx == 0); + _midi_host.num_cables_rx = p_csep->bNumEmbMIDIJack; + } + len_parsed += p_csep->bLength; + prev_ep_addr = 0; + } + else if (p_mdh->bDescriptorType == TUSB_DESC_ENDPOINT) { + // parse out the bulk endpoint info + tusb_desc_endpoint_t const *p_ep = (tusb_desc_endpoint_t const *)p_mdh; + TU_LOG2("found ENDPOINT Descriptor for %u\r\n", p_ep->bEndpointAddress); + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) + { + TU_VERIFY(_midi_host.ep_out == 0); + TU_VERIFY(_midi_host.num_cables_tx == 0); + _midi_host.ep_out = p_ep->bEndpointAddress; + _midi_host.ep_out_max = p_ep->wMaxPacketSize; + if (_midi_host.ep_out_max > CFG_TUH_MIDI_TX_BUFSIZE) + _midi_host.ep_out_max = CFG_TUH_MIDI_TX_BUFSIZE; + prev_ep_addr = _midi_host.ep_out; + out_desc = p_ep; + } + else + { + TU_VERIFY(_midi_host.ep_in == 0); + TU_VERIFY(_midi_host.num_cables_rx == 0); + _midi_host.ep_in = p_ep->bEndpointAddress; + _midi_host.ep_in_max = p_ep->wMaxPacketSize; + if (_midi_host.ep_in_max > CFG_TUH_MIDI_RX_BUFSIZE) + _midi_host.ep_in_max = CFG_TUH_MIDI_RX_BUFSIZE; + prev_ep_addr = _midi_host.ep_in; + in_desc = p_ep; + } + len_parsed += p_mdh->bLength; + } + p_desc = tu_desc_next(p_desc); + p_mdh = (midi_desc_header_t const *)p_desc; + } + TU_VERIFY((_midi_host.ep_out != 0 && _midi_host.num_cables_tx != 0) || + (_midi_host.ep_in != 0 && _midi_host.num_cables_rx != 0)); + TU_LOG1("MIDI descriptor parsed successfully\r\n"); + + if (in_desc) + { + TU_ASSERT(usbh_edpt_open(rhport, dev_addr, in_desc)); + } + if (out_desc) + { + TU_ASSERT(usbh_edpt_open(rhport, dev_addr, out_desc)); + } + _midi_host.dev_addr = dev_addr; + + if (tuh_midi_mount_cb) + { + tuh_midi_mount_cb(dev_addr, _midi_host.ep_in, _midi_host.ep_out, _midi_host.num_cables_rx, _midi_host.num_cables_tx); + } + return true; +} + +#if 0 +void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep, uint8_t num_cables_rx, uint16_t num_cables_tx) +{ + printf("MIDI endpoints OK. MIDI Interface opened\r\n"); +} +#endif + +bool tuh_midi_configured(void) +{ + return _midi_host.configured; +} +bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) +{ + (void) itf_num; + if (dev_addr == _midi_host.dev_addr) + _midi_host.configured = true; + // TODO I don't think there are any special config things to do for MIDI + #if 0 + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + // Idle rate = 0 mean only report when there is changes + uint16_t const idle_rate = 0; + + // SET IDLE request, device can stall if not support this request + TU_LOG2("HID Set Idle \r\n"); + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_IDLE, + .wValue = idle_rate, + .wIndex = itf_num, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, NULL, (hid_itf->itf_protocol != HID_ITF_PROTOCOL_NONE) ? config_set_protocol : config_get_report_desc) ); +#endif + return true; +} +#if 0 +static void config_driver_mount_complete(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + // enumeration is complete + tuh_hid_mount_cb(dev_addr, instance, desc_report, desc_len); + + // notify usbh that driver enumeration is complete + usbh_driver_set_config_complete(dev_addr, hid_itf->itf_num); +} + + +#endif +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ +static uint32_t write_flush(uint8_t dev_addr, midih_interface_t* midi) +{ + // No data to send + if ( !tu_fifo_count(&midi->tx_ff) ) return 0; + + // skip if previous transfer not complete + TU_VERIFY( usbh_edpt_claim(dev_addr, midi->ep_out) ); + + uint16_t count = tu_fifo_read_n(&midi->tx_ff, midi->epout_buf, midi->ep_out_max); + + if (count) + { + TU_ASSERT( usbh_edpt_xfer(dev_addr, midi->ep_out, midi->epout_buf, count), 0 ); + return count; + }else + { + // Release endpoint since we don't make any transfer + usbh_edpt_release(dev_addr, midi->ep_out); + return 0; + } +} + +bool tuh_midi_read_poll( void ) +{ + // MIDI bulk endpoints are shared with the control endpoints. None can be busy before we start a transfer + bool control_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,0) && !usbh_edpt_busy(_midi_host.dev_addr,0x80); + bool out_edpt_not_busy = true; + if (_midi_host.num_cables_tx > 0) + out_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_out); + if (!usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_in) && control_edpt_not_busy && out_edpt_not_busy) + { + TU_LOG3("Requesting poll IN endpoint %d\r\n", _midi_host.ep_in); + TU_ASSERT(usbh_edpt_xfer(_midi_host.dev_addr, _midi_host.ep_in, _midi_host.epin_buf, _midi_host.ep_in_max), 0); + } + return true; +} + +uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) +{ + bool control_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,0) || usbh_edpt_busy(_midi_host.dev_addr,0x80); + bool in_edpt_busy = false; + if (_midi_host.num_cables_rx > 0) + in_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_in); + if (control_edpt_busy || in_edpt_busy || usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_out)) + { + return 0; // can't send a packet now + } + TU_VERIFY(cable_num < _midi_host.num_cables_tx); + midi_stream_t stream = _midi_host.stream_write; + + uint32_t i = 0; + while ( (i < bufsize) && (tu_fifo_remaining(&_midi_host.tx_ff) >= 4) ) + { + uint8_t const data = buffer[i]; + i++; + + 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] = (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] = (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] = 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, i); + 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 == stream.total ) + { + // zeroes unused bytes + for(uint8_t idx = stream.total; idx < 4; idx++) stream.buffer[idx] = 0; + + uint16_t const count = tu_fifo_write_n(&_midi_host.tx_ff, 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, i); + } + } + + write_flush(_midi_host.dev_addr, &_midi_host); + + return i; +} + + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +uint8_t tuh_midih_get_num_tx_cables (void) +{ + TU_VERIFY(_midi_host.ep_out != 0); // returns 0 if fails + return _midi_host.num_cables_tx; +} + +uint8_t tuh_midih_get_num_rx_cables (void) +{ + TU_VERIFY(_midi_host.ep_in != 0); // returns 0 if fails + return _midi_host.num_cables_rx; +} + +#endif diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h new file mode 100644 index 0000000000..216d288e8a --- /dev/null +++ b/src/class/midi/midi_host.h @@ -0,0 +1,113 @@ +/* + * 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 +//--------------------------------------------------------------------+ + +// TODO Highspeed bulk transfer can be up to 512 bytes +#ifndef CFG_TUH_HID_EPIN_BUFSIZE +#define CFG_TUH_HID_EPIN_BUFSIZE 64 +#endif + +#ifndef CFG_TUH_HID_EPOUT_BUFSIZE +#define CFG_TUH_HID_EPOUT_BUFSIZE 64 +#endif + + +//--------------------------------------------------------------------+ +// Application API (Single Interface) +//--------------------------------------------------------------------+ +bool tuh_midi_configured (void); +uint32_t tuh_midi_available (void); + +// return the number of virtual midi cables on the device's OUT endpoint +uint8_t tuh_midih_get_num_tx_cables (void); + +// return the number of virtual midi cables on the device's IN endpoint +uint8_t tuh_midih_get_num_rx_cables (void); + +// request available data from the device. tuh_midi_message_received_cb() will +// be called if the device has any data to send. Otherwise, the device will +// respond NAK. This function blocks until the transfer completes or the +// devices sends NAK. +// This function will return false if the hardware is busy. +bool tuh_midi_read_poll( void ); + +// Send a message to the device. This function blocks until the send completes. +// If the device sends NAK during the transmission, this function will block +// until the NAK clears and the whole message gets sent. +// NOTE: check the return value and make sure the whole buffer was sent. +// The transmit queue might be full or the transmit hardware may not be able +// to start a new transmission. +uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void midih_init (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 dev_addr); + +//--------------------------------------------------------------------+ +// Callbacks (Weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when device with MIDI interface is mounted. +// If the MIDI host application requires MIDI IN, it should requst an +// IN transfer here. The device will likely NAK this transfer. How the driver +// handles the NAK is hardware dependent. +TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep, uint8_t num_cables_rx, uint16_t num_cables_tx); + +// Invoked when device with MIDI interface is un-mounted +// For now, the instance parameter is always 0 and can be ignored +TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance); + +// Invoked when a message is received on the bulk IN endpoint +// For now, the instance parameter is always 0 and can be ignored +// If a system exclusive message is long, the whole sysex message +// may not be contained in the result from a single callback. +// The application must consume this message (either by copying it or interpreting +// and using it immediately) before issuing another bulk in transfer request +TU_ATTR_WEAK void tuh_midi_message_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cable_num, uint8_t const* message, uint16_t message_len); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_MIDI_HOST_H_ */ From a73a95edc2db2851ec4951bf2c2de1ebe342aa50 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 21:00:18 -0800 Subject: [PATCH 07/35] Integrate MIDI host code to the stack --- src/host/usbh.c | 11 +++++++++++ src/tusb.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/src/host/usbh.c b/src/host/usbh.c index b8439addce..65f45a0832 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -171,6 +171,17 @@ static usbh_class_driver_t const usbh_class_drivers[] = }, #endif + #if CFG_TUH_MIDI + { + DRIVER_NAME("MIDI") + .init = midih_init, + .open = midih_open, + .set_config = midih_set_config, + .xfer_cb = midih_xfer_cb, + .close = midih_close + }, + #endif + #if CFG_TUH_HUB { DRIVER_NAME("HUB") diff --git a/src/tusb.h b/src/tusb.h index 0d29e106c9..2f68147142 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -54,6 +54,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 From 5ba5287958d6114b062eb4a68dbb778b4d45190c Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 30 Nov 2021 21:01:11 -0800 Subject: [PATCH 08/35] Create MIDI host test code --- examples/host/CMakeLists.txt | 1 + examples/host/midi/.only.MCU_RP2040 | 0 examples/host/midi/CMakeLists.txt | 28 ++++ examples/host/midi/Makefile | 29 ++++ examples/host/midi/src/CMakeLists.txt | 28 ++++ examples/host/midi/src/Makefile | 27 ++++ examples/host/midi/src/main.c | 85 ++++++++++++ examples/host/midi/src/midi_app.c | 182 ++++++++++++++++++++++++++ examples/host/midi/src/tusb_config.h | 95 ++++++++++++++ 9 files changed, 475 insertions(+) create mode 100644 examples/host/midi/.only.MCU_RP2040 create mode 100644 examples/host/midi/CMakeLists.txt create mode 100644 examples/host/midi/Makefile create mode 100644 examples/host/midi/src/CMakeLists.txt create mode 100644 examples/host/midi/src/Makefile create mode 100644 examples/host/midi/src/main.c create mode 100644 examples/host/midi/src/midi_app.c create mode 100644 examples/host/midi/src/tusb_config.h diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt index 5c63ec0c06..ce679815d4 100644 --- a/examples/host/CMakeLists.txt +++ b/examples/host/CMakeLists.txt @@ -8,3 +8,4 @@ family_initialize_project(tinyusb_host_examples ${CMAKE_CURRENT_LIST_DIR}) # family_add_subdirectory will filter what to actually add based on selected FAMILY family_add_subdirectory(cdc_msc_hid) family_add_subdirectory(hid_controller) +family_add_subdirectory(midi) diff --git a/examples/host/midi/.only.MCU_RP2040 b/examples/host/midi/.only.MCU_RP2040 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/host/midi/CMakeLists.txt b/examples/host/midi/CMakeLists.txt new file mode 100644 index 0000000000..15a818bac6 --- /dev/null +++ b/examples/host/midi/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +# gets PROJECT name for the example (e.g. -) +family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR}) + +project(${PROJECT}) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}) + +add_executable(${PROJECT}) + +# Example source +target_sources(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/midi_app.c + ${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... see the corresponding function +# in hw/bsp/FAMILY/family.cmake for details. +family_configure_host_example(${PROJECT}) \ No newline at end of file diff --git a/examples/host/midi/Makefile b/examples/host/midi/Makefile new file mode 100644 index 0000000000..1012759d7a --- /dev/null +++ b/examples/host/midi/Makefile @@ -0,0 +1,29 @@ +include ../../../tools/top.mk +include ../../make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += $(wildcard src/*.c) +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +# TODO: suppress warning caused by host stack +CFLAGS += -Wno-error=cast-align -Wno-error=null-dereference + +# TinyUSB Host Stack source +SRC_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/host/hub.c \ + src/host/usbh.c \ + src/host/usbh_control.c \ + src/portable/ehci/ehci.c \ + src/portable/ohci/ohci.c \ + src/portable/nxp/transdimension/hcd_transdimension.c \ + src/portable/nxp/lpc17_40/hcd_lpc17_40.c + +include ../../rules.mk diff --git a/examples/host/midi/src/CMakeLists.txt b/examples/host/midi/src/CMakeLists.txt new file mode 100644 index 0000000000..15a818bac6 --- /dev/null +++ b/examples/host/midi/src/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +# gets PROJECT name for the example (e.g. -) +family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR}) + +project(${PROJECT}) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}) + +add_executable(${PROJECT}) + +# Example source +target_sources(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/midi_app.c + ${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... see the corresponding function +# in hw/bsp/FAMILY/family.cmake for details. +family_configure_host_example(${PROJECT}) \ No newline at end of file diff --git a/examples/host/midi/src/Makefile b/examples/host/midi/src/Makefile new file mode 100644 index 0000000000..a50783ccd0 --- /dev/null +++ b/examples/host/midi/src/Makefile @@ -0,0 +1,27 @@ +include ../../../tools/top.mk +include ../../make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += $(wildcard src/*.c) +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +# TODO: suppress warning caused by host stack +CFLAGS += -Wno-error=cast-align -Wno-error=null-dereference + +# TinyUSB Host Stack source +SRC_C += \ + src/class/midi/midi_host.c \ + src/host/hub.c \ + src/host/usbh.c \ + src/host/usbh_control.c \ + src/portable/ehci/ehci.c \ + src/portable/ohci/ohci.c \ + src/portable/nxp/transdimension/hcd_transdimension.c \ + src/portable/nxp/lpc17_40/hcd_lpc17_40.c \ + src/portable/raspberrypi/rp2040/hcd_rp2040.c + +include ../../rules.mk diff --git a/examples/host/midi/src/main.c b/examples/host/midi/src/main.c new file mode 100644 index 0000000000..1cbae755f6 --- /dev/null +++ b/examples/host/midi/src/main.c @@ -0,0 +1,85 @@ +/* + * 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.h" +#include "tusb.h" +#if CFG_TUH_MIDI + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ +void led_blinking_task(void); + +extern void midi_host_app_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + + printf("TinyUSB Host MIDI Example\r\n"); + + tusb_init(); + + while (1) + { + // tinyusb host task + tuh_task(); + led_blinking_task(); + + midi_host_app_task(); + + } + + return 0; +} + +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Callbacks +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// 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 +} diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c new file mode 100644 index 0000000000..464b8adcaa --- /dev/null +++ b/examples/host/midi/src/midi_app.c @@ -0,0 +1,182 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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 "bsp/board.h" +#include "tusb.h" +#include "class/midi/midi_host.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +static bool device_mounted = false; + +static void test_tx(void) +{ + // toggle NOTE On, Note Off for the Mackie Control channels 1-8 REC LED + const uint32_t interval_ms = 1000; + static uint32_t start_ms = 0; + + static uint8_t message[] = + { + 0x90, 0x00, 0x7f, + 0x90, 0x01, 0x7f, + 0x90, 0x02, 0x7f, + 0x90, 0x03, 0x7f, + 0x90, 0x04, 0x7f, + 0x90, 0x05, 0x7f, + 0x90, 0x06, 0x7f, + 0x90, 0x07, 0x7f, + }; + + // device must be attached and have at least one endpoint ready to receive a message + if (!tuh_midi_configured()) + { + return; + } + if (tuh_midih_get_num_tx_cables() < 1) + { + return; + } + + // Blink every interval ms + if ( board_millis() - start_ms < interval_ms) + { + return; // not enough time + } + start_ms += interval_ms; + + uint32_t nwritten = tuh_midi_stream_write(0, message, sizeof(message)); + printf ("wrote %d bytes to MIDI device\r\n", nwritten); + if (message[2] == 0x7f) + { + message[2] = 0; + message[5] = 0; + message[8] = 0; + message[11] = 0; + message[14] = 0; + message[17] = 0; + message[20] = 0; + message[23] = 0; + } + else + { + message[2] = 0x7f; + message[5] = 0x7f; + message[8] = 0x7f; + message[11] = 0x7f; + message[14] = 0x7f; + message[17] = 0x7f; + message[20] = 0x7f; + message[23] = 0x7f; + } +} + +static void test_rx(void) +{ + #if 1 + const uint32_t interval_ms = 10; + static uint32_t start_ms = 0; + #endif + // device must be attached and have at least one endpoint ready to receive a message + if (!tuh_midi_configured()) + { + return; + } + if (tuh_midih_get_num_rx_cables() < 1) + { + return; + } + #if 1 + // poll every interval_ms ms + if ( board_millis() - start_ms < interval_ms) + { + return; // not enough time + } + start_ms += interval_ms; + #endif + tuh_midi_read_poll(); +} + +void midi_host_app_task(void) +{ + test_tx(); + //test_rx(); +} + +//--------------------------------------------------------------------+ +// TinyUSB Callbacks +//--------------------------------------------------------------------+ + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. tuh_hid_parse_report_descriptor() +// can be used to parse common/simple enough descriptor. +// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped +// therefore report_desc = NULL, desc_len = 0 +void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep, uint8_t num_cables_rx, uint16_t num_cables_tx) +{ + printf("MIDI device address = %u, IN endpoint %u has %u cables, OUT endpoint %u has %u cables\r\n", + dev_addr, in_ep & 0xf, num_cables_rx, out_ep & 0xf, num_cables_tx); + device_mounted = true; +} + +// Invoked when device with hid interface is un-mounted +void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) +{ + device_mounted = false; + printf("MIDI device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); +} + +#if 0 +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +{ + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + switch (itf_protocol) + { + case HID_ITF_PROTOCOL_KEYBOARD: + TU_LOG2("HID receive boot keyboard report\r\n"); + process_kbd_report( (hid_keyboard_report_t const*) report ); + break; + + case HID_ITF_PROTOCOL_MOUSE: + TU_LOG2("HID receive boot mouse report\r\n"); + process_mouse_report( (hid_mouse_report_t const*) report ); + break; + + default: + // Generic report requires matching ReportID and contents with previous parsed report info + process_generic_report(dev_addr, instance, report, len); + break; + } + + // continue to request to receive report + if ( !tuh_hid_receive_report(dev_addr, instance) ) + { + printf("Error: cannot request to receive report\r\n"); + } +} +#endif + diff --git a/examples/host/midi/src/tusb_config.h b/examples/host/midi/src/tusb_config.h new file mode 100644 index 0000000000..f1bd045976 --- /dev/null +++ b/examples/host/midi/src/tusb_config.h @@ -0,0 +1,95 @@ +/* + * 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 + +#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_HOST | OPT_MODE_HIGH_SPEED) +#else + #define CFG_TUSB_RHPORT0_MODE OPT_MODE_HOST +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* 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_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// CONFIGURATION +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +#define CFG_TUH_HUB 1 +#define CFG_TUH_CDC 0 +#define CFG_TUH_HID 0 // typical keyboard + mouse device can have 3-4 HID interfaces +#define CFG_TUH_MIDI 1 // there will be at most one MIDIStreaming Interface descriptor +#define CFG_TUH_MSC 0 +#define CFG_TUH_VENDOR 0 + +// max device support (excluding hub device) +#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports + +//------------- HID -------------// +#define CFG_TUH_HID_EPIN_BUFSIZE 64 +#define CFG_TUH_HID_EPOUT_BUFSIZE 64 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ From 72064dd52fd6507b8f68269c0211ee4f3fe3de4d Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 2 Dec 2021 20:15:55 -0800 Subject: [PATCH 09/35] Fix command line build failure --- hw/bsp/rp2040/family.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake index 1aa180ef82..6576138e00 100644 --- a/hw/bsp/rp2040/family.cmake +++ b/hw/bsp/rp2040/family.cmake @@ -87,6 +87,7 @@ if (NOT TARGET _rp2040_family_inclusion_marker) ${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 ) From 8144761b45925af196162103aca83747871b1d09 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 15:29:32 -0800 Subject: [PATCH 10/35] Do not try to manage IN endpoint NAK flag --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 4ef2f0c841..359e65dec1 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -110,12 +110,6 @@ static void hw_xfer_complete(struct hw_endpoint *ep, xfer_result_t xfer_result) uint8_t ep_addr = ep->ep_addr; uint xferred_len = ep->xferred_len; hw_endpoint_reset_transfer(ep); - if (tu_edpt_number(ep_addr) != 0 && nak_received()) - { - TU_LOG2("NAK Received EP=%x\r\n", ep_addr); - clear_nak_received(); - xferred_len = 0; - } hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); } From e158447350459560a6ce4e096dca7145d988fbf7 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 15:30:12 -0800 Subject: [PATCH 11/35] Add support for forcing the LAST_BUFF flag to be set for an endpoint buffer --- src/class/midi/midi_host.c | 3 +++ src/host/hcd.h | 2 +- src/host/usbh.c | 6 +++++- src/host/usbh.h | 2 ++ src/portable/raspberrypi/rp2040/hcd_rp2040.c | 6 ++++++ src/portable/raspberrypi/rp2040/rp2040_usb.c | 2 +- src/portable/raspberrypi/rp2040/rp2040_usb.h | 3 +++ 7 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 2521142f64..29632370e6 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -542,6 +542,9 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d if (in_desc) { TU_ASSERT(usbh_edpt_open(rhport, dev_addr, in_desc)); + // Some devices always return exactly the request length so transfers won't complete + // unless you assume every transfer is the last one. + usbh_edpt_force_last_buffer(dev_addr, _midi_host.ep_in, true); } if (out_desc) { diff --git a/src/host/hcd.h b/src/host/hcd.h index eb53d2e80e..ac9d0cfa55 100644 --- a/src/host/hcd.h +++ b/src/host/hcd.h @@ -148,7 +148,7 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc); bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr); - +TU_ATTR_WEAK void hcd_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force); //--------------------------------------------------------------------+ // USBH implemented API //--------------------------------------------------------------------+ diff --git a/src/host/usbh.c b/src/host/usbh.c index 65f45a0832..9735885761 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -1210,6 +1210,10 @@ bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) return dev->ep_status[epnum][dir].busy; } - +void usbh_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force) +{ + if (hcd_edpt_force_last_buffer) + hcd_edpt_force_last_buffer(dev_addr, ep_addr, force); +} #endif diff --git a/src/host/usbh.h b/src/host/usbh.h index 8411cad283..ad82c15acd 100644 --- a/src/host/usbh.h +++ b/src/host/usbh.h @@ -81,6 +81,8 @@ static inline bool tuh_ready(uint8_t dev_addr) // Carry out control transfer bool tuh_control_xfer (uint8_t dev_addr, tusb_control_request_t const* request, void* buffer, tuh_control_complete_cb_t complete_cb); + +void usbh_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force); //--------------------------------------------------------------------+ // APPLICATION CALLBACK //--------------------------------------------------------------------+ diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 359e65dec1..3ebaef50d3 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -663,4 +663,10 @@ bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr) return true; } +void hcd_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force) +{ + struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr); + assert(ep); + ep->force_last_buff = force; +} #endif diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index c9e2f6b266..32d0c7308d 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -128,7 +128,7 @@ static uint32_t prepare_ep_buffer(struct hw_endpoint *ep, uint8_t buf_id) // Is this the last buffer? Only really matters for host mode. Will trigger // the trans complete irq but also stop it polling. We only really care about // trans complete for setup packets being sent - if (ep->remaining_len == 0) + if (ep->remaining_len == 0 || ep->force_last_buff) { buf_ctrl |= USB_BUF_CTRL_LAST; } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index a9cf1dd07f..43e9272e8d 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -62,6 +62,9 @@ typedef struct hw_endpoint // If interrupt endpoint uint8_t interrupt_num; + + // Set to true to force the LAST_BUFF flag to true in the next xfer request + bool force_last_buff; #endif } hw_endpoint_t; From ad5a510011f6ac7029c452273844b325a8d0e9de Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 15:34:17 -0800 Subject: [PATCH 12/35] Add a more useful callback for the app to manage MIDI host IN endpoint completions --- src/class/midi/midi_host.c | 25 +++++++------------------ src/class/midi/midi_host.h | 9 +-------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 29632370e6..4217803c81 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -279,44 +279,33 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint } // receive new data if available - TU_LOG0("Data Received Result=%u #bytes=%u\r\n", result, xferred_bytes); if (xferred_bytes) { // put in the RX FIFO only non-zero MIDI IN 4-byte packets uint8_t* buf = _midi_host.epin_buf; uint32_t npackets = xferred_bytes / 4; uint32_t packet_num; - uint32_t num_packets_queued = 0; + uint32_t packets_queued = 0; for (packet_num = 0; packet_num < npackets; packet_num++) { // some devices send back all zero packets even if there is no data ready - uint32_t packet = (uint32_t)(*buf) | ((uint32_t)(*(buf+1))<<8) | ((uint32_t)(*(buf+2))<<16) | ((uint32_t)(*(buf+3))<<24); + uint32_t packet = (uint32_t)((*buf)<<24) | ((uint32_t)(*(buf+1))<<16) | ((uint32_t)(*(buf+2))<<8) | ((uint32_t)(*(buf+3))); if (packet != 0) { tu_fifo_write_n(&_midi_host.rx_ff, buf, 4); - ++num_packets_queued; - TU_LOG1("MIDI RX=%08x\r\n", packet); + ++packets_queued; + TU_LOG3("MIDI RX=%08x\r\n", packet); } buf += 4; } - #if 0 // TODO + + // invoke receive callback if available if (tuh_midi_rx_cb) { - tuh_midi_rx_cb(itf); + tuh_midi_rx_cb(dev_addr, packets_queued); } - #endif } - #if 0 // TODO - tu_fifo_write_n(&p_midi->rx_ff, p_midi->epin_buf, xferred_bytes); - - - - // prepare for next - // TODO for now ep_out is not used by public API therefore there is no race condition, - // and does not need to claim like ep_in - _prep_out_transaction(p_midi); - #endif } else if ( ep_addr == _midi_host.ep_out ) { diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h index 216d288e8a..50d7c53cb0 100644 --- a/src/class/midi/midi_host.h +++ b/src/class/midi/midi_host.h @@ -98,14 +98,7 @@ TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out // For now, the instance parameter is always 0 and can be ignored TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance); -// Invoked when a message is received on the bulk IN endpoint -// For now, the instance parameter is always 0 and can be ignored -// If a system exclusive message is long, the whole sysex message -// may not be contained in the result from a single callback. -// The application must consume this message (either by copying it or interpreting -// and using it immediately) before issuing another bulk in transfer request -TU_ATTR_WEAK void tuh_midi_message_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cable_num, uint8_t const* message, uint16_t message_len); - +TU_ATTR_WEAK void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets); #ifdef __cplusplus } #endif From fe8ae2cd31a9ddbd050eeeb24df7dec20372d514 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 15:35:10 -0800 Subject: [PATCH 13/35] create tuh_midi_stream_read() utility --- src/class/midi/midi.h | 5 ++ src/class/midi/midi_host.c | 121 +++++++++++++++++++++++++++++++++++++ src/class/midi/midi_host.h | 12 +++- 3 files changed, 137 insertions(+), 1 deletion(-) diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h index 57668e326c..e4b2b7bfdb 100644 --- a/src/class/midi/midi.h +++ b/src/class/midi/midi.h @@ -106,6 +106,11 @@ enum MIDI_STATUS_SYSREAL_SYSTEM_RESET = 0xFF, }; +enum +{ + MIDI_MAX_DATA_VAL = 0x7F, +}; + /// MIDI Interface Header Descriptor typedef struct TU_ATTR_PACKED { diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 4217803c81..0ea9f1ac35 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -787,4 +787,125 @@ uint8_t tuh_midih_get_num_rx_cables (void) return _midi_host.num_cables_rx; } +uint32_t tuh_midi_stream_read (uint8_t dev_addr, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize) +{ + uint32_t bytes_buffered = 0; + if (_midi_host.dev_addr == dev_addr) + { + TU_ASSERT(p_cable_num); + TU_ASSERT(p_buffer); + TU_ASSERT(bufsize); + uint8_t one_byte; + if (!tu_fifo_peek(&_midi_host.rx_ff, &one_byte)) + { + return 0; + } + *p_cable_num = (one_byte >> 4) & 0xf; + uint32_t nread = tu_fifo_read_n(&_midi_host.rx_ff, _midi_host.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=(_midi_host.stream_read.buffer[0] & 0xf) >> 4; + uint8_t bytes_to_add_to_stream = 0; + if (*p_cable_num < _midi_host.num_cables_rx) + { + // ignore the CIN field; too many devices out there encode this wrong + uint8_t status = _midi_host.stream_read.buffer[1]; + uint16_t cable_mask = 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; + uint8_t idx; + for (idx = 2; idx < 4; idx++) + { + if (_midi_host.stream_read.buffer[idx] <= MIDI_MAX_DATA_VAL) + { + ++bytes_to_add_to_stream; + } + else if (_midi_host.stream_read.buffer[idx] == MIDI_STATUS_SYSEX_END) + { + ++bytes_to_add_to_stream; + cable_sysex_in_progress &= ~cable_mask; + idx = 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 &= ~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 &= ~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; + } + } + uint8_t idx; + for (idx = 1; idx <= bytes_to_add_to_stream; idx++) + { + *p_buffer++ = _midi_host.stream_read.buffer[idx]; + } + bytes_buffered += bytes_to_add_to_stream; + nread = 0; + if (tu_fifo_peek(&_midi_host.rx_ff, &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_fifo_read_n(&_midi_host.rx_ff, _midi_host.stream_read.buffer, 4); + } + } + } + } + + return bytes_buffered; +} #endif diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h index 50d7c53cb0..8e04265470 100644 --- a/src/class/midi/midi_host.h +++ b/src/class/midi/midi_host.h @@ -73,7 +73,17 @@ bool tuh_midi_read_poll( void ); // NOTE: check the return value and make sure the whole buffer was sent. // The transmit queue might be full or the transmit hardware may not be able // to start a new transmission. -uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize); +uint32_t tuh_midi_stream_write (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 dev_addr, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize); + //--------------------------------------------------------------------+ // Internal Class Driver API From fb0849ebb258a14bcab2170bb56da386f6baf8f6 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 15:35:54 -0800 Subject: [PATCH 14/35] Demonstrate MIDI Host IN endpoint works with OUT endpoint disabled --- examples/host/midi/src/midi_app.c | 52 +++++++++++-------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index 464b8adcaa..5c66fb0665 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -31,7 +31,7 @@ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ static bool device_mounted = false; - +static bool need_to_poll_device = true; static void test_tx(void) { // toggle NOTE On, Note Off for the Mackie Control channels 1-8 REC LED @@ -95,7 +95,7 @@ static void test_tx(void) static void test_rx(void) { - #if 1 + #if 0 const uint32_t interval_ms = 10; static uint32_t start_ms = 0; #endif @@ -108,7 +108,7 @@ static void test_rx(void) { return; } - #if 1 + #if 0 // poll every interval_ms ms if ( board_millis() - start_ms < interval_ms) { @@ -116,13 +116,17 @@ static void test_rx(void) } start_ms += interval_ms; #endif - tuh_midi_read_poll(); + if (need_to_poll_device) + { + tuh_midi_read_poll(); + need_to_poll_device = false; + } } void midi_host_app_task(void) { - test_tx(); - //test_rx(); + //test_tx(); + test_rx(); } //--------------------------------------------------------------------+ @@ -148,35 +152,17 @@ void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) printf("MIDI device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); } -#if 0 -// Invoked when received report from device via interrupt endpoint -void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) { - uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); - - switch (itf_protocol) - { - case HID_ITF_PROTOCOL_KEYBOARD: - TU_LOG2("HID receive boot keyboard report\r\n"); - process_kbd_report( (hid_keyboard_report_t const*) report ); - break; - - case HID_ITF_PROTOCOL_MOUSE: - TU_LOG2("HID receive boot mouse report\r\n"); - process_mouse_report( (hid_mouse_report_t const*) report ); - break; - - default: - // Generic report requires matching ReportID and contents with previous parsed report info - process_generic_report(dev_addr, instance, report, len); - break; - } - - // continue to request to receive report - if ( !tuh_hid_receive_report(dev_addr, instance) ) + need_to_poll_device = true; + if (num_packets != 0) { - printf("Error: cannot request to receive report\r\n"); + uint8_t cable_num; + uint8_t buffer[48]; + uint32_t bytes_read = tuh_midi_stream_read(dev_addr, &cable_num, buffer, sizeof(buffer)); + TU_LOG1("Read bytes %u cable %u", bytes_read, cable_num); + TU_LOG1_MEM(buffer, bytes_read, 2); } } -#endif + From a99545c0f6825b01a3a06f112a0d973cf25ca5e1 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 19:43:53 -0800 Subject: [PATCH 15/35] Try to fix build error for device-side USB RP2040 build --- src/portable/raspberrypi/rp2040/rp2040_usb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 32d0c7308d..763ed00cb6 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -128,7 +128,11 @@ static uint32_t prepare_ep_buffer(struct hw_endpoint *ep, uint8_t buf_id) // Is this the last buffer? Only really matters for host mode. Will trigger // the trans complete irq but also stop it polling. We only really care about // trans complete for setup packets being sent + #if TUSB_OPT_HOST_ENABLED if (ep->remaining_len == 0 || ep->force_last_buff) + #else + if (ep->remaining_len == 0) + #endif { buf_ctrl |= USB_BUF_CTRL_LAST; } From e30708aa927ba2c67a0e1fe4aea393f205bc59f1 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 20:40:15 -0800 Subject: [PATCH 16/35] Remove dead code --- examples/host/midi/src/midi_app.c | 58 +++++------ src/class/midi/midi_host.c | 153 ------------------------------ 2 files changed, 25 insertions(+), 186 deletions(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index 5c66fb0665..80984882ac 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -68,37 +68,37 @@ static void test_tx(void) start_ms += interval_ms; uint32_t nwritten = tuh_midi_stream_write(0, message, sizeof(message)); - printf ("wrote %d bytes to MIDI device\r\n", nwritten); - if (message[2] == 0x7f) + if (nwritten != 0) { - message[2] = 0; - message[5] = 0; - message[8] = 0; - message[11] = 0; - message[14] = 0; - message[17] = 0; - message[20] = 0; - message[23] = 0; - } - else - { - message[2] = 0x7f; - message[5] = 0x7f; - message[8] = 0x7f; - message[11] = 0x7f; - message[14] = 0x7f; - message[17] = 0x7f; - message[20] = 0x7f; - message[23] = 0x7f; + need_to_poll_device = (tuh_midi_stream_flush() == 0); + + if (message[2] == 0x7f) + { + message[2] = 0; + message[5] = 0; + message[8] = 0; + message[11] = 0; + message[14] = 0; + message[17] = 0; + message[20] = 0; + message[23] = 0; + } + else + { + message[2] = 0x7f; + message[5] = 0x7f; + message[8] = 0x7f; + message[11] = 0x7f; + message[14] = 0x7f; + message[17] = 0x7f; + message[20] = 0x7f; + message[23] = 0x7f; + } } } static void test_rx(void) { - #if 0 - const uint32_t interval_ms = 10; - static uint32_t start_ms = 0; - #endif // device must be attached and have at least one endpoint ready to receive a message if (!tuh_midi_configured()) { @@ -108,14 +108,6 @@ static void test_rx(void) { return; } - #if 0 - // poll every interval_ms ms - if ( board_millis() - start_ms < interval_ms) - { - return; // not enough time - } - start_ms += interval_ms; - #endif if (need_to_poll_device) { tuh_midi_read_poll(); diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 0ea9f1ac35..f38ff944dd 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -97,160 +97,7 @@ static midih_interface_t _midi_host; //------------- Internal prototypes -------------// static uint32_t write_flush(uint8_t dev_addr, midih_interface_t* midi); -#if 0 -typedef struct -{ - uint8_t inst_count; - hidh_interface_t instances[CFG_TUH_HID]; -} hidh_device_t; - -static hidh_device_t _hidh_dev[CFG_TUH_DEVICE_MAX]; - -//------------- Internal prototypes -------------// - -// Get HID device & interface -TU_ATTR_ALWAYS_INLINE static inline hidh_device_t* get_dev(uint8_t dev_addr); -TU_ATTR_ALWAYS_INLINE static inline hidh_interface_t* get_instance(uint8_t dev_addr, uint8_t instance); -static uint8_t get_instance_id_by_itfnum(uint8_t dev_addr, uint8_t itf); -static uint8_t get_instance_id_by_epaddr(uint8_t dev_addr, uint8_t ep_addr); - -//--------------------------------------------------------------------+ -// Interface API -//--------------------------------------------------------------------+ - -uint8_t tuh_hid_instance_count(uint8_t dev_addr) -{ - return get_dev(dev_addr)->inst_count; -} - -bool tuh_hid_mounted(uint8_t dev_addr, uint8_t instance) -{ - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - return (hid_itf->ep_in != 0) || (hid_itf->ep_out != 0); -} - -uint8_t tuh_hid_interface_protocol(uint8_t dev_addr, uint8_t instance) -{ - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - return hid_itf->itf_protocol; -} - -//--------------------------------------------------------------------+ -// Control Endpoint API -//--------------------------------------------------------------------+ - -uint8_t tuh_hid_get_protocol(uint8_t dev_addr, uint8_t instance) -{ - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - return hid_itf->protocol_mode; -} - -static bool set_protocol_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) -{ - uint8_t const itf_num = (uint8_t) request->wIndex; - uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - - if (XFER_RESULT_SUCCESS == result) hid_itf->protocol_mode = (uint8_t) request->wValue; - - if (tuh_hid_set_protocol_complete_cb) - { - tuh_hid_set_protocol_complete_cb(dev_addr, instance, hid_itf->protocol_mode); - } - - return true; -} - -bool tuh_hid_set_protocol(uint8_t dev_addr, uint8_t instance, uint8_t protocol) -{ - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - TU_VERIFY(hid_itf->itf_protocol != HID_ITF_PROTOCOL_NONE); - - TU_LOG2("HID Set Protocol = %d\r\n", protocol); - - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = HID_REQ_CONTROL_SET_PROTOCOL, - .wValue = protocol, - .wIndex = hid_itf->itf_num, - .wLength = 0 - }; - - TU_ASSERT( tuh_control_xfer(dev_addr, &request, NULL, set_protocol_complete) ); - return true; -} - -static bool set_report_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) -{ - TU_LOG2("HID Set Report complete\r\n"); - if (tuh_hid_set_report_complete_cb) - { - uint8_t const itf_num = (uint8_t) request->wIndex; - uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); - - uint8_t const report_type = tu_u16_high(request->wValue); - uint8_t const report_id = tu_u16_low(request->wValue); - - tuh_hid_set_report_complete_cb(dev_addr, instance, report_id, report_type, (result == XFER_RESULT_SUCCESS) ? request->wLength : 0); - } - - return true; -} - -bool tuh_hid_set_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) -{ - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - TU_LOG2("HID Set Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); - - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = HID_REQ_CONTROL_SET_REPORT, - .wValue = tu_u16(report_type, report_id), - .wIndex = hid_itf->itf_num, - .wLength = len - }; - - TU_ASSERT( tuh_control_xfer(dev_addr, &request, report, set_report_complete) ); - return true; -} - -//--------------------------------------------------------------------+ -// Interrupt Endpoint API -//--------------------------------------------------------------------+ - -bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t instance) -{ - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - - // claim endpoint - TU_VERIFY( usbh_edpt_claim(dev_addr, hid_itf->ep_in) ); - - return usbh_edpt_xfer(dev_addr, hid_itf->ep_in, hid_itf->epin_buf, hid_itf->epin_size); -} - -//bool tuh_n_hid_n_ready(uint8_t dev_addr, uint8_t instance) -//{ -// TU_VERIFY(tuh_n_hid_n_mounted(dev_addr, instance)); -// -// hidh_interface_t* hid_itf = get_instance(dev_addr, instance); -// return !usbh_edpt_busy(dev_addr, hid_itf->ep_in); -//} - -//void tuh_hid_send_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t const* report, uint16_t len); -#endif //--------------------------------------------------------------------+ // USBH API //--------------------------------------------------------------------+ From d800bcf3c2e5690313c94fb762d83a0fd56e237b Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 20:41:03 -0800 Subject: [PATCH 17/35] Create API for alternating between IN endpoint and OUT endpoint --- src/class/midi/midi_host.c | 33 +++++++++++++++++++++------------ src/class/midi/midi_host.h | 16 +++++++++------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index f38ff944dd..08330531ad 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -168,6 +168,10 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint } } } + if (tuh_midi_tx_cb) + { + tuh_midi_tx_cb(dev_addr); + } } return true; @@ -483,26 +487,20 @@ bool tuh_midi_read_poll( void ) // MIDI bulk endpoints are shared with the control endpoints. None can be busy before we start a transfer bool control_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,0) && !usbh_edpt_busy(_midi_host.dev_addr,0x80); bool out_edpt_not_busy = true; + bool result = false; if (_midi_host.num_cables_tx > 0) out_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_out); if (!usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_in) && control_edpt_not_busy && out_edpt_not_busy) { TU_LOG3("Requesting poll IN endpoint %d\r\n", _midi_host.ep_in); TU_ASSERT(usbh_edpt_xfer(_midi_host.dev_addr, _midi_host.ep_in, _midi_host.epin_buf, _midi_host.ep_in_max), 0); + result = true; } - return true; + return result; } uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) { - bool control_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,0) || usbh_edpt_busy(_midi_host.dev_addr,0x80); - bool in_edpt_busy = false; - if (_midi_host.num_cables_rx > 0) - in_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_in); - if (control_edpt_busy || in_edpt_busy || usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_out)) - { - return 0; // can't send a packet now - } TU_VERIFY(cable_num < _midi_host.num_cables_tx); midi_stream_t stream = _midi_host.stream_write; @@ -612,13 +610,24 @@ uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32 TU_ASSERT(count == 4, i); } } - - write_flush(_midi_host.dev_addr, &_midi_host); - return i; } +uint32_t tuh_midi_stream_flush( void ) +{ + + bool control_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,0) || usbh_edpt_busy(_midi_host.dev_addr,0x80); + bool in_edpt_busy = false; + uint32_t bytes_flushed = 0; + if (_midi_host.num_cables_rx > 0) + in_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_in); + if (!control_edpt_busy && !in_edpt_busy && !usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_out)) + { + bytes_flushed = write_flush(_midi_host.dev_addr, &_midi_host); + } + return bytes_flushed; +} //--------------------------------------------------------------------+ // Helper //--------------------------------------------------------------------+ diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h index 8e04265470..67891d2cb5 100644 --- a/src/class/midi/midi_host.h +++ b/src/class/midi/midi_host.h @@ -67,14 +67,16 @@ uint8_t tuh_midih_get_num_rx_cables (void); // This function will return false if the hardware is busy. bool tuh_midi_read_poll( void ); -// Send a message to the device. This function blocks until the send completes. -// If the device sends NAK during the transmission, this function will block -// until the NAK clears and the whole message gets sent. -// NOTE: check the return value and make sure the whole buffer was sent. -// The transmit queue might be full or the transmit hardware may not be able -// to start a new transmission. +// Queue a message to the device. The application +// must call tuh_midi_stream_flush to actually have the +// data go out. uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* p_buffer, uint32_t bufsize); +// 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_stream_flush( void); + // 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. @@ -84,7 +86,6 @@ uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* p_buffer, uint // it properly. uint32_t tuh_midi_stream_read (uint8_t dev_addr, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize); - //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ @@ -109,6 +110,7 @@ TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance); TU_ATTR_WEAK void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets); +TU_ATTR_WEAK void tuh_midi_tx_cb(uint8_t dev_addr); #ifdef __cplusplus } #endif From 55393fd4e016f919dfa64c75e1591d7e4b133118 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Thu, 9 Dec 2021 20:41:51 -0800 Subject: [PATCH 18/35] Fix MIDI host example app to support both IN endpoint and OUT endpoint --- examples/host/midi/src/midi_app.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index 80984882ac..ca85246a58 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -110,14 +110,13 @@ static void test_rx(void) } if (need_to_poll_device) { - tuh_midi_read_poll(); - need_to_poll_device = false; + need_to_poll_device = !tuh_midi_read_poll(); } } void midi_host_app_task(void) { - //test_tx(); + test_tx(); test_rx(); } @@ -146,7 +145,6 @@ void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) { - need_to_poll_device = true; if (num_packets != 0) { uint8_t cable_num; @@ -155,6 +153,12 @@ void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) TU_LOG1("Read bytes %u cable %u", bytes_read, cable_num); TU_LOG1_MEM(buffer, bytes_read, 2); } + // Try to send any queued packets before polling IN again + need_to_poll_device = (tuh_midi_stream_flush() == 0); } - +void tuh_midi_tx_cb(uint8_t dev_addr) +{ + // We are done sending. Can receive + need_to_poll_device = true; +} From 5e4364f194ab37f22fac302c56beb93717edd94b Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Sun, 12 Dec 2021 10:17:57 -0800 Subject: [PATCH 19/35] Fix random bulk transfer start failure --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 3ebaef50d3..afcf563dfb 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -571,7 +571,9 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * if ( is_bulk_or_isochronous ) { if (ep != _current_epx_endpoint) + { _hw_endpoint_reinit_epx(ep); + } _current_epx_endpoint = ep; hw_endpoint_xfer_start(ep, buffer, buflen); @@ -579,11 +581,16 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * // for host we have to initiate the transfer usb_hw->dev_addr_ctrl = dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB); - uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE | - (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); + uint32_t flags = SIE_CTRL_BASE | + (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); // Set pre if we are a low speed device on full speed hub flags |= need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0; - + // Set up the hardware with all flags except the start bit + usb_hw->sie_ctrl = flags; + // Now start the transaction; if you don't do this in two parts, + // and the host has switched direction, sometimes the transaction + // does not start + flags |= USB_SIE_CTRL_START_TRANS_BITS; usb_hw->sie_ctrl = flags; }else // If a normal transfer (non-interrupt) then initiate using From 9feff496f3fa04f1a3de8bc04fd48b5d370c1e3c Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Sun, 12 Dec 2021 10:19:02 -0800 Subject: [PATCH 20/35] Remove unnecessary need_to_poll_device flag from test app --- examples/host/midi/src/midi_app.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index ca85246a58..7de1084524 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -31,7 +31,6 @@ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ static bool device_mounted = false; -static bool need_to_poll_device = true; static void test_tx(void) { // toggle NOTE On, Note Off for the Mackie Control channels 1-8 REC LED @@ -59,7 +58,8 @@ static void test_tx(void) { return; } - + // transmit any previously queued bytes + tuh_midi_stream_flush(); // Blink every interval ms if ( board_millis() - start_ms < interval_ms) { @@ -70,8 +70,6 @@ static void test_tx(void) uint32_t nwritten = tuh_midi_stream_write(0, message, sizeof(message)); if (nwritten != 0) { - need_to_poll_device = (tuh_midi_stream_flush() == 0); - if (message[2] == 0x7f) { message[2] = 0; @@ -108,10 +106,7 @@ static void test_rx(void) { return; } - if (need_to_poll_device) - { - need_to_poll_device = !tuh_midi_read_poll(); - } + tuh_midi_read_poll(); } void midi_host_app_task(void) @@ -153,12 +148,9 @@ void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) TU_LOG1("Read bytes %u cable %u", bytes_read, cable_num); TU_LOG1_MEM(buffer, bytes_read, 2); } - // Try to send any queued packets before polling IN again - need_to_poll_device = (tuh_midi_stream_flush() == 0); } void tuh_midi_tx_cb(uint8_t dev_addr) { - // We are done sending. Can receive - need_to_poll_device = true; + } From bb7e564e7e89dd918e7a65165ca508eefde1fc77 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Sun, 12 Dec 2021 10:21:05 -0800 Subject: [PATCH 21/35] Always call tuh_midi_rx_cb on IN complete --- src/class/midi/midi_host.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 08330531ad..7708ba53ee 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -126,13 +126,13 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint } // receive new data if available + uint32_t packets_queued = 0; if (xferred_bytes) { // put in the RX FIFO only non-zero MIDI IN 4-byte packets uint8_t* buf = _midi_host.epin_buf; uint32_t npackets = xferred_bytes / 4; uint32_t packet_num; - uint32_t packets_queued = 0; for (packet_num = 0; packet_num < npackets; packet_num++) { // some devices send back all zero packets even if there is no data ready @@ -145,13 +145,11 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint } buf += 4; } - - - // invoke receive callback if available - if (tuh_midi_rx_cb) - { - tuh_midi_rx_cb(dev_addr, packets_queued); - } + } + // invoke receive callback if available + if (tuh_midi_rx_cb) + { + tuh_midi_rx_cb(dev_addr, packets_queued); } } else if ( ep_addr == _midi_host.ep_out ) From b9199511d6e2314f3006052541f9518d2509c36c Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Sun, 12 Dec 2021 10:22:23 -0800 Subject: [PATCH 22/35] Assert if usbh_edpt_xfer fails --- src/class/midi/midi_host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 7708ba53ee..2c43515376 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -162,7 +162,7 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint { if ( usbh_edpt_claim(dev_addr, _midi_host.ep_out) ) { - usbh_edpt_xfer(dev_addr, _midi_host.ep_out, XFER_RESULT_SUCCESS, 0); + TU_ASSERT(usbh_edpt_xfer(dev_addr, _midi_host.ep_out, XFER_RESULT_SUCCESS, 0)); } } } From 82e78f59e5b7d9939ed80e309f388693d4e218fe Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Sun, 12 Dec 2021 10:23:04 -0800 Subject: [PATCH 23/35] make variable names in busy logic consistent --- src/class/midi/midi_host.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 2c43515376..492da9c4cc 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -614,13 +614,13 @@ uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32 uint32_t tuh_midi_stream_flush( void ) { - bool control_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,0) || usbh_edpt_busy(_midi_host.dev_addr,0x80); - bool in_edpt_busy = false; + bool control_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,0) && !usbh_edpt_busy(_midi_host.dev_addr,0x80); + bool in_edpt_not_busy = true; uint32_t bytes_flushed = 0; if (_midi_host.num_cables_rx > 0) - in_edpt_busy = usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_in); - if (!control_edpt_busy && !in_edpt_busy && !usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_out)) + in_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_in); + if (control_edpt_not_busy && in_edpt_not_busy && !usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_out)) { bytes_flushed = write_flush(_midi_host.dev_addr, &_midi_host); } From 1c69bd1ae134f74008cf3a95fad3c82561a3d12c Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 21 Dec 2021 19:15:51 -0800 Subject: [PATCH 24/35] Add debug prints to test tx --- examples/host/midi/src/midi_app.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index 7de1084524..8e13eace22 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -68,10 +68,14 @@ static void test_tx(void) start_ms += interval_ms; uint32_t nwritten = tuh_midi_stream_write(0, message, sizeof(message)); + + char off_on[4] = {'O','n','\0'}; if (nwritten != 0) { if (message[2] == 0x7f) { + off_on[1] = 'n'; + off_on[2] = '\0'; message[2] = 0; message[5] = 0; message[8] = 0; @@ -83,6 +87,9 @@ static void test_tx(void) } else { + off_on[1] = 'f'; + off_on[2] = 'f'; + off_on[3] = '\0'; message[2] = 0x7f; message[5] = 0x7f; message[8] = 0x7f; @@ -92,6 +99,7 @@ static void test_tx(void) message[20] = 0x7f; message[23] = 0x7f; } + TU_LOG1("Switched lights %s\r\n", off_on); } } @@ -112,6 +120,7 @@ static void test_rx(void) void midi_host_app_task(void) { test_tx(); + test_rx(); } From 8a9de2bee7e1e197c57786f1ce4723a62279dd4d Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 21 Dec 2021 19:16:18 -0800 Subject: [PATCH 25/35] Remove dead code --- src/class/midi/midi_host.c | 87 +------------------------------------- 1 file changed, 1 insertion(+), 86 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 492da9c4cc..6c2e103d76 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -197,25 +197,9 @@ void midih_close(uint8_t dev_addr) } } -#if 0 -// Invoked when device with midi interface is un-mounted -void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) -{ - printf("MIDI device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); - -} -#endif - //--------------------------------------------------------------------+ // Enumeration //--------------------------------------------------------------------+ -#if 0 -static bool config_set_protocol (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); -static bool config_get_report_desc (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); -static bool config_get_report_desc_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); - -static void config_driver_mount_complete(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len); -#endif bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) { (void) rhport; @@ -234,32 +218,7 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d p_desc = tu_desc_next(p_desc); desc_itf = (tusb_desc_interface_t const *)p_desc; } - #if 0 - p_desc = tu_desc_next(p_desc); - // p_desc now should point to the class-specific audio interface descriptor header - audio_desc_cs_ac_interface_t const *p_cs_ac = (audio_desc_cs_ac_interface_t const *)p_desc; - TU_VERIFY(p_cs_ac->bDescriptorType == TUSB_DESC_CS_INTERFACE); - TU_VERIFY(p_cs_ac->bDescriptorSubType == AUDIO_CS_AC_INTERFACE_HEADER); - if (p_cs_ac->bcdADC == 0x0200) - { - // skip the audio interface header - p_desc += p_cs_ac->wTotalLength; - len_parsed += p_cs_ac->wTotalLength; - } - else if (p_cs_ac->bcdADC == 0x0100) - { - // it's audio class 1.0 - audio_desc_cs_ac1_interface_t const *p_cs_ac1 = (audio_desc_cs_ac1_interface_t const *)p_desc; - // skip the audio interface header - p_desc += p_cs_ac1->wTotalLength; - len_parsed += p_cs_ac1->wTotalLength; - } - else - { - return false; - } - desc_itf = (tusb_desc_interface_t const *)p_desc; - #endif + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); } TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass); @@ -397,13 +356,6 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d return true; } -#if 0 -void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep, uint8_t num_cables_rx, uint16_t num_cables_tx) -{ - printf("MIDI endpoints OK. MIDI Interface opened\r\n"); -} -#endif - bool tuh_midi_configured(void) { return _midi_host.configured; @@ -414,47 +366,10 @@ bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) if (dev_addr == _midi_host.dev_addr) _midi_host.configured = true; // TODO I don't think there are any special config things to do for MIDI - #if 0 - uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - - // Idle rate = 0 mean only report when there is changes - uint16_t const idle_rate = 0; - // SET IDLE request, device can stall if not support this request - TU_LOG2("HID Set Idle \r\n"); - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = HID_REQ_CONTROL_SET_IDLE, - .wValue = idle_rate, - .wIndex = itf_num, - .wLength = 0 - }; - - TU_ASSERT( tuh_control_xfer(dev_addr, &request, NULL, (hid_itf->itf_protocol != HID_ITF_PROTOCOL_NONE) ? config_set_protocol : config_get_report_desc) ); -#endif return true; } -#if 0 -static void config_driver_mount_complete(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) -{ - hidh_interface_t* hid_itf = get_instance(dev_addr, instance); - // enumeration is complete - tuh_hid_mount_cb(dev_addr, instance, desc_report, desc_len); - - // notify usbh that driver enumeration is complete - usbh_driver_set_config_complete(dev_addr, hid_itf->itf_num); -} - - -#endif //--------------------------------------------------------------------+ // Stream API //--------------------------------------------------------------------+ From 59c3b82fea6e6c2ff2742c3d77d0b7d51325a469 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 21 Dec 2021 19:17:51 -0800 Subject: [PATCH 26/35] modify the midi host module to support use with a hub --- examples/host/midi/src/midi_app.c | 22 +- src/class/midi/midi_host.c | 379 +++++++++++++++--------------- src/class/midi/midi_host.h | 14 +- 3 files changed, 214 insertions(+), 201 deletions(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index 8e13eace22..a4cd4ee8f7 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -30,7 +30,7 @@ //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ -static bool device_mounted = false; +static uint8_t midi_dev_addr = 0; static void test_tx(void) { // toggle NOTE On, Note Off for the Mackie Control channels 1-8 REC LED @@ -50,16 +50,16 @@ static void test_tx(void) }; // device must be attached and have at least one endpoint ready to receive a message - if (!tuh_midi_configured()) + if (!midi_dev_addr || !tuh_midi_configured(midi_dev_addr)) { return; } - if (tuh_midih_get_num_tx_cables() < 1) + if (tuh_midih_get_num_tx_cables(midi_dev_addr) < 1) { return; } // transmit any previously queued bytes - tuh_midi_stream_flush(); + tuh_midi_stream_flush(midi_dev_addr); // Blink every interval ms if ( board_millis() - start_ms < interval_ms) { @@ -67,7 +67,7 @@ static void test_tx(void) } start_ms += interval_ms; - uint32_t nwritten = tuh_midi_stream_write(0, message, sizeof(message)); + uint32_t nwritten = tuh_midi_stream_write(midi_dev_addr, 0, message, sizeof(message)); char off_on[4] = {'O','n','\0'}; if (nwritten != 0) @@ -106,15 +106,15 @@ static void test_tx(void) static void test_rx(void) { // device must be attached and have at least one endpoint ready to receive a message - if (!tuh_midi_configured()) + if (!midi_dev_addr || !tuh_midi_configured(midi_dev_addr)) { return; } - if (tuh_midih_get_num_rx_cables() < 1) + if (tuh_midih_get_num_rx_cables(midi_dev_addr) < 1) { return; } - tuh_midi_read_poll(); + tuh_midi_read_poll(midi_dev_addr); } void midi_host_app_task(void) @@ -137,19 +137,19 @@ void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep, uint8_t { printf("MIDI device address = %u, IN endpoint %u has %u cables, OUT endpoint %u has %u cables\r\n", dev_addr, in_ep & 0xf, num_cables_rx, out_ep & 0xf, num_cables_tx); - device_mounted = true; + midi_dev_addr = dev_addr; } // Invoked when device with hid interface is un-mounted void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) { - device_mounted = false; + midi_dev_addr = 0; printf("MIDI device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); } void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) { - if (num_packets != 0) + if (num_packets != 0 && midi_dev_addr == dev_addr) { uint8_t cable_num; uint8_t buffer[48]; diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 6c2e103d76..ab17742f43 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -93,7 +93,13 @@ typedef struct bool configured; }midih_interface_t; -static midih_interface_t _midi_host; +static midih_interface_t _midi_host[CFG_TUH_DEVICE_MAX]; + +static midih_interface_t *get_midi_host(uint8_t dev_addr) +{ + TU_ASSERT(dev_addr >0 && dev_addr <= CFG_TUH_DEVICE_MAX); + return (_midi_host + dev_addr - 1); +} //------------- Internal prototypes -------------// static uint32_t write_flush(uint8_t dev_addr, midih_interface_t* midi); @@ -105,20 +111,24 @@ void midih_init(void) { tu_memclr(&_midi_host, sizeof(_midi_host)); - // config fifo - tu_fifo_config(&_midi_host.rx_ff, _midi_host.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, 1, false); // true, true - tu_fifo_config(&_midi_host.tx_ff, _midi_host.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, 1, false); // OBVS. + // config fifos + for (int inst = 0; inst < CFG_TUH_DEVICE_MAX; inst++) + { + midih_interface_t *p_midi_host = &_midi_host[inst]; + tu_fifo_config(&p_midi_host->rx_ff, p_midi_host->rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, 1, false); // true, true + tu_fifo_config(&p_midi_host->tx_ff, p_midi_host->tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, 1, false); // OBVS. #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&_midi_host.rx_ff, NULL, osal_mutex_create(&_midi_host.rx_ff_mutex)); - tu_fifo_config_mutex(&_midi_host.tx_ff, osal_mutex_create(&_midi_host.tx_ff_mutex), NULL); + tu_fifo_config_mutex(&p_midi_host->rx_ff, NULL, osal_mutex_create(&p_midi_host->rx_ff_mutex)); + tu_fifo_config_mutex(&p_midi_host->tx_ff, osal_mutex_create(&p_midi_host->tx_ff_mutex), NULL); #endif + } } bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { - - if ( ep_addr == _midi_host.ep_in) + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + if ( ep_addr == p_midi_host->ep_in) { if (0 == xferred_bytes) { @@ -130,7 +140,7 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint if (xferred_bytes) { // put in the RX FIFO only non-zero MIDI IN 4-byte packets - uint8_t* buf = _midi_host.epin_buf; + uint8_t* buf = p_midi_host->epin_buf; uint32_t npackets = xferred_bytes / 4; uint32_t packet_num; for (packet_num = 0; packet_num < npackets; packet_num++) @@ -139,7 +149,7 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint uint32_t packet = (uint32_t)((*buf)<<24) | ((uint32_t)(*(buf+1))<<16) | ((uint32_t)(*(buf+2))<<8) | ((uint32_t)(*(buf+3))); if (packet != 0) { - tu_fifo_write_n(&_midi_host.rx_ff, buf, 4); + tu_fifo_write_n(&p_midi_host->rx_ff, buf, 4); ++packets_queued; TU_LOG3("MIDI RX=%08x\r\n", packet); } @@ -152,17 +162,17 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint tuh_midi_rx_cb(dev_addr, packets_queued); } } - else if ( ep_addr == _midi_host.ep_out ) + else if ( ep_addr == p_midi_host->ep_out ) { - if (0 == write_flush(dev_addr, &_midi_host)) + if (0 == write_flush(dev_addr, p_midi_host)) { // If there is no data left, a ZLP should be sent if // xferred_bytes is multiple of EP size and not zero - if ( !tu_fifo_count(&_midi_host.tx_ff) && xferred_bytes && (0 == (xferred_bytes % _midi_host.ep_out_max)) ) + if ( !tu_fifo_count(&p_midi_host->tx_ff) && xferred_bytes && (0 == (xferred_bytes % p_midi_host->ep_out_max)) ) { - if ( usbh_edpt_claim(dev_addr, _midi_host.ep_out) ) + if ( usbh_edpt_claim(dev_addr, p_midi_host->ep_out) ) { - TU_ASSERT(usbh_edpt_xfer(dev_addr, _midi_host.ep_out, XFER_RESULT_SUCCESS, 0)); + TU_ASSERT(usbh_edpt_xfer(dev_addr, p_midi_host->ep_out, XFER_RESULT_SUCCESS, 0)); } } } @@ -177,24 +187,22 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint void midih_close(uint8_t dev_addr) { - if (dev_addr == _midi_host.dev_addr) - { - if (tuh_midi_umount_cb) - tuh_midi_umount_cb(dev_addr, 0); - tu_fifo_clear(&_midi_host.rx_ff); - tu_fifo_clear(&_midi_host.tx_ff); - _midi_host.ep_in = 0; - _midi_host.ep_in_max = 0; - _midi_host.ep_out = 0; - _midi_host.ep_out_max = 0; - _midi_host.itf_num = 0; - _midi_host.num_cables_rx = 0; - _midi_host.num_cables_tx = 0; - _midi_host.dev_addr = 255; // invalid - _midi_host.configured = false; - tu_memclr(&_midi_host.stream_read, sizeof(_midi_host.stream_read)); - tu_memclr(&_midi_host.stream_read, sizeof(_midi_host.stream_write)); - } + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + if (tuh_midi_umount_cb) + tuh_midi_umount_cb(dev_addr, 0); + tu_fifo_clear(&p_midi_host->rx_ff); + tu_fifo_clear(&p_midi_host->tx_ff); + p_midi_host->ep_in = 0; + p_midi_host->ep_in_max = 0; + p_midi_host->ep_out = 0; + p_midi_host->ep_out_max = 0; + p_midi_host->itf_num = 0; + p_midi_host->num_cables_rx = 0; + p_midi_host->num_cables_tx = 0; + p_midi_host->dev_addr = 255; // invalid + p_midi_host->configured = false; + tu_memclr(&p_midi_host->stream_read, sizeof(p_midi_host->stream_read)); + tu_memclr(&p_midi_host->stream_write, sizeof(p_midi_host->stream_write)); } //--------------------------------------------------------------------+ @@ -203,6 +211,7 @@ void midih_close(uint8_t dev_addr) bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) { (void) rhport; + midih_interface_t *p_midi_host = get_midi_host(dev_addr); TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); // There can be just a MIDI interface or an audio and a MIDI interface. Only open the MIDI interface uint8_t const *p_desc = (uint8_t const *) desc_itf; @@ -238,7 +247,7 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d p_mdh->bDescriptorType == TUSB_DESC_ENDPOINT); uint8_t prev_ep_addr = 0; // the CS endpoint descriptor is associated with the previous endpoint descrptor - _midi_host.itf_num = desc_itf->bInterfaceNumber; + p_midi_host->itf_num = desc_itf->bInterfaceNumber; tusb_desc_endpoint_t const* in_desc = NULL; tusb_desc_endpoint_t const* out_desc = NULL; while (len_parsed < max_len) @@ -288,15 +297,15 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d midi_cs_desc_endpoint_t const* p_csep = (midi_cs_desc_endpoint_t const*)p_mdh; if (tu_edpt_dir(prev_ep_addr) == TUSB_DIR_OUT) { - TU_VERIFY(_midi_host.ep_out == prev_ep_addr); - TU_VERIFY(_midi_host.num_cables_tx == 0); - _midi_host.num_cables_tx = p_csep->bNumEmbMIDIJack; + TU_VERIFY(p_midi_host->ep_out == prev_ep_addr); + TU_VERIFY(p_midi_host->num_cables_tx == 0); + p_midi_host->num_cables_tx = p_csep->bNumEmbMIDIJack; } else { - TU_VERIFY(_midi_host.ep_in == prev_ep_addr); - TU_VERIFY(_midi_host.num_cables_rx == 0); - _midi_host.num_cables_rx = p_csep->bNumEmbMIDIJack; + TU_VERIFY(p_midi_host->ep_in == prev_ep_addr); + TU_VERIFY(p_midi_host->num_cables_rx == 0); + p_midi_host->num_cables_rx = p_csep->bNumEmbMIDIJack; } len_parsed += p_csep->bLength; prev_ep_addr = 0; @@ -307,24 +316,24 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d TU_LOG2("found ENDPOINT Descriptor for %u\r\n", p_ep->bEndpointAddress); if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { - TU_VERIFY(_midi_host.ep_out == 0); - TU_VERIFY(_midi_host.num_cables_tx == 0); - _midi_host.ep_out = p_ep->bEndpointAddress; - _midi_host.ep_out_max = p_ep->wMaxPacketSize; - if (_midi_host.ep_out_max > CFG_TUH_MIDI_TX_BUFSIZE) - _midi_host.ep_out_max = CFG_TUH_MIDI_TX_BUFSIZE; - prev_ep_addr = _midi_host.ep_out; + TU_VERIFY(p_midi_host->ep_out == 0); + TU_VERIFY(p_midi_host->num_cables_tx == 0); + p_midi_host->ep_out = p_ep->bEndpointAddress; + p_midi_host->ep_out_max = p_ep->wMaxPacketSize; + if (p_midi_host->ep_out_max > CFG_TUH_MIDI_TX_BUFSIZE) + p_midi_host->ep_out_max = CFG_TUH_MIDI_TX_BUFSIZE; + prev_ep_addr = p_midi_host->ep_out; out_desc = p_ep; } else { - TU_VERIFY(_midi_host.ep_in == 0); - TU_VERIFY(_midi_host.num_cables_rx == 0); - _midi_host.ep_in = p_ep->bEndpointAddress; - _midi_host.ep_in_max = p_ep->wMaxPacketSize; - if (_midi_host.ep_in_max > CFG_TUH_MIDI_RX_BUFSIZE) - _midi_host.ep_in_max = CFG_TUH_MIDI_RX_BUFSIZE; - prev_ep_addr = _midi_host.ep_in; + TU_VERIFY(p_midi_host->ep_in == 0); + TU_VERIFY(p_midi_host->num_cables_rx == 0); + p_midi_host->ep_in = p_ep->bEndpointAddress; + p_midi_host->ep_in_max = p_ep->wMaxPacketSize; + if (p_midi_host->ep_in_max > CFG_TUH_MIDI_RX_BUFSIZE) + p_midi_host->ep_in_max = CFG_TUH_MIDI_RX_BUFSIZE; + prev_ep_addr = p_midi_host->ep_in; in_desc = p_ep; } len_parsed += p_mdh->bLength; @@ -332,8 +341,8 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d p_desc = tu_desc_next(p_desc); p_mdh = (midi_desc_header_t const *)p_desc; } - TU_VERIFY((_midi_host.ep_out != 0 && _midi_host.num_cables_tx != 0) || - (_midi_host.ep_in != 0 && _midi_host.num_cables_rx != 0)); + TU_VERIFY((p_midi_host->ep_out != 0 && p_midi_host->num_cables_tx != 0) || + (p_midi_host->ep_in != 0 && p_midi_host->num_cables_rx != 0)); TU_LOG1("MIDI descriptor parsed successfully\r\n"); if (in_desc) @@ -341,30 +350,32 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d TU_ASSERT(usbh_edpt_open(rhport, dev_addr, in_desc)); // Some devices always return exactly the request length so transfers won't complete // unless you assume every transfer is the last one. - usbh_edpt_force_last_buffer(dev_addr, _midi_host.ep_in, true); + usbh_edpt_force_last_buffer(dev_addr, p_midi_host->ep_in, true); } if (out_desc) { TU_ASSERT(usbh_edpt_open(rhport, dev_addr, out_desc)); } - _midi_host.dev_addr = dev_addr; + p_midi_host->dev_addr = dev_addr; if (tuh_midi_mount_cb) { - tuh_midi_mount_cb(dev_addr, _midi_host.ep_in, _midi_host.ep_out, _midi_host.num_cables_rx, _midi_host.num_cables_tx); + tuh_midi_mount_cb(dev_addr, p_midi_host->ep_in, p_midi_host->ep_out, p_midi_host->num_cables_rx, p_midi_host->num_cables_tx); } return true; } -bool tuh_midi_configured(void) +bool tuh_midi_configured(uint8_t dev_addr) { - return _midi_host.configured; + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + return p_midi_host->configured; } + bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) { (void) itf_num; - if (dev_addr == _midi_host.dev_addr) - _midi_host.configured = true; + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + p_midi_host->configured = true; // TODO I don't think there are any special config things to do for MIDI return true; @@ -395,30 +406,32 @@ static uint32_t write_flush(uint8_t dev_addr, midih_interface_t* midi) } } -bool tuh_midi_read_poll( void ) +bool tuh_midi_read_poll( uint8_t dev_addr ) { + midih_interface_t *p_midi_host = get_midi_host(dev_addr); // MIDI bulk endpoints are shared with the control endpoints. None can be busy before we start a transfer - bool control_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,0) && !usbh_edpt_busy(_midi_host.dev_addr,0x80); + bool control_edpt_not_busy = !usbh_edpt_busy(dev_addr,0) && !usbh_edpt_busy(dev_addr,0x80); bool out_edpt_not_busy = true; bool result = false; - if (_midi_host.num_cables_tx > 0) - out_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_out); - if (!usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_in) && control_edpt_not_busy && out_edpt_not_busy) + if (p_midi_host->num_cables_tx > 0) + out_edpt_not_busy = !usbh_edpt_busy(p_midi_host->dev_addr, p_midi_host->ep_out); + if (!usbh_edpt_busy(dev_addr, p_midi_host->ep_in) && control_edpt_not_busy && out_edpt_not_busy) { - TU_LOG3("Requesting poll IN endpoint %d\r\n", _midi_host.ep_in); - TU_ASSERT(usbh_edpt_xfer(_midi_host.dev_addr, _midi_host.ep_in, _midi_host.epin_buf, _midi_host.ep_in_max), 0); + TU_LOG3("Requesting poll IN endpoint %d\r\n", p_midi_host->ep_in); + TU_ASSERT(usbh_edpt_xfer(p_midi_host->dev_addr, p_midi_host->ep_in, _midi_host->epin_buf, _midi_host->ep_in_max), 0); result = true; } return result; } -uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) +uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) { - TU_VERIFY(cable_num < _midi_host.num_cables_tx); - midi_stream_t stream = _midi_host.stream_write; + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + TU_VERIFY(cable_num < p_midi_host->num_cables_tx); + midi_stream_t stream = p_midi_host->stream_write; uint32_t i = 0; - while ( (i < bufsize) && (tu_fifo_remaining(&_midi_host.tx_ff) >= 4) ) + while ( (i < bufsize) && (tu_fifo_remaining(&p_midi_host->tx_ff) >= 4) ) { uint8_t const data = buffer[i]; i++; @@ -513,7 +526,7 @@ uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32 // zeroes unused bytes for(uint8_t idx = stream.total; idx < 4; idx++) stream.buffer[idx] = 0; - uint16_t const count = tu_fifo_write_n(&_midi_host.tx_ff, stream.buffer, 4); + uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, stream.buffer, 4); // complete current event packet, reset stream stream.index = 0; @@ -526,151 +539,151 @@ uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32 return i; } -uint32_t tuh_midi_stream_flush( void ) +uint32_t tuh_midi_stream_flush( uint8_t dev_addr ) { - - bool control_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,0) && !usbh_edpt_busy(_midi_host.dev_addr,0x80); + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + bool control_edpt_not_busy = !usbh_edpt_busy(dev_addr,0) && !usbh_edpt_busy(dev_addr,0x80); bool in_edpt_not_busy = true; uint32_t bytes_flushed = 0; - if (_midi_host.num_cables_rx > 0) - in_edpt_not_busy = !usbh_edpt_busy(_midi_host.dev_addr,_midi_host.ep_in); - if (control_edpt_not_busy && in_edpt_not_busy && !usbh_edpt_busy(_midi_host.dev_addr, _midi_host.ep_out)) + if (p_midi_host->num_cables_rx > 0) + in_edpt_not_busy = !usbh_edpt_busy(dev_addr, p_midi_host->ep_in); + if (control_edpt_not_busy && in_edpt_not_busy && !usbh_edpt_busy(p_midi_host->dev_addr, p_midi_host->ep_out)) { - bytes_flushed = write_flush(_midi_host.dev_addr, &_midi_host); + bytes_flushed = write_flush(dev_addr, p_midi_host); } return bytes_flushed; } //--------------------------------------------------------------------+ // Helper //--------------------------------------------------------------------+ -uint8_t tuh_midih_get_num_tx_cables (void) +uint8_t tuh_midih_get_num_tx_cables (uint8_t dev_addr) { - TU_VERIFY(_midi_host.ep_out != 0); // returns 0 if fails - return _midi_host.num_cables_tx; + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + TU_VERIFY(p_midi_host->ep_out != 0); // returns 0 if fails + return p_midi_host->num_cables_tx; } -uint8_t tuh_midih_get_num_rx_cables (void) +uint8_t tuh_midih_get_num_rx_cables (uint8_t dev_addr) { - TU_VERIFY(_midi_host.ep_in != 0); // returns 0 if fails - return _midi_host.num_cables_rx; + midih_interface_t *p_midi_host = get_midi_host(dev_addr); + TU_VERIFY(p_midi_host->ep_in != 0); // returns 0 if fails + return p_midi_host->num_cables_rx; } uint32_t tuh_midi_stream_read (uint8_t dev_addr, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize) { + midih_interface_t *p_midi_host = get_midi_host(dev_addr); uint32_t bytes_buffered = 0; - if (_midi_host.dev_addr == dev_addr) + TU_ASSERT(p_cable_num); + TU_ASSERT(p_buffer); + TU_ASSERT(bufsize); + uint8_t one_byte; + if (!tu_fifo_peek(&p_midi_host->rx_ff, &one_byte)) { - TU_ASSERT(p_cable_num); - TU_ASSERT(p_buffer); - TU_ASSERT(bufsize); - uint8_t one_byte; - if (!tu_fifo_peek(&_midi_host.rx_ff, &one_byte)) - { - return 0; - } - *p_cable_num = (one_byte >> 4) & 0xf; - uint32_t nread = tu_fifo_read_n(&_midi_host.rx_ff, _midi_host.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) + return 0; + } + *p_cable_num = (one_byte >> 4) & 0xf; + uint32_t nread = tu_fifo_read_n(&p_midi_host->rx_ff, p_midi_host->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_host->stream_read.buffer[0] & 0xf) >> 4; + uint8_t bytes_to_add_to_stream = 0; + if (*p_cable_num < p_midi_host->num_cables_rx) { - *p_cable_num=(_midi_host.stream_read.buffer[0] & 0xf) >> 4; - uint8_t bytes_to_add_to_stream = 0; - if (*p_cable_num < _midi_host.num_cables_rx) + // ignore the CIN field; too many devices out there encode this wrong + uint8_t status = p_midi_host->stream_read.buffer[1]; + uint16_t cable_mask = 1 << *p_cable_num; + if (status <= MIDI_MAX_DATA_VAL || status == MIDI_STATUS_SYSEX_START) { - // ignore the CIN field; too many devices out there encode this wrong - uint8_t status = _midi_host.stream_read.buffer[1]; - uint16_t cable_mask = 1 << *p_cable_num; - if (status <= MIDI_MAX_DATA_VAL || status == MIDI_STATUS_SYSEX_START) + if (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; - uint8_t idx; - for (idx = 2; idx < 4; idx++) - { - if (_midi_host.stream_read.buffer[idx] <= MIDI_MAX_DATA_VAL) - { - ++bytes_to_add_to_stream; - } - else if (_midi_host.stream_read.buffer[idx] == MIDI_STATUS_SYSEX_END) - { - ++bytes_to_add_to_stream; - cable_sysex_in_progress &= ~cable_mask; - idx = 4; // force the loop to exit; I hate break statements in loops - } - } - } + cable_sysex_in_progress |= cable_mask; } - else if (status < MIDI_STATUS_SYSEX_START) + // only add the packet if a sysex message is in progress + if (cable_sysex_in_progress & cable_mask) { - // then it is a channel message either three bytes or two - uint8_t fake_cin = (status & 0xf0) >> 4; - switch (fake_cin) + ++bytes_to_add_to_stream; + uint8_t idx; + for (idx = 2; idx < 4; idx++) { - 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 + if (p_midi_host->stream_read.buffer[idx] <= MIDI_MAX_DATA_VAL) + { + ++bytes_to_add_to_stream; + } + else if (p_midi_host->stream_read.buffer[idx] == MIDI_STATUS_SYSEX_END) + { + ++bytes_to_add_to_stream; + cable_sysex_in_progress &= ~cable_mask; + idx = 4; // force the loop to exit; I hate break statements in loops + } } - cable_sysex_in_progress &= ~cable_mask; } - else if (status < MIDI_STATUS_SYSREAL_TIMING_CLOCK) + } + 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) { - 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 &= ~cable_mask; - } + 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 } - else + cable_sysex_in_progress &= ~cable_mask; + } + else if (status < MIDI_STATUS_SYSREAL_TIMING_CLOCK) + { + switch (status) { - // 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; + 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 &= ~cable_mask; } } - uint8_t idx; - for (idx = 1; idx <= bytes_to_add_to_stream; idx++) + else { - *p_buffer++ = _midi_host.stream_read.buffer[idx]; + // 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; } - bytes_buffered += bytes_to_add_to_stream; - nread = 0; - if (tu_fifo_peek(&_midi_host.rx_ff, &one_byte)) + } + uint8_t idx; + for (idx = 1; idx <= bytes_to_add_to_stream; idx++) + { + *p_buffer++ = p_midi_host->stream_read.buffer[idx]; + } + bytes_buffered += bytes_to_add_to_stream; + nread = 0; + if (tu_fifo_peek(&p_midi_host->rx_ff, &one_byte)) + { + uint8_t new_cable = (one_byte >> 4) & 0xf; + if (new_cable == *p_cable_num) { - 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_fifo_read_n(&_midi_host.rx_ff, _midi_host.stream_read.buffer, 4); - } + // still on the same cable. Continue reading the stream + nread = tu_fifo_read_n(&p_midi_host->rx_ff, p_midi_host->stream_read.buffer, 4); } } } diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h index 67891d2cb5..870e27c6f6 100644 --- a/src/class/midi/midi_host.h +++ b/src/class/midi/midi_host.h @@ -51,31 +51,31 @@ //--------------------------------------------------------------------+ // Application API (Single Interface) //--------------------------------------------------------------------+ -bool tuh_midi_configured (void); -uint32_t tuh_midi_available (void); +bool tuh_midi_configured (uint8_t dev_addr); +uint32_t tuh_midi_available (uint8_t dev_addr); // return the number of virtual midi cables on the device's OUT endpoint -uint8_t tuh_midih_get_num_tx_cables (void); +uint8_t tuh_midih_get_num_tx_cables (uint8_t dev_addr); // return the number of virtual midi cables on the device's IN endpoint -uint8_t tuh_midih_get_num_rx_cables (void); +uint8_t tuh_midih_get_num_rx_cables (uint8_t dev_addr); // request available data from the device. tuh_midi_message_received_cb() will // be called if the device has any data to send. Otherwise, the device will // respond NAK. This function blocks until the transfer completes or the // devices sends NAK. // This function will return false if the hardware is busy. -bool tuh_midi_read_poll( void ); +bool tuh_midi_read_poll( uint8_t dev_addr ); // Queue a message to the device. The application // must call tuh_midi_stream_flush to actually have the // data go out. -uint32_t tuh_midi_stream_write (uint8_t cable_num, uint8_t const* p_buffer, uint32_t bufsize); +uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t const* p_buffer, uint32_t bufsize); // 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_stream_flush( void); +uint32_t tuh_midi_stream_flush( uint8_t dev_addr); // 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. From 946a108c58e5d1b50c6a8bfb5052230d1e915b1e Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 21 Dec 2021 19:19:52 -0800 Subject: [PATCH 27/35] Disable RP2040 auto NAK retry for bulk IN transfers --- src/class/midi/midi_host.c | 21 ++++-- src/host/hcd.h | 1 + src/host/usbh.c | 5 ++ src/host/usbh.h | 7 +- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 68 +++++++++++++++++++- src/portable/raspberrypi/rp2040/rp2040_usb.c | 2 +- 6 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index ab17742f43..5939880165 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -376,6 +376,7 @@ bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) (void) itf_num; midih_interface_t *p_midi_host = get_midi_host(dev_addr); p_midi_host->configured = true; + // TODO I don't think there are any special config things to do for MIDI return true; @@ -415,11 +416,23 @@ bool tuh_midi_read_poll( uint8_t dev_addr ) bool result = false; if (p_midi_host->num_cables_tx > 0) out_edpt_not_busy = !usbh_edpt_busy(p_midi_host->dev_addr, p_midi_host->ep_out); - if (!usbh_edpt_busy(dev_addr, p_midi_host->ep_in) && control_edpt_not_busy && out_edpt_not_busy) + if (control_edpt_not_busy && out_edpt_not_busy) { - TU_LOG3("Requesting poll IN endpoint %d\r\n", p_midi_host->ep_in); - TU_ASSERT(usbh_edpt_xfer(p_midi_host->dev_addr, p_midi_host->ep_in, _midi_host->epin_buf, _midi_host->ep_in_max), 0); - result = true; + bool in_edpt_not_busy = !usbh_edpt_busy(dev_addr, p_midi_host->ep_in); + if (in_edpt_not_busy) + { + TU_LOG2("Requesting poll IN endpoint %d\r\n", p_midi_host->ep_in); + TU_ASSERT(usbh_edpt_xfer(p_midi_host->dev_addr, p_midi_host->ep_in, _midi_host->epin_buf, _midi_host->ep_in_max), 0); + result = true; + } + else + { + // Maybe the IN endpoint is only busy because the RP2040 host hardware + // is retrying a NAK'd IN transfer forever. Try aborting the NAK'd + // transfer to allow other transfers to happen on the one shared + // epx endpoint. + usbh_edpt_clear_in_on_nak(p_midi_host->dev_addr, p_midi_host->ep_in); + } } return result; } diff --git a/src/host/hcd.h b/src/host/hcd.h index ac9d0cfa55..70b1ea15e6 100644 --- a/src/host/hcd.h +++ b/src/host/hcd.h @@ -149,6 +149,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr); TU_ATTR_WEAK void hcd_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force); +TU_ATTR_WEAK void hcd_edpt_clear_in_on_nak(uint8_t dev_addr, uint8_t ep_addr); //--------------------------------------------------------------------+ // USBH implemented API //--------------------------------------------------------------------+ diff --git a/src/host/usbh.c b/src/host/usbh.c index 9735885761..05a9d3397a 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -1216,4 +1216,9 @@ void usbh_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force) hcd_edpt_force_last_buffer(dev_addr, ep_addr, force); } +void usbh_edpt_clear_in_on_nak(uint8_t dev_addr, uint8_t ep_addr) +{ + if (hcd_edpt_clear_in_on_nak) + hcd_edpt_clear_in_on_nak(dev_addr, ep_addr); +} #endif diff --git a/src/host/usbh.h b/src/host/usbh.h index ad82c15acd..2156b2597c 100644 --- a/src/host/usbh.h +++ b/src/host/usbh.h @@ -81,8 +81,13 @@ static inline bool tuh_ready(uint8_t dev_addr) // Carry out control transfer bool tuh_control_xfer (uint8_t dev_addr, tusb_control_request_t const* request, void* buffer, tuh_control_complete_cb_t complete_cb); - +// For bulk transfer devices that might not properly signal the end of a transfer void usbh_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force); + +// Clear IN endpoint busy due to NAK if possible by sending +// a transfer complete message with 0 length data +void usbh_edpt_clear_in_on_nak(uint8_t dev_addr, uint8_t ep_addr); + //--------------------------------------------------------------------+ // APPLICATION CALLBACK //--------------------------------------------------------------------+ diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index afcf563dfb..83918f95d9 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -67,6 +67,10 @@ enum { USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS }; +// See hcd_edpt_clear_in_on_nak() for an explanation of this hack on top of a hack +#define SKIP_CLEAR_IN_ON_NAK_COUNT 20 +static uint8_t skip_hcd_edpt_clear_in_on_nak = SKIP_CLEAR_IN_ON_NAK_COUNT; // for some reason doing this algorithm right away doesn't work + static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) { uint8_t num = tu_edpt_number(ep_addr); @@ -93,7 +97,7 @@ static inline void clear_nak_received(void) static inline bool nak_received() { - return (usb_hw->sie_status & USB_SIE_STATUS_NAK_REC_BITS) != 0; + return ((usb_hw->sie_status & USB_SIE_STATUS_NAK_REC_BITS) != 0); } static bool need_pre(uint8_t dev_addr) @@ -110,6 +114,7 @@ static void hw_xfer_complete(struct hw_endpoint *ep, xfer_result_t xfer_result) uint8_t ep_addr = ep->ep_addr; uint xferred_len = ep->xferred_len; hw_endpoint_reset_transfer(ep); + hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); } @@ -456,7 +461,7 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) (void) rhport; if (dev_addr == 0) return; - + skip_hcd_edpt_clear_in_on_nak = SKIP_CLEAR_IN_ON_NAK_COUNT; for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { hw_endpoint_t* ep = &ep_pool[i]; @@ -540,6 +545,13 @@ static void _hw_endpoint_reinit_epx(struct hw_endpoint *ep) *ep->endpoint_control = ep_reg; pico_trace("endpoint control (0x%p) <- 0x%x\n", ep->endpoint_control, ep_reg); + if (nak_received()) + clear_nak_received(); + if (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN) + { + uint32_t nak_poll_delay = 1000; // set to one ms. + usb_hw->nak_poll = (nak_poll_delay << USB_NAK_POLL_DELAY_FS_LSB) | (nak_poll_delay << USB_NAK_POLL_DELAY_LS_LSB); + } } bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) @@ -676,4 +688,56 @@ void hcd_edpt_force_last_buffer(uint8_t dev_addr, uint8_t ep_addr, bool force) assert(ep); ep->force_last_buff = force; } + +// This function is a hack. It appears that the RP2040 host +// controller will keep retrying a NAK'd bulk transfer forever +// until the transfer completes. This function detects that +// the sie status register NAK bit is set, stops the +// current transaction, clears the NAK status, and fixes +// up the endpoint so we can safely start a new transaction +void hcd_edpt_clear_in_on_nak(uint8_t dev_addr, uint8_t ep_addr) +{ + struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr); + if (ep == _current_epx_endpoint) + { + if (nak_received() && (usb_hw->ints & (USB_INTS_BUFF_STATUS_BITS | USB_INTS_TRANS_COMPLETE_BITS)) == 0) + { + if (skip_hcd_edpt_clear_in_on_nak > 0) + { + --skip_hcd_edpt_clear_in_on_nak; + clear_nak_received(); + return; + } + // Disable USB interrupts in case there is an in-flight transaction + uint32_t temp_inte = usb_hw->inte; + usb_hw->inte = 0; + // clear the NAK status bit + clear_nak_received(); + // Wait for either the next nak or any raw irq that would interrupt the USB + // The amount of time between host NAK retries is set in the nak_poll register + uint32_t masked_ints = (usb_hw->intr & temp_inte); + while (!nak_received() && masked_ints == 0) + masked_ints = (usb_hw->intr & temp_inte); + if (masked_ints == 0 && nak_received()) + { + // stop the current transaction to free up the host epx HW + usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS; + // clear the NAK status bit + clear_nak_received(); + // There was no transfer, but the logic that sets up the buffer + // control register needs to see USB_BUF_CTRL_AVAIL bit clear + // or else it will assert. + _hw_endpoint_buffer_control_clear_mask32(ep, USB_BUF_CTRL_AVAIL); + // Fixup next PID: prepare_ep_buffer() toggles the PID, but there was + // no successful data transfer, so we need to toggle it here so that + // it is back where we started. + ep->next_pid ^= 1u; + // Notify host stack that the transfer is done (clears busy) + hw_xfer_complete(ep, XFER_RESULT_SUCCESS); + } + // restore the USB interrupts; a transaction is in process + usb_hw->inte = temp_inte; + } + } +} #endif diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 763ed00cb6..0e12359b9d 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -127,7 +127,7 @@ static uint32_t prepare_ep_buffer(struct hw_endpoint *ep, uint8_t buf_id) // Is this the last buffer? Only really matters for host mode. Will trigger // the trans complete irq but also stop it polling. We only really care about - // trans complete for setup packets being sent + // trans complete for setup packets being sent and for bulk transfers #if TUSB_OPT_HOST_ENABLED if (ep->remaining_len == 0 || ep->force_last_buff) #else From d5b2f0406ded9eb3ff7d8e3a3703eb536f9b5f61 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 21 Dec 2021 19:20:43 -0800 Subject: [PATCH 28/35] Flush the transmit queue right after a successful IN transfer --- examples/host/midi/src/midi_app.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index a4cd4ee8f7..5e2d6344a7 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -31,6 +31,7 @@ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ static uint8_t midi_dev_addr = 0; + static void test_tx(void) { // toggle NOTE On, Note Off for the Mackie Control channels 1-8 REC LED @@ -149,13 +150,17 @@ void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance) void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) { - if (num_packets != 0 && midi_dev_addr == dev_addr) + if (midi_dev_addr == dev_addr) { - uint8_t cable_num; - uint8_t buffer[48]; - uint32_t bytes_read = tuh_midi_stream_read(dev_addr, &cable_num, buffer, sizeof(buffer)); - TU_LOG1("Read bytes %u cable %u", bytes_read, cable_num); - TU_LOG1_MEM(buffer, bytes_read, 2); + if (num_packets != 0) + { + uint8_t cable_num; + uint8_t buffer[48]; + uint32_t bytes_read = tuh_midi_stream_read(dev_addr, &cable_num, buffer, sizeof(buffer)); + TU_LOG1("Read bytes %u cable %u", bytes_read, cable_num); + TU_LOG1_MEM(buffer, bytes_read, 2); + } + tuh_midi_stream_flush(midi_dev_addr); } } From 89beebffafc6cd03a59e2f17eb084e4fff51d075 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Wed, 22 Dec 2021 16:49:53 -0800 Subject: [PATCH 29/35] fix cable number bug on receive --- src/class/midi/midi_host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 5939880165..7562ed3b0e 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -601,7 +601,7 @@ uint32_t tuh_midi_stream_read (uint8_t dev_addr, uint8_t *p_cable_num, uint8_t * 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_host->stream_read.buffer[0] & 0xf) >> 4; + *p_cable_num=(p_midi_host->stream_read.buffer[0] >> 4) & 0x0f; uint8_t bytes_to_add_to_stream = 0; if (*p_cable_num < p_midi_host->num_cables_rx) { From b7af5efb10c98405ceeed37ecf8b67397799ef25 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Wed, 22 Dec 2021 16:50:44 -0800 Subject: [PATCH 30/35] Shorten NAK auto-retry interval to 100us --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 83918f95d9..f9418be08d 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -549,7 +549,7 @@ static void _hw_endpoint_reinit_epx(struct hw_endpoint *ep) clear_nak_received(); if (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN) { - uint32_t nak_poll_delay = 1000; // set to one ms. + uint32_t nak_poll_delay = 100; // increase the spacing between NAK auto-retries usb_hw->nak_poll = (nak_poll_delay << USB_NAK_POLL_DELAY_FS_LSB) | (nak_poll_delay << USB_NAK_POLL_DELAY_LS_LSB); } } From 5eeee324f932d4ff7af201cadde9f33b10ef55fa Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Wed, 22 Dec 2021 16:51:23 -0800 Subject: [PATCH 31/35] Remove unnecessary midi stream flush --- examples/host/midi/src/midi_app.c | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index 5e2d6344a7..6d1fdef810 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -160,7 +160,6 @@ void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets) TU_LOG1("Read bytes %u cable %u", bytes_read, cable_num); TU_LOG1_MEM(buffer, bytes_read, 2); } - tuh_midi_stream_flush(midi_dev_addr); } } From 5ad9e8cf8e4fd07eb8ec5d3f12dea72d5ea4f188 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Wed, 22 Dec 2021 16:52:19 -0800 Subject: [PATCH 32/35] Make example more keyboard friendly --- examples/host/midi/src/midi_app.c | 122 +++++++++++++++++++----------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/examples/host/midi/src/midi_app.c b/examples/host/midi/src/midi_app.c index 6d1fdef810..c2fc4132a4 100644 --- a/examples/host/midi/src/midi_app.c +++ b/examples/host/midi/src/midi_app.c @@ -23,14 +23,51 @@ * */ +/** + * This test program is designed to send and receive MIDI data concurrently + * It send G, G#, A, A#, B over and over again on the highest cable number + * of the MIDI device. The notes correspond to the Mackie Control transport + * button LEDs, so most keyboards and controllers will react to these note + * messages from the host. The host will print on the serial port console + * any incoming messages along with the cable number that sent them. + * + * If you define MIDIH_TEST_KORG_NANOKONTROL2 to 1 and connect a Korg + * nanoKONTROL2 controller to the host, then the host will request a dump + * of the current scene, and the nanoKONTROL2 will respond with a long + * sysex message (used to prove that sysex decoding appears to work) + */ #include "bsp/board.h" #include "tusb.h" #include "class/midi/midi_host.h" - //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ +#ifndef MIDIH_TEST_KORG_NANOKONTROL2 +#define MIDIH_TEST_KORG_NANOKONTROL2 0 +#endif + +#define MIDIH_TEST_KORG_NANOKONTROL2_COUNT 5 +//--------------------------------------------------------------------+ +// STATIC GLOBALS DECLARATION +//--------------------------------------------------------------------+ static uint8_t midi_dev_addr = 0; +static uint8_t first_note = 0x5b; // Mackie Control rewind +static uint8_t last_note = 0x5f; // Mackie Control stop +static uint8_t message[6] = +{ + 0x90, 0x5f, 0x00, + 0x90, 0x5b, 0x7f, +}; +#if MIDIH_TEST_KORG_NANOKONTROL2 +// This command will dump a scene from a Kork nanoKONTROL2 control surface +// Use it to test receiving long sysex messages +static uint8_t nanoKONTROL2_dump_req[] = +{ + 0xf0, 0x42, 0x40, 0x00, 0x01, 0x13, 0x00, 0x1f, 0x10, 0x00, 0xf7 +}; +// wait MIDIH_TEST_KORG_NANOKONTROL2_COUNT seconds before requesting a dump +static uint8_t dump_req_countdown = MIDIH_TEST_KORG_NANOKONTROL2_COUNT; +#endif static void test_tx(void) { @@ -38,18 +75,6 @@ static void test_tx(void) const uint32_t interval_ms = 1000; static uint32_t start_ms = 0; - static uint8_t message[] = - { - 0x90, 0x00, 0x7f, - 0x90, 0x01, 0x7f, - 0x90, 0x02, 0x7f, - 0x90, 0x03, 0x7f, - 0x90, 0x04, 0x7f, - 0x90, 0x05, 0x7f, - 0x90, 0x06, 0x7f, - 0x90, 0x07, 0x7f, - }; - // device must be attached and have at least one endpoint ready to receive a message if (!midi_dev_addr || !tuh_midi_configured(midi_dev_addr)) { @@ -68,39 +93,45 @@ static void test_tx(void) } start_ms += interval_ms; - uint32_t nwritten = tuh_midi_stream_write(midi_dev_addr, 0, message, sizeof(message)); - - char off_on[4] = {'O','n','\0'}; - if (nwritten != 0) + uint32_t nwritten = 0; +#if MIDIH_TEST_KORG_NANOKONTROL2 + if (dump_req_countdown == 0) { - if (message[2] == 0x7f) + nwritten = tuh_midi_stream_write(midi_dev_addr, 0, nanoKONTROL2_dump_req, sizeof(nanoKONTROL2_dump_req)); + if (nwritten != sizeof(nanoKONTROL2_dump_req)) { - off_on[1] = 'n'; - off_on[2] = '\0'; - message[2] = 0; - message[5] = 0; - message[8] = 0; - message[11] = 0; - message[14] = 0; - message[17] = 0; - message[20] = 0; - message[23] = 0; + _MESS_FAILED(); + TU_BREAKPOINT(); } - else - { - off_on[1] = 'f'; - off_on[2] = 'f'; - off_on[3] = '\0'; - message[2] = 0x7f; - message[5] = 0x7f; - message[8] = 0x7f; - message[11] = 0x7f; - message[14] = 0x7f; - message[17] = 0x7f; - message[20] = 0x7f; - message[23] = 0x7f; - } - TU_LOG1("Switched lights %s\r\n", off_on); + dump_req_countdown = MIDIH_TEST_KORG_NANOKONTROL2_COUNT; + } + else + { + nwritten = 0; + nwritten += tuh_midi_stream_write(midi_dev_addr, 0, message, sizeof(message)); + --dump_req_countdown; + } +#else + uint8_t cable = tuh_midih_get_num_tx_cables(midi_dev_addr) - 1; + nwritten = 0; + /// @bug For some reason, Arturia Keylab-88 ignores these messages if + /// they are transimitted on cable 0 first + //for (cable = 0; cable < tuh_midih_get_num_tx_cables(midi_dev_addr); cable++) + nwritten += tuh_midi_stream_write(midi_dev_addr, cable, message, sizeof(message)); +#endif + +#if MIDIH_TEST_KORG_NANOKONTROL2 + if (nwritten != 0 && dump_req_countdown != MIDIH_TEST_KORG_NANOKONTROL2_COUNT) +#else + if (nwritten != 0) +#endif + { + ++message[1]; + ++message[4]; + if (message[1] > last_note) + message[1] = first_note; + if (message[4] > last_note) + message[4] = first_note; } } @@ -138,6 +169,11 @@ void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep, uint8_t { printf("MIDI device address = %u, IN endpoint %u has %u cables, OUT endpoint %u has %u cables\r\n", dev_addr, in_ep & 0xf, num_cables_rx, out_ep & 0xf, num_cables_tx); + message[1] = last_note; + message[4] = first_note; +#if MIDIH_TEST_KORG_NANOKONTROL2 + dump_req_countdown = MIDIH_TEST_KORG_NANOKONTROL2_COUNT; +#endif midi_dev_addr = dev_addr; } From 0ab21272a68473036649da46333f646c592a7885 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 11 Jan 2022 07:58:43 -0800 Subject: [PATCH 33/35] Handle real time system messages mid stream --- src/class/midi/midi_host.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 7562ed3b0e..5455094437 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -448,8 +448,19 @@ uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t con { uint8_t const data = buffer[i]; i++; - - if ( stream.index == 0 ) + if (data >= MIDI_STATUS_SYSREAL_TIMING_CLOCK) + { + // real-time messages need to be sent right away + midi_stream_t streamrt; + streamrt.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + streamrt.buffer[1] = data; + streamrt.index = 2; + streamrt.total = 2; + uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, stream->buffer, 4); + // FIFO overflown, since we already check fifo remaining. It is probably race condition + TU_ASSERT(count == 4, i); + } + else if ( stream->index == 0 ) { //------------- New event packet -------------// From 95020e42aa2c68ccce844449a2302a605d424094 Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 11 Jan 2022 07:59:09 -0800 Subject: [PATCH 34/35] handle sending incomplete streams --- src/class/midi/midi_host.c | 72 +++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index 5455094437..bc25ec1297 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -441,7 +441,7 @@ uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t con { midih_interface_t *p_midi_host = get_midi_host(dev_addr); TU_VERIFY(cable_num < p_midi_host->num_cables_tx); - midi_stream_t stream = p_midi_host->stream_write; + midi_stream_t *stream = &p_midi_host->stream_write; uint32_t i = 0; while ( (i < bufsize) && (tu_fifo_remaining(&p_midi_host->tx_ff) >= 4) ) @@ -466,95 +466,95 @@ uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t con uint8_t const msg = data >> 4; - stream.index = 2; - stream.buffer[1] = data; + 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 ( stream->buffer[0] == MIDI_CIN_SYSEX_START ) { if ( data == MIDI_STATUS_SYSEX_END ) { - stream.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; - stream.total = 2; + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; } else { - stream.total = 4; + stream->total = 4; } } else if ( (msg >= 0x8 && msg <= 0xB) || msg == 0xE ) { // Channel Voice Messages - stream.buffer[0] = (cable_num << 4) | msg; - stream.total = 4; + stream->buffer[0] = (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] = (cable_num << 4) | msg; - stream.total = 3; + stream->buffer[0] = (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; + 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; + 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; + stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE; + stream->total = 4; } else { - stream.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; - stream.total = 2; + 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] = cable_num << 4 | 0xf; - stream.buffer[2] = 0; - stream.buffer[3] = 0; - stream.index = 2; - stream.total = 2; + stream->buffer[0] = 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, i); - stream.buffer[stream.index] = data; - stream.index++; - + TU_ASSERT(stream->index < 4, i); + 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 ) + 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; + stream->buffer[0] = MIDI_CIN_SYSEX_START + (stream->index - 1); + stream->total = stream->index; } } // Send out packet - if ( stream.index == stream.total ) + if ( stream->index >= 2 && stream->index == stream->total ) { // zeroes unused bytes - for(uint8_t idx = stream.total; idx < 4; idx++) stream.buffer[idx] = 0; + for(uint8_t idx = stream->total; idx < 4; idx++) stream->buffer[idx] = 0; + TU_LOG1_MEM(stream->buffer, 4, 2); - uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, stream.buffer, 4); + uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, stream->buffer, 4); // complete current event packet, reset stream - stream.index = 0; - stream.total = 0; + stream->index = 0; + stream->total = 0; // FIFO overflown, since we already check fifo remaining. It is probably race condition TU_ASSERT(count == 4, i); From 537150f931d218e16b88dc5ac50a625622812c2b Mon Sep 17 00:00:00 2001 From: rppicomidi Date: Tue, 11 Jan 2022 10:34:13 -0800 Subject: [PATCH 35/35] Fix typos in midi_host.c --- src/class/midi/midi_host.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c index bc25ec1297..c0f0599bd7 100644 --- a/src/class/midi/midi_host.c +++ b/src/class/midi/midi_host.c @@ -456,7 +456,7 @@ uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t con streamrt.buffer[1] = data; streamrt.index = 2; streamrt.total = 2; - uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, stream->buffer, 4); + uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, streamrt.buffer, 4); // FIFO overflown, since we already check fifo remaining. It is probably race condition TU_ASSERT(count == 4, i); } @@ -548,7 +548,7 @@ uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t con { // zeroes unused bytes for(uint8_t idx = stream->total; idx < 4; idx++) stream->buffer[idx] = 0; - TU_LOG1_MEM(stream->buffer, 4, 2); + TU_LOG3_MEM(stream->buffer, 4, 2); uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, stream->buffer, 4);