From 77cef83304c92402a133fc936c44d182fc99ecf0 Mon Sep 17 00:00:00 2001 From: Saulo VerĂ­ssimo Date: Tue, 24 Mar 2026 23:38:14 -0300 Subject: test: add MIDI 2.0 Device and Host unit tests Add unit tests for MIDI 2.0 drivers: - Device: UMP word count (all 16 message types), descriptor macro validation (length, byte layout, alt settings, endpoints), CS endpoint subtypes, traversal integrity - Host: UMP word count, callback struct validation, CS endpoint subtypes Also add Sphinx documentation for MIDI 2.0 class drivers (Device and Host API reference, lifecycle, configuration, examples). Tests: 60/60 PASS (FIFO 26/26, USBD 5/5, MIDI2 Device 18/18, MIDI2 Host 6/6, USBD internal 5/5) --- .../test/device/midi2/test_midi2_device.c | 263 +++++++++++++++++++++ test/unit-test/test/host/midi2/test_midi2_host.c | 101 ++++++++ 2 files changed, 364 insertions(+) create mode 100644 test/unit-test/test/device/midi2/test_midi2_device.c create mode 100644 test/unit-test/test/host/midi2/test_midi2_host.c (limited to 'test') diff --git a/test/unit-test/test/device/midi2/test_midi2_device.c b/test/unit-test/test/device/midi2/test_midi2_device.c new file mode 100644 index 000000000..9d716d93b --- /dev/null +++ b/test/unit-test/test/device/midi2/test_midi2_device.c @@ -0,0 +1,263 @@ +/* + * 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. + */ + +#include "unity.h" +#include "tusb_types.h" +#include "class/audio/audio.h" +#include "class/midi/midi.h" +#include "device/usbd.h" + +void setUp(void) {} +void tearDown(void) {} + +//--------------------------------------------------------------------+ +// UMP Word Count: all 16 message types +//--------------------------------------------------------------------+ + +void test_ump_word_count_1word_types(void) { + uint8_t types[] = {0x0, 0x1, 0x2, 0x6, 0x7}; + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(1, midi2_ump_word_count(types[i])); + } +} + +void test_ump_word_count_2word_types(void) { + uint8_t types[] = {0x3, 0x4, 0x8, 0x9, 0xA}; + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(2, midi2_ump_word_count(types[i])); + } +} + +void test_ump_word_count_3word_types(void) { + TEST_ASSERT_EQUAL(3, midi2_ump_word_count(0xB)); + TEST_ASSERT_EQUAL(3, midi2_ump_word_count(0xC)); +} + +void test_ump_word_count_4word_types(void) { + uint8_t types[] = {0x5, 0xD, 0xE, 0xF}; + for (int i = 0; i < 4; i++) { + TEST_ASSERT_EQUAL(4, midi2_ump_word_count(types[i])); + } +} + +void test_ump_word_count_covers_all_16(void) { + for (uint8_t mt = 0; mt <= 0xF; mt++) { + uint8_t wc = midi2_ump_word_count(mt); + TEST_ASSERT_TRUE(wc >= 1 && wc <= 4); + } +} + +//--------------------------------------------------------------------+ +// CS Endpoint subtypes (defined in midi.h) +//--------------------------------------------------------------------+ + +void test_cs_endpoint_subtypes(void) { + TEST_ASSERT_EQUAL(0x01, MIDI_CS_ENDPOINT_GENERAL); + TEST_ASSERT_EQUAL(0x02, MIDI_CS_ENDPOINT_GENERAL_2_0); +} + +//--------------------------------------------------------------------+ +// Descriptor macro length calculations +//--------------------------------------------------------------------+ + +void test_midi1_desc_len(void) { + TEST_ASSERT_EQUAL(TUD_MIDI_DESC_HEAD_LEN + TUD_MIDI_DESC_JACK_LEN + TUD_MIDI_DESC_EP_LEN(1) * 2, + TUD_MIDI_DESC_LEN); +} + +void test_midi2_alt1_head_len(void) { + TEST_ASSERT_EQUAL(16, TUD_MIDI2_DESC_ALT1_HEAD_LEN); +} + +void test_midi2_alt1_ep_len(void) { + // EP(7) + CS base(4) + numgtbs + TEST_ASSERT_EQUAL(12, TUD_MIDI2_DESC_ALT1_EP_LEN(1)); + TEST_ASSERT_EQUAL(13, TUD_MIDI2_DESC_ALT1_EP_LEN(2)); + TEST_ASSERT_EQUAL(18, TUD_MIDI2_DESC_ALT1_EP_LEN(7)); +} + +void test_midi2_desc_len(void) { + int expected = TUD_MIDI_DESC_LEN + TUD_MIDI2_DESC_ALT1_HEAD_LEN + TUD_MIDI2_DESC_ALT1_EP_LEN(1) * 2; + TEST_ASSERT_EQUAL(expected, TUD_MIDI2_DESC_LEN); +} + +void test_midi2_desc_len_greater_than_midi1(void) { + TEST_ASSERT_TRUE(TUD_MIDI2_DESC_LEN > TUD_MIDI_DESC_LEN); +} + +//--------------------------------------------------------------------+ +// Descriptor macro byte validation +//--------------------------------------------------------------------+ + +void test_midi2_descriptor_bytes(void) { + uint8_t desc[] = { TUD_MIDI2_DESCRIPTOR(0, 0, 0x01, 0x81, 64) }; + + TEST_ASSERT_EQUAL(TUD_MIDI2_DESC_LEN, sizeof(desc)); + + // First byte: Audio Control Interface descriptor length = 9 + TEST_ASSERT_EQUAL(9, desc[0]); + TEST_ASSERT_EQUAL(TUSB_DESC_INTERFACE, desc[1]); + TEST_ASSERT_EQUAL(0, desc[2]); + + // Find Alt Setting 1 by scanning + int alt1_offset = -1; + int pos = 0; + while (pos < (int)sizeof(desc)) { + if (desc[pos + 1] == TUSB_DESC_INTERFACE && desc[pos + 3] == 1) { + alt1_offset = pos; + break; + } + pos += desc[pos]; + } + + TEST_ASSERT_TRUE_MESSAGE(alt1_offset >= 0, "Alt Setting 1 interface not found"); + + TEST_ASSERT_EQUAL(9, desc[alt1_offset]); + TEST_ASSERT_EQUAL(TUSB_DESC_INTERFACE, desc[alt1_offset + 1]); + TEST_ASSERT_EQUAL(1, desc[alt1_offset + 2]); // bInterfaceNumber + TEST_ASSERT_EQUAL(1, desc[alt1_offset + 3]); // bAlternateSetting + TEST_ASSERT_EQUAL(2, desc[alt1_offset + 4]); // bNumEndpoints + TEST_ASSERT_EQUAL(TUSB_CLASS_AUDIO, desc[alt1_offset + 5]); + + // MS Header after Alt Setting 1 interface: bcdMSC = 0x0200 + int ms2_offset = alt1_offset + 9; + TEST_ASSERT_EQUAL(7, desc[ms2_offset]); + TEST_ASSERT_EQUAL(TUSB_DESC_CS_INTERFACE, desc[ms2_offset + 1]); + TEST_ASSERT_EQUAL(MIDI_CS_INTERFACE_HEADER, desc[ms2_offset + 2]); + TEST_ASSERT_EQUAL(0x00, desc[ms2_offset + 3]); + TEST_ASSERT_EQUAL(0x02, desc[ms2_offset + 4]); +} + +void test_midi2_descriptor_alt1_cs_endpoint_subtype(void) { + uint8_t desc[] = { TUD_MIDI2_DESCRIPTOR(0, 0, 0x01, 0x81, 64) }; + + int cs_ep_count = 0; + int pos = 0; + while (pos < (int)sizeof(desc)) { + if (desc[pos + 1] == TUSB_DESC_CS_ENDPOINT && + desc[pos + 2] == MIDI_CS_ENDPOINT_GENERAL_2_0) { + cs_ep_count++; + TEST_ASSERT_EQUAL(1, desc[pos + 3]); + } + pos += desc[pos]; + } + TEST_ASSERT_EQUAL(2, cs_ep_count); +} + +void test_midi2_descriptor_has_both_alt_settings(void) { + uint8_t desc[] = { TUD_MIDI2_DESCRIPTOR(0, 0, 0x01, 0x81, 64) }; + + int alt0_count = 0; + int alt1_count = 0; + int pos = 0; + while (pos < (int)sizeof(desc)) { + if (desc[pos + 1] == TUSB_DESC_INTERFACE) { + if (desc[pos + 3] == 0) alt0_count++; + if (desc[pos + 3] == 1) alt1_count++; + } + pos += desc[pos]; + } + TEST_ASSERT_TRUE(alt0_count >= 2); + TEST_ASSERT_EQUAL(1, alt1_count); +} + +void test_midi2_descriptor_endpoint_addresses(void) { + uint8_t desc[] = { TUD_MIDI2_DESCRIPTOR(0, 0, 0x02, 0x82, 64) }; + + int ep_out_count = 0; + int ep_in_count = 0; + int pos = 0; + while (pos < (int)sizeof(desc)) { + if (desc[pos + 1] == TUSB_DESC_ENDPOINT) { + uint8_t ep_addr = desc[pos + 2]; + if (ep_addr == 0x02) ep_out_count++; + if (ep_addr == 0x82) ep_in_count++; + TEST_ASSERT_EQUAL(TUSB_XFER_BULK, desc[pos + 3]); + TEST_ASSERT_EQUAL(64, desc[pos + 4]); + TEST_ASSERT_EQUAL(0, desc[pos + 5]); + } + pos += desc[pos]; + } + TEST_ASSERT_EQUAL(2, ep_out_count); + TEST_ASSERT_EQUAL(2, ep_in_count); +} + +void test_midi2_descriptor_nonzero_itfnum(void) { + uint8_t desc[] = { TUD_MIDI2_DESCRIPTOR(2, 0, 0x03, 0x83, 64) }; + + TEST_ASSERT_EQUAL(2, desc[2]); + + int pos = desc[0]; + while (pos < (int)sizeof(desc)) { + if (desc[pos + 1] == TUSB_DESC_INTERFACE) { + TEST_ASSERT_EQUAL(3, desc[pos + 2]); + break; + } + pos += desc[pos]; + } +} + +//--------------------------------------------------------------------+ +// Descriptor traversal integrity +//--------------------------------------------------------------------+ + +void test_midi2_descriptor_no_zero_length(void) { + uint8_t desc[] = { TUD_MIDI2_DESCRIPTOR(0, 0, 0x01, 0x81, 64) }; + + int pos = 0; + int desc_count = 0; + while (pos < (int)sizeof(desc)) { + TEST_ASSERT_TRUE_MESSAGE(desc[pos] > 0, "Zero-length descriptor found"); + TEST_ASSERT_TRUE_MESSAGE(desc[pos] <= (int)sizeof(desc) - pos, + "Descriptor length exceeds remaining bytes"); + pos += desc[pos]; + desc_count++; + } + TEST_ASSERT_EQUAL((int)sizeof(desc), pos); + TEST_ASSERT_TRUE(desc_count > 5); +} + +void test_midi2_descriptor_valid_types(void) { + uint8_t desc[] = { TUD_MIDI2_DESCRIPTOR(0, 0, 0x01, 0x81, 64) }; + + int pos = 0; + while (pos < (int)sizeof(desc)) { + uint8_t dtype = desc[pos + 1]; + bool valid = (dtype == TUSB_DESC_INTERFACE || + dtype == TUSB_DESC_ENDPOINT || + dtype == TUSB_DESC_CS_INTERFACE || + dtype == TUSB_DESC_CS_ENDPOINT); + TEST_ASSERT_TRUE_MESSAGE(valid, "Invalid descriptor type found"); + pos += desc[pos]; + } +} + +//--------------------------------------------------------------------+ +// Edge cases +//--------------------------------------------------------------------+ + +void test_ump_word_count_with_values_beyond_0xf(void) { + TEST_ASSERT_EQUAL(4, midi2_ump_word_count(0x10)); + TEST_ASSERT_EQUAL(4, midi2_ump_word_count(0xFF)); +} diff --git a/test/unit-test/test/host/midi2/test_midi2_host.c b/test/unit-test/test/host/midi2/test_midi2_host.c new file mode 100644 index 000000000..8ad77c14e --- /dev/null +++ b/test/unit-test/test/host/midi2/test_midi2_host.c @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#include "unity.h" +#include "tusb_option.h" +#include "class/midi/midi.h" +#include "class/midi/midi2_host.h" + +void setUp(void) {} +void tearDown(void) {} + +//--------------------------------------------------------------------+ +// UMP Word Count (shared helper, defined in midi.h) +//--------------------------------------------------------------------+ + +void test_midi2_host_ump_word_count_1word(void) { + uint8_t types[] = {0x0, 0x1, 0x2, 0x6, 0x7}; + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(1, midi2_ump_word_count(types[i])); + } +} + +void test_midi2_host_ump_word_count_2word(void) { + uint8_t types[] = {0x3, 0x4, 0x8, 0x9, 0xA}; + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(2, midi2_ump_word_count(types[i])); + } +} + +void test_midi2_host_ump_word_count_4word(void) { + uint8_t types[] = {0x5, 0xD, 0xE, 0xF}; + for (int i = 0; i < 4; i++) { + TEST_ASSERT_EQUAL(4, midi2_ump_word_count(types[i])); + } +} + +//--------------------------------------------------------------------+ +// Callback struct field validation +//--------------------------------------------------------------------+ + +void test_midi2_descriptor_cb_struct_fields(void) { + tuh_midi2_descriptor_cb_t desc = { + .protocol_version = 1, + .bcdMSC_hi = 0x02, + .bcdMSC_lo = 0x00, + .rx_cable_count = 1, + .tx_cable_count = 1 + }; + TEST_ASSERT_EQUAL(1, desc.protocol_version); + TEST_ASSERT_EQUAL(0x02, desc.bcdMSC_hi); + TEST_ASSERT_EQUAL(0x00, desc.bcdMSC_lo); + TEST_ASSERT_EQUAL(1, desc.rx_cable_count); + TEST_ASSERT_EQUAL(1, desc.tx_cable_count); +} + +void test_midi2_mount_cb_struct_fields(void) { + tuh_midi2_mount_cb_t mount = { + .daddr = 1, + .bInterfaceNumber = 0, + .protocol_version = 1, + .alt_setting_active = 1, + .rx_cable_count = 2, + .tx_cable_count = 2 + }; + TEST_ASSERT_EQUAL(1, mount.daddr); + TEST_ASSERT_EQUAL(0, mount.bInterfaceNumber); + TEST_ASSERT_EQUAL(1, mount.protocol_version); + TEST_ASSERT_EQUAL(1, mount.alt_setting_active); + TEST_ASSERT_EQUAL(2, mount.rx_cable_count); + TEST_ASSERT_EQUAL(2, mount.tx_cable_count); +} + +//--------------------------------------------------------------------+ +// CS Endpoint subtypes +//--------------------------------------------------------------------+ + +void test_midi2_host_cs_endpoint_subtypes(void) { + TEST_ASSERT_EQUAL(0x01, MIDI_CS_ENDPOINT_GENERAL); + TEST_ASSERT_EQUAL(0x02, MIDI_CS_ENDPOINT_GENERAL_2_0); +} -- cgit v1.3.1 From 97852816e873bf7f91f3a81f093ad08f96179656 Mon Sep 17 00:00:00 2001 From: Saulo VerĂ­ssimo Date: Wed, 22 Apr 2026 18:04:56 -0300 Subject: midi2: align descriptors with USB-MIDI 2.0 spec Brings the MIDI 2.0 device driver into full conformance with USB Device Class Definition for MIDI Devices v2.0 (USB-IF, May 2020). - Alt 1 MS Interface Header wTotalLength now reports 0x0007 per Table 5-2 ("set to match bLength"), replacing the prior 0x0011 carried over from USB-MIDI 1.0 conventions. - GET_DESCRIPTOR class request now validates bmRequestType direction, type and recipient plus wIndex and wValue high byte per Section 6. - iBlockItem in the default Group Terminal Block is driven by CFG_TUD_MIDI2_BLOCK_STRIDX so applications can attach a UI string descriptor to the block per Table 5-6. - UMP word byte order assumption (little-endian host per Section 3.2.2) is documented inline so future big-endian ports know where to wrap access with tu_htole32 / tu_le32toh. Validated on RP2040 and ESP32-P4 under Linux kernel 6.17: lsusb -v reports wTotalLength = 0x0007 on Alt 1 MS Header (raw bytes 07 24 01 00 02 07 00). amidi -l enumerates Group Terminals exposed via the class-specific GET_DESCRIPTOR response. --- src/class/midi/midi2_device.c | 47 ++++++++++++++++------ src/device/usbd.h | 12 +++--- src/tusb_option.h | 6 +++ .../test/device/midi2/test_midi2_device.c | 3 ++ 4 files changed, 49 insertions(+), 19 deletions(-) (limited to 'test') diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c index 34e467fc6..15daad096 100644 --- a/src/class/midi/midi2_device.c +++ b/src/class/midi/midi2_device.c @@ -43,6 +43,16 @@ TU_ATTR_WEAK bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_re (void) rhport; (void) request; return false; } +//--------------------------------------------------------------------+ +// 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 //--------------------------------------------------------------------+ @@ -133,7 +143,7 @@ static const uint8_t _default_gtb_desc[] = { 0x00, // bGrpTrmBlkType: bidirectional 0x00, // nGroupTrm: first group (0) CFG_TUD_MIDI2_NUM_GROUPS, // nNumGroupTrm - 0, // iBlockItem: no string + CFG_TUD_MIDI2_BLOCK_STRIDX, // iBlockItem: string descriptor index (0 = none) 0x00, // bMIDIProtocol: unknown/not fixed 0, 0, // wMaxInputBandwidth: unknown 0, 0 // wMaxOutputBandwidth: unknown @@ -554,19 +564,30 @@ bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_re } case TUSB_REQ_GET_DESCRIPTOR: { - // wValue: descriptor type (high) | index (low) - // 0x26 = CS_GRP_TRM_BLOCK, index 0x01 - if (request->wValue == ((uint16_t)MIDI2_CS_GRP_TRM_BLOCK << 8 | 0x01)) { - 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; + // 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); } - return false; + tud_control_xfer(rhport, request, (void*)(uintptr_t) _default_gtb_desc, len); + return true; } default: diff --git a/src/device/usbd.h b/src/device/usbd.h index a9f4c5f08..abce5a887 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -429,14 +429,14 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ //--------------------------------------------------------------------+ // Alt Setting 1: MS Interface + MS Header (bcdMSC=0x0200) -// wTotalLength covers MS Header + all CS Endpoint descriptors -#define TUD_MIDI2_DESC_ALT1_CS_LEN(_numgtbs) (7 + (4 + (_numgtbs)) * 2) +// 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, _numgtbs) \ +#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 = header + 2x CS Endpoint */\ - 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0200), U16_TO_U8S_LE(TUD_MIDI2_DESC_ALT1_CS_LEN(_numgtbs)) + /* 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)) @@ -457,7 +457,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ TUD_MIDI_DESC_EP(_epin, _epsize, 1),\ TUD_MIDI_JACKID_OUT_EMB(1),\ /* Alt Setting 1 (UMP) */\ - TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx, 1),\ + 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 */) diff --git a/src/tusb_option.h b/src/tusb_option.h index 4483c2200..2614110fc 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -682,6 +682,12 @@ #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 + #ifndef CFG_TUD_VENDOR #define CFG_TUD_VENDOR 0 #endif diff --git a/test/unit-test/test/device/midi2/test_midi2_device.c b/test/unit-test/test/device/midi2/test_midi2_device.c index 9d716d93b..1314c2585 100644 --- a/test/unit-test/test/device/midi2/test_midi2_device.c +++ b/test/unit-test/test/device/midi2/test_midi2_device.c @@ -147,6 +147,9 @@ void test_midi2_descriptor_bytes(void) { TEST_ASSERT_EQUAL(MIDI_CS_INTERFACE_HEADER, desc[ms2_offset + 2]); TEST_ASSERT_EQUAL(0x00, desc[ms2_offset + 3]); TEST_ASSERT_EQUAL(0x02, desc[ms2_offset + 4]); + // USB-MIDI 2.0 Table 5-2: wTotalLength shall match bLength (= 0x0007) + TEST_ASSERT_EQUAL(0x07, desc[ms2_offset + 5]); + TEST_ASSERT_EQUAL(0x00, desc[ms2_offset + 6]); } void test_midi2_descriptor_alt1_cs_endpoint_subtype(void) { -- cgit v1.3.1