summaryrefslogtreecommitdiff
path: root/src/class
diff options
context:
space:
mode:
Diffstat (limited to 'src/class')
-rw-r--r--src/class/audio/audio.h65
-rw-r--r--src/class/audio/audio_device.c2
-rw-r--r--src/class/midi/midi2_device.c204
-rw-r--r--src/class/midi/midi2_device.h28
-rw-r--r--src/class/net/ncm_device.c47
-rw-r--r--src/class/usbtmc/usbtmc_device.c21
-rw-r--r--src/class/usbtmc/usbtmc_device.h1
-rw-r--r--src/class/vendor/vendor_host.c127
-rw-r--r--src/class/vendor/vendor_host.h48
-rw-r--r--src/class/video/video_device.c3
10 files changed, 339 insertions, 207 deletions
diff --git a/src/class/audio/audio.h b/src/class/audio/audio.h
index 7981396c2..82db9ed9e 100644
--- a/src/class/audio/audio.h
+++ b/src/class/audio/audio.h
@@ -83,6 +83,71 @@ typedef enum {
AUDIO_TERM_TYPE_OUT_LOW_FRQ_EFFECTS_SPEAKER = 0x0307,
} audio_terminal_output_type_t;
+/// 2.4 - Audio Class-Bi-directional Terminal Types UAC2
+typedef enum
+{
+ AUDIO_TERM_TYPE_BI_DIRECTIONAL_UNDEFINED = 0x0400,
+ AUDIO_TERM_TYPE_BI_DIRECTIONAL_HEADSET_HAND_HELD = 0x0401,
+ AUDIO_TERM_TYPE_BI_DIRECTIONAL_HEADSET_MOUNTED = 0x0402,
+ AUDIO_TERM_TYPE_BI_DIRECTIONAL_SPEAKERPHONE = 0x0403,
+ AUDIO_TERM_TYPE_BI_DIRECTIONAL_SPEAKERPHONE_ECHO_SUPPRESS = 0x0404,
+ AUDIO_TERM_TYPE_BI_DIRECTIONAL_SPEAKERPHONE_ECHO_CANCEL = 0x0405,
+} audio_terminal_bi_directional_type_t;
+
+/// 2.5 - Audio Class-Telephone Terminal Types UAC2
+typedef enum
+{
+ AUDIO_TERM_TYPE_TELEPHONE_UNDEFINED = 0x0500,
+ AUDIO_TERM_TYPE_TELEPHONE_PHONE_LINE = 0x0501,
+ AUDIO_TERM_TYPE_TELEPHONE_TELEPHONE = 0x0502,
+ AUDIO_TERM_TYPE_TELEPHONE_DOWN_LINE_PHONE = 0x0503,
+} audio_terminal_telephony_type_t;
+
+/// 2.6 - Audio Class-External Types UAC2
+typedef enum
+{
+ AUDIO_TERM_TYPE_EXTERNAL_UNDEFINED = 0x0600,
+ AUDIO_TERM_TYPE_EXTERNAL_ANALOG_CONNECTOR = 0x0601,
+ AUDIO_TERM_TYPE_EXTERNAL_DIGITAL_AUDIO = 0x0602,
+ AUDIO_TERM_TYPE_EXTERNAL_LINE_CONNECTOR = 0x0603,
+ AUDIO_TERM_TYPE_EXTERNAL_LEGACY_AUDIO_CONNECTOR = 0x0604,
+ AUDIO_TERM_TYPE_EXTERNAL_SPDIF_INTERFACE = 0x0605,
+ AUDIO_TERM_TYPE_EXTERNAL_1394_DA_STREAM = 0x0606,
+ AUDIO_TERM_TYPE_EXTERNAL_1394_DV_STREAM_SOUNDTRACK = 0x0607,
+ AUDIO_TERM_TYPE_EXTERNAL_ADAT_LIGHTPIPE = 0x0608,
+ AUDIO_TERM_TYPE_EXTERNAL_TDIF = 0x0609,
+ AUDIO_TERM_TYPE_EXTERNAL_MADI = 0x060A,
+} audio_terminal_external_type_t;
+
+/// 2.7 - Audio Class-Embedded Types UAC2
+typedef enum
+{
+ AUDIO_TERM_TYPE_EMBEDDED_UNDEFINED = 0x0700,
+ AUDIO_TERM_TYPE_EMBEDDED_LEVEL_CALIBRATION_NOISE_SOURCE = 0x0701,
+ AUDIO_TERM_TYPE_EMBEDDED_EQUALIZATION_NOISE = 0x0702,
+ AUDIO_TERM_TYPE_EMBEDDED_CD_PLAYER = 0x0703,
+ AUDIO_TERM_TYPE_EMBEDDED_DAT = 0x0704,
+ AUDIO_TERM_TYPE_EMBEDDED_DCC = 0x0705,
+ AUDIO_TERM_TYPE_EMBEDDED_COMPRESSED_AUDIO_PLAYER = 0x0706,
+ AUDIO_TERM_TYPE_EMBEDDED_ANALOG_TAPE = 0x0707,
+ AUDIO_TERM_TYPE_EMBEDDED_PHONOGRAPH = 0x0708,
+ AUDIO_TERM_TYPE_EMBEDDED_VCR_AUDIO = 0x0709,
+ AUDIO_TERM_TYPE_EMBEDDED_VIDEO_DISC_AUDIO = 0x070A,
+ AUDIO_TERM_TYPE_EMBEDDED_DVD_AUDIO = 0x070B,
+ AUDIO_TERM_TYPE_EMBEDDED_TV_TUNER_AUDIO = 0x070C,
+ AUDIO_TERM_TYPE_EMBEDDED_SATELLITE_RECEIVER_AUDIO = 0x070D,
+ AUDIO_TERM_TYPE_EMBEDDED_CABLE_TUNER_AUDIO = 0x070E,
+ AUDIO_TERM_TYPE_EMBEDDED_DSS_AUDIO = 0x070F,
+ AUDIO_TERM_TYPE_EMBEDDED_RADIO_RECEIVER = 0x0710,
+ AUDIO_TERM_TYPE_EMBEDDED_RADIO_TRANSMITTER = 0x0711,
+ AUDIO_TERM_TYPE_EMBEDDED_MULTI_TRACK_RECORDER = 0x0712,
+ AUDIO_TERM_TYPE_EMBEDDED_SYNTHESIZER = 0x0713,
+ AUDIO_TERM_TYPE_EMBEDDED_PIANO = 0x0714,
+ AUDIO_TERM_TYPE_EMBEDDED_GUITAR = 0x0715,
+ AUDIO_TERM_TYPE_EMBEDDED_DRUMS = 0x0716,
+ AUDIO_TERM_TYPE_EMBEDDED_OTHER_MUSICAL_INSTRUMENT = 0x0717,
+} audio_terminal_embedded_type_t;
+
/// Rest is yet to be implemented
//--------------------------------------------------------------------+
diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c
index 94881521a..bc4c7e544 100644
--- a/src/class/audio/audio_device.c
+++ b/src/class/audio/audio_device.c
@@ -1841,7 +1841,7 @@ static bool audiod_calc_tx_packet_sz(audiod_function_t *audio) {
static uint16_t audiod_tx_packet_size(const uint16_t *nominal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t fifo_threshold, uint16_t max_depth) {
// Flow control need a FIFO size of at least 4*Navg
- if (nominal_size[1] && nominal_size[1] <= fifo_depth * 4) {
+ if (nominal_size[1] && nominal_size[1] * 4 <= fifo_depth) {
// Use blackout to prioritize normal size packet
static int ctrl_blackout = 0;
uint16_t packet_size;
diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c
index 1d40a2efa..b0a9e2503 100644
--- a/src/class/midi/midi2_device.c
+++ b/src/class/midi/midi2_device.c
@@ -36,6 +36,9 @@ TU_ATTR_WEAK const char* tud_midi2_fb_name_cb(uint8_t itf, uint8_t fb_idx) {
TU_ATTR_WEAK tud_midi2_stream_result_t tud_midi2_stream_msg_cb(uint8_t itf, const uint32_t* ump_words) {
(void) itf; (void) ump_words; return MIDI2_STREAM_PASS;
}
+TU_ATTR_WEAK bool tud_midi2_device_identity_cb(uint8_t itf, tud_midi2_device_identity_t* identity) {
+ (void) itf; (void) identity; return false;
+}
//--------------------------------------------------------------------+
// Byte order note
@@ -59,6 +62,7 @@ enum {
enum {
STREAM_ENDPOINT_DISCOVERY = 0x000,
STREAM_ENDPOINT_INFO = 0x001,
+ STREAM_DEVICE_IDENTITY = 0x002,
STREAM_EP_NAME = 0x003,
STREAM_PROD_INSTANCE_ID = 0x004,
STREAM_CONFIG_REQUEST = 0x005,
@@ -103,6 +107,16 @@ typedef struct {
uint8_t protocol;
bool negotiated;
+ // Discovery reply bits waiting for TX FIFO room, drained on TX complete
+ uint8_t nego_pending_ep_filter;
+ uint8_t nego_pending_fb_filter;
+ uint8_t nego_pending_fb_num; // block requested by the pending discovery, 0xFF = all
+ uint8_t nego_pending_fb_next; // next block index to reply for
+ bool nego_pending_fb_restart; // restart after the active FB name when requests merge
+ uint16_t nego_text_status; // text reply owning nego_text_offset, 0 = none
+ uint16_t nego_text_offset; // progress into the text reply being sent
+ uint8_t nego_text_index; // Function Block index for an active FB name
+
/*------------- From this point, data is not cleared by bus reset -------------*/
struct {
midi2d_tx_t tx;
@@ -327,16 +341,20 @@ static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) {
// index byte (the Function Block number for FB Name) and 13 chars fit per
// packet; otherwise the text starts there and 14 chars fit (Endpoint Name,
// Product Instance Id).
-static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status,
- bool has_index, uint8_t index, const char* str) {
- if (!str || str[0] == '\0') return;
+// Sends a stream text from `offset` and returns how far it got. Resuming keeps
+// the End packet, which dropping the tail would lose.
+static uint16_t _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status,
+ bool has_index, uint8_t index, const char* str,
+ uint16_t offset) {
+ if (!str || str[0] == '\0') return 0;
- uint16_t total_len = (uint16_t) strlen(str);
- uint16_t offset = 0;
+ const uint16_t total_len = (uint16_t) strlen(str);
const uint8_t per_pkt = has_index ? 13 : 14;
const uint8_t head_chars = has_index ? 1 : 2; // chars carried in word0
+ if (offset >= total_len) return total_len;
while (offset < total_len) {
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < 16) break;
uint16_t remaining = total_len - offset;
uint8_t n = (uint8_t)((remaining > per_pkt) ? per_pkt : remaining);
bool is_first = (offset == 0);
@@ -370,6 +388,7 @@ static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status,
_nego_send_ump(p_midi, msg, 4);
offset += n;
}
+ return offset;
}
static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protocol) {
@@ -380,6 +399,33 @@ static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protoco
_nego_send_ump(p_midi, msg, 4);
}
+static void _nego_send_device_identity(midi2d_interface_t* p_midi) {
+ tud_midi2_device_identity_t id;
+ tu_memclr(&id, sizeof(id));
+ if (!tud_midi2_device_identity_cb(_itf_idx(p_midi), &id)) return;
+
+ // Every field is a run of bytes, each carrying 7 bits, laid out in the same
+ // order as the MIDI 1.0 Device Inquiry reply this message mirrors. A 1-byte
+ // manufacturer ID occupies the first of the three bytes, the other two stay
+ // zero, so the caller passes it as 0x7D0000 and not 0x00007D.
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_DEVICE_IDENTITY << 16);
+ msg[1] = id.manufacturer & UINT32_C(0x7F7F7F);
+ // Family and model are 14-bit numbers sent least significant byte first,
+ // as in the Device Inquiry reply. Manufacturer above is a byte sequence
+ // rather than a number, so it keeps its own order.
+ msg[2] = ((uint32_t) (id.family & 0x7F) << 24)
+ | ((uint32_t) ((id.family >> 7) & 0x7F) << 16)
+ | ((uint32_t) (id.model & 0x7F) << 8)
+ | ((uint32_t) ((id.model >> 7) & 0x7F));
+ msg[3] = ((uint32_t) ((id.sw_revision >> 24) & 0x7F) << 24)
+ | ((uint32_t) ((id.sw_revision >> 16) & 0x7F) << 16)
+ | ((uint32_t) ((id.sw_revision >> 8) & 0x7F) << 8)
+ | ((uint32_t) (id.sw_revision & 0x7F));
+ _nego_send_ump(p_midi, msg, 4);
+}
+
static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) {
// Derive direction and group span for this block from the GTB descriptor.
uint16_t gtb_len = 0;
@@ -395,10 +441,122 @@ static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) {
| ((uint32_t) fb_idx << 8)
| _fb_dir_byte(type); // UI hint + bDirection from the GTB block type
msg[1] = ((uint32_t) first_group << 24)
- | ((uint32_t) num_groups << 16);
+ | ((uint32_t) num_groups << 16)
+ | ((uint32_t) (CFG_TUD_MIDI2_FB_CI_VERSION & 0xFF) << 8)
+ | ((uint32_t) (CFG_TUD_MIDI2_FB_SYSEX8_STREAMS & 0xFF));
_nego_send_ump(p_midi, msg, 4);
}
+static void _nego_clear_pending(midi2d_interface_t* p_midi) {
+ p_midi->nego_pending_ep_filter = 0;
+ p_midi->nego_pending_fb_filter = 0;
+ p_midi->nego_pending_fb_num = 0;
+ p_midi->nego_pending_fb_next = 0;
+ p_midi->nego_pending_fb_restart = false;
+ p_midi->nego_text_status = 0;
+ p_midi->nego_text_offset = 0;
+ p_midi->nego_text_index = 0;
+}
+
+static const char* _nego_text_cb(midi2d_interface_t* p_midi, uint16_t status, uint8_t index) {
+ const uint8_t itf = _itf_idx(p_midi);
+ switch (status) {
+ case STREAM_EP_NAME: return tud_midi2_ep_name_cb(itf);
+ case STREAM_PROD_INSTANCE_ID: return tud_midi2_product_id_cb(itf);
+ case STREAM_FB_NAME: return tud_midi2_fb_name_cb(itf, index);
+ default: return NULL;
+ }
+}
+
+// Send or resume one text reply. While it is incomplete, its status and index
+// identify the sole owner of nego_text_offset so another discovery request
+// cannot resume a different string from the same offset.
+static bool _nego_send_text(midi2d_interface_t* p_midi, uint16_t status, uint8_t index) {
+ const char* text = _nego_text_cb(p_midi, status, index);
+ const uint16_t len = text ? (uint16_t) strlen(text) : 0;
+
+ p_midi->nego_text_status = status;
+ p_midi->nego_text_index = index;
+ p_midi->nego_text_offset = _nego_send_stream_text(p_midi, status, status == STREAM_FB_NAME,
+ index, text, p_midi->nego_text_offset);
+ if (p_midi->nego_text_offset < len) return false;
+
+ p_midi->nego_text_status = 0;
+ p_midi->nego_text_offset = 0;
+ p_midi->nego_text_index = 0;
+ return true;
+}
+
+// Send pending discovery replies, one whole reply at a time and only when the
+// TX FIFO can take it. A full-filter Endpoint Discovery asks for more bytes
+// than the default FIFO holds; replies that do not fit stay pending and are
+// retried from the TX complete path, paced by the transfer flow.
+static void _nego_send_pending(midi2d_interface_t* p_midi) {
+ tu_fifo_t* tx_ff = &p_midi->ep_stream.tx.ff;
+
+ // An incomplete text sequence must finish before any newly arrived request
+ // is serviced; otherwise its Continue/End packets could be attached to a
+ // different Endpoint or Function Block string.
+ if (p_midi->nego_text_status) {
+ const uint16_t status = p_midi->nego_text_status;
+ const uint8_t index = p_midi->nego_text_index;
+ if (!_nego_send_text(p_midi, status, index)) return;
+
+ if (status == STREAM_FB_NAME) {
+ if (p_midi->nego_pending_fb_restart) {
+ p_midi->nego_pending_fb_next = 0;
+ p_midi->nego_pending_fb_restart = false;
+ } else {
+ p_midi->nego_pending_fb_next++;
+ }
+ } else {
+ const uint8_t bit = (status == STREAM_EP_NAME) ? 0x04 : 0x08;
+ p_midi->nego_pending_ep_filter &= (uint8_t) ~bit;
+ }
+ }
+
+ while (p_midi->nego_pending_ep_filter) {
+ const uint8_t bit = (uint8_t)(p_midi->nego_pending_ep_filter & (uint8_t)(-p_midi->nego_pending_ep_filter));
+ uint16_t status = 0;
+ switch (bit) {
+ case 0x04: status = STREAM_EP_NAME; break;
+ case 0x08: status = STREAM_PROD_INSTANCE_ID; break;
+ default: break;
+ }
+
+ if (status != 0) {
+ if (!_nego_send_text(p_midi, status, 0)) return;
+ } else {
+ if (tu_fifo_remaining(tx_ff) < 16) return;
+ switch (bit) {
+ case 0x01: _nego_send_endpoint_info(p_midi); break;
+ case 0x02: _nego_send_device_identity(p_midi); break;
+ case 0x10: _nego_send_config_notify(p_midi, p_midi->protocol); break;
+ default: break;
+ }
+ }
+ p_midi->nego_pending_ep_filter &= (uint8_t) ~bit;
+ }
+
+ const uint8_t fb_count = _gtb_block_count(p_midi);
+ while (p_midi->nego_pending_fb_filter && p_midi->nego_pending_fb_next < fb_count) {
+ const uint8_t f = p_midi->nego_pending_fb_next;
+ if (p_midi->nego_pending_fb_num != 0xFF && p_midi->nego_pending_fb_num != f) {
+ p_midi->nego_pending_fb_next++;
+ continue;
+ }
+ if ((p_midi->nego_pending_fb_filter & 0x01) && p_midi->nego_text_offset == 0) {
+ if (tu_fifo_remaining(tx_ff) < 16) return;
+ _nego_send_fb_info(p_midi, f);
+ }
+ if (p_midi->nego_pending_fb_filter & 0x02) {
+ if (!_nego_send_text(p_midi, STREAM_FB_NAME, f)) return;
+ }
+ p_midi->nego_pending_fb_next++;
+ }
+ if (p_midi->nego_pending_fb_next >= fb_count) p_midi->nego_pending_fb_filter = 0;
+}
+
static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* words) {
// Let the application override this message before the built-in responder.
switch (tud_midi2_stream_msg_cb(_itf_idx(p_midi), words)) {
@@ -421,9 +579,9 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t*
switch (status) {
case STREAM_ENDPOINT_DISCOVERY:
- _nego_send_endpoint_info(p_midi);
- _nego_send_stream_text(p_midi, STREAM_EP_NAME, false, 0, tud_midi2_ep_name_cb(_itf_idx(p_midi)));
- _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, false, 0, tud_midi2_product_id_cb(_itf_idx(p_midi)));
+ // Filter bitmap: each bit set asks for one individual reply.
+ p_midi->nego_pending_ep_filter |= (uint8_t)(words[1] & 0x1F);
+ _nego_send_pending(p_midi);
break;
case STREAM_CONFIG_REQUEST: {
@@ -437,14 +595,23 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t*
}
case STREAM_FB_DISCOVERY: {
- uint8_t fb_idx = (words[0] >> 8) & 0xFF;
- uint8_t filter = words[0] & 0xFF; // bit 0: FB Info, bit 1: FB Name
- uint8_t fb_count = _gtb_block_count(p_midi);
- for (uint8_t f = 0; f < fb_count; f++) {
- if (fb_idx != 0xFF && fb_idx != f) continue;
- if (filter & 0x01) _nego_send_fb_info(p_midi, f);
- if (filter & 0x02) _nego_send_stream_text(p_midi, STREAM_FB_NAME, true, f, tud_midi2_fb_name_cb(_itf_idx(p_midi), f));
+ const uint8_t req_num = (uint8_t)((words[0] >> 8) & 0xFF);
+ const uint8_t req_filter = (uint8_t)(words[0] & 0x03);
+ // Merge with a pending request: repeating a Function Block Info is allowed
+ // at any time, losing a requested one is not.
+ if (req_filter && p_midi->nego_pending_fb_filter) {
+ if (p_midi->nego_pending_fb_num != req_num) p_midi->nego_pending_fb_num = 0xFF;
+ if (p_midi->nego_text_status == STREAM_FB_NAME) {
+ p_midi->nego_pending_fb_restart = true;
+ } else {
+ p_midi->nego_pending_fb_next = 0;
+ }
+ } else if (!p_midi->nego_pending_fb_filter) {
+ p_midi->nego_pending_fb_num = req_num;
+ p_midi->nego_pending_fb_next = 0;
}
+ p_midi->nego_pending_fb_filter |= req_filter; // bit 0: FB Info, bit 1: FB Name
+ _nego_send_pending(p_midi);
break;
}
@@ -754,6 +921,7 @@ bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_re
tu_edpt_stream_clear(&p_midi->ep_stream.rx);
tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ _nego_clear_pending(p_midi);
if (alt == 1) {
p_midi->negotiated = false;
@@ -824,6 +992,10 @@ 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) {
+ // Completed transfer freed FIFO room: flush discovery replies still pending.
+ if (p_midi->alt_setting == 1) {
+ _nego_send_pending(p_midi);
+ }
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 &&
diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h
index 171b404b7..e3eb084d9 100644
--- a/src/class/midi/midi2_device.h
+++ b/src/class/midi/midi2_device.h
@@ -58,6 +58,17 @@ extern "C" {
#define CFG_TUD_MIDI2_PRODUCT_ID "TinyUSB-MIDI2"
#endif
+// Function Block capabilities reported in Function Block Info Notification.
+// The GTB descriptor carries direction and group span, but not these: they
+// depend on what the application implements, so they default to "none".
+#ifndef CFG_TUD_MIDI2_FB_CI_VERSION
+ #define CFG_TUD_MIDI2_FB_CI_VERSION 0 // 0: none or unknown, 1 or higher: MIDI-CI version
+#endif
+
+#ifndef CFG_TUD_MIDI2_FB_SYSEX8_STREAMS
+ #define CFG_TUD_MIDI2_FB_SYSEX8_STREAMS 0 // 0: unsupported, 1: single, 2-255: simultaneous streams
+#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
@@ -118,6 +129,17 @@ typedef enum {
MIDI2_STREAM_NEGOTIATED_MIDI2,
} tud_midi2_stream_result_t;
+// Device identity fields, as defined for the MIDI 1.0 Device Inquiry reply and
+// reused by the Device Identity Notification. Every byte carries 7 bits.
+// A 1-byte System Exclusive ID goes in the first of the three manufacturer
+// bytes, so 0x7D is passed as 0x7D0000.
+typedef struct {
+ uint32_t manufacturer; // 3 bytes, first byte is most significant
+ uint16_t family; // 2 bytes
+ uint16_t model; // 2 bytes
+ uint32_t sw_revision; // 4 bytes
+} tud_midi2_device_identity_t;
+
//--------------------------------------------------------------------+
// Application Callback API (weak, optional)
//--------------------------------------------------------------------+
@@ -138,6 +160,12 @@ const uint8_t* tud_midi2_gtb_desc_cb(uint8_t itf, uint16_t* len);
// discovery. Return NULL or "" for no name.
const char* tud_midi2_fb_name_cb(uint8_t itf, uint8_t fb_idx);
+// Optional device identity, sent as a Device Identity Notification when the
+// host sets the 'd' bit in the Endpoint Discovery filter. Same four fields as
+// the MIDI 1.0 Device Inquiry reply. Return false to skip the notification,
+// which is the default. All values are 7-bit per byte.
+bool tud_midi2_device_identity_cb(uint8_t itf, tud_midi2_device_identity_t* identity);
+
// Optional: intercept an incoming UMP Stream message (MT 0xF). Return PASS to
// let the built-in responder handle it, or HANDLED / NEGOTIATED_* if the app
// answered it (e.g. via tud_midi2_n_ump_write). Lets an app override a single
diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c
index 84a524f49..72b592787 100644
--- a/src/class/net/ncm_device.c
+++ b/src/class/net/ncm_device.c
@@ -800,31 +800,56 @@ static void tud_network_recv_renew_r(uint8_t rhport) {
} // tud_network_recv_renew
/**
- * Set the link state and send notification to host
+ * usbd-task trampoline for tud_network_link_state(), packing rhport and is_up
+ * into a single pointer-sized argument.
+ *
+ * Runs entirely in the usbd task context, so it cannot race the notify
+ * xfer-completion callback over the notification state machine. Re-arming
+ * notification_xmit_state and kicking notification_xmit() (rather than
+ * sending NETWORK_CONNECTION directly) means a state change that collides
+ * with an in-flight notification is picked up by the existing completion
+ * callback instead of being silently dropped - which would otherwise leave
+ * the host stuck at NO-CARRIER after a link-state change.
*/
-void tud_network_link_state(uint8_t rhport, bool is_up) {
- TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up);
+static void ncm_link_state_task(void *param) {
+ uintptr_t const arg = (uintptr_t) param;
+ uint8_t const rhport = (uint8_t) (arg >> 1);
+ bool const is_up = (arg & 1u) != 0;
if (ncm_interface.link_is_up == is_up) {
- // No change in link state
- return;
+ return; // no change in link state
}
ncm_interface.link_is_up = is_up;
- // Only send notification if we have an active data interface
if (ncm_interface.itf_data_alt != 1) {
- TU_LOG_DRV(" link state notification skipped (interface not active)\n");
- return;
+ TU_LOG_DRV(" link state notification deferred (interface not active)\n");
+ return; // data interface not active yet; SET_INTERFACE(alt=1) will notify
}
- // Reset notification state to send speed change notification first, then link state notification
+ // A link toggle does not change the link speed, so strictly only the
+ // NETWORK_CONNECTION notification would need (re)sending. Re-running the
+ // speed-then-connection sequence keeps this on the same state machine the
+ // completion callback already drives, at the cost of a redundant speed
+ // notification on every toggle.
ncm_interface.notification_xmit_state = NOTIFICATION_SPEED;
-
- // Trigger notification transmission
notification_xmit(rhport, false);
}
+/**
+ * Set the link state and notify the host.
+ *
+ * Defers onto the usbd task so a caller running in a different task than
+ * tud_task() cannot race the notification state machine against the notify
+ * xfer-completion callback.
+ */
+void tud_network_link_state(uint8_t rhport, bool is_up) {
+ TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up);
+
+ uintptr_t const arg = ((uintptr_t) rhport << 1) | (is_up ? 1u : 0u);
+ usbd_defer_func(ncm_link_state_task, (void *) arg, false);
+}
+
//-----------------------------------------------------------------------------
//
// all the netd_*() stuff (interface TinyUSB -> driver)
diff --git a/src/class/usbtmc/usbtmc_device.c b/src/class/usbtmc/usbtmc_device.c
index 07190d89f..0e9978a81 100644
--- a/src/class/usbtmc/usbtmc_device.c
+++ b/src/class/usbtmc/usbtmc_device.c
@@ -497,9 +497,24 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
#if (CFG_TUD_USBTMC_ENABLE_488)
case USBTMC_MSGID_USB488_TRIGGER:
- // Spec says we halt the EP if we didn't declare we support it.
- TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger);
- TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg));
+ // Unlike the messages above, TRIGGER is complete on arrival and has no response, so nothing else
+ // will move us out of STATE_IDLE. Do it here, otherwise the tud_usbtmc_start_bus_read() below (and
+ // any call the application makes from its callback) is a no-op and the bulk-OUT endpoint is left
+ // un-armed, silently timing out every subsequent host transfer.
+ TU_VERIFY(atomicChangeState(STATE_IDLE, STATE_NAK));
+
+ // Spec says we halt the EP if we didn't declare we support it; do the same when the application
+ // rejects the trigger. The callback result must not be wrapped in TU_VERIFY() here: returning
+ // early would skip both the stall and the re-arm below.
+ if (!usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger ||
+ !tud_usbtmc_msg_trigger_cb(msg)) {
+ usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out);
+ return false;
+ }
+ // Result deliberately ignored: false here means the endpoint is already armed - either the
+ // application re-armed it from its callback, or a transfer is still queued - not that arming
+ // failed. Stalling on it would halt a healthy endpoint.
+ tud_usbtmc_start_bus_read();
break;
#endif
diff --git a/src/class/usbtmc/usbtmc_device.h b/src/class/usbtmc/usbtmc_device.h
index 3dc700876..efda84f16 100644
--- a/src/class/usbtmc/usbtmc_device.h
+++ b/src/class/usbtmc/usbtmc_device.h
@@ -25,7 +25,6 @@
// * tud_usbtmc_open_cb
// * tud_usbtmc_msg_data_cb
// * tud_usbtmc_msgBulkIn_complete_cb
-// * tud_usbtmc_msg_trigger_cb
// * (successful) tud_usbtmc_check_abort_bulk_out_cb
// * (successful) tud_usbtmc_check_abort_bulk_in_cb
// * (successful) tud_usmtmc_bulkOut_clearFeature_cb
diff --git a/src/class/vendor/vendor_host.c b/src/class/vendor/vendor_host.c
deleted file mode 100644
index dd2c5ac5d..000000000
--- a/src/class/vendor/vendor_host.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
- * SPDX-License-Identifier: MIT
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if (CFG_TUH_ENABLED && CFG_TUH_VENDOR)
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "host/usbh.h"
-#include "vendor_host.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-custom_interface_info_t custom_interface[CFG_TUH_DEVICE_MAX];
-
-static tusb_error_t cush_validate_paras(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length)
-{
- if ( !tusbh_custom_is_mounted(dev_addr, vendor_id, product_id) )
- {
- return TUSB_ERROR_DEVICE_NOT_READY;
- }
-
- TU_ASSERT( p_buffer != NULL && length != 0, TUSB_ERROR_INVALID_PARA);
-
- return TUSB_ERROR_NONE;
-}
-//--------------------------------------------------------------------+
-// APPLICATION API (need to check parameters)
-//--------------------------------------------------------------------+
-tusb_error_t tusbh_custom_read(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length)
-{
- TU_ASSERT_ERR( cush_validate_paras(dev_addr, vendor_id, product_id, p_buffer, length) );
-
- if ( !hcd_pipe_is_idle(custom_interface[dev_addr-1].pipe_in) )
- {
- return TUSB_ERROR_INTERFACE_IS_BUSY;
- }
-
- (void) usbh_edpt_xfer( custom_interface[dev_addr-1].pipe_in, p_buffer, length);
-
- return TUSB_ERROR_NONE;
-}
-
-tusb_error_t tusbh_custom_write(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void const * p_data, uint16_t length)
-{
- TU_ASSERT_ERR( cush_validate_paras(dev_addr, vendor_id, product_id, p_data, length) );
-
- if ( !hcd_pipe_is_idle(custom_interface[dev_addr-1].pipe_out) )
- {
- return TUSB_ERROR_INTERFACE_IS_BUSY;
- }
-
- (void) usbh_edpt_xfer( custom_interface[dev_addr-1].pipe_out, p_data, length);
-
- return TUSB_ERROR_NONE;
-}
-
-//--------------------------------------------------------------------+
-// USBH-CLASS API
-//--------------------------------------------------------------------+
-void cush_init(void)
-{
- tu_memclr(&custom_interface, sizeof(custom_interface_info_t) * CFG_TUH_DEVICE_MAX);
-}
-
-tusb_error_t cush_open_subtask(uint8_t dev_addr, tusb_desc_interface_t const *p_interface_desc, uint16_t *p_length)
-{
- // FIXME quick hack to test lpc1k custom class with 2 bulk endpoints
- uint8_t const *p_desc = (uint8_t const *) p_interface_desc;
- p_desc = tu_desc_next(p_desc);
-
- //------------- Bulk Endpoints Descriptor -------------//
- for(uint32_t i=0; i<2; i++)
- {
- tusb_desc_endpoint_t const *p_endpoint = (tusb_desc_endpoint_t const *) p_desc;
- TU_ASSERT(TUSB_DESC_ENDPOINT == p_endpoint->bDescriptorType, TUSB_ERROR_INVALID_PARA);
-
- pipe_handle_t * p_pipe_hdl = ( p_endpoint->bEndpointAddress & TUSB_DIR_IN_MASK ) ?
- &custom_interface[dev_addr-1].pipe_in : &custom_interface[dev_addr-1].pipe_out;
- *p_pipe_hdl = usbh_edpt_open(dev_addr, p_endpoint, TUSB_CLASS_VENDOR_SPECIFIC);
- TU_ASSERT ( pipehandle_is_valid(*p_pipe_hdl), TUSB_ERROR_HCD_OPEN_PIPE_FAILED );
-
- p_desc = tu_desc_next(p_desc);
- }
-
- (*p_length) = sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t);
- return TUSB_ERROR_NONE;
-}
-
-void cush_isr(pipe_handle_t pipe_hdl, xfer_result_t event)
-{
-
-}
-
-void cush_close(uint8_t dev_addr)
-{
- tusb_error_t err1, err2;
- custom_interface_info_t * p_interface = &custom_interface[dev_addr-1];
-
- // TODO re-consider to check pipe valid before calling pipe_close
- if( pipehandle_is_valid( p_interface->pipe_in ) )
- {
- err1 = hcd_pipe_close( p_interface->pipe_in );
- }
-
- if ( pipehandle_is_valid( p_interface->pipe_out ) )
- {
- err2 = hcd_pipe_close( p_interface->pipe_out );
- }
-
- tu_memclr(p_interface, sizeof(custom_interface_info_t));
-
- TU_ASSERT(err1 == TUSB_ERROR_NONE && err2 == TUSB_ERROR_NONE, (void) 0 );
-}
-
-#endif
diff --git a/src/class/vendor/vendor_host.h b/src/class/vendor/vendor_host.h
deleted file mode 100644
index dc55663b9..000000000
--- a/src/class/vendor/vendor_host.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
- * SPDX-License-Identifier: MIT
- *
- * This file is part of the TinyUSB stack.
- */
-
-#ifndef TUSB_VENDOR_HOST_H_
-#define TUSB_VENDOR_HOST_H_
-
-#include "common/tusb_common.h"
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
-typedef struct {
- pipe_handle_t pipe_in;
- pipe_handle_t pipe_out;
-}custom_interface_info_t;
-
-//--------------------------------------------------------------------+
-// USBH-CLASS DRIVER API
-//--------------------------------------------------------------------+
-static inline bool tusbh_custom_is_mounted(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id)
-{
- (void) vendor_id; // TODO check this later
- (void) product_id;
-// return (tusbh_device_get_mounted_class_flag(dev_addr) & TU_BIT(TUSB_CLASS_MAPPED_INDEX_END-1) ) != 0;
- return false;
-}
-
-bool tusbh_custom_read(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length);
-bool tusbh_custom_write(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void const * p_data, uint16_t length);
-
-//--------------------------------------------------------------------+
-// Internal Class Driver API
-//--------------------------------------------------------------------+
-void cush_init(void);
-bool cush_open_subtask(uint8_t dev_addr, tusb_desc_interface_t const *p_interface_desc, uint16_t *p_length);
-void cush_isr(pipe_handle_t pipe_hdl, xfer_result_t event);
-void cush_close(uint8_t dev_addr);
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* TUSB_VENDOR_HOST_H_ */
diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c
index 3797e6b2b..770595178 100644
--- a/src/class/video/video_device.c
+++ b/src/class/video/video_device.c
@@ -1144,6 +1144,9 @@ static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage,
video_probe_and_commit_control_t *param = &stm->probe_commit_payload;
TU_VERIFY(_update_streaming_parameters(stm, param), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE);
/* Set the negotiated value */
+ if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < param->dwMaxPayloadTransferSize) {
+ param->dwMaxPayloadTransferSize = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE;
+ }
stm->max_payload_transfer_size = param->dwMaxPayloadTransferSize;
int ret = tud_video_commit_cb(stm->index_vc, stm->index_vs, param);
if (VIDEO_ERROR_NONE == ret) {