summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHiFiPhile <[email protected]>2026-05-18 23:07:49 +0200
committerHiFiPhile <[email protected]>2026-05-18 23:07:49 +0200
commit4baf1883c194c2c4b6fa30cc0c443ad6c83e0b19 (patch)
treece5bb9d756807f9d255ab970794ad21f201c7e59 /src
parentaaca323bd34945178e88de51acf67c4d0443a726 (diff)
midi2: convert to raw Tx FIFO for better segmentation handling, add count to packet api
Signed-off-by: HiFiPhile <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/class/midi/midi2_device.c276
-rw-r--r--src/class/midi/midi2_device.h14
2 files changed, 174 insertions, 116 deletions
diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c
index c61884689..b14f439f8 100644
--- a/src/class/midi/midi2_device.c
+++ b/src/class/midi/midi2_device.c
@@ -101,6 +101,16 @@ enum {
// 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;
@@ -109,7 +119,7 @@ typedef struct {
/*------------- From this point, data is not cleared by bus reset -------------*/
struct {
- tu_edpt_stream_t tx;
+ midi2d_tx_t tx;
tu_edpt_stream_t rx;
uint8_t rx_ff_buf[CFG_TUD_MIDI2_RX_BUFSIZE];
@@ -117,19 +127,6 @@ typedef struct {
} ep_stream;
} midi2d_interface_t;
-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];
-
-static inline uint8_t _itf_idx(const midi2d_interface_t* p_midi) {
- return (uint8_t)(p_midi - _midi2d_itf);
-}
-
// Skip local EP buffer if dedicated hw FIFO is supported
#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
typedef struct {
@@ -140,6 +137,15 @@ typedef struct {
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)
@@ -163,14 +169,111 @@ static const uint8_t _default_gtb_desc[] = {
};
//--------------------------------------------------------------------+
+// 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) {
- tu_edpt_stream_t* ep_tx = &p_midi->ep_stream.tx;
- if (!tu_edpt_stream_is_opened(ep_tx)) return;
- if (tu_edpt_stream_write_available(ep_tx) < count * 4) return;
- tu_edpt_stream_write(ep_tx, words, count * 4);
- tu_edpt_stream_write_xfer(ep_tx);
+ 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) {
@@ -307,7 +410,7 @@ static void _nego_process_rx(midi2d_interface_t* p_midi) {
bool tud_midi2_n_mounted(uint8_t itf) {
TU_VERIFY(itf < CFG_TUD_MIDI2, false);
midi2d_interface_t* p_midi = &_midi2d_itf[itf];
- return tu_edpt_stream_is_opened(&p_midi->ep_stream.tx) &&
+ return _tx_opened(p_midi) &&
tu_edpt_stream_is_opened(&p_midi->ep_stream.rx);
}
@@ -346,10 +449,10 @@ uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words)
return total_read;
}
-bool tud_midi2_n_packet_read(uint8_t itf, uint8_t packet[4]) {
- TU_VERIFY(itf < CFG_TUD_MIDI2, false);
+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 4 == tu_edpt_stream_read(&p_midi->ep_stream.rx, packet, 4);
+ return tu_edpt_stream_read(&p_midi->ep_stream.rx, packets, max_packets * 4u) >> 2u;
}
//--------------------------------------------------------------------+
@@ -362,44 +465,31 @@ uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t coun
// 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);
- tu_edpt_stream_t* ep_tx = &p_midi->ep_stream.tx;
- TU_VERIFY(tu_edpt_stream_is_opened(ep_tx), 0);
+ return _tx_ump_write(p_midi, words, 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);
- uint32_t pkt_bytes = (uint32_t)pkt_words * 4;
+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;
- if (written + pkt_words > count) break;
- if (tu_edpt_stream_write_available(ep_tx) < pkt_bytes) break;
+ // 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);
- // Flush whole packets already queued before adding one that would cross
- // the wMaxPacketSize boundary. Prevents an UMP message from being split
- // across two USB transfers, which would corrupt the host RX context.
- uint16_t ff_count = tu_fifo_count(&ep_tx->ff);
- if (ff_count > 0 && ff_count + pkt_bytes > ep_tx->mps) {
- tu_edpt_stream_write_xfer(ep_tx);
- }
+ uint32_t written = 0;
+ while (written < count) {
+ if (tu_fifo_remaining(&tx->ff) < 4) break;
- tu_edpt_stream_write(ep_tx, &words[written], pkt_bytes);
- written += pkt_words;
+ if (tu_fifo_write_n(&tx->ff, packets + written * 4u, 4) != 4) break;
+ written++;
}
- (void) tu_edpt_stream_write_xfer(ep_tx);
- return written;
-}
+ (void) _tx_start_xfer(p_midi);
-bool tud_midi2_n_packet_write(uint8_t itf, const uint8_t packet[4]) {
- TU_VERIFY(itf < CFG_TUD_MIDI2, false);
- midi2d_interface_t* p_midi = &_midi2d_itf[itf];
- tu_edpt_stream_t* ep_tx = &p_midi->ep_stream.tx;
- TU_VERIFY(tu_edpt_stream_is_opened(ep_tx), false);
- TU_VERIFY(tu_edpt_stream_write_available(ep_tx) >= 4, false);
- TU_VERIFY(tu_edpt_stream_write(ep_tx, packet, 4) > 0, false);
- (void) tu_edpt_stream_write_xfer(ep_tx);
- return true;
+ return written;
}
//--------------------------------------------------------------------+
@@ -439,8 +529,14 @@ void midi2d_init(void) {
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);
- tu_edpt_stream_init(&p_midi->ep_stream.tx, false, true, false,
- p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI2_TX_BUFSIZE, epin_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
}
}
@@ -448,7 +544,6 @@ 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);
- tu_edpt_stream_deinit(&p_midi->ep_stream.tx);
}
return true;
}
@@ -462,8 +557,8 @@ void midi2d_reset(uint8_t rhport) {
tu_edpt_stream_clear(&p_midi->ep_stream.rx);
tu_edpt_stream_close(&p_midi->ep_stream.rx);
- tu_edpt_stream_clear(&p_midi->ep_stream.tx);
- tu_edpt_stream_close(&p_midi->ep_stream.tx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ p_midi->ep_stream.tx.ep_addr = 0;
}
}
@@ -533,8 +628,9 @@ uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint
const uint8_t ep_addr = desc_ep->bEndpointAddress;
if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
- tu_edpt_stream_open(&p_midi->ep_stream.tx, rhport, desc_ep, CFG_TUD_MIDI2_TX_EPSIZE);
- tu_edpt_stream_clear(&p_midi->ep_stream.tx);
+ 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);
@@ -588,7 +684,7 @@ bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_re
p_midi->alt_setting = alt;
tu_edpt_stream_clear(&p_midi->ep_stream.rx);
- tu_edpt_stream_clear(&p_midi->ep_stream.tx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
if (alt == 1) {
p_midi->negotiated = false;
@@ -635,53 +731,6 @@ bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_re
}
}
-// Drain whole UMP packets from the TX FIFO into the EP buffer, capped at
-// wMaxPacketSize. Needed when CFG_TUD_MIDI2_TX_BUFSIZE > mps: the FIFO can
-// then hold more bytes than fit in a single USB transfer, and a blind
-// tu_edpt_stream_write_xfer would split a UMP across two transfers.
-static void midi2d_flush_tx_boundary_aware(midi2d_interface_t* p_midi, uint32_t last_xferred) {
- tu_edpt_stream_t* ep_tx = &p_midi->ep_stream.tx;
- const uint16_t mps = ep_tx->mps;
- const uint16_t ff_count = tu_fifo_count(&ep_tx->ff);
-
- if (ff_count == 0) {
- (void) tu_edpt_stream_write_zlp_if_needed(ep_tx, last_xferred);
- return;
- }
- if (ff_count <= mps) {
- // Whole FIFO fits in one transfer; the stream API drain is safe.
- (void) tu_edpt_stream_write_xfer(ep_tx);
- return;
- }
- if (ep_tx->ep_buf == NULL) {
- // HWFIFO mode: relies on CFG_TUD_MIDI2_TX_BUFSIZE <= mps for UMP integrity.
- (void) tu_edpt_stream_write_xfer(ep_tx);
- return;
- }
-
- // ff_count > mps and a local EP buffer is available: drain only whole UMP
- // packets up to mps to preserve packet boundaries on the USB wire.
- uint8_t word_bytes[4];
- uint8_t* buf = ep_tx->ep_buf;
- uint16_t bytes = 0;
- while (bytes < mps) {
- if (tu_fifo_count(&ep_tx->ff) < 4) break;
- if (4 != tu_fifo_peek_n(&ep_tx->ff, word_bytes, 4)) break;
- uint8_t mt = (uint8_t)((word_bytes[3] >> 4) & 0x0F);
- uint8_t pkt_words = midi2_ump_word_count(mt);
- uint16_t pkt_bytes = (uint16_t)(pkt_words * 4);
- if (tu_fifo_count(&ep_tx->ff) < pkt_bytes) break;
- if (bytes + pkt_bytes > mps) break;
- tu_fifo_read_n(&ep_tx->ff, buf + bytes, pkt_bytes);
- bytes = (uint16_t)(bytes + pkt_bytes);
- }
- if (bytes == 0) return;
- if (!usbd_edpt_claim(p_midi->rhport, ep_tx->ep_addr)) return;
- if (!usbd_edpt_xfer(p_midi->rhport, ep_tx->ep_addr, buf, bytes, false)) {
- usbd_edpt_release(p_midi->rhport, ep_tx->ep_addr);
- }
-}
-
bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
(void) rhport;
@@ -690,7 +739,7 @@ bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3
midi2d_interface_t* p_midi = &_midi2d_itf[idx];
tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
- tu_edpt_stream_t* ep_tx = &p_midi->ep_stream.tx;
+ midi2d_tx_t* ep_tx = &p_midi->ep_stream.tx;
if (ep_addr == ep_rx->ep_addr) {
if (result == XFER_RESULT_SUCCESS) {
@@ -702,7 +751,14 @@ bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3
}
tu_edpt_stream_read_xfer(ep_rx);
} else if (ep_addr == ep_tx->ep_addr && result == XFER_RESULT_SUCCESS) {
- midi2d_flush_tx_boundary_aware(p_midi, xferred_bytes);
+ 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;
}
diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h
index 6c29b8ddf..e53535693 100644
--- a/src/class/midi/midi2_device.h
+++ b/src/class/midi/midi2_device.h
@@ -130,8 +130,8 @@ uint8_t tud_midi2_n_protocol(uint8_t itf);
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);
-bool tud_midi2_n_packet_read(uint8_t itf, uint8_t packet[4]);
-bool tud_midi2_n_packet_write(uint8_t itf, const uint8_t packet[4]);
+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)
@@ -166,12 +166,14 @@ 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 bool tud_midi2_packet_read(uint8_t packet[4]) {
- return tud_midi2_n_packet_read(0, packet);
+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 bool tud_midi2_packet_write(const uint8_t packet[4]) {
- return tud_midi2_n_packet_write(0, packet);
+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);
}
//--------------------------------------------------------------------+