diff options
| author | HiFiPhile <[email protected]> | 2026-06-02 10:46:13 +0200 |
|---|---|---|
| committer | HiFiPhile <[email protected]> | 2026-06-02 11:03:50 +0200 |
| commit | 64af21f930db48f4dfbe8c0d4c4cb0fabf839ca6 (patch) | |
| tree | 240648aeeb3db80d6959593a9aeeb627acc9165d /src | |
| parent | e35b070dae92fd2750b8116a6ca0f1de393c7a6a (diff) | |
| parent | 5004a24b2cceffbe998a5238f625da97537f5de7 (diff) | |
Merge remote-tracking branch 'tinyusb/master' into ch32_warning
Signed-off-by: HiFiPhile <[email protected]>
Diffstat (limited to 'src')
42 files changed, 3098 insertions, 950 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00f466007..b3e05f60f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,13 +8,13 @@ function(tinyusb_sources_get OUTPUT_VAR) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/common/tusb_fifo.c # device ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd.c - ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd_control.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/audio/audio_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_rt_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi2_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/mtp/mtp_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ecm_rndis_device.c @@ -29,6 +29,7 @@ function(tinyusb_sources_get OUTPUT_VAR) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_host.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi2_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_host.c # typec diff --git a/src/class/hid/hid_host.c b/src/class/hid/hid_host.c index 7935b84d3..fc7704258 100644 --- a/src/class/hid/hid_host.c +++ b/src/class/hid/hid_host.c @@ -74,44 +74,31 @@ static uint8_t _hidh_default_protocol = HID_PROTOCOL_BOOT; // Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ TU_ATTR_WEAK void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report_desc, uint16_t desc_len) { - (void) dev_addr; - (void) idx; - (void) report_desc; - (void) desc_len; + (void) dev_addr; (void) idx; (void) report_desc; (void) desc_len; } TU_ATTR_WEAK void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t idx) { - (void) dev_addr; - (void) idx; + (void) dev_addr; (void) idx; +} + +TU_ATTR_WEAK void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t idx, const uint8_t *report, uint16_t len) { + (void) dev_addr; (void) idx; (void) report; (void) len; } TU_ATTR_WEAK void tuh_hid_report_sent_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len) { - (void) dev_addr; - (void) idx; - (void) report; - (void) len; + (void) dev_addr; (void) idx; (void) report; (void) len; } TU_ATTR_WEAK void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) { - (void) dev_addr; - (void) idx; - (void) report_id; - (void) report_type; - (void) len; + (void) dev_addr; (void) idx; (void) report_id; (void) report_type; (void) len; } TU_ATTR_WEAK void tuh_hid_set_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) { - (void) dev_addr; - (void) idx; - (void) report_id; - (void) report_type; - (void) len; + (void) dev_addr; (void) idx; (void) report_id; (void) report_type; (void) len; } TU_ATTR_WEAK void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) { - (void) dev_addr; - (void) idx; - (void) protocol; + (void) dev_addr; (void) idx; (void) protocol; } //--------------------------------------------------------------------+ diff --git a/src/class/hid/hid_host.h b/src/class/hid/hid_host.h index 922848fc2..95ba859ad 100644 --- a/src/class/hid/hid_host.h +++ b/src/class/hid/hid_host.h @@ -140,7 +140,7 @@ bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx); bool tuh_hid_send_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, const void *report, uint16_t len); //--------------------------------------------------------------------+ -// Callbacks (Weak is optional) +// Callbacks (optional) //--------------------------------------------------------------------+ // Invoked when device with hid interface is mounted diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h index cd67640e4..8121ec016 100644 --- a/src/class/midi/midi.h +++ b/src/class/midi/midi.h @@ -186,6 +186,24 @@ typedef midi_desc_cs_endpoint_n_t(1) midi_desc_cs_endpoint_1jack_t; TU_VERIFY_STATIC(sizeof(midi_desc_cs_endpoint_1jack_t) == 4+1, "size is not correct"); //--------------------------------------------------------------------+ +// MIDI 2.0 UMP Helpers +//--------------------------------------------------------------------+ + +// Return the number of 32-bit words for a UMP message given its Message Type +static inline uint8_t midi2_ump_word_count(uint8_t mt) { + switch (mt) { + case 0x0: case 0x1: case 0x2: case 0x6: case 0x7: + return 1; + case 0x3: case 0x4: case 0x8: case 0x9: case 0xA: + return 2; + case 0xB: case 0xC: + return 3; + default: // 0x5, 0xD, 0xE, 0xF + return 4; + } +} + +//--------------------------------------------------------------------+ // For Internal Driver Use //--------------------------------------------------------------------+ typedef struct { diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c new file mode 100644 index 000000000..b14f439f8 --- /dev/null +++ b/src/class/midi/midi2_device.c @@ -0,0 +1,769 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_MIDI2 + +#include <string.h> + +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#include "midi2_device.h" + +//--------------------------------------------------------------------+ +// Weak stubs +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_midi2_rx_cb(uint8_t itf) { (void) itf; } +TU_ATTR_WEAK void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) { (void) itf; (void) alt; } +TU_ATTR_WEAK bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request) { + (void) rhport; (void) request; return false; +} +TU_ATTR_WEAK uint8_t tud_midi2_num_groups_cb(uint8_t itf) { + (void) itf; return CFG_TUD_MIDI2_NUM_GROUPS; +} +TU_ATTR_WEAK uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf) { + (void) itf; return CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS; +} +TU_ATTR_WEAK const char* tud_midi2_ep_name_cb(uint8_t itf) { + (void) itf; return CFG_TUD_MIDI2_EP_NAME; +} +TU_ATTR_WEAK const char* tud_midi2_product_id_cb(uint8_t itf) { + (void) itf; return CFG_TUD_MIDI2_PRODUCT_ID; +} + +//--------------------------------------------------------------------+ +// Byte order note +//--------------------------------------------------------------------+ +// Per USB-MIDI 2.0 Section 3.2.2, each 32-bit UMP word is transmitted with the +// least significant byte first. This driver reads and writes UMP words as +// native uint32_t through tu_edpt_stream_read/write. All TinyUSB targets are +// little-endian, so the in-memory layout already matches the wire order and no +// swap is needed. If a big-endian target is ever supported, wrap access with +// tu_htole32 / tu_le32toh at the buffer boundary. + +//--------------------------------------------------------------------+ +// UMP Stream Message Constants +//--------------------------------------------------------------------+ +// UMP Message Type for Stream messages (bits 31:28) +enum { + MT_STREAM = 0x0F, +}; + +// UMP Stream Status values (10-bit, bits 25:16) +enum { + STREAM_ENDPOINT_DISCOVERY = 0x000, + STREAM_ENDPOINT_INFO = 0x001, + STREAM_EP_NAME = 0x003, + STREAM_PROD_INSTANCE_ID = 0x004, + STREAM_CONFIG_REQUEST = 0x005, + STREAM_CONFIG_NOTIFY = 0x006, + STREAM_FB_DISCOVERY = 0x010, + STREAM_FB_INFO = 0x011, +}; + +enum { + UMP_VER_MAJOR = 1, + UMP_VER_MINOR = 1, +}; + +// Group Terminal Block descriptor types (USB-MIDI 2.0) +enum { + MIDI2_CS_GRP_TRM_BLOCK = 0x26, + MIDI2_GRP_TRM_BLOCK_HEADER = 0x01, + MIDI2_GRP_TRM_BLOCK_ENTRY = 0x02, +}; + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t ep_addr; + uint16_t mps; + tu_fifo_t ff; + +#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 + uint8_t* ep_buf; +#endif +} midi2d_tx_t; + +typedef struct { + uint8_t rhport; + uint8_t itf_num; + uint8_t alt_setting; + uint8_t protocol; + bool negotiated; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + struct { + midi2d_tx_t tx; + tu_edpt_stream_t rx; + + uint8_t rx_ff_buf[CFG_TUD_MIDI2_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUD_MIDI2_TX_BUFSIZE]; + } ep_stream; +} midi2d_interface_t; + +// Skip local EP buffer if dedicated hw FIFO is supported +#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 +typedef struct { + TUD_EPBUF_DEF(epin, CFG_TUD_MIDI2_TX_EPSIZE); + TUD_EPBUF_DEF(epout, CFG_TUD_MIDI2_RX_EPSIZE); +} midi2d_epbuf_t; + +CFG_TUD_MEM_SECTION static midi2d_epbuf_t _midi2d_epbuf[CFG_TUD_MIDI2]; +#endif + +TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_GROUPS >= 1 && CFG_TUD_MIDI2_NUM_GROUPS <= 16, + "CFG_TUD_MIDI2_NUM_GROUPS must be 1..16"); +TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS >= 1 && CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS <= 32, + "CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS must be 1..32"); + +#define ITF_MEM_RESET_SIZE offsetof(midi2d_interface_t, ep_stream) + +static midi2d_interface_t _midi2d_itf[CFG_TUD_MIDI2]; + +// Default Group Terminal Block descriptor (USB-MIDI 2.0 spec, Table 5-5/5-6) +static const uint8_t _default_gtb_desc[] = { + // GTB Header (5 bytes) + 5, // bLength + MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType + MIDI2_GRP_TRM_BLOCK_HEADER, // bDescriptorSubtype + U16_TO_U8S_LE(18), // wTotalLength (5 + 13 = 18) + + // GTB Entry (13 bytes) + 13, // bLength + MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType + MIDI2_GRP_TRM_BLOCK_ENTRY, // bDescriptorSubtype + 1, // bGrpTrmBlkID + 0x00, // bGrpTrmBlkType: bidirectional + 0x00, // nGroupTrm: first group (0) + CFG_TUD_MIDI2_NUM_GROUPS, // nNumGroupTrm + CFG_TUD_MIDI2_BLOCK_STRIDX, // iBlockItem: string descriptor index (0 = none) + 0x00, // bMIDIProtocol: unknown/not fixed + 0, 0, // wMaxInputBandwidth: unknown + 0, 0 // wMaxOutputBandwidth: unknown +}; + +//--------------------------------------------------------------------+ +// Common utility functions +//--------------------------------------------------------------------+ + +static inline uint8_t _itf_idx(const midi2d_interface_t* p_midi) { + return (uint8_t)(p_midi - _midi2d_itf); +} + +static inline bool _tx_opened(const midi2d_interface_t* p_midi) { + return p_midi->ep_stream.tx.ep_addr != 0; +} + +static uint8_t _tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) { + if (offset < info->linear.len) { + return info->linear.ptr[offset]; + } + + offset = (uint16_t) (offset - info->linear.len); + if (offset < info->wrapped.len) { + return info->wrapped.ptr[offset]; + } + + return 0; +} + +// Calculate the largest byte count that contains only whole UMP packets and +// fits in one USB transfer (<= mps). +static uint16_t _tx_nonseg_len_to_mps(midi2d_tx_t* tx) { + tu_fifo_buffer_info_t info; + tu_fifo_get_read_info(&tx->ff, &info); + + const uint16_t available = (uint16_t) (info.linear.len + info.wrapped.len); + uint16_t bytes = 0; + + while (bytes < tx->mps) { + if ((uint16_t) (available - bytes) < 4) break; + + uint8_t mt = (uint8_t)((_tx_byte_at(&info, (uint16_t) (bytes + 3)) >> 4) & 0x0F); + uint8_t pkt_words = midi2_ump_word_count(mt); + uint16_t pkt_bytes = (uint16_t) pkt_words * 4; + + if (pkt_bytes == 0) break; + if ((uint16_t) (available - bytes) < pkt_bytes) break; + if ((uint16_t) (bytes + pkt_bytes) > tx->mps) break; + + bytes = (uint16_t) (bytes + pkt_bytes); + } + + return bytes; +} + +// Start one IN transfer capped at mps, return number of bytes queued to the controller, or 0 if nothing was queued. +static uint16_t _tx_start_xfer(midi2d_interface_t* p_midi) { + midi2d_tx_t* tx = &p_midi->ep_stream.tx; + uint16_t ff_count = tu_fifo_count(&tx->ff); + + if (ff_count == 0) return 0; + + if (!usbd_edpt_claim(p_midi->rhport, tx->ep_addr)) return 0; + + uint16_t bytes; + if (p_midi->alt_setting == 1) { + bytes = _tx_nonseg_len_to_mps(tx); + } else { + bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps); + } + if (bytes == 0) { + usbd_edpt_release(p_midi->rhport, tx->ep_addr); + return 0; + } + +#if CFG_TUD_EDPT_DEDICATED_HWFIFO + TU_ASSERT(usbd_edpt_xfer_fifo(p_midi->rhport, tx->ep_addr, &tx->ff, bytes, false), 0); +#else + tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes); + TU_ASSERT(usbd_edpt_xfer(p_midi->rhport, tx->ep_addr, tx->ep_buf, bytes, false), 0); +#endif + + return bytes; +} + +static uint32_t _tx_ump_write(midi2d_interface_t* p_midi, const uint32_t* words, uint32_t count) { + uint32_t written = 0; + while (written < count) { + uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F); + uint8_t pkt_words = midi2_ump_word_count(mt); + uint16_t pkt_bytes = (uint16_t) pkt_words * 4; + + if (written + pkt_words > count) break; + if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break; + + if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break; + written += pkt_words; + } + + (void) _tx_start_xfer(p_midi); + return written; +} + +//--------------------------------------------------------------------+ +// Protocol Negotiation +//--------------------------------------------------------------------+ +static void _nego_send_ump(midi2d_interface_t* p_midi, const uint32_t* words, uint8_t count) { + if (!_tx_opened(p_midi)) return; + if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < (uint32_t) count * 4) return; + (void) _tx_ump_write(p_midi, words, count); +} + +static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) { + uint32_t msg[4] = {0}; + msg[0] = ((uint32_t) MT_STREAM << 28) + | ((uint32_t) STREAM_ENDPOINT_INFO << 16) + | ((uint32_t) UMP_VER_MAJOR << 8) + | (uint32_t) UMP_VER_MINOR; + msg[1] = (UINT32_C(1) << 31) // Static Function Blocks flag + | ((uint32_t)(tud_midi2_num_function_blocks_cb(_itf_idx(p_midi)) & 0x7F) << 24) + | (UINT32_C(1) << 9) // MIDI 2.0 Protocol capability + | (UINT32_C(1) << 8); // MIDI 1.0 Protocol capability + _nego_send_ump(p_midi, msg, 4); +} + +static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, const char* str) { + if (!str || str[0] == '\0') return; + + uint16_t total_len = (uint16_t) strlen(str); + uint16_t offset = 0; + + while (offset < total_len) { + uint16_t remaining = total_len - offset; + uint8_t n = (uint8_t)((remaining > 14) ? 14 : remaining); + bool is_first = (offset == 0); + bool is_last = (remaining <= 14); + + uint8_t form; + if (is_first && is_last) form = 0; + else if (is_first) form = 1; + else if (is_last) form = 3; + else form = 2; + + uint32_t msg[4] = {0}; + msg[0] = ((uint32_t) MT_STREAM << 28) + | ((uint32_t) form << 26) + | ((uint32_t) status << 16); + + const char* p = str + offset; + if (n > 0) msg[0] |= ((uint32_t)(uint8_t) p[0] << 8); + if (n > 1) msg[0] |= (uint32_t)(uint8_t) p[1]; + for (uint8_t i = 2; i < n; i++) { + uint8_t word_idx = (uint8_t)(1 + (i - 2) / 4); + uint8_t shift = (uint8_t)(24 - ((i - 2) % 4) * 8); + msg[word_idx] |= ((uint32_t)(uint8_t) p[i] << shift); + } + + _nego_send_ump(p_midi, msg, 4); + offset += n; + } +} + +static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protocol) { + uint32_t msg[4] = {0}; + msg[0] = ((uint32_t) MT_STREAM << 28) + | ((uint32_t) STREAM_CONFIG_NOTIFY << 16) + | ((uint32_t) protocol << 8); + _nego_send_ump(p_midi, msg, 4); +} + +static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) { + uint32_t msg[4] = {0}; + msg[0] = ((uint32_t) MT_STREAM << 28) + | ((uint32_t) STREAM_FB_INFO << 16) + | (UINT32_C(1) << 15) + | ((uint32_t) fb_idx << 8) + | 0x02; // bDirection: bidirectional + msg[1] = ((uint32_t) 0 << 24) // bFirstGroup + | ((uint32_t) tud_midi2_num_groups_cb(_itf_idx(p_midi)) << 16); + _nego_send_ump(p_midi, msg, 4); +} + +static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* words) { + uint16_t status = (words[0] >> 16) & 0x3FF; + + switch (status) { + case STREAM_ENDPOINT_DISCOVERY: + _nego_send_endpoint_info(p_midi); + _nego_send_stream_text(p_midi, STREAM_EP_NAME, tud_midi2_ep_name_cb(_itf_idx(p_midi))); + _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, tud_midi2_product_id_cb(_itf_idx(p_midi))); + break; + + case STREAM_CONFIG_REQUEST: { + uint8_t req_proto = (words[0] >> 8) & 0xFF; + if (req_proto == MIDI_PROTOCOL_MIDI1 || req_proto == MIDI_PROTOCOL_MIDI2) { + p_midi->protocol = req_proto; + } + _nego_send_config_notify(p_midi, p_midi->protocol); + p_midi->negotiated = true; + break; + } + + case STREAM_FB_DISCOVERY: { + uint8_t fb_idx = (words[0] >> 8) & 0xFF; + uint8_t fb_count = tud_midi2_num_function_blocks_cb(_itf_idx(p_midi)); + if (fb_idx == 0xFF) { + for (uint8_t f = 0; f < fb_count; f++) { + _nego_send_fb_info(p_midi, f); + } + } else if (fb_idx < fb_count) { + _nego_send_fb_info(p_midi, fb_idx); + } + break; + } + + default: + break; + } +} + +static void _nego_process_rx(midi2d_interface_t* p_midi) { + tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx; + uint8_t word_bytes[4]; + + while (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) == 4) { + // UMP words travel LSB-first on the wire and in LE memory, so MT is in + // the high nibble of byte 3, not byte 0. + uint8_t mt = (word_bytes[3] >> 4) & 0x0F; + uint8_t pkt_words = midi2_ump_word_count(mt); + uint32_t pkt_bytes = (uint32_t)pkt_words * 4; + + if (mt != MT_STREAM) break; + if (tu_edpt_stream_read_available(ep_rx) < pkt_bytes) break; + + uint32_t buf[4] = {0}; + tu_edpt_stream_read(ep_rx, buf, pkt_bytes); + _nego_handle_stream_msg(p_midi, buf); + } +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +bool tud_midi2_n_mounted(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_MIDI2, false); + midi2d_interface_t* p_midi = &_midi2d_itf[itf]; + return _tx_opened(p_midi) && + tu_edpt_stream_is_opened(&p_midi->ep_stream.rx); +} + +uint32_t tud_midi2_n_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_MIDI2, 0); + midi2d_interface_t* p_midi = &_midi2d_itf[itf]; + return tu_edpt_stream_read_available(&p_midi->ep_stream.rx) / 4; +} + +uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words) { + TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && max_words > 0, 0); + midi2d_interface_t* p_midi = &_midi2d_itf[itf]; + + // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0). + // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words. + if (p_midi->alt_setting != 1) { return 0; } + + tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx; + + uint32_t total_read = 0; + while (total_read < max_words) { + uint8_t word_bytes[4]; + if (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) < 4) break; + + // UMP words travel LSB-first; MT is the high nibble of byte 3, not byte 0. + uint8_t mt = (word_bytes[3] >> 4) & 0x0F; + uint8_t pkt_words = midi2_ump_word_count(mt); + + if (total_read + pkt_words > max_words) break; + if (tu_edpt_stream_read_available(ep_rx) < (uint32_t)pkt_words * 4) break; + + tu_edpt_stream_read(ep_rx, &words[total_read], pkt_words * 4); + total_read += pkt_words; + } + + return total_read; +} + +uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets) { + TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && max_packets > 0, 0); + midi2d_interface_t* p_midi = &_midi2d_itf[itf]; + return tu_edpt_stream_read(&p_midi->ep_stream.rx, packets, max_packets * 4u) >> 2u; +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ +uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count) { + TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && count > 0, 0); + midi2d_interface_t* p_midi = &_midi2d_itf[itf]; + + // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0). + // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words. + if (p_midi->alt_setting != 1) { return 0; } + TU_VERIFY(_tx_opened(p_midi), 0); + + return _tx_ump_write(p_midi, words, count); +} + +uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count) { + TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && count > 0, 0); + midi2d_interface_t* p_midi = &_midi2d_itf[itf]; + midi2d_tx_t* tx = &p_midi->ep_stream.tx; + + // Packet API is for Alt Setting 0 (USB-MIDI 1.0) event packets. + TU_VERIFY(p_midi->alt_setting == 0, 0); + TU_VERIFY(_tx_opened(p_midi), 0); + + uint32_t written = 0; + while (written < count) { + if (tu_fifo_remaining(&tx->ff) < 4) break; + + if (tu_fifo_write_n(&tx->ff, packets + written * 4u, 4) != 4) break; + written++; + } + + (void) _tx_start_xfer(p_midi); + + return written; +} + +//--------------------------------------------------------------------+ +// STATE GETTERS +//--------------------------------------------------------------------+ +uint8_t tud_midi2_n_alt_setting(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_MIDI2, 0); + return _midi2d_itf[itf].alt_setting; +} + +bool tud_midi2_n_negotiated(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_MIDI2, false); + return _midi2d_itf[itf].negotiated; +} + +uint8_t tud_midi2_n_protocol(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_MIDI2, 0); + return _midi2d_itf[itf].protocol; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void midi2d_init(void) { + tu_memclr(_midi2d_itf, sizeof(_midi2d_itf)); + for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) { + midi2d_interface_t* p_midi = &_midi2d_itf[i]; + p_midi->protocol = MIDI_PROTOCOL_MIDI2; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *epout_buf = NULL; + uint8_t *epin_buf = NULL; + #else + uint8_t *epout_buf = _midi2d_epbuf[i].epout; + uint8_t *epin_buf = _midi2d_epbuf[i].epin; + #endif + + tu_edpt_stream_init(&p_midi->ep_stream.rx, false, false, false, + p_midi->ep_stream.rx_ff_buf, CFG_TUD_MIDI2_RX_BUFSIZE, epout_buf); + + midi2d_tx_t* tx = &p_midi->ep_stream.tx; + (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI2_TX_BUFSIZE, false); +#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 + tx->ep_buf = epin_buf; +#else + (void) epin_buf; +#endif + } +} + +bool midi2d_deinit(void) { + for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) { + midi2d_interface_t* p_midi = &_midi2d_itf[i]; + tu_edpt_stream_deinit(&p_midi->ep_stream.rx); + } + return true; +} + +void midi2d_reset(uint8_t rhport) { + (void) rhport; + for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) { + midi2d_interface_t* p_midi = &_midi2d_itf[i]; + tu_memclr(p_midi, ITF_MEM_RESET_SIZE); + + tu_edpt_stream_clear(&p_midi->ep_stream.rx); + tu_edpt_stream_close(&p_midi->ep_stream.rx); + + tu_fifo_clear(&p_midi->ep_stream.tx.ff); + p_midi->ep_stream.tx.ep_addr = 0; + } +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t find_midi2_itf(uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) { + const midi2d_interface_t* p_midi = &_midi2d_itf[idx]; + if (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static uint8_t find_midi2_itf_by_num(uint8_t itf_num) { + for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) { + if (_midi2d_itf[idx].itf_num == itf_num) return idx; + } + return TUSB_INDEX_INVALID_8; +} + +uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) { + const uint8_t* p_desc = (const uint8_t*) desc_itf; + const uint8_t* desc_end = p_desc + max_len; + + // 1st Interface: Audio Control v1 (optional) + if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { + p_desc = tu_desc_next(desc_itf); + while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) { + p_desc = tu_desc_next(p_desc); + } + } + + // 2nd Interface: MIDI Streaming + TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); + const tusb_desc_interface_t* desc_midi = (const tusb_desc_interface_t*) p_desc; + + TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass && + AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol, + 0); + + uint8_t idx = find_midi2_itf(0); + TU_ASSERT(idx < CFG_TUD_MIDI2, 0); + midi2d_interface_t* p_midi = &_midi2d_itf[idx]; + + p_midi->rhport = rhport; + p_midi->itf_num = desc_midi->bInterfaceNumber; + p_midi->alt_setting = 0; + p_midi->protocol = MIDI_PROTOCOL_MIDI2; + p_midi->negotiated = false; + + p_desc = tu_desc_next(p_desc); + + // Skip class-specific descriptors + while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) { + p_desc = tu_desc_next(p_desc); + } + + // Find and open endpoint descriptors + uint8_t found_ep = 0; + while ((found_ep < desc_midi->bNumEndpoints) && tu_desc_in_bounds(p_desc, desc_end)) { + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + const uint8_t ep_addr = desc_ep->bEndpointAddress; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + p_midi->ep_stream.tx.ep_addr = ep_addr; + p_midi->ep_stream.tx.mps = tu_edpt_packet_size(desc_ep); + tu_fifo_clear(&p_midi->ep_stream.tx.ff); + } else { + tu_edpt_stream_open(&p_midi->ep_stream.rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep)); + tu_edpt_stream_clear(&p_midi->ep_stream.rx); + TU_ASSERT(tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx) > 0, 0); + } + + found_ep++; + } + + p_desc = tu_desc_next(p_desc); + } + + // Skip remaining descriptors (alt setting 1, CS endpoints, GTB) + // Stop at any interface descriptor that is not our MIDI Streaming alt setting + while (tu_desc_in_bounds(p_desc, desc_end)) { + uint8_t dtype = tu_desc_type(p_desc); + + if (dtype == TUSB_DESC_INTERFACE) { + const tusb_desc_interface_t* next_itf = (const tusb_desc_interface_t*) p_desc; + // Continue only if this is an alternate setting of our own interface + if (next_itf->bInterfaceNumber != desc_midi->bInterfaceNumber) break; + } else if (dtype != TUSB_DESC_CS_INTERFACE && dtype != TUSB_DESC_CS_ENDPOINT && + dtype != TUSB_DESC_ENDPOINT) { + break; + } + + p_desc = tu_desc_next(p_desc); + } + + return (uint16_t)(p_desc - (const uint8_t*) desc_itf); +} + +bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + TU_LOG2("MIDI2 ctrl: stage=%u bRequest=0x%02X wValue=0x%04X wIndex=0x%04X wLength=%u\r\n", + stage, request->bRequest, request->wValue, request->wIndex, request->wLength); + + if (stage != CONTROL_STAGE_SETUP) return true; + + switch (request->bRequest) { + case TUSB_REQ_SET_INTERFACE: { + uint8_t itf_num = tu_u16_low(request->wIndex); + uint8_t alt = tu_u16_low(request->wValue); + + // Only Alt Setting 0 (MIDI 1.0) and 1 (UMP) are valid + if (alt > 1) return false; + + uint8_t idx = find_midi2_itf_by_num(itf_num); + if (idx >= CFG_TUD_MIDI2) return false; + + midi2d_interface_t* p_midi = &_midi2d_itf[idx]; + p_midi->alt_setting = alt; + + tu_edpt_stream_clear(&p_midi->ep_stream.rx); + tu_fifo_clear(&p_midi->ep_stream.tx.ff); + + if (alt == 1) { + p_midi->negotiated = false; + p_midi->protocol = MIDI_PROTOCOL_MIDI2; + } + + // Re-arm RX endpoint for receiving data after alt setting change + tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx); + + tud_midi2_set_itf_cb(idx, alt); + tud_control_status(rhport, request); + return true; + } + + case TUSB_REQ_GET_DESCRIPTOR: { + // USB-MIDI 2.0 Section 6: GTB descriptor retrieval + // bmRequestType = 0x81 (Device-to-Host, Standard, Interface) + // wValue = CS_GR_TRM_BLOCK (0x26) in high byte, alt setting in low byte + // wIndex = interface number + if (request->bmRequestType_bit.direction != TUSB_DIR_IN) return false; + if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_STANDARD) return false; + if (request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_INTERFACE) return false; + if (tu_u16_high(request->wValue) != MIDI2_CS_GRP_TRM_BLOCK) return false; + + uint8_t itf_num = tu_u16_low(request->wIndex); + uint8_t idx = find_midi2_itf_by_num(itf_num); + if (idx >= CFG_TUD_MIDI2) return false; + + // Only Alt Setting 1 exposes Group Terminal Block descriptors. + if (tu_u16_low(request->wValue) != 0x01) return false; + + if (tud_midi2_get_req_itf_cb(rhport, request)) return true; + + uint16_t len = request->wLength; + if (len > sizeof(_default_gtb_desc)) { + len = sizeof(_default_gtb_desc); + } + tud_control_xfer(rhport, request, (void*)(uintptr_t) _default_gtb_desc, len); + return true; + } + + default: + return false; + } +} + +bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) rhport; + + uint8_t idx = find_midi2_itf(ep_addr); + TU_ASSERT(idx < CFG_TUD_MIDI2); + midi2d_interface_t* p_midi = &_midi2d_itf[idx]; + + tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx; + midi2d_tx_t* ep_tx = &p_midi->ep_stream.tx; + + if (ep_addr == ep_rx->ep_addr) { + if (result == XFER_RESULT_SUCCESS) { + tu_edpt_stream_read_xfer_complete(ep_rx, xferred_bytes); + if (p_midi->alt_setting == 1) { + _nego_process_rx(p_midi); + } + tud_midi2_rx_cb(idx); + } + tu_edpt_stream_read_xfer(ep_rx); + } else if (ep_addr == ep_tx->ep_addr && result == XFER_RESULT_SUCCESS) { + uint16_t queued = _tx_start_xfer(p_midi); + // Send ZLP if no more data is queued but the last transfer was exactly mps + if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 && + (0 == (xferred_bytes & (ep_tx->mps - 1)))) { + if (usbd_edpt_claim(rhport, ep_tx->ep_addr)) { + usbd_edpt_xfer(rhport, ep_tx->ep_addr, NULL, 0, false); + } + } + } else { + return false; + } + + return true; +} + +#endif diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h new file mode 100644 index 000000000..e53535693 --- /dev/null +++ b/src/class/midi/midi2_device.h @@ -0,0 +1,193 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * 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_MIDI2_DEVICE_H_ +#define TUSB_MIDI2_DEVICE_H_ + +#include "class/audio/audio.h" +#include "midi.h" + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// Config defaults are in tusb_option.h: +// CFG_TUD_MIDI2_RX_EPSIZE, CFG_TUD_MIDI2_TX_EPSIZE, +// CFG_TUD_MIDI2_RX_BUFSIZE, CFG_TUD_MIDI2_TX_BUFSIZE, +// CFG_TUD_MIDI2_NUM_GROUPS, CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS, +// CFG_TUD_MIDI2_EP_NAME, CFG_TUD_MIDI2_PRODUCT_ID + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUD_MIDI2_TX_EPSIZE + #define CFG_TUD_MIDI2_TX_EPSIZE TUD_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUD_MIDI2_RX_EPSIZE + #define CFG_TUD_MIDI2_RX_EPSIZE TUD_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUD_MIDI2_TX_BUFSIZE + #define CFG_TUD_MIDI2_TX_BUFSIZE CFG_TUD_MIDI2_TX_EPSIZE +#endif + +#ifndef CFG_TUD_MIDI2_RX_BUFSIZE + #define CFG_TUD_MIDI2_RX_BUFSIZE CFG_TUD_MIDI2_RX_EPSIZE +#endif + +#ifndef CFG_TUD_MIDI2_NUM_GROUPS + #define CFG_TUD_MIDI2_NUM_GROUPS 1 +#endif + +#ifndef CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS + #define CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS 1 +#endif + +#ifndef CFG_TUD_MIDI2_EP_NAME + #define CFG_TUD_MIDI2_EP_NAME "TinyUSB MIDI 2.0" +#endif + +#ifndef CFG_TUD_MIDI2_PRODUCT_ID + #define CFG_TUD_MIDI2_PRODUCT_ID "TinyUSB-MIDI2" +#endif + +// String descriptor index for the Group Terminal Block (iBlockItem, Table 5-6). +// 0 = no string descriptor (default, spec-allowed). +#ifndef CFG_TUD_MIDI2_BLOCK_STRIDX + #define CFG_TUD_MIDI2_BLOCK_STRIDX 0 +#endif + +//--------------------------------------------------------------------+ +// MIDI Protocol Values (returned by tud_midi2_n_protocol) +//--------------------------------------------------------------------+ + +// Per USB-MIDI 2.0 spec, UMP Stream Configuration messages. +enum { + MIDI_PROTOCOL_MIDI1 = 0x01, + MIDI_PROTOCOL_MIDI2 = 0x02, +}; + +//--------------------------------------------------------------------+ +// Application Callback API (weak, optional) +//--------------------------------------------------------------------+ +void tud_midi2_rx_cb(uint8_t itf); +void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt); +bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request); + +// Per-interface UMP Stream config (override for per-itf values). +uint8_t tud_midi2_num_groups_cb(uint8_t itf); +uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf); +const char* tud_midi2_ep_name_cb(uint8_t itf); +const char* tud_midi2_product_id_cb(uint8_t itf); + +//--------------------------------------------------------------------+ +// Application API (Multiple Interfaces) +//--------------------------------------------------------------------+ + +bool tud_midi2_n_mounted(uint8_t itf); +uint32_t tud_midi2_n_available(uint8_t itf); +uint8_t tud_midi2_n_alt_setting(uint8_t itf); +bool tud_midi2_n_negotiated(uint8_t itf); +uint8_t tud_midi2_n_protocol(uint8_t itf); + +// Read up to max_words UMP words from the RX FIFO. Returns the number of +// words actually read (0 if FIFO is empty). +// +// NOTE: this function returns when max_words is reached or when the FIFO is +// empty, whichever comes first. Applications should invoke it in a loop +// until it returns 0 to guarantee the RX FIFO is fully drained per +// tud_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can +// prevent subsequent bulk OUT transfers from landing. +uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words); +uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count); + +uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets); +uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count); + +//--------------------------------------------------------------------+ +// Application API (Single Interface) +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_mounted(void) { + return tud_midi2_n_mounted(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi2_available(void) { + return tud_midi2_n_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_alt_setting(void) { + return tud_midi2_n_alt_setting(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_negotiated(void) { + return tud_midi2_n_negotiated(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_protocol(void) { + return tud_midi2_n_protocol(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi2_ump_read(uint32_t* words, uint32_t max_words) { + return tud_midi2_n_ump_read(0, words, max_words); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi2_ump_write(const uint32_t* words, uint32_t count) { + return tud_midi2_n_ump_write(0, words, count); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi2_packet_read(uint8_t packets[], uint32_t max_packets) { + return tud_midi2_n_packet_read(0, packets, max_packets); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi2_packet_write(const uint8_t packets[], uint32_t count) { + return tud_midi2_n_packet_write(0, packets, count); +} + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void midi2d_init(void); +bool midi2d_deinit(void); +void midi2d_reset(uint8_t rhport); +uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len); +bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request); +bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/class/midi/midi2_host.c b/src/class/midi/midi2_host.c new file mode 100644 index 000000000..6d8861f4b --- /dev/null +++ b/src/class/midi/midi2_host.c @@ -0,0 +1,635 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_MIDI2) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" +#include "midi2_host.h" + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI2_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Weak stubs for application callbacks +//--------------------------------------------------------------------+ + +TU_ATTR_WEAK void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data) { + (void) idx; (void) desc_cb_data; +} + +TU_ATTR_WEAK void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data) { + (void) idx; (void) mount_cb_data; +} + +TU_ATTR_WEAK void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void) idx; (void) xferred_bytes; +} + +TU_ATTR_WEAK void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void) idx; (void) xferred_bytes; +} + +TU_ATTR_WEAK void tuh_midi2_umount_cb(uint8_t idx) { + (void) idx; +} + +//--------------------------------------------------------------------+ +// Internal structure and state +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t ep_addr; + uint16_t mps; + tu_fifo_t ff; + uint8_t* ep_buf; +} midih2_tx_t; + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; + + uint8_t alt_setting_current; + + uint8_t protocol_version; + uint8_t bcdMSC_hi, bcdMSC_lo; + uint8_t rx_cable_count_alt0; + uint8_t tx_cable_count_alt0; + uint8_t rx_cable_count_alt1; + uint8_t tx_cable_count_alt1; + + struct { + midih2_tx_t tx; + tu_edpt_stream_t rx; + + uint8_t rx_ff_buf[CFG_TUH_MIDI2_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUH_MIDI2_TX_BUFSIZE]; + } ep_stream; + + bool mounted; +} midih2_interface_t; + +static midih2_interface_t _midi2_host[CFG_TUH_MIDI2]; + +typedef struct { + TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MAX); + TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MAX); +} midih2_epbuf_t; + +CFG_TUH_MEM_SECTION static midih2_epbuf_t _midi2_epbuf[CFG_TUH_MIDI2]; + +//--------------------------------------------------------------------+ +// Helper functions +//--------------------------------------------------------------------+ + +static inline uint8_t find_new_midi2_index(void) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) { + if (_midi2_host[idx].daddr == 0) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) { + const midih2_interface_t *p_midi = &_midi2_host[idx]; + if ((p_midi->daddr == daddr) && + (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr)) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static inline bool _tuh_tx_opened(const midih2_interface_t* p_midi) { + return p_midi->ep_stream.tx.ep_addr != 0; +} + +static uint8_t _tuh_tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) { + if (offset < info->linear.len) { + return info->linear.ptr[offset]; + } + offset = (uint16_t)(offset - info->linear.len); + if (offset < info->wrapped.len) { + return info->wrapped.ptr[offset]; + } + return 0; +} + +// Largest byte count containing only whole UMP packets and fitting one xfer. +static uint16_t _tuh_tx_nonseg_len_to_mps(midih2_tx_t* tx) { + tu_fifo_buffer_info_t info; + tu_fifo_get_read_info(&tx->ff, &info); + + const uint16_t available = (uint16_t)(info.linear.len + info.wrapped.len); + uint16_t bytes = 0; + + while (bytes < tx->mps) { + if ((uint16_t)(available - bytes) < 4) break; + + uint8_t mt = (uint8_t)((_tuh_tx_byte_at(&info, (uint16_t)(bytes + 3)) >> 4) & 0x0F); + uint8_t pkt_words = midi2_ump_word_count(mt); + uint16_t pkt_bytes = (uint16_t)(pkt_words * 4); + + if (pkt_bytes == 0) break; + if ((uint16_t)(available - bytes) < pkt_bytes) break; + if ((uint16_t)(bytes + pkt_bytes) > tx->mps) break; + + bytes = (uint16_t)(bytes + pkt_bytes); + } + + return bytes; +} + +// Start one OUT transfer capped at mps. Returns bytes queued, or 0 if nothing. +static uint16_t _tuh_tx_start_xfer(midih2_interface_t* p_midi) { + midih2_tx_t* tx = &p_midi->ep_stream.tx; + uint16_t ff_count = tu_fifo_count(&tx->ff); + if (ff_count == 0) return 0; + if (!usbh_edpt_claim(p_midi->daddr, tx->ep_addr)) return 0; + + uint16_t bytes; + if (p_midi->alt_setting_current == 1) { + bytes = _tuh_tx_nonseg_len_to_mps(tx); + } else { + bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps); + } + if (bytes == 0) { + usbh_edpt_release(p_midi->daddr, tx->ep_addr); + return 0; + } + + tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes); + TU_ASSERT(usbh_edpt_xfer(p_midi->daddr, tx->ep_addr, tx->ep_buf, bytes), 0); + return bytes; +} + +static uint32_t _tuh_tx_ump_write(midih2_interface_t* p_midi, const uint32_t* words, uint32_t count) { + uint32_t written = 0; + while (written < count) { + uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F); + uint8_t pkt_words = midi2_ump_word_count(mt); + uint16_t pkt_bytes = (uint16_t)(pkt_words * 4); + + if (written + pkt_words > count) break; + if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break; + if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break; + written += pkt_words; + } + + (void) _tuh_tx_start_xfer(p_midi); + return written; +} + +//--------------------------------------------------------------------+ +// Descriptor parsing +//--------------------------------------------------------------------+ + +// Parse Alt Setting 0 (MIDI 1.0) descriptors. Returns pointer past last consumed descriptor. +static const uint8_t* midih2_parse_descriptors_alt0(midih2_interface_t *p_midi, + const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) { + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL); + + p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber; + + const uint8_t *p_desc = (const uint8_t *) desc_itf; + p_desc = tu_desc_next(p_desc); + + uint8_t rx_cable_count = 0; + uint8_t tx_cable_count = 0; + bool found_new_interface = false; + + while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) { + switch (tu_desc_type(p_desc)) { + case TUSB_DESC_INTERFACE: + found_new_interface = true; + break; + + case TUSB_DESC_ENDPOINT: { + const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; + + TU_ASSERT(tuh_edpt_open(p_midi->daddr, p_ep), NULL); + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_IN) { + tu_edpt_stream_open(&p_midi->ep_stream.rx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep)); + tu_edpt_stream_clear(&p_midi->ep_stream.rx); + } else { + p_midi->ep_stream.tx.ep_addr = p_ep->bEndpointAddress; + p_midi->ep_stream.tx.mps = tu_edpt_packet_size(p_ep); + tu_fifo_clear(&p_midi->ep_stream.tx.ff); + } + + p_desc = tu_desc_next(p_desc); + if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) { + const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc; + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { + tx_cable_count = p_csep->bNumEmbMIDIJack; + } else { + rx_cable_count = p_csep->bNumEmbMIDIJack; + } + } + break; + } + + default: + break; + } + + if (!found_new_interface) { + p_desc = tu_desc_next(p_desc); + } + } + + p_midi->rx_cable_count_alt0 = rx_cable_count; + p_midi->tx_cable_count_alt0 = tx_cable_count; + return p_desc; +} + +// Parse Alt Setting 1 (MIDI 2.0 UMP) descriptors. Returns pointer past last consumed descriptor. +static const uint8_t* midih2_parse_descriptors_alt1(midih2_interface_t *p_midi, + const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) { + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL); + TU_VERIFY(desc_itf->bAlternateSetting == 1, NULL); + + const uint8_t *p_desc = (const uint8_t *) desc_itf; + p_desc = tu_desc_next(p_desc); + + uint8_t rx_cable_count = 0; + uint8_t tx_cable_count = 0; + bool found_new_interface = false; + + while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) { + switch (tu_desc_type(p_desc)) { + case TUSB_DESC_INTERFACE: + found_new_interface = true; + break; + + case TUSB_DESC_CS_INTERFACE: + if (tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_HEADER) { + // bcdMSC at offset 3-4 in CS Interface Header + const uint8_t *bcd_ptr = p_desc + 3; + p_midi->bcdMSC_lo = bcd_ptr[0]; + p_midi->bcdMSC_hi = bcd_ptr[1]; + if (p_midi->bcdMSC_hi == 0x02) { // bcdMSC 0x0200 = USB-MIDI 2.0 + p_midi->protocol_version = 1; + } + } + break; + + case TUSB_DESC_ENDPOINT: { + const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; + p_desc = tu_desc_next(p_desc); + + if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) { + // MIDI 2.0 CS Endpoint General 2.0: bNumGrpTrmBlk at offset 3 + if (p_desc[0] >= 4 && p_desc[2] == MIDI_CS_ENDPOINT_GENERAL_2_0) { + uint8_t num_grp_trm_blk = p_desc[3]; + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { + tx_cable_count = num_grp_trm_blk; + } else { + rx_cable_count = num_grp_trm_blk; + } + } + } + break; + } + + default: + break; + } + + if (!found_new_interface) { + p_desc = tu_desc_next(p_desc); + } + } + + p_midi->rx_cable_count_alt1 = rx_cable_count; + p_midi->tx_cable_count_alt1 = tx_cable_count; + return p_desc; +} + +//--------------------------------------------------------------------+ +// Auto-selection logic +//--------------------------------------------------------------------+ + +static void midih2_auto_select_alt_setting(midih2_interface_t *p_midi) { + p_midi->alt_setting_current = 0; + if (p_midi->protocol_version == 1) { + p_midi->alt_setting_current = 1; + } +} + +//--------------------------------------------------------------------+ +// Init/Deinit +//--------------------------------------------------------------------+ + +bool midih2_init(void) { + tu_memclr(&_midi2_host, sizeof(_midi2_host)); + for (int inst = 0; inst < CFG_TUH_MIDI2; inst++) { + midih2_interface_t *p_midi = &_midi2_host[inst]; + + tu_edpt_stream_init(&p_midi->ep_stream.rx, true, false, false, + p_midi->ep_stream.rx_ff_buf, CFG_TUH_MIDI2_RX_BUFSIZE, _midi2_epbuf[inst].rx); + + // TX uses raw tu_fifo + direct usbh_edpt_xfer (no FIFO wrapper) to preserve + // UMP packet boundaries across USB transfers. + midih2_tx_t* tx = &p_midi->ep_stream.tx; + (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUH_MIDI2_TX_BUFSIZE, false); + tx->ep_buf = _midi2_epbuf[inst].tx; + } + return true; +} + +bool midih2_deinit(void) { + for (size_t i = 0; i < CFG_TUH_MIDI2; i++) { + midih2_interface_t* p_midi = &_midi2_host[i]; + tu_edpt_stream_deinit(&p_midi->ep_stream.rx); + } + return true; +} + +//--------------------------------------------------------------------+ +// Class driver callbacks +//--------------------------------------------------------------------+ + +uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { + (void) rhport; + + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0); + + // For Alt Setting 1, reuse existing slot for same device+interface + uint8_t idx = TUSB_INDEX_INVALID_8; + if (desc_itf->bAlternateSetting > 0) { + for (uint8_t i = 0; i < CFG_TUH_MIDI2; i++) { + if (_midi2_host[i].daddr == dev_addr && + _midi2_host[i].bInterfaceNumber == desc_itf->bInterfaceNumber) { + idx = i; + break; + } + } + } + if (idx == TUSB_INDEX_INVALID_8) { + idx = find_new_midi2_index(); + } + TU_VERIFY(idx < CFG_TUH_MIDI2, 0); + + midih2_interface_t *p_midi = &_midi2_host[idx]; + p_midi->daddr = dev_addr; + + const uint8_t *desc_start = (const uint8_t *) desc_itf; + const uint8_t *desc_end = desc_start + max_len; + + // Skip Audio Control interface and any non-MIDI-Streaming descriptors + // (following midi_host.c pattern from Ha Thach) + if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) { + const uint8_t *p_desc = tu_desc_next((const uint8_t *)desc_itf); + // Skip CS_INTERFACE header + TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE, 0); + p_desc = tu_desc_next(p_desc); + desc_itf = (const tusb_desc_interface_t *) p_desc; + // Skip until we find MIDI Streaming interface + while (tu_desc_in_bounds(p_desc, desc_end) && + (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE || + (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO && + desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) { + p_desc = tu_desc_next(p_desc); + desc_itf = (const tusb_desc_interface_t *) p_desc; + } + TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0); + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0); + } + + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, 0); + + TU_LOG_DRV("MIDI2 opening Interface %u Alt %u (addr = %u)\r\n", + desc_itf->bInterfaceNumber, desc_itf->bAlternateSetting, dev_addr); + + // Dispatch to appropriate parser based on Alt Setting + const uint8_t *p_end = NULL; + if (desc_itf->bAlternateSetting == 0) { + p_end = midih2_parse_descriptors_alt0(p_midi, desc_itf, desc_end); + } else if (desc_itf->bAlternateSetting == 1) { + p_end = midih2_parse_descriptors_alt1(p_midi, desc_itf, desc_end); + } + + // Return number of bytes consumed (following midi_host.c pattern) + uint16_t const parsed_len = (p_end != NULL) ? (uint16_t)(p_end - desc_start) : 0; + return parsed_len; +} + +static void midih2_set_config_complete(midih2_interface_t *p_midi, uint8_t idx) { + uint8_t dev_addr = p_midi->daddr; + + // Invoke descriptor_cb + tuh_midi2_descriptor_cb_t desc_cb = { + .protocol_version = p_midi->protocol_version, + .bcdMSC_hi = p_midi->bcdMSC_hi, + .bcdMSC_lo = p_midi->bcdMSC_lo, + .rx_cable_count = (p_midi->alt_setting_current == 0) ? + p_midi->rx_cable_count_alt0 : p_midi->rx_cable_count_alt1, + .tx_cable_count = (p_midi->alt_setting_current == 0) ? + p_midi->tx_cable_count_alt0 : p_midi->tx_cable_count_alt1, + }; + tuh_midi2_descriptor_cb(idx, &desc_cb); + + // Mark as mounted + TU_LOG_DRV("MIDI2 mounted addr = %u, alt = %u, protocol = %u\r\n", + dev_addr, p_midi->alt_setting_current, p_midi->protocol_version); + p_midi->mounted = true; + + // Invoke mount_cb + tuh_midi2_mount_cb_t mount_cb = { + .daddr = dev_addr, + .bInterfaceNumber = p_midi->bInterfaceNumber, + .protocol_version = p_midi->protocol_version, + .alt_setting_active = p_midi->alt_setting_current, + .rx_cable_count = desc_cb.rx_cable_count, + .tx_cable_count = desc_cb.tx_cable_count, + }; + tuh_midi2_mount_cb(idx, &mount_cb); + + // Prepare RX transfer + tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx); + + // Signal USBH that configuration is complete + usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber); +} + +static void midih2_set_interface_cb(tuh_xfer_t *xfer) { + uint8_t const dev_addr = xfer->daddr; + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + + // Find our interface + for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) { + if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) { + if (xfer->result == XFER_RESULT_SUCCESS) { + midih2_set_config_complete(&_midi2_host[idx], idx); + } else { + // SET_INTERFACE failed, fall back to alt 0 + TU_LOG_DRV("MIDI2 SET_INTERFACE failed, falling back to alt 0\r\n"); + _midi2_host[idx].alt_setting_current = 0; + midih2_set_config_complete(&_midi2_host[idx], idx); + } + return; + } + } +} + +bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) { + uint8_t idx = 0; + for (idx = 0; idx < CFG_TUH_MIDI2; idx++) { + if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) { + break; + } + } + + if (idx >= CFG_TUH_MIDI2) { + // Not our interface (e.g. Audio Control) - pass through to next + usbh_driver_set_config_complete(dev_addr, itf_num); + return true; + } + + midih2_interface_t *p_midi = &_midi2_host[idx]; + + // Auto-select alt setting + midih2_auto_select_alt_setting(p_midi); + + // If MIDI 2.0 detected, issue SET_INTERFACE to activate Alt Setting 1 + if (p_midi->alt_setting_current == 1) { + TU_LOG_DRV("MIDI2 requesting SET_INTERFACE alt 1 for itf %u\r\n", itf_num); + TU_ASSERT(tuh_interface_set(dev_addr, itf_num, 1, midih2_set_interface_cb, 0)); + } else { + // MIDI 1.0 only, complete immediately + midih2_set_config_complete(p_midi, idx); + } + + return true; +} + +void midih2_close(uint8_t dev_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) { + midih2_interface_t *p_midi = &_midi2_host[idx]; + if (p_midi->daddr == dev_addr) { + TU_LOG_DRV(" MIDI2 close addr = %u index = %u\r\n", dev_addr, idx); + tu_edpt_stream_close(&p_midi->ep_stream.rx); + tu_fifo_clear(&p_midi->ep_stream.tx.ff); + p_midi->ep_stream.tx.ep_addr = 0; + tuh_midi2_umount_cb(idx); + tu_memclr(p_midi, sizeof(midih2_interface_t)); + } + } +} + +bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr); + TU_VERIFY(idx < CFG_TUH_MIDI2); + + midih2_interface_t *p_midi = &_midi2_host[idx]; + midih2_tx_t* ep_tx = &p_midi->ep_stream.tx; + + if (ep_addr == p_midi->ep_stream.rx.ep_addr) { + if (result == XFER_RESULT_SUCCESS && xferred_bytes > 0) { + tu_edpt_stream_read_xfer_complete(&p_midi->ep_stream.rx, xferred_bytes); + tuh_midi2_rx_cb(idx, xferred_bytes); + } + tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx); + } else if (ep_addr == ep_tx->ep_addr) { + tuh_midi2_tx_cb(idx, xferred_bytes); + if (result == XFER_RESULT_SUCCESS) { + uint16_t queued = _tuh_tx_start_xfer(p_midi); + // Send ZLP if no more data is queued but the last transfer was exactly mps + if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 && + (0 == (xferred_bytes & (ep_tx->mps - 1)))) { + if (usbh_edpt_claim(dev_addr, ep_tx->ep_addr)) { + usbh_edpt_xfer(dev_addr, ep_tx->ep_addr, NULL, 0); + } + } + } + } + + return true; +} + +//--------------------------------------------------------------------+ +// Public API +//--------------------------------------------------------------------+ + +bool tuh_midi2_mounted(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI2); + return _midi2_host[idx].mounted; +} + +uint8_t tuh_midi2_get_protocol_version(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI2); + return _midi2_host[idx].protocol_version; +} + +uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI2); + return _midi2_host[idx].alt_setting_current; +} + +uint8_t tuh_midi2_get_cable_count(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI2); + return (_midi2_host[idx].alt_setting_current == 0) ? + _midi2_host[idx].rx_cable_count_alt0 : _midi2_host[idx].rx_cable_count_alt1; +} + +uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words) { + TU_VERIFY(idx < CFG_TUH_MIDI2 && words && max_words); + + midih2_interface_t *p_midi = &_midi2_host[idx]; + tu_edpt_stream_t *ep_rx = &p_midi->ep_stream.rx; + + uint32_t n_words = 0; + for (uint32_t i = 0; i < max_words; i++) { + if (tu_edpt_stream_read_available(ep_rx) >= 4) { + tu_edpt_stream_read(ep_rx, (uint8_t *) &words[i], 4); + n_words++; + } else { + break; + } + } + + return n_words; +} + +uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count) { + TU_VERIFY(idx < CFG_TUH_MIDI2 && words && count); + + midih2_interface_t *p_midi = &_midi2_host[idx]; + TU_VERIFY(_tuh_tx_opened(p_midi), 0); + + return _tuh_tx_ump_write(p_midi, words, count); +} + +uint32_t tuh_midi2_write_flush(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI2); + return _tuh_tx_start_xfer(&_midi2_host[idx]); +} + +#endif // CFG_TUH_ENABLED && CFG_TUH_MIDI2 diff --git a/src/class/midi/midi2_host.h b/src/class/midi/midi2_host.h new file mode 100644 index 000000000..47039eb85 --- /dev/null +++ b/src/class/midi/midi2_host.h @@ -0,0 +1,107 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * 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_MIDI2_HOST_H_ +#define TUSB_MIDI2_HOST_H_ + +#include "class/audio/audio.h" +#include "midi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Callback Type Definitions +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t protocol_version; // 0 = MIDI 1.0 only, 1 = MIDI 2.0 + uint8_t bcdMSC_hi, bcdMSC_lo; // MIDI version from descriptor + uint8_t rx_cable_count; // For both alt settings (same for Alt 0 and Alt 1) + uint8_t tx_cable_count; +} tuh_midi2_descriptor_cb_t; + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; + uint8_t protocol_version; // 0 = MIDI 1.0, 1 = MIDI 2.0 + uint8_t alt_setting_active; // 0 or 1 + uint8_t rx_cable_count; + uint8_t tx_cable_count; +} tuh_midi2_mount_cb_t; + +//--------------------------------------------------------------------+ +// Application Callback API (weak, optional) +//--------------------------------------------------------------------+ + +void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data); +void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data); +void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes); +void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes); +void tuh_midi2_umount_cb(uint8_t idx); + +//--------------------------------------------------------------------+ +// Application API - Query +//--------------------------------------------------------------------+ + +bool tuh_midi2_mounted(uint8_t idx); +uint8_t tuh_midi2_get_protocol_version(uint8_t idx); +uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx); +uint8_t tuh_midi2_get_cable_count(uint8_t idx); + +//--------------------------------------------------------------------+ +// Application API - I/O +//--------------------------------------------------------------------+ + +// Read up to max_words UMP words from the RX FIFO. Returns the number of +// words actually read (0 if FIFO is empty). +// +// NOTE: this function returns when max_words is reached or when the FIFO is +// empty, whichever comes first. Applications should invoke it in a loop +// until it returns 0 to guarantee the RX FIFO is fully drained per +// tuh_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can +// prevent subsequent bulk IN transfers from landing. +uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words); +uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count); +uint32_t tuh_midi2_write_flush(uint8_t idx); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ + +bool midih2_init(void); +bool midih2_deinit(void); +bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num); +void midih2_close(uint8_t dev_addr); +uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len); +bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h index b9ab0130d..8fdfd8966 100644 --- a/src/class/midi/midi_host.h +++ b/src/class/midi/midi_host.h @@ -150,6 +150,13 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, const uint8_t *p_ // 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. +// +// NOTE: this function terminates when it encounters an event whose cable +// number differs from the one being returned. Applications should invoke +// it in a loop until it returns 0 (or until tuh_midi_read_available() +// returns 0) to guarantee the stream FIFO is fully drained per callback. +// Leaving bytes in the FIFO across callbacks can prevent subsequent bulk +// IN transfers from landing. uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize); #endif diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 0da984f4a..fd06b4601 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -441,9 +441,19 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t threshold = CFG_TUD_MTP_EP_BUFSIZE; } - // Check completion: ZLP, short packet, or total length reached - const bool is_complete = - (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len); + // Check completion for IN and OUT separately + bool is_complete; + + if (is_data_in) + { + // IN completion: short packet, ZLP, or reaching total_len + is_complete = (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len); + } + else + { + // OUT completion: reaching total_len or ZLP + is_complete = (p_mtp->xferred_len >= p_mtp->total_len) || ((xferred_bytes == 0 && p_mtp->xferred_len > 0)); + } TU_LOG_DRV(" MTP Data %s CB: xferred_bytes=%lu, xferred_len/total_len=%lu/%lu, is_complete=%d\r\n", is_data_in ? "IN" : "OUT", xferred_bytes, p_mtp->xferred_len, p_mtp->total_len, is_complete ? 1 : 0); diff --git a/src/class/net/ecm_rndis_device.c b/src/class/net/ecm_rndis_device.c index 9282e0605..643bcfbcd 100644 --- a/src/class/net/ecm_rndis_device.c +++ b/src/class/net/ecm_rndis_device.c @@ -48,9 +48,10 @@ typedef struct { uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active - uint8_t ep_notif; uint8_t ep_in; uint8_t ep_out; + uint16_t ep_size; // bulk endpoint max packet size (IN and OUT assumed equal) + uint8_t ep_notif; bool ecm_mode; @@ -81,6 +82,13 @@ CFG_TUD_MEM_SECTION static netd_epbuf_t _netd_epbuf; static bool can_xmit; static bool ecm_link_is_up = true; // Store link state for ECM mode +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_network_set_packet_filter_cb(uint16_t packet_filter) { + (void) packet_filter; +} + void tud_network_recv_renew(void) { usbd_edpt_xfer(0, _netd_itf.ep_out, _netd_epbuf.rx, NETD_PACKET_SIZE, false); } @@ -176,6 +184,9 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1 // Pair of endpoints TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); + // Save the actual bulk endpoint size (IN and OUT assumed equal) + _netd_itf.ep_size = tu_edpt_packet_size((tusb_desc_endpoint_t const *) p_desc); + if (_netd_itf.ecm_mode) { // ECM by default is in-active, save the endpoint attribute // to open later when received setInterface @@ -286,6 +297,7 @@ bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t if (_netd_itf.ecm_mode) { /* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */ if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) { + tud_network_set_packet_filter_cb(request->wValue); tud_control_xfer(rhport, request, NULL, 0); // Only send connection notification if link is up if (ecm_link_is_up) { @@ -357,8 +369,7 @@ bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_ /* data transmission finished */ if (ep_addr == _netd_itf.ep_in) { /* TinyUSB requires the class driver to implement ZLP (since ZLP usage is class-specific) */ - - if (xferred_bytes && (0 == (xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE))) { + if (xferred_bytes > 0 && 0 == (xferred_bytes & (_netd_itf.ep_size-1))) { do_in_xfer(NULL, 0); /* a ZLP is needed */ } else { /* we're finally finished */ diff --git a/src/class/net/ncm.h b/src/class/net/ncm.h index 8989fe0b4..27ff89b72 100644 --- a/src/class/net/ncm.h +++ b/src/class/net/ncm.h @@ -161,4 +161,18 @@ typedef struct { uint32_t uplink; } ncm_notify_t; +typedef struct TU_ATTR_PACKED { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t bcdNcmVersion; + uint8_t bmCapabilities; +} tusb_desc_cdc_ncm_func_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwNtbInMaxSize; + uint16_t wNtbInMaxDatagrams; + uint16_t wReserved; +} ncm_ntb_input_size_t; + #endif diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c index 405e4467b..a78e472c2 100644 --- a/src/class/net/ncm_device.c +++ b/src/class/net/ncm_device.c @@ -83,6 +83,7 @@ typedef struct { uint8_t itf_num; // interface number uint8_t itf_data_alt; // ==0 -> no endpoints, i.e. no network traffic, ==1 -> normal operation with two endpoints (spec, chapter 5.3) uint8_t rhport; // storage of \a rhport because some callbacks are done without it + uint16_t ep_size; // bulk endpoint max packet size (IN and OUT assumed equal) // recv handling recv_ntb_t *recv_free_ntb[RECV_NTB_N]; // free list of recv NTBs @@ -118,6 +119,12 @@ typedef struct { bool notification_xmit_is_running; // notification is currently transmitted bool link_is_up; // current link state + // host-configured transmit limits + uint8_t bm_capabilities; + uint16_t xmit_max_ntb_size; // maximum NTB size device may send + uint16_t xmit_max_datagrams; // maximum datagrams per NTB device may send + ncm_ntb_input_size_t ntb_input_size; + // misc bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations) bool tud_network_recv_renew_process_again; // tud_network_recv_renew() should process again @@ -138,6 +145,13 @@ typedef struct { static ncm_interface_t ncm_interface; CFG_TUD_MEM_SECTION static ncm_epbuf_t ncm_epbuf; +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_network_set_packet_filter_cb(uint16_t packet_filter) { + (void) packet_filter; +} + /** * This is the NTB parameter structure * @@ -340,7 +354,8 @@ static xmit_ntb_t *xmit_get_next_ready_ntb(void) { static bool xmit_insert_required_zlp(uint8_t rhport, uint32_t xferred_bytes) { TU_LOG_DRV("xmit_insert_required_zlp(%d,%ld)\n", rhport, xferred_bytes); - if (xferred_bytes == 0 || xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE != 0) { + uint16_t const ep_size = ncm_interface.ep_size; + if (xferred_bytes == 0 || (xferred_bytes & (ep_size-1)) != 0) { return false; } @@ -408,10 +423,10 @@ static bool xmit_requested_datagram_fits_into_current_ntb(uint16_t datagram_size if (ncm_interface.xmit_glue_ntb == NULL) { return false; } - if (ncm_interface.xmit_glue_ntb_datagram_ndx >= CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB) { + if (ncm_interface.xmit_glue_ntb_datagram_ndx >= ncm_interface.xmit_max_datagrams) { return false; } - if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + XMIT_ALIGN_OFFSET(datagram_size) > CFG_TUD_NCM_IN_NTB_MAX_SIZE) { + if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + (uint32_t)XMIT_ALIGN_OFFSET(datagram_size) > (uint32_t)ncm_interface.xmit_max_ntb_size) { return false; } return true; @@ -708,7 +723,7 @@ static void recv_transfer_datagram_to_glue_logic(void) { bool tud_network_can_xmit(uint16_t size) { TU_LOG_DRV("tud_network_can_xmit(%d)\n", size); - TU_ASSERT(size <= CFG_TUD_NCM_IN_NTB_MAX_SIZE - (sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)), false); + TU_ASSERT(size <= ncm_interface.xmit_max_ntb_size - (sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)), false); if (xmit_requested_datagram_fits_into_current_ntb(size) || xmit_setup_next_glue_ntb()) { // -> everything is fine @@ -828,6 +843,9 @@ void netd_init(void) { memset(&ncm_interface, 0, sizeof(ncm_interface)); + ncm_interface.xmit_max_ntb_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE; + ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB; + for (int i = 0; i < XMIT_NTB_N; ++i) { ncm_interface.xmit_free_ntb[i] = &ncm_epbuf.xmit[i].ntb; } @@ -878,10 +896,14 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16 ncm_interface.itf_num = itf_desc->bInterfaceNumber;// management interface - // skip the two first entries and the following TUSB_DESC_CS_INTERFACE entries uint16_t drv_len = sizeof(tusb_desc_interface_t); uint8_t const *p_desc = tu_desc_next(itf_desc); while (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && drv_len <= max_len) { + if (tu_desc_subtype(p_desc) == CDC_FUNC_DESC_NCM) { + TU_ASSERT(tu_desc_len(p_desc) >= sizeof(tusb_desc_cdc_ncm_func_t), 0); + tusb_desc_cdc_ncm_func_t const *ncm_func = (tusb_desc_cdc_ncm_func_t const *) p_desc; + ncm_interface.bm_capabilities = ncm_func->bmCapabilities; + } drv_len += tu_desc_len(p_desc); p_desc = tu_desc_next(p_desc); } @@ -905,6 +927,7 @@ uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16 // a TUSB_DESC_ENDPOINT (actually two) must follow, open these endpoints TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0); TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in)); + ncm_interface.ep_size = tu_edpt_packet_size((tusb_desc_endpoint_t const *) p_desc); drv_len += 2 * sizeof(tusb_desc_endpoint_t); return drv_len; @@ -954,12 +977,12 @@ bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_ * At startup transmission of notification packets are done here. */ bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { - if (stage != CONTROL_STAGE_SETUP) { - return true; - } switch (request->bmRequestType_bit.type) { case TUSB_REQ_TYPE_STANDARD: + if (stage != CONTROL_STAGE_SETUP) { + return true; + } switch (request->bRequest) { case TUSB_REQ_GET_INTERFACE: { @@ -993,16 +1016,75 @@ bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t TU_VERIFY(ncm_interface.itf_num == request->wIndex, false); switch (request->bRequest) { case NCM_GET_NTB_PARAMETERS: { + if (stage != CONTROL_STAGE_SETUP) { + return true; + } // transfer NTB parameters to host. tud_control_xfer(rhport, request, (void *) (uintptr_t) &ntb_parameters, sizeof(ntb_parameters)); } break; - // unsupported request + case NCM_SET_ETHERNET_PACKET_FILTER: { + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + // Some hosts issue this request even if ETH_FILTER is not advertised, + // see https://bugzilla.kernel.org/show_bug.cgi?id=217290 + + tud_network_set_packet_filter_cb(request->wValue); + tud_control_xfer(rhport, request, NULL, 0); + } break; + + case NCM_GET_NTB_INPUT_SIZE: { + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + TU_VERIFY(request->wLength >=4, false); + + uint8_t resp_len = (request->wLength >= 8 && (ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE)) ? 8 : 4; + + ncm_ntb_input_size_t ntb_input_size = { + .dwNtbInMaxSize = ncm_interface.xmit_max_ntb_size, + .wNtbInMaxDatagrams = ncm_interface.xmit_max_datagrams + }; + tud_control_xfer(rhport, request, &ntb_input_size, resp_len); + } break; + + case NCM_SET_NTB_INPUT_SIZE: { + if (stage == CONTROL_STAGE_SETUP) { + /* wLength == 8 -> the NTB Input Size Structure (if NCM_NETWORK_CAPS_NTB_INPUT_SIZE is set) + wLength == 4 -> dwNtbInMaxSize field of the NTB Input Size Structure. */ + TU_VERIFY(request->wLength == 4 || request->wLength == 8, false); + if (request->wLength == 8) { + TU_VERIFY(ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE, false); + } + + tu_memclr(&ncm_interface.ntb_input_size, sizeof(ncm_interface.ntb_input_size)); + tud_control_xfer(rhport, request, &ncm_interface.ntb_input_size, request->wLength); + } else if (stage == CONTROL_STAGE_DATA) { + /* CDC-NCM 1.0 Table 6-4, up to NTB16 size */ + const uint32_t requested_size = ncm_interface.ntb_input_size.dwNtbInMaxSize; + if (requested_size < 2048u || requested_size > 65535u) { + return false; + } + ncm_interface.xmit_max_ntb_size = tu_min16(requested_size, CFG_TUD_NCM_IN_NTB_MAX_SIZE); + + if (ncm_interface.ntb_input_size.wNtbInMaxDatagrams == 0 || ncm_interface.ntb_input_size.wNtbInMaxDatagrams > CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB) { + ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB; + } else { + ncm_interface.xmit_max_datagrams = ncm_interface.ntb_input_size.wNtbInMaxDatagrams; + } + } + } break; + + // unsupported request default: return false; } break; - // unsupported request + + // unsupported request default: return false; } diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h index 96c03fd61..332df09b3 100644 --- a/src/class/net/net_device.h +++ b/src/class/net/net_device.h @@ -35,9 +35,6 @@ #error "Cannot enable both ECM_RNDIS and NCM network drivers" #endif -/* declared here, NOT in usb_descriptors.c, so that the driver can intelligently ZLP as needed */ -#define CFG_TUD_NET_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) - /* Maximum Transmission Unit (in bytes) of the network, including Ethernet header */ #ifndef CFG_TUD_NET_MTU #define CFG_TUD_NET_MTU 1514 @@ -50,6 +47,16 @@ typedef enum NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK = 0x01 } ncm_data_interface_protocol_code_t; +// Table 5.2 bmNetworkCapabilities bits +typedef enum { + NCM_NETWORK_CAPS_NONE = 0x00, + NCM_NETWORK_CAPS_ETH_FILTER = (1 << 0), + NCM_NETWORK_CAPS_NET_ADDRESS = (1 << 1), + NCM_NETWORK_CAPS_ENCAP_COMMAND = (1 << 2), + NCM_NETWORK_CAPS_MAX_DATAGRAM_SIZE = (1 << 3), + NCM_NETWORK_CAPS_CRC_MODE = (1 << 4), + NCM_NETWORK_CAPS_NTB_INPUT_SIZE = (1 << 5) +} ncm_network_capabilities_t; #ifdef __cplusplus extern "C" { @@ -96,6 +103,9 @@ extern uint8_t tud_network_mac_address[6]; //------------- NCM -------------// +// Optional callback: informs the application about host requested packet filter bits +void tud_network_set_packet_filter_cb(uint16_t packet_filter); + // Set the network link state (up/down) and notify the host void tud_network_link_state(uint8_t rhport, bool is_up); diff --git a/src/class/printer/printer_device.c b/src/class/printer/printer_device.c index d2dc9b163..158455fc9 100644 --- a/src/class/printer/printer_device.c +++ b/src/class/printer/printer_device.c @@ -41,7 +41,6 @@ typedef struct { uint8_t itf_num; /*------------- From this point, data is not cleared by bus reset -------------*/ - tu_edpt_stream_t rx_stream; tu_edpt_stream_t tx_stream; diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index c85ade4d0..413ac4850 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -616,10 +616,6 @@ #define CFG_TUH_WCH_USBIP_USBFS 1 #endif - #define TUP_USBIP_FSDEV - #define TUP_USBIP_FSDEV_CH32 - #define CFG_TUSB_FSDEV_PMA_SIZE 512u - // default to FSDEV for device #if !defined(CFG_TUD_WCH_USBIP_USBFS) #define CFG_TUD_WCH_USBIP_USBFS 0 @@ -629,6 +625,12 @@ #define CFG_TUD_WCH_USBIP_FSDEV (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1) #endif + #if CFG_TUD_WCH_USBIP_FSDEV + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_CH32 + #define CFG_TUSB_FSDEV_PMA_SIZE 512u + #endif + #define TUP_DCD_ENDPOINT_MAX 8 #elif TU_CHECK_MCU(OPT_MCU_CH32V307) diff --git a/src/common/tusb_private.h b/src/common/tusb_private.h index 91d213755..a31bf7b03 100644 --- a/src/common/tusb_private.h +++ b/src/common/tusb_private.h @@ -46,17 +46,10 @@ extern tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM]; // Endpoint //--------------------------------------------------------------------+ -enum { - TU_EDPT_STATE_BUSY = 0x01, - TU_EDPT_STATE_STALLED = 0x02, - TU_EDPT_STATE_CLAIMED = 0x04, -}; - -typedef struct TU_ATTR_PACKED { - volatile uint8_t busy : 1; - volatile uint8_t stalled : 1; - volatile uint8_t claimed : 1; -} tu_edpt_state_t; +// Endpoint state bits — manipulate the bare uint8_t with these masks. +#define TU_EDPT_STATE_BUSY 0x01u +#define TU_EDPT_STATE_STALLED 0x02u +#define TU_EDPT_STATE_CLAIMED 0x04u typedef struct { uint8_t hwid; // device: rhport, host: daddr @@ -92,10 +85,10 @@ bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t it const uint8_t *p_desc, uint16_t desc_len); // Claim an endpoint with provided mutex -bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex); +bool tu_edpt_claim(volatile uint8_t* ep_state, osal_mutex_t mutex); // Release an endpoint with provided mutex -bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex); +bool tu_edpt_release(volatile uint8_t* ep_state, osal_mutex_t mutex); //--------------------------------------------------------------------+ // Endpoint Stream diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index 959fc129a..cb06b89bb 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -282,6 +282,7 @@ typedef enum { XFER_RESULT_FAILED, XFER_RESULT_STALLED, XFER_RESULT_TIMEOUT, + XFER_RESULT_ABORTED, XFER_RESULT_INVALID } xfer_result_t; diff --git a/src/device/usbd.c b/src/device/usbd.c index da0ffb4c6..55ad330c1 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -115,7 +115,20 @@ TU_ATTR_WEAK bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_si //--------------------------------------------------------------------+ // Device Data //--------------------------------------------------------------------+ + +// Per-control-transfer state: populated at process_setup_received() entry, +// consumed asynchronously by usbd_control_xfer_cb() when the EP0 transfer completes. +typedef struct { + tusb_control_request_t request; + uint8_t* buffer; + uint16_t data_len; + uint16_t total_xferred; + usbd_control_xfer_cb_t complete_cb; +} usbd_control_xfer_t; + typedef struct { + usbd_control_xfer_t ctrl_xfer; + // Note: these may share an enum state volatile uint8_t connected; volatile uint8_t addressed; @@ -136,12 +149,16 @@ typedef struct { uint8_t itf2drv[CFG_TUD_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) uint8_t ep2drv[CFG_TUD_ENDPPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each - tu_edpt_state_t ep_status[CFG_TUD_ENDPPOINT_MAX][2]; + volatile uint8_t ep_status[CFG_TUD_ENDPPOINT_MAX][2]; } usbd_device_t; static usbd_device_t _usbd_dev; static volatile uint8_t _usbd_queued_setup; +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE); +} _ctrl_epbuf; + //--------------------------------------------------------------------+ // Class Driver //--------------------------------------------------------------------+ @@ -237,6 +254,20 @@ static const usbd_class_driver_t _usbd_driver[] = { }, #endif + #if CFG_TUD_MIDI2 + { + .name = DRIVER_NAME("MIDI2"), + .init = midi2d_init, + .deinit = midi2d_deinit, + .open = midi2d_open, + .reset = midi2d_reset, + .control_xfer_cb = midi2d_control_xfer_cb, + .xfer_cb = midi2d_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + #if CFG_TUD_VENDOR { .name = DRIVER_NAME("VENDOR"), @@ -405,7 +436,8 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event, //--------------------------------------------------------------------+ // Prototypes //--------------------------------------------------------------------+ -static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request); +static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request); static bool process_set_config(uint8_t rhport, uint8_t cfg_num); static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request); @@ -419,12 +451,6 @@ static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_req } #endif -// from usbd_control.c -void usbd_control_reset(void); -void usbd_control_set_request(tusb_control_request_t const *request); -void usbd_control_set_complete_callback( usbd_control_xfer_cb_t fp ); -bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); - //--------------------------------------------------------------------+ // Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ @@ -459,17 +485,6 @@ static char const *const _usbd_event_str[DCD_EVENT_COUNT] = { "Func Call" }; -// for usbd_control to print the name of control complete driver -void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback) { - for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { - usbd_class_driver_t const* driver = get_driver(i); - if (driver && driver->control_xfer_cb == callback) { - TU_LOG_USBD("%s control complete\r\n", driver->name); - return; - } - } -} - #endif //--------------------------------------------------------------------+ @@ -609,9 +624,7 @@ bool tud_deinit(uint8_t rhport) { } } - // Clear device data - tu_varclr(&_usbd_dev); - usbd_control_reset(); + tu_varclr(&_usbd_dev); // Clear device data // Deinit device queue & task osal_queue_delete(_usbd_q); @@ -646,7 +659,6 @@ static void configuration_reset(uint8_t rhport) { static void usbd_reset(uint8_t rhport) { configuration_reset(rhport); - usbd_control_reset(); } bool tud_task_event_ready(void) { @@ -713,7 +725,9 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { break; case DCD_EVENT_SETUP_RECEIVED: - TU_ASSERT(_usbd_queued_setup > 0,); + if (_usbd_queued_setup == 0) { + break; + } _usbd_queued_setup--; TU_LOG_BUF(CFG_TUD_LOG_LEVEL, &event.setup_received, 8); if (_usbd_queued_setup != 0) { @@ -725,18 +739,16 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { // But it is easier to set it every time instead of wasting time to check then set _usbd_dev.connected = 1; - // mark both in & out control as free - _usbd_dev.ep_status[0][TUSB_DIR_OUT].busy = 0; - _usbd_dev.ep_status[0][TUSB_DIR_OUT].claimed = 0; - _usbd_dev.ep_status[0][TUSB_DIR_IN].busy = 0; - _usbd_dev.ep_status[0][TUSB_DIR_IN].claimed = 0; + // reset ep state + _usbd_dev.ep_status[0][TUSB_DIR_OUT] = 0; + _usbd_dev.ep_status[0][TUSB_DIR_IN] = 0; // Process control request - if (!process_control_request(event.rhport, &event.setup_received)) { + if (!process_setup_received(event.rhport, &event.setup_received)) { TU_LOG_USBD(" Stall EP0\r\n"); // Failed -> stall both control endpoint IN and OUT - dcd_edpt_stall(event.rhport, 0); - dcd_edpt_stall(event.rhport, 0 | TUSB_DIR_IN_MASK); + dcd_edpt_stall(event.rhport, TU_EP0_OUT); + dcd_edpt_stall(event.rhport, TU_EP0_IN); } break; @@ -748,8 +760,8 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { TU_LOG_USBD("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len); - _usbd_dev.ep_status[epnum][ep_dir].busy = 0; - _usbd_dev.ep_status[epnum][ep_dir].claimed = 0; + // Clear busy + claimed + _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); if (0 == epnum) { usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); @@ -809,25 +821,273 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { } //--------------------------------------------------------------------+ +// Control Endpoint +//--------------------------------------------------------------------+ + +// Weak hook: invoked when the control transfer's status stage completes +TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) { + (void) rhport; + (void) request; +} + +uint8_t* usbd_get_ctrl_buf(void) { + return _ctrl_epbuf.buf; +} + +// Endpoint used for the Status stage of a control transfer. +// Per USB 2.0 §9.3.1, when wLength == 0 the Direction bit is ignored and the Status +// stage is always IN. Otherwise the Status stage is opposite of the Data stage direction. +TU_ATTR_ALWAYS_INLINE static inline uint8_t status_stage_ep(const tusb_control_request_t* request) { + return (request->wLength != 0 && request->bmRequestType_bit.direction) ? TU_EP0_OUT : TU_EP0_IN; +} + +// Queue ZLP status transaction +TU_ATTR_ALWAYS_INLINE static inline bool status_stage_xact(uint8_t rhport, uint8_t ep_status) { + return usbd_edpt_xfer(rhport, ep_status, NULL, 0, false); +} + +// Queue a transaction in Data Stage. Each transaction has up to Endpoint0's max +// packet size. This function can also transfer a zero-length packet. +static bool data_stage_xact(uint8_t rhport) { + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + const uint16_t xact_len = tu_min16(ctrl_xfer->data_len - ctrl_xfer->total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE); + uint8_t ep_addr = TU_EP0_OUT; + + if (ctrl_xfer->request.bmRequestType_bit.direction == TUSB_DIR_IN) { + ep_addr = TU_EP0_IN; + if (0u != xact_len && ctrl_xfer->buffer != _ctrl_epbuf.buf) { + TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, ctrl_xfer->buffer, xact_len)); + } + } + + return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false); +} + +// Status phase +bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) { + // _usbd_dev.ctrl_xfer fields are pre-initialized at process_setup_received entry + (void) request; + return status_stage_xact(rhport, status_stage_ep(&_usbd_dev.ctrl_xfer.request)); +} + +// Transmit data to/from the control endpoint. If wLength is zero, a status packet is sent instead. +bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) { + // _usbd_dev.ctrl_xfer.request and reset fields are pre-initialized at process_setup_received entry + (void) request; + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + ctrl_xfer->buffer = (uint8_t*) buffer; + ctrl_xfer->data_len = tu_min16(len, ctrl_xfer->request.wLength); + + if (ctrl_xfer->request.wLength > 0U) { + if (ctrl_xfer->data_len > 0U) { + TU_ASSERT(buffer); + } + TU_ASSERT(data_stage_xact(rhport)); + } else { + // wLength == 0: Status stage is always IN per USB 2.0 §9.3.1 + TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN)); + } + + return true; +} + +// Callback when a transaction completes on the DATA stage or Status stage of EP0 +static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + + // Status Stage complete: ep_addr matches the resolved Status stage endpoint + uint8_t const ep_status = status_stage_ep(&ctrl_xfer->request); + if (ep_addr == ep_status) { + TU_ASSERT(0 == xferred_bytes); + + // invoke optional dcd hook if available + dcd_edpt0_status_complete(rhport, &ctrl_xfer->request); + + if (NULL != ctrl_xfer->complete_cb) { + ctrl_xfer->complete_cb(rhport, CONTROL_STAGE_ACK, &ctrl_xfer->request); + } + + return true; + } + + // Data stage progress + if (ctrl_xfer->request.bmRequestType_bit.direction == TUSB_DIR_OUT) { + TU_VERIFY(ctrl_xfer->buffer); + if (ctrl_xfer->buffer != _ctrl_epbuf.buf) { + memcpy(ctrl_xfer->buffer, _ctrl_epbuf.buf, xferred_bytes); + } + TU_LOG_MEM(CFG_TUD_LOG_LEVEL, ctrl_xfer->buffer, xferred_bytes, 2); + } + + ctrl_xfer->total_xferred += (uint16_t) xferred_bytes; + ctrl_xfer->buffer += xferred_bytes; + + // Data Stage complete when wLength reached or short packet (incl. ZLP) seen + if ((ctrl_xfer->request.wLength == ctrl_xfer->total_xferred) || + (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) { + bool is_ok = true; + + if (NULL != ctrl_xfer->complete_cb) { + // Callback can still stall control in status phase, e.g. OUT data doesn't make sense + is_ok = ctrl_xfer->complete_cb(rhport, CONTROL_STAGE_DATA, &ctrl_xfer->request); + } + + if (is_ok) { + TU_ASSERT(status_stage_xact(rhport, ep_status)); + } else { + // Stall both IN and OUT control endpoint + dcd_edpt_stall(rhport, TU_EP0_OUT); + dcd_edpt_stall(rhport, TU_EP0_IN); + } + } else { + // More data to transfer + TU_ASSERT(data_stage_xact(rhport)); + } + + return true; +} + +//--------------------------------------------------------------------+ // Control Request Parser & Handling //--------------------------------------------------------------------+ // Helper to invoke class driver control request handler static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * driver, tusb_control_request_t const * request) { - usbd_control_set_complete_callback(driver->control_xfer_cb); + _usbd_dev.ctrl_xfer.complete_cb = driver->control_xfer_cb; TU_LOG_USBD(" %s control request\r\n", driver->name); return driver->control_xfer_cb(rhport, CONTROL_STAGE_SETUP, request); } +// Process a standard request to the device recipient. +static bool process_std_device_request(uint8_t rhport, tusb_control_request_t const * p_request) { + switch (p_request->bRequest) { //-V2520 + case TUSB_REQ_SET_ADDRESS: + // Depending on mcu, status phase could be sent either before or after changing device address, + // or even require stack to not response with status at all + // Therefore DCD must take full responsibility to response and include zlp status packet if needed. + dcd_set_address(rhport, (uint8_t) p_request->wValue); + _usbd_dev.addressed = 1; + return true; + + case TUSB_REQ_GET_CONFIGURATION: { + uint8_t cfg_num = _usbd_dev.cfg_num; + tud_control_xfer(rhport, p_request, &cfg_num, 1); + return true; + } + + case TUSB_REQ_SET_CONFIGURATION: { + uint8_t const cfg_num = (uint8_t) p_request->wValue; + + // Only process if new configure is different + if (_usbd_dev.cfg_num != cfg_num) { + if (_usbd_dev.cfg_num != 0) { + // already configured: need to clear all endpoints and driver first + TU_LOG_USBD(" Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num); + + dcd_sof_enable(rhport, false); + dcd_edpt_close_all(rhport); + + // close all drivers and current configured state except bus speed + const uint8_t speed = _usbd_dev.speed; + configuration_reset(rhport); + + _usbd_dev.speed = speed; // restore speed + } + + _usbd_dev.cfg_num = cfg_num; + + // Handle the new configuration + if (cfg_num == 0) { + tud_umount_cb(); + } else { + if (!process_set_config(rhport, cfg_num)) { + _usbd_dev.cfg_num = 0; + TU_ASSERT(false); + } + tud_mount_cb(); + } + } + + tud_control_status(rhport, p_request); + return true; + } + + case TUSB_REQ_GET_DESCRIPTOR: + return process_get_descriptor(rhport, p_request); + + case TUSB_REQ_SET_FEATURE: + switch (p_request->wValue) { //-V2520 + case TUSB_REQ_FEATURE_REMOTE_WAKEUP: + TU_LOG_USBD(" Enable Remote Wakeup\r\n"); + // Host may enable remote wake up before suspending especially HID device + _usbd_dev.remote_wakeup_en = 1; + tud_control_status(rhport, p_request); + return true; + + #if CFG_TUD_TEST_MODE + case TUSB_REQ_FEATURE_TEST_MODE: { + // Only handle the test mode if supported and valid + TU_VERIFY(0 == tu_u16_low(p_request->wIndex)); + + uint8_t const selector = tu_u16_high(p_request->wIndex); + TU_VERIFY(TUSB_FEATURE_TEST_J <= selector && selector <= TUSB_FEATURE_TEST_FORCE_ENABLE); + + _usbd_dev.ctrl_xfer.complete_cb = process_test_mode_cb; + tud_control_status(rhport, p_request); + return true; + } + #endif + + // Stall unsupported feature selector + default: return false; + } + + case TUSB_REQ_CLEAR_FEATURE: + // Only support remote wakeup for device feature + TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); + TU_LOG_USBD(" Disable Remote Wakeup\r\n"); + + // Host may disable remote wake up after resuming + _usbd_dev.remote_wakeup_en = 0; + tud_control_status(rhport, p_request); + return true; + + case TUSB_REQ_GET_STATUS: { + // Device status bit mask + // - Bit 0: Self Powered TODO must invoke callback to get actual status + // - Bit 1: Remote Wakeup enabled + uint16_t status = (uint16_t) _usbd_dev.dev_state_bm; + tud_control_xfer(rhport, p_request, &status, 2); + return true; + } + + default: + TU_BREAKPOINT(); + return false; + } +} + + // This handles the actual request and its response. // Returns false if unable to complete the request, causing caller to stall control endpoints. -static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request) { - usbd_control_set_complete_callback(NULL); +static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request) { + // Initialize control transfer state for this request. The request copy must be + // visible to usbd_control_xfer_cb when the (asynchronous) status ZLP completes, + // since the SETUP packet event has already gone out of scope by then. + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + ctrl_xfer->request = *p_request; + ctrl_xfer->buffer = NULL; + ctrl_xfer->total_xferred = 0; + ctrl_xfer->data_len = 0; + ctrl_xfer->complete_cb = NULL; + + p_request = &ctrl_xfer->request; // re-direct request pointer to internal copy (modifiable for hacking) TU_ASSERT(p_request->bmRequestType_bit.type < TUSB_REQ_TYPE_INVALID); // Vendor request if ( p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR ) { - usbd_control_set_complete_callback(tud_vendor_control_xfer_cb); + ctrl_xfer->complete_cb = tud_vendor_control_xfer_cb; return tud_vendor_control_xfer_cb(rhport, CONTROL_STAGE_SETUP, p_request); } @@ -860,115 +1120,7 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const return false; } - switch (p_request->bRequest) { //-V2520 - case TUSB_REQ_SET_ADDRESS: - // Depending on mcu, status phase could be sent either before or after changing device address, - // or even require stack to not response with status at all - // Therefore DCD must take full responsibility to response and include zlp status packet if needed. - usbd_control_set_request(p_request); // set request since DCD has no access to tud_control_status() API - dcd_set_address(rhport, (uint8_t) p_request->wValue); - // skip tud_control_status() - _usbd_dev.addressed = 1; - break; - - case TUSB_REQ_GET_CONFIGURATION: { - uint8_t cfg_num = _usbd_dev.cfg_num; - tud_control_xfer(rhport, p_request, &cfg_num, 1); - } - break; - - case TUSB_REQ_SET_CONFIGURATION: { - uint8_t const cfg_num = (uint8_t) p_request->wValue; - - // Only process if new configure is different - if (_usbd_dev.cfg_num != cfg_num) { - if (_usbd_dev.cfg_num != 0) { - // already configured: need to clear all endpoints and driver first - TU_LOG_USBD(" Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num); - - dcd_sof_enable(rhport, false); - dcd_edpt_close_all(rhport); - - // close all drivers and current configured state except bus speed - const uint8_t speed = _usbd_dev.speed; - configuration_reset(rhport); - - _usbd_dev.speed = speed; // restore speed - } - - _usbd_dev.cfg_num = cfg_num; - - // Handle the new configuration - if (cfg_num == 0) { - tud_umount_cb(); - } else { - if (!process_set_config(rhport, cfg_num)) { - _usbd_dev.cfg_num = 0; - TU_ASSERT(false); - } - tud_mount_cb(); - } - } - - tud_control_status(rhport, p_request); - } - break; - - case TUSB_REQ_GET_DESCRIPTOR: - TU_VERIFY(process_get_descriptor(rhport, p_request)); - break; - - case TUSB_REQ_SET_FEATURE: - switch(p_request->wValue) { //-V2520 - case TUSB_REQ_FEATURE_REMOTE_WAKEUP: - TU_LOG_USBD(" Enable Remote Wakeup\r\n"); - // Host may enable remote wake up before suspending especially HID device - _usbd_dev.remote_wakeup_en = 1; - tud_control_status(rhport, p_request); - break; - - #if CFG_TUD_TEST_MODE - case TUSB_REQ_FEATURE_TEST_MODE: { - // Only handle the test mode if supported and valid - TU_VERIFY(0 == tu_u16_low(p_request->wIndex)); - - uint8_t const selector = tu_u16_high(p_request->wIndex); - TU_VERIFY(TUSB_FEATURE_TEST_J <= selector && selector <= TUSB_FEATURE_TEST_FORCE_ENABLE); - - usbd_control_set_complete_callback(process_test_mode_cb); - tud_control_status(rhport, p_request); - break; - } - #endif - - // Stall unsupported feature selector - default: return false; - } - break; - - case TUSB_REQ_CLEAR_FEATURE: - // Only support remote wakeup for device feature - TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); - TU_LOG_USBD(" Disable Remote Wakeup\r\n"); - - // Host may disable remote wake up after resuming - _usbd_dev.remote_wakeup_en = 0; - tud_control_status(rhport, p_request); - break; - - case TUSB_REQ_GET_STATUS: { - // Device status bit mask - // - Bit 0: Self Powered TODO must invoke callback to get actual status - // - Bit 1: Remote Wakeup enabled - uint16_t status = (uint16_t)_usbd_dev.dev_state_bm; - tud_control_xfer(rhport, p_request, &status, 2); - break; - } - - // Unknown/Unsupported request - default: TU_BREAKPOINT(); return false; - } - break; + return process_std_device_request(rhport, p_request); //------------- Class/Interface Specific Request -------------// case TUSB_REQ_RCPT_INTERFACE: { @@ -1004,7 +1156,7 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const TU_VERIFY(TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type); // Clear complete callback if driver set since it can also stall the request. - usbd_control_set_complete_callback(NULL); + ctrl_xfer->complete_cb = NULL; switch (p_request->bRequest) { //-V2520 case TUSB_REQ_GET_INTERFACE: { @@ -1062,10 +1214,10 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const // STD request must always be ACKed regardless of driver returned value // Also clear complete callback if driver set since it can also stall the request. (void) invoke_class_control(rhport, driver, p_request); - usbd_control_set_complete_callback(NULL); + ctrl_xfer->complete_cb = NULL; // skip ZLP status if driver already did that - if (!_usbd_dev.ep_status[0][TUSB_DIR_IN].busy) { + if (!(_usbd_dev.ep_status[0][TUSB_DIR_IN] & TU_EDPT_STATE_BUSY)) { tud_control_status(rhport, p_request); } } @@ -1144,8 +1296,7 @@ static bool process_set_config(uint8_t rhport, uint8_t cfg_num) { } // return descriptor's buffer and update desc_len -static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request) -{ +static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request) { tusb_desc_type_t const desc_type = (tusb_desc_type_t) tu_u16_high(p_request->wValue); uint8_t const desc_index = tu_u16_low( p_request->wValue ); @@ -1153,20 +1304,18 @@ static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const case TUSB_DESC_DEVICE: { TU_LOG_USBD(" Device\r\n"); - void* desc_device = (void*) (uintptr_t) tud_descriptor_device_cb(); + void *desc_device = (void *)(uintptr_t)tud_descriptor_device_cb(); TU_ASSERT(desc_device); // Only response with exactly 1 Packet if: not addressed and host requested more data than device descriptor has. // This only happens with the very first get device descriptor and EP0 size = 8 or 16. if ((CFG_TUD_ENDPOINT0_SIZE < sizeof(tusb_desc_device_t)) && !_usbd_dev.addressed && - ((tusb_control_request_t const*) p_request)->wLength > sizeof(tusb_desc_device_t)) { + p_request->wLength > sizeof(tusb_desc_device_t)) { // Hack here: we modify the request length to prevent usbd_control response with zlp // since we are responding with 1 packet & less data than wLength. - tusb_control_request_t mod_request = *p_request; - mod_request.wLength = CFG_TUD_ENDPOINT0_SIZE; - - return tud_control_xfer(rhport, &mod_request, desc_device, CFG_TUD_ENDPOINT0_SIZE); - }else { + ((tusb_control_request_t *)(uintptr_t)p_request)->wLength = CFG_TUD_ENDPOINT0_SIZE; + return tud_control_xfer(rhport, p_request, desc_device, CFG_TUD_ENDPOINT0_SIZE); + } else { return tud_control_xfer(rhport, p_request, desc_device, sizeof(tusb_desc_device_t)); } } @@ -1305,15 +1454,15 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); if (driver && driver->xfer_isr) { - _usbd_dev.ep_status[epnum][ep_dir].busy = 0; - _usbd_dev.ep_status[epnum][ep_dir].claimed = 0; + // Clear busy + claimed + _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); send = !driver->xfer_isr(event->rhport, ep_addr, (xfer_result_t) event->xfer_complete.result, event->xfer_complete.len); // xfer_isr() is deferred to xfer_cb(), revert busy/claimed status if (send) { - _usbd_dev.ep_status[epnum][ep_dir].busy = 1; - _usbd_dev.ep_status[epnum][ep_dir].claimed = 1; + // set busy + claimed + _usbd_dev.ep_status[epnum][ep_dir] |= (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); } } } @@ -1403,9 +1552,7 @@ bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir]; - - return tu_edpt_claim(ep_state, _usbd_mutex); + return tu_edpt_claim(&_usbd_dev.ep_status[epnum][dir], _usbd_mutex); } bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) { @@ -1413,9 +1560,7 @@ bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir]; - - return tu_edpt_release(ep_state, _usbd_mutex); + return tu_edpt_release(&_usbd_dev.ep_status[epnum][dir], _usbd_mutex); } bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) { @@ -1435,18 +1580,17 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t t #endif // Attempt to transfer on a busy endpoint, sound like an race condition ! - TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0); + TU_ASSERT((_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) == 0); // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() // could return and USBD task can preempt and clear the busy - _usbd_dev.ep_status[epnum][dir].busy = 1; + _usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_BUSY; if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes, is_isr)) { return true; } else { // DCD error, mark endpoint as ready to allow next transfer - _usbd_dev.ep_status[epnum][dir].busy = 0; - _usbd_dev.ep_status[epnum][dir].claimed = 0; + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); TU_LOG_USBD("FAILED\r\n"); TU_BREAKPOINT(); return false; @@ -1467,19 +1611,18 @@ bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_ TU_LOG_USBD(" Queue FIFO EP %02X with %u bytes ... ", ep_addr, total_bytes); // Attempt to transfer on a busy endpoint, sound like a race condition ! - TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0); + TU_ASSERT((_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) == 0); // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() could return // and usbd task can preempt and clear the busy - _usbd_dev.ep_status[epnum][dir].busy = 1; + _usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_BUSY; if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes, is_isr)) { TU_LOG_USBD("OK\r\n"); return true; } else { // DCD error, mark endpoint as ready to allow next transfer - _usbd_dev.ep_status[epnum][dir].busy = 0; - _usbd_dev.ep_status[epnum][dir].claimed = 0; + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); TU_LOG_USBD("failed\r\n"); TU_BREAKPOINT(); return false; @@ -1500,7 +1643,7 @@ bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - return _usbd_dev.ep_status[epnum][dir].busy; + return (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) != 0; } void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { @@ -1512,8 +1655,7 @@ void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { // only stalled if currently cleared TU_LOG_USBD(" Stall EP %02X\r\n", ep_addr); dcd_edpt_stall(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir].stalled = 1; - _usbd_dev.ep_status[epnum][dir].busy = 1; + _usbd_dev.ep_status[epnum][dir] |= (TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY); } void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { @@ -1525,8 +1667,7 @@ void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { // only clear if currently stalled TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr); dcd_edpt_clear_stall(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir].stalled = 0; - _usbd_dev.ep_status[epnum][dir].busy = 0; + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY); } bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) { @@ -1535,7 +1676,7 @@ bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - return _usbd_dev.ep_status[epnum][dir].stalled; + return (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_STALLED) != 0; } /** @@ -1555,9 +1696,7 @@ void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) { uint8_t const dir = tu_edpt_dir(ep_addr); dcd_edpt_close(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir].stalled = 0; - _usbd_dev.ep_status[epnum][dir].busy = 0; - _usbd_dev.ep_status[epnum][dir].claimed = 0; + _usbd_dev.ep_status[epnum][dir] = 0; #endif return; @@ -1602,9 +1741,7 @@ bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX); TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t)_usbd_dev.speed)); - _usbd_dev.ep_status[epnum][dir].stalled = 0; - _usbd_dev.ep_status[epnum][dir].busy = 0; - _usbd_dev.ep_status[epnum][dir].claimed = 0; + _usbd_dev.ep_status[epnum][dir] = 0; return dcd_edpt_iso_activate(rhport, desc_ep); #else (void) rhport; (void) desc_ep; diff --git a/src/device/usbd.h b/src/device/usbd.h index d3a6dccbb..abce5a887 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -425,6 +425,43 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ TUD_MIDI_JACKID_OUT_EMB(1) //--------------------------------------------------------------------+ +// MIDI 2.0 Descriptor Templates (USB-MIDI 2.0) +//--------------------------------------------------------------------+ + +// Alt Setting 1: MS Interface + MS Header (bcdMSC=0x0200) +// Per USB-MIDI 2.0 Table 5-2: wTotalLength in the MS Header is not used in 2.0 +// and shall be set to match bLength (= 0x0007) for conformity with USB-MIDI 1.0. +#define TUD_MIDI2_DESC_ALT1_HEAD_LEN (9 + 7) +#define TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx) \ + /* MIDI Streaming Interface, Alt Setting 1 */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 1, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, 0,\ + /* MS Header (MIDI 2.0): wTotalLength = bLength per spec */\ + 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0200), U16_TO_U8S_LE(0x0007) + +// Alt Setting 1: Standard USB Endpoint (7 bytes) + CS Endpoint General 2.0 +#define TUD_MIDI2_DESC_ALT1_EP_LEN(_numgtbs) (7 + 4 + (_numgtbs)) +#define TUD_MIDI2_DESC_ALT1_EP(_ep, _epsize, _numgtbs, ...) \ + 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, \ + (uint8_t)(4 + (_numgtbs)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL_2_0, _numgtbs, ## __VA_ARGS__ + +// Total length: Alt 0 (MIDI 1.0) + Alt 1 (UMP) +#define TUD_MIDI2_DESC_LEN (TUD_MIDI_DESC_LEN + TUD_MIDI2_DESC_ALT1_HEAD_LEN + TUD_MIDI2_DESC_ALT1_EP_LEN(1) * 2) + +// Complete MIDI 2.0 descriptor with both alternate settings (single cable/GTB) +#define TUD_MIDI2_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* Alt Setting 0 (MIDI 1.0) */\ + TUD_MIDI_DESC_HEAD(_itfnum, _stridx, 1),\ + TUD_MIDI_DESC_JACK_DESC(1, 0),\ + TUD_MIDI_DESC_EP(_epout, _epsize, 1),\ + TUD_MIDI_JACKID_IN_EMB(1),\ + TUD_MIDI_DESC_EP(_epin, _epsize, 1),\ + TUD_MIDI_JACKID_OUT_EMB(1),\ + /* Alt Setting 1 (UMP) */\ + TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx),\ + TUD_MIDI2_DESC_ALT1_EP(_epout, _epsize, 1, 1 /* bAssoGrpTrmBlkID */),\ + TUD_MIDI2_DESC_ALT1_EP(_epin, _epsize, 1, 1 /* bAssoGrpTrmBlkID */) + +//--------------------------------------------------------------------+ // Audio Descriptor Templates //--------------------------------------------------------------------+ @@ -1026,23 +1063,23 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Length of template descriptor #define TUD_CDC_NCM_DESC_LEN (8+9+5+5+13+6+7+9+9+7+7) -// CDC-ECM Descriptor Template -// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. -#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \ +// CDC-NCM Descriptor Template +// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size, EP notification bInterval, capability. +#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize, _ep_notif_interval, _capability) \ /* Interface Association */\ 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, 0,\ /* CDC Control Interface */\ 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, _desc_stridx,\ - /* CDC-NCM Header */\ + /* CDC Header */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\ - /* CDC-NCM Union */\ + /* CDC Union */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ - /* CDC-NCM Functional Descriptor */\ + /* CDC Ethernet Networking Descriptor */\ 13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \ /* CDC-NCM Functional Descriptor */\ - 6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \ + 6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), _capability, \ /* Endpoint Notification */\ - 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 50,\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), _ep_notif_interval,\ /* CDC Data Interface (default inactive) */\ 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\ /* CDC Data Interface (alternative active) */\ diff --git a/src/device/usbd_control.c b/src/device/usbd_control.c deleted file mode 100644 index b14d08a9c..000000000 --- a/src/device/usbd_control.c +++ /dev/null @@ -1,220 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUD_ENABLED - -#include "dcd.h" -#include "tusb.h" -#include "device/usbd_pvt.h" - -//--------------------------------------------------------------------+ -// Callback weak stubs (called if application does not provide) -//--------------------------------------------------------------------+ -TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) { - (void) rhport; - (void) request; -} - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ - - -typedef struct { - tusb_control_request_t request; - uint8_t* buffer; - uint16_t data_len; - uint16_t total_xferred; - usbd_control_xfer_cb_t complete_cb; -} usbd_control_xfer_t; - -static usbd_control_xfer_t _ctrl_xfer; - -CFG_TUD_MEM_SECTION static struct { - TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE); -} _ctrl_epbuf; - -uint8_t* usbd_get_ctrl_buf(void) { - return _ctrl_epbuf.buf; -} - -//--------------------------------------------------------------------+ -// Application API -//--------------------------------------------------------------------+ - -// Endpoint used for the Status stage of a control transfer. -// Per USB 2.0 §9.3.1, when wLength == 0 the Direction bit is ignored and the Status stage -// is always IN. Otherwise the Status stage is opposite to the Data stage direction. -TU_ATTR_ALWAYS_INLINE static inline uint8_t status_stage_ep(const tusb_control_request_t* request) { - return (request->wLength != 0 && request->bmRequestType_bit.direction) ? TU_EP0_OUT : TU_EP0_IN; -} - -// Queue ZLP status transaction -TU_ATTR_ALWAYS_INLINE static inline bool status_stage_xact(uint8_t rhport, uint8_t ep_status) { - return usbd_edpt_xfer(rhport, ep_status, NULL, 0, false); -} - -// Status phase -bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) { - _ctrl_xfer.request = (*request); - _ctrl_xfer.buffer = NULL; - _ctrl_xfer.total_xferred = 0; - _ctrl_xfer.data_len = 0; - - return status_stage_xact(rhport, status_stage_ep(request)); -} - -// Queue a transaction in Data Stage -// Each transaction has up to Endpoint0's max packet size. -// This function can also transfer an zero-length packet -static bool data_stage_xact(uint8_t rhport) { - const uint16_t xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE); - uint8_t ep_addr = TU_EP0_OUT; - - if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN) { - ep_addr = TU_EP0_IN; - if (0u != xact_len && _ctrl_xfer.buffer != _ctrl_epbuf.buf) { - TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, _ctrl_xfer.buffer, xact_len)); - } - } - - return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false); -} - -// Transmit data to/from the control endpoint. -// If the request's wLength is zero, a status packet is sent instead. -bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) { - _ctrl_xfer.request = (*request); - _ctrl_xfer.buffer = (uint8_t*) buffer; - _ctrl_xfer.total_xferred = 0U; - _ctrl_xfer.data_len = tu_min16(len, request->wLength); - - if (request->wLength > 0U) { - if (_ctrl_xfer.data_len > 0U) { - TU_ASSERT(buffer); - } - TU_ASSERT(data_stage_xact(rhport)); - } else { - TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN)); - } - - return true; -} - -//--------------------------------------------------------------------+ -// USBD API -//--------------------------------------------------------------------+ -void usbd_control_reset(void); -void usbd_control_set_request(const tusb_control_request_t* request); -void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp); -bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); - -void usbd_control_reset(void) { - tu_varclr(&_ctrl_xfer); -} - -// Set complete callback -void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp) { - _ctrl_xfer.complete_cb = fp; -} - -// for dcd_set_address where DCD is responsible for status response -void usbd_control_set_request(const tusb_control_request_t* request) { - _ctrl_xfer.request = (*request); - _ctrl_xfer.buffer = NULL; - _ctrl_xfer.total_xferred = 0; - _ctrl_xfer.data_len = 0; -} - -// callback when a transaction complete on -// - DATA stage of control endpoint or -// - Status stage -bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { - (void) result; - - // Status Stage complete: endpoint matches the Status stage endpoint - uint8_t const ep_status = status_stage_ep(&_ctrl_xfer.request); - if (ep_addr == ep_status) { - TU_ASSERT(0 == xferred_bytes); - - // invoke optional dcd hook if available - dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request); - - if (NULL != _ctrl_xfer.complete_cb) { - // TODO refactor with usbd_driver_print_control_complete_name - _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_ACK, &_ctrl_xfer.request); - } - - return true; - } - - // Data stage complete - if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT) { - TU_VERIFY(_ctrl_xfer.buffer); - if (_ctrl_xfer.buffer != _ctrl_epbuf.buf) { - memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes); - } - TU_LOG_MEM(CFG_TUD_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2); - } - - _ctrl_xfer.total_xferred += (uint16_t) xferred_bytes; - _ctrl_xfer.buffer += xferred_bytes; - - // Data Stage is complete when all request's length are transferred or - // a short packet is sent including zero-length packet. - if ((_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || - (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) { - // DATA stage is complete - bool is_ok = true; - - // invoke complete callback if set - // callback can still stall control in status phase e.g out data does not make sense - if (NULL != _ctrl_xfer.complete_cb) { - #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL - usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb); - #endif - - is_ok = _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_DATA, &_ctrl_xfer.request); - } - - if (is_ok) { - TU_ASSERT(status_stage_xact(rhport, ep_status)); - } else { - // Stall both IN and OUT control endpoint - dcd_edpt_stall(rhport, TU_EP0_OUT); - dcd_edpt_stall(rhport, TU_EP0_IN); - } - } else { - // More data to transfer - TU_ASSERT(data_stage_xact(rhport)); - } - - return true; -} - -#endif diff --git a/src/device/usbd_pvt.h b/src/device/usbd_pvt.h index 5f11ea481..be778f9af 100644 --- a/src/device/usbd_pvt.h +++ b/src/device/usbd_pvt.h @@ -130,10 +130,6 @@ void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en); bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in); void usbd_defer_func(osal_task_func_t func, void *param, bool in_isr); -#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL -void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback); -#endif - #ifdef __cplusplus } #endif diff --git a/src/host/usbh.c b/src/host/usbh.c index 8f80800e9..9d159985e 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -40,6 +40,14 @@ #define CFG_TUH_TASK_QUEUE_SZ 16 #endif +#ifndef CFG_TUH_CONTROL_PENDING_QUEUE_SZ + #if CFG_TUH_HUB + #define CFG_TUH_CONTROL_PENDING_QUEUE_SZ 4 + #else + #define CFG_TUH_CONTROL_PENDING_QUEUE_SZ 2 + #endif +#endif + #ifndef CFG_TUH_INTERFACE_MAX #define CFG_TUH_INTERFACE_MAX 8 #endif @@ -140,7 +148,7 @@ typedef struct { uint8_t itf2drv[CFG_TUH_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) uint8_t ep2drv[CFG_TUH_ENDPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each - tu_edpt_state_t ep_status[CFG_TUH_ENDPOINT_MAX][2]; + volatile uint8_t ep_status[CFG_TUH_ENDPOINT_MAX][2]; #if CFG_TUH_API_EDPT_XFER // TODO array can be CFG_TUH_ENDPOINT_MAX-1 @@ -175,11 +183,11 @@ static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set); OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); static osal_queue_t _usbh_q; - #if CFG_TUH_HUB +#if CFG_TUH_HUB // Deferred attachment queue, only needed when using hub OSAL_QUEUE_DEF(usbh_int_set, _usbh_daqdef, CFG_TUH_HUB, hcd_event_t); static osal_queue_t _usbh_daq; - #endif +#endif // Control transfers: since most controllers do not support multiple control transfers // on multiple devices concurrently and control transfers are not used much except for @@ -189,9 +197,9 @@ typedef struct { tuh_xfer_cb_t complete_cb; uintptr_t user_data; + volatile uint16_t actual_len; volatile uint8_t stage; uint8_t daddr; - volatile uint16_t actual_len; uint8_t failed_count; } usbh_ctrl_xfer_info_t; @@ -202,17 +210,32 @@ typedef struct { } usbh_call_after_t; typedef struct { - uint8_t controller_id; // controller ID + tusb_control_request_t setup; + uint8_t* buffer; + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + uint8_t daddr; + uint8_t daddr_gen; +} usbh_pending_ctrl_t; + +// FIFO for pending async control transfers since we only execute 1 control transfer at a time +TU_FIFO_DEF(_usbh_pending_ctrl_q, CFG_TUH_CONTROL_PENDING_QUEUE_SZ * sizeof(usbh_pending_ctrl_t), false); + +typedef struct { uint8_t enumerating_daddr; // device address of the device being enumerated uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer usbh_call_after_t call_after; + // Per-daddr generation counter — bumped on usbh_device_close() to identify stale pending control transfer + uint8_t daddr_gen[TOTAL_DEVICES + 1]; +#if CFG_TUSB_OS_HAS_SCHEDULER + osal_task_handle_t task_hdl; // host task handle, lazy-captured on first tuh_task_ext() +#endif } usbh_data_t; -static usbh_data_t _usbh_data = { - .controller_id = TUSB_INDEX_INVALID_8, -}; +static uint8_t _usbh_controller_id = TUSB_INDEX_INVALID_8; +static usbh_data_t _usbh_data; typedef struct { TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); @@ -278,6 +301,18 @@ static usbh_class_driver_t const usbh_class_drivers[] = { }, #endif + #if CFG_TUH_MIDI2 + { + .name = DRIVER_NAME("MIDI2"), + .init = midih2_init, + .deinit = midih2_deinit, + .open = midih2_open, + .set_config = midih2_set_config, + .xfer_cb = midih2_xfer_cb, + .close = midih2_close + }, + #endif + #if CFG_TUH_HUB { .name = DRIVER_NAME("HUB"), @@ -334,8 +369,11 @@ static void enum_new_device(hcd_event_t* event); static void enum_delay_async(uintptr_t state); static void process_remove_event(hcd_event_t *event); static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port); + static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size); static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +static void control_xfer_dispatch_pending(void); +static void control_xfer_complete(uint8_t daddr, xfer_result_t result); TU_ATTR_ALWAYS_INLINE static inline usbh_device_t* get_device(uint8_t dev_addr) { TU_VERIFY(dev_addr > 0 && dev_addr <= TOTAL_DEVICES, NULL); @@ -352,7 +390,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, return true; } -TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) { +TU_ATTR_ALWAYS_INLINE static inline void control_xfer_set_stage(uint8_t stage) { if (_usbh_data.ctrl_xfer_info.stage != stage) { (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); _usbh_data.ctrl_xfer_info.stage = stage; @@ -360,15 +398,6 @@ TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) } } -TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const uint8_t setup_packet[8]) { - const uint8_t rhport = usbh_get_rhport(daddr); - const bool ret = hcd_setup_send(rhport, daddr, setup_packet); - if (!ret) { - _control_set_xfer_stage(CONTROL_STAGE_IDLE); - } - return ret; -} - bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param) { TU_ASSERT(_usbh_data.call_after.func == NULL); TU_LOG_USBH("USBH schedule function after %u ms\r\n", (unsigned int)ms); @@ -382,9 +411,16 @@ bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t par TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) { hcd_device_close(rhport, daddr); - // abort any ongoing control transfer - if (daddr == _usbh_data.ctrl_xfer_info.daddr) { - _control_set_xfer_stage(CONTROL_STAGE_IDLE); + // Bump the generation under the mutex so a concurrent producer in + // tuh_control_xfer stamps a value that is strictly monotonic w.r.t. close. + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + _usbh_data.daddr_gen[daddr]++; + (void) osal_mutex_unlock(_usbh_mutex); + + // If this device has in-flight control xfer, complete as FAILED + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + if (daddr == ctrl_info->daddr && ctrl_info->stage != CONTROL_STAGE_IDLE) { + control_xfer_complete(daddr, XFER_RESULT_FAILED); } // invalidate if enumerating @@ -446,7 +482,7 @@ tusb_speed_t tuh_speed_get(uint8_t daddr) { } bool tuh_rhport_is_active(uint8_t rhport) { - return _usbh_data.controller_id == rhport; + return _usbh_controller_id == rhport; } bool tuh_rhport_reset_bus(uint8_t rhport, bool active) { @@ -473,7 +509,7 @@ static void clear_device(usbh_device_t* dev) { } bool tuh_inited(void) { - return _usbh_data.controller_id != TUSB_INDEX_INVALID_8; + return _usbh_controller_id != TUSB_INDEX_INVALID_8; } bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { @@ -535,7 +571,7 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { tu_memclr(_usbh_devices, sizeof(_usbh_devices)); tu_memclr(&_usbh_data, sizeof(_usbh_data)); - _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + _usbh_controller_id = TUSB_INDEX_INVALID_8; _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; for (uint8_t i = 0; i < TOTAL_DEVICES; i++) { @@ -553,7 +589,7 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } // Init host controller - _usbh_data.controller_id = rhport; + _usbh_controller_id = rhport; TU_ASSERT(hcd_init(rhport, rh_init)); hcd_int_enable(rhport); @@ -568,7 +604,7 @@ bool tuh_deinit(uint8_t rhport) { // deinit host controller hcd_int_disable(rhport); TU_ASSERT(hcd_deinit(rhport)); - _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + _usbh_controller_id = TUSB_INDEX_INVALID_8; // remove all devices on this rhport (hub_addr = 0, hub_port = 0) remove_device_tree(rhport, 0, 0); @@ -592,6 +628,25 @@ bool tuh_deinit(uint8_t rhport) { _usbh_daq = NULL; #endif + // Fire FAILED cb for any queued async control xfer so callers aren't stranded. + usbh_pending_ctrl_t pending; + while (tu_fifo_read_n(&_usbh_pending_ctrl_q, &pending, sizeof(pending)) == sizeof(pending)) { + if (pending.complete_cb) { + tuh_xfer_t x = { + .daddr = pending.daddr, + .ep_addr = 0, + .result = XFER_RESULT_FAILED, + .actual_len = 0, + .setup = &pending.setup, + .buffer = pending.buffer, + .complete_cb = pending.complete_cb, + .user_data = pending.user_data, + }; + pending.complete_cb(&x); + } + } + tu_fifo_clear(&_usbh_pending_ctrl_q); + #if OSAL_MUTEX_REQUIRED // TODO make sure there is no task waiting on this mutex osal_mutex_delete(_usbh_mutex); @@ -617,6 +672,12 @@ bool tuh_task_event_ready(void) { } #endif + // Pending control xfer waiting for an idle slot + if (_usbh_data.ctrl_xfer_info.stage == CONTROL_STAGE_IDLE && + !tu_fifo_empty(&_usbh_pending_ctrl_q)) { + return true; + } + if (_usbh_data.call_after.func) { int32_t remain_ms = (int32_t)(_usbh_data.call_after.at_ms - tusb_time_millis_api()); if (remain_ms <= 0) { @@ -651,6 +712,13 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { (void) in_isr; // not implemented yet +#if CFG_TUSB_OS_HAS_SCHEDULER + // Save task handle on 1st run + if (_usbh_data.task_hdl == NULL) { + _usbh_data.task_hdl = osal_task_get_current_handle(); + } +#endif + // Loop until there are no more events in the queue or CFG_TUH_TASK_EVENTS_PER_RUN is reached for (unsigned epr = 0;; epr++) { #if CFG_TUH_TASK_EVENTS_PER_RUN > 0 @@ -683,6 +751,16 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { } } + // Drain pending async control xfers. Slot transitions and dispatch are + // decoupled: completion / abort / device_close set stage = IDLE via + // control_xfer_set_stage() and the actual FIFO drain happens here in the + // event loop. The check is a fast non-mutex sanity gate; the dispatcher + // itself re-checks under the mutex. + if (_usbh_data.ctrl_xfer_info.stage == CONTROL_STAGE_IDLE && + !tu_fifo_empty(&_usbh_pending_ctrl_q)) { + control_xfer_dispatch_pending(); + } + hcd_event_t event; #if CFG_TUH_HUB @@ -744,8 +822,8 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { usbh_device_t* dev = get_device(event.dev_addr); TU_VERIFY(dev && dev->connected,); - dev->ep_status[epnum][ep_dir].busy = 0; - dev->ep_status[epnum][ep_dir].claimed = 0; + // clear busy and claimed + dev->ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); if (0 == epnum) { usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); @@ -806,73 +884,179 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { // Control transfer //--------------------------------------------------------------------+ -static void _control_blocking_complete_cb(tuh_xfer_t* xfer) { - // update result - *((xfer_result_t*) xfer->user_data) = xfer->result; +// Carries both fields the sync waiter cares about — capturing from xfer_temp +// (snapshot taken before release_slot resets ctrl_info for the next pending +// entry) so the waiter sees this xfer's data, not the next dispatched one's. +typedef struct { + volatile xfer_result_t result; + volatile uint32_t actual_len; +} control_xfer_sync_param_t; + +static void control_xfer_sync_complete(tuh_xfer_t* xfer) { + control_xfer_sync_param_t* s = (control_xfer_sync_param_t*) xfer->user_data; + s->actual_len = xfer->actual_len; + s->result = xfer->result; } // TODO timeout_ms is not supported yet bool tuh_control_xfer (tuh_xfer_t* xfer) { - TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet const uint8_t daddr = xfer->daddr; - TU_VERIFY(tuh_connected(daddr)); - + TU_VERIFY(daddr <= TOTAL_DEVICES && xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; - TU_VERIFY(ctrl_info->stage == CONTROL_STAGE_IDLE); // pre-check to help reducing mutex lock - (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); - bool const is_idle = (ctrl_info->stage == CONTROL_STAGE_IDLE); - if (is_idle) { - ctrl_info->stage = CONTROL_STAGE_SETUP; - ctrl_info->daddr = daddr; - ctrl_info->actual_len = 0; - ctrl_info->failed_count = 0; +#if CFG_TUSB_OS_HAS_SCHEDULER + // Sync (complete_cb == NULL) from a host-stack callback is forbidden on + // RTOS targets — the event-loop driver can't block on its own pending xfer + // (deadlock if other control xfers are queued behind). Use async with a + // chained cb instead. OS_NONE / OS_PICO are exempt: they have a single + // execution context and the recursive-drive path is the only way to wait. + TU_ASSERT(!(xfer->complete_cb == NULL && + osal_task_get_current_handle() == _usbh_data.task_hdl)); +#endif - ctrl_info->buffer = xfer->buffer; - ctrl_info->complete_cb = xfer->complete_cb; - ctrl_info->user_data = xfer->user_data; - _usbh_epbuf.request = (*xfer->setup); - } - (void) osal_mutex_unlock(_usbh_mutex); + // Slot is single-threaded — when busy, sync callers block until it frees + // (blocking semantics require the result); async callers get queued in the + // pending FIFO and submitted by control_xfer_complete() when the slot + // drains. The test-and-{claim|enqueue} is one critical section so a slot + // that becomes IDLE between the check and the enqueue can't strand an async + // request in a queue nothing else drains. + const bool is_nonblocking = (xfer->complete_cb != NULL); + while (true) { + TU_VERIFY(tuh_connected(daddr)); + bool claimed = false; + bool is_queued = false; + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + if (ctrl_info->stage == CONTROL_STAGE_IDLE) { + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->daddr = daddr; + ctrl_info->actual_len = 0; + ctrl_info->failed_count = 0; + + ctrl_info->buffer = xfer->buffer; + ctrl_info->complete_cb = xfer->complete_cb; + ctrl_info->user_data = xfer->user_data; + _usbh_epbuf.request = (*xfer->setup); + claimed = true; + } else if (is_nonblocking) { + // Async + busy: queue the transfer. + const usbh_pending_ctrl_t entry = { + .setup = *xfer->setup, + .buffer = xfer->buffer, + .complete_cb = xfer->complete_cb, + .user_data = xfer->user_data, + .daddr = daddr, + .daddr_gen = _usbh_data.daddr_gen[daddr] + }; + is_queued = tu_fifo_write_n(&_usbh_pending_ctrl_q, &entry, sizeof(entry)) == sizeof(entry); + } + + (void) osal_mutex_unlock(_usbh_mutex); + + if (claimed) { + break; + } - TU_VERIFY(is_idle); + if (is_nonblocking) { + return is_queued; + } + + // - OS_HAS_SCHEDULER: delay 1 ms + // - Otherwise: single execution context; drive the loop ourselves to progress the in-flight transfer. +#if CFG_TUSB_OS_HAS_SCHEDULER + osal_task_delay(1); +#else + tuh_task_ext(0, false); +#endif + } TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(daddr), daddr, (xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ? tu_str_std_request[xfer->setup->bRequest] : "Class Request"); TU_LOG_BUF_USBH(xfer->setup, 8); - if (xfer->complete_cb != NULL) { - TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); - }else { - // blocking if complete callback is not provided - // change callback to internal blocking, and result as user argument - volatile xfer_result_t result = XFER_RESULT_INVALID; - - // use user_data to point to xfer_result_t - ctrl_info->user_data = (uintptr_t) &result; - ctrl_info->complete_cb = _control_blocking_complete_cb; + // Sync: wire control_xfer_sync_complete BEFORE submit so a fast completion + // event has the cb in place. control_xfer_complete() captures both result + // and actual_len through this cb before release_slot overwrites ctrl_info. + volatile control_xfer_sync_param_t sync_state; + if (!is_nonblocking) { + sync_state.result = XFER_RESULT_INVALID; + sync_state.actual_len = 0; + ctrl_info->user_data = (uintptr_t) &sync_state; + ctrl_info->complete_cb = control_xfer_sync_complete; + } - TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); + if (!hcd_setup_send(usbh_get_rhport(daddr), daddr, (uint8_t const *) &_usbh_epbuf.request)) { + control_xfer_set_stage(CONTROL_STAGE_IDLE); + return false; + } - while (result == XFER_RESULT_INVALID) { - // Note: this can be called within an callback ie. part of tuh_task() - // therefore even with RTOS tuh_task_ext() still need to be invoked + if (!is_nonblocking) { + // No tuh_connected() escape needed: usbh_device_close() routes through + // control_xfer_complete(daddr, FAILED) on disconnect, which fires + // sync_complete and unblocks this poll. + while (sync_state.result == XFER_RESULT_INVALID) { +#if CFG_TUSB_OS_HAS_SCHEDULER + osal_task_delay(1); +#else tuh_task_ext(0, false); - // TODO probably some timeout to prevent hanged +#endif } - // update transfer result, user_data is expected to point to xfer_result_t + // Forward to caller (xfer->user_data, if set, is a xfer_result_t pointer). if (xfer->user_data != 0) { - *((xfer_result_t*) xfer->user_data) = result; + *((xfer_result_t*) xfer->user_data) = sync_state.result; } - xfer->result = result; - xfer->actual_len = ctrl_info->actual_len; + xfer->result = sync_state.result; + xfer->actual_len = sync_state.actual_len; } return true; } -static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { +// Start control transfer from pending fifo +static void control_xfer_dispatch_pending(void) { + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + while (true) { + usbh_pending_ctrl_t xfer; + bool has_xfer = false; + + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + if (ctrl_info->stage == CONTROL_STAGE_IDLE && + tu_fifo_read_n(&_usbh_pending_ctrl_q, &xfer, sizeof(xfer)) == sizeof(xfer)) { + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->daddr = xfer.daddr; + ctrl_info->actual_len = 0; + ctrl_info->failed_count = 0; + ctrl_info->buffer = xfer.buffer; + ctrl_info->complete_cb = xfer.complete_cb; + ctrl_info->user_data = xfer.user_data; + _usbh_epbuf.request = xfer.setup; + has_xfer = true; + } + (void) osal_mutex_unlock(_usbh_mutex); + + if (!has_xfer) { + return; // nothing to do + } + + // mismatched daddr_gen means pending transfer is stale due to the device got disconnected while in the FIFO + // Note: the address can be re-allocated to another device at this point. + if (xfer.daddr_gen == _usbh_data.daddr_gen[xfer.daddr]) { + TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(xfer.daddr), xfer.daddr, + (xfer.setup.bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer.setup.bRequest <= TUSB_REQ_SYNCH_FRAME) ? + tu_str_std_request[xfer.setup.bRequest] : "Class Request"); + TU_LOG_BUF_USBH(&xfer.setup, 8); + if (hcd_setup_send(usbh_get_rhport(xfer.daddr), xfer.daddr, (uint8_t const *) &_usbh_epbuf.request)) { + return; // transfer kicked-off, we are done + } + } + + // complete callback as FAILED and continue with next pending xfer + control_xfer_complete(xfer.daddr, XFER_RESULT_FAILED); + } +} + +static void control_xfer_complete(uint8_t daddr, xfer_result_t result) { TU_LOG_USBH("\r\n"); usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; @@ -889,7 +1073,8 @@ static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { .user_data = ctrl_info->user_data }; - _control_set_xfer_stage(CONTROL_STAGE_IDLE); + // set to IDLE before callback since cb can invoke another transfer + control_xfer_set_stage(CONTROL_STAGE_IDLE); if (xfer_temp.complete_cb != NULL) { xfer_temp.complete_cb(&xfer_temp); @@ -903,11 +1088,17 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t tusb_control_request_t const * request = &_usbh_epbuf.request; usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + // Drop stale completions: slot already released (abort/close fired its cb) + // or now owns a different device's xfer (a pending entry was dispatched). + if (ctrl_info->stage == CONTROL_STAGE_IDLE || ctrl_info->daddr != daddr) { + return true; + } + switch (result) { case XFER_RESULT_STALLED: TU_LOG_USBH("[%u:%u] Control STALLED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); TU_LOG_BUF_USBH(request, 8); - _control_xfer_complete(daddr, result); + control_xfer_complete(daddr, result); break; case XFER_RESULT_FAILED: @@ -919,11 +1110,14 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t ctrl_info->actual_len = 0; // reset actual_len (void) osal_mutex_unlock(_usbh_mutex); - TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) request)); + if (!hcd_setup_send(rhport, daddr, (uint8_t const *) request)) { + control_xfer_complete(daddr, XFER_RESULT_FAILED); + return false; + } } else { TU_LOG_USBH("[%u:%u] Control FAILED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); TU_LOG_BUF_USBH(request, 8); - _control_xfer_complete(daddr, result); + control_xfer_complete(daddr, result); } break; @@ -932,7 +1126,7 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t case CONTROL_STAGE_SETUP: if (request->wLength > 0) { // DATA stage: initial data toggle is always 1 - _control_set_xfer_stage(CONTROL_STAGE_DATA); + control_xfer_set_stage(CONTROL_STAGE_DATA); const uint8_t ep_data = tu_edpt_addr(0, request->bmRequestType_bit.direction); TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_data, ctrl_info->buffer, request->wLength)); return true; @@ -947,7 +1141,7 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t ctrl_info->actual_len = (uint16_t) xferred_bytes; // ACK stage: toggle is always 1 - _control_set_xfer_stage(CONTROL_STAGE_ACK); + control_xfer_set_stage(CONTROL_STAGE_ACK); const uint8_t ep_status = tu_edpt_addr(0, 1 - request->bmRequestType_bit.direction); TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_status, NULL, 0)); break; @@ -964,7 +1158,7 @@ static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t } } - _control_xfer_complete(daddr, result); + control_xfer_complete(daddr, result); break; } @@ -1011,15 +1205,15 @@ bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { const usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; TU_VERIFY(daddr == ctrl_info->daddr && ctrl_info->stage != CONTROL_STAGE_IDLE); hcd_edpt_abort_xfer(rhport, daddr, ep_addr); - _control_set_xfer_stage(CONTROL_STAGE_IDLE); // reset control transfer state to idle + control_xfer_complete(daddr, XFER_RESULT_ABORTED); } else { usbh_device_t* dev = get_device(daddr); TU_VERIFY(dev); - TU_VERIFY(dev->ep_status[epnum][dir].busy); // non-control skip if not busy + TU_VERIFY(dev->ep_status[epnum][dir] & TU_EDPT_STATE_BUSY); // non-control skip if not busy // abort then mark as ready and release endpoint hcd_edpt_abort_xfer(dev->bus_info.rhport, daddr, ep_addr); - dev->ep_status[epnum][dir].busy = false; + dev->ep_status[epnum][dir] &= (uint8_t) ~TU_EDPT_STATE_BUSY; // clear busy tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex); } @@ -1043,9 +1237,9 @@ uint8_t *usbh_get_enum_buf(void) { void usbh_int_set(bool enabled) { // TODO all host controller if multiple are used since they shared the same event queue if (enabled) { - hcd_int_enable(_usbh_data.controller_id); + hcd_int_enable(_usbh_controller_id); } else { - hcd_int_disable(_usbh_data.controller_id); + hcd_int_disable(_usbh_controller_id); } } @@ -1110,16 +1304,16 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* bu uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - tu_edpt_state_t* ep_state = &dev->ep_status[epnum][dir]; + volatile uint8_t* ep_state = &dev->ep_status[epnum][dir]; TU_LOG_USBH(" Queue EP %02X with %u bytes ... \r\n", ep_addr, total_bytes); // Attempt to transfer on a busy endpoint, sound like an race condition ! - TU_ASSERT(ep_state->busy == 0); + TU_ASSERT((*ep_state & TU_EDPT_STATE_BUSY) == 0); // Set busy first since the actual transfer can be complete before hcd_edpt_xfer() // could return and USBH task can preempt and clear the busy - ep_state->busy = 1; + *ep_state |= TU_EDPT_STATE_BUSY; #if CFG_TUH_API_EDPT_XFER dev->ep_callback[epnum][dir].complete_cb = complete_cb; @@ -1130,9 +1324,8 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* bu TU_LOG_USBH("OK\r\n"); return true; } else { - // HCD error, mark endpoint as ready to allow next transfer - ep_state->busy = 0; - ep_state->claimed = 0; + // HCD error, clear busy and claimed to allow next transfer + *ep_state &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); TU_LOG1("Failed\r\n"); // TU_BREAKPOINT(); return false; @@ -1178,7 +1371,7 @@ bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - return dev->ep_status[epnum][dir].busy; + return (dev->ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) != 0; } //--------------------------------------------------------------------+ diff --git a/src/osal/osal.h b/src/osal/osal.h index 4840463f3..69cb356d4 100644 --- a/src/osal/osal.h +++ b/src/osal/osal.h @@ -76,28 +76,31 @@ typedef void (*osal_task_func_t)(void* param); /*-------------------------------------------------------------------- OSAL Porting API Should be implemented as static inline function in osal_port.h header - uint32_t osal_time_millis(void); + uint32_t osal_time_millis(void); - void osal_spin_init(osal_spinlock_t *ctx); - void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) - void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); + void osal_task_delay(uint32_t msec); + osal_task_handle_t osal_task_get_current_handle(void); - osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); - bool osal_semaphore_delete(osal_semaphore_t semd_hdl); - bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); - bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); - void osal_semaphore_reset(osal_semaphore_t sem_hdl); + void osal_spin_init(osal_spinlock_t *ctx); + void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) + void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); - osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); - bool osal_mutex_delete(osal_mutex_t mutex_hdl) - bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); - bool osal_mutex_unlock(osal_mutex_t mutex_hdl); + osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); + bool osal_semaphore_delete(osal_semaphore_t semd_hdl); + bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); + bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); + void osal_semaphore_reset(osal_semaphore_t sem_hdl); - osal_queue_t osal_queue_create(osal_queue_def_t* qdef); - bool osal_queue_delete(osal_queue_t qhdl); - bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec); - bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); - bool osal_queue_empty(osal_queue_t qhdl); + osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); + bool osal_mutex_delete(osal_mutex_t mutex_hdl) + bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); + bool osal_mutex_unlock(osal_mutex_t mutex_hdl); + + osal_queue_t osal_queue_create(osal_queue_def_t* qdef); + bool osal_queue_delete(osal_queue_t qhdl); + bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec); + bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); + bool osal_queue_empty(osal_queue_t qhdl); --------------------------------------------------------------------------*/ diff --git a/src/osal/osal_freertos.h b/src/osal/osal_freertos.h index 898edd4ed..2f36aa9e8 100644 --- a/src/osal/osal_freertos.h +++ b/src/osal/osal_freertos.h @@ -83,6 +83,19 @@ typedef struct { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef TaskHandle_t osal_task_handle_t; + +// Requires INCLUDE_xTaskGetCurrentTaskHandle == 1 in FreeRTOSConfig.h. FreeRTOS +// also exposes the symbol when configUSE_MUTEXES == 1, so accept either. +#if !defined(INCLUDE_xTaskGetCurrentTaskHandle) || (INCLUDE_xTaskGetCurrentTaskHandle == 0) + #if !defined(configUSE_MUTEXES) || (configUSE_MUTEXES == 0) + #error "TinyUSB host stack requires INCLUDE_xTaskGetCurrentTaskHandle or configUSE_MUTEXES to be enabled in FreeRTOSConfig.h" + #endif +#endif +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return xTaskGetCurrentTaskHandle(); +} + TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { return portMAX_DELAY; } if (msec == 0) { return 0; } diff --git a/src/osal/osal_mynewt.h b/src/osal/osal_mynewt.h index 335d53491..d1fa77ecb 100644 --- a/src/osal/osal_mynewt.h +++ b/src/osal/osal_mynewt.h @@ -36,6 +36,12 @@ //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef struct os_task* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return os_sched_get_current_task(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { os_time_delay( os_time_ms_to_ticks32(msec) ); } diff --git a/src/osal/osal_none.h b/src/osal/osal_none.h index 7bf6029d6..e174d3518 100644 --- a/src/osal/osal_none.h +++ b/src/osal/osal_none.h @@ -34,6 +34,22 @@ extern "C" { // osal_time_millis() is not provided, tusb_time_millis_api() must be implemented by user application //--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +// Bare-metal single context: return a non-NULL sentinel so equality compares true. +typedef void* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return (osal_task_handle_t) 1; +} + +// Bare-metal has no scheduler to yield to; this is dead code in practice because +// callers gate it on running outside the host task, which can't happen here. +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + (void) msec; +} + +//--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ // Note: This implementation is designed for bare-metal single-core systems without RTOS. diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index 6a0a21bb3..364c38b01 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -39,6 +39,13 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +// Bare-metal single context: return a non-NULL sentinel so equality compares true. +typedef void* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return (osal_task_handle_t) 1; +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { sleep_ms(msec); } diff --git a/src/osal/osal_rtthread.h b/src/osal/osal_rtthread.h index f560281c5..a151a7d70 100644 --- a/src/osal/osal_rtthread.h +++ b/src/osal/osal_rtthread.h @@ -38,6 +38,12 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef rt_thread_t osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return rt_thread_self(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { rt_thread_mdelay(msec); } diff --git a/src/osal/osal_rtx4.h b/src/osal/osal_rtx4.h index e1930c96c..e5b708a2c 100644 --- a/src/osal/osal_rtx4.h +++ b/src/osal/osal_rtx4.h @@ -37,6 +37,12 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef OS_TID osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return os_tsk_self(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { uint16_t hi = msec >> 16; uint16_t lo = msec; diff --git a/src/osal/osal_threadx.h b/src/osal/osal_threadx.h index 6bcf9c5ab..cca4eb487 100644 --- a/src/osal/osal_threadx.h +++ b/src/osal/osal_threadx.h @@ -37,6 +37,11 @@ extern "C" { //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef TX_THREAD* osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return tx_thread_identify(); +} TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { if ( msec == TX_WAIT_FOREVER ) { diff --git a/src/osal/osal_zephyr.h b/src/osal/osal_zephyr.h index 900ac786c..6ea45131e 100644 --- a/src/osal/osal_zephyr.h +++ b/src/osal/osal_zephyr.h @@ -31,6 +31,12 @@ //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ +typedef k_tid_t osal_task_handle_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_task_handle_t osal_task_get_current_handle(void) { + return k_current_get(); +} + TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { k_msleep(msec); } diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c index 6ed5fde8e..befbaa338 100644 --- a/src/portable/nordic/nrf5x/dcd_nrf5x.c +++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c @@ -1049,6 +1049,12 @@ void tusb_hal_nrf_power_event(uint32_t event) { NVIC_EnableIRQ(USBD_IRQn); } + // Ensure HFCLK is requested in the current context. The hfclk_enable() in + // USB_EVT_DETECTED may have been pre-SoftDevice. After Softdevice is + // enabled, HFXO is physically off again. So any caller that fires + // USB_EVT_READY post-SD would hang here. + hfclk_enable(); + // Wait for HFCLK while (!hfclk_running()) {} diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 02a4e055e..064834efb 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -617,10 +617,16 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); } else { - // Control endpoint can change direction 0x00 <-> 0x80 when changing stages - if (ep_addr != ep->ep_addr) { + // Control transfer data and status stages always start with DATA1, regardless of + // whether the direction changed since the previous stage. SET_REPORT (and any other + // host-to-device class request with an OUT data stage) keeps the same direction + // across SETUP -> DATA, so we cannot key off "direction changed" -- we must reset + // next_pid every time hcd_edpt_xfer is invoked on ep 0. Without this, the data stage + // of SET_REPORT goes out as DATA0 because ep->next_pid is still 0 from hcd_edpt_open(), + // which strict devices treat as a protocol violation and disconnect. + if (tu_edpt_number(ep_addr) == 0) { ep->ep_addr = ep_addr; - ep->next_pid = 1; // data and status stage start with DATA1 + ep->next_pid = 1; } // If EPX is busy with another transfer, mark as pending diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 30e24a9ad..c90429a15 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -73,8 +73,9 @@ typedef struct { static dcd_data_t _dcd_data; -CFG_TUD_MEM_SECTION static struct { - TUD_EPBUF_DEF(setup_packet, 8); +CFG_TUD_MEM_SECTION static union { + TUD_EPBUF_DEF(setup_buffer, 8); + tusb_control_request_t setup_packet; } _dcd_usbbuf; static tud_configure_dwc2_t _tud_cfg = CFG_TUD_CONFIGURE_DWC2_DEFAULT; @@ -138,7 +139,7 @@ static void dma_setup_prepare(uint8_t rhport) { // Receive only 1 packet dwc2->epout[0].doeptsiz = (1 << DOEPTSIZ_STUPCNT_Pos) | (1 << DOEPTSIZ_PKTCNT_Pos) | (8 << DOEPTSIZ_XFRSIZ_Pos); - dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_packet; + dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_buffer; dwc2->epout[0].doepctl |= DOEPCTL_EPENA | DOEPCTL_USBAEP; } @@ -793,13 +794,15 @@ static void handle_bus_reset(uint8_t rhport) { xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + uint32_t oepmsk = 0; if(dma_device_enabled(dwc2)) { + oepmsk = GINTMSK_OEPINT; dma_setup_prepare(rhport); } else { dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); } - dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; + dwc2->gintmsk |= GINTMSK_OTGINT | oepmsk | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; } static void handle_enum_done(uint8_t rhport) { @@ -896,17 +899,31 @@ static void handle_rxflvl_irq(uint8_t rhport) { case GRXSTS_PKTSTS_SETUP_RX: { // Setup packet received - uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_packet; + uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer; // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); + + dwc2_dep_t* epin0 = &dwc2->epin[0]; + if (edpt_is_enabled(epin0)) { + edpt_disable(rhport, 0x80, false); + } + + // (GenID < 3.00a) Must wait SETUP_DONE before next OUT transfer, otherwise OUT data may be corrupted. + // (GenID >= 3.00a) On the other hand STUPCNT is auto reloaded and SETUP_DONE is only triggered once after bus reset. + if (dwc2->gsnpsid >= DWC2_CORE_REV_3_00a) { + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); + } break; } case GRXSTS_PKTSTS_SETUP_DONE: // Setup packet done: - // After popping this out, dwc2 asserts a DOEPINT_SETUP interrupt which is handled by handle_epout_irq() epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); + + if (dwc2->gsnpsid < DWC2_CORE_REV_3_00a) { + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); + } break; case GRXSTS_PKTSTS_RX_DATA: { @@ -935,41 +952,19 @@ static void handle_rxflvl_irq(uint8_t rhport) { break; } - case GRXSTS_PKTSTS_RX_COMPLETE: + case GRXSTS_PKTSTS_RX_COMPLETE: { // Out packet done - // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on - // the specified OUT endpoint which will be handled by handle_epout_irq() - break; - - default: break; // nothing to do - } -} - -static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { - if (doepint_bm.setup_phase_done) { - // Cleanup previous pending EP0 IN transfer if any - dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; - if (edpt_is_enabled(epin0)) { - edpt_disable(rhport, 0x80, false); - } - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); - return; - } - - // Normal OUT transfer complete - if (doepint_bm.xfer_complete) { - // only handle data skip if it is setup or status related - // Note: even though (xfer_complete + status_phase_rx) is for buffered DMA only, for STM32L47x (dwc2 v3.00a) they - // can is set when GRXSTS_PKTSTS_SETUP_RX is popped therefore they can bet set before/together with setup_phase_done - if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) { xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); - if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) { - // EP0 can only handle one packet, Schedule another packet to be received. - edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); + if (epnum == 0 && _dcd_data.ep0_pending[TUSB_DIR_OUT] > 0) { + // EP0 can only handle one packet, schedule another packet to be received. + edpt_schedule_packets(rhport, 0, TUSB_DIR_OUT); } else { dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } + break; } + + default: break; // nothing to do } } @@ -1012,9 +1007,13 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi if (edpt_is_enabled(epin0)) { edpt_disable(rhport, 0x80, false); } - dma_setup_prepare(rhport); - dcd_dcache_invalidate(_dcd_usbbuf.setup_packet, 8); - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); + dcd_dcache_invalidate(_dcd_usbbuf.setup_buffer, 8); + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); + + // Prepare EP0 for next setup if this setup has no data stage + if (_dcd_usbbuf.setup_packet.wLength == 0) { + dma_setup_prepare(rhport); + } return; } @@ -1035,9 +1034,8 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi const uint16_t remain = tsiz.xfer_size; xfer->total_len -= remain; - // this is ZLP, so prepare EP0 for next setup - // TODO use status phase rx - if(epnum == 0 && xfer->total_len == 0) { + // prepare EP0 for next setup + if(epnum == 0) { dma_setup_prepare(rhport); } @@ -1056,9 +1054,6 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin // EP0 can only handle one packet. Schedule another packet to be transmitted. edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN); } else { - if(epnum == 0) { - dma_setup_prepare(rhport); - } dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); } } @@ -1098,8 +1093,6 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) { #if CFG_TUD_DWC2_SLAVE_ENABLE if (dir == TUSB_DIR_IN) { handle_epin_slave(rhport, epnum, intr.diepint_bm); - } else { - handle_epout_slave(rhport, epnum, intr.doepint_bm); } #endif } @@ -1136,8 +1129,9 @@ static void handle_incomplete_iso_in(uint8_t rhport) { } epin->diepctl = depctl.value; } else { - // too many retries, give up + // too many retries, give up, but keep endpoint activated edpt_disable(rhport, epnum | TUSB_DIR_IN_MASK, false); + epin->diepctl |= DIEPCTL_USBAEP; dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, 0, XFER_RESULT_FAILED, true); } } @@ -1206,7 +1200,7 @@ void dcd_int_handler(uint8_t rhport) { dwc2->gotgint = otg_int; } - if(gintsts & GINTSTS_SOF) { + if(gintsts & GINTSTS_SOF && dwc2->gintmsk & GINTMSK_SOFM) { dwc2->gintsts = GINTSTS_SOF; dwc2->gintmsk |= GINTMSK_USBSUSPM; const uint32_t frame = (dwc2->dsts & DSTS_FNSOF) >> DSTS_FNSOF_Pos; @@ -1219,6 +1213,12 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_sof(rhport, frame, true); } + // IN endpoint interrupt handling. + if (gintsts & GINTSTS_IEPINT) { + // IEPINT bit read-only, clear using DIEPINTn + handle_ep_irq(rhport, TUSB_DIR_IN); + } + #if CFG_TUD_DWC2_SLAVE_ENABLE // RxFIFO non-empty interrupt handling. if (gintsts & GINTSTS_RXFLVL) { @@ -1233,17 +1233,13 @@ void dcd_int_handler(uint8_t rhport) { } #endif +#if CFG_TUD_DWC2_DMA_ENABLE // OUT endpoint interrupt handling. if (gintsts & GINTSTS_OEPINT) { // OEPINT is read-only, clear using DOEPINTn handle_ep_irq(rhport, TUSB_DIR_OUT); } - - // IN endpoint interrupt handling. - if (gintsts & GINTSTS_IEPINT) { - // IEPINT bit read-only, clear using DIEPINTn - handle_ep_irq(rhport, TUSB_DIR_IN); - } +#endif // Incomplete isochronous IN transfer interrupt handling. if (gintsts & GINTSTS_IISOIXFR) { diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index 6098d6eaa..9ea5f33c5 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -903,9 +903,6 @@ static void handle_rxflvl_irq(uint8_t rhport) { // return true if there is still pending data and need more ISR static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { - // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) - const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; - const uint8_t max_channel = dwc2_channel_count(dwc2); for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { dwc2_channel_t* channel = &dwc2->channel[ch_id]; @@ -923,6 +920,8 @@ static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { // skip if there is not enough space in FIFO and RequestQueue. // Packet's last word written to FIFO will trigger a request queue + // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) + const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) { return true; } diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c index 5cd25e33e..af0f17785 100644 --- a/src/portable/wch/dcd_ch32_usbfs.c +++ b/src/portable/wch/dcd_ch32_usbfs.c @@ -29,29 +29,29 @@ #if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBFS) && CFG_TUD_WCH_USBIP_USBFS -#include "device/dcd.h" -#include "ch32_usbfs_reg.h" + #include "device/dcd.h" + #include "ch32_usbfs_reg.h" -/* private defines */ -#define EP_MAX (8) + /* private defines */ + #define EP_MAX (8) -#define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep]) -#define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep]) -#define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep]) -#define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep]) + #define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep]) + #define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep]) + #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep]) + #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep]) /* private data */ struct usb_xfer { - bool valid; - uint8_t* buffer; - size_t len; - size_t processed_len; - size_t max_size; + bool valid; + uint8_t *buffer; + size_t len; + size_t processed_len; + size_t max_size; }; static struct { - bool ep0_tog; - bool isochronous[EP_MAX]; + bool ep0_tog; + bool isochronous[EP_MAX]; struct usb_xfer xfer[EP_MAX][2]; TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64]; TU_ATTR_ALIGNED(4) struct { @@ -64,7 +64,7 @@ static struct { /* private helpers */ static void update_in(uint8_t rhport, uint8_t ep, bool force) { - struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_IN]; + struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_IN]; if (xfer->valid) { if (force || xfer->len) { size_t len = TU_MIN(xfer->max_size, xfer->len); @@ -82,7 +82,7 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) { EP_TX_LEN(ep) = len; if (ep == 0) { EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0); - data.ep0_tog = !data.ep0_tog; + data.ep0_tog = !data.ep0_tog; } else if (data.isochronous[ep]) { EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NYET; } else { @@ -90,16 +90,18 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) { } } else { xfer->valid = false; - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK; - dcd_event_xfer_complete( - rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, - XFER_RESULT_SUCCESS, true); + if (ep == 0) { + EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0); + } else if (!data.isochronous[ep]) { + EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK; + } + dcd_event_xfer_complete(rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, XFER_RESULT_SUCCESS, true); } } } static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) { - struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_OUT]; + struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_OUT]; if (xfer->valid) { size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len)); if (ep == 3) { @@ -117,25 +119,39 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) { } if (ep == 0) { - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + EP_RX_CTRL(0) = USBFS_EP_R_RES_NAK; + } else { + uint8_t rx_res = + data.isochronous[ep] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK); + EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res; } } } +static void reset_ep_ctrls(void) { + for (uint8_t ep = 1; ep < EP_MAX; ep++) { + EP_DMA(ep) = (uint32_t)&data.buffer[ep][0]; + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET; + EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET; + } + EP_DMA(3) = (uint32_t)&data.ep3_buffer.out[0]; +} + /* public functions */ -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rh_init; +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rh_init; // init registers USBOTG_FS->BASE_CTRL = USBFS_CTRL_SYS_CTRL | USBFS_CTRL_INT_BUSY | USBFS_CTRL_DMA_EN; USBOTG_FS->UDEV_CTRL = USBFS_UDEV_CTRL_PD_DIS | USBFS_UDEV_CTRL_PORT_EN; - USBOTG_FS->DEV_ADDR = 0x00; + USBOTG_FS->DEV_ADDR = 0x00; USBOTG_FS->INT_FG = 0xFF; USBOTG_FS->INT_EN = USBFS_INT_EN_BUS_RST | USBFS_INT_EN_TRANSFER | USBFS_INT_EN_SUSPEND; // setup endpoint 0 - EP_DMA(0) = (uint32_t) &data.buffer[0][0]; - EP_TX_LEN(0) = 0; + EP_DMA(0) = (uint32_t)&data.buffer[0][0]; + EP_TX_LEN(0) = 0; EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; @@ -143,15 +159,9 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { USBOTG_FS->UEP4_1_MOD = 0xCC; USBOTG_FS->UEP2_3_MOD = 0xCC; USBOTG_FS->UEP5_6_MOD = 0xCC; - USBOTG_FS->UEP7_MOD = 0x0C; + USBOTG_FS->UEP7_MOD = 0x0C; - for (uint8_t ep = 1; ep < EP_MAX; ep++) { - EP_DMA(ep) = (uint32_t) &data.buffer[ep][0]; - EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK; - } - EP_DMA(3) = (uint32_t) &data.ep3_buffer.out[0]; + reset_ep_ctrls(); dcd_connect(rhport); @@ -159,15 +169,15 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } void dcd_int_handler(uint8_t rhport) { - (void) rhport; + (void)rhport; uint8_t status = USBOTG_FS->INT_FG; if (status & USBFS_INT_FG_TRANSFER) { - uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST); - uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST); + uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST); + uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST); + uint16_t rx_len = USBOTG_FS->RX_LEN; switch (token) { case PID_OUT: { - uint16_t rx_len = USBOTG_FS->RX_LEN; update_out(rhport, ep, rx_len); break; } @@ -179,24 +189,30 @@ void dcd_int_handler(uint8_t rhport) { case PID_SETUP: // setup clears stall EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + data.ep0_tog = true; + + const tusb_control_request_t *setup = (const tusb_control_request_t *)&data.buffer[0][TUSB_DIR_OUT][0]; + EP_RX_CTRL(0) = (setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK; - data.ep0_tog = true; dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true); break; } USBOTG_FS->INT_FG = USBFS_INT_FG_TRANSFER; } else if (status & USBFS_INT_FG_BUS_RST) { - data.ep0_tog = true; + data.ep0_tog = true; data.xfer[0][TUSB_DIR_OUT].max_size = 64; - data.xfer[0][TUSB_DIR_IN].max_size = 64; + data.xfer[0][TUSB_DIR_IN].max_size = 64; - //dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); - dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); + // dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, + // true); + dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, + true); USBOTG_FS->DEV_ADDR = 0x00; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + + reset_ep_ctrls(); USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST; } else if (status & USBFS_INT_FG_SUSPEND) { @@ -207,71 +223,62 @@ void dcd_int_handler(uint8_t rhport) { } void dcd_int_enable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_EnableIRQ(USBHD_IRQn); } void dcd_int_disable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_DisableIRQ(USBHD_IRQn); } void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - (void) dev_addr; + (void)dev_addr; dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); // zlp status response } void dcd_remote_wakeup(uint8_t rhport) { - (void) rhport; + (void)rhport; // TODO optional } void dcd_connect(uint8_t rhport) { - (void) rhport; + (void)rhport; USBOTG_FS->BASE_CTRL |= USBFS_CTRL_DEV_PUEN; } void dcd_disconnect(uint8_t rhport) { - (void) rhport; + (void)rhport; USBOTG_FS->BASE_CTRL &= ~USBFS_CTRL_DEV_PUEN; } void dcd_sof_enable(uint8_t rhport, bool en) { - (void) rhport; - (void) en; + (void)rhport; + (void)en; // TODO implement later } -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { - (void) rhport; +void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) { + (void)rhport; if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS) { - USBOTG_FS->DEV_ADDR = (uint8_t) request->wValue; + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) { + USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue; } - EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; } -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { - (void) rhport; - uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress); +bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress); uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress); TU_ASSERT(ep < EP_MAX); - data.isochronous[ep] = desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS; data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep); if (ep != 0) { if (dir == TUSB_DIR_OUT) { - if (data.isochronous[ep]) { - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET; - } else { - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_ACK; - } + EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_T_RES_NAK; } else { - EP_TX_LEN(ep) = 0; EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; } } @@ -279,52 +286,60 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { } void dcd_edpt_close_all(uint8_t rhport) { - (void) rhport; + (void)rhport; // TODO optional } bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - (void) rhport; - (void) ep_addr; + (void)rhport; + (void)ep_addr; (void)largest_packet_size; - return false; + uint8_t ep = tu_edpt_number(ep_addr); + uint8_t dir = tu_edpt_dir(ep_addr); + + data.isochronous[ep] = true; + data.xfer[ep][dir].max_size = largest_packet_size; + return true; } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { (void)rhport; (void)desc_ep; - return false; + return true; } -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { - (void) is_isr; - (void) rhport; - uint8_t ep = tu_edpt_number(ep_addr); +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + (void)rhport; + uint8_t ep = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); - struct usb_xfer* xfer = &data.xfer[ep][dir]; + struct usb_xfer *xfer = &data.xfer[ep][dir]; dcd_int_disable(rhport); - xfer->valid = true; - xfer->buffer = buffer; - xfer->len = total_bytes; + xfer->valid = true; + xfer->buffer = buffer; + xfer->len = total_bytes; xfer->processed_len = 0; dcd_int_enable(rhport); if (dir == TUSB_DIR_IN) { update_in(rhport, ep, true); + } else { + uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK; + EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res; } return true; } void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; - uint8_t ep = tu_edpt_number(ep_addr); + (void)rhport; + uint8_t ep = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); if (ep == 0) { if (dir == TUSB_DIR_OUT) { EP_RX_CTRL(0) = USBFS_EP_R_RES_STALL; } else { - EP_TX_LEN(0) = 0; + EP_TX_LEN(0) = 0; EP_TX_CTRL(0) = USBFS_EP_T_RES_STALL; } } else { @@ -337,8 +352,8 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; - uint8_t ep = tu_edpt_number(ep_addr); + (void)rhport; + uint8_t ep = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); if (ep == 0) { if (dir == TUSB_DIR_OUT) { @@ -346,9 +361,9 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { } } else { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~(USBFS_EP_R_RES_MASK | USBFS_EP_R_TOG)) | USBFS_EP_R_RES_ACK; + EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK; } else { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK | USBFS_EP_T_TOG)) | USBFS_EP_T_RES_NAK; + EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; } } } diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c index 11734de37..ea3b052ad 100644 --- a/src/portable/wch/dcd_ch32_usbhs.c +++ b/src/portable/wch/dcd_ch32_usbhs.c @@ -24,7 +24,6 @@ * * This file is part of the TinyUSB stack. */ - #include "tusb_option.h" #if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && defined(CFG_TUD_WCH_USBIP_USBHS) && \ @@ -37,138 +36,182 @@ #define EP_MAX 16 typedef struct { - uint8_t* buffer; + uint8_t *buffer; uint16_t total_len; uint16_t queued_len; uint16_t max_size; - bool is_last_packet; - bool is_iso; + bool is_iso; + bool valid; } xfer_ctl_t; -typedef enum { - EP_RESPONSE_ACK, - EP_RESPONSE_NAK, -} ep_response_list_t; - -#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] + #define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] static xfer_ctl_t xfer_status[EP_MAX][2]; -#define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2) -#define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4) -#define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4) -#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2) + #define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2) + #define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4) + #define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4) + #define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2) -#define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1)) -#define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1)) + #define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1)) + #define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1)) /* Endpoint Buffer */ TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE]; +static bool ep0_tog; +static bool ep_data_tog[EP_MAX][2]; -static void ep_set_response_and_toggle(uint8_t ep_num, tusb_dir_t ep_dir, ep_response_list_t response_type) { +static void set_ep_toggle(uint8_t ep_num, tusb_dir_t ep_dir, bool data1) { if (ep_dir == TUSB_DIR_IN) { - uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_T_RES_ACK : USBHS_EP_T_RES_NAK; - if (ep_num == 0) { - if (response_type == EP_RESPONSE_ACK) { - if (EP_TX_LEN(ep_num) == 0) { - EP_TX_CTRL(ep_num) |= USBHS_EP_T_TOG_1; - } else { - EP_TX_CTRL(ep_num) ^= USBHS_EP_T_TOG_1; - } - } - } - if (xfer_status[ep_num][TUSB_DIR_IN].is_iso == true) { - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG; - } else { - EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | response; - } + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_TOG_MASK)) | + (data1 ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0); } else { - uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK; - if (ep_num == 0) { - if (response_type == EP_RESPONSE_ACK) { - if (xfer_status[ep_num][TUSB_DIR_OUT].queued_len == 0) { - EP_RX_CTRL(ep_num) |= USBHS_EP_R_TOG_1; - } - } else { - EP_RX_CTRL(ep_num) ^= USBHS_EP_R_TOG_1; - } - } - EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | response; + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_TOG_MASK)) | + (data1 ? USBHS_EP_R_TOG_1 : USBHS_EP_R_TOG_0); } } -static void xfer_data_packet(uint8_t ep_num, tusb_dir_t ep_dir, xfer_ctl_t* xfer) { - if (ep_dir == TUSB_DIR_IN) { - uint16_t remaining = xfer->total_len - xfer->queued_len; - uint16_t next_tx_size = TU_MIN(remaining, xfer->max_size); +static void queue_in_packet(uint8_t ep_num, xfer_ctl_t* xfer) { + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t tx_len = TU_MIN(remaining, xfer->max_size); + + if (ep_num == 0) { + memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], tx_len); + } else { + EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + } + + EP_TX_LEN(ep_num) = tx_len; + xfer->queued_len += tx_len; + + if (ep_num == 0) { + EP_TX_CTRL(0) = USBHS_EP_T_RES_ACK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0); + ep0_tog = !ep0_tog; + } else if (xfer->is_iso) { + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NYET; + } else { + set_ep_toggle(ep_num, TUSB_DIR_IN, ep_data_tog[ep_num][TUSB_DIR_IN]); + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_ACK; + } +} + +static void queue_out_packet(uint8_t ep_num, xfer_ctl_t* xfer) { + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t rx_len = TU_MIN(remaining, xfer->max_size); + + if (ep_num > 0) { + EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + EP_RX_MAX_LEN(ep_num) = rx_len; + } + + if (ep_num == 0) { + EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK; + } else if (xfer->is_iso) { + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NYET; + } else { + set_ep_toggle(ep_num, TUSB_DIR_OUT, ep_data_tog[ep_num][TUSB_DIR_OUT]); + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK; + } +} +static void update_in(uint8_t rhport, uint8_t ep_num, bool force) { + xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN); + if (!xfer->valid) { + return; + } + + if (!force && ep_num != 0 && !xfer->is_iso) { + ep_data_tog[ep_num][TUSB_DIR_IN] = !ep_data_tog[ep_num][TUSB_DIR_IN]; + } + + if (force || (xfer->total_len > xfer->queued_len)) { + queue_in_packet(ep_num, xfer); + } else { + xfer->valid = false; if (ep_num == 0) { - memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], next_tx_size); + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0); } else { - EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NAK; } + dcd_event_xfer_complete(rhport, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } +} - EP_TX_LEN(ep_num) = next_tx_size; - xfer->queued_len += next_tx_size; - if (xfer->queued_len == xfer->total_len) { - xfer->is_last_packet = true; - } - if (xfer->is_iso == true) { - /* Enable EP to generate ISA_ACT interrupt */ - USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); - } - } else { /* TUSB_DIR_OUT */ - uint16_t left_to_receive = xfer->total_len - xfer->queued_len; - uint16_t max_possible_rx_size = TU_MIN(xfer->max_size, left_to_receive); +static void update_out(uint8_t rhport, uint8_t ep_num, uint16_t rx_len) { + xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT); + if (!xfer->valid) { + return; + } + + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t len = TU_MIN(rx_len, TU_MIN(remaining, xfer->max_size)); + + if (ep_num == 0) { + memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, len); + } - if (max_possible_rx_size == left_to_receive) { - xfer->is_last_packet = true; + xfer->queued_len += len; + + if (ep_num != 0 && !xfer->is_iso) { + ep_data_tog[ep_num][TUSB_DIR_OUT] = !ep_data_tog[ep_num][TUSB_DIR_OUT]; + } + + if ((xfer->queued_len == xfer->total_len) || (len < xfer->max_size)) { + xfer->valid = false; + if (ep_num == 0) { + EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NAK; } + dcd_event_xfer_complete(rhport, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } - if (ep_num > 0) { - EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; - EP_RX_MAX_LEN(ep_num) = max_possible_rx_size; + if (ep_num != 0) { + if (xfer->valid) { + queue_out_packet(ep_num, xfer); + } else { + uint8_t rx_res = xfer->is_iso ? USBHS_EP_R_RES_NYET : USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | rx_res; } } - ep_set_response_and_toggle(ep_num, ep_dir, USBHS_EP_R_RES_ACK); } -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rhport; + (void)rh_init; memset(&xfer_status, 0, sizeof(xfer_status)); + memset(ep_data_tog, 0, sizeof(ep_data_tog)); + ep0_tog = true; USBHSD->HOST_CTRL = 0x00; USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM; USBHSD->CONTROL = 0; -#if TUD_OPT_HIGH_SPEED + #if TUD_OPT_HIGH_SPEED USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED; -#else - #error OPT_MODE_FULL_SPEED not currently supported on CH32 + #else + #error OPT_MODE_FULL_SPEED not currently supported on CH32 USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED; -#endif + #endif USBHSD->INT_EN = 0; - USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN | USBHS_ISO_ACT_EN; + USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN; USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN; - USBHSD->ENDP_TYPE = 0x00; - USBHSD->BUF_MODE = 0x00; + USBHSD->ENDP_TYPE = 0x00; + USBHSD->BUF_MODE = 0x00; for (int ep = 0; ep < EP_MAX; ep++) { - EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK; - EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; EP_RX_MAX_LEN(ep) = 0; } - USBHSD->UEP0_DMA = (uint32_t) ep0_buffer; - USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE; + USBHSD->UEP0_DMA = (uint32_t)ep0_buffer; + USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; - xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; USBHSD->DEV_AD = 0; USBHSD->CONTROL |= USBHS_DEV_PU_EN; @@ -177,22 +220,24 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } void dcd_int_enable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_EnableIRQ(USBHS_IRQn); } void dcd_int_disable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_DisableIRQ(USBHS_IRQn); } void dcd_edpt_close_all(uint8_t rhport) { - (void) rhport; + (void)rhport; + + memset(ep_data_tog, 0, sizeof(ep_data_tog)); for (size_t ep = 1; ep < EP_MAX; ep++) { - EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK; - EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; EP_RX_MAX_LEN(ep) = 0; } @@ -201,18 +246,18 @@ void dcd_edpt_close_all(uint8_t rhport) { } void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - (void) dev_addr; + (void)dev_addr; // Response with zlp status dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); } void dcd_remote_wakeup(uint8_t rhport) { - (void) rhport; + (void)rhport; } void dcd_sof_enable(uint8_t rhport, bool en) { - (void) rhport; + (void)rhport; if (en) { USBHSD->INT_EN |= USBHS_SOF_ACT_EN; } else { @@ -220,24 +265,19 @@ void dcd_sof_enable(uint8_t rhport, bool en) { } } -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { - (void) rhport; - +void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) { + (void)rhport; if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS) { - USBHSD->DEV_AD = (uint8_t) request->wValue; + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) { + USBHSD->DEV_AD = (uint8_t)request->wValue; } - - EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; - EP_RX_CTRL(0) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; } -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { - (void) rhport; +bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_edpt) { + (void)rhport; - uint8_t const ep_num = tu_edpt_number(desc_edpt->bEndpointAddress); - tusb_dir_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); + const uint8_t ep_num = tu_edpt_number(desc_edpt->bEndpointAddress); + const tusb_dir_t dir = tu_edpt_dir(desc_edpt->bEndpointAddress); TU_ASSERT(ep_num < EP_MAX); @@ -245,13 +285,14 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { return true; } - xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir); - xfer->max_size = tu_edpt_packet_size(desc_edpt); + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir); + xfer->max_size = tu_edpt_packet_size(desc_edpt); + ep_data_tog[ep_num][dir] = false; xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS); if (dir == TUSB_DIR_OUT) { USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << ep_num); - EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; if (xfer->is_iso == true) { USBHSD->ENDP_TYPE |= (USBHS_EP0_R_TYP << ep_num); } @@ -259,31 +300,31 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { } else { if (xfer->is_iso == true) { USBHSD->ENDP_TYPE |= (USBHS_EP0_T_TYP << ep_num); - } else { - /* Enable all types except Isochronous to avoid ISO_ACT interrupt generation */ - USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); } - EP_TX_LEN(ep_num) = 0; - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); + EP_TX_LEN(ep_num) = 0; + EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; } return true; } void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; + (void)rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; EP_RX_MAX_LEN(ep_num) = 0; + ep_data_tog[ep_num][TUSB_DIR_OUT] = false; USBHSD->ENDP_TYPE &= ~(USBHS_EP0_R_TYP << ep_num); USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_R_EN << ep_num); - } else { // TUSB_DIR_IN - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; - EP_TX_LEN(ep_num) = 0; + } else { // TUSB_DIR_IN + EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_TX_LEN(ep_num) = 0; + ep_data_tog[ep_num][TUSB_DIR_IN] = false; USBHSD->ENDP_TYPE &= ~(USBHS_EP0_T_TYP << ep_num); USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num); } @@ -305,128 +346,120 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) #endif void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; + (void)rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); if (dir == TUSB_DIR_OUT) { EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_STALL; } else { - EP_TX_LEN(0) = 0; + EP_TX_LEN(ep_num) = 0; EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_STALL; } } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; + (void)rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; + ep_data_tog[ep_num][TUSB_DIR_OUT] = false; } else { - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + ep_data_tog[ep_num][TUSB_DIR_IN] = false; } } -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { - (void) is_isr; - (void) rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + (void)rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir); + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + xfer->valid = true; - xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->queued_len = 0; - xfer->is_last_packet = false; + if (ep_num == 0 && dir == TUSB_DIR_OUT) { + if (total_bytes == 0) { + EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_TOG_MASK)) | USBHS_EP_R_TOG_1; + } else { + EP_RX_CTRL(0) ^= USBHS_EP_R_TOG_1; + } + } - xfer_data_packet(ep_num, dir, xfer); + if (dir == TUSB_DIR_IN) { + update_in(rhport, ep_num, true); + } else { + queue_out_packet(ep_num, xfer); + } return true; } void dcd_int_handler(uint8_t rhport) { - (void) rhport; + (void)rhport; - uint8_t int_flag = USBHSD->INT_FG; + uint8_t int_flag = USBHSD->INT_FG; uint8_t int_status = USBHSD->INT_ST; - if (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)) { - uint8_t const token = int_status & MASK_UIS_TOKEN; + if (int_flag & USBHS_TRANSFER_FLAG) { + const uint8_t token = int_status & MASK_UIS_TOKEN; + const uint8_t ep_num = int_status & MASK_UIS_ENDP; + const uint16_t len = USBHSD->RX_LEN; if (token == USBHS_TOKEN_PID_SOF) { uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK; dcd_event_sof(rhport, frame_count, true); - }else { - uint8_t const ep_num = int_status & MASK_UIS_ENDP; - tusb_dir_t const ep_dir = (token == USBHS_TOKEN_PID_IN) ? TUSB_DIR_IN : TUSB_DIR_OUT; - uint8_t const ep_addr = tu_edpt_addr(ep_num, ep_dir); - xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, ep_dir); - - if (token == USBHS_TOKEN_PID_OUT) { - uint16_t rx_len = USBHSD->RX_LEN; - - if (ep_num == 0) { - memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, rx_len); - } - - xfer->queued_len += rx_len; - if (rx_len < xfer->max_size) { - xfer->is_last_packet = true; - } - } else if (token == USBHS_TOKEN_PID_IN) { - if (xfer->is_iso && xfer->is_last_packet) { - /* Disable EP to avoid ISO_ACT interrupt generation */ - USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num); - } else { - // Do nothing, no need to update xfer->is_last_packet, it is already updated in xfer_data_packet - } - } - - if (xfer->is_last_packet == true) { - ep_set_response_and_toggle(ep_num, ep_dir, EP_RESPONSE_NAK); - dcd_event_xfer_complete(0, ep_addr, xfer->queued_len, XFER_RESULT_SUCCESS, true); - } else { - /* prepare next part of packet to xref */ - xfer_data_packet(ep_num, ep_dir, xfer); - } + } else if (token == USBHS_TOKEN_PID_OUT) { + update_out(rhport, ep_num, len); + } else if (token == USBHS_TOKEN_PID_IN) { + update_in(rhport, ep_num, false); } - - USBHSD->INT_FG = (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)); /* Clear flag */ + USBHSD->INT_FG = (int_flag & USBHS_TRANSFER_FLAG); /* Clear flag */ } else if (int_flag & USBHS_SETUP_FLAG) { - ep_set_response_and_toggle(0, TUSB_DIR_IN, EP_RESPONSE_NAK); - ep_set_response_and_toggle(0, TUSB_DIR_OUT, EP_RESPONSE_NAK); + tusb_control_request_t const* setup = + (tusb_control_request_t const*) ep0_buffer; + ep0_tog = true; + EP_RX_CTRL(0) = (setup->wLength == 0) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK; + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK; + dcd_event_setup_received(0, ep0_buffer, true); USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */ } else if (int_flag & USBHS_BUS_RST_FLAG) { // TODO CH32 does not detect actual speed at this time (should be known at end of reset) // This interrupt probably triggered at start of bus reset -// tusb_speed_t actual_speed; -// switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){ -// case USBHS_SPEED_TYPE_HIGH: -// actual_speed = TUSB_SPEED_HIGH; -// break; -// case USBHS_SPEED_TYPE_FULL: -// actual_speed = TUSB_SPEED_FULL; -// break; -// case USBHS_SPEED_TYPE_LOW: -// actual_speed = TUSB_SPEED_LOW; -// break; -// default: -// TU_ASSERT(0,); -// break; -// } -// dcd_event_bus_reset(0, actual_speed, true); + // tusb_speed_t actual_speed; + // switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){ + // case USBHS_SPEED_TYPE_HIGH: + // actual_speed = TUSB_SPEED_HIGH; + // break; + // case USBHS_SPEED_TYPE_FULL: + // actual_speed = TUSB_SPEED_FULL; + // break; + // case USBHS_SPEED_TYPE_LOW: + // actual_speed = TUSB_SPEED_LOW; + // break; + // default: + // TU_ASSERT(0,); + // break; + // } + // dcd_event_bus_reset(0, actual_speed, true); dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true); USBHSD->DEV_AD = 0; - EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0; - EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + memset(ep_data_tog, 0, sizeof(ep_data_tog)); + ep0_tog = true; + EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0; + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; USBHSD->INT_FG = USBHS_BUS_RST_FLAG; /* Clear flag */ } else if (int_flag & USBHS_SUSPEND_FLAG) { @@ -434,6 +467,9 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_handler(&event, true); USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */ + } else { + // Unhandled interrupt + USBHSD->INT_FG = int_flag; /* Clear all flags */ } } #endif diff --git a/src/tinyusb.mk b/src/tinyusb.mk index 169098016..365043927 100644 --- a/src/tinyusb.mk +++ b/src/tinyusb.mk @@ -3,7 +3,6 @@ TINYUSB_SRC_C += \ src/tusb.c \ src/common/tusb_fifo.c \ src/device/usbd.c \ - src/device/usbd_control.c \ src/typec/usbc.c \ src/class/audio/audio_device.c \ src/class/cdc/cdc_device.c \ @@ -11,6 +10,7 @@ TINYUSB_SRC_C += \ src/class/dfu/dfu_rt_device.c \ src/class/hid/hid_device.c \ src/class/midi/midi_device.c \ + src/class/midi/midi2_device.c \ src/class/msc/msc_device.c \ src/class/mtp/mtp_device.c \ src/class/net/ecm_rndis_device.c \ @@ -24,5 +24,6 @@ TINYUSB_SRC_C += \ src/class/cdc/cdc_host.c \ src/class/hid/hid_host.c \ src/class/midi/midi_host.c \ + src/class/midi/midi2_host.c \ src/class/msc/msc_host.c \ src/class/vendor/vendor_host.c \ diff --git a/src/tusb.c b/src/tusb.c index 5e4422e41..634cbc10b 100644 --- a/src/tusb.c +++ b/src/tusb.c @@ -224,32 +224,31 @@ uint8_t const* tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t by // Endpoint Helper for both Host and Device stack //--------------------------------------------------------------------+ -bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { +bool tu_edpt_claim(volatile uint8_t* ep_state, osal_mutex_t mutex) { (void) mutex; // pre-check to help reducing mutex lock - TU_VERIFY(ep_state->busy == 0); - TU_VERIFY(ep_state->claimed == 0); + TU_VERIFY((*ep_state & (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED)) == 0); (void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); // can only claim the endpoint if it is not busy and not claimed yet. - bool const available = (ep_state->busy == 0) && (ep_state->claimed == 0); + bool const available = (*ep_state & (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED)) == 0; if (available) { - ep_state->claimed = 1; + *ep_state |= TU_EDPT_STATE_CLAIMED; } (void) osal_mutex_unlock(mutex); return available; } -bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { +bool tu_edpt_release(volatile uint8_t* ep_state, osal_mutex_t mutex) { (void) mutex; (void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); // can only release the endpoint if it is claimed and not busy - bool const ret = (ep_state->claimed == 1) && (ep_state->busy == 0); + bool const ret = (*ep_state & (TU_EDPT_STATE_CLAIMED | TU_EDPT_STATE_BUSY)) == TU_EDPT_STATE_CLAIMED; if (ret) { - ep_state->claimed = 0; + *ep_state &= (uint8_t) ~TU_EDPT_STATE_CLAIMED; } (void) osal_mutex_unlock(mutex); @@ -498,7 +497,7 @@ char const* const tu_str_std_request[] = { }; char const* const tu_str_xfer_result[] = { - "OK", "FAILED", "STALLED", "TIMEOUT" + "OK", "FAILED", "STALLED", "TIMEOUT", "ABORTED", "INVALID" }; #endif diff --git a/src/tusb.h b/src/tusb.h index c80c8433c..aa6b461e5 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -63,6 +63,10 @@ #include "class/midi/midi_host.h" #endif + #if CFG_TUH_MIDI2 + #include "class/midi/midi2_host.h" + #endif + #if CFG_TUH_VENDOR #include "class/vendor/vendor_host.h" #endif @@ -108,6 +112,10 @@ #include "class/midi/midi_device.h" #endif + #if CFG_TUD_MIDI2 + #include "class/midi/midi2_device.h" + #endif + #if CFG_TUD_VENDOR #include "class/vendor/vendor_device.h" #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index 154f8e2a4..dcf0646cf 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -535,6 +535,18 @@ #define CFG_TUSB_OS OPT_OS_NONE #endif +// 1 when CFG_TUSB_OS provides a preemptive scheduler with distinct tasks +// (FreeRTOS, Zephyr, ThreadX, etc.); 0 when the application is single-context +// (bare-metal OS_NONE or Pico SDK). Sync host control xfers from the host +// task are forbidden when this is 1. +#ifndef CFG_TUSB_OS_HAS_SCHEDULER + #if CFG_TUSB_OS == OPT_OS_NONE || CFG_TUSB_OS == OPT_OS_PICO + #define CFG_TUSB_OS_HAS_SCHEDULER 0 + #else + #define CFG_TUSB_OS_HAS_SCHEDULER 1 + #endif +#endif + #ifndef CFG_TUSB_OS_INC_PATH #ifndef CFG_TUSB_OS_INC_PATH_DEFAULT #define CFG_TUSB_OS_INC_PATH_DEFAULT @@ -646,6 +658,10 @@ #define CFG_TUD_MIDI 0 #endif +#ifndef CFG_TUD_MIDI2 + #define CFG_TUD_MIDI2 0 +#endif + #ifndef CFG_TUD_VENDOR #define CFG_TUD_VENDOR 0 #endif @@ -815,6 +831,22 @@ #define CFG_TUH_MIDI 0 #endif +#ifndef CFG_TUH_MIDI2 + #define CFG_TUH_MIDI2 0 +#endif + +#ifndef CFG_TUH_MIDI2_RX_BUFSIZE + #define CFG_TUH_MIDI2_RX_BUFSIZE TUH_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUH_MIDI2_TX_BUFSIZE + #define CFG_TUH_MIDI2_TX_BUFSIZE TUH_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUH_MIDI2_LOG_LEVEL + #define CFG_TUH_MIDI2_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + #ifndef CFG_TUH_MSC #define CFG_TUH_MSC 0 #endif |
