summaryrefslogtreecommitdiff
path: root/src/class
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-03-11 13:34:59 +0700
committerhathach <[email protected]>2026-03-11 13:34:59 +0700
commit336e89792f9b572c5087b1df479d4e89542f6360 (patch)
tree866a0acbf5dbc952b183768f2b916ff9fe36a591 /src/class
parent60154a128795bcd6ca36042cac1b8bebf1a2764e (diff)
parent7249c65b2a5b995ff7c2b45c1171f218b9d71112 (diff)
Merge branch 'master' into ep0_direct
# Conflicts: # hw/bsp/same7x/boards/same70_qmtech/board.cmake # hw/bsp/same7x/boards/same70_xplained/board.cmake # hw/bsp/same7x/family.cmake
Diffstat (limited to 'src/class')
-rw-r--r--src/class/cdc/cdc_host.c169
-rw-r--r--src/class/dfu/dfu_device.c6
-rw-r--r--src/class/midi/midi_device.c102
-rw-r--r--src/class/midi/midi_device.h12
-rw-r--r--src/class/msc/msc_device.c6
-rw-r--r--src/class/mtp/mtp_device.c105
-rw-r--r--src/class/mtp/mtp_device.h2
-rw-r--r--src/class/printer/printer.h62
-rw-r--r--src/class/printer/printer_device.c329
-rw-r--r--src/class/printer/printer_device.h140
-rw-r--r--src/class/vendor/vendor_device.c262
-rw-r--r--src/class/vendor/vendor_device.h44
-rw-r--r--src/class/video/video_device.c13
-rw-r--r--src/class/video/video_device.h3
14 files changed, 997 insertions, 258 deletions
diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c
index f19c4a327..8f6dd7200 100644
--- a/src/class/cdc/cdc_host.c
+++ b/src/class/cdc/cdc_host.c
@@ -127,7 +127,7 @@ static bool acm_set_control_line_state(cdch_interface_t *p_cdc, tuh_xfer_cb_
static uint16_t const ftdi_vid_pid_list[][2] = {CFG_TUH_CDC_FTDI_VID_PID_LIST};
static uint16_t ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len);
-static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer);
+static bool ftdi_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer);
static void ftdi_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer);
static bool ftdi_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data);
static bool ftdi_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data);
@@ -216,82 +216,84 @@ typedef struct {
#define DRIVER_NAME_DECLARE(_str)
#endif
+// clang-format off
// Note driver list must be in the same order as SERIAL_DRIVER enum
static const cdch_serial_driver_t serial_drivers[] = {
{
- .vid_pid_list = NULL,
- .vid_pid_count = 0,
- .open = acm_open,
- .process_set_config = acm_process_set_config,
- .request_complete = acm_internal_control_complete,
- .set_control_line_state = acm_set_control_line_state,
- .set_baudrate = acm_set_line_coding,
- .set_data_format = acm_set_line_coding,
- .set_line_coding = acm_set_line_coding,
- DRIVER_NAME_DECLARE("ACM")
+ .vid_pid_list = NULL,
+ .vid_pid_count = 0,
+ .open = acm_open,
+ .process_set_config = acm_process_set_config,
+ .request_complete = acm_internal_control_complete,
+ .set_control_line_state = acm_set_control_line_state,
+ .set_baudrate = acm_set_line_coding,
+ .set_data_format = acm_set_line_coding,
+ .set_line_coding = acm_set_line_coding,
+ DRIVER_NAME_DECLARE("ACM")
},
#if CFG_TUH_CDC_FTDI
{
- .vid_pid_list = ftdi_vid_pid_list,
- .vid_pid_count = TU_ARRAY_SIZE(ftdi_vid_pid_list),
- .open = ftdi_open,
- .process_set_config = ftdi_proccess_set_config,
- .request_complete = ftdi_internal_control_complete,
- .set_control_line_state = ftdi_set_modem_ctrl,
- .set_baudrate = ftdi_set_baudrate,
- .set_data_format = ftdi_set_data_format,
- .set_line_coding = NULL, // 2 stage set line coding
- DRIVER_NAME_DECLARE("FTDI")
+ .vid_pid_list = ftdi_vid_pid_list,
+ .vid_pid_count = TU_ARRAY_SIZE(ftdi_vid_pid_list),
+ .open = ftdi_open,
+ .process_set_config = ftdi_process_set_config,
+ .request_complete = ftdi_internal_control_complete,
+ .set_control_line_state = ftdi_set_modem_ctrl,
+ .set_baudrate = ftdi_set_baudrate,
+ .set_data_format = ftdi_set_data_format,
+ .set_line_coding = NULL, // 2 stage set line coding
+ DRIVER_NAME_DECLARE("FTDI")
},
#endif
#if CFG_TUH_CDC_CP210X
{
- .vid_pid_list = cp210x_vid_pid_list,
- .vid_pid_count = TU_ARRAY_SIZE(cp210x_vid_pid_list),
- .open = cp210x_open,
- .process_set_config = cp210x_process_set_config,
- .request_complete = cp210x_internal_control_complete,
- .set_control_line_state = cp210x_set_modem_ctrl,
- .set_baudrate = cp210x_set_baudrate,
- .set_data_format = cp210x_set_data_format,
- .set_line_coding = NULL, // 2 stage set line coding
- DRIVER_NAME_DECLARE("CP210x")
+ .vid_pid_list = cp210x_vid_pid_list,
+ .vid_pid_count = TU_ARRAY_SIZE(cp210x_vid_pid_list),
+ .open = cp210x_open,
+ .process_set_config = cp210x_process_set_config,
+ .request_complete = cp210x_internal_control_complete,
+ .set_control_line_state = cp210x_set_modem_ctrl,
+ .set_baudrate = cp210x_set_baudrate,
+ .set_data_format = cp210x_set_data_format,
+ .set_line_coding = NULL, // 2 stage set line coding
+ DRIVER_NAME_DECLARE("CP210x")
},
#endif
#if CFG_TUH_CDC_CH34X
{
- .vid_pid_list = ch34x_vid_pid_list,
- .vid_pid_count = TU_ARRAY_SIZE(ch34x_vid_pid_list),
- .open = ch34x_open,
- .process_set_config = ch34x_process_set_config,
- .request_complete = ch34x_internal_control_complete,
+ .vid_pid_list = ch34x_vid_pid_list,
+ .vid_pid_count = TU_ARRAY_SIZE(ch34x_vid_pid_list),
+ .open = ch34x_open,
+ .process_set_config = ch34x_process_set_config,
+ .request_complete = ch34x_internal_control_complete,
- .set_control_line_state = ch34x_set_modem_ctrl,
- .set_baudrate = ch34x_set_baudrate,
- .set_data_format = ch34x_set_data_format,
- .set_line_coding = NULL, // 2 stage set line coding
- DRIVER_NAME_DECLARE("CH34x")
+ .set_control_line_state = ch34x_set_modem_ctrl,
+ .set_baudrate = ch34x_set_baudrate,
+ .set_data_format = ch34x_set_data_format,
+ .set_line_coding = NULL, // 2 stage set line coding
+ DRIVER_NAME_DECLARE("CH34x")
},
#endif
#if CFG_TUH_CDC_PL2303
{
- .vid_pid_list = pl2303_vid_pid_list,
- .vid_pid_count = TU_ARRAY_SIZE(pl2303_vid_pid_list),
- .open = pl2303_open,
- .process_set_config = pl2303_process_set_config,
- .request_complete = pl2303_internal_control_complete,
- .set_control_line_state = pl2303_set_modem_ctrl,
- .set_baudrate = pl2303_set_line_coding,
- .set_data_format = pl2303_set_line_coding,
- .set_line_coding = pl2303_set_line_coding,
- DRIVER_NAME_DECLARE("PL2303")
+ .vid_pid_list = pl2303_vid_pid_list,
+ .vid_pid_count = TU_ARRAY_SIZE(pl2303_vid_pid_list),
+ .open = pl2303_open,
+ .process_set_config = pl2303_process_set_config,
+ .request_complete = pl2303_internal_control_complete,
+ .set_control_line_state = pl2303_set_modem_ctrl,
+ .set_baudrate = pl2303_set_line_coding,
+ .set_data_format = pl2303_set_line_coding,
+ .set_line_coding = pl2303_set_line_coding,
+ DRIVER_NAME_DECLARE("PL2303")
}
#endif
};
+// clang-format on
TU_VERIFY_STATIC(TU_ARRAY_SIZE(serial_drivers) == SERIAL_DRIVER_COUNT, "Serial driver count mismatch");
@@ -761,7 +763,8 @@ uint16_t cdch_open(uint8_t rhport, uint8_t daddr, const tusb_desc_interface_t *i
for (size_t i = 0; i < driver->vid_pid_count; i++) {
if (driver->vid_pid_list[i][0] == vid && driver->vid_pid_list[i][1] == pid) {
const uint16_t drv_len = driver->open(daddr, itf_desc, max_len);
- TU_LOG_DRV("[:%u:%u] CDCh %s open %s\r\n", daddr, itf_desc->bInterfaceNumber, driver->name, drv_len > 0 ? "OK" : "FAILED");
+ TU_LOG_DRV("[:%u:%u] CDCh %s open %s\r\n", daddr, itf_desc->bInterfaceNumber, driver->name,
+ drv_len > 0 ? "OK" : "FAILED");
return drv_len;
}
}
@@ -773,35 +776,16 @@ uint16_t cdch_open(uint8_t rhport, uint8_t daddr, const tusb_desc_interface_t *i
return 0;
}
-bool cdch_set_config(uint8_t daddr, uint8_t itf_num) {
- tusb_control_request_t request;
- request.wIndex = tu_htole16((uint16_t) itf_num);
- uint8_t const idx = tuh_cdc_itf_get_index(daddr, itf_num);
- cdch_interface_t *p_cdc = get_itf(idx);
- TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT);
- TU_LOG_CDC(p_cdc, "set config");
-
- // fake transfer to kick-off process_set_config()
- tuh_xfer_t xfer;
- xfer.daddr = daddr;
- xfer.result = XFER_RESULT_SUCCESS;
- xfer.setup = &request;
- xfer.user_data = 0; // initial state 0
- cdch_process_set_config(&xfer);
-
- return true;
-}
-
static void set_config_complete(cdch_interface_t *p_cdc, bool success) {
if (success) {
const uint8_t idx = get_idx_by_ptr(p_cdc);
- p_cdc->mounted = true;
+ p_cdc->mounted = true;
tuh_cdc_mount_cb(idx);
// Prepare for incoming data
tu_edpt_stream_read_xfer(&p_cdc->stream.rx);
} else {
// clear the interface entry
- p_cdc->daddr = 0;
+ p_cdc->daddr = 0;
p_cdc->bInterfaceNumber = 0;
}
@@ -810,6 +794,33 @@ static void set_config_complete(cdch_interface_t *p_cdc, bool success) {
usbh_driver_set_config_complete(p_cdc->daddr, p_cdc->bInterfaceNumber + itf_offset);
}
+bool cdch_set_config(uint8_t daddr, uint8_t itf_num) {
+ const uint8_t idx = tuh_cdc_itf_get_index(daddr, itf_num);
+ cdch_interface_t *p_cdc = get_itf(idx);
+ TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT);
+ TU_LOG_CDC(p_cdc, "set config");
+
+ // fake transfer to kick-off process_set_config()
+ tusb_control_request_t request;
+ request.wIndex = tu_htole16((uint16_t)itf_num);
+
+ tuh_xfer_t xfer;
+ xfer.daddr = daddr;
+ xfer.ep_addr = 0;
+ xfer.result = XFER_RESULT_SUCCESS;
+ xfer.setup = &request;
+ xfer.complete_cb = NULL;
+ xfer.buffer = NULL;
+ xfer.user_data = 0; // initial state 0
+
+ const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid];
+ if (!driver->process_set_config(p_cdc, &xfer)) {
+ set_config_complete(p_cdc, false);
+ }
+
+ return true;
+}
+
static void cdch_process_set_config(tuh_xfer_t *xfer) {
cdch_interface_t *p_cdc = get_itf_by_xfer(xfer);
TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,);
@@ -1215,22 +1226,14 @@ static uint16_t ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc,
return drv_len;
}
-static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) {
+static bool ftdi_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) {
TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS);
const uintptr_t state = xfer->user_data;
switch (state) {
// from here sequence overtaken from Linux Kernel function ftdi_port_probe()
case CONFIG_FTDI_DETERMINE_TYPE:
// determine type
- if (p_cdc->bInterfaceNumber == 0) {
- TU_ASSERT(ftdi_determine_type(p_cdc));
- } else {
- // other interfaces have same type as interface 0
- uint8_t const idx_itf0 = tuh_cdc_itf_get_index(xfer->daddr, 0);
- cdch_interface_t const *p_cdc_itf0 = get_itf(idx_itf0);
- TU_ASSERT(p_cdc_itf0);
- p_cdc->ftdi.chip_type = p_cdc_itf0->ftdi.chip_type;
- }
+ TU_ASSERT(ftdi_determine_type(p_cdc));
TU_ATTR_FALLTHROUGH;
case CONFIG_FTDI_WRITE_LATENCY:
diff --git a/src/class/dfu/dfu_device.c b/src/class/dfu/dfu_device.c
index 36f9ffcf4..65332eae3 100644
--- a/src/class/dfu/dfu_device.c
+++ b/src/class/dfu/dfu_device.c
@@ -338,7 +338,7 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control
default:
if (stage == CONTROL_STAGE_SETUP) {
- return reply_getstatus(rhport, request, _dfu_ctx.state, _dfu_ctx.status, 0);
+ return reply_getstatus(rhport, request, (dfu_state_t) _dfu_ctx.state, (dfu_status_t) _dfu_ctx.status, 0);
}
break;
}
@@ -387,7 +387,7 @@ static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tus
timeout = 0;
}
- return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout);
+ return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout);
} else if (stage == CONTROL_STAGE_ACK) {
if (_dfu_ctx.flashing_in_progress) {
_dfu_ctx.state = DFU_DNBUSY;
@@ -416,7 +416,7 @@ static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tus
timeout = 0;
}
- return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout);
+ return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout);
} else if (stage == CONTROL_STAGE_ACK) {
if (_dfu_ctx.flashing_in_progress) {
_dfu_ctx.state = DFU_MANIFEST;
diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c
index 023a81595..a49cd725b 100644
--- a/src/class/midi/midi_device.c
+++ b/src/class/midi/midi_device.c
@@ -168,6 +168,108 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, ui
return total_read;
}
+// Note: this function shares stream->buffer with tud_midi_n_stream_read().
+// Do not mix calls to both functions on the same interface.
+uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize) {
+ TU_VERIFY(p_cable_num != NULL && buffer != NULL && bufsize > 0, 0);
+
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ midi_driver_stream_t *stream = &p_midi->stream_read;
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
+
+ uint8_t *buf8 = (uint8_t *)buffer;
+ uint32_t total_read = 0;
+
+ // Initialize to invalid cable so callers can detect "no data" even when
+ // the return value is 0.
+ *p_cable_num = 0xff;
+
+ // If there are leftover bytes from a previous partial read, return them first
+ if (stream->total > 0) {
+ *p_cable_num = (stream->buffer[0] >> 4) & 0x0f;
+ const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize);
+ TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count));
+
+ total_read += count;
+ stream->index += count;
+ buf8 += count;
+ bufsize -= count;
+
+ if (stream->total == stream->index) {
+ stream->index = 0;
+ stream->total = 0;
+ }
+
+ if (bufsize == 0) {
+ return total_read;
+ }
+ }
+
+ while (bufsize > 0) {
+ // Peek at next packet header to get cable number without consuming
+ uint8_t one_byte;
+ if (!tu_edpt_stream_peek(ep_str, &one_byte)) {
+ break;
+ }
+
+ const uint8_t next_cable = (one_byte >> 4) & 0x0f;
+
+ // Stop if cable changed (covers both leftover-originated reads and
+ // freshly consumed packets — total_read > 0 in either case)
+ if (total_read > 0 && next_cable != *p_cable_num) {
+ break;
+ }
+ *p_cable_num = next_cable;
+
+ // Consume the packet
+ if (!tud_midi_n_packet_read(itf, stream->buffer)) {
+ break;
+ }
+
+ const uint8_t code_index = stream->buffer[0] & 0x0f;
+ uint8_t msg_bytes;
+
+ // MIDI 1.0 Table 4-1: Code Index Number Classifications
+ switch (code_index) {
+ case MIDI_CIN_MISC:
+ case MIDI_CIN_CABLE_EVENT:
+ // Reserved and unused, skip this packet
+ continue;
+
+ case MIDI_CIN_SYSEX_END_1BYTE:
+ case MIDI_CIN_1BYTE_DATA:
+ msg_bytes = 1;
+ break;
+
+ case MIDI_CIN_SYSCOM_2BYTE:
+ case MIDI_CIN_SYSEX_END_2BYTE:
+ case MIDI_CIN_PROGRAM_CHANGE:
+ case MIDI_CIN_CHANNEL_PRESSURE:
+ msg_bytes = 2;
+ break;
+
+ default:
+ msg_bytes = 3;
+ break;
+ }
+
+ const uint8_t count = (uint8_t)tu_min32((uint32_t)msg_bytes, bufsize);
+ TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1, count));
+
+ total_read += count;
+ buf8 += count;
+ bufsize -= count;
+
+ if (count < msg_bytes) {
+ // Output buffer full, save remaining for next call
+ stream->total = msg_bytes;
+ stream->index = count;
+ }
+ }
+
+ return total_read;
+}
+
bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]) {
midid_interface_t *p_midi = &_midid_itf[itf];
tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
diff --git a/src/class/midi/midi_device.h b/src/class/midi/midi_device.h
index ddbc2f9f0..b80ad544a 100644
--- a/src/class/midi/midi_device.h
+++ b/src/class/midi/midi_device.h
@@ -66,6 +66,13 @@ uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num);
// Read byte stream (legacy)
uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize);
+// Read byte stream with cable demultiplexing: returns the cable number of the
+// data that was read. Reads from a single cable per call; stops when the next
+// packet belongs to a different cable so the caller can dispatch per-cable.
+// Note: shares internal state with tud_midi_n_stream_read(); do not mix both
+// on the same interface.
+uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize);
+
// Write byte Stream (legacy)
uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize);
@@ -97,6 +104,11 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_read(void *buffer,
}
TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi_demux_stream_read(uint8_t *p_cable_num, void *buffer, uint32_t bufsize) {
+ return tud_midi_n_demux_stream_read(0, p_cable_num, buffer, bufsize);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
tud_midi_stream_write(uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) {
return tud_midi_n_stream_write(0, cable_num, buffer, bufsize);
}
diff --git a/src/class/msc/msc_device.c b/src/class/msc/msc_device.c
index 15bfafc35..3766e3a25 100644
--- a/src/class/msc/msc_device.c
+++ b/src/class/msc/msc_device.c
@@ -646,7 +646,11 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
break;
}
- TU_ASSERT(prepare_cbw(p_msc));
+ if (!usbd_edpt_stalled(rhport, p_msc->ep_out)) {
+ TU_ASSERT(prepare_cbw(p_msc));
+ } else {
+ p_msc->stage = MSC_STAGE_CMD;
+ }
} else {
// Any xfer ended here is considered unknown error, ignore it
TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n");
diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c
index 4942a105a..59096e476 100644
--- a/src/class/mtp/mtp_device.c
+++ b/src/class/mtp/mtp_device.c
@@ -92,8 +92,9 @@ typedef struct {
uint8_t itf_num;
uint8_t ep_in;
uint8_t ep_out;
- uint8_t ep_event;
+ uint8_t ep_event;
+ uint8_t ep_sz_fs;
// Bulk Only Transfer (BOT) Protocol
uint8_t phase;
@@ -194,42 +195,47 @@ static bool prepare_new_command(mtpd_interface_t* p_mtp) {
return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE, false);
}
-static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) {
- mtpd_interface_t* p_mtp = &_mtpd_itf;
+bool tud_mtp_data_send(mtp_container_info_t *p_container) {
+ mtpd_interface_t *p_mtp = &_mtpd_itf;
if (p_mtp->phase == MTP_PHASE_COMMAND) {
// 1st data block: header + payload
p_mtp->phase = MTP_PHASE_DATA;
p_mtp->xferred_len = 0;
+ p_mtp->total_len = p_container->header->len;
- if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
- p_mtp->total_len = p_container->header->len;
- p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK;
- p_container->header->transaction_id = p_mtp->command.header.transaction_id;
- p_mtp->io_header = *p_container->header; // save header for subsequent data
- } else {
- // OUT transfer: total length is at least max packet size
- p_mtp->total_len = tu_max32(p_container->header->len, CFG_TUD_MTP_EP_BUFSIZE);
- }
- } else {
- // subsequent data block: payload only
- TU_ASSERT(p_mtp->phase == MTP_PHASE_DATA);
+ p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK;
+ p_container->header->transaction_id = p_mtp->command.header.transaction_id;
+ p_mtp->io_header = *p_container->header; // save header for subsequent data
}
- const uint16_t xact_len = (uint16_t) tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE);
+ const uint16_t xact_len = (uint16_t)tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE);
+
+ TU_LOG_DRV(" MTP Data IN: xferred_len/total_len=%lu/%lu, xact_len=%u\r\n", p_mtp->xferred_len, p_mtp->total_len,
+ xact_len);
if (xact_len) {
- // already transferred all bytes in header's length. Application make an unnecessary extra call
- TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr));
- TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len, false));
+ TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_in));
+ TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, xact_len, false));
}
return true;
}
-bool tud_mtp_data_send(mtp_container_info_t* p_container) {
- return mtpd_data_xfer(p_container, _mtpd_itf.ep_in);
-}
+bool tud_mtp_data_receive(mtp_container_info_t *p_container) {
+ mtpd_interface_t *p_mtp = &_mtpd_itf;
+ if (p_mtp->phase == MTP_PHASE_COMMAND) {
+ // 1st data block: header + payload
+ p_mtp->phase = MTP_PHASE_DATA;
+ p_mtp->xferred_len = 0;
+ p_mtp->total_len = p_container->header->len;
+ }
-bool tud_mtp_data_receive(mtp_container_info_t* p_container) {
- return mtpd_data_xfer(p_container, _mtpd_itf.ep_out);
+ // up to buffer size since 1st packet (with header) may also contain payload
+ const uint16_t xact_len = CFG_TUD_MTP_EP_BUFSIZE;
+
+ TU_LOG_DRV(" MTP Data OUT: xferred_len/total_len=%lu/%lu, xact_len=%u\r\n", p_mtp->xferred_len, p_mtp->total_len,
+ xact_len);
+ TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_out));
+ TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, xact_len, false));
+ return true;
}
bool tud_mtp_response_send(mtp_container_info_t* p_container) {
@@ -287,15 +293,20 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16
p_mtp->itf_num = itf_desc->bInterfaceNumber;
// Open interrupt IN endpoint
- const tusb_desc_endpoint_t* ep_desc = (const tusb_desc_endpoint_t*) tu_desc_next(itf_desc);
- TU_ASSERT(ep_desc->bDescriptorType == TUSB_DESC_ENDPOINT && ep_desc->bmAttributes.xfer == TUSB_XFER_INTERRUPT, 0);
- TU_ASSERT(usbd_edpt_open(rhport, ep_desc), 0);
- p_mtp->ep_event = ep_desc->bEndpointAddress;
+ const tusb_desc_endpoint_t* ep_desc_int = (const tusb_desc_endpoint_t*) tu_desc_next(itf_desc);
+ TU_ASSERT(ep_desc_int->bDescriptorType == TUSB_DESC_ENDPOINT && ep_desc_int->bmAttributes.xfer == TUSB_XFER_INTERRUPT, 0);
+ TU_ASSERT(usbd_edpt_open(rhport, ep_desc_int), 0);
+ p_mtp->ep_event = ep_desc_int->bEndpointAddress;
// Open endpoint pair
- TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0);
+ const tusb_desc_endpoint_t* ep_desc_bulk = (const tusb_desc_endpoint_t*) tu_desc_next(ep_desc_int);
+ TU_ASSERT(usbd_open_edpt_pair(rhport, (const uint8_t*)ep_desc_bulk, 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0);
TU_ASSERT(prepare_new_command(p_mtp), 0);
+ if (tud_speed_get() == TUSB_SPEED_FULL) {
+ p_mtp->ep_sz_fs = (uint8_t)tu_edpt_packet_size(ep_desc_bulk);
+ }
+
return mtpd_itf_size;
}
@@ -377,8 +388,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
mtp_generic_container_t* p_container = (mtp_generic_container_t*) _mtpd_epbuf.buf;
#if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL
- tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code);
- TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code),
+ const uint16_t code = (p_mtp->phase == MTP_PHASE_COMMAND) ? p_container->header.code : p_mtp->command.header.code;
+ TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, code),
_mtp_phase_str[p_mtp->phase]);
#endif
@@ -417,19 +428,35 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
}
case MTP_PHASE_DATA: {
- const uint16_t bulk_mps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 512 : 64;
p_mtp->xferred_len += xferred_bytes;
cb_data.total_xferred_bytes = p_mtp->xferred_len;
- bool is_complete = false;
- // complete if ZLP or short packet or total length reached
- if (xferred_bytes == 0 || // ZLP
- (xferred_bytes & (bulk_mps - 1)) || // short packet
- p_mtp->xferred_len >= p_mtp->total_len) { // total length reached
- is_complete = true;
+ const bool is_data_in = (ep_addr == p_mtp->ep_in);
+ // For IN endpoint, threshold is bulk max packet size
+ // For OUT endpoint, threshold is endpoint buffer size, since we always queue fixed size
+ uint16_t threshold;
+ if (is_data_in) {
+ threshold = (p_mtp->ep_sz_fs > 0) ? p_mtp->ep_sz_fs : 512; // full speed bulk if set
+ } else {
+ 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);
+
+ 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);
+
+ // Send/queue ZLP if packet is full-sized but transfer is complete
+ if (is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1))) {
+ TU_LOG_DRV(" queue ZLP\r\n");
+ TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr));
+ TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false));
+ return true;
}
- if (ep_addr == p_mtp->ep_in) {
+ if (is_data_in) {
// Data In
if (is_complete) {
cb_data.io_container.header->len = sizeof(mtp_container_header_t);
diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h
index a33f1dc08..6cce7efbb 100644
--- a/src/class/mtp/mtp_device.h
+++ b/src/class/mtp/mtp_device.h
@@ -18,7 +18,7 @@
* 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 IN0
+ * 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.
diff --git a/src/class/printer/printer.h b/src/class/printer/printer.h
new file mode 100644
index 000000000..09d1a8956
--- /dev/null
+++ b/src/class/printer/printer.h
@@ -0,0 +1,62 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_PRINTER_H_
+#define TUSB_PRINTER_H_
+
+#include "common/tusb_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Printer Class Specific Control Request
+typedef enum {
+ TUSB_PRINTER_REQUEST_GET_DEVICE_ID = 0x00, ///< Get device ID
+ TUSB_PRINTER_REQUEST_GET_PORT_STATUS = 0x01, ///< Get port status
+ TUSB_PRINTER_REQUEST_SOFT_RESET = 0x02, ///< Soft reset
+} tusb_printer_request_type_t;
+
+/// Printer Port Status (returned by GET_PORT_STATUS request)
+/// USB Printer Class spec 1.1, Section 4.2
+typedef union TU_ATTR_PACKED {
+ uint8_t status;
+ struct TU_ATTR_PACKED {
+ uint8_t reserved0 : 3; ///< Reserved (bits 0-2)
+ uint8_t not_error : 1; ///< 1 = no error, 0 = error
+ uint8_t selected : 1; ///< 1 = selected (online), 0 = not selected
+ uint8_t paper_empty : 1; ///< 1 = paper empty, 0 = paper not empty
+ uint8_t reserved6 : 2; ///< Reserved (bits 6-7)
+ } status_bm;
+} tusb_printer_port_status_t;
+
+TU_VERIFY_STATIC(sizeof(tusb_printer_port_status_t) == 1, "size is not correct");
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/printer/printer_device.c b/src/class/printer/printer_device.c
new file mode 100644
index 000000000..f5bb33795
--- /dev/null
+++ b/src/class/printer/printer_device.c
@@ -0,0 +1,329 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 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 && CFG_TUD_PRINTER)
+
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+
+#include "printer_device.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+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;
+
+ uint8_t rx_ff_buf[CFG_TUD_PRINTER_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUD_PRINTER_TX_BUFSIZE];
+} printer_interface_t;
+
+#define ITF_MEM_RESET_SIZE offsetof(printer_interface_t, rx_stream)
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+typedef struct {
+ TUD_EPBUF_DEF(epout, CFG_TUD_PRINTER_EP_BUFSIZE);
+ TUD_EPBUF_DEF(epin, CFG_TUD_PRINTER_EP_BUFSIZE);
+} printer_epbuf_t;
+
+CFG_TUD_MEM_SECTION static printer_epbuf_t _printer_epbuf[CFG_TUD_PRINTER];
+#endif
+
+static printer_interface_t _printer_itf[CFG_TUD_PRINTER];
+
+//--------------------------------------------------------------------+
+// INTERNAL HELPERS
+//--------------------------------------------------------------------+
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t _find_itf(uint8_t ep_addr) {
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ const printer_interface_t *p = &_printer_itf[i];
+ if (ep_addr == p->rx_stream.ep_addr || ep_addr == p->tx_stream.ep_addr) {
+ return i;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_printer_rx_cb(uint8_t itf) {
+ (void)itf;
+}
+
+TU_ATTR_WEAK void tud_printer_tx_complete_cb(uint8_t itf) {
+ (void)itf;
+}
+
+TU_ATTR_WEAK void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request) {
+ (void)itf;
+ (void)request;
+}
+
+TU_ATTR_WEAK uint8_t const *tud_printer_get_device_id_cb(uint8_t itf) {
+ (void)itf;
+ return NULL;
+}
+
+TU_ATTR_WEAK uint8_t tud_printer_get_port_status_cb(uint8_t itf) {
+ (void)itf;
+ return 0x18; // not error, selected, paper not empty
+}
+
+TU_ATTR_WEAK void tud_printer_soft_reset_cb(uint8_t itf) {
+ (void)itf;
+}
+
+//--------------------------------------------------------------------+
+// READ API
+//--------------------------------------------------------------------+
+uint32_t tud_printer_n_read_available(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_read_available(&_printer_itf[itf].rx_stream);
+}
+
+uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_read(&_printer_itf[itf].rx_stream, buffer, bufsize);
+}
+
+bool tud_printer_n_peek(uint8_t itf, uint8_t *chr) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER);
+ return tu_edpt_stream_peek(&_printer_itf[itf].rx_stream, chr);
+}
+
+void tud_printer_n_read_flush(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, );
+ printer_interface_t *p = &_printer_itf[itf];
+ tu_edpt_stream_clear(&p->rx_stream);
+ tu_edpt_stream_read_xfer(&p->rx_stream);
+}
+
+//--------------------------------------------------------------------+
+// WRITE API
+//--------------------------------------------------------------------+
+uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_write(&_printer_itf[itf].tx_stream, buffer, bufsize);
+}
+
+uint32_t tud_printer_n_write_flush(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_write_xfer(&_printer_itf[itf].tx_stream);
+}
+
+uint32_t tud_printer_n_write_available(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER, 0);
+ return tu_edpt_stream_write_available(&_printer_itf[itf].tx_stream);
+}
+
+bool tud_printer_n_write_clear(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_PRINTER);
+ tu_edpt_stream_clear(&_printer_itf[itf].tx_stream);
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// USBD-CLASS API
+//--------------------------------------------------------------------+
+void printerd_init(void) {
+ tu_memclr(_printer_itf, sizeof(_printer_itf));
+
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ printer_interface_t *p = &_printer_itf[i];
+
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ uint8_t *epout_buf = NULL;
+ uint8_t *epin_buf = NULL;
+ #else
+ uint8_t *epout_buf = _printer_epbuf[i].epout;
+ uint8_t *epin_buf = _printer_epbuf[i].epin;
+ #endif
+
+ tu_edpt_stream_init(&p->rx_stream, false, false, false,
+ p->rx_ff_buf, CFG_TUD_PRINTER_RX_BUFSIZE,
+ epout_buf, CFG_TUD_PRINTER_EP_BUFSIZE);
+
+ tu_edpt_stream_init(&p->tx_stream, false, true, true,
+ p->tx_ff_buf, CFG_TUD_PRINTER_TX_BUFSIZE,
+ epin_buf, CFG_TUD_PRINTER_EP_BUFSIZE);
+ }
+}
+
+bool printerd_deinit(void) {
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ printer_interface_t *p = &_printer_itf[i];
+ tu_edpt_stream_deinit(&p->rx_stream);
+ tu_edpt_stream_deinit(&p->tx_stream);
+ }
+ return true;
+}
+
+void printerd_reset(uint8_t rhport) {
+ (void)rhport;
+
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ printer_interface_t *p = &_printer_itf[i];
+ tu_memclr(p, ITF_MEM_RESET_SIZE);
+ tu_edpt_stream_close(&p->rx_stream);
+ tu_edpt_stream_close(&p->tx_stream);
+ }
+}
+
+uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len) {
+ TU_VERIFY(TUSB_CLASS_PRINTER == itf_desc->bInterfaceClass, 0);
+
+ // Find available interface slot
+ uint8_t const printer_id = _find_itf(0);
+ TU_ASSERT(printer_id < CFG_TUD_PRINTER, 0);
+ printer_interface_t *p = &_printer_itf[printer_id];
+
+ p->itf_num = itf_desc->bInterfaceNumber;
+
+ //------------- Endpoints -------------//
+ const uint8_t *p_desc = (const uint8_t *)itf_desc;
+ const uint8_t *desc_end = p_desc + max_len;
+ uint16_t drv_len = sizeof(tusb_desc_interface_t);
+
+ p_desc = tu_desc_next(itf_desc);
+ for (uint8_t e = 0; e < itf_desc->bNumEndpoints; e++) {
+ TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0);
+ const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc;
+ TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0);
+
+ TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
+
+ if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
+ tu_edpt_stream_open(&p->tx_stream, rhport, desc_ep);
+ tu_edpt_stream_clear(&p->tx_stream);
+ } else {
+ tu_edpt_stream_open(&p->rx_stream, rhport, desc_ep);
+ tu_edpt_stream_clear(&p->rx_stream);
+ TU_ASSERT(tu_edpt_stream_read_xfer(&p->rx_stream) > 0, 0);
+ }
+
+ drv_len += sizeof(tusb_desc_endpoint_t);
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ return drv_len;
+}
+
+bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request) {
+ TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE &&
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS);
+
+ // GET_DEVICE_ID: wIndex = (interface_number << 8) | alt_setting
+ // GET_PORT_STATUS / SOFT_RESET: wIndex = interface_number
+ uint8_t itf_num;
+ if (TUSB_PRINTER_REQUEST_GET_DEVICE_ID == request->bRequest) {
+ itf_num = tu_u16_high(request->wIndex);
+ } else {
+ itf_num = tu_u16_low(request->wIndex);
+ }
+
+ // Find the printer instance index from the USB interface number
+ uint8_t itf = TUSB_INDEX_INVALID_8;
+ for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) {
+ if (_printer_itf[i].itf_num == itf_num) {
+ itf = i;
+ break;
+ }
+ }
+ TU_VERIFY(itf < CFG_TUD_PRINTER);
+
+ // https://www.usb.org/sites/default/files/usbprint11a021811.pdf
+ if (stage == CONTROL_STAGE_SETUP) {
+ switch (request->bRequest) {
+ case TUSB_PRINTER_REQUEST_GET_DEVICE_ID: {
+ const uint8_t *device_id = tud_printer_get_device_id_cb(itf);
+ TU_VERIFY(device_id);
+ const uint16_t total_len = (uint16_t)((device_id[0] << 8) | device_id[1]);
+ return tud_control_xfer(rhport, request, (void *)(uintptr_t)device_id, total_len);
+ }
+
+ case TUSB_PRINTER_REQUEST_GET_PORT_STATUS: {
+ static uint8_t port_status;
+ port_status = tud_printer_get_port_status_cb(itf);
+ return tud_control_xfer(rhport, request, &port_status, sizeof(port_status));
+ }
+
+ case TUSB_PRINTER_REQUEST_SOFT_RESET:
+ tud_printer_soft_reset_cb(itf);
+ tud_control_status(rhport, request);
+ return true;
+
+ default:
+ return false;
+ }
+ } else if (stage == CONTROL_STAGE_ACK) {
+ tud_printer_request_complete_cb(itf, request);
+ }
+
+ return true;
+}
+
+bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void)rhport;
+ (void)result;
+
+ uint8_t const itf = _find_itf(ep_addr);
+ TU_ASSERT(itf < CFG_TUD_PRINTER);
+ printer_interface_t *p = &_printer_itf[itf];
+
+ // Received new data
+ if (ep_addr == p->rx_stream.ep_addr) {
+ tu_edpt_stream_read_xfer_complete(&p->rx_stream, xferred_bytes);
+
+ if (!tu_edpt_stream_empty(&p->rx_stream)) {
+ tud_printer_rx_cb(itf);
+ }
+
+ tu_edpt_stream_read_xfer(&p->rx_stream);
+ }
+
+ // Data sent to host
+ if (ep_addr == p->tx_stream.ep_addr) {
+ tud_printer_tx_complete_cb(itf);
+
+ if (0 == tu_edpt_stream_write_xfer(&p->tx_stream)) {
+ tu_edpt_stream_write_zlp_if_needed(&p->tx_stream, xferred_bytes);
+ }
+ }
+
+ return true;
+}
+
+#endif
diff --git a/src/class/printer/printer_device.h b/src/class/printer/printer_device.h
new file mode 100644
index 000000000..a6b7052cb
--- /dev/null
+++ b/src/class/printer/printer_device.h
@@ -0,0 +1,140 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_PRINTER_DEVICE_H_
+#define TUSB_PRINTER_DEVICE_H_
+
+#include "printer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Application API (Multiple Ports) i.e. CFG_TUD_PRINTER > 1
+//--------------------------------------------------------------------+
+
+// Get the number of bytes available for reading
+uint32_t tud_printer_n_read_available(uint8_t itf);
+
+// Read received bytes
+uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize);
+
+// Get the number of bytes available for writing
+uint32_t tud_printer_n_write_available(uint8_t itf);
+
+// Clear the received FIFO
+void tud_printer_n_read_flush(uint8_t itf);
+
+// Get a byte from FIFO without removing it
+bool tud_printer_n_peek(uint8_t itf, uint8_t *ui8);
+
+// Write data to host
+uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize);
+
+// Force sending data in the TX FIFO
+uint32_t tud_printer_n_write_flush(uint8_t itf);
+
+// Clear the transmit FIFO
+bool tud_printer_n_write_clear(uint8_t itf);
+
+//--------------------------------------------------------------------+
+// Application API (Single Port)
+//--------------------------------------------------------------------+
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read_available(void) {
+ return tud_printer_n_read_available(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_available(void) {
+ return tud_printer_n_write_available(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read(void *buffer, uint32_t bufsize) {
+ return tud_printer_n_read(0, buffer, bufsize);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void tud_printer_read_flush(void) {
+ tud_printer_n_read_flush(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_peek(uint8_t *ui8) {
+ return tud_printer_n_peek(0, ui8);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write(const void *buffer, uint32_t bufsize) {
+ return tud_printer_n_write(0, buffer, bufsize);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_flush(void) {
+ return tud_printer_n_write_flush(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_write_clear(void) {
+ return tud_printer_n_write_clear(0);
+}
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak is optional)
+//--------------------------------------------------------------------+
+
+// Invoked when received new data
+void tud_printer_rx_cb(uint8_t itf);
+
+// Invoked when last write transfer is completed
+void tud_printer_tx_complete_cb(uint8_t itf);
+
+// Invoked when host requests device ID string (IEEE 1284).
+// Application returns pointer to device ID buffer (must remain valid until transfer completes).
+// First 2 bytes of returned buffer must contain big-endian length (including the 2 length bytes).
+const uint8_t *tud_printer_get_device_id_cb(uint8_t itf);
+
+// Invoked when host requests port status.
+uint8_t tud_printer_get_port_status_cb(uint8_t itf);
+
+// Invoked when host requests soft reset.
+void tud_printer_soft_reset_cb(uint8_t itf);
+
+// Invoked when a control request is completed (GET_DEVICE_ID, GET_PORT_STATUS, etc.)
+void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request);
+
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void printerd_init(void);
+bool printerd_deinit(void);
+void printerd_reset(uint8_t rhport);
+uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len);
+bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request);
+bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c
index b8e6fec6f..b917c8dc7 100644
--- a/src/class/vendor/vendor_device.c
+++ b/src/class/vendor/vendor_device.c
@@ -40,36 +40,33 @@ typedef struct {
uint8_t rhport;
uint8_t itf_num;
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
/*------------- From this point, data is not cleared by bus reset -------------*/
- struct {
- tu_edpt_stream_t tx;
- tu_edpt_stream_t rx;
-
- #if CFG_TUD_VENDOR_TX_BUFSIZE > 0
- uint8_t tx_ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE];
- #endif
-
- #if CFG_TUD_VENDOR_RX_BUFSIZE > 0
- uint8_t rx_ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE];
+ tu_edpt_stream_t tx_stream;
+ tu_edpt_stream_t rx_stream;
+ uint8_t tx_ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE];
+ uint8_t rx_ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE];
+ #else
+ uint8_t ep_in;
+ uint8_t ep_out;
+ uint16_t ep_in_mps;
+ uint16_t ep_out_mps;
#endif
- } stream;
} vendord_interface_t;
-#define ITF_MEM_RESET_SIZE (offsetof(vendord_interface_t, itf_num) + sizeof(((vendord_interface_t *)0)->itf_num))
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ #define ITF_MEM_RESET_SIZE (offsetof(vendord_interface_t, itf_num) + TU_FIELD_SIZE(vendord_interface_t, itf_num))
+ #else
+ #define ITF_MEM_RESET_SIZE sizeof(vendord_interface_t)
+ #endif
static vendord_interface_t _vendord_itf[CFG_TUD_VENDOR];
-#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || CFG_TUD_VENDOR_RX_BUFSIZE == 0
+ // Skip local EP buffer if dedicated hw FIFO is supported or no fifo mode
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || !CFG_TUD_VENDOR_TXRX_BUFFERED
typedef struct {
- // Skip local EP buffer if dedicated hw FIFO is supported
- #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || CFG_TUD_VENDOR_RX_BUFSIZE == 0
TUD_EPBUF_DEF(epout, CFG_TUD_VENDOR_EPSIZE);
- #endif
-
- // Skip local EP buffer if dedicated hw FIFO is supported
- #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
TUD_EPBUF_DEF(epin, CFG_TUD_VENDOR_EPSIZE);
- #endif
} vendord_epbuf_t;
CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR];
@@ -78,7 +75,6 @@ CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR];
//--------------------------------------------------------------------+
// Weak stubs: invoked if no strong implementation is available
//--------------------------------------------------------------------+
-
TU_ATTR_WEAK void tud_vendor_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize) {
(void)idx;
(void)buffer;
@@ -93,40 +89,44 @@ TU_ATTR_WEAK void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes) {
//--------------------------------------------------------------------
// Application API
//--------------------------------------------------------------------
-
bool tud_vendor_n_mounted(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return p_itf->stream.rx.ep_addr || p_itf->stream.tx.ep_addr;
+
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ return (p_itf->rx_stream.ep_addr != 0) || (p_itf->tx_stream.ep_addr != 0);
+ #else
+ return (p_itf->ep_out != 0) || (p_itf->ep_in != 0);
+ #endif
}
//--------------------------------------------------------------------+
// Read API
//--------------------------------------------------------------------+
-#if CFG_TUD_VENDOR_RX_BUFSIZE > 0
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
uint32_t tud_vendor_n_available(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return tu_edpt_stream_read_available(&p_itf->stream.rx);
+ return tu_edpt_stream_read_available(&p_itf->rx_stream);
}
bool tud_vendor_n_peek(uint8_t idx, uint8_t *u8) {
TU_VERIFY(idx < CFG_TUD_VENDOR);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return tu_edpt_stream_peek(&p_itf->stream.rx, u8);
+ return tu_edpt_stream_peek(&p_itf->rx_stream, u8);
}
uint32_t tud_vendor_n_read(uint8_t idx, void *buffer, uint32_t bufsize) {
TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return tu_edpt_stream_read(&p_itf->stream.rx, buffer, bufsize);
+ return tu_edpt_stream_read(&p_itf->rx_stream, buffer, bufsize);
}
void tud_vendor_n_read_flush(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR, );
vendord_interface_t *p_itf = &_vendord_itf[idx];
- tu_edpt_stream_clear(&p_itf->stream.rx);
- tu_edpt_stream_read_xfer(&p_itf->stream.rx);
+ tu_edpt_stream_clear(&p_itf->rx_stream);
+ tu_edpt_stream_read_xfer(&p_itf->rx_stream);
}
#endif
@@ -134,9 +134,17 @@ void tud_vendor_n_read_flush(uint8_t idx) {
bool tud_vendor_n_read_xfer(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return tu_edpt_stream_read_xfer(&p_itf->stream.rx);
+
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ return tu_edpt_stream_read_xfer(&p_itf->rx_stream);
+
+ #else
+ // Non-FIFO mode
+ TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_out));
+ return usbd_edpt_xfer(p_itf->rhport, p_itf->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false);
+ #endif
}
-#endif
+ #endif
//--------------------------------------------------------------------+
@@ -145,26 +153,45 @@ bool tud_vendor_n_read_xfer(uint8_t idx) {
uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize) {
TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return tu_edpt_stream_write(&p_itf->stream.tx, buffer, (uint16_t)bufsize);
+
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ return tu_edpt_stream_write(&p_itf->tx_stream, buffer, (uint16_t)bufsize);
+
+ #else
+ // non-fifo mode: direct transfer
+ TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_in), 0);
+ const uint32_t xact_len = tu_min32(bufsize, CFG_TUD_VENDOR_EPSIZE);
+ memcpy(_vendord_epbuf[idx].epin, buffer, xact_len);
+ TU_ASSERT(usbd_edpt_xfer(p_itf->rhport, p_itf->ep_in, _vendord_epbuf[idx].epin, (uint16_t)xact_len, false), 0);
+ return xact_len;
+ #endif
}
-#if CFG_TUD_VENDOR_TX_BUFSIZE > 0
-uint32_t tud_vendor_n_write_flush(uint8_t idx) {
+uint32_t tud_vendor_n_write_available(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return tu_edpt_stream_write_xfer(&p_itf->stream.tx);
+
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ return tu_edpt_stream_write_available(&p_itf->tx_stream);
+
+ #else
+ // Non-FIFO mode
+ TU_VERIFY(p_itf->ep_in > 0, 0); // must be opened
+ return usbd_edpt_busy(p_itf->rhport, p_itf->ep_in) ? 0 : CFG_TUD_VENDOR_EPSIZE;
+ #endif
}
-uint32_t tud_vendor_n_write_available(uint8_t idx) {
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+uint32_t tud_vendor_n_write_flush(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- return tu_edpt_stream_write_available(&p_itf->stream.tx);
+ return tu_edpt_stream_write_xfer(&p_itf->tx_stream);
}
bool tud_vendor_n_write_clear(uint8_t idx) {
TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
vendord_interface_t *p_itf = &_vendord_itf[idx];
- tu_edpt_stream_clear(&p_itf->stream.tx);
+ tu_edpt_stream_clear(&p_itf->tx_stream);
return true;
}
#endif
@@ -175,48 +202,37 @@ bool tud_vendor_n_write_clear(uint8_t idx) {
void vendord_init(void) {
tu_memclr(_vendord_itf, sizeof(_vendord_itf));
- for(uint8_t i=0; i<CFG_TUD_VENDOR; i++) {
- vendord_interface_t* p_itf = &_vendord_itf[i];
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ for (uint8_t i = 0; i < CFG_TUD_VENDOR; i++) {
+ vendord_interface_t *p_itf = &_vendord_itf[i];
- #if CFG_TUD_EDPT_DEDICATED_HWFIFO
- #if CFG_TUD_VENDOR_RX_BUFSIZE == 0 // non-fifo rx still need ep buffer
- uint8_t *epout_buf = _vendord_epbuf[i].epout;
- #else
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
uint8_t *epout_buf = NULL;
- #endif
-
- uint8_t *epin_buf = NULL;
- #else
+ uint8_t *epin_buf = NULL;
+ #else
uint8_t *epout_buf = _vendord_epbuf[i].epout;
uint8_t *epin_buf = _vendord_epbuf[i].epin;
- #endif
-
- #if CFG_TUD_VENDOR_RX_BUFSIZE > 0
- uint8_t *rx_ff_buf = p_itf->stream.rx_ff_buf;
- #else
- uint8_t *rx_ff_buf = NULL;
- #endif
+ #endif
- tu_edpt_stream_init(&p_itf->stream.rx, false, false, false, rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, epout_buf,
+ uint8_t *rx_ff_buf = p_itf->rx_ff_buf;
+ tu_edpt_stream_init(&p_itf->rx_stream, false, false, false, rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, epout_buf,
CFG_TUD_VENDOR_EPSIZE);
- #if CFG_TUD_VENDOR_TX_BUFSIZE > 0
- uint8_t *tx_ff_buf = p_itf->stream.tx_ff_buf;
- #else
- uint8_t *tx_ff_buf = NULL;
- #endif
-
- tu_edpt_stream_init(&p_itf->stream.tx, false, true, false, tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, epin_buf,
+ uint8_t *tx_ff_buf = p_itf->tx_ff_buf;
+ tu_edpt_stream_init(&p_itf->tx_stream, false, true, false, tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, epin_buf,
CFG_TUD_VENDOR_EPSIZE);
}
+ #endif
}
bool vendord_deinit(void) {
- for(uint8_t i=0; i<CFG_TUD_VENDOR; i++) {
- vendord_interface_t* p_itf = &_vendord_itf[i];
- tu_edpt_stream_deinit(&p_itf->stream.rx);
- tu_edpt_stream_deinit(&p_itf->stream.tx);
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ for (uint8_t i = 0; i < CFG_TUD_VENDOR; i++) {
+ vendord_interface_t *p_itf = &_vendord_itf[i];
+ tu_edpt_stream_deinit(&p_itf->rx_stream);
+ tu_edpt_stream_deinit(&p_itf->tx_stream);
}
+ #endif
return true;
}
@@ -227,11 +243,12 @@ void vendord_reset(uint8_t rhport) {
vendord_interface_t* p_itf = &_vendord_itf[i];
tu_memclr(p_itf, ITF_MEM_RESET_SIZE);
- tu_edpt_stream_clear(&p_itf->stream.rx);
- tu_edpt_stream_close(&p_itf->stream.rx);
-
- tu_edpt_stream_clear(&p_itf->stream.tx);
- tu_edpt_stream_close(&p_itf->stream.tx);
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ tu_edpt_stream_clear(&p_itf->rx_stream);
+ tu_edpt_stream_close(&p_itf->rx_stream);
+ tu_edpt_stream_clear(&p_itf->tx_stream);
+ tu_edpt_stream_close(&p_itf->tx_stream);
+ #endif
}
}
@@ -241,13 +258,25 @@ static uint8_t find_vendor_itf(uint8_t ep_addr) {
const vendord_interface_t *p_vendor = &_vendord_itf[idx];
if (ep_addr == 0) {
// find unused: require both ep == 0
- if (p_vendor->stream.rx.ep_addr == 0 && p_vendor->stream.tx.ep_addr == 0) {
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ if (p_vendor->rx_stream.ep_addr == 0 && p_vendor->tx_stream.ep_addr == 0) {
return idx;
}
- } else if (ep_addr == p_vendor->stream.rx.ep_addr || ep_addr == p_vendor->stream.tx.ep_addr) {
- return idx;
+ #else
+ if (p_vendor->ep_out == 0 && p_vendor->ep_in == 0) {
+ return idx;
+ }
+ #endif
} else {
- // nothing to do
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ if (ep_addr == p_vendor->rx_stream.ep_addr || ep_addr == p_vendor->tx_stream.ep_addr) {
+ return idx;
+ }
+ #else
+ if (ep_addr == p_vendor->ep_out || ep_addr == p_vendor->ep_in) {
+ return idx;
+ }
+ #endif
}
}
return 0xff;
@@ -273,28 +302,39 @@ uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uin
const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc;
TU_ASSERT(usbd_edpt_open(rhport, desc_ep));
- // open endpoint stream, skip if already opened (multiple IN/OUT endpoints)
+ #if CFG_TUD_VENDOR_TXRX_BUFFERED
+ // open endpoint stream
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
- tu_edpt_stream_t *stream_tx = &p_vendor->stream.tx;
- if (stream_tx->ep_addr == 0) {
- tu_edpt_stream_open(stream_tx, rhport, desc_ep);
- tu_edpt_stream_write_xfer(stream_tx); // flush pending data
- }
+ tu_edpt_stream_t *tx_stream = &p_vendor->tx_stream;
+ tu_edpt_stream_open(tx_stream, rhport, desc_ep);
+ tu_edpt_stream_write_xfer(tx_stream); // flush pending data
} else {
- tu_edpt_stream_t *stream_rx = &p_vendor->stream.rx;
- if (stream_rx->ep_addr == 0) {
- tu_edpt_stream_open(stream_rx, rhport, desc_ep);
- #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
- TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare for incoming data
- #endif
- }
+ tu_edpt_stream_t *rx_stream = &p_vendor->rx_stream;
+ tu_edpt_stream_open(rx_stream, rhport, desc_ep);
+ #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
+ TU_ASSERT(tu_edpt_stream_read_xfer(rx_stream) > 0, 0); // prepare for incoming data
+ #endif
+ }
+ #else
+ // Non-FIFO mode: store endpoint info
+ if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
+ p_vendor->ep_in = desc_ep->bEndpointAddress;
+ p_vendor->ep_in_mps = tu_edpt_packet_size(desc_ep);
+ } else {
+ p_vendor->ep_out = desc_ep->bEndpointAddress;
+ p_vendor->ep_out_mps = tu_edpt_packet_size(desc_ep);
+ #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
+ // Prepare for incoming data
+ TU_ASSERT(usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false), 0);
+ #endif
}
+ #endif
}
p_desc = tu_desc_next(p_desc);
}
- return (uint16_t) ((uintptr_t) p_desc - (uintptr_t) desc_itf);
+ return (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_itf);
}
bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
@@ -304,34 +344,36 @@ bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
TU_VERIFY(idx < CFG_TUD_VENDOR);
vendord_interface_t *p_vendor = &_vendord_itf[idx];
- if (ep_addr == p_vendor->stream.rx.ep_addr) {
- #if CFG_TUD_VENDOR_RX_BUFSIZE
- // Received new data: put into stream's fifo
- tu_edpt_stream_read_xfer_complete(&p_vendor->stream.rx, xferred_bytes);
- #endif
-
- // invoke callback
- #if CFG_TUD_VENDOR_RX_BUFSIZE == 0
- tud_vendor_rx_cb(idx, p_vendor->stream.rx.ep_buf, xferred_bytes);
- #else
+#if CFG_TUD_VENDOR_TXRX_BUFFERED
+ if (ep_addr == p_vendor->rx_stream.ep_addr) {
+ // Put received data to FIFO
+ tu_edpt_stream_read_xfer_complete(&p_vendor->rx_stream, xferred_bytes);
tud_vendor_rx_cb(idx, NULL, 0);
- #endif
-
- #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
- tu_edpt_stream_read_xfer(&p_vendor->stream.rx); // prepare next data
- #endif
- } else if (ep_addr == p_vendor->stream.tx.ep_addr) {
+ #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
+ tu_edpt_stream_read_xfer(&p_vendor->rx_stream); // prepare next data
+ #endif
+ } else if (ep_addr == p_vendor->tx_stream.ep_addr) {
// Send complete
tud_vendor_tx_cb(idx, (uint16_t)xferred_bytes);
- #if CFG_TUD_VENDOR_TX_BUFSIZE > 0
// try to send more if possible
- if (0 == tu_edpt_stream_write_xfer(&p_vendor->stream.tx)) {
+ if (0 == tu_edpt_stream_write_xfer(&p_vendor->tx_stream)) {
// If there is no data left, a ZLP should be sent if xferred_bytes is multiple of EP Packet size and not zero
- tu_edpt_stream_write_zlp_if_needed(&p_vendor->stream.tx, xferred_bytes);
+ tu_edpt_stream_write_zlp_if_needed(&p_vendor->tx_stream, xferred_bytes);
}
- #endif
}
+ #else
+ if (ep_addr == p_vendor->ep_out) {
+ // Non-FIFO mode: invoke callback with buffer
+ tud_vendor_rx_cb(idx, _vendord_epbuf[idx].epout, xferred_bytes);
+ #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0
+ usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false);
+ #endif
+ } else if (ep_addr == p_vendor->ep_in) {
+ // Send complete
+ tud_vendor_tx_cb(idx, (uint16_t)xferred_bytes);
+ }
+ #endif
return true;
}
diff --git a/src/class/vendor/vendor_device.h b/src/class/vendor/vendor_device.h
index c3de4c49d..101765bb1 100644
--- a/src/class/vendor/vendor_device.h
+++ b/src/class/vendor/vendor_device.h
@@ -50,8 +50,14 @@ extern "C" {
#define CFG_TUD_VENDOR_TX_BUFSIZE 64
#endif
+// Vendor is buffered (FIFO mode) if both TX and RX buffers are configured
+// If either is 0, vendor operates in non-buffered (direct transfer) mode
+#ifndef CFG_TUD_VENDOR_TXRX_BUFFERED
+ #define CFG_TUD_VENDOR_TXRX_BUFFERED ((CFG_TUD_VENDOR_RX_BUFSIZE > 0) && (CFG_TUD_VENDOR_TX_BUFSIZE > 0))
+#endif
+
// Application will manually schedule RX transfer. This can be useful when using with non-fifo (buffered) mode
-// i.e. CFG_TUD_VENDOR_RX_BUFSIZE = 0
+// i.e. CFG_TUD_VENDOR_TXRX_BUFFERED = 0
#ifndef CFG_TUD_VENDOR_RX_MANUAL_XFER
#define CFG_TUD_VENDOR_RX_MANUAL_XFER 0
#endif
@@ -63,7 +69,8 @@ extern "C" {
// Return whether the vendor interface is mounted
bool tud_vendor_n_mounted(uint8_t idx);
-#if CFG_TUD_VENDOR_RX_BUFSIZE > 0
+//------------- RX -------------//
+#if CFG_TUD_VENDOR_TXRX_BUFFERED
// Return number of available bytes for reading
uint32_t tud_vendor_n_available(uint8_t idx);
@@ -82,16 +89,17 @@ void tud_vendor_n_read_flush(uint8_t idx);
bool tud_vendor_n_read_xfer(uint8_t idx);
#endif
+//------------- TX -------------//
// Write to TX FIFO. This can be buffered and not sent immediately unless buffered bytes >= USB endpoint size
uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize);
-#if CFG_TUD_VENDOR_TX_BUFSIZE > 0
+// Return number of bytes available for writing in TX FIFO (or endpoint if non-buffered)
+uint32_t tud_vendor_n_write_available(uint8_t idx);
+
+#if CFG_TUD_VENDOR_TXRX_BUFFERED
// Force sending buffered data, return number of bytes sent
uint32_t tud_vendor_n_write_flush(uint8_t idx);
-// Return number of bytes available for writing in TX FIFO
-uint32_t tud_vendor_n_write_available(uint8_t idx);
-
// Clear the transmit FIFO
bool tud_vendor_n_write_clear(uint8_t idx);
#endif
@@ -111,7 +119,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_mounted(void) {
return tud_vendor_n_mounted(0);
}
-#if CFG_TUD_VENDOR_RX_BUFSIZE > 0
+#if CFG_TUD_VENDOR_TXRX_BUFFERED
TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_available(void) {
return tud_vendor_n_available(0);
}
@@ -127,6 +135,14 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_read(void *buffer, uint3
TU_ATTR_ALWAYS_INLINE static inline void tud_vendor_read_flush(void) {
tud_vendor_n_read_flush(0);
}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_flush(void) {
+ return tud_vendor_n_write_flush(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_write_clear(void) {
+ return tud_vendor_n_write_clear(0);
+}
#endif
#if CFG_TUD_VENDOR_RX_MANUAL_XFER
@@ -143,20 +159,10 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_str(const char *st
return tud_vendor_n_write_str(0, str);
}
-#if CFG_TUD_VENDOR_TX_BUFSIZE > 0
-TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_flush(void) {
- return tud_vendor_n_write_flush(0);
-}
-
TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_available(void) {
return tud_vendor_n_write_available(0);
}
-TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_write_clear(void) {
- return tud_vendor_n_write_clear(0);
-}
-#endif
-
// backward compatible
#define tud_vendor_flush() tud_vendor_write_flush()
@@ -165,8 +171,8 @@ TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_write_clear(void) {
//--------------------------------------------------------------------+
// Invoked when received new data.
-// - CFG_TUD_VENDOR_RX_BUFSIZE > 0; buffer and bufsize must not be used (both NULL,0) since data is in RX FIFO
-// - CFG_TUD_VENDOR_RX_BUFSIZE = 0: Buffer and bufsize are valid
+// - CFG_TUD_VENDOR_TXRX_BUFFERED = 1: buffer and bufsize must not be used (both NULL,0) since data is in RX FIFO
+// - CFG_TUD_VENDOR_TXRX_BUFFERED = 0: Buffer and bufsize are valid
void tud_vendor_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize);
// Invoked when tx transfer is finished
diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c
index b2375def9..bbcfe45d5 100644
--- a/src/class/video/video_device.c
+++ b/src/class/video/video_device.c
@@ -70,6 +70,13 @@ typedef struct TU_ATTR_PACKED {
uint8_t bEntityId;
} tusb_desc_cs_video_entity_itf_t;
+typedef struct TU_ATTR_PACKED {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint16_t wMaxTransferSize;
+} tusb_desc_cs_video_vc_ep_t;
+
typedef union {
struct TU_ATTR_PACKED {
uint8_t bLength;
@@ -740,6 +747,9 @@ static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self)
/* The end of the video control interface descriptor. */
void const *end = _end_of_control_descriptor(vc);
if (vc->std.bNumEndpoints != 0) {
+ /* Extend end to cover the standard endpoint and class-specific endpoint descriptors
+ * that follow wTotalLength */
+ end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t);
/* Find the notification endpoint descriptor. */
cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT);
TU_ASSERT(cur < end);
@@ -780,6 +790,9 @@ static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, uint_fast8_t
if (vc->std.bNumEndpoints != 0) {
/* Support for 1 endpoint only. */
TU_VERIFY(1 == vc->std.bNumEndpoints);
+ /* Extend end to cover the standard endpoint and class-specific endpoint descriptors
+ * that follow wTotalLength */
+ end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t);
/* Find the notification endpoint descriptor. */
cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT);
TU_VERIFY(cur < end);
diff --git a/src/class/video/video_device.h b/src/class/video/video_device.h
index f14555e4f..2750bb2fb 100644
--- a/src/class/video/video_device.h
+++ b/src/class/video/video_device.h
@@ -99,8 +99,7 @@ int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx,
* @param[in] stm_idx Destination streaming interface index
* @param[out] payload_buf Payload storage buffer (target buffer for requested data)
* @param[in] payload_size Size of payload_buf (requested data size)
- * @param[in] offset Current byte offset relative to given bufsize from tud_video_n_frame_xfer (framesize)
- * @return video_error_code_t */
+ * @param[in] offset Current byte offset relative to given bufsize from tud_video_n_frame_xfer (framesize) */
void tud_video_prepare_payload_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, tud_video_payload_request_t* request);
//--------------------------------------------------------------------+