Преглед изворни кода

Merge pull request #1809 from hathach/host-cdc

Support Host CDC
Ha Thach пре 3 година
родитељ
комит
2777df411f

+ 1 - 0
examples/host/cdc_msc_hid/CMakeLists.txt

@@ -14,6 +14,7 @@ add_executable(${PROJECT})
 
 # Example source
 target_sources(${PROJECT} PUBLIC
+        ${CMAKE_CURRENT_SOURCE_DIR}/src/cdc_app.c
         ${CMAKE_CURRENT_SOURCE_DIR}/src/hid_app.c
         ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
         ${CMAKE_CURRENT_SOURCE_DIR}/src/msc_app.c

+ 2 - 1
examples/host/cdc_msc_hid/Makefile

@@ -7,7 +7,8 @@ INC += \
 
 # Example source
 EXAMPLE_SOURCE = \
-	src/hid_app.c \
+  src/cdc_app.c \
+  src/hid_app.c \
   src/main.c \
   src/msc_app.c \
 

+ 113 - 0
examples/host/cdc_msc_hid/src/cdc_app.c

@@ -0,0 +1,113 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2022, 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.h"
+#include "bsp/board.h"
+
+//--------------------------------------------------------------------+
+// MACRO TYPEDEF CONSTANT ENUM DECLARATION
+//--------------------------------------------------------------------+
+
+
+//------------- IMPLEMENTATION -------------//
+
+size_t get_console_inputs(uint8_t* buf, size_t bufsize)
+{
+  size_t count = 0;
+  while (count < bufsize)
+  {
+    int ch = board_getchar();
+    if ( ch <= 0 ) break;
+
+    buf[count] = (uint8_t) ch;
+    count++;
+  }
+
+  return count;
+}
+
+void cdc_app_task(void)
+{
+  uint8_t buf[64+1]; // +1 for extra null character
+  uint32_t const bufsize = sizeof(buf)-1;
+
+  uint32_t count = get_console_inputs(buf, bufsize);
+  buf[count] = 0;
+
+  // loop over all mounted interfaces
+  for(uint8_t idx=0; idx<CFG_TUH_CDC; idx++)
+  {
+    if ( tuh_cdc_mounted(idx) )
+    {
+      // console --> cdc interfaces
+      if (count)
+      {
+        tuh_cdc_write(idx, buf, count);
+        tuh_cdc_write_flush(idx);
+      }
+    }
+  }
+}
+
+// Invoked when received new data
+void tuh_cdc_rx_cb(uint8_t idx)
+{
+  uint8_t buf[64+1]; // +1 for extra null character
+  uint32_t const bufsize = sizeof(buf)-1;
+
+  // forward cdc interfaces -> console
+  uint32_t count = tuh_cdc_read(idx, buf, bufsize);
+  buf[count] = 0;
+
+  printf((char*) buf);
+}
+
+void tuh_cdc_mount_cb(uint8_t idx)
+{
+  tuh_cdc_itf_info_t itf_info = { 0 };
+  tuh_cdc_itf_get_info(idx, &itf_info);
+
+  printf("CDC Interface is mounted: address = %u, itf_num = %u\r\n", itf_info.daddr, itf_info.bInterfaceNumber);
+
+#ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM
+  // CFG_TUH_CDC_LINE_CODING_ON_ENUM must be defined for line coding is set by tinyusb in enumeration
+  // otherwise you need to call tuh_cdc_set_line_coding() first
+  cdc_line_coding_t line_coding = { 0 };
+  if ( tuh_cdc_get_local_line_coding(idx, &line_coding) )
+  {
+    printf("  Baudrate: %lu, Stop Bits : %u\r\n", line_coding.bit_rate, line_coding.stop_bits);
+    printf("  Parity  : %u, Data Width: %u\r\n", line_coding.parity  , line_coding.data_bits);
+  }
+#endif
+}
+
+void tuh_cdc_umount_cb(uint8_t idx)
+{
+  tuh_cdc_itf_info_t itf_info = { 0 };
+  tuh_cdc_itf_get_info(idx, &itf_info);
+
+  printf("CDC Interface is unmounted: address = %u, itf_num = %u\r\n", itf_info.daddr, itf_info.bInterfaceNumber);
+}

+ 3 - 26
examples/host/cdc_msc_hid/src/main.c

@@ -35,7 +35,7 @@
 //--------------------------------------------------------------------+
 void led_blinking_task(void);
 
-extern void cdc_task(void);
+extern void cdc_app_task(void);
 extern void hid_app_task(void);
 
 /*------------- MAIN -------------*/
@@ -52,38 +52,15 @@ int main(void)
   {
     // tinyusb host task
     tuh_task();
-    led_blinking_task();
 
-    cdc_task();
+    led_blinking_task();
+    cdc_app_task();
     hid_app_task();
   }
 
   return 0;
 }
 
-//--------------------------------------------------------------------+
-// USB CDC
-//--------------------------------------------------------------------+
-CFG_TUSB_MEM_SECTION static char serial_in_buffer[64] = { 0 };
-
-// invoked ISR context
-void tuh_cdc_xfer_isr(uint8_t dev_addr, xfer_result_t event, cdc_pipeid_t pipe_id, uint32_t xferred_bytes)
-{
-  (void) event;
-  (void) pipe_id;
-  (void) xferred_bytes;
-
-  printf(serial_in_buffer);
-  tu_memclr(serial_in_buffer, sizeof(serial_in_buffer));
-
-  tuh_cdc_receive(dev_addr, serial_in_buffer, sizeof(serial_in_buffer), true); // waiting for next data
-}
-
-void cdc_task(void)
-{
-
-}
-
 //--------------------------------------------------------------------+
 // TinyUSB Callbacks
 //--------------------------------------------------------------------+

+ 11 - 0
examples/host/cdc_msc_hid/src/tusb_config.h

@@ -108,6 +108,17 @@
 #define CFG_TUH_HID_EPIN_BUFSIZE    64
 #define CFG_TUH_HID_EPOUT_BUFSIZE   64
 
+//------------- CDC -------------//
+
+// Set Line Control state on enumeration/mounted:
+// DTR ( bit 0), RTS (bit 1)
+#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM    0x03
+
+// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t
+// bit rate = 115200, 1 stop bit, no parity, 8 bit data width
+#define CFG_TUH_CDC_LINE_CODING_ON_ENUM   { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 }
+
+
 #ifdef __cplusplus
  }
 #endif

+ 24 - 12
src/class/cdc/cdc.h

@@ -41,16 +41,6 @@
 /** \defgroup ClassDriver_CDC_Common Common Definitions
  *  @{ */
 
-// TODO remove
-/// CDC Pipe ID, used to indicate which pipe the API is addressing to (Notification, Out, In)
-typedef enum
-{
-  CDC_PIPE_NOTIFICATION , ///< Notification pipe
-  CDC_PIPE_DATA_IN      , ///< Data in pipe
-  CDC_PIPE_DATA_OUT     , ///< Data out pipe
-  CDC_PIPE_ERROR        , ///< Invalid Pipe ID
-}cdc_pipeid_t;
-
 //--------------------------------------------------------------------+
 // CDC Communication Interface Class
 //--------------------------------------------------------------------+
@@ -192,6 +182,28 @@ typedef enum
   CDC_REQUEST_MDLM_SEMANTIC_MODEL                          = 0x60,
 }cdc_management_request_t;
 
+enum
+{
+  CDC_CONTROL_LINE_STATE_DTR = 0x01,
+  CDC_CONTROL_LINE_STATE_RTS = 0x02,
+};
+
+enum
+{
+  CDC_LINE_CONDING_STOP_BITS_1   = 0, // 1   bit
+  CDC_LINE_CONDING_STOP_BITS_1_5 = 1, // 1.5 bits
+  CDC_LINE_CONDING_STOP_BITS_2   = 2, // 2   bits
+};
+
+enum
+{
+  CDC_LINE_CODING_PARITY_NONE  = 0,
+  CDC_LINE_CODING_PARITY_ODD   = 1,
+  CDC_LINE_CODING_PARITY_EVEN  = 2,
+  CDC_LINE_CODING_PARITY_MARK  = 3,
+  CDC_LINE_CODING_PARITY_SPACE = 4,
+};
+
 //--------------------------------------------------------------------+
 // Management Element Notification (Notification Endpoint)
 //--------------------------------------------------------------------+
@@ -390,8 +402,8 @@ TU_VERIFY_STATIC(sizeof(cdc_line_coding_t) == 7, "size is not correct");
 
 typedef struct TU_ATTR_PACKED
 {
-  uint16_t dte_is_present : 1; ///< Indicates to DCE if DTE is presentor not. This signal corresponds to V.24 signal 108/2 and RS-232 signal DTR.
-  uint16_t half_duplex_carrier_control : 1;
+  uint16_t dtr : 1;
+  uint16_t rts : 1;
   uint16_t : 14;
 } cdc_line_control_state_t;
 

+ 0 - 1
src/class/cdc/cdc_device.h

@@ -27,7 +27,6 @@
 #ifndef _TUSB_CDC_DEVICE_H_
 #define _TUSB_CDC_DEVICE_H_
 
-#include "common/tusb_common.h"
 #include "cdc.h"
 
 //--------------------------------------------------------------------+

+ 406 - 84
src/class/cdc/cdc_host.c

@@ -33,96 +33,252 @@
 
 #include "cdc_host.h"
 
+
+// Debug level, TUSB_CFG_DEBUG must be at least this level for debug message
+#define CDCH_DEBUG   2
+
+#define TU_LOG_CDCH(...)   TU_LOG(CDCH_DEBUG, __VA_ARGS__)
+
 //--------------------------------------------------------------------+
 // MACRO CONSTANT TYPEDEF
 //--------------------------------------------------------------------+
+
 typedef struct {
-  uint8_t itf_num;
-  uint8_t itf_protocol;
+  uint8_t daddr;
+  uint8_t bInterfaceNumber;
+  uint8_t bInterfaceSubClass;
+  uint8_t bInterfaceProtocol;
 
+  cdc_acm_capability_t acm_capability;
   uint8_t ep_notif;
-  uint8_t ep_in;
-  uint8_t ep_out;
 
-  cdc_acm_capability_t acm_capability;
+  cdc_line_coding_t line_coding;  // Baudrate, stop bits, parity, data width
+  uint8_t line_state;             // DTR (bit0), RTS (bit1)
+
+  tuh_xfer_cb_t user_control_cb;
+
+  struct {
+    tu_edpt_stream_t tx;
+    tu_edpt_stream_t rx;
+
+    uint8_t tx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE];
+    CFG_TUSB_MEM_ALIGN uint8_t tx_ep_buf[CFG_TUH_CDC_TX_EPSIZE];
+
+    uint8_t rx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE];
+    CFG_TUSB_MEM_ALIGN uint8_t rx_ep_buf[CFG_TUH_CDC_TX_EPSIZE];
+  } stream;
 
-} cdch_data_t;
+} cdch_interface_t;
 
 //--------------------------------------------------------------------+
 // INTERNAL OBJECT & FUNCTION DECLARATION
 //--------------------------------------------------------------------+
-static cdch_data_t cdch_data[CFG_TUH_DEVICE_MAX];
 
-static inline cdch_data_t* get_itf(uint8_t dev_addr)
+CFG_TUSB_MEM_SECTION
+static cdch_interface_t cdch_data[CFG_TUH_CDC];
+
+static inline cdch_interface_t* get_itf(uint8_t idx)
 {
-  return &cdch_data[dev_addr-1];
+  TU_ASSERT(idx < CFG_TUH_CDC, NULL);
+  cdch_interface_t* p_cdc = &cdch_data[idx];
+
+  return (p_cdc->daddr != 0) ? p_cdc : NULL;
 }
 
-bool tuh_cdc_mounted(uint8_t dev_addr)
+static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr)
 {
-  cdch_data_t* cdc = get_itf(dev_addr);
-  return cdc->ep_in && cdc->ep_out;
+  for(uint8_t i=0; i<CFG_TUH_CDC; i++)
+  {
+    cdch_interface_t* p_cdc = &cdch_data[i];
+    if ( (p_cdc->daddr == daddr) &&
+         (ep_addr == p_cdc->ep_notif || ep_addr == p_cdc->stream.rx.ep_addr || ep_addr == p_cdc->stream.tx.ep_addr))
+    {
+      return i;
+    }
+  }
+
+  return TUSB_INDEX_INVALID;
 }
 
-bool tuh_cdc_is_busy(uint8_t dev_addr, cdc_pipeid_t pipeid)
+
+static cdch_interface_t* find_new_itf(void)
 {
-  if ( !tuh_cdc_mounted(dev_addr) ) return false;
+  for(uint8_t i=0; i<CFG_TUH_CDC; i++)
+  {
+    if (cdch_data[i].daddr == 0) return &cdch_data[i];
+  }
 
-  cdch_data_t const * p_cdc = get_itf(dev_addr);
+  return NULL;
+}
 
-  switch (pipeid)
+//--------------------------------------------------------------------+
+// APPLICATION API
+//--------------------------------------------------------------------+
+
+uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num)
+{
+  for(uint8_t i=0; i<CFG_TUH_CDC; i++)
   {
-    case CDC_PIPE_NOTIFICATION:
-      return usbh_edpt_busy(dev_addr, p_cdc->ep_notif );
+    const cdch_interface_t* p_cdc = &cdch_data[i];
 
-    case CDC_PIPE_DATA_IN:
-      return usbh_edpt_busy(dev_addr, p_cdc->ep_in );
+    if (p_cdc->daddr == daddr && p_cdc->bInterfaceNumber == itf_num) return i;
+  }
 
-    case CDC_PIPE_DATA_OUT:
-      return usbh_edpt_busy(dev_addr, p_cdc->ep_out );
+  return TUSB_INDEX_INVALID;
+}
 
-    default:
-      return false;
-  }
+bool tuh_cdc_itf_get_info(uint8_t idx, tuh_cdc_itf_info_t* info)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc && info);
+
+  info->daddr              = p_cdc->daddr;
+  info->bInterfaceNumber   = p_cdc->bInterfaceNumber;
+  info->bInterfaceSubClass = p_cdc->bInterfaceSubClass;
+  info->bInterfaceProtocol = p_cdc->bInterfaceProtocol;
+
+  return true;
+}
+
+bool tuh_cdc_mounted(uint8_t idx)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  return p_cdc != NULL;
+}
+
+bool tuh_cdc_get_dtr(uint8_t idx)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_DTR) ? true : false;
+}
+
+bool tuh_cdc_get_rts(uint8_t idx)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_RTS) ? true : false;
+}
+
+bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  *line_coding = p_cdc->line_coding;
+
+  return true;
 }
 
 //--------------------------------------------------------------------+
-// APPLICATION API (parameter validation needed)
+// Write
 //--------------------------------------------------------------------+
-bool tuh_cdc_serial_is_mounted(uint8_t dev_addr)
+
+uint32_t tuh_cdc_write(uint8_t idx, void const* buffer, uint32_t bufsize)
 {
-  // TODO consider all AT Command as serial candidate
-  return tuh_cdc_mounted(dev_addr)                                         &&
-      (cdch_data[dev_addr-1].itf_protocol <= CDC_COMM_PROTOCOL_ATCOMMAND_CDMA);
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  return tu_edpt_stream_write(&p_cdc->stream.tx, buffer, bufsize);
 }
 
-bool tuh_cdc_send(uint8_t dev_addr, void const * p_data, uint32_t length, bool is_notify)
+uint32_t tuh_cdc_write_flush(uint8_t idx)
 {
-  (void) is_notify;
-  TU_VERIFY( tuh_cdc_mounted(dev_addr) );
-  TU_VERIFY( p_data != NULL && length);
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
 
-  uint8_t const ep_out = cdch_data[dev_addr-1].ep_out;
-  if ( usbh_edpt_busy(dev_addr, ep_out) ) return false;
+  return tu_edpt_stream_write_xfer(&p_cdc->stream.tx);
+}
 
-  return usbh_edpt_xfer(dev_addr, ep_out, (void*)(uintptr_t) p_data, (uint16_t) length);
+bool tuh_cdc_write_clear(uint8_t idx)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  return tu_edpt_stream_clear(&p_cdc->stream.tx);
 }
 
-bool tuh_cdc_receive(uint8_t dev_addr, void * p_buffer, uint32_t length, bool is_notify)
+uint32_t tuh_cdc_write_available(uint8_t idx)
 {
-  (void) is_notify;
-  TU_VERIFY( tuh_cdc_mounted(dev_addr) );
-  TU_VERIFY( p_buffer != NULL && length );
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  return tu_edpt_stream_write_available(&p_cdc->stream.tx);
+}
+
+//--------------------------------------------------------------------+
+// Read
+//--------------------------------------------------------------------+
+
+uint32_t tuh_cdc_read (uint8_t idx, void* buffer, uint32_t bufsize)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  return tu_edpt_stream_read(&p_cdc->stream.rx, buffer, bufsize);
+}
+
+uint32_t tuh_cdc_read_available(uint8_t idx)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  return tu_edpt_stream_read_available(&p_cdc->stream.rx);
+}
+
+bool tuh_cdc_read_clear (uint8_t idx)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc);
+
+  bool ret = tu_edpt_stream_clear(&p_cdc->stream.rx);
+  tu_edpt_stream_read_xfer(&p_cdc->stream.rx);
+  return ret;
+}
+
+//--------------------------------------------------------------------+
+// Control Endpoint API
+//--------------------------------------------------------------------+
+
+// internal control complete to update state such as line state, encoding
+static void cdch_internal_control_complete(tuh_xfer_t* xfer)
+{
+  uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex);
+  uint8_t idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num);
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_ASSERT(p_cdc, );
+
+  if (xfer->result == XFER_RESULT_SUCCESS)
+  {
+    switch(xfer->setup->bRequest)
+    {
+      case CDC_REQUEST_SET_CONTROL_LINE_STATE:
+        p_cdc->line_state = (uint8_t) tu_le16toh(xfer->setup->wValue);
+      break;
 
-  uint8_t const ep_in = cdch_data[dev_addr-1].ep_in;
-  if ( usbh_edpt_busy(dev_addr, ep_in) ) return false;
+      case CDC_REQUEST_SET_LINE_CODING:
+      {
+        uint16_t const len = tu_min16(sizeof(cdc_line_coding_t), tu_le16toh(xfer->setup->wLength));
+        memcpy(&p_cdc->line_coding, xfer->buffer, len);
+      }
+      break;
+
+      default: break;
+    }
+  }
 
-  return usbh_edpt_xfer(dev_addr, ep_in, p_buffer, (uint16_t) length);
+  xfer->complete_cb = p_cdc->user_control_cb;
+  xfer->complete_cb(xfer);
 }
 
-bool tuh_cdc_set_control_line_state(uint8_t dev_addr, bool dtr, bool rts, tuh_xfer_cb_t complete_cb)
+bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data)
 {
-  cdch_data_t const * p_cdc = get_itf(dev_addr);
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc && p_cdc->acm_capability.support_line_request);
+
+  TU_LOG_CDCH("CDC Set Control Line State\r\n");
 
   tusb_control_request_t const request =
   {
@@ -133,36 +289,154 @@ bool tuh_cdc_set_control_line_state(uint8_t dev_addr, bool dtr, bool rts, tuh_xf
       .direction = TUSB_DIR_OUT
     },
     .bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE,
-    .wValue   = tu_htole16((uint16_t) ((dtr ? 1u : 0u) | (rts ? 2u : 0u))),
-    .wIndex   = tu_htole16(p_cdc->itf_num),
+    .wValue   = tu_htole16(line_state),
+    .wIndex   = tu_htole16((uint16_t) p_cdc->bInterfaceNumber),
     .wLength  = 0
   };
 
+  p_cdc->user_control_cb = complete_cb;
   tuh_xfer_t xfer =
   {
-    .daddr       = dev_addr,
+    .daddr       = p_cdc->daddr,
     .ep_addr     = 0,
     .setup       = &request,
     .buffer      = NULL,
-    .complete_cb = complete_cb,
-    .user_data    = 0
+    .complete_cb = cdch_internal_control_complete,
+    .user_data   = user_data
+  };
+
+  return tuh_control_xfer(&xfer);
+}
+
+bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data)
+{
+  cdch_interface_t* p_cdc = get_itf(idx);
+  TU_VERIFY(p_cdc && p_cdc->acm_capability.support_line_request);
+
+  TU_LOG_CDCH("CDC Set Line Conding\r\n");
+
+  tusb_control_request_t const request =
+  {
+    .bmRequestType_bit =
+    {
+      .recipient = TUSB_REQ_RCPT_INTERFACE,
+      .type      = TUSB_REQ_TYPE_CLASS,
+      .direction = TUSB_DIR_OUT
+    },
+    .bRequest = CDC_REQUEST_SET_LINE_CODING,
+    .wValue   = 0,
+    .wIndex   = tu_htole16(p_cdc->bInterfaceNumber),
+    .wLength  = tu_htole16(sizeof(cdc_line_coding_t))
+  };
+
+  // use usbh enum buf to hold line coding since user line_coding variable may not live long enough
+  // for the transfer to complete
+  uint8_t* enum_buf = usbh_get_enum_buf();
+  memcpy(enum_buf, line_coding, sizeof(cdc_line_coding_t));
+
+  p_cdc->user_control_cb = complete_cb;
+  tuh_xfer_t xfer =
+  {
+    .daddr       = p_cdc->daddr,
+    .ep_addr     = 0,
+    .setup       = &request,
+    .buffer      = enum_buf,
+    .complete_cb = cdch_internal_control_complete,
+    .user_data   = user_data
   };
 
   return tuh_control_xfer(&xfer);
 }
 
 //--------------------------------------------------------------------+
-// USBH-CLASS DRIVER API
+// CLASS-USBH API
 //--------------------------------------------------------------------+
+
 void cdch_init(void)
 {
   tu_memclr(cdch_data, sizeof(cdch_data));
+
+  for(size_t i=0; i<CFG_TUH_CDC; i++)
+  {
+    cdch_interface_t* p_cdc = &cdch_data[i];
+
+    tu_edpt_stream_init(&p_cdc->stream.tx, true, true, false,
+                          p_cdc->stream.tx_ff_buf, CFG_TUH_CDC_TX_BUFSIZE,
+                          p_cdc->stream.tx_ep_buf, CFG_TUH_CDC_TX_EPSIZE);
+
+    tu_edpt_stream_init(&p_cdc->stream.rx, true, false, false,
+                          p_cdc->stream.rx_ff_buf, CFG_TUH_CDC_RX_BUFSIZE,
+                          p_cdc->stream.rx_ep_buf, CFG_TUH_CDC_RX_EPSIZE);
+  }
 }
 
-bool cdch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len)
+void cdch_close(uint8_t daddr)
+{
+  for(uint8_t idx=0; idx<CFG_TUH_CDC; idx++)
+  {
+    cdch_interface_t* p_cdc = &cdch_data[idx];
+    if (p_cdc->daddr == daddr)
+    {
+      // Invoke application callback
+      if (tuh_cdc_umount_cb) tuh_cdc_umount_cb(idx);
+
+      //tu_memclr(p_cdc, sizeof(cdch_interface_t));
+      p_cdc->daddr = 0;
+      p_cdc->bInterfaceNumber = 0;
+      tu_edpt_stream_close(&p_cdc->stream.tx);
+      tu_edpt_stream_close(&p_cdc->stream.rx);
+    }
+  }
+}
+
+bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
+{
+  // TODO handle stall response, retry failed transfer ...
+  TU_ASSERT(event == XFER_RESULT_SUCCESS);
+
+  uint8_t const idx = get_idx_by_ep_addr(daddr, ep_addr);
+  cdch_interface_t * p_cdc = get_itf(idx);
+  TU_ASSERT(p_cdc);
+
+  if ( ep_addr == p_cdc->stream.tx.ep_addr )
+  {
+    // invoke tx complete callback to possibly refill tx fifo
+    if (tuh_cdc_tx_complete_cb) tuh_cdc_tx_complete_cb(idx);
+
+    if ( 0 == tu_edpt_stream_write_xfer(&p_cdc->stream.tx) )
+    {
+      // 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_cdc->stream.tx, xferred_bytes);
+    }
+  }
+  else if ( ep_addr == p_cdc->stream.rx.ep_addr )
+  {
+    tu_edpt_stream_read_xfer_complete(&p_cdc->stream.rx, xferred_bytes);
+
+    // invoke receive callback
+    if (tuh_cdc_rx_cb)  tuh_cdc_rx_cb(idx);
+
+    // prepare for next transfer if needed
+    tu_edpt_stream_read_xfer(&p_cdc->stream.rx);
+  }else if ( ep_addr == p_cdc->ep_notif )
+  {
+    // TODO handle notification endpoint
+  }else
+  {
+    TU_ASSERT(false);
+  }
+
+  return true;
+}
+
+//--------------------------------------------------------------------+
+// Enumeration
+//--------------------------------------------------------------------+
+
+bool cdch_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len)
 {
   (void) rhport;
-  (void) max_len;
 
   // Only support ACM subclass
   // Protocol 0xFF can be RNDIS device for windows XP
@@ -170,17 +444,22 @@ bool cdch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *it
              CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass &&
              0xFF                                     != itf_desc->bInterfaceProtocol);
 
-  cdch_data_t * p_cdc = get_itf(dev_addr);
+  uint8_t const * p_desc_end = ((uint8_t const*) itf_desc) + max_len;
 
-  p_cdc->itf_num      = itf_desc->bInterfaceNumber;
-  p_cdc->itf_protocol = itf_desc->bInterfaceProtocol;
+  cdch_interface_t * p_cdc = find_new_itf();
+  TU_VERIFY(p_cdc);
 
-  //------------- Communication Interface -------------//
-  uint16_t drv_len = tu_desc_len(itf_desc);
+  p_cdc->daddr              = daddr;
+  p_cdc->bInterfaceNumber   = itf_desc->bInterfaceNumber;
+  p_cdc->bInterfaceSubClass = itf_desc->bInterfaceSubClass;
+  p_cdc->bInterfaceProtocol = itf_desc->bInterfaceProtocol;
+  p_cdc->line_state         = 0;
+
+  //------------- Control Interface -------------//
   uint8_t const * p_desc = tu_desc_next(itf_desc);
 
   // Communication Functional Descriptors
-  while( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len )
+  while( (p_desc < p_desc_end) && (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) )
   {
     if ( CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc) )
     {
@@ -188,19 +467,18 @@ bool cdch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *it
       p_cdc->acm_capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities;
     }
 
-    drv_len += tu_desc_len(p_desc);
     p_desc = tu_desc_next(p_desc);
   }
 
-  if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
+  // Open notification endpoint of control interface if any
+  if (itf_desc->bNumEndpoints == 1)
   {
-    // notification endpoint
+    TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc));
     tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc;
 
-    TU_ASSERT( tuh_edpt_open(dev_addr, desc_ep) );
+    TU_ASSERT( tuh_edpt_open(daddr, desc_ep) );
     p_cdc->ep_notif = desc_ep->bEndpointAddress;
 
-    drv_len += tu_desc_len(p_desc);
     p_desc = tu_desc_next(p_desc);
   }
 
@@ -209,52 +487,96 @@ bool cdch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *it
        (TUSB_CLASS_CDC_DATA == ((tusb_desc_interface_t const *) p_desc)->bInterfaceClass) )
   {
     // next to endpoint descriptor
-    drv_len += tu_desc_len(p_desc);
     p_desc = tu_desc_next(p_desc);
 
     // data endpoints expected to be in pairs
     for(uint32_t i=0; i<2; i++)
     {
       tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc;
-      TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer);
+      TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType &&
+                TUSB_XFER_BULK     == desc_ep->bmAttributes.xfer);
 
-      TU_ASSERT(tuh_edpt_open(dev_addr, desc_ep));
+      TU_ASSERT(tuh_edpt_open(daddr, desc_ep));
 
       if ( tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN )
       {
-        p_cdc->ep_in = desc_ep->bEndpointAddress;
+        tu_edpt_stream_open(&p_cdc->stream.rx, daddr, desc_ep);
       }else
       {
-        p_cdc->ep_out = desc_ep->bEndpointAddress;
+        tu_edpt_stream_open(&p_cdc->stream.tx, daddr, desc_ep);
       }
 
-      drv_len += tu_desc_len(p_desc);
-      p_desc = tu_desc_next( p_desc );
+      p_desc = tu_desc_next(p_desc);
     }
   }
 
   return true;
 }
 
-bool cdch_set_config(uint8_t dev_addr, uint8_t itf_num)
+enum
 {
-  (void) dev_addr; (void) itf_num;
-  return true;
-}
+  CONFIG_SET_CONTROL_LINE_STATE,
+  CONFIG_SET_LINE_CODING,
+  CONFIG_COMPLETE
+};
 
-bool cdch_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
+static void process_cdc_config(tuh_xfer_t* xfer)
 {
-  (void) ep_addr;
-  tuh_cdc_xfer_isr( dev_addr, event, 0, xferred_bytes );
-  return true;
+  uintptr_t const state = xfer->user_data;
+  uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex);
+  uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num);
+  TU_ASSERT(idx != TUSB_INDEX_INVALID, );
+
+  switch(state)
+  {
+    case CONFIG_SET_CONTROL_LINE_STATE:
+    #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM
+      TU_ASSERT( tuh_cdc_set_control_line_state(idx, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, process_cdc_config, CONFIG_SET_LINE_CODING), );
+      break;
+    #endif
+    TU_ATTR_FALLTHROUGH;
+
+    case CONFIG_SET_LINE_CODING:
+    #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM
+    {
+      cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM;
+      TU_ASSERT( tuh_cdc_set_line_coding(idx, &line_coding, process_cdc_config, CONFIG_COMPLETE), );
+      break;
+    }
+    #endif
+    TU_ATTR_FALLTHROUGH;
+
+    case CONFIG_COMPLETE:
+      if (tuh_cdc_mount_cb) tuh_cdc_mount_cb(idx);
+
+      // Prepare for incoming data
+      cdch_interface_t* p_cdc = get_itf(idx);
+      tu_edpt_stream_read_xfer(&p_cdc->stream.rx);
+
+      // notify usbh that driver enumeration is complete
+      // itf_num+1 to account for data interface as well
+      usbh_driver_set_config_complete(xfer->daddr, itf_num+1);
+    break;
+
+    default: break;
+  }
 }
 
-void cdch_close(uint8_t dev_addr)
+bool cdch_set_config(uint8_t daddr, uint8_t itf_num)
 {
-  TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX, );
+  // fake transfer to kick-off process
+  tusb_control_request_t request;
+  request.wIndex = tu_htole16((uint16_t) itf_num);
+
+  tuh_xfer_t xfer;
+  xfer.daddr     = daddr;
+  xfer.result    = XFER_RESULT_SUCCESS;
+  xfer.setup     = &request;
+  xfer.user_data = CONFIG_SET_CONTROL_LINE_STATE;
 
-  cdch_data_t * p_cdc = get_itf(dev_addr);
-  tu_memclr(p_cdc, sizeof(cdch_data_t));
+  process_cdc_config(&xfer);
+
+  return true;
 }
 
 #endif

+ 132 - 68
src/class/cdc/cdc_host.h

@@ -34,89 +34,153 @@
 #endif
 
 //--------------------------------------------------------------------+
-// CDC APPLICATION PUBLIC API
+// Class Driver Configuration
 //--------------------------------------------------------------------+
-/** \ingroup ClassDriver_CDC Communication Device Class (CDC)
- * \addtogroup CDC_Serial Serial
- * @{
- * \defgroup   CDC_Serial_Host Host
- * @{ */
 
-bool tuh_cdc_set_control_line_state(uint8_t dev_addr, bool dtr, bool rts, tuh_xfer_cb_t complete_cb);
+// Set Line Control state on enumeration/mounted: DTR ( bit 0), RTS (bit 1)
+#ifndef CFG_TUH_CDC_LINE_CONTROL_ON_ENUM
+#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM    0
+#endif
+
+// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t
+//#ifndef CFG_TUH_CDC_LINE_CODING_ON_ENUM
+//#define CFG_TUH_CDC_LINE_CODING_ON_ENUM   { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 }
+//#endif
+
+// RX FIFO size
+#ifndef CFG_TUH_CDC_RX_BUFSIZE
+#define CFG_TUH_CDC_RX_BUFSIZE USBH_EPSIZE_BULK_MAX
+#endif
+
+// RX Endpoint size
+#ifndef CFG_TUH_CDC_RX_EPSIZE
+#define CFG_TUH_CDC_RX_EPSIZE  USBH_EPSIZE_BULK_MAX
+#endif
+
+// TX FIFO size
+#ifndef CFG_TUH_CDC_TX_BUFSIZE
+#define CFG_TUH_CDC_TX_BUFSIZE USBH_EPSIZE_BULK_MAX
+#endif
+
+// TX Endpoint size
+#ifndef CFG_TUH_CDC_TX_EPSIZE
+#define CFG_TUH_CDC_TX_EPSIZE  USBH_EPSIZE_BULK_MAX
+#endif
+
+//--------------------------------------------------------------------+
+// Application API
+//--------------------------------------------------------------------+
 
-static inline bool tuh_cdc_connect(uint8_t dev_addr, tuh_xfer_cb_t complete_cb)
+typedef struct
 {
-  return tuh_cdc_set_control_line_state(dev_addr, true, true, complete_cb);
+  uint8_t daddr;
+  uint8_t bInterfaceNumber;
+  uint8_t bInterfaceSubClass;
+  uint8_t bInterfaceProtocol;
+} tuh_cdc_itf_info_t;
+
+// Get Interface index from device address + interface number
+// return TUSB_INDEX_INVALID (0xFF) if not found
+uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num);
+
+// Get Interface information
+// return true if index is correct and interface is currently mounted
+bool tuh_cdc_itf_get_info(uint8_t idx, tuh_cdc_itf_info_t* info);
+
+// Check if a interface is mounted
+bool tuh_cdc_mounted(uint8_t idx);
+
+// Get current DTR status
+bool tuh_cdc_get_dtr(uint8_t idx);
+
+// Get current RTS status
+bool tuh_cdc_get_rts(uint8_t idx);
+
+// Check if interface is connected (DTR active)
+TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connected(uint8_t idx)
+{
+  return tuh_cdc_get_dtr(idx);
 }
 
-static inline bool tuh_cdc_disconnect(uint8_t dev_addr, tuh_xfer_cb_t complete_cb)
+// Get local (saved/cached) version of line coding.
+// This function should return correct values if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding()
+// are invoked previously or CFG_TUH_CDC_LINE_CODING_ON_ENUM is defined.
+// NOTE: This function does not make any USB transfer request to device.
+bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding);
+
+//--------------------------------------------------------------------+
+// Write API
+//--------------------------------------------------------------------+
+
+// Get the number of bytes available for writing
+uint32_t tuh_cdc_write_available(uint8_t idx);
+
+// Write to cdc interface
+uint32_t tuh_cdc_write(uint8_t idx, void const* buffer, uint32_t bufsize);
+
+// Force sending data if possible, return number of forced bytes
+uint32_t tuh_cdc_write_flush(uint8_t idx);
+
+// Clear the transmit FIFO
+bool tuh_cdc_write_clear(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Read API
+//--------------------------------------------------------------------+
+
+// Get the number of bytes available for reading
+uint32_t tuh_cdc_read_available(uint8_t idx);
+
+// Read from cdc interface
+uint32_t tuh_cdc_read (uint8_t idx, void* buffer, uint32_t bufsize);
+
+// Clear the received FIFO
+bool tuh_cdc_read_clear (uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Control Endpoint (Request) API
+// Each Function will make a USB transfer request to/from device
+//--------------------------------------------------------------------+
+
+// Request to Set Control Line State: DTR (bit 0), RTS (bit 1)
+bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data);
+
+// Request to Set Line Coding
+bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data);
+
+// Request to Get Line Coding
+// Should only use if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() never got invoked and
+// CFG_TUH_CDC_LINE_CODING_ON_ENUM is not defined
+// bool tuh_cdc_get_line_coding(uint8_t idx, cdc_line_coding_t* coding);
+
+// Connect by set both DTR, RTS
+static inline bool tuh_cdc_connect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data)
 {
-  return tuh_cdc_set_control_line_state(dev_addr, false, false, complete_cb);
+  return tuh_cdc_set_control_line_state(idx, CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS, complete_cb, user_data);
 }
 
-/** \brief 			Check if device support CDC Serial interface or not
- * \param[in]		dev_addr	device address
- * \retval      true if device supports
- * \retval      false if device does not support or is not mounted
- */
-bool tuh_cdc_serial_is_mounted(uint8_t dev_addr);
-
-/** \brief      Check if the interface is currently busy or not
- * \param[in]   dev_addr device address
- * \param[in]   pipeid value from \ref cdc_pipeid_t to indicate target pipe.
- * \retval      true if the interface is busy, meaning the stack is still transferring/waiting data from/to device
- * \retval      false if the interface is not busy, meaning the stack successfully transferred data from/to device
- * \note        This function is used to check if previous transfer is complete (success or error), so that the next transfer
- *              can be scheduled. User needs to make sure the corresponding interface is mounted
- *              (by \ref tuh_cdc_serial_is_mounted) before calling this function.
- */
-bool tuh_cdc_is_busy(uint8_t dev_addr, cdc_pipeid_t pipeid);
-
-/** \brief 			Perform USB OUT transfer to device
- * \param[in]		dev_addr	device address
- * \param[in]	  p_data    Buffer containing data. Must be accessible by USB controller (see \ref CFG_TUSB_MEM_SECTION)
- * \param[in]		length    Number of bytes to be transferred via USB bus
- * \retval      TUSB_ERROR_NONE on success
- * \retval      TUSB_ERROR_INTERFACE_IS_BUSY if the interface is already transferring data with device
- * \retval      TUSB_ERROR_DEVICE_NOT_READY if device is not yet configured (by SET CONFIGURED request)
- * \retval      TUSB_ERROR_INVALID_PARA if input parameters are not correct
- * \note        This function is non-blocking and returns immediately. The result of USB transfer will be reported by the
- *              interface's callback function. \a p_data must be declared with \ref CFG_TUSB_MEM_SECTION.
- */
-bool tuh_cdc_send(uint8_t dev_addr, void const * p_data, uint32_t length, bool is_notify);
-
-/** \brief 			Perform USB IN transfer to get data from device
- * \param[in]		dev_addr	device address
- * \param[in]	  p_buffer  Buffer containing received data. Must be accessible by USB controller (see \ref CFG_TUSB_MEM_SECTION)
- * \param[in]		length    Number of bytes to be transferred via USB bus
- * \retval      TUSB_ERROR_NONE on success
- * \retval      TUSB_ERROR_INTERFACE_IS_BUSY if the interface is already transferring data with device
- * \retval      TUSB_ERROR_DEVICE_NOT_READY if device is not yet configured (by SET CONFIGURED request)
- * \retval      TUSB_ERROR_INVALID_PARA if input parameters are not correct
- * \note        This function is non-blocking and returns immediately. The result of USB transfer will be reported by the
- *              interface's callback function. \a p_data must be declared with \ref CFG_TUSB_MEM_SECTION.
- */
-bool tuh_cdc_receive(uint8_t dev_addr, void * p_buffer, uint32_t length, bool is_notify);
+// Disconnect by clear both DTR, RTS
+static inline bool tuh_cdc_disconnect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data)
+{
+  return tuh_cdc_set_control_line_state(idx, 0x00, complete_cb, user_data);
+}
 
 //--------------------------------------------------------------------+
 // CDC APPLICATION CALLBACKS
 //--------------------------------------------------------------------+
 
-/** \brief      Callback function that is invoked when an transferring event occurred
- * \param[in]		dev_addr	Address of device
- * \param[in]   event an value from \ref xfer_result_t
- * \param[in]   pipe_id value from \ref cdc_pipeid_t indicate the pipe
- * \param[in]   xferred_bytes Number of bytes transferred via USB bus
- * \note        event can be one of following
- *              - XFER_RESULT_SUCCESS : previously scheduled transfer completes successfully.
- *              - XFER_RESULT_FAILED   : previously scheduled transfer encountered a transaction error.
- *              - XFER_RESULT_STALLED : previously scheduled transfer is stalled by device.
- * \note
- */
-void tuh_cdc_xfer_isr(uint8_t dev_addr, xfer_result_t event, cdc_pipeid_t pipe_id, uint32_t xferred_bytes);
+// Invoked when a device with CDC interface is mounted
+// idx is index of cdc interface in the internal pool.
+TU_ATTR_WEAK extern void tuh_cdc_mount_cb(uint8_t idx);
+
+// Invoked when a device with CDC interface is unmounted
+TU_ATTR_WEAK extern void tuh_cdc_umount_cb(uint8_t idx);
+
+// Invoked when received new data
+TU_ATTR_WEAK extern void tuh_cdc_rx_cb(uint8_t idx);
 
-/// @} // group CDC_Serial_Host
-/// @}
+// Invoked when a TX is complete and therefore space becomes available in TX buffer
+TU_ATTR_WEAK extern void tuh_cdc_tx_complete_cb(uint8_t idx);
 
 //--------------------------------------------------------------------+
 // Internal Class Driver API

+ 2 - 1
src/class/hid/hid_host.c

@@ -62,6 +62,7 @@ typedef struct
   hidh_interface_t instances[CFG_TUH_HID];
 } hidh_device_t;
 
+CFG_TUSB_MEM_SECTION
 static hidh_device_t _hidh_dev[CFG_TUH_DEVICE_MAX];
 
 //------------- Internal prototypes -------------//
@@ -258,7 +259,7 @@ bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t instance)
 
   if ( !usbh_edpt_xfer(dev_addr, hid_itf->ep_in, hid_itf->epin_buf, hid_itf->epin_size) )
   {
-    usbh_edpt_claim(dev_addr, hid_itf->ep_in);
+    usbh_edpt_release(dev_addr, hid_itf->ep_in);
     return false;
   }
 

+ 9 - 4
src/class/msc/msc_host.c

@@ -33,6 +33,11 @@
 
 #include "msc_host.h"
 
+// Debug level, TUSB_CFG_DEBUG must be at least this level for debug message
+#define MSCH_DEBUG   2
+
+#define TU_LOG_MSCH(...)   TU_LOG(MSCH_DEBUG, __VA_ARGS__)
+
 //--------------------------------------------------------------------+
 // MACRO CONSTANT TYPEDEF
 //--------------------------------------------------------------------+
@@ -417,7 +422,7 @@ bool msch_set_config(uint8_t dev_addr, uint8_t itf_num)
   p_msc->configured = true;
 
   //------------- Get Max Lun -------------//
-  TU_LOG2("MSC Get Max Lun\r\n");
+  TU_LOG_MSCH("MSC Get Max Lun\r\n");
   tusb_control_request_t const request =
   {
     .bmRequestType_bit =
@@ -456,7 +461,7 @@ static void config_get_maxlun_complete (tuh_xfer_t* xfer)
   p_msc->max_lun++; // MAX LUN is minus 1 by specs
 
   // TODO multiple LUN support
-  TU_LOG2("SCSI Test Unit Ready\r\n");
+  TU_LOG_MSCH("SCSI Test Unit Ready\r\n");
   uint8_t const lun = 0;
   tuh_msc_test_unit_ready(daddr, lun, config_test_unit_ready_complete, 0);
 }
@@ -469,14 +474,14 @@ static bool config_test_unit_ready_complete(uint8_t dev_addr, tuh_msc_complete_d
   if (csw->status == 0)
   {
     // Unit is ready, read its capacity
-    TU_LOG2("SCSI Read Capacity\r\n");
+    TU_LOG_MSCH("SCSI Read Capacity\r\n");
     tuh_msc_read_capacity(dev_addr, cbw->lun, (scsi_read_capacity10_resp_t*) ((void*) _msch_buffer), config_read_capacity_complete, 0);
   }else
   {
     // Note: During enumeration, some device fails Test Unit Ready and require a few retries
     // with Request Sense to start working !!
     // TODO limit number of retries
-    TU_LOG2("SCSI Request Sense\r\n");
+    TU_LOG_MSCH("SCSI Request Sense\r\n");
     TU_ASSERT(tuh_msc_request_sense(dev_addr, cbw->lun, _msch_buffer, config_request_sense_complete, 0));
   }
 

+ 9 - 9
src/common/tusb_debug.h

@@ -66,7 +66,7 @@ static inline void tu_print_arr(uint8_t const* buf, uint32_t bufsize)
 #define TU_LOG(n, ...)        TU_XSTRCAT(TU_LOG, n)(__VA_ARGS__)
 #define TU_LOG_MEM(n, ...)    TU_XSTRCAT3(TU_LOG, n, _MEM)(__VA_ARGS__)
 #define TU_LOG_ARR(n, ...)    TU_XSTRCAT3(TU_LOG, n, _ARR)(__VA_ARGS__)
-#define TU_LOG_VAR(n, ...)    TU_XSTRCAT3(TU_LOG, n, _VAR)(__VA_ARGS__)
+#define TU_LOG_PTR(n, ...)    TU_XSTRCAT3(TU_LOG, n, _PTR)(__VA_ARGS__)
 #define TU_LOG_INT(n, ...)    TU_XSTRCAT3(TU_LOG, n, _INT)(__VA_ARGS__)
 #define TU_LOG_HEX(n, ...)    TU_XSTRCAT3(TU_LOG, n, _HEX)(__VA_ARGS__)
 #define TU_LOG_LOCATION()     tu_printf("%s: %d:\r\n", __PRETTY_FUNCTION__, __LINE__)
@@ -76,7 +76,7 @@ static inline void tu_print_arr(uint8_t const* buf, uint32_t bufsize)
 #define TU_LOG1               tu_printf
 #define TU_LOG1_MEM           tu_print_mem
 #define TU_LOG1_ARR(_x, _n)   tu_print_arr((uint8_t const*)(_x), _n)
-#define TU_LOG1_VAR(_x)       tu_print_arr((uint8_t const*)(_x), sizeof(*(_x)))
+#define TU_LOG1_PTR(_x)       tu_print_arr((uint8_t const*)(_x), sizeof(*(_x)))
 #define TU_LOG1_INT(_x)       tu_printf(#_x " = %ld\r\n", (unsigned long) (_x) )
 #define TU_LOG1_HEX(_x)       tu_printf(#_x " = %lX\r\n", (unsigned long) (_x) )
 
@@ -85,7 +85,7 @@ static inline void tu_print_arr(uint8_t const* buf, uint32_t bufsize)
   #define TU_LOG2             TU_LOG1
   #define TU_LOG2_MEM         TU_LOG1_MEM
   #define TU_LOG2_ARR         TU_LOG1_ARR
-  #define TU_LOG2_VAR         TU_LOG1_VAR
+  #define TU_LOG2_PTR         TU_LOG1_PTR
   #define TU_LOG2_INT         TU_LOG1_INT
   #define TU_LOG2_HEX         TU_LOG1_HEX
 #endif
@@ -95,7 +95,7 @@ static inline void tu_print_arr(uint8_t const* buf, uint32_t bufsize)
   #define TU_LOG3             TU_LOG1
   #define TU_LOG3_MEM         TU_LOG1_MEM
   #define TU_LOG3_ARR         TU_LOG1_ARR
-  #define TU_LOG3_VAR         TU_LOG1_VAR
+  #define TU_LOG3_PTR         TU_LOG1_PTR
   #define TU_LOG3_INT         TU_LOG1_INT
   #define TU_LOG3_HEX         TU_LOG1_HEX
 #endif
@@ -132,7 +132,7 @@ static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint3
 #ifndef TU_LOG
   #define TU_LOG(n, ...)
   #define TU_LOG_MEM(n, ...)
-  #define TU_LOG_VAR(n, ...)
+  #define TU_LOG_PTR(n, ...)
   #define TU_LOG_INT(n, ...)
   #define TU_LOG_HEX(n, ...)
   #define TU_LOG_LOCATION()
@@ -143,14 +143,14 @@ static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint3
 
 #define TU_LOG0(...)
 #define TU_LOG0_MEM(...)
-#define TU_LOG0_VAR(...)
+#define TU_LOG0_PTR(...)
 #define TU_LOG0_INT(...)
 #define TU_LOG0_HEX(...)
 
 #ifndef TU_LOG1
   #define TU_LOG1(...)
   #define TU_LOG1_MEM(...)
-  #define TU_LOG1_VAR(...)
+  #define TU_LOG1_PTR(...)
   #define TU_LOG1_INT(...)
   #define TU_LOG1_HEX(...)
 #endif
@@ -158,7 +158,7 @@ static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint3
 #ifndef TU_LOG2
   #define TU_LOG2(...)
   #define TU_LOG2_MEM(...)
-  #define TU_LOG2_VAR(...)
+  #define TU_LOG2_PTR(...)
   #define TU_LOG2_INT(...)
   #define TU_LOG2_HEX(...)
 #endif
@@ -166,7 +166,7 @@ static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint3
 #ifndef TU_LOG3
   #define TU_LOG3(...)
   #define TU_LOG3_MEM(...)
-  #define TU_LOG3_VAR(...)
+  #define TU_LOG3_PTR(...)
   #define TU_LOG3_INT(...)
   #define TU_LOG3_HEX(...)
 #endif

+ 104 - 1
src/common/tusb_private.h

@@ -28,6 +28,8 @@
 #ifndef _TUSB_PRIVATE_H_
 #define _TUSB_PRIVATE_H_
 
+// Internal Helper used by Host and Device Stack
+
 #ifdef __cplusplus
  extern "C" {
 #endif
@@ -39,8 +41,31 @@ typedef struct TU_ATTR_PACKED
   volatile uint8_t claimed : 1;
 }tu_edpt_state_t;
 
+typedef struct {
+  bool is_host; // host or device most
+  union {
+      uint8_t daddr;
+      uint8_t rhport;
+      uint8_t hwid;
+  };
+  uint8_t ep_addr;
+  uint8_t ep_speed;
+
+  uint16_t ep_packetsize;
+  uint16_t ep_bufsize;
+
+  // TODO xfer_fifo can skip this buffer
+  uint8_t* ep_buf;
+
+  tu_fifo_t ff;
+
+  // mutex: read if ep rx, write if e tx
+  OSAL_MUTEX_DEF(ff_mutex);
+
+}tu_edpt_stream_t;
+
 //--------------------------------------------------------------------+
-// Internal Helper used by Host and Device Stack
+// Endpoint
 //--------------------------------------------------------------------+
 
 // Check if endpoint descriptor is valid per USB specs
@@ -58,6 +83,84 @@ bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex);
 // Release an endpoint with provided mutex
 bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex);
 
+//--------------------------------------------------------------------+
+// Endpoint Stream
+//--------------------------------------------------------------------+
+
+// Init an stream, should only be called once
+bool tu_edpt_stream_init(tu_edpt_stream_t* s, bool is_host, bool is_tx, bool overwritable,
+                         void* ff_buf, uint16_t ff_bufsize, uint8_t* ep_buf, uint16_t ep_bufsize);
+
+// Open an stream for an endpoint
+// hwid is either device address (host mode) or rhport (device mode)
+TU_ATTR_ALWAYS_INLINE static inline
+void tu_edpt_stream_open(tu_edpt_stream_t* s, uint8_t hwid, tusb_desc_endpoint_t const *desc_ep)
+{
+  tu_fifo_clear(&s->ff);
+  s->hwid = hwid;
+  s->ep_addr = desc_ep->bEndpointAddress;
+  s->ep_packetsize = tu_edpt_packet_size(desc_ep);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline
+void tu_edpt_stream_close(tu_edpt_stream_t* s)
+{
+  s->hwid = 0;
+  s->ep_addr = 0;
+}
+
+// Clear fifo
+TU_ATTR_ALWAYS_INLINE static inline
+bool tu_edpt_stream_clear(tu_edpt_stream_t* s)
+{
+  return tu_fifo_clear(&s->ff);
+}
+
+//--------------------------------------------------------------------+
+// Stream Write
+//--------------------------------------------------------------------+
+
+// Write to stream
+uint32_t tu_edpt_stream_write(tu_edpt_stream_t* s, void const *buffer, uint32_t bufsize);
+
+// Start an usb transfer if endpoint is not busy
+uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t* s);
+
+// Start an zero-length packet if needed
+bool tu_edpt_stream_write_zlp_if_needed(tu_edpt_stream_t* s, uint32_t last_xferred_bytes);
+
+// Get the number of bytes available for writing
+TU_ATTR_ALWAYS_INLINE static inline
+uint32_t tu_edpt_stream_write_available(tu_edpt_stream_t* s)
+{
+  return (uint32_t) tu_fifo_remaining(&s->ff);
+}
+
+//--------------------------------------------------------------------+
+// Stream Read
+//--------------------------------------------------------------------+
+
+// Read from stream
+uint32_t tu_edpt_stream_read(tu_edpt_stream_t* s, void* buffer, uint32_t bufsize);
+
+// Start an usb transfer if endpoint is not busy
+uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t* s);
+
+// Must be called in the transfer complete callback
+TU_ATTR_ALWAYS_INLINE static inline
+void tu_edpt_stream_read_xfer_complete(tu_edpt_stream_t* s, uint32_t xferred_bytes)
+{
+  tu_fifo_write_n(&s->ff, s->ep_buf, (uint16_t) xferred_bytes);
+}
+
+// Get the number of bytes available for reading
+TU_ATTR_ALWAYS_INLINE static inline
+uint32_t tu_edpt_stream_read_available(tu_edpt_stream_t* s)
+{
+  return (uint32_t) tu_fifo_count(&s->ff);
+}
+
+
 #ifdef __cplusplus
  }
 #endif

+ 14 - 1
src/common/tusb_types.h

@@ -69,6 +69,15 @@ typedef enum
   TUSB_DIR_IN_MASK = 0x80
 }tusb_dir_t;
 
+enum
+{
+  TUSB_EPSIZE_BULK_FS = 64,
+  TUSB_EPSIZE_BULK_HS= 512,
+
+  TUSB_EPSIZE_ISO_FS_MAX = 1023,
+  TUSB_EPSIZE_ISO_HS_MAX = 1024,
+};
+
 /// Isochronous End Point Attributes
 typedef enum
 {
@@ -243,7 +252,6 @@ enum
   INTERFACE_INVALID_NUMBER = 0xff
 };
 
-
 typedef enum
 {
   MS_OS_20_SET_HEADER_DESCRIPTOR       = 0x00,
@@ -265,6 +273,11 @@ enum
   CONTROL_STAGE_ACK
 };
 
+enum
+{
+  TUSB_INDEX_INVALID = 0xff
+};
+
 //--------------------------------------------------------------------+
 // USB Descriptors
 //--------------------------------------------------------------------+

+ 4 - 4
src/device/usbd.c

@@ -39,13 +39,13 @@
 // USBD Configuration
 //--------------------------------------------------------------------+
 
-// Debug level of USBD
-#define USBD_DBG   2
-
 #ifndef CFG_TUD_TASK_QUEUE_SZ
   #define CFG_TUD_TASK_QUEUE_SZ   16
 #endif
 
+// Debug level of USBD
+#define USBD_DBG   2
+
 //--------------------------------------------------------------------+
 // Device Data
 //--------------------------------------------------------------------+
@@ -506,7 +506,7 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr)
       break;
 
       case DCD_EVENT_SETUP_RECEIVED:
-        TU_LOG_VAR(USBD_DBG, &event.setup_received);
+        TU_LOG_PTR(USBD_DBG, &event.setup_received);
         TU_LOG(USBD_DBG, "\r\n");
 
         // Mark as connected after receiving 1st setup packet.

+ 57 - 45
src/host/usbh.c

@@ -30,7 +30,6 @@
 
 #include "host/hcd.h"
 #include "tusb.h"
-#include "common/tusb_private.h"
 #include "host/usbh_classdriver.h"
 #include "hub.h"
 
@@ -46,8 +45,10 @@
 #define CFG_TUH_INTERFACE_MAX   8
 #endif
 
-// Debug level of USBD
-#define USBH_DBG_LVL   2
+// Debug level, TUSB_CFG_DEBUG must be at least this level for debug message
+#define USBH_DEBUG   2
+
+#define TU_LOG_USBH(...)   TU_LOG(USBH_DEBUG, __VA_ARGS__)
 
 //--------------------------------------------------------------------+
 // USBH-HCD common data structure
@@ -324,11 +325,13 @@ bool tuh_init(uint8_t controller_id)
   // skip if already initialized
   if ( tuh_inited() ) return true;
 
-  TU_LOG2("USBH init on controller %u\r\n", controller_id);
-  TU_LOG2_INT(sizeof(usbh_device_t));
-  TU_LOG2_INT(sizeof(hcd_event_t));
-  TU_LOG2_INT(sizeof(_ctrl_xfer));
-  TU_LOG2_INT(sizeof(tuh_xfer_t));
+  TU_LOG_USBH("USBH init on controller %u\r\n", controller_id);
+  TU_LOG_INT(USBH_DEBUG, sizeof(usbh_device_t));
+  TU_LOG_INT(USBH_DEBUG, sizeof(hcd_event_t));
+  TU_LOG_INT(USBH_DEBUG, sizeof(_ctrl_xfer));
+  TU_LOG_INT(USBH_DEBUG, sizeof(tuh_xfer_t));
+  TU_LOG_INT(USBH_DEBUG, sizeof(tu_fifo_t));
+  TU_LOG_INT(USBH_DEBUG, sizeof(tu_edpt_stream_t));
 
   // Event queue
   _usbh_q = osal_queue_create( &_usbh_qdef );
@@ -353,7 +356,7 @@ bool tuh_init(uint8_t controller_id)
   // Class drivers
   for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++)
   {
-    TU_LOG2("%s init\r\n", usbh_class_drivers[drv_id].name);
+    TU_LOG_USBH("%s init\r\n", usbh_class_drivers[drv_id].name);
     usbh_class_drivers[drv_id].init();
   }
 
@@ -401,12 +404,12 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr)
       case HCD_EVENT_DEVICE_ATTACH:
         // TODO due to the shared _usbh_ctrl_buf, we must complete enumerating
         // one device before enumerating another one.
-        TU_LOG2("[%u:] USBH DEVICE ATTACH\r\n", event.rhport);
+        TU_LOG_USBH("[%u:] USBH DEVICE ATTACH\r\n", event.rhport);
         enum_new_device(&event);
       break;
 
       case HCD_EVENT_DEVICE_REMOVE:
-        TU_LOG2("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port);
+        TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port);
         process_device_unplugged(event.rhport, event.connection.hub_addr, event.connection.hub_port);
 
         #if CFG_TUH_HUB
@@ -425,7 +428,7 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr)
         uint8_t const epnum   = tu_edpt_number(ep_addr);
         uint8_t const ep_dir  = tu_edpt_dir(ep_addr);
 
-        TU_LOG2("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len);
+        TU_LOG_USBH("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len);
 
         if (event.dev_addr == 0)
         {
@@ -449,7 +452,7 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr)
             uint8_t drv_id = dev->ep2drv[epnum][ep_dir];
             if(drv_id < USBH_CLASS_DRIVER_COUNT)
             {
-              TU_LOG2("%s xfer callback\r\n", usbh_class_drivers[drv_id].name);
+              TU_LOG_USBH("%s xfer callback\r\n", usbh_class_drivers[drv_id].name);
               usbh_class_drivers[drv_id].xfer_cb(event.dev_addr, ep_addr, event.xfer_complete.result, event.xfer_complete.len);
             }
             else
@@ -539,9 +542,11 @@ bool tuh_control_xfer (tuh_xfer_t* xfer)
   TU_VERIFY(is_idle);
   const uint8_t rhport = usbh_get_rhport(daddr);
 
-  TU_LOG2("[%u:%u] %s: ", rhport, daddr, xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME ? tu_str_std_request[xfer->setup->bRequest] : "Unknown Request");
-  TU_LOG2_VAR(xfer->setup);
-  TU_LOG2("\r\n");
+  TU_LOG_USBH("[%u:%u] %s: ", rhport, daddr,
+              (xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ?
+                  tu_str_std_request[xfer->setup->bRequest] : "Class Request");
+  TU_LOG_PTR(USBH_DEBUG, xfer->setup);
+  TU_LOG_USBH("\r\n");
 
   if (xfer->complete_cb)
   {
@@ -585,7 +590,7 @@ TU_ATTR_ALWAYS_INLINE static inline void _set_control_xfer_stage(uint8_t stage)
 
 static void _xfer_complete(uint8_t daddr, xfer_result_t result)
 {
-  TU_LOG2("\r\n");
+  TU_LOG_USBH("\r\n");
 
   // duplicate xfer since user can execute control transfer within callback
   tusb_control_request_t const request = _ctrl_xfer.request;
@@ -618,7 +623,11 @@ static bool usbh_control_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result
 
   if (XFER_RESULT_SUCCESS != result)
   {
-    TU_LOG1("[%u:%u] Control %s\r\n", rhport, dev_addr, result == XFER_RESULT_STALLED ? "STALLED" : "FAILED");
+    TU_LOG1("[%u:%u] Control %s, xferred_bytes = %lu\r\n", rhport, dev_addr, result == XFER_RESULT_STALLED ? "STALLED" : "FAILED", xferred_bytes);
+    #if CFG_TUSB_DEBUG == 1
+    TU_LOG1_PTR(request);
+    TU_LOG1("\r\n");
+    #endif
 
     // terminate transfer if any stage failed
     _xfer_complete(dev_addr, result);
@@ -639,8 +648,8 @@ static bool usbh_control_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result
       case CONTROL_STAGE_DATA:
         if (request->wLength)
         {
-          TU_LOG2("[%u:%u] Control data:\r\n", rhport, dev_addr);
-          TU_LOG2_MEM(_ctrl_xfer.buffer, xferred_bytes, 2);
+          TU_LOG_USBH("[%u:%u] Control data:\r\n", rhport, dev_addr);
+          TU_LOG_MEM(USBH_DEBUG, _ctrl_xfer.buffer, xferred_bytes, 2);
         }
 
         _ctrl_xfer.actual_len = (uint16_t) xferred_bytes;
@@ -756,7 +765,7 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * b
   uint8_t const dir   = tu_edpt_dir(ep_addr);
   tu_edpt_state_t* ep_state = &dev->ep_status[epnum][dir];
 
-  TU_LOG2("  Queue EP %02X with %u bytes ... ", ep_addr, total_bytes);
+  TU_LOG_USBH("  Queue EP %02X with %u bytes ... ", ep_addr, total_bytes);
 
   // Attempt to transfer on a busy endpoint, sound like an race condition !
   TU_ASSERT(ep_state->busy == 0);
@@ -772,7 +781,7 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * b
 
   if ( hcd_edpt_xfer(dev->rhport, dev_addr, ep_addr, buffer, total_bytes) )
   {
-    TU_LOG2("OK\r\n");
+    TU_LOG_USBH("OK\r\n");
     return true;
   }else
   {
@@ -787,7 +796,7 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * b
 
 static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size)
 {
-  TU_LOG2("[%u:%u] Open EP0 with Size = %u\r\n", usbh_get_rhport(dev_addr), dev_addr, max_packet_size);
+  TU_LOG_USBH("[%u:%u] Open EP0 with Size = %u\r\n", usbh_get_rhport(dev_addr), dev_addr, max_packet_size);
 
   tusb_desc_endpoint_t ep0_desc =
   {
@@ -956,7 +965,7 @@ bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void*
 bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len,
                                    tuh_xfer_cb_t complete_cb, uintptr_t user_data)
 {
-  TU_LOG2("HID Get Report Descriptor\r\n");
+  TU_LOG_USBH("HID Get Report Descriptor\r\n");
   tusb_control_request_t const request =
   {
     .bmRequestType_bit =
@@ -995,7 +1004,7 @@ bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_
 bool tuh_configuration_set(uint8_t daddr, uint8_t config_num,
                            tuh_xfer_cb_t complete_cb, uintptr_t user_data)
 {
-  TU_LOG2("Set Configuration = %d\r\n", config_num);
+  TU_LOG_USBH("Set Configuration = %d\r\n", config_num);
 
   tusb_control_request_t const request =
   {
@@ -1099,11 +1108,11 @@ static void process_device_unplugged(uint8_t rhport, uint8_t hub_addr, uint8_t h
         (hub_port == 0 || dev->hub_port == hub_port) && // hub_port = 0 means all devices of downstream hub
         dev->connected)
     {
-      TU_LOG2("  Address = %u\r\n", dev_addr);
+      TU_LOG_USBH("  Address = %u\r\n", dev_addr);
 
       if (is_hub_addr(dev_addr))
       {
-        TU_LOG(USBH_DBG_LVL, "HUB address = %u is unmounted\r\n", dev_addr);
+        TU_LOG(USBH_DEBUG, "HUB address = %u is unmounted\r\n", dev_addr);
         // If the device itself is a usb hub, unplug downstream devices.
         // FIXME un-roll recursive calls to prevent potential stack overflow
         process_device_unplugged(rhport, dev_addr, 0);
@@ -1116,7 +1125,7 @@ static void process_device_unplugged(uint8_t rhport, uint8_t hub_addr, uint8_t h
       // Close class driver
       for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++)
       {
-        TU_LOG2("%s close\r\n", usbh_class_drivers[drv_id].name);
+        TU_LOG_USBH("%s close\r\n", usbh_class_drivers[drv_id].name);
         usbh_class_drivers[drv_id].close(dev_addr);
       }
 
@@ -1241,7 +1250,7 @@ static void process_enumeration(tuh_xfer_t* xfer)
       TU_ASSERT( usbh_edpt_control_open(addr0, 8), );
 
       // Get first 8 bytes of device descriptor for Control Endpoint size
-      TU_LOG2("Get 8 byte of Device Descriptor\r\n");
+      TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n");
       TU_ASSERT(tuh_descriptor_get_device(addr0, _usbh_ctrl_buf, 8, process_enumeration, ENUM_SET_ADDR), );
     }
     break;
@@ -1250,7 +1259,7 @@ static void process_enumeration(tuh_xfer_t* xfer)
     case ENUM_RESET_2:
       // TODO not used by now, but may be needed for some devices !?
       // Reset device again before Set Address
-      TU_LOG2("Port reset2 \r\n");
+      TU_LOG_USBH("Port reset2 \r\n");
       if (_dev0.hub_addr == 0)
       {
         // connected directly to roothub
@@ -1268,7 +1277,7 @@ static void process_enumeration(tuh_xfer_t* xfer)
         break;
       }
       #endif
-      __attribute__((fallthrough));
+      TU_ATTR_FALLTHROUGH;
 #endif
 
     case ENUM_SET_ADDR:
@@ -1290,7 +1299,7 @@ static void process_enumeration(tuh_xfer_t* xfer)
       TU_ASSERT( usbh_edpt_control_open(new_addr, new_dev->ep0_size), );
 
       // Get full device descriptor
-      TU_LOG2("Get Device Descriptor\r\n");
+      TU_LOG_USBH("Get Device Descriptor\r\n");
       TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_ctrl_buf, sizeof(tusb_desc_device_t), process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC), );
     }
     break;
@@ -1311,7 +1320,7 @@ static void process_enumeration(tuh_xfer_t* xfer)
 
       // Get 9-byte for total length
       uint8_t const config_idx = CONFIG_NUM - 1;
-      TU_LOG2("Get Configuration[0] Descriptor (9 bytes)\r\n");
+      TU_LOG_USBH("Get Configuration[0] Descriptor (9 bytes)\r\n");
       TU_ASSERT( tuh_descriptor_get_configuration(daddr, config_idx, _usbh_ctrl_buf, 9, process_enumeration, ENUM_GET_FULL_CONFIG_DESC), );
     }
     break;
@@ -1328,7 +1337,7 @@ static void process_enumeration(tuh_xfer_t* xfer)
 
       // Get full configuration descriptor
       uint8_t const config_idx = CONFIG_NUM - 1;
-      TU_LOG2("Get Configuration[0] Descriptor\r\n");
+      TU_LOG_USBH("Get Configuration[0] Descriptor\r\n");
       TU_ASSERT( tuh_descriptor_get_configuration(daddr, config_idx, _usbh_ctrl_buf, total_len, process_enumeration, ENUM_SET_CONFIG), );
     }
     break;
@@ -1343,7 +1352,7 @@ static void process_enumeration(tuh_xfer_t* xfer)
 
     case ENUM_CONFIG_DRIVER:
     {
-      TU_LOG2("Device configured\r\n");
+      TU_LOG_USBH("Device configured\r\n");
       usbh_device_t* dev = get_device(daddr);
       TU_ASSERT(dev, );
 
@@ -1383,7 +1392,7 @@ static bool enum_new_device(hcd_event_t* event)
     if ( !hcd_port_connect_status(_dev0.rhport) ) return true;
 
     _dev0.speed = hcd_port_speed_get(_dev0.rhport );
-    TU_LOG2("%s Speed\r\n", tu_str_speed[_dev0.speed]);
+    TU_LOG_USBH("%s Speed\r\n", tu_str_speed[_dev0.speed]);
 
     // fake transfer to kick-off the enumeration process
     tuh_xfer_t xfer;
@@ -1440,7 +1449,7 @@ static bool enum_request_set_addr(void)
   uint8_t const new_addr = get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB);
   TU_ASSERT(new_addr != 0);
 
-  TU_LOG2("Set Address = %d\r\n", new_addr);
+  TU_LOG_USBH("Set Address = %d\r\n", new_addr);
 
   usbh_device_t* new_dev = get_device(new_addr);
 
@@ -1484,9 +1493,12 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur
 {
   usbh_device_t* dev = get_device(dev_addr);
 
-  uint8_t const* desc_end = ((uint8_t const*) desc_cfg) + tu_le16toh(desc_cfg->wTotalLength);
+  uint16_t const total_len = tu_le16toh(desc_cfg->wTotalLength);
+  uint8_t const* desc_end = ((uint8_t const*) desc_cfg) + total_len;
   uint8_t const* p_desc   = tu_desc_next(desc_cfg);
 
+  TU_LOG_USBH("Parsing Configuration descriptor (wTotalLength = %u)\r\n", total_len);
+
   // parse each interfaces
   while( p_desc < desc_end )
   {
@@ -1524,15 +1536,14 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur
     TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t));
 
     // Find driver for this interface
-    uint8_t drv_id;
-    for (drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++)
+    for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++)
     {
       usbh_class_driver_t const * driver = &usbh_class_drivers[drv_id];
 
       if ( driver->open(dev->rhport, dev_addr, desc_itf, drv_len) )
       {
         // open successfully
-        TU_LOG2("  %s opened\r\n", driver->name);
+        TU_LOG_USBH("  %s opened\r\n", driver->name);
 
         // bind (associated) interfaces to found driver
         for(uint8_t i=0; i<assoc_itf_count; i++)
@@ -1552,7 +1563,7 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur
 
       if( drv_id >= USBH_CLASS_DRIVER_COUNT )
       {
-        TU_LOG(USBH_DBG_LVL, "Interface %u: class = %u subclass = %u protocol = %u is not supported\r\n",
+        TU_LOG(USBH_DEBUG, "Interface %u: class = %u subclass = %u protocol = %u is not supported\r\n",
                desc_itf->bInterfaceNumber, desc_itf->bInterfaceClass, desc_itf->bInterfaceSubClass, desc_itf->bInterfaceProtocol);
       }
     }
@@ -1571,12 +1582,13 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num)
   for(itf_num++; itf_num < CFG_TUH_INTERFACE_MAX; itf_num++)
   {
     // continue with next valid interface
-    // TODO skip IAD binding interface such as CDCs
+    // IAD binding interface such as CDCs should return itf_num + 1 when complete
+    // with usbh_driver_set_config_complete()
     uint8_t const drv_id = dev->itf2drv[itf_num];
     if (drv_id != DRVID_INVALID)
     {
       usbh_class_driver_t const * driver = &usbh_class_drivers[drv_id];
-      TU_LOG2("%s set config: itf = %u\r\n", driver->name, itf_num);
+      TU_LOG_USBH("%s set config: itf = %u\r\n", driver->name, itf_num);
       driver->set_config(dev_addr, itf_num);
       break;
     }
@@ -1589,7 +1601,7 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num)
 
     if (is_hub_addr(dev_addr))
     {
-      TU_LOG(USBH_DBG_LVL, "HUB address = %u is mounted\r\n", dev_addr);
+      TU_LOG(USBH_DEBUG, "HUB address = %u is mounted\r\n", dev_addr);
     }else
     {
       // Invoke callback if available

+ 5 - 0
src/host/usbh_classdriver.h

@@ -29,11 +29,16 @@
 
 #include "osal/osal.h"
 #include "common/tusb_fifo.h"
+#include "common/tusb_private.h"
 
 #ifdef __cplusplus
  extern "C" {
 #endif
 
+enum {
+  USBH_EPSIZE_BULK_MAX = (TUH_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS)
+};
+
 //--------------------------------------------------------------------+
 // Class Driver API
 //--------------------------------------------------------------------+

+ 187 - 5
src/tusb.c

@@ -31,11 +31,18 @@
 #include "tusb.h"
 #include "common/tusb_private.h"
 
-// TODO clean up
 #if CFG_TUD_ENABLED
 #include "device/usbd_pvt.h"
 #endif
 
+#if CFG_TUH_ENABLED
+#include "host/usbh_classdriver.h"
+#endif
+
+//--------------------------------------------------------------------+
+// Public API
+//--------------------------------------------------------------------+
+
 bool tusb_init(void)
 {
 #if CFG_TUD_ENABLED && defined(TUD_OPT_RHPORT)
@@ -67,7 +74,7 @@ bool tusb_inited(void)
 }
 
 //--------------------------------------------------------------------+
-// Internal Helper for both Host and Device stack
+// Endpoint Helper for both Host and Device stack
 //--------------------------------------------------------------------+
 
 bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex)
@@ -196,9 +203,184 @@ uint16_t tu_desc_get_interface_total_len(tusb_desc_interface_t const* desc_itf,
   return len;
 }
 
-/*------------------------------------------------------------------*/
-/* Debug
- *------------------------------------------------------------------*/
+//--------------------------------------------------------------------+
+// Endpoint Stream Helper for both Host and Device stack
+//--------------------------------------------------------------------+
+
+bool tu_edpt_stream_init(tu_edpt_stream_t* s, bool is_host, bool is_tx, bool overwritable,
+                         void* ff_buf, uint16_t ff_bufsize, uint8_t* ep_buf, uint16_t ep_bufsize)
+{
+  osal_mutex_t new_mutex = osal_mutex_create(&s->ff_mutex);
+  (void) new_mutex;
+  (void) is_tx;
+
+  s->is_host = is_host;
+  tu_fifo_config(&s->ff, ff_buf, ff_bufsize, 1, overwritable);
+  tu_fifo_config_mutex(&s->ff, is_tx ? new_mutex : NULL, is_tx ? NULL : new_mutex);
+
+  s->ep_buf = ep_buf;
+  s->ep_bufsize = ep_bufsize;
+
+  return true;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline
+bool stream_claim(tu_edpt_stream_t* s)
+{
+  if (s->is_host)
+  {
+    #if CFG_TUH_ENABLED
+    return usbh_edpt_claim(s->daddr, s->ep_addr);
+    #endif
+  }else
+  {
+    #if CFG_TUD_ENABLED
+    return usbd_edpt_claim(s->rhport, s->ep_addr);
+    #endif
+  }
+
+  return false;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline
+bool stream_xfer(tu_edpt_stream_t* s, uint16_t count)
+{
+  if (s->is_host)
+  {
+    #if CFG_TUH_ENABLED
+    return usbh_edpt_xfer(s->daddr, s->ep_addr, count ? s->ep_buf : NULL, count);
+    #endif
+  }else
+  {
+    #if CFG_TUD_ENABLED
+    return usbd_edpt_xfer(s->rhport, s->ep_addr, count ? s->ep_buf : NULL, count);
+    #endif
+  }
+
+  return false;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline
+bool stream_release(tu_edpt_stream_t* s)
+{
+  if (s->is_host)
+  {
+    #if CFG_TUH_ENABLED
+    return usbh_edpt_release(s->daddr, s->ep_addr);
+    #endif
+  }else
+  {
+    #if CFG_TUD_ENABLED
+    return usbd_edpt_release(s->rhport, s->ep_addr);
+    #endif
+  }
+
+  return false;
+}
+
+//--------------------------------------------------------------------+
+// Stream Write
+//--------------------------------------------------------------------+
+
+bool tu_edpt_stream_write_zlp_if_needed(tu_edpt_stream_t* s, uint32_t last_xferred_bytes)
+{
+  // ZLP condition: no pending data, last transferred bytes is multiple of packet size
+  TU_VERIFY( !tu_fifo_count(&s->ff) && last_xferred_bytes && (0 == (last_xferred_bytes & (s->ep_packetsize-1))) );
+
+  TU_VERIFY( stream_claim(s) );
+  TU_ASSERT( stream_xfer(s, 0) );
+
+  return true;
+}
+
+uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t* s)
+{
+  // skip if no data
+  TU_VERIFY( tu_fifo_count(&s->ff), 0 );
+
+  // Claim the endpoint
+  TU_VERIFY( stream_claim(s), 0 );
+
+  // Pull data from FIFO -> EP buf
+  uint16_t const count = tu_fifo_read_n(&s->ff, s->ep_buf, s->ep_bufsize);
+
+  if ( count )
+  {
+    TU_ASSERT( stream_xfer(s, count), 0 );
+    return count;
+  }else
+  {
+    // Release endpoint since we don't make any transfer
+    // Note: data is dropped if terminal is not connected
+    stream_release(s);
+    return 0;
+  }
+}
+
+uint32_t tu_edpt_stream_write(tu_edpt_stream_t* s, void const *buffer, uint32_t bufsize)
+{
+  TU_VERIFY(bufsize); // TODO support ZLP
+
+  uint16_t ret = tu_fifo_write_n(&s->ff, buffer, (uint16_t) bufsize);
+
+  // flush if fifo has more than packet size or
+  // in rare case: fifo depth is configured too small (which never reach packet size)
+  if ( (tu_fifo_count(&s->ff) >= s->ep_packetsize) || (tu_fifo_depth(&s->ff) < s->ep_packetsize) )
+  {
+    tu_edpt_stream_write_xfer(s);
+  }
+
+  return ret;
+}
+
+//--------------------------------------------------------------------+
+// Stream Read
+//--------------------------------------------------------------------+
+
+uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t* s)
+{
+  uint16_t available = tu_fifo_remaining(&s->ff);
+
+  // Prepare for incoming data but only allow what we can store in the ring buffer.
+  // TODO Actually we can still carry out the transfer, keeping count of received bytes
+  // and slowly move it to the FIFO when read().
+  // This pre-check reduces endpoint claiming
+  TU_VERIFY(available >= s->ep_packetsize);
+
+  // claim endpoint
+  TU_VERIFY(stream_claim(s), 0);
+
+  // get available again since fifo can be changed before endpoint is claimed
+  available = tu_fifo_remaining(&s->ff);
+
+  if ( available >= s->ep_packetsize )
+  {
+    // multiple of packet size limit by ep bufsize
+    uint16_t count = (uint16_t) (available & ~(s->ep_packetsize -1));
+    count = tu_min16(count, s->ep_bufsize);
+
+    TU_ASSERT( stream_xfer(s, count), 0 );
+
+    return count;
+  }else
+  {
+    // Release endpoint since we don't make any transfer
+    stream_release(s);
+    return 0;
+  }
+}
+
+uint32_t tu_edpt_stream_read(tu_edpt_stream_t* s, void* buffer, uint32_t bufsize)
+{
+  uint32_t num_read = tu_fifo_read_n(&s->ff, buffer, (uint16_t) bufsize);
+  tu_edpt_stream_read_xfer(s);
+  return num_read;
+}
+
+//--------------------------------------------------------------------+
+// Debug
+//--------------------------------------------------------------------+
+
 #if CFG_TUSB_DEBUG
 #include <ctype.h>
 

+ 4 - 0
src/tusb_option.h

@@ -256,6 +256,10 @@ typedef int make_iso_compilers_happy;
 // For backward compatible
 #define TUSB_OPT_HOST_ENABLED   CFG_TUH_ENABLED
 
+// highspeed support indicator
+#define TUH_OPT_HIGH_SPEED    (CFG_TUH_MAX_SPEED ? (CFG_TUH_MAX_SPEED & OPT_MODE_HIGH_SPEED) : TUP_RHPORT_HIGHSPEED)
+
+
 //--------------------------------------------------------------------+
 // TODO move later
 //--------------------------------------------------------------------+