Selaa lähdekoodia

refactor(serial): add host serial framework

Signed-off-by: sakumisu <1203593632@qq.com>
sakumisu 1 kuukausi sitten
vanhempi
sitoutus
da2263728a
43 muutettua tiedostoa jossa 4647 lisäystä ja 3295 poistoa
  1. 5 0
      CMakeLists.txt
  2. 22 3
      Kconfig
  3. 22 3
      Kconfig.rtt
  4. 21 3
      Kconfig.rttpkg
  5. 3 2
      README.md
  6. 3 2
      README_zh.md
  7. 5 3
      SConscript
  8. 20 7
      cherryusb.cmake
  9. 5 1
      cherryusb_config_template.h
  10. 0 294
      class/cdc/usbh_cdc_acm.c
  11. 0 50
      class/cdc/usbh_cdc_acm.h
  12. 266 0
      class/serial/usbh_cdc_acm.c
  13. 19 0
      class/serial/usbh_cdc_acm.h
  14. 410 0
      class/serial/usbh_ch34x.c
  15. 56 0
      class/serial/usbh_ch34x.h
  16. 510 0
      class/serial/usbh_cp210x.c
  17. 187 0
      class/serial/usbh_cp210x.h
  18. 407 0
      class/serial/usbh_ftdi.c
  19. 341 0
      class/serial/usbh_ftdi.h
  20. 127 0
      class/serial/usbh_gsm.c
  21. 726 0
      class/serial/usbh_pl2303.c
  22. 43 0
      class/serial/usbh_pl2303.h
  23. 719 0
      class/serial/usbh_serial.c
  24. 179 0
      class/serial/usbh_serial.h
  25. 0 379
      class/vendor/serial/usbh_ch34x.c
  26. 0 76
      class/vendor/serial/usbh_ch34x.h
  27. 0 328
      class/vendor/serial/usbh_cp210x.c
  28. 0 73
      class/vendor/serial/usbh_cp210x.h
  29. 0 510
      class/vendor/serial/usbh_ftdi.c
  30. 0 96
      class/vendor/serial/usbh_ftdi.h
  31. 0 449
      class/vendor/serial/usbh_pl2303.c
  32. 0 62
      class/vendor/serial/usbh_pl2303.h
  33. 17 0
      common/usb_util.h
  34. 104 47
      demo/usb_host.c
  35. 103 2
      docs/source/api/api_host.rst
  36. 93 1
      docs/source/demo/usbh_serial.rst
  37. 6 2
      osal/idf/usb_config.h
  38. 6 0
      platform/rtthread/usb_msh.c
  39. 209 0
      platform/rtthread/usbh_rtserial.c
  40. 0 899
      platform/rtthread/usbh_serial.c
  41. 5 1
      tests/bouffalolab/inc/usb_config.h
  42. 6 2
      tests/hpmicro/inc/usb_config.h
  43. 2 0
      tests/hpmicro/src/main.c

+ 5 - 0
CMakeLists.txt

@@ -26,6 +26,7 @@ if(BL_SDK_BASE)
     set(CONFIG_CHERRYUSB_HOST_CP210X 1)
     set(CONFIG_CHERRYUSB_HOST_FTDI 1)
     set(CONFIG_CHERRYUSB_HOST_PL2303 1)
+    set(CONFIG_CHERRYUSB_HOST_GSM 1)
 
     set(CONFIG_CHERRYUSB_DEVICE_BL 1)
     set(CONFIG_CHERRYUSB_HOST_EHCI_BL 1)
@@ -139,6 +140,9 @@ elseif(ESP_PLATFORM)
         if(CONFIG_CHERRYUSB_HOST_PL2303)
             target_link_libraries(${COMPONENT_LIB} INTERFACE "-u pl2303_class_info")
         endif()
+        if(CONFIG_CHERRYUSB_HOST_GSM)
+            target_link_libraries(${COMPONENT_LIB} INTERFACE "-u gsm_class_info")
+        endif()
     endif()
 
     if(CONFIG_CHERRYUSB)
@@ -194,6 +198,7 @@ elseif(HPM_SDK_BASE)
     set(CONFIG_CHERRYUSB_HOST_CP210X 1)
     set(CONFIG_CHERRYUSB_HOST_FTDI 1)
     set(CONFIG_CHERRYUSB_HOST_PL2303 1)
+    set(CONFIG_CHERRYUSB_HOST_GSM 1)
 
     set(CONFIG_CHERRYUSB_DEVICE_HPM 1)
     set(CONFIG_CHERRYUSB_HOST_EHCI_HPM 1)

+ 22 - 3
Kconfig

@@ -299,6 +299,7 @@ if CHERRYUSB
         config CHERRYUSB_HOST_CDC_ACM
             bool
             prompt "Enable usb cdc acm driver"
+            select USBHOST_SERIAL
             default n
 
         config CHERRYUSB_HOST_HID
@@ -360,21 +361,31 @@ if CHERRYUSB
         config CHERRYUSB_HOST_FTDI
             bool
             prompt "Enable usb ftdi driver"
+            select USBHOST_SERIAL
             default n
 
         config CHERRYUSB_HOST_CH34X
             bool
             prompt "Enable usb ch34x driver"
+            select USBHOST_SERIAL
             default n
 
         config CHERRYUSB_HOST_CP210X
             bool
             prompt "Enable usb cp210x driver"
+            select USBHOST_SERIAL
             default n
 
         config CHERRYUSB_HOST_PL2303
             bool
             prompt "Enable usb pl2303 driver"
+            select USBHOST_SERIAL
+            default n
+
+        config CHERRYUSB_HOST_GSM
+            bool
+            prompt "Enable usb gsm driver for 4g module"
+            select USBHOST_SERIAL
             default n
 
         config CHERRYUSB_HOST_AOA
@@ -382,6 +393,9 @@ if CHERRYUSB
             prompt "Enable usb aoa driver"
             default n
 
+        config USBHOST_SERIAL
+            bool
+
         config USBHOST_PLATFORM_CDC_ECM
             bool
 
@@ -417,12 +431,17 @@ if CHERRYUSB
             prompt "Set host control transfer timeout, unit is ms"
             default 500
 
+        config USBHOST_SERIAL_RX_SIZE
+            int
+            prompt "Set host serial rx max buffer size"
+            default 2048
+
         menu "Select USB host template, please select class driver first"
-            config TEST_USBH_CDC_ACM
+            config TEST_USBH_SERIAL
                 int
-                prompt "demo for test cdc acm"
+                prompt "demo for test serial"
                 default 0
-                depends on CHERRYUSB_HOST_CDC_ACM
+                depends on CHERRYUSB_HOST_CDC_ACM || CHERRYUSB_HOST_FTDI || CHERRYUSB_HOST_CH34X || CHERRYUSB_HOST_CP210X || CHERRYUSB_HOST_PL2303
             config TEST_USBH_HID
                 int
                 prompt "demo for test hid"

+ 22 - 3
Kconfig.rtt

@@ -315,6 +315,7 @@ if RT_USING_CHERRYUSB
         config RT_CHERRYUSB_HOST_CDC_ACM
             bool
             prompt "Enable usb cdc acm driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
         config RT_CHERRYUSB_HOST_HID
@@ -382,23 +383,36 @@ if RT_USING_CHERRYUSB
         config RT_CHERRYUSB_HOST_FTDI
             bool
             prompt "Enable usb ftdi driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
         config RT_CHERRYUSB_HOST_CH34X
             bool
             prompt "Enable usb ch34x driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
         config RT_CHERRYUSB_HOST_CP210X
             bool
             prompt "Enable usb cp210x driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
         config RT_CHERRYUSB_HOST_PL2303
             bool
             prompt "Enable usb pl2303 driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
+        config RT_CHERRYUSB_HOST_GSM
+            bool
+            prompt "Enable usb gsm driver for 4g module"
+            select CONFIG_USBHOST_SERIAL
+            default n
+
+        config CONFIG_USBHOST_SERIAL
+            bool
+
         config CONFIG_USBHOST_PLATFORM_CDC_ECM
             bool
 
@@ -434,6 +448,11 @@ if RT_USING_CHERRYUSB
             prompt "Set host control transfer timeout, unit is ms"
             default 500
 
+        config CONFIG_USBHOST_SERIAL_RX_SIZE
+            int
+            prompt "Set host serial rx max buffer size"
+            default 2048
+
         config RT_LWIP_PBUF_POOL_BUFSIZE
             int "The size of each pbuf in the pbuf pool"
             range 1500 2000
@@ -445,11 +464,11 @@ if RT_USING_CHERRYUSB
             default "/"
 
         menu "Select USB host template, please select class driver first"
-            config CONFIG_TEST_USBH_CDC_ACM
+            config CONFIG_TEST_USBH_SERIAL
                 int
-                prompt "demo for test cdc acm, cannot enable this demo, we have used serial framework instead"
+                prompt "demo for test serial, cannot enable this demo, we have used serial framework instead"
                 default 0
-                depends on RT_CHERRYUSB_HOST_CDC_ACM
+                depends on RT_CHERRYUSB_HOST_CDC_ACM || RT_CHERRYUSB_HOST_FTDI || RT_CHERRYUSB_HOST_CH34X || RT_CHERRYUSB_HOST_CP210X || RT_CHERRYUSB_HOST_PL2303
             config CONFIG_TEST_USBH_HID
                 int
                 prompt "demo for test hid"

+ 21 - 3
Kconfig.rttpkg

@@ -381,23 +381,36 @@ if PKG_USING_CHERRYUSB
         config PKG_CHERRYUSB_HOST_FTDI
             bool
             prompt "Enable usb ftdi driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
         config PKG_CHERRYUSB_HOST_CH34X
             bool
             prompt "Enable usb ch34x driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
         config PKG_CHERRYUSB_HOST_CP210X
             bool
             prompt "Enable usb cp210x driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
         config PKG_CHERRYUSB_HOST_PL2303
             bool
             prompt "Enable usb pl2303 driver"
+            select CONFIG_USBHOST_SERIAL
             default n
 
+        config PKG_CHERRYUSB_HOST_GSM
+            bool
+            prompt "Enable usb gsm driver for 4g module"
+            select CONFIG_USBHOST_SERIAL
+            default n
+
+        config CONFIG_USBHOST_SERIAL
+            bool
+
         config CONFIG_USBHOST_PLATFORM_CDC_ECM
             bool
 
@@ -433,6 +446,11 @@ if PKG_USING_CHERRYUSB
             prompt "Set host control transfer timeout, unit is ms"
             default 500
 
+        config CONFIG_USBHOST_SERIAL_RX_SIZE
+            int
+            prompt "Set host serial rx max buffer size"
+            default 2048
+
         config RT_LWIP_PBUF_POOL_BUFSIZE
             int "The size of each pbuf in the pbuf pool"
             range 1500 2000
@@ -444,11 +462,11 @@ if PKG_USING_CHERRYUSB
             default "/"
 
         menu "Select USB host template, please select class driver first"
-            config CONFIG_TEST_USBH_CDC_ACM
+            config CONFIG_TEST_USBH_SERIAL
                 int
-                prompt "demo for test cdc acm, cannot enable this demo, we have used serial framework instead"
+                prompt "demo for test usb serial, cannot enable this demo, we have used serial framework instead"
                 default 0
-                depends on PKG_CHERRYUSB_HOST_CDC_ACM
+                depends on PKG_CHERRYUSB_HOST_CDC_ACM || PKG_CHERRYUSB_HOST_FTDI || PKG_CHERRYUSB_HOST_CH34X || PKG_CHERRYUSB_HOST_CP210X || PKG_CHERRYUSB_HOST_PL2303
             config CONFIG_TEST_USBH_HID
                 int
                 prompt "demo for test hid"

+ 3 - 2
README.md

@@ -112,7 +112,8 @@ CherryUSB Host Stack has the following functions:
 - Support USB Audio CLASS (UAC1.0)
 - Support Remote NDIS (RNDIS)
 - Support USB Bluetooth class (support nimble and zephyr bluetooth stack, support **CLASS:0xE0** or vendor class like cdc acm)
-- Support Vendor class (serial, net, wifi)
+- Support Vendor Serial Class(CH34X、CP210X、PL2303、FTDI、GSM)
+- Support Vendor network Class(RTL8152、AX88772)
 - Support USB modeswitch
 - Support Android Open Accessory
 - Support multi host with the same USB IP
@@ -150,7 +151,7 @@ Among them, `sizeof(struct usbh_hub)` and `sizeof(struct usbh_hubport)` are affe
 x is affected by the following macros:
 
 ```
-#define CONFIG_USBHOST_MAX_CDC_ACM_CLASS 4
+#define CONFIG_USBHOST_MAX_SERIAL_CLASS  4
 #define CONFIG_USBHOST_MAX_HID_CLASS     4
 #define CONFIG_USBHOST_MAX_MSC_CLASS     2
 #define CONFIG_USBHOST_MAX_AUDIO_CLASS   1

+ 3 - 2
README_zh.md

@@ -112,7 +112,8 @@ CherryUSB Host 协议栈当前实现以下功能:
 - Support USB Audio CLASS (UAC1.0)
 - 支持 Remote NDIS (RNDIS)
 - 支持 USB Bluetooth (支持 nimble and zephyr bluetooth 协议栈,支持 **CLASS: 0xE0** 或者厂家自定义类,类似于 cdc acm 功能)
-- 支持 Vendor 类 class (serial, net, wifi)
+- 支持 Vendor Serial 类(CH34X、CP210X、PL2303、FTDI、GSM)
+- 支持 Vendor network 类(RTL8152、AX88772)
 - 支持 USB modeswitch
 - 支持 Android Open Accessory
 - 支持相同 USB IP 的多主机
@@ -150,7 +151,7 @@ CherryUSB Host 协议栈资源占用说明(GCC 10.2 with -O2,关闭 log)
 x 受以下宏影响:
 
 ```
-#define CONFIG_USBHOST_MAX_CDC_ACM_CLASS 4
+#define CONFIG_USBHOST_MAX_SERIAL_CLASS  4
 #define CONFIG_USBHOST_MAX_HID_CLASS     4
 #define CONFIG_USBHOST_MAX_MSC_CLASS     2
 #define CONFIG_USBHOST_MAX_AUDIO_CLASS   1

+ 5 - 3
SConscript

@@ -14,7 +14,7 @@ path += [cwd + '/class/wireless']
 path += [cwd + '/class/midi']
 path += [cwd + '/class/adb']
 path += [cwd + '/class/dfu']
-path += [cwd + '/class/midi']
+path += [cwd + '/class/serial']
 path += [cwd + '/class/vendor/net']
 path += [cwd + '/class/vendor/serial']
 path += [cwd + '/class/vendor/wifi']
@@ -306,8 +306,10 @@ if GetDepend(['PKG_CHERRYUSB_HOST']):
         or GetDepend(['PKG_CHERRYUSB_HOST_FTDI'])   \
         or GetDepend(['PKG_CHERRYUSB_HOST_CH34X'])  \
         or GetDepend(['PKG_CHERRYUSB_HOST_CP210X']) \
-        or GetDepend(['PKG_CHERRYUSB_HOST_PL2303']):
-        src += Glob('platform/rtthread/usbh_serial.c')
+        or GetDepend(['PKG_CHERRYUSB_HOST_PL2303']) \
+        or GetDepend(['PKG_CHERRYUSB_HOST_GSM']):
+        src += Glob('class/serial/usbh_serial.c')
+        src += Glob('platform/rtthread/usbh_rtserial.c')
 
     if GetDepend('RT_USING_DFS') and GetDepend(['PKG_CHERRYUSB_HOST_MSC']):
        src += Glob('platform/rtthread/usbh_dfs.c')

+ 20 - 7
cherryusb.cmake

@@ -47,8 +47,8 @@ list(
     ${CMAKE_CURRENT_LIST_DIR}/class/midi
     ${CMAKE_CURRENT_LIST_DIR}/class/adb
     ${CMAKE_CURRENT_LIST_DIR}/class/dfu
+    ${CMAKE_CURRENT_LIST_DIR}/class/serial
     ${CMAKE_CURRENT_LIST_DIR}/class/vendor/net
-    ${CMAKE_CURRENT_LIST_DIR}/class/vendor/serial
     ${CMAKE_CURRENT_LIST_DIR}/class/vendor/wifi
     ${CMAKE_CURRENT_LIST_DIR}/class/aoa
 )
@@ -156,7 +156,7 @@ if(CONFIG_CHERRYUSB_HOST)
     )
 
     if(CONFIG_CHERRYUSB_HOST_CDC_ACM)
-        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/cdc/usbh_cdc_acm.c)
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/serial/usbh_cdc_acm.c)
     endif()
     if(CONFIG_CHERRYUSB_HOST_CDC_ECM)
         list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/cdc/usbh_cdc_ecm.c)
@@ -235,21 +235,34 @@ if(CONFIG_CHERRYUSB_HOST)
         list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/vendor/net/usbh_rtl8152.c)
     endif()
     if(CONFIG_CHERRYUSB_HOST_CH34X)
-        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/vendor/serial/usbh_ch34x.c)
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/serial/usbh_ch34x.c)
     endif()
     if(CONFIG_CHERRYUSB_HOST_CP210X)
-        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/vendor/serial/usbh_cp210x.c)
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/serial/usbh_cp210x.c)
     endif()
     if(CONFIG_CHERRYUSB_HOST_FTDI)
-        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/vendor/serial/usbh_ftdi.c)
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/serial/usbh_ftdi.c)
     endif()
     if(CONFIG_CHERRYUSB_HOST_PL2303)
-        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/vendor/serial/usbh_pl2303.c)
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/serial/usbh_pl2303.c)
+    endif()
+    if(CONFIG_CHERRYUSB_HOST_GSM)
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/serial/usbh_gsm.c)
     endif()
     if(CONFIG_CHERRYUSB_HOST_AOA)
         list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/aoa/usbh_aoa.c)
     endif()
 
+    if(CONFIG_CHERRYUSB_HOST_CDC_ACM
+    OR CONFIG_CHERRYUSB_HOST_CH34X
+    OR CONFIG_CHERRYUSB_HOST_CP210X
+    OR CONFIG_CHERRYUSB_HOST_FTDI
+    OR CONFIG_CHERRYUSB_HOST_PL2303
+    OR CONFIG_CHERRYUSB_HOST_GSM
+    )
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/serial/usbh_serial.c)
+    endif()
+
     if(CONFIG_CHERRYUSB_HOST_CDC_ECM
     OR CONFIG_CHERRYUSB_HOST_CDC_RNDIS
     OR CONFIG_CHERRYUSB_HOST_CDC_NCM
@@ -331,7 +344,7 @@ if(CONFIG_CHERRYUSB_HOST)
         list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/port/rp2040/usb_hc_rp2040.c)
     endif()
 
-    if(CONFIG_TEST_USBH_CDC_ACM OR CONFIG_TEST_USBH_HID OR CONFIG_TEST_USBH_MSC)
+    if(CONFIG_TEST_USBH_SERIAL OR CONFIG_TEST_USBH_HID OR CONFIG_TEST_USBH_MSC)
         list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/demo/usb_host.c)
     endif()
 endif()

+ 5 - 1
cherryusb_config_template.h

@@ -157,7 +157,7 @@
 #define CONFIG_USBHOST_MAX_INTF_ALTSETTINGS 2
 #define CONFIG_USBHOST_MAX_ENDPOINTS        4
 
-#define CONFIG_USBHOST_MAX_CDC_ACM_CLASS 4
+#define CONFIG_USBHOST_MAX_SERIAL_CLASS  4
 #define CONFIG_USBHOST_MAX_HID_CLASS     4
 #define CONFIG_USBHOST_MAX_MSC_CLASS     2
 #define CONFIG_USBHOST_MAX_AUDIO_CLASS   1
@@ -188,6 +188,10 @@
 #define CONFIG_USBHOST_CONTROL_TRANSFER_TIMEOUT 500
 #endif
 
+#ifndef CONFIG_USBHOST_SERIAL_RX_SIZE
+#define CONFIG_USBHOST_SERIAL_RX_SIZE 2048
+#endif
+
 #ifndef CONFIG_USBHOST_MSC_TIMEOUT
 #define CONFIG_USBHOST_MSC_TIMEOUT 5000
 #endif

+ 0 - 294
class/cdc/usbh_cdc_acm.c

@@ -1,294 +0,0 @@
-/*
- * Copyright (c) 2022, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#include "usbh_core.h"
-#include "usbh_cdc_acm.h"
-
-#undef USB_DBG_TAG
-#define USB_DBG_TAG "usbh_cdc_acm"
-#include "usb_log.h"
-
-#define DEV_FORMAT "/dev/ttyACM%d"
-
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cdc_acm_buf[CONFIG_USBHOST_MAX_CDC_ACM_CLASS][USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
-
-static struct usbh_cdc_acm g_cdc_acm_class[CONFIG_USBHOST_MAX_CDC_ACM_CLASS];
-static uint32_t g_devinuse = 0;
-
-static struct usbh_cdc_acm *usbh_cdc_acm_class_alloc(void)
-{
-    uint8_t devno;
-
-    for (devno = 0; devno < CONFIG_USBHOST_MAX_CDC_ACM_CLASS; devno++) {
-        if ((g_devinuse & (1U << devno)) == 0) {
-            g_devinuse |= (1U << devno);
-            memset(&g_cdc_acm_class[devno], 0, sizeof(struct usbh_cdc_acm));
-            g_cdc_acm_class[devno].minor = devno;
-            return &g_cdc_acm_class[devno];
-        }
-    }
-    return NULL;
-}
-
-static void usbh_cdc_acm_class_free(struct usbh_cdc_acm *cdc_acm_class)
-{
-    uint8_t devno = cdc_acm_class->minor;
-
-    if (devno < 32) {
-        g_devinuse &= ~(1U << devno);
-    }
-    memset(cdc_acm_class, 0, sizeof(struct usbh_cdc_acm));
-}
-
-int usbh_cdc_acm_set_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding)
-{
-    struct usb_setup_packet *setup;
-
-    if (!cdc_acm_class || !cdc_acm_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cdc_acm_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
-    setup->wValue = 0;
-    setup->wIndex = cdc_acm_class->intf;
-    setup->wLength = 7;
-
-    memcpy(g_cdc_acm_buf[cdc_acm_class->minor], line_coding, sizeof(struct cdc_line_coding));
-
-    return usbh_control_transfer(cdc_acm_class->hport, setup, g_cdc_acm_buf[cdc_acm_class->minor]);
-}
-
-int usbh_cdc_acm_get_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding)
-{
-    struct usb_setup_packet *setup;
-    int ret;
-
-    if (!cdc_acm_class || !cdc_acm_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cdc_acm_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
-    setup->wValue = 0;
-    setup->wIndex = cdc_acm_class->intf;
-    setup->wLength = 7;
-
-    ret = usbh_control_transfer(cdc_acm_class->hport, setup, g_cdc_acm_buf[cdc_acm_class->minor]);
-    if (ret < 0) {
-        return ret;
-    }
-    memcpy(line_coding, g_cdc_acm_buf[cdc_acm_class->minor], sizeof(struct cdc_line_coding));
-    return ret;
-}
-
-int usbh_cdc_acm_set_line_state(struct usbh_cdc_acm *cdc_acm_class, bool dtr, bool rts)
-{
-    struct usb_setup_packet *setup;
-
-    if (!cdc_acm_class || !cdc_acm_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cdc_acm_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
-    setup->wValue = (dtr << 0) | (rts << 1);
-    setup->wIndex = cdc_acm_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(cdc_acm_class->hport, setup, NULL);
-}
-
-static int usbh_cdc_acm_connect(struct usbh_hubport *hport, uint8_t intf)
-{
-    struct usb_endpoint_descriptor *ep_desc;
-    int ret = 0;
-
-    struct usbh_cdc_acm *cdc_acm_class = usbh_cdc_acm_class_alloc();
-    if (cdc_acm_class == NULL) {
-        USB_LOG_ERR("Fail to alloc cdc_acm_class\r\n");
-        return -USB_ERR_NOMEM;
-    }
-
-    cdc_acm_class->hport = hport;
-    cdc_acm_class->intf = intf;
-
-    hport->config.intf[intf].priv = cdc_acm_class;
-    hport->config.intf[intf + 1].priv = NULL;
-
-#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
-    ep_desc = &hport->config.intf[intf].altsetting[0].ep[0].ep_desc;
-    USBH_EP_INIT(cdc_acm_class->intin, ep_desc);
-#endif
-    for (uint8_t i = 0; i < hport->config.intf[intf + 1].altsetting[0].intf_desc.bNumEndpoints; i++) {
-        ep_desc = &hport->config.intf[intf + 1].altsetting[0].ep[i].ep_desc;
-
-        if (ep_desc->bEndpointAddress & 0x80) {
-            USBH_EP_INIT(cdc_acm_class->bulkin, ep_desc);
-        } else {
-            USBH_EP_INIT(cdc_acm_class->bulkout, ep_desc);
-        }
-    }
-
-    snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, cdc_acm_class->minor);
-
-    USB_LOG_INFO("Register CDC ACM Class:%s\r\n", hport->config.intf[intf].devname);
-
-#if 0
-    USB_LOG_INFO("Test cdc acm rx and tx and rx for 5 times, baudrate is 115200\r\n");
-
-    struct cdc_line_coding linecoding;
-    uint8_t count = 5;
-
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_cdc_acm_set_line_coding(cdc_acm_class, &linecoding);
-    usbh_cdc_acm_set_line_state(cdc_acm_class, true, false);
-
-    memset(g_cdc_acm_buf, 'a', sizeof(g_cdc_acm_buf));
-    ret = usbh_cdc_acm_bulk_out_transfer(cdc_acm_class, g_cdc_acm_buf, sizeof(g_cdc_acm_buf), 0xfffffff);
-    USB_LOG_RAW("out ret:%d\r\n", ret);
-    while (count--) {
-        ret = usbh_cdc_acm_bulk_in_transfer(cdc_acm_class, g_cdc_acm_buf, sizeof(g_cdc_acm_buf), 0xfffffff);
-        USB_LOG_RAW("in ret:%d\r\n", ret);
-        if (ret > 0) {
-            for (uint32_t i = 0; i < ret; i++) {
-                USB_LOG_RAW("%02x ", g_cdc_acm_buf[i]);
-            }
-        }
-        USB_LOG_RAW("\r\n");
-    }
-#endif
-
-    usbh_cdc_acm_run(cdc_acm_class);
-    return ret;
-}
-
-static int usbh_cdc_acm_disconnect(struct usbh_hubport *hport, uint8_t intf)
-{
-    int ret = 0;
-
-    struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)hport->config.intf[intf].priv;
-
-    if (cdc_acm_class) {
-        if (cdc_acm_class->bulkin) {
-            usbh_kill_urb(&cdc_acm_class->bulkin_urb);
-        }
-
-        if (cdc_acm_class->bulkout) {
-            usbh_kill_urb(&cdc_acm_class->bulkout_urb);
-        }
-
-#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
-        if (cdc_acm_class->intin) {
-            usbh_kill_urb(&cdc_acm_class->intin_urb);
-        }
-#endif
-
-        if (hport->config.intf[intf].devname[0] != '\0') {
-            usb_osal_thread_schedule_other();
-            USB_LOG_INFO("Unregister CDC ACM Class:%s\r\n", hport->config.intf[intf].devname);
-            usbh_cdc_acm_stop(cdc_acm_class);
-        }
-
-        usbh_cdc_acm_class_free(cdc_acm_class);
-    }
-
-    return ret;
-}
-
-int usbh_cdc_acm_bulk_in_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &cdc_acm_class->bulkin_urb;
-
-    usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkin, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-int usbh_cdc_acm_bulk_out_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &cdc_acm_class->bulkout_urb;
-
-    usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkout, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-static int usbh_cdc_data_connect(struct usbh_hubport *hport, uint8_t intf)
-{
-    (void)hport;
-    (void)intf;
-    return 0;
-}
-
-static int usbh_cdc_data_disconnect(struct usbh_hubport *hport, uint8_t intf)
-{
-    (void)hport;
-    (void)intf;
-    return 0;
-}
-
-__WEAK void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class)
-{
-    (void)cdc_acm_class;
-}
-
-__WEAK void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class)
-{
-    (void)cdc_acm_class;
-}
-
-const struct usbh_class_driver cdc_acm_class_driver = {
-    .driver_name = "cdc_acm",
-    .connect = usbh_cdc_acm_connect,
-    .disconnect = usbh_cdc_acm_disconnect
-};
-
-const struct usbh_class_driver cdc_data_class_driver = {
-    .driver_name = "cdc_data",
-    .connect = usbh_cdc_data_connect,
-    .disconnect = usbh_cdc_data_disconnect
-};
-
-CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_none_class_info = {
-    .match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
-    .bInterfaceClass = USB_DEVICE_CLASS_CDC,
-    .bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
-    .bInterfaceProtocol = CDC_COMMON_PROTOCOL_NONE,
-    .id_table = NULL,
-    .class_driver = &cdc_acm_class_driver
-};
-
-CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_at_class_info = {
-    .match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
-    .bInterfaceClass = USB_DEVICE_CLASS_CDC,
-    .bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
-    .bInterfaceProtocol = CDC_COMMON_PROTOCOL_AT_COMMANDS,
-    .id_table = NULL,
-    .class_driver = &cdc_acm_class_driver
-};
-
-CLASS_INFO_DEFINE const struct usbh_class_info cdc_data_class_info = {
-    .match_flags = USB_CLASS_MATCH_INTF_CLASS,
-    .bInterfaceClass = USB_DEVICE_CLASS_CDC_DATA,
-    .bInterfaceSubClass = 0x00,
-    .bInterfaceProtocol = 0x00,
-    .id_table = NULL,
-    .class_driver = &cdc_data_class_driver
-};

+ 0 - 50
class/cdc/usbh_cdc_acm.h

@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2022, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#ifndef USBH_CDC_ACM_H
-#define USBH_CDC_ACM_H
-
-#include "usb_cdc.h"
-
-struct usbh_cdc_acm {
-    struct usbh_hubport *hport;
-    struct usb_endpoint_descriptor *bulkin;  /* Bulk IN endpoint */
-    struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
-#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
-    struct usb_endpoint_descriptor *intin;   /* INTR IN endpoint (optional) */
-#endif
-    struct usbh_urb bulkout_urb;
-    struct usbh_urb bulkin_urb;
-#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
-    struct usbh_urb intin_urb;
-#endif
-
-    struct cdc_line_coding linecoding;
-
-    uint8_t intf;
-    uint8_t minor;
-
-    void *user_data;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int usbh_cdc_acm_set_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding);
-int usbh_cdc_acm_get_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding);
-int usbh_cdc_acm_set_line_state(struct usbh_cdc_acm *cdc_acm_class, bool dtr, bool rts);
-
-int usbh_cdc_acm_bulk_in_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-int usbh_cdc_acm_bulk_out_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-
-void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class);
-void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* USBH_CDC_ACM_H */

+ 266 - 0
class/serial/usbh_cdc_acm.c

@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2022 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_serial.h"
+#include "usbh_cdc_acm.h"
+
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbh_cdc_acm"
+#include "usb_log.h"
+
+struct usbh_cdc_acm {
+    struct usb_endpoint_descriptor *intin;
+    struct usbh_urb intin_urb;
+    struct usb_osal_timer *modem_timer;
+    uint16_t modem_status;
+};
+
+static int usbh_cdc_acm_attach(struct usbh_serial *serial)
+{
+    struct usb_endpoint_descriptor *ep_desc;
+    int ret;
+
+    struct usbh_cdc_acm *cdc_acm_class = usb_osal_malloc(sizeof(struct usbh_cdc_acm));
+    if (!cdc_acm_class) {
+        USB_LOG_ERR("No memory for cdc_acm_class\r\n");
+        return -USB_ERR_NOMEM;
+    }
+    memset(cdc_acm_class, 0, sizeof(struct usbh_cdc_acm));
+    serial->priv = cdc_acm_class;
+
+    for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
+        ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
+
+        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
+            if (ep_desc->bEndpointAddress & 0x80) {
+                USBH_EP_INIT(cdc_acm_class->intin, ep_desc);
+                break;
+            } else {
+            }
+        }
+    }
+
+    if (!cdc_acm_class->intin) {
+        USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
+        ret = -USB_ERR_NODEV;
+        goto errout;
+    }
+    return 0;
+errout:
+    serial->priv = NULL;
+    usb_osal_free(cdc_acm_class);
+    return ret;
+}
+
+static void usbh_cdc_acm_detach(struct usbh_serial *serial)
+{
+    struct usbh_cdc_acm *cdc_acm_class;
+
+    if (!serial || !serial->priv) {
+        return;
+    }
+
+    cdc_acm_class = (struct usbh_cdc_acm *)serial->priv;
+    if (cdc_acm_class->intin) {
+        usbh_kill_urb(&cdc_acm_class->intin_urb);
+    }
+    serial->priv = NULL;
+    usb_osal_free(cdc_acm_class);
+}
+
+static int usbh_cdc_acm_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
+    setup->wValue = 0;
+    setup->wIndex = serial->intf;
+    setup->wLength = 7;
+
+    memcpy(serial->iobuffer, line_coding, sizeof(struct cdc_line_coding));
+
+    return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+}
+
+static int usbh_cdc_acm_get_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
+{
+    struct usb_setup_packet *setup;
+    int ret;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
+    setup->wValue = 0;
+    setup->wIndex = serial->intf;
+    setup->wLength = 7;
+
+    ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    if (ret < 0) {
+        return ret;
+    }
+    memcpy(line_coding, serial->iobuffer, sizeof(struct cdc_line_coding));
+    return ret;
+}
+
+static int usbh_cdc_acm_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
+    setup->wValue = (dtr << 0) | (rts << 1);
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_cdc_acm_get_modem_status(struct usbh_serial *serial)
+{
+    struct usbh_cdc_acm *cdc_acm_class;
+    uintptr_t flags;
+    uint16_t status;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    flags = usb_osal_enter_critical_section();
+
+    cdc_acm_class = (struct usbh_cdc_acm *)serial->priv;
+
+    status = (cdc_acm_class->modem_status & CDC_SERIAL_STATE_TX_CARRIER ? USBH_SERIAL_TIOCM_DSR : 0) |
+             (cdc_acm_class->modem_status & CDC_SERIAL_STATE_RING ? USBH_SERIAL_TIOCM_RI : 0) |
+             (cdc_acm_class->modem_status & CDC_SERIAL_STATE_RX_CARRIER ? USBH_SERIAL_TIOCM_CD : 0) |
+             (serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
+             (serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
+
+    usb_osal_leave_critical_section(flags);
+
+    return status;
+}
+
+#ifdef CONFIG_USBH_SERIAL_GET_MODEM_STATUS
+static int __usbh_cdc_acm_get_modem_status(struct usbh_serial *serial)
+{
+    struct usbh_cdc_acm *cdc_acm_class;
+    struct cdc_acm_notification *notification;
+    uint16_t difference;
+    uintptr_t flags;
+    int ret;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+    cdc_acm_class = (struct usbh_cdc_acm *)serial->priv;
+
+    usbh_int_urb_fill(&cdc_acm_class->intin_urb, serial->hport, cdc_acm_class->intin, &serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET], cdc_acm_class->intin->wMaxPacketSize, 0xffffffff, NULL, NULL);
+    ret = usbh_submit_urb(&cdc_acm_class->intin_urb);
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (cdc_acm_class->intin_urb.actual_length < sizeof(struct cdc_acm_notification)) {
+        return -USB_ERR_INVAL;
+    }
+
+    notification = (struct cdc_acm_notification *)&serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET];
+    if (notification->bNotificationType != CDC_NOTIFICATION_SERIAL_STATE) {
+        return -USB_ERR_INVAL;
+    }
+
+    flags = usb_osal_enter_critical_section();
+
+    difference = cdc_acm_class->modem_status ^ notification->data;
+    cdc_acm_class->modem_status = notification->data;
+
+    if (difference & CDC_SERIAL_STATE_TX_CARRIER)
+        serial->iocount.dsr++;
+    if (difference & CDC_SERIAL_STATE_RX_CARRIER)
+        serial->iocount.dsr++;
+    if (notification->data & CDC_SERIAL_STATE_BREAK)
+        serial->iocount.brk++;
+    if (notification->data & CDC_SERIAL_STATE_FRAMING)
+        serial->iocount.frame++;
+    if (notification->data & CDC_SERIAL_STATE_PARITY)
+        serial->iocount.parity++;
+    if (notification->data & CDC_SERIAL_STATE_OVERRUN)
+        serial->iocount.overrun++;
+
+    usb_osal_leave_critical_section(flags);
+
+    return ret;
+}
+#endif
+
+static const struct usbh_serial_driver cdc_acm_driver = {
+    .driver_name = "cdc_acm",
+
+    .ignore_rx_header = 0,
+    .ignore_tx_header = 0,
+
+    .attach = usbh_cdc_acm_attach,
+    .detach = usbh_cdc_acm_detach,
+    .set_flow_control = NULL,
+    .set_line_coding = usbh_cdc_acm_set_line_coding,
+    .get_line_coding = usbh_cdc_acm_get_line_coding,
+    .set_line_state = usbh_cdc_acm_set_line_state,
+    .get_modem_status = usbh_cdc_acm_get_modem_status,
+};
+
+static int usbh_cdc_acm_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+    return usbh_serial_probe(hport, intf, &cdc_acm_driver) ? 0 : -USB_ERR_NOMEM;
+}
+
+static int usbh_cdc_acm_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+    struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
+
+    if (serial) {
+        usbh_serial_remove(serial);
+    }
+    return 0;
+}
+
+const struct usbh_class_driver cdc_acm_class_driver = {
+    .driver_name = "cdc_acm",
+    .connect = usbh_cdc_acm_connect,
+    .disconnect = usbh_cdc_acm_disconnect
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_none_class_info = {
+    .match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
+    .bInterfaceClass = USB_DEVICE_CLASS_CDC,
+    .bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
+    .bInterfaceProtocol = CDC_COMMON_PROTOCOL_NONE,
+    .id_table = NULL,
+    .class_driver = &cdc_acm_class_driver
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_at_class_info = {
+    .match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
+    .bInterfaceClass = USB_DEVICE_CLASS_CDC,
+    .bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
+    .bInterfaceProtocol = CDC_COMMON_PROTOCOL_AT_COMMANDS,
+    .id_table = NULL,
+    .class_driver = &cdc_acm_class_driver
+};

+ 19 - 0
class/serial/usbh_cdc_acm.h

@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2022 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBH_CDC_ACM_H
+#define USBH_CDC_ACM_H
+
+#include "usb_cdc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USBH_CDC_ACM_H */

+ 410 - 0
class/serial/usbh_ch34x.c

@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_serial.h"
+#include "usbh_ch34x.h"
+
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbh_ch43x"
+#include "usb_log.h"
+
+struct usbh_ch34x {
+    struct usb_endpoint_descriptor *intin;
+    struct usbh_urb intin_urb;
+    struct usb_osal_timer *modem_timer;
+    uint16_t modem_status;
+};
+
+/* refer to https://github.com/WCHSoftGroup/ch341ser_linux/blob/main/driver/ch341.c */
+
+static int usbh_ch34x_get_baudrate_div(uint32_t baudrate, uint8_t *factor, uint8_t *divisor)
+{
+    uint8_t a;
+    uint8_t b;
+    uint32_t c;
+
+    switch (baudrate) {
+        case 921600:
+            a = 0xf3;
+            b = 7;
+            break;
+
+        case 307200:
+            a = 0xd9;
+            b = 7;
+            break;
+
+        default:
+            if (baudrate > 6000000 / 255) {
+                b = 3;
+                c = 6000000;
+            } else if (baudrate > 750000 / 255) {
+                b = 2;
+                c = 750000;
+            } else if (baudrate > 93750 / 255) {
+                b = 1;
+                c = 93750;
+            } else {
+                b = 0;
+                c = 11719;
+            }
+            a = (uint8_t)(c / baudrate);
+            if (a == 0 || a == 0xFF) {
+                return -USB_ERR_INVAL;
+            }
+            if ((c / a - baudrate) > (baudrate - c / (a + 1))) {
+                a++;
+            }
+            a = (uint8_t)(256 - a);
+            break;
+    }
+
+    *factor = a;
+    *divisor = b;
+
+    return 0;
+}
+
+static int usbh_ch34x_control_out(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = bRequest;
+    setup->wValue = wValue;
+    setup->wIndex = wIndex;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_ch34x_control_in(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint8_t *data, uint16_t size)
+{
+    struct usb_setup_packet *setup;
+    int ret;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = bRequest;
+    setup->wValue = wValue;
+    setup->wIndex = wIndex;
+    setup->wLength = size;
+
+    ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    if (ret < 0) {
+        return ret;
+    }
+    memcpy(data, serial->iobuffer, size);
+
+    return ret;
+}
+
+static int usbh_ch34x_get_version(struct usbh_serial *serial)
+{
+    int ret;
+    uint8_t buf[2];
+
+    ret = usbh_ch34x_control_in(serial, CH34X_READ_VERSION, 0, 0, buf, 2);
+    if (ret < 0) {
+        return ret;
+    }
+
+    USB_LOG_INFO("chip version: 0x%02x\r\n", buf[0]);
+    return ret;
+}
+
+static int usbh_ch34x_attach(struct usbh_serial *serial)
+{
+    struct usb_endpoint_descriptor *ep_desc;
+    int ret;
+
+    struct usbh_ch34x *ch34x_class = usb_osal_malloc(sizeof(struct usbh_ch34x));
+    if (!ch34x_class) {
+        USB_LOG_ERR("No memory for ch34x_class\r\n");
+        return -USB_ERR_NOMEM;
+    }
+    memset(ch34x_class, 0, sizeof(struct usbh_ch34x));
+    serial->priv = ch34x_class;
+
+    for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
+        ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
+
+        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
+            if (ep_desc->bEndpointAddress & 0x80) {
+                USBH_EP_INIT(ch34x_class->intin, ep_desc);
+                break;
+            } else {
+            }
+        }
+    }
+
+    if (!ch34x_class->intin) {
+        USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
+        ret = -USB_ERR_NODEV;
+        goto errout;
+    }
+
+    ret = usbh_ch34x_get_version(serial);
+    ret |= usbh_ch34x_control_out(serial, CH34X_SERIAL_INIT, 0, 0);
+    ret |= usbh_ch34x_control_out(serial, CH34X_WRITE_REG, 0x1312, 0xd982);
+    ret |= usbh_ch34x_control_out(serial, CH34X_WRITE_REG, 0x0f2c, 0x0007);
+    if (ret < 0) {
+        goto errout;
+    }
+
+    return 0;
+errout:
+    serial->priv = NULL;
+    usb_osal_free(ch34x_class);
+    return ret;
+}
+
+static void usbh_ch34x_detach(struct usbh_serial *serial)
+{
+    struct usbh_ch34x *ch34x_class;
+
+    if (!serial || !serial->priv) {
+        return;
+    }
+
+    ch34x_class = (struct usbh_ch34x *)serial->priv;
+    if (ch34x_class->intin) {
+        usbh_kill_urb(&ch34x_class->intin_urb);
+    }
+    serial->priv = NULL;
+    usb_osal_free(ch34x_class);
+}
+
+static int usbh_ch34x_set_flow_ctrl(struct usbh_serial *serial, bool hardctrl)
+{
+    return usbh_ch34x_control_out(serial, CH34X_WRITE_REG, 0x2727, hardctrl ? 0x0101 : 0x0000);
+}
+
+static int usbh_ch34x_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
+{
+    uint16_t reg_value = 0;
+    uint16_t value = 0;
+    uint16_t index = 0;
+    uint8_t factor = 0;
+    uint8_t divisor = 0;
+
+    switch (line_coding->bParityType) {
+        case 0:
+            break;
+        case 1:
+            reg_value |= CH341_L_PO;
+            break;
+        case 2:
+            reg_value |= CH341_L_PE;
+            break;
+        case 3:
+            reg_value |= CH341_L_PM;
+            break;
+        case 4:
+            reg_value |= CH341_L_PS;
+            break;
+        default:
+            return -USB_ERR_INVAL;
+    }
+
+    switch (line_coding->bDataBits) {
+        case 5:
+            reg_value |= CH341_L_D5;
+            break;
+        case 6:
+            reg_value |= CH341_L_D6;
+            break;
+        case 7:
+            reg_value |= CH341_L_D7;
+            break;
+        case 8:
+            reg_value |= CH341_L_D8;
+            break;
+        default:
+            return -USB_ERR_INVAL;
+    }
+
+    if (line_coding->bCharFormat == 2) {
+        reg_value |= CH341_L_SB;
+    }
+
+    reg_value |= 0xC0;
+
+    value |= 0x9c;
+    value |= reg_value << 8;
+    index |= 0x80 | divisor;
+    index |= (uint16_t)factor << 8;
+
+    usbh_ch34x_get_baudrate_div(line_coding->dwDTERate, &factor, &divisor);
+
+    return usbh_ch34x_control_out(serial, CH34X_SERIAL_INIT, value, index);
+}
+
+static int usbh_ch34x_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
+{
+    uint16_t value = 0;
+    uint8_t control = 0;
+
+    control = (dtr << 5) | (rts << 6);
+    value = (uint8_t)~control;
+
+    return usbh_ch34x_control_out(serial, CH34X_MODEM_CTRL, value, 0x0000);
+}
+
+static int usbh_ch34x_get_modem_status(struct usbh_serial *serial)
+{
+    struct usbh_ch34x *ch34x_class;
+    uintptr_t flags;
+    uint16_t status;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    flags = usb_osal_enter_critical_section();
+
+    ch34x_class = (struct usbh_ch34x *)serial->priv;
+
+    status = (ch34x_class->modem_status & CH341_CTI_DS ? USBH_SERIAL_TIOCM_DSR : 0) |
+             (ch34x_class->modem_status & CH341_CTI_C ? USBH_SERIAL_TIOCM_CTS : 0) |
+             (ch34x_class->modem_status & CH341_CTRL_RI ? USBH_SERIAL_TIOCM_RI : 0) |
+             (ch34x_class->modem_status & CH341_CTI_DC ? USBH_SERIAL_TIOCM_CD : 0) |
+             (serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
+             (serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
+
+    usb_osal_leave_critical_section(flags);
+
+    return status;
+}
+
+#ifdef CONFIG_USBH_SERIAL_GET_MODEM_STATUS
+static int __usbh_ch34x_get_modem_status(struct usbh_serial *serial, uint16_t *status)
+{
+    struct usbh_ch34x *ch34x_class;
+    uint8_t type = 0;
+    uint8_t data = 0;
+    uint16_t difference;
+    uintptr_t flags;
+    int ret;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+    ch34x_class = (struct usbh_ch34x *)serial->priv;
+
+    usbh_int_urb_fill(&ch34x_class->intin_urb, serial->hport, ch34x_class->intin, &serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET], ch34x_class->intin->wMaxPacketSize, 0xffffffff, NULL, NULL);
+    ret = usbh_submit_urb(&ch34x_class->intin_urb);
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (ret < 4) {
+        return -USB_ERR_INVAL;
+    }
+
+    flags = usb_osal_enter_critical_section();
+
+    type = serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET];
+    if (type & CH341_CTT_M) {
+        data = ~serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET + 2] & CH341_CTI_ST;
+        difference = data ^ (ch34x_class->modem_status & CH341_CTI_ST);
+        ch34x_class->modem_status = data;
+
+        if (difference) {
+            if (difference & CH341_CTI_C) {
+                serial->iocount.cts++;
+            }
+            if (difference & CH341_CTI_DS) {
+                serial->iocount.dsr++;
+            }
+            if (difference & CH341_CTRL_RI) {
+                serial->iocount.rng++;
+            }
+            if (difference & CH341_CTI_DC) {
+                serial->iocount.dcd++;
+            }
+        }
+    }
+
+    if (type & CH341_CTT_O) {
+        serial->iocount.overrun++;
+    }
+    if ((type & CH341_CTT_F) == CH341_CTT_F) {
+        serial->iocount.frame++;
+    }
+    if (type & CH341_CTT_P) {
+        serial->iocount.parity++;
+    }
+
+    usb_osal_leave_critical_section(flags);
+
+    return ret;
+}
+#endif
+
+static const struct usbh_serial_driver ch34x_driver = {
+    .driver_name = "ch34x",
+
+    .ignore_rx_header = 0,
+    .ignore_tx_header = 0,
+
+    .attach = usbh_ch34x_attach,
+    .detach = usbh_ch34x_detach,
+    .set_flow_control = usbh_ch34x_set_flow_ctrl,
+    .set_line_coding = usbh_ch34x_set_line_coding,
+    .get_line_coding = NULL,
+    .set_line_state = usbh_ch34x_set_line_state,
+    .get_modem_status = usbh_ch34x_get_modem_status,
+};
+
+static int usbh_ch34x_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+    return usbh_serial_probe(hport, intf, &ch34x_driver) ? 0 : -USB_ERR_NOMEM;
+}
+
+static int usbh_ch34x_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+    struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
+
+    if (serial) {
+        usbh_serial_remove(serial);
+    }
+
+    return 0;
+}
+
+static const uint16_t ch34x_id_table[][2] = {
+    { 0x1A86, 0x7523 }, /* ch340 chip */
+    { 0x1A86, 0x7522 }, /* ch340k chip */
+    { 0x1A86, 0x5523 }, /* ch341 chip */
+    { 0x1A86, 0xe523 }, /* ch330 chip */
+    { 0x4348, 0x5523 }, /* ch340 custom chip */
+    { 0, 0 },
+};
+
+const struct usbh_class_driver ch34x_class_driver = {
+    .driver_name = "ch34x",
+    .connect = usbh_ch34x_connect,
+    .disconnect = usbh_ch34x_disconnect
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info ch34x_class_info = {
+    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
+    .bInterfaceClass = 0xff,
+    .bInterfaceSubClass = 0x00,
+    .bInterfaceProtocol = 0x00,
+    .id_table = ch34x_id_table,
+    .class_driver = &ch34x_class_driver
+};

+ 56 - 0
class/serial/usbh_ch34x.h

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBH_CH34X_H
+#define USBH_CH34X_H
+
+#include "usb_cdc.h"
+
+/* Requests */
+#define CH34X_READ_VERSION 0x5F
+#define CH34X_WRITE_REG    0x9A
+#define CH34X_READ_REG     0x95
+#define CH34X_SERIAL_INIT  0xA1
+#define CH34X_MODEM_CTRL   0xA4
+
+// modem control bits
+#define CH34X_BIT_RTS (1 << 6)
+#define CH34X_BIT_DTR (1 << 5)
+
+#define CH341_CTO_O   0x10
+#define CH341_CTO_D   0x20
+#define CH341_CTO_R   0x40
+#define CH341_CTI_C   0x01
+#define CH341_CTI_DS  0x02
+#define CH341_CTRL_RI 0x04
+#define CH341_CTI_DC  0x08
+#define CH341_CTI_ST  0x0f
+
+#define CH341_CTT_M BIT(3)
+#define CH341_CTT_F (BIT(2) | BIT(6))
+#define CH341_CTT_P BIT(2)
+#define CH341_CTT_O BIT(1)
+
+#define CH341_L_ER 0x80
+#define CH341_L_ET 0x40
+#define CH341_L_PS 0x38
+#define CH341_L_PM 0x28
+#define CH341_L_PE 0x18
+#define CH341_L_PO 0x08
+#define CH341_L_SB 0x04
+#define CH341_L_D8 0x03
+#define CH341_L_D7 0x02
+#define CH341_L_D6 0x01
+#define CH341_L_D5 0x00
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USBH_CH34X_H */

+ 510 - 0
class/serial/usbh_cp210x.c

@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_serial.h"
+#include "usbh_cp210x.h"
+
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbh_cp210x"
+#include "usb_log.h"
+
+struct usbh_cp210x {
+    uint8_t partnum;
+    uint32_t fw_version;
+    uint32_t min_speed;
+    uint32_t max_speed;
+    bool use_actual_rate;
+    bool no_flow_control;
+    bool no_event_mode;
+};
+
+struct cp210x_rate {
+    uint32_t rate;
+    uint32_t high;
+};
+
+static const struct cp210x_rate cp210x_an205_table1[] = {
+    { 300, 300 },
+    { 600, 600 },
+    { 1200, 1200 },
+    { 1800, 1800 },
+    { 2400, 2400 },
+    { 4000, 4000 },
+    { 4800, 4803 },
+    { 7200, 7207 },
+    { 9600, 9612 },
+    { 14400, 14428 },
+    { 16000, 16062 },
+    { 19200, 19250 },
+    { 28800, 28912 },
+    { 38400, 38601 },
+    { 51200, 51558 },
+    { 56000, 56280 },
+    { 57600, 58053 },
+    { 64000, 64111 },
+    { 76800, 77608 },
+    { 115200, 117028 },
+    { 128000, 129347 },
+    { 153600, 156868 },
+    { 230400, 237832 },
+    { 250000, 254234 },
+    { 256000, 273066 },
+    { 460800, 491520 },
+    { 500000, 567138 },
+    { 576000, 670254 },
+    { 921600, 0xffffffff }
+};
+
+/*
+ * Quantises the baud rate as per AN205 Table 1
+ */
+static uint32_t cp210x_get_an205_rate(uint32_t baud)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(cp210x_an205_table1); ++i) {
+        if (baud <= cp210x_an205_table1[i].high)
+            break;
+    }
+
+    return cp210x_an205_table1[i].rate;
+}
+
+static uint32_t cp210x_get_actual_rate(uint32_t baud)
+{
+    unsigned int prescale = 1;
+    unsigned int div;
+
+    if (baud <= 365)
+        prescale = 4;
+
+    div = DIV_ROUND_CLOSEST(48000000, 2 * prescale * baud);
+    baud = 48000000 / (2 * prescale * div);
+
+    return baud;
+}
+
+static void usbh_cp210x_init_max_speed(struct usbh_serial *serial)
+{
+    struct usbh_cp210x *cp210x_class;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return;
+    }
+
+    cp210x_class = (struct usbh_cp210x *)serial->priv;
+
+    bool use_actual_rate = false;
+    uint32_t min = 300;
+    uint32_t max;
+
+    switch (cp210x_class->partnum) {
+        case CP210X_PARTNUM_CP2101:
+            max = 921600;
+            break;
+        case CP210X_PARTNUM_CP2102:
+        case CP210X_PARTNUM_CP2103:
+            max = 1000000;
+            break;
+        case CP210X_PARTNUM_CP2104:
+            use_actual_rate = true;
+            max = 2000000;
+            break;
+        case CP210X_PARTNUM_CP2108:
+            max = 2000000;
+            break;
+        case CP210X_PARTNUM_CP2105:
+            if (serial->intf == 0) {
+                use_actual_rate = true;
+                max = 2000000; /* ECI */
+            } else {
+                min = 2400;
+                max = 921600; /* SCI */
+            }
+            break;
+        case CP210X_PARTNUM_CP2102N_QFN28:
+        case CP210X_PARTNUM_CP2102N_QFN24:
+        case CP210X_PARTNUM_CP2102N_QFN20:
+            use_actual_rate = true;
+            max = 3000000;
+            break;
+        default:
+            max = 2000000;
+            break;
+    }
+
+    cp210x_class->min_speed = min;
+    cp210x_class->max_speed = max;
+    cp210x_class->use_actual_rate = use_actual_rate;
+}
+
+static int usbh_cp210x_control_out(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint8_t *data, uint16_t size)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = bRequest;
+    setup->wValue = wValue;
+    setup->wIndex = wIndex;
+    setup->wLength = size;
+
+    if (data && size) {
+        memcpy(serial->iobuffer, data, size);
+        return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    } else {
+        return usbh_control_transfer(serial->hport, setup, NULL);
+    }
+}
+
+static int usbh_cp210x_control_in(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint8_t *data, uint16_t size)
+{
+    struct usb_setup_packet *setup;
+    int ret;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = bRequest;
+    setup->wValue = wValue;
+    setup->wIndex = wIndex;
+    setup->wLength = size;
+
+    ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    if (ret < 0) {
+        return ret;
+    }
+    memcpy(data, serial->iobuffer, size);
+
+    return ret;
+}
+
+static int usbh_cp210x_get_partnum(struct usbh_serial *serial)
+{
+    uint8_t version[3];
+    struct usbh_cp210x *cp210x_class;
+    int ret;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    cp210x_class = (struct usbh_cp210x *)serial->priv;
+
+    ret = usbh_cp210x_control_in(serial, CP210X_VENDOR_SPECIFIC, CP210X_GET_PARTNUM, serial->intf, (uint8_t *)&cp210x_class->partnum, 1);
+    if (ret < 0) {
+        return ret;
+    }
+
+    USB_LOG_INFO("chip partnum: 0x%02x\r\n", cp210x_class->partnum);
+
+    switch (cp210x_class->partnum) {
+        case CP210X_PARTNUM_CP2102:
+            break;
+        case CP210X_PARTNUM_CP2105:
+        case CP210X_PARTNUM_CP2108:
+            ret = usbh_cp210x_control_in(serial, CP210X_VENDOR_SPECIFIC, CP210X_GET_FW_VER_2N, serial->intf, version, 3);
+            if (ret < 0) {
+                return ret;
+            }
+            cp210x_class->fw_version = version[0] << 16 | version[1] << 8 | version[2];
+            break;
+        case CP210X_PARTNUM_CP2102N_QFN28:
+        case CP210X_PARTNUM_CP2102N_QFN24:
+        case CP210X_PARTNUM_CP2102N_QFN20:
+            ret = usbh_cp210x_control_in(serial, CP210X_VENDOR_SPECIFIC, CP210X_GET_FW_VER_2N, serial->intf, version, 3);
+            if (ret < 0) {
+                return ret;
+            }
+            cp210x_class->fw_version = version[0] << 16 | version[1] << 8 | version[2];
+            if (cp210x_class->fw_version <= 0x10004)
+                cp210x_class->no_flow_control = true;
+            break;
+        default:
+            break;
+    }
+    return ret;
+}
+
+static int usbh_cp210x_enable(struct usbh_serial *serial)
+{
+    return usbh_cp210x_control_out(serial, CP210X_IFC_ENABLE, 1, serial->intf, NULL, 0);
+}
+
+static int usbh_cp210x_set_chars(struct usbh_serial *serial)
+{
+    struct cp210x_special_chars chars = { 0 };
+
+    return usbh_cp210x_control_out(serial, CP210X_SET_CHARS, 0, serial->intf, (uint8_t *)&chars, sizeof(struct cp210x_special_chars));
+}
+
+// static int usbh_cp210x_get_common_status(struct usbh_serial *serial, struct cp210x_comm_status *status)
+// {
+//     return usbh_cp210x_control_in(serial, CP210X_GET_COMM_STATUS, 0, serial->intf, (uint8_t *)status, sizeof(struct cp210x_comm_status));
+// }
+
+static int usbh_cp210x_set_baudrate(struct usbh_serial *serial, uint32_t baudrate)
+{
+    struct usb_setup_packet *setup;
+    struct usbh_cp210x *cp210x_class;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+    cp210x_class = (struct usbh_cp210x *)serial->priv;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CP210X_SET_BAUDRATE;
+    setup->wValue = 0;
+    setup->wIndex = serial->intf;
+    setup->wLength = 4;
+
+    if (cp210x_class->use_actual_rate)
+        baudrate = cp210x_get_actual_rate(baudrate);
+    else if (baudrate < 1000000)
+        baudrate = cp210x_get_an205_rate(baudrate);
+
+    memcpy(serial->iobuffer, (uint8_t *)&baudrate, 4);
+    return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+}
+
+static int usbh_cp210x_set_data_format(struct usbh_serial *serial, uint8_t databits, uint8_t parity, uint8_t stopbits)
+{
+    struct usb_setup_packet *setup;
+    uint16_t value;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    value = ((databits & 0x0F) << 8) | ((parity & 0x0f) << 4) | ((stopbits & 0x03) << 0);
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CP210X_SET_LINE_CTL;
+    setup->wValue = value;
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_cp210x_attach(struct usbh_serial *serial)
+{
+    struct cp210x_comm_status status = { 0 };
+    int ret;
+
+    struct usbh_cp210x *cp210x_class = usb_osal_malloc(sizeof(struct usbh_cp210x));
+    if (!cp210x_class) {
+        return -USB_ERR_NOMEM;
+    }
+    memset(cp210x_class, 0, sizeof(struct usbh_cp210x));
+    serial->priv = cp210x_class;
+
+    ret = usbh_cp210x_get_partnum(serial);
+    usbh_cp210x_init_max_speed(serial);
+    ret |= usbh_cp210x_enable(serial);
+    ret |= usbh_cp210x_set_chars(serial);
+    if (ret < 0) {
+        goto errout;
+    }
+
+    USB_LOG_INFO("ulAmountInInQueue: %u, ulAmountInOutQueue: %u\r\n", (unsigned int)status.ulAmountInInQueue, (unsigned int)status.ulAmountInOutQueue);
+    return 0;
+errout:
+    serial->priv = NULL;
+    usb_osal_free(cp210x_class);
+    return ret;
+}
+
+static void usbh_cp210x_detach(struct usbh_serial *serial)
+{
+    if (serial && serial->priv) {
+        serial->priv = NULL;
+        usb_osal_free(serial->priv);
+    }
+}
+
+int usbh_cp210x_set_flow_ctrl(struct usbh_serial *serial, bool enable)
+{
+    struct cp210x_flow_ctl flow_ctl = { 0 };
+    uint32_t flow_repl;
+    uint32_t ctl_hs;
+    int ret;
+
+    ret = usbh_cp210x_control_in(serial, CP210X_GET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
+    if (ret < 0) {
+        return ret;
+    }
+
+    ctl_hs = flow_ctl.lControlHandshake;
+    flow_repl = flow_ctl.lFlowReplace;
+
+    ctl_hs &= ~CP210X_SERIAL_DSR_HANDSHAKE;
+    ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE;
+    ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY;
+    ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
+    ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
+
+    flow_repl &= ~CP210X_SERIAL_RTS_MASK;
+    flow_repl &= ~CP210X_SERIAL_AUTO_RECEIVE;
+    flow_repl &= ~CP210X_SERIAL_AUTO_TRANSMIT;
+    flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
+
+    flow_repl &= ~CP210X_SERIAL_RTS_MASK;
+    if (enable) {
+        ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
+    } else {
+        ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
+    }
+    flow_ctl.lControlHandshake = ctl_hs;
+    flow_ctl.lFlowReplace = flow_repl;
+
+    return usbh_cp210x_control_out(serial, CP210X_SET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
+}
+
+int usbh_cp210x_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
+{
+    int ret;
+
+    ret = usbh_cp210x_set_baudrate(serial, line_coding->dwDTERate);
+    if (ret < 0) {
+        return ret;
+    }
+    return usbh_cp210x_set_data_format(serial, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat);
+}
+
+int usbh_cp210x_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
+{
+    struct cp210x_flow_ctl flow_ctl = { 0 };
+    uint32_t flow_repl;
+    uint32_t ctl_hs;
+    uint16_t control = 0;
+    int ret;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->rtscts) {
+        ret = usbh_cp210x_control_in(serial, CP210X_GET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
+        if (ret < 0) {
+            return ret;
+        }
+        ctl_hs = flow_ctl.lControlHandshake;
+        flow_repl = flow_ctl.lFlowReplace;
+
+        ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
+        if (dtr)
+            ctl_hs |= CP210X_SERIAL_DTR_ACTIVE;
+        else
+            ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
+
+        flow_repl &= ~CP210X_SERIAL_RTS_MASK;
+        if (rts)
+            flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
+        else
+            flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
+
+        flow_ctl.lControlHandshake = ctl_hs;
+        flow_ctl.lFlowReplace = flow_repl;
+
+        return usbh_cp210x_control_out(serial, CP210X_SET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
+    } else {
+        if (dtr) {
+            control |= CP210X_CONTROL_DTR;
+        }
+        if (rts) {
+            control |= CP210X_CONTROL_RTS;
+        }
+        control |= CP210X_CONTROL_WRITE_DTR;
+        control |= CP210X_CONTROL_WRITE_RTS;
+        return usbh_cp210x_control_out(serial, CP210X_SET_MHS, control, serial->intf, NULL, 0);
+    }
+}
+
+static int usbh_cp210x_get_modem_status(struct usbh_serial *serial)
+{
+    int ret;
+    uint8_t control;
+    uint16_t status;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+
+    ret = usbh_cp210x_control_in(serial, CP210X_GET_MDMSTS, 0, serial->intf, (uint8_t *)&control, 1);
+    if (ret < 0) {
+        return ret;
+    }
+
+    status = ((control & CP210X_CONTROL_DTR) ? USBH_SERIAL_TIOCM_DTR : 0) |
+             ((control & CP210X_CONTROL_RTS) ? USBH_SERIAL_TIOCM_RTS : 0) |
+             ((control & CP210X_CONTROL_CTS) ? USBH_SERIAL_TIOCM_CTS : 0) |
+             ((control & CP210X_CONTROL_DSR) ? USBH_SERIAL_TIOCM_DSR : 0) |
+             ((control & CP210X_CONTROL_RING) ? USBH_SERIAL_TIOCM_RI : 0) |
+             ((control & CP210X_CONTROL_DCD) ? USBH_SERIAL_TIOCM_CD : 0);
+
+    return status;
+}
+
+static const struct usbh_serial_driver cp210x_driver = {
+    .driver_name = "cp210x",
+
+    .ignore_rx_header = 0,
+    .ignore_tx_header = 0,
+
+    .attach = usbh_cp210x_attach,
+    .detach = usbh_cp210x_detach,
+    .set_flow_control = usbh_cp210x_set_flow_ctrl,
+    .set_line_coding = usbh_cp210x_set_line_coding,
+    .get_line_coding = NULL,
+    .set_line_state = usbh_cp210x_set_line_state,
+    .get_modem_status = usbh_cp210x_get_modem_status,
+};
+
+static int usbh_cp210x_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+    return usbh_serial_probe(hport, intf, &cp210x_driver) ? 0 : -USB_ERR_NOMEM;
+}
+
+static int usbh_cp210x_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+    struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
+
+    if (serial) {
+        usbh_serial_remove(serial);
+    }
+    return 0;
+}
+
+static const uint16_t cp210x_id_table[][2] = {
+    { 0x10C4, 0xEA60 },
+    { 0, 0 },
+};
+
+const struct usbh_class_driver cp210x_class_driver = {
+    .driver_name = "cp210x",
+    .connect = usbh_cp210x_connect,
+    .disconnect = usbh_cp210x_disconnect
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info cp210x_class_info = {
+    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
+    .bInterfaceClass = 0xff,
+    .bInterfaceSubClass = 0x00,
+    .bInterfaceProtocol = 0x00,
+    .id_table = cp210x_id_table,
+    .class_driver = &cp210x_class_driver
+};

+ 187 - 0
class/serial/usbh_cp210x.h

@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBH_CP210X_H
+#define USBH_CP210X_H
+
+#include "usb_cdc.h"
+
+/* Requests */
+#define CP210X_IFC_ENABLE      0x00
+#define CP210X_SET_BAUDDIV     0x01
+#define CP210X_GET_BAUDDIV     0x02
+#define CP210X_SET_LINE_CTL    0x03 // Set parity, data bits, stop bits
+#define CP210X_GET_LINE_CTL    0x04
+#define CP210X_SET_BREAK       0x05
+#define CP210X_IMM_CHAR        0x06
+#define CP210X_SET_MHS         0x07 // Set DTR, RTS
+#define CP210X_GET_MDMSTS      0x08
+#define CP210X_SET_XON         0x09
+#define CP210X_SET_XOFF        0x0A
+#define CP210X_SET_EVENTMASK   0x0B
+#define CP210X_GET_EVENTMASK   0x0C
+#define CP210X_SET_CHAR        0x0D
+#define CP210X_GET_CHARS       0x0E
+#define CP210X_GET_PROPS       0x0F
+#define CP210X_GET_COMM_STATUS 0x10
+#define CP210X_RESET           0x11
+#define CP210X_PURGE           0x12
+#define CP210X_SET_FLOW        0x13
+#define CP210X_GET_FLOW        0x14
+#define CP210X_EMBED_EVENTS    0x15
+#define CP210X_GET_EVENTSTATE  0x16
+#define CP210X_SET_CHARS       0x19
+#define CP210X_GET_BAUDRATE    0x1D
+#define CP210X_SET_BAUDRATE    0x1E // Set baudrate
+#define CP210X_VENDOR_SPECIFIC 0xFF
+
+/* CP210X_VENDOR_SPECIFIC values */
+#define CP210X_GET_FW_VER     0x000E
+#define CP210X_READ_2NCONFIG  0x000E
+#define CP210X_GET_FW_VER_2N  0x0010
+#define CP210X_READ_LATCH     0x00C2
+#define CP210X_GET_PARTNUM    0x370B
+#define CP210X_GET_PORTCONFIG 0x370C
+#define CP210X_GET_DEVICEMODE 0x3711
+#define CP210X_WRITE_LATCH    0x37E1
+
+/* CP210X_IFC_ENABLE */
+#define CP210X_UART_ENABLE  0x0001
+#define CP210X_UART_DISABLE 0x0000
+
+/* CP210X_(SET|GET)_BAUDDIV */
+#define CP210X_BAUD_RATE_GEN_FREQ 0x384000
+
+/* CP210X_(SET|GET)_LINE_CTL */
+#define CP210X_BITS_DATA_MASK 0X0f00
+#define CP210X_BITS_DATA_5    0X0500
+#define CP210X_BITS_DATA_6    0X0600
+#define CP210X_BITS_DATA_7    0X0700
+#define CP210X_BITS_DATA_8    0X0800
+#define CP210X_BITS_DATA_9    0X0900
+
+#define CP210X_BITS_PARITY_MASK  0x00f0
+#define CP210X_BITS_PARITY_NONE  0x0000
+#define CP210X_BITS_PARITY_ODD   0x0010
+#define CP210X_BITS_PARITY_EVEN  0x0020
+#define CP210X_BITS_PARITY_MARK  0x0030
+#define CP210X_BITS_PARITY_SPACE 0x0040
+
+#define CP210X_BITS_STOP_MASK 0x000f
+#define CP210X_BITS_STOP_1    0x0000
+#define CP210X_BITS_STOP_1_5  0x0001
+#define CP210X_BITS_STOP_2    0x0002
+
+/* CP210X_SET_BREAK */
+#define CP210X_BREAK_ON  0x0001
+#define CP210X_BREAK_OFF 0x0000
+
+/* CP210X_(SET_MHS|GET_MDMSTS) */
+#define CP210X_CONTROL_DTR       0x0001
+#define CP210X_CONTROL_RTS       0x0002
+#define CP210X_CONTROL_CTS       0x0010
+#define CP210X_CONTROL_DSR       0x0020
+#define CP210X_CONTROL_RING      0x0040
+#define CP210X_CONTROL_DCD       0x0080
+#define CP210X_CONTROL_WRITE_DTR 0x0100
+#define CP210X_CONTROL_WRITE_RTS 0x0200
+
+/* CP210X_(GET|SET)_CHARS */
+struct cp210x_special_chars {
+    uint8_t bEofChar;
+    uint8_t bErrorChar;
+    uint8_t bBreakChar;
+    uint8_t bEventChar;
+    uint8_t bXonChar;
+    uint8_t bXoffChar;
+};
+
+/* CP210X_GET_COMM_STATUS returns these 0x13 bytes */
+struct cp210x_comm_status {
+    uint32_t ulErrors;
+    uint32_t ulHoldReasons;
+    uint32_t ulAmountInInQueue;
+    uint32_t ulAmountInOutQueue;
+    uint8_t bEofReceived;
+    uint8_t bWaitForImmediate;
+    uint8_t bReserved;
+} __PACKED;
+
+/*
+ * CP210X_PURGE - 16 bits passed in wValue of USB request.
+ * SiLabs app note AN571 gives a strange description of the 4 bits:
+ * bit 0 or bit 2 clears the transmit queue and 1 or 3 receive.
+ * writing 1 to all, however, purges cp2108 well enough to avoid the hang.
+ */
+#define PURGE_ALL 0x000f
+
+/* CP210X_EMBED_EVENTS */
+#define CP210X_ESCCHAR 0xec
+
+#define CP210X_LSR_OVERRUN BIT(1)
+#define CP210X_LSR_PARITY  BIT(2)
+#define CP210X_LSR_FRAME   BIT(3)
+#define CP210X_LSR_BREAK   BIT(4)
+
+/* CP210X_GET_FLOW/CP210X_SET_FLOW read/write these 0x10 bytes */
+struct cp210x_flow_ctl {
+    uint32_t lControlHandshake;
+    uint32_t lFlowReplace;
+    uint32_t lXonLimit;
+    uint32_t lXoffLimit;
+};
+
+/* cp210x_flow_ctl::ulControlHandshake */
+#define CP210X_SERIAL_DTR_MASK        (0x03 << 0)
+#define CP210X_SERIAL_DTR_INACTIVE    (0 << 0)
+#define CP210X_SERIAL_DTR_ACTIVE      (1 << 0)
+#define CP210X_SERIAL_DTR_FLOW_CTL    (2 << 0)
+#define CP210X_SERIAL_CTS_HANDSHAKE   BIT(3)
+#define CP210X_SERIAL_DSR_HANDSHAKE   BIT(4)
+#define CP210X_SERIAL_DCD_HANDSHAKE   BIT(5)
+#define CP210X_SERIAL_DSR_SENSITIVITY BIT(6)
+
+/* cp210x_flow_ctl::ulFlowReplace */
+#define CP210X_SERIAL_AUTO_TRANSMIT  BIT(0)
+#define CP210X_SERIAL_AUTO_RECEIVE   BIT(1)
+#define CP210X_SERIAL_ERROR_CHAR     BIT(2)
+#define CP210X_SERIAL_NULL_STRIPPING BIT(3)
+#define CP210X_SERIAL_BREAK_CHAR     BIT(4)
+#define CP210X_SERIAL_RTS_MASK       (0x03 << 6)
+#define CP210X_SERIAL_RTS_INACTIVE   (0 << 6)
+#define CP210X_SERIAL_RTS_ACTIVE     (1 << 6)
+#define CP210X_SERIAL_RTS_FLOW_CTL   (2 << 6)
+#define CP210X_SERIAL_XOFF_CONTINUE  BIT(31)
+
+/* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */
+struct cp210x_pin_mode {
+    uint8_t eci;
+    uint8_t sci;
+};
+
+#define CP210X_PIN_MODE_MODEM 0
+#define CP210X_PIN_MODE_GPIO  BIT(0)
+
+/* Part number definitions */
+#define CP210X_PARTNUM_CP2101        0x01
+#define CP210X_PARTNUM_CP2102        0x02
+#define CP210X_PARTNUM_CP2103        0x03
+#define CP210X_PARTNUM_CP2104        0x04
+#define CP210X_PARTNUM_CP2105        0x05
+#define CP210X_PARTNUM_CP2108        0x08
+#define CP210X_PARTNUM_CP2102N_QFN28 0x20
+#define CP210X_PARTNUM_CP2102N_QFN24 0x21
+#define CP210X_PARTNUM_CP2102N_QFN20 0x22
+#define CP210X_PARTNUM_UNKNOWN       0xFF
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USBH_CP210X_H */

+ 407 - 0
class/serial/usbh_ftdi.c

@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_serial.h"
+#include "usbh_ftdi.h"
+
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbh_ftdi"
+#include "usb_log.h"
+
+enum ftdi_chip_type {
+    SIO,
+    FT232A,
+    FT232B,
+    FT2232C,
+    FT232R,
+    FT232H,
+    FT2232H,
+    FT4232H,
+    FT4232HA,
+    FT232HP,
+    FT233HP,
+    FT2232HP,
+    FT2233HP,
+    FT4232HP,
+    FT4233HP,
+    FTX,
+};
+
+static const char *ftdi_chip_name[] = {
+    [SIO] = "SIO", /* the serial part of FT8U100AX */
+    [FT232A] = "FT232A",
+    [FT232B] = "FT232B",
+    [FT2232C] = "FT2232C/D",
+    [FT232R] = "FT232R",
+    [FT232H] = "FT232H",
+    [FT2232H] = "FT2232H",
+    [FT4232H] = "FT4232H",
+    [FT4232HA] = "FT4232HA",
+    [FT232HP] = "FT232HP",
+    [FT233HP] = "FT233HP",
+    [FT2232HP] = "FT2232HP",
+    [FT2233HP] = "FT2233HP",
+    [FT4232HP] = "FT4232HP",
+    [FT4233HP] = "FT4233HP",
+    [FTX] = "FT-X",
+};
+
+struct usbh_ftdi {
+    enum ftdi_chip_type chip_type;
+};
+
+static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, int base)
+{
+    static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
+    uint32_t divisor;
+    /* divisor shifted 3 bits to the left */
+    int divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud);
+    divisor = divisor3 >> 3;
+    divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
+    /* Deal with special cases for highest baud rates. */
+    if (divisor == 1) /* 1.0 */
+        divisor = 0;
+    else if (divisor == 0x4001) /* 1.5 */
+        divisor = 1;
+    return divisor;
+}
+
+static uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud)
+{
+    return ftdi_232bm_baud_base_to_divisor(baud, 48000000);
+}
+
+static uint32_t ftdi_2232h_baud_base_to_divisor(uint32_t baud, int base)
+{
+    static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
+    uint32_t divisor;
+    int divisor3;
+
+    /* hi-speed baud rate is 10-bit sampling instead of 16-bit */
+    divisor3 = DIV_ROUND_CLOSEST(8 * base, 10 * baud);
+
+    divisor = divisor3 >> 3;
+    divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
+    /* Deal with special cases for highest baud rates. */
+    if (divisor == 1) /* 1.0 */
+        divisor = 0;
+    else if (divisor == 0x4001) /* 1.5 */
+        divisor = 1;
+    /*
+	 * Set this bit to turn off a divide by 2.5 on baud rate generator
+	 * This enables baud rates up to 12Mbaud but cannot reach below 1200
+	 * baud with this bit set
+	 */
+    divisor |= 0x00020000;
+    return divisor;
+}
+
+static uint32_t ftdi_2232h_baud_to_divisor(uint32_t baud)
+{
+    return ftdi_2232h_baud_base_to_divisor(baud, 120000000);
+}
+
+int usbh_ftdi_reset(struct usbh_serial *serial)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = FTDI_SIO_RESET;
+    setup->wValue = 0;
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_ftdi_set_baudrate(struct usbh_serial *serial, uint32_t baudrate)
+{
+    struct usb_setup_packet *setup;
+    struct usbh_ftdi *ftdi_class;
+    uint32_t div_value;
+    uint16_t value;
+    uint8_t baudrate_high;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+    ftdi_class = (struct usbh_ftdi *)serial->priv;
+
+    switch (ftdi_class->chip_type) {
+        case FT232B:
+        case FT2232C:
+        case FT232R:
+            if (baudrate > 3000000) {
+                return -USB_ERR_INVAL;
+            }
+            div_value = ftdi_232bm_baud_to_divisor(baudrate);
+            break;
+        default:
+            if ((baudrate <= 12000000) && (baudrate >= 1200)) {
+                div_value = ftdi_2232h_baud_to_divisor(baudrate);
+            } else if (baudrate < 1200) {
+                div_value = ftdi_232bm_baud_to_divisor(baudrate);
+            } else {
+                return -USB_ERR_INVAL;
+            }
+            break;
+    }
+
+    value = div_value & 0xFFFF;
+    baudrate_high = (div_value >> 16) & 0xff;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = FTDI_SIO_SET_BAUDRATE;
+    setup->wValue = value;
+    setup->wIndex = (baudrate_high << 8) | serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_ftdi_set_data_format(struct usbh_serial *serial, uint8_t databits, uint8_t parity, uint8_t stopbits, uint8_t isbreak)
+{
+    /**
+     * D0-D7 databits  BITS_7=7, BITS_8=8
+     * D8-D10 parity  NONE=0, ODD=1, EVEN=2, MARK=3, SPACE=4
+     * D11-D12 		STOP_BIT_1=0, STOP_BIT_15=1, STOP_BIT_2=2
+     * D14  		BREAK_OFF=0, BREAK_ON=1
+     **/
+    struct usb_setup_packet *setup;
+    uint16_t value;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    value = ((isbreak & 0x01) << 14) | ((stopbits & 0x03) << 11) | ((parity & 0x0f) << 8) | (databits & 0x0f);
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = FTDI_SIO_SET_DATA;
+    setup->wValue = value;
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_ftdi_set_latency_timer(struct usbh_serial *serial, uint16_t value)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = FTDI_SIO_SET_LATENCY_TIMER;
+    setup->wValue = value;
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_ftdi_attach(struct usbh_serial *serial)
+{
+    uint16_t version;
+    uint8_t chip_type;
+    int ret;
+
+    version = serial->hport->device_desc.bcdDevice;
+
+    switch (version) {
+        case 0x400:
+            chip_type = FT232B;
+            break;
+        case 0x500:
+            chip_type = FT2232C;
+            break;
+        case 0x600:
+            chip_type = FT232R;
+            break;
+        case 0x700:
+            chip_type = FT2232H;
+            break;
+        case 0x900:
+            chip_type = FT232H;
+            break;
+
+        default:
+            USB_LOG_ERR("Unsupported FTDI chip version: 0x%04x\r\n", version);
+            return -USB_ERR_NOTSUPP;
+    }
+
+    USB_LOG_INFO("chip name: %s\r\n", ftdi_chip_name[chip_type]);
+
+    struct usbh_ftdi *ftdi_class = usb_osal_malloc(sizeof(struct usbh_ftdi));
+    if (!ftdi_class) {
+        USB_LOG_ERR("No memory for ftdi_class\r\n");
+        return -USB_ERR_NOMEM;
+    }
+    memset(ftdi_class, 0, sizeof(struct usbh_ftdi));
+    serial->priv = ftdi_class;
+
+    ftdi_class->chip_type = chip_type;
+    ret = usbh_ftdi_set_latency_timer(serial, 0x10);
+    if (ret < 0) {
+        goto errout;
+    }
+    return 0;
+errout:
+    serial->priv = NULL;
+    usb_osal_free(ftdi_class);
+    return ret;
+}
+
+static void usbh_ftdi_detach(struct usbh_serial *serial)
+{
+    if (serial && serial->priv) {
+        serial->priv = NULL;
+        usb_osal_free(serial->priv);
+    }
+}
+
+static int usbh_ftdi_set_flow_ctrl(struct usbh_serial *serial, bool hardctrl)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = FTDI_SIO_SET_FLOW_CTRL;
+    setup->wValue = hardctrl ? FTDI_SIO_RTS_CTS_HS : FTDI_SIO_DISABLE_FLOW_CTRL;
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_ftdi_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
+{
+    int ret = usbh_ftdi_set_baudrate(serial, line_coding->dwDTERate);
+    if (ret < 0) {
+        return ret;
+    }
+    return usbh_ftdi_set_data_format(serial, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat, 0);
+}
+
+static int usbh_ftdi_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
+{
+    struct usb_setup_packet *setup;
+    uint16_t value = 0;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    value = ((dtr ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW) | (rts ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW));
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = FTDI_SIO_SET_MODEM_CTRL;
+    setup->wValue = value;
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_ftdi_get_modem_status(struct usbh_serial *serial)
+{
+    struct usb_setup_packet *setup;
+    uint16_t status = 0;
+    int ret;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = FTDI_SIO_GET_MODEM_STATUS;
+    setup->wValue = 0x0000;
+    setup->wIndex = serial->intf;
+    setup->wLength = 2;
+
+    ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    if (ret < 0) {
+        return 0;
+    }
+
+    status = (serial->iobuffer[0] & FTDI_SIO_DSR_MASK ? USBH_SERIAL_TIOCM_DSR : 0) |
+              (serial->iobuffer[0] & FTDI_SIO_CTS_MASK ? USBH_SERIAL_TIOCM_CTS : 0) |
+              (serial->iobuffer[0] & FTDI_SIO_RI_MASK ? USBH_SERIAL_TIOCM_RI : 0) |
+              (serial->iobuffer[0] & FTDI_SIO_RLSD_MASK ? USBH_SERIAL_TIOCM_CD : 0) |
+              (serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
+              (serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
+
+    return status;
+}
+
+static const struct usbh_serial_driver ftdi_driver = {
+    .driver_name = "ftdi",
+
+    .ignore_rx_header = 2,
+    .ignore_tx_header = 0,
+
+    .attach = usbh_ftdi_attach,
+    .detach = usbh_ftdi_detach,
+    .set_flow_control = usbh_ftdi_set_flow_ctrl,
+    .set_line_coding = usbh_ftdi_set_line_coding,
+    .get_line_coding = NULL,
+    .set_line_state = usbh_ftdi_set_line_state,
+    .get_modem_status = usbh_ftdi_get_modem_status,
+};
+
+static int usbh_ftdi_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+    return usbh_serial_probe(hport, intf, &ftdi_driver) ? 0 : -USB_ERR_NOMEM;
+}
+
+static int usbh_ftdi_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+    struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
+
+    if (serial) {
+        usbh_serial_remove(serial);
+    }
+    return 0;
+}
+
+static const uint16_t ftdi_id_table[][2] = {
+    { 0x0403, 0x6001 },
+    { 0x0403, 0x6010 },
+    { 0x0403, 0x6014 },
+    { 0, 0 },
+};
+
+const struct usbh_class_driver ftdi_class_driver = {
+    .driver_name = "ftdi",
+    .connect = usbh_ftdi_connect,
+    .disconnect = usbh_ftdi_disconnect
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info ftdi_class_info = {
+    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
+    .bInterfaceClass = 0xff,
+    .bInterfaceSubClass = 0x00,
+    .bInterfaceProtocol = 0x00,
+    .id_table = ftdi_id_table,
+    .class_driver = &ftdi_class_driver
+};

+ 341 - 0
class/serial/usbh_ftdi.h

@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBH_FTDI_H
+#define USBH_FTDI_H
+
+#include "usb_cdc.h"
+
+#define FTDI_VID 0x0403 /* Vendor Id */
+
+/* FTDI device PIDs */
+#define FTDI_8U232AM_PID     0x6001 /* Similar device to SIO above */
+#define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */
+#define FTDI_8U2232C_PID     0x6010 /* Dual channel device */
+#define FTDI_4232H_PID       0x6011 /* Quad channel hi-speed device */
+#define FTDI_232H_PID        0x6014 /* Single channel hi-speed device */
+#define FTDI_FTX_PID         0x6015 /* FT-X series (FT201X, FT230X, FT231X, etc) */
+#define FTDI_FT2233HP_PID    0x6040 /* Dual channel hi-speed device with PD */
+#define FTDI_FT4233HP_PID    0x6041 /* Quad channel hi-speed device with PD */
+#define FTDI_FT2232HP_PID    0x6042 /* Dual channel hi-speed device with PD */
+#define FTDI_FT4232HP_PID    0x6043 /* Quad channel hi-speed device with PD */
+#define FTDI_FT233HP_PID     0x6044 /* Dual channel hi-speed device with PD */
+#define FTDI_FT232HP_PID     0x6045 /* Dual channel hi-speed device with PD */
+#define FTDI_FT4232HA_PID    0x6048 /* Quad channel automotive grade hi-speed device */
+#define FTDI_SIO_PID         0x8372 /* Product Id SIO application of 8U100AX */
+#define FTDI_232RL_PID       0xFBFA /* Product ID for FT232RL */
+
+/* Requests */
+#define FTDI_SIO_RESET             0x00 /* Reset the port */
+#define FTDI_SIO_SET_MODEM_CTRL    0x01 /* Set the modem control register */
+#define FTDI_SIO_SET_FLOW_CTRL     0x02 /* Set flow control register */
+#define FTDI_SIO_SET_BAUDRATE      0x03 /* Set baud rate */
+#define FTDI_SIO_SET_DATA          0x04 /* Set the data characteristics of the port */
+#define FTDI_SIO_GET_MODEM_STATUS  0x05
+#define FTDI_SIO_SET_EVENT_CHAR    0x06
+#define FTDI_SIO_SET_ERROR_CHAR    0x07
+#define FTDI_SIO_SET_LATENCY_TIMER 0x09
+#define FTDI_SIO_GET_LATENCY_TIMER 0x0A
+#define FTDI_SIO_SET_BITMODE       0x0B
+#define FTDI_SIO_READ_PINS         0x0C
+#define FTDI_SIO_READ_EEPROM       0x90
+#define FTDI_SIO_WRITE_EEPROM      0x91
+#define FTDI_SIO_ERASE_EEPROM      0x92
+
+/* Channel indices for FT2232, FT2232H and FT4232H devices */
+#define FTDI_SIO_CHANNEL_A 1
+#define FTDI_SIO_CHANNEL_B 2
+#define FTDI_SIO_CHANNEL_C 3
+#define FTDI_SIO_CHANNEL_D 4
+
+/*
+ * BmRequestType:  0100 0000B
+ * bRequest:       FTDI_SIO_RESET
+ * wValue:         Control Value
+ *                   0 = Reset SIO
+ *                   1 = Purge RX buffer
+ *                   2 = Purge TX buffer
+ * wIndex:         Port
+ * wLength:        0
+ * Data:           None
+ *
+ * The Reset SIO command has this effect:
+ *
+ *    Sets flow control set to 'none'
+ *    Event char = $0D
+ *    Event trigger = disabled
+ *    Purge RX buffer
+ *    Purge TX buffer
+ *    Clear DTR
+ *    Clear RTS
+ *    baud and data format not reset
+ *
+ * The Purge RX and TX buffer commands affect nothing except the buffers
+ *
+   */
+
+#define FTDI_SIO_RESET_SIO      0
+#define FTDI_SIO_RESET_PURGE_RX 1
+#define FTDI_SIO_RESET_PURGE_TX 2
+
+/*
+ * BmRequestType:  0100 0000B
+ * bRequest:       FTDI_SIO_SET_BAUDRATE
+ * wValue:         BaudDivisor value - see below
+ * wIndex:         Port
+ * wLength:        0
+ * Data:           None
+ * The BaudDivisor values are calculated as follows:
+ * - BaseClock is either 12000000 or 48000000 depending on the device.
+ *   FIXME: I wish I knew how to detect old chips to select proper base clock!
+ * - BaudDivisor is a fixed point number encoded in a funny way.
+ *   (--WRONG WAY OF THINKING--)
+ *   BaudDivisor is a fixed point number encoded with following bit weighs:
+ *   (-2)(-1)(13..0). It is a radical with a denominator of 4, so values
+ *   end with 0.0 (00...), 0.25 (10...), 0.5 (01...), and 0.75 (11...).
+ *   (--THE REALITY--)
+ *   The both-bits-set has quite different meaning from 0.75 - the chip
+ *   designers have decided it to mean 0.125 instead of 0.75.
+ *   This info looked up in FTDI application note "FT8U232 DEVICES \ Data Rates
+ *   and Flow Control Consideration for USB to RS232".
+ * - BaudDivisor = (BaseClock / 16) / BaudRate, where the (=) operation should
+ *   automagically re-encode the resulting value to take fractions into
+ *   consideration.
+ * As all values are integers, some bit twiddling is in order:
+ *   BaudDivisor = (BaseClock / 16 / BaudRate) |
+ *   (((BaseClock / 2 / BaudRate) & 4) ? 0x4000    // 0.5
+ *    : ((BaseClock / 2 / BaudRate) & 2) ? 0x8000  // 0.25
+ *    : ((BaseClock / 2 / BaudRate) & 1) ? 0xc000  // 0.125
+ *    : 0)
+ *
+ * For the FT232BM, a 17th divisor bit was introduced to encode the multiples
+ * of 0.125 missing from the FT8U232AM.  Bits 16 to 14 are coded as follows
+ * (the first four codes are the same as for the FT8U232AM, where bit 16 is
+ * always 0):
+ *   000 - add .000 to divisor
+ *   001 - add .500 to divisor
+ *   010 - add .250 to divisor
+ *   011 - add .125 to divisor
+ *   100 - add .375 to divisor
+ *   101 - add .625 to divisor
+ *   110 - add .750 to divisor
+ *   111 - add .875 to divisor
+ * Bits 15 to 0 of the 17-bit divisor are placed in the urb value.  Bit 16 is
+ * placed in bit 0 of the urb index.
+ *
+ * Note that there are a couple of special cases to support the highest baud
+ * rates.  If the calculated divisor value is 1, this needs to be replaced with
+ * 0.  Additionally for the FT232BM, if the calculated divisor value is 0x4001
+ * (1.5), this needs to be replaced with 0x0001 (1) (but this divisor value is
+ * not supported by the FT8U232AM).
+ */
+
+enum ftdi_sio_baudrate {
+    ftdi_sio_b300 = 0,
+    ftdi_sio_b600 = 1,
+    ftdi_sio_b1200 = 2,
+    ftdi_sio_b2400 = 3,
+    ftdi_sio_b4800 = 4,
+    ftdi_sio_b9600 = 5,
+    ftdi_sio_b19200 = 6,
+    ftdi_sio_b38400 = 7,
+    ftdi_sio_b57600 = 8,
+    ftdi_sio_b115200 = 9
+};
+
+/*
+ * BmRequestType:  0100 0000B
+ * bRequest:       FTDI_SIO_SET_DATA
+ * wValue:         Data characteristics (see below)
+ * wIndex:         Port
+ * wLength:        0
+ * Data:           No
+ *
+ * Data characteristics
+ *
+ *   B0..7   Number of data bits
+ *   B8..10  Parity
+ *           0 = None
+ *           1 = Odd
+ *           2 = Even
+ *           3 = Mark
+ *           4 = Space
+ *   B11..13 Stop Bits
+ *           0 = 1
+ *           1 = 1.5
+ *           2 = 2
+ *   B14
+ *           1 = TX ON (break)
+ *           0 = TX OFF (normal state)
+ *   B15 Reserved
+ *
+ */
+
+#define FTDI_SIO_SET_DATA_PARITY_NONE  (0x0 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_ODD   (0x1 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_EVEN  (0x2 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_MARK  (0x3 << 8)
+#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8)
+#define FTDI_SIO_SET_DATA_STOP_BITS_1  (0x0 << 11)
+#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11)
+#define FTDI_SIO_SET_DATA_STOP_BITS_2  (0x2 << 11)
+#define FTDI_SIO_SET_BREAK             (0x1 << 14)
+
+/*
+ * BmRequestType:   0100 0000B
+ * bRequest:        FTDI_SIO_MODEM_CTRL
+ * wValue:          ControlValue (see below)
+ * wIndex:          Port
+ * wLength:         0
+ * Data:            None
+ *
+ * NOTE: If the device is in RTS/CTS flow control, the RTS set by this
+ * command will be IGNORED without an error being returned
+ * Also - you can not set DTR and RTS with one control message
+ *
+ * ControlValue
+ * B0    DTR state
+ *          0 = reset
+ *          1 = set
+ * B1    RTS state
+ *          0 = reset
+ *          1 = set
+ * B2..7 Reserved
+ * B8    DTR state enable
+ *          0 = ignore
+ *          1 = use DTR state
+ * B9    RTS state enable
+ *          0 = ignore
+ *          1 = use RTS state
+ * B10..15 Reserved
+ *
+ */
+
+#define FTDI_SIO_SET_DTR_MASK 0x1
+#define FTDI_SIO_SET_DTR_HIGH ((FTDI_SIO_SET_DTR_MASK << 8) | 1)
+#define FTDI_SIO_SET_DTR_LOW  ((FTDI_SIO_SET_DTR_MASK << 8) | 0)
+#define FTDI_SIO_SET_RTS_MASK 0x2
+#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2)
+#define FTDI_SIO_SET_RTS_LOW  ((FTDI_SIO_SET_RTS_MASK << 8) | 0)
+
+/*
+ *   BmRequestType:  0100 0000b
+ *   bRequest:       FTDI_SIO_SET_FLOW_CTRL
+ *   wValue:         Xoff/Xon
+ *   wIndex:         Protocol/Port - hIndex is protocol / lIndex is port
+ *   wLength:        0
+ *   Data:           None
+ *
+ * hIndex protocol is:
+ *   B0 Output handshaking using RTS/CTS
+ *       0 = disabled
+ *       1 = enabled
+ *   B1 Output handshaking using DTR/DSR
+ *       0 = disabled
+ *       1 = enabled
+ *   B2 Xon/Xoff handshaking
+ *       0 = disabled
+ *       1 = enabled
+ *
+ * A value of zero in the hIndex field disables handshaking
+ *
+ * If Xon/Xoff handshaking is specified, the hValue field should contain the
+ * XOFF character and the lValue field contains the XON character.
+ */
+
+#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0
+#define FTDI_SIO_RTS_CTS_HS        (0x1 << 8)
+#define FTDI_SIO_DTR_DSR_HS        (0x2 << 8)
+#define FTDI_SIO_XON_XOFF_HS       (0x4 << 8)
+
+/*
+ *   BmRequestType:   1100 0000b
+ *   bRequest:        FTDI_SIO_GET_MODEM_STATUS
+ *   wValue:          zero
+ *   wIndex:          Port
+ *   wLength:         1
+ *   Data:            Status
+ *
+ * One byte of data is returned
+ * B0..3 0
+ * B4    CTS
+ *         0 = inactive
+ *         1 = active
+ * B5    DSR
+ *         0 = inactive
+ *         1 = active
+ * B6    Ring Indicator (RI)
+ *         0 = inactive
+ *         1 = active
+ * B7    Receive Line Signal Detect (RLSD)
+ *         0 = inactive
+ *         1 = active
+ */
+
+#define FTDI_SIO_CTS_MASK  0x10
+#define FTDI_SIO_DSR_MASK  0x20
+#define FTDI_SIO_RI_MASK   0x40
+#define FTDI_SIO_RLSD_MASK 0x80
+
+/* Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST */
+#define FTDI_SIO_BITMODE_RESET 0x00
+#define FTDI_SIO_BITMODE_CBUS  0x20
+
+/*
+ * IN Endpoint
+ *
+ * The device reserves the first two bytes of data on this endpoint to contain
+ * the current values of the modem and line status registers. In the absence of
+ * data, the device generates a message consisting of these two status bytes
+ * every 40 ms
+ *
+ * Byte 0: Modem Status
+ *
+ * Offset	Description
+ * B0	Reserved - must be 1
+ * B1	Reserved - must be 0
+ * B2	Reserved - must be 0
+ * B3	Reserved - must be 0
+ * B4	Clear to Send (CTS)
+ * B5	Data Set Ready (DSR)
+ * B6	Ring Indicator (RI)
+ * B7	Receive Line Signal Detect (RLSD)
+ *
+ * Byte 1: Line Status
+ *
+ * Offset	Description
+ * B0	Data Ready (DR)
+ * B1	Overrun Error (OE)
+ * B2	Parity Error (PE)
+ * B3	Framing Error (FE)
+ * B4	Break Interrupt (BI)
+ * B5	Transmitter Holding Register (THRE)
+ * B6	Transmitter Empty (TEMT)
+ * B7	Error in RCVR FIFO
+ *
+ */
+#define FTDI_RS0_CTS  (1 << 4)
+#define FTDI_RS0_DSR  (1 << 5)
+#define FTDI_RS0_RI   (1 << 6)
+#define FTDI_RS0_RLSD (1 << 7)
+
+#define FTDI_RS_DR   1
+#define FTDI_RS_OE   (1 << 1)
+#define FTDI_RS_PE   (1 << 2)
+#define FTDI_RS_FE   (1 << 3)
+#define FTDI_RS_BI   (1 << 4)
+#define FTDI_RS_THRE (1 << 5)
+#define FTDI_RS_TEMT (1 << 6)
+#define FTDI_RS_FIFO (1 << 7)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USBH_FTDI_H */

+ 127 - 0
class/serial/usbh_gsm.c

@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_serial.h"
+
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbh_gsm"
+#include "usb_log.h"
+
+struct usbh_gsm {
+    struct usb_endpoint_descriptor *intin;
+    struct usbh_urb intin_urb;
+    struct usb_osal_timer *modem_timer;
+    uint16_t modem_status;
+};
+
+static int usbh_gsm_attach(struct usbh_serial *serial)
+{
+    struct usb_endpoint_descriptor *ep_desc;
+    int ret;
+
+    struct usbh_gsm *gsm_class = usb_osal_malloc(sizeof(struct usbh_gsm));
+    if (!gsm_class) {
+        USB_LOG_ERR("No memory for gsm_class\r\n");
+        return -USB_ERR_NOMEM;
+    }
+    memset(gsm_class, 0, sizeof(struct usbh_gsm));
+    serial->priv = gsm_class;
+
+    for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
+        ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
+
+        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
+            if (ep_desc->bEndpointAddress & 0x80) {
+                USBH_EP_INIT(gsm_class->intin, ep_desc);
+                break;
+            } else {
+            }
+        }
+    }
+
+    if (!gsm_class->intin) {
+        USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
+        ret = -USB_ERR_NODEV;
+        goto errout;
+    }
+    return 0;
+errout:
+    serial->priv = NULL;
+    usb_osal_free(gsm_class);
+    return ret;
+}
+
+static void usbh_gsm_detach(struct usbh_serial *serial)
+{
+    struct usbh_gsm *gsm_class;
+
+    if (!serial || !serial->priv) {
+        return;
+    }
+
+    gsm_class = (struct usbh_gsm *)serial->priv;
+    if (gsm_class->intin) {
+        usbh_kill_urb(&gsm_class->intin_urb);
+    }
+    serial->priv = NULL;
+    usb_osal_free(gsm_class);
+}
+
+static const struct usbh_serial_driver gsm_driver = {
+    .driver_name = "gsm",
+
+    .ignore_rx_header = 0,
+    .ignore_tx_header = 0,
+
+    .attach = usbh_gsm_attach,
+    .detach = usbh_gsm_detach,
+    .set_flow_control = NULL,
+    .set_line_coding = NULL,
+    .get_line_coding = NULL,
+    .set_line_state = NULL,
+    .get_modem_status = NULL,
+};
+
+static int usbh_gsm_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+    return usbh_serial_probe(hport, intf, &gsm_driver) ? 0 : -USB_ERR_NOMEM;
+}
+
+static int usbh_gsm_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+    struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
+
+    if (serial) {
+        usbh_serial_remove(serial);
+    }
+    return 0;
+}
+
+const struct usbh_class_driver gsm_class_driver = {
+    .driver_name = "gsm",
+    .connect = usbh_gsm_connect,
+    .disconnect = usbh_gsm_disconnect
+};
+
+static const uint16_t gsm_id_table[][2] = {
+    { 0x2C7C, 0x0120 }, /* Quectel EC20 */
+    { 0x2C7C, 0x0121 }, /* Quectel EC21 */
+    { 0x2C7C, 0x0125 }, /* Quectel EC25 */
+    { 0x2C7C, 0x0191 }, /* Quectel EG91 */
+    { 0x2C7C, 0x0195 }, /* Quectel EG95 */
+    { 0x2C7C, 0x6002 }, /* Quectel EC200/EC600/EC800/EG91x */
+    { 0x1E0E, 0x9001 }, /* SIMCOM SIM7600 */
+    { 0, 0 },
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info gsm_class_info = {
+    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
+    .bInterfaceClass = 0xff,
+    .bInterfaceSubClass = 0x00,
+    .bInterfaceProtocol = 0x00,
+    .id_table = gsm_id_table,
+    .class_driver = &gsm_class_driver
+};

+ 726 - 0
class/serial/usbh_pl2303.c

@@ -0,0 +1,726 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ * Copyright (c) 2024, Derek Konigsberg
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_serial.h"
+#include "usbh_pl2303.h"
+
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbh_pl2303"
+#include "usb_log.h"
+
+#define UART_STATE_INDEX          8
+#define UART_STATE_MSR_MASK       0x8b
+#define UART_STATE_TRANSIENT_MASK 0x74
+#define UART_DCD                  0x01
+#define UART_DSR                  0x02
+#define UART_BREAK_ERROR          0x04
+#define UART_RING                 0x08
+#define UART_FRAME_ERROR          0x10
+#define UART_PARITY_ERROR         0x20
+#define UART_OVERRUN_ERROR        0x40
+#define UART_CTS                  0x80
+
+struct pl2303_type_data {
+    const char *name;
+    uint32_t max_baud_rate;
+    unsigned long quirks;
+    unsigned int no_autoxonxoff : 1;
+    unsigned int no_divisors    : 1;
+    unsigned int alt_divisors   : 1;
+};
+
+enum pl2303_type {
+    TYPE_H,
+    TYPE_HX,
+    TYPE_TA,
+    TYPE_TB,
+    TYPE_HXD,
+    TYPE_HXN,
+    TYPE_COUNT
+};
+
+struct usbh_pl2303 {
+    enum pl2303_type chip_type;
+    uint32_t quirks;
+    struct usb_endpoint_descriptor *intin;
+    struct usbh_urb intin_urb;
+    struct usb_osal_timer *modem_timer;
+    uint16_t modem_status;
+};
+
+static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = {
+    [TYPE_H] = {
+        .name = "PL2303H",
+        .max_baud_rate = 1228800,
+        .quirks = PL2303_QUIRK_LEGACY,
+        .no_autoxonxoff = true,
+    },
+    [TYPE_HX] = {
+        .name = "PL2303HX",
+        .max_baud_rate = 6000000,
+    },
+    [TYPE_TA] = {
+        .name = "PL2303TA",
+        .max_baud_rate = 6000000,
+        .alt_divisors = true,
+    },
+    [TYPE_TB] = {
+        .name = "PL2303TB",
+        .max_baud_rate = 12000000,
+        .alt_divisors = true,
+    },
+    [TYPE_HXD] = {
+        .name = "PL2303HXD",
+        .max_baud_rate = 12000000,
+    },
+    [TYPE_HXN] = {
+        .name = "PL2303G",
+        .max_baud_rate = 12000000,
+        .no_divisors = true,
+    },
+};
+
+/*
+ * Returns the nearest supported baud rate that can be set directly without
+ * using divisors.
+ */
+static uint32_t pl2303_get_supported_baud_rate(uint32_t baud)
+{
+    static const uint32_t baud_sup[] = {
+        75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600,
+        14400, 19200, 28800, 38400, 57600, 115200, 230400, 460800,
+        614400, 921600, 1228800, 2457600, 3000000, 6000000
+    };
+
+    unsigned i;
+
+    for (i = 0; i < ARRAY_SIZE(baud_sup); ++i) {
+        if (baud_sup[i] > baud)
+            break;
+    }
+
+    if (i == ARRAY_SIZE(baud_sup))
+        baud = baud_sup[i - 1];
+    else if (i > 0 && (baud_sup[i] - baud) > (baud - baud_sup[i - 1]))
+        baud = baud_sup[i - 1];
+    else
+        baud = baud_sup[i];
+
+    return baud;
+}
+
+/*
+ * NOTE: If unsupported baud rates are set directly, the PL2303 seems to
+ *       use 9600 baud.
+ */
+static uint32_t pl2303_encode_baud_rate_direct(unsigned char buf[4],
+                                               uint32_t baud)
+{
+    memcpy(buf, &baud, 4);
+
+    return baud;
+}
+
+static uint32_t pl2303_encode_baud_rate_divisor_alt(unsigned char buf[4],
+                                                    uint32_t baud)
+{
+    unsigned int baseline, mantissa, exponent;
+
+    /*
+	 * Apparently, for the TA version the formula is:
+	 *   baudrate = 12M * 32 / (mantissa * 2^exponent)
+	 * where
+	 *   mantissa = buf[10:0]
+	 *   exponent = buf[15:13 16]
+	 */
+    baseline = 12000000 * 32;
+    mantissa = baseline / baud;
+    if (mantissa == 0)
+        mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */
+    exponent = 0;
+    while (mantissa >= 2048) {
+        if (exponent < 15) {
+            mantissa >>= 1; /* divide by 2 */
+            exponent++;
+        } else {
+            /* Exponent is maxed. Trim mantissa and leave. */
+            mantissa = 2047;
+            break;
+        }
+    }
+
+    buf[3] = 0x80;
+    buf[2] = exponent & 0x01;
+    buf[1] = (exponent & ~0x01) << 4 | mantissa >> 8;
+    buf[0] = mantissa & 0xff;
+
+    /* Calculate and return the exact baud rate. */
+    baud = (baseline / mantissa) >> exponent;
+
+    return baud;
+}
+
+static uint32_t pl2303_encode_baud_rate_divisor(unsigned char buf[4],
+                                                uint32_t baud)
+{
+    unsigned int baseline, mantissa, exponent;
+
+    /*
+	 * Apparently the formula is:
+	 *   baudrate = 12M * 32 / (mantissa * 4^exponent)
+	 * where
+	 *   mantissa = buf[8:0]
+	 *   exponent = buf[11:9]
+	 */
+    baseline = 12000000 * 32;
+    mantissa = baseline / baud;
+    if (mantissa == 0)
+        mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */
+    exponent = 0;
+    while (mantissa >= 512) {
+        if (exponent < 7) {
+            mantissa >>= 2; /* divide by 4 */
+            exponent++;
+        } else {
+            /* Exponent is maxed. Trim mantissa and leave. */
+            mantissa = 511;
+            break;
+        }
+    }
+
+    buf[3] = 0x80;
+    buf[2] = 0;
+    buf[1] = exponent << 1 | mantissa >> 8;
+    buf[0] = mantissa & 0xff;
+
+    /* Calculate and return the exact baud rate. */
+    baud = (baseline / mantissa) >> (exponent << 1);
+
+    return baud;
+}
+
+static int pl2303_vendor_write(struct usbh_serial *serial, uint16_t wValue, uint16_t wIndex)
+{
+    struct usb_setup_packet *setup;
+    struct usbh_pl2303 *pl2303_class;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = pl2303_class->chip_type == TYPE_HXN ? PL2303_VENDOR_WRITE_NREQUEST : PL2303_VENDOR_WRITE_REQUEST;
+    setup->wValue = wValue;
+    setup->wIndex = wIndex;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int pl2303_vendor_read(struct usbh_serial *serial, uint16_t wValue, uint8_t *data)
+{
+    struct usb_setup_packet *setup;
+    struct usbh_pl2303 *pl2303_class;
+    int ret;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+
+    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = pl2303_class->chip_type == TYPE_HXN ? PL2303_VENDOR_READ_NREQUEST : PL2303_VENDOR_READ_REQUEST;
+    setup->wValue = wValue;
+    setup->wIndex = 0;
+    setup->wLength = 1;
+
+    ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    if (ret < 0) {
+        return ret;
+    }
+    memcpy(data, serial->iobuffer, 1);
+
+    return ret;
+}
+
+static bool pl2303_supports_hx_status(struct usbh_serial *serial)
+{
+    int ret;
+    uint8_t buf;
+
+    ret = pl2303_vendor_read(serial, PL2303_READ_TYPE_HX_STATUS, &buf);
+    if (ret < 0) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool pl2303_is_hxd_clone(struct usbh_serial *serial)
+{
+    struct usb_setup_packet *setup;
+    int ret;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_DEVICE;
+    setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
+    setup->wValue = 0;
+    setup->wIndex = 0;
+    setup->wLength = 7;
+
+    ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    if (ret < 0) {
+        return false;
+    }
+    return true;
+}
+
+static int pl2303_update_reg(struct usbh_serial *serial, uint8_t reg, uint8_t mask, uint8_t val)
+{
+    int ret;
+    uint8_t buf[1];
+    struct usbh_pl2303 *pl2303_class;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+
+    if (pl2303_class->chip_type == TYPE_HXN)
+        ret = pl2303_vendor_read(serial, reg, buf);
+    else
+        ret = pl2303_vendor_read(serial, reg | 0x80, buf);
+
+    if (ret < 0) {
+        return ret;
+    }
+
+    *buf &= ~mask;
+    *buf |= val & mask;
+
+    return pl2303_vendor_write(serial, reg, *buf);
+}
+
+static int usbh_pl2303_get_chiptype(struct usbh_serial *serial)
+{
+    if (serial->hport->device_desc.bDeviceClass == 0x02) {
+        return TYPE_H; /* variant 0 */
+    }
+
+    if (serial->hport->device_desc.bMaxPacketSize0 != 0x40) {
+        if (serial->hport->device_desc.bDeviceClass == 0x00 || serial->hport->device_desc.bDeviceClass == 0xff)
+            return TYPE_H; /* variant 1 */
+
+        return TYPE_H; /* variant 0 */
+    }
+
+    switch (serial->hport->device_desc.bcdUSB) {
+        case 0x101:
+            /* USB 1.0.1? Let's assume they meant 1.1... */
+        case 0x110:
+            switch (serial->hport->device_desc.bcdDevice) {
+                case 0x300:
+                    return TYPE_HX;
+                case 0x400:
+                    return TYPE_HXD;
+                default:
+                    return TYPE_HX;
+            }
+            break;
+        case 0x200:
+            switch (serial->hport->device_desc.bcdDevice) {
+                case 0x100: /* GC */
+                case 0x105:
+                    return TYPE_HXN;
+                case 0x300: /* GT / TA */
+                    if (pl2303_supports_hx_status(serial))
+                        return TYPE_TA;
+                __attribute__((fallthrough));
+                case 0x305:
+                case 0x400: /* GL */
+                case 0x405:
+                    return TYPE_HXN;
+                case 0x500: /* GE / TB */
+                    if (pl2303_supports_hx_status(serial))
+                        return TYPE_TB;
+                __attribute__((fallthrough));
+                case 0x505:
+                case 0x600: /* GS */
+                case 0x605:
+                case 0x700: /* GR */
+                case 0x705:
+                case 0x905:  /* GT-2AB */
+                case 0x1005: /* GC-Q20 */
+                    return TYPE_HXN;
+            }
+            break;
+    }
+
+    USB_LOG_ERR("Unsupported PL2303 Device\r\n");
+    return -USB_ERR_NOTSUPP;
+}
+
+static int usbh_pl2303_attach(struct usbh_serial *serial)
+{
+    struct usbh_pl2303 *pl2303_class;
+    struct usb_endpoint_descriptor *ep_desc;
+    uint8_t type;
+    int ret;
+
+    ret = usbh_pl2303_get_chiptype(serial);
+    if (ret < 0) {
+        return ret;
+    }
+
+    pl2303_class = usb_osal_malloc(sizeof(struct usbh_pl2303));
+    if (pl2303_class == NULL) {
+        USB_LOG_ERR("Fail to alloc pl2303_class\r\n");
+        return -USB_ERR_NOMEM;
+    }
+    memset(pl2303_class, 0, sizeof(struct usbh_pl2303));
+    serial->priv = pl2303_class;
+
+    for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
+        ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
+
+        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
+            if (ep_desc->bEndpointAddress & 0x80) {
+                USBH_EP_INIT(pl2303_class->intin, ep_desc);
+                break;
+            } else {
+            }
+        }
+    }
+
+    if (!pl2303_class->intin) {
+        USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
+        ret = -USB_ERR_NODEV;
+        goto errout;
+    }
+
+    type = (uint8_t)ret;
+    pl2303_class->chip_type = type;
+    pl2303_class->quirks = pl2303_type_data[pl2303_class->chip_type].quirks;
+
+    USB_LOG_INFO("chip type: %s\r\n", pl2303_type_data[pl2303_class->chip_type].name);
+
+    if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) {
+        pl2303_class->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE;
+    }
+
+    if (type != TYPE_HXN) {
+        uint8_t buf[1];
+        ret = pl2303_vendor_read(serial, 0x8484, buf);
+        ret |= pl2303_vendor_write(serial, 0x0404, 0);
+        ret |= pl2303_vendor_read(serial, 0x8484, buf);
+        ret |= pl2303_vendor_read(serial, 0x8383, buf);
+        ret |= pl2303_vendor_read(serial, 0x8484, buf);
+        ret |= pl2303_vendor_write(serial, 0x0404, 1);
+        ret |= pl2303_vendor_read(serial, 0x8484, buf);
+        ret |= pl2303_vendor_read(serial, 0x8383, buf);
+        ret |= pl2303_vendor_write(serial, 0, 1);
+        ret |= pl2303_vendor_write(serial, 1, 0);
+        if (pl2303_class->quirks & PL2303_QUIRK_LEGACY)
+            ret |= pl2303_vendor_write(serial, 2, 0x24);
+        else
+            ret |= pl2303_vendor_write(serial, 2, 0x44);
+    } else {
+        ret = 0;
+    }
+
+    if (ret < 0) {
+        USB_LOG_ERR("pl2303 init failed\r\n");
+        goto errout;
+    }
+
+    return 0;
+errout:
+    serial->priv = NULL;
+    usb_osal_free(pl2303_class);
+    return ret;
+}
+
+static void usbh_pl2303_detach(struct usbh_serial *serial)
+{
+    struct usbh_pl2303 *pl2303_class;
+
+    if (!serial || !serial->priv) {
+        return;
+    }
+
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+    if (pl2303_class->intin) {
+        usbh_kill_urb(&pl2303_class->intin_urb);
+    }
+    serial->priv = NULL;
+    usb_osal_free(pl2303_class);
+}
+
+static int usbh_pl2303_set_flow_ctrl(struct usbh_serial *serial, bool hardctrl)
+{
+    struct usbh_pl2303 *pl2303_class;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+
+    if (hardctrl) {
+        if (pl2303_class->quirks & PL2303_QUIRK_LEGACY) {
+            return pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x40);
+        } else if (pl2303_class->chip_type == TYPE_HXN) {
+            return pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
+                                     PL2303_HXN_FLOWCTRL_MASK,
+                                     PL2303_HXN_FLOWCTRL_RTS_CTS);
+        } else {
+            return pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60);
+        }
+    } else {
+        if (pl2303_class->chip_type == TYPE_HXN) {
+            return pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
+                                     PL2303_HXN_FLOWCTRL_MASK,
+                                     PL2303_HXN_FLOWCTRL_NONE);
+        } else {
+            return pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0);
+        }
+    }
+}
+
+static int usbh_pl2303_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
+{
+    struct usb_setup_packet *setup;
+    struct usbh_pl2303 *pl2303_class;
+    uint32_t baud;
+    uint32_t baud_sup;
+    uint8_t buf[7];
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    setup = serial->hport->setup;
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
+    setup->wValue = 0;
+    setup->wIndex = serial->intf;
+    setup->wLength = 7;
+
+    baud = line_coding->dwDTERate;
+    if (pl2303_type_data[pl2303_class->chip_type].max_baud_rate) {
+        baud = MIN(baud, pl2303_type_data[pl2303_class->chip_type].max_baud_rate);
+    }
+    /*
+	 * Use direct method for supported baud rates, otherwise use divisors.
+	 * Newer chip types do not support divisor encoding.
+	 */
+    if (pl2303_type_data[pl2303_class->chip_type].no_divisors)
+        baud_sup = baud;
+    else
+        baud_sup = pl2303_get_supported_baud_rate(baud);
+
+    if (baud == baud_sup)
+        baud = pl2303_encode_baud_rate_direct(buf, baud);
+    else if (pl2303_type_data[pl2303_class->chip_type].alt_divisors)
+        baud = pl2303_encode_baud_rate_divisor_alt(buf, baud);
+    else
+        baud = pl2303_encode_baud_rate_divisor(buf, baud);
+
+    buf[4] = line_coding->bCharFormat;
+    buf[5] = line_coding->bParityType;
+    buf[6] = line_coding->bDataBits;
+
+    memcpy(serial->iobuffer, buf, sizeof(struct cdc_line_coding));
+
+    return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+}
+
+static int usbh_pl2303_get_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
+{
+    struct usb_setup_packet *setup;
+    int ret;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
+    setup->wValue = 0;
+    setup->wIndex = serial->intf;
+    setup->wLength = 7;
+
+    ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
+    if (ret < 0) {
+        return ret;
+    }
+    memcpy(line_coding, serial->iobuffer, sizeof(struct cdc_line_coding));
+    return ret;
+}
+
+static int usbh_pl2303_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
+{
+    struct usb_setup_packet *setup;
+
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+    setup = serial->hport->setup;
+
+    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
+    setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
+    setup->wValue = (dtr << 0) | (rts << 1);
+    setup->wIndex = serial->intf;
+    setup->wLength = 0;
+
+    return usbh_control_transfer(serial->hport, setup, NULL);
+}
+
+static int usbh_pl2303_get_modem_status(struct usbh_serial *serial)
+{
+    struct usbh_pl2303 *pl2303_class;
+    uintptr_t flags;
+    uint16_t status;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+
+    flags = usb_osal_enter_critical_section();
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+
+    status = (pl2303_class->modem_status & UART_DSR ? USBH_SERIAL_TIOCM_DSR : 0) |
+             (pl2303_class->modem_status & UART_CTS ? USBH_SERIAL_TIOCM_CTS : 0) |
+             (pl2303_class->modem_status & UART_RING ? USBH_SERIAL_TIOCM_RI : 0) |
+             (pl2303_class->modem_status & UART_DCD ? USBH_SERIAL_TIOCM_CD : 0) |
+             (serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
+             (serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
+
+    usb_osal_leave_critical_section(flags);
+
+    return status;
+}
+
+#ifdef CONFIG_USBH_SERIAL_GET_MODEM_STATUS
+static int __usbh_pl2303_get_modem_status(struct usbh_serial *serial)
+{
+    struct usbh_pl2303 *pl2303_class;
+    uint8_t status = 0;
+    uint16_t difference;
+    uintptr_t flags;
+    int ret;
+
+    if (!serial || !serial->hport || !serial->priv) {
+        return -USB_ERR_INVAL;
+    }
+    pl2303_class = (struct usbh_pl2303 *)serial->priv;
+
+    usbh_int_urb_fill(&pl2303_class->intin_urb, serial->hport, pl2303_class->intin, &serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET], pl2303_class->intin->wMaxPacketSize, 0xffffffff, NULL, NULL);
+    ret = usbh_submit_urb(&pl2303_class->intin_urb);
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (ret < 1) {
+        return -USB_ERR_INVAL;
+    }
+
+    flags = usb_osal_enter_critical_section();
+
+    status = serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET];
+    difference = pl2303_class->modem_status ^ status;
+    pl2303_class->modem_status = status;
+
+    if (status & UART_BREAK_ERROR)
+        serial->iocount.brk++;
+
+    if (difference & UART_STATE_MSR_MASK) {
+        if (difference & UART_CTS)
+            serial->iocount.cts++;
+        if (difference & UART_DSR)
+            serial->iocount.dsr++;
+        if (difference & UART_RING)
+            serial->iocount.rng++;
+        if (difference & UART_DCD) {
+            serial->iocount.dcd++;
+        }
+    }
+
+    usb_osal_leave_critical_section(flags);
+
+    return ret;
+}
+#endif
+
+static const struct usbh_serial_driver pl2303_driver = {
+    .driver_name = "pl2303",
+
+    .ignore_rx_header = 0,
+    .ignore_tx_header = 0,
+
+    .attach = usbh_pl2303_attach,
+    .detach = usbh_pl2303_detach,
+    .set_flow_control = usbh_pl2303_set_flow_ctrl,
+    .set_line_coding = usbh_pl2303_set_line_coding,
+    .get_line_coding = usbh_pl2303_get_line_coding,
+    .set_line_state = usbh_pl2303_set_line_state,
+    .get_modem_status = usbh_pl2303_get_modem_status,
+};
+
+static int usbh_pl2303_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+    return usbh_serial_probe(hport, intf, &pl2303_driver) ? 0 : -USB_ERR_NOMEM;
+}
+
+static int usbh_pl2303_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+    struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
+
+    if (serial) {
+        usbh_serial_remove(serial);
+    }
+
+    return 0;
+}
+
+static const uint16_t pl2303_id_table[][2] = {
+    { 0x067B, 0x2303 }, // PL2303 Serial (ATEN/IOGEAR UC232A)
+    { 0x067B, 0x2304 }, // PL2303HXN Serial, type TB
+    { 0x067B, 0x23A3 }, // PL2303HXN Serial, type GC
+    { 0x067B, 0x23B3 }, // PL2303HXN Serial, type GB
+    { 0x067B, 0x23C3 }, // PL2303HXN Serial, type GT
+    { 0x067B, 0x23D3 }, // PL2303HXN Serial, type GL
+    { 0x067B, 0x23E3 }, // PL2303HXN Serial, type GE
+    { 0x067B, 0x23F3 }, // PL2303HXN Serial, type GS
+    { 0, 0 },
+};
+
+const struct usbh_class_driver pl2303_class_driver = {
+    .driver_name = "pl2303",
+    .connect = usbh_pl2303_connect,
+    .disconnect = usbh_pl2303_disconnect
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info pl2303_class_info = {
+    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
+    .bInterfaceClass = 0xff,
+    .bInterfaceSubClass = 0x00,
+    .bInterfaceProtocol = 0x00,
+    .id_table = pl2303_id_table,
+    .class_driver = &pl2303_class_driver
+};

+ 43 - 0
class/serial/usbh_pl2303.h

@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 ~ 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBH_PL2303_H
+#define USBH_PL2303_H
+
+#include "usb_cdc.h"
+
+#define PL2303_VENDOR_WRITE_REQUEST  0x01
+#define PL2303_VENDOR_WRITE_NREQUEST 0x80
+#define PL2303_VENDOR_READ_REQUEST   0x01
+#define PL2303_VENDOR_READ_NREQUEST  0x81
+
+#define PL2303_FLOWCTRL_MASK 0xf0
+
+#define PL2303_READ_TYPE_HX_STATUS 0x8080
+
+#define PL2303_HXN_RESET_REG             0x07
+#define PL2303_HXN_RESET_UPSTREAM_PIPE   0x02
+#define PL2303_HXN_RESET_DOWNSTREAM_PIPE 0x01
+
+#define PL2303_HXN_FLOWCTRL_REG      0x0a
+#define PL2303_HXN_FLOWCTRL_MASK     0x1c
+#define PL2303_HXN_FLOWCTRL_NONE     0x1c
+#define PL2303_HXN_FLOWCTRL_RTS_CTS  0x18
+#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c
+
+#define PL2303_QUIRK_UART_STATE_IDX0  BIT(0)
+#define PL2303_QUIRK_LEGACY           BIT(1)
+#define PL2303_QUIRK_ENDPOINT_HACK    BIT(2)
+#define PL2303_QUIRK_NO_BREAK_GETLINE BIT(3)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USBH_PL2303_H */

+ 719 - 0
class/serial/usbh_serial.c

@@ -0,0 +1,719 @@
+/*
+ * Copyright (c) 2025, sakumisu
+ * Copyright (c) 2025, MDLZCOOL
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_serial.h"
+
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbh_serial"
+#include "usb_log.h"
+
+#define DEV_FORMAT_VENDOR  "/dev/ttyUSB%d"
+#define DEV_FORMAT_CDC_ACM "/dev/ttyACM%d"
+
+#define CONFIG_USBHOST_MAX_SERIAL_CLASS 4
+
+static struct usbh_serial g_serial_class[CONFIG_USBHOST_MAX_SERIAL_CLASS];
+
+static uint32_t g_devinuse = 0;
+static uint32_t g_cdcacm_devinuse = 0;
+
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_serial_iobuffer[CONFIG_USBHOST_MAX_SERIAL_CLASS][USB_ALIGN_UP((USBH_SERIAL_INT_NOCACHE_OFFSET + USBH_SERIAL_INT_NOCACHE_SIZE), CONFIG_USB_ALIGN_SIZE)];
+
+/* refer to cherryrb */
+static int usbh_serial_ringbuffer_init(usbh_serial_ringbuf_t *rb, void *pool, uint32_t size)
+{
+    if (NULL == rb) {
+        return -1;
+    }
+
+    if (NULL == pool) {
+        return -1;
+    }
+
+    if ((size < 2) || (size & (size - 1))) {
+        return -1;
+    }
+
+    rb->in = 0;
+    rb->out = 0;
+    rb->mask = size - 1;
+    rb->pool = pool;
+
+    return 0;
+}
+
+static void usbh_serial_ringbuffer_reset(usbh_serial_ringbuf_t *rb)
+{
+    rb->in = 0;
+    rb->out = 0;
+}
+
+static uint32_t usbh_serial_ringbuffer_get_used(usbh_serial_ringbuf_t *rb)
+{
+    return rb->in - rb->out;
+}
+
+static uint32_t usbh_serial_ringbuffer_write(usbh_serial_ringbuf_t *rb, void *data, uint32_t size)
+{
+    uint32_t unused;
+    uint32_t offset;
+    uint32_t remain;
+
+    unused = (rb->mask + 1) - (rb->in - rb->out);
+
+    if (size > unused) {
+        size = unused;
+    }
+
+    offset = rb->in & rb->mask;
+
+    remain = rb->mask + 1 - offset;
+    remain = remain > size ? size : remain;
+
+    memcpy(((uint8_t *)(rb->pool)) + offset, data, remain);
+    memcpy(rb->pool, (uint8_t *)data + remain, size - remain);
+
+    rb->in += size;
+
+    return size;
+}
+
+static uint32_t usbh_serial_ringbuffer_peek(usbh_serial_ringbuf_t *rb, void *data, uint32_t size)
+{
+    uint32_t used;
+    uint32_t offset;
+    uint32_t remain;
+
+    used = rb->in - rb->out;
+    if (size > used) {
+        size = used;
+    }
+
+    offset = rb->out & rb->mask;
+
+    remain = rb->mask + 1 - offset;
+    remain = remain > size ? size : remain;
+
+    memcpy(data, ((uint8_t *)(rb->pool)) + offset, remain);
+    memcpy((uint8_t *)data + remain, rb->pool, size - remain);
+
+    return size;
+}
+
+static uint32_t usbh_serial_ringbuffer_read(usbh_serial_ringbuf_t *rb, void *data, uint32_t size)
+{
+    size = usbh_serial_ringbuffer_peek(rb, data, size);
+    rb->out += size;
+    return size;
+}
+
+static struct usbh_serial *usbh_serial_alloc(bool is_cdcacm)
+{
+    uint8_t devno;
+    uint8_t devno2;
+
+    for (devno = 0; devno < CONFIG_USBHOST_MAX_SERIAL_CLASS; devno++) {
+        if ((g_devinuse & (1U << devno)) == 0) {
+            g_devinuse |= (1U << devno);
+            memset(&g_serial_class[devno], 0, sizeof(struct usbh_serial));
+            g_serial_class[devno].minor = devno;
+            g_serial_class[devno].cdc_minor = -1;
+            g_serial_class[devno].iobuffer = g_serial_iobuffer[devno];
+            g_serial_class[devno].rx_complete_sem = usb_osal_sem_create(0);
+
+            if (is_cdcacm) {
+                for (devno2 = 0; devno2 < CONFIG_USBHOST_MAX_SERIAL_CLASS; devno2++) {
+                    if ((g_cdcacm_devinuse & (1U << devno2)) == 0) {
+                        g_cdcacm_devinuse |= (1U << devno2);
+                        g_serial_class[devno].cdc_minor = devno2;
+                        return &g_serial_class[devno];
+                    }
+                }
+
+                g_devinuse &= ~(1U << devno);
+                return NULL;
+            } else {
+                return &g_serial_class[devno];
+            }
+        }
+    }
+    return NULL;
+}
+
+static void usbh_serial_free(struct usbh_serial *serial)
+{
+    uint8_t devno = serial->minor;
+    if (devno < 32) {
+        g_devinuse &= ~(1U << devno);
+    }
+
+    if (serial->cdc_minor >= 0) {
+        g_cdcacm_devinuse &= ~(1U << serial->cdc_minor);
+    }
+
+    if (g_serial_class[devno].rx_complete_sem) {
+        usb_osal_sem_delete(g_serial_class[devno].rx_complete_sem);
+    }
+}
+
+static void usbh_serial_callback(void *arg, int nbytes)
+{
+    struct usbh_serial *serial = (struct usbh_serial *)arg;
+    int ret;
+
+    if (nbytes >= serial->driver->ignore_rx_header) {
+        usbh_serial_ringbuffer_write(&serial->rx_rb,
+                                     &serial->iobuffer[USBH_SERIAL_RX_NOCACHE_OFFSET + serial->driver->ignore_rx_header],
+                                     (nbytes - serial->driver->ignore_rx_header));
+
+        /* resubmit the read urb */
+        usbh_bulk_urb_fill(&serial->bulkin_urb, serial->hport, serial->bulkin, &serial->iobuffer[USBH_SERIAL_RX_NOCACHE_OFFSET], serial->bulkin->wMaxPacketSize,
+                           0, usbh_serial_callback, serial);
+        ret = usbh_submit_urb(&serial->bulkin_urb);
+        if (ret < 0) {
+            USB_LOG_ERR("serial submit failed: %d\n", ret);
+        }
+
+        if (serial->rx_complete_callback) {
+            serial->rx_complete_callback(serial, nbytes - serial->driver->ignore_rx_header);
+        }
+        serial->rx_errorcode = 0;
+        usb_osal_sem_give(serial->rx_complete_sem);
+    } else {
+        serial->rx_errorcode = nbytes;
+        usb_osal_sem_give(serial->rx_complete_sem);
+    }
+}
+
+struct usbh_serial *usbh_serial_probe(struct usbh_hubport *hport, uint8_t intf,
+                                      const struct usbh_serial_driver *driver)
+{
+    struct usb_endpoint_descriptor *ep_desc;
+    struct usbh_serial *serial;
+    bool is_cdcacm = false;
+    int ret;
+
+    if (strcmp(driver->driver_name, "cdc_acm") == 0) {
+        is_cdcacm = true;
+    }
+
+    serial = usbh_serial_alloc(is_cdcacm);
+    if (serial == NULL) {
+        USB_LOG_ERR("Fail to alloc serial class\r\n");
+        return NULL;
+    }
+
+    serial->hport = hport;
+    serial->intf = intf;
+    serial->driver = driver;
+
+    if (driver->attach) {
+        ret = driver->attach(serial);
+        if (ret < 0) {
+            USB_LOG_ERR("Serial attach failed: %d\r\n", ret);
+            usbh_serial_free(serial);
+            return NULL;
+        }
+    }
+
+    if (is_cdcacm) {
+        intf = intf + 1; /* data interface */
+    }
+
+    for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
+        ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
+
+        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_BULK) {
+            if (ep_desc->bEndpointAddress & 0x80) {
+                USBH_EP_INIT(serial->bulkin, ep_desc);
+            } else {
+                USBH_EP_INIT(serial->bulkout, ep_desc);
+            }
+        }
+    }
+
+    if (is_cdcacm) {
+        intf = intf - 1; /* data interface */
+    }
+
+    if (!serial->bulkin || !serial->bulkout) {
+        USB_LOG_ERR("Serial bulk in/out endpoint not found\r\n");
+        usbh_serial_free(serial);
+        return NULL;
+    }
+
+    if (is_cdcacm) {
+        snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_CDC_ACM, serial->cdc_minor);
+    } else {
+        snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_VENDOR, serial->minor);
+    }
+
+    hport->config.intf[intf].priv = serial;
+    USB_LOG_INFO("Register Serial Class: %s (%s)\r\n", hport->config.intf[intf].devname, driver->driver_name);
+
+    usbh_serial_run(serial);
+
+    return serial;
+}
+
+void usbh_serial_remove(struct usbh_serial *serial)
+{
+    if (!serial || !serial->hport)
+        return;
+
+    usbh_serial_close(serial);
+
+    if (serial->driver && serial->driver->detach) {
+        serial->driver->detach(serial);
+    }
+
+    if (serial->hport->config.intf[serial->intf].priv) {
+        usb_osal_thread_schedule_other();
+        USB_LOG_INFO("Unregister Serial Class: %s (%s)\r\n", serial->hport->config.intf[serial->intf].devname, serial->driver->driver_name);
+        usbh_serial_stop(serial);
+    }
+
+    usbh_serial_free(serial);
+}
+
+struct usbh_serial *usbh_serial_open(const char *devname, uint32_t open_flags)
+{
+    struct usbh_serial *serial;
+    int ret;
+
+    serial = usbh_find_class_instance(devname);
+    if (!serial) {
+        return NULL;
+    }
+
+    if (serial->ref_count != 0) {
+        USB_LOG_ERR("Device busy: %s\r\n", devname);
+        return NULL;
+    }
+
+    if (serial && serial->driver && serial->driver->open) {
+        ret = serial->driver->open(serial);
+        if (ret < 0) {
+            return NULL;
+        }
+    }
+
+    usbh_serial_ringbuffer_init(&serial->rx_rb, serial->rx_rb_pool, CONFIG_USBHOST_SERIAL_RX_SIZE);
+
+    serial->ref_count++;
+    serial->open_flags = open_flags;
+
+    return serial;
+}
+
+int usbh_serial_close(struct usbh_serial *serial)
+{
+    if (!serial || !serial->hport) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->ref_count == 0) {
+        return 0;
+    }
+
+    if (serial->bulkin) {
+        usbh_kill_urb(&serial->bulkin_urb);
+    }
+    if (serial->bulkout) {
+        usbh_kill_urb(&serial->bulkout_urb);
+    }
+
+    if (serial && serial->driver && serial->driver->set_flow_control && serial->rtscts) {
+        serial->driver->set_flow_control(serial, false);
+    }
+
+    if (serial && serial->driver && serial->driver->close) {
+        serial->driver->close(serial);
+    }
+
+    serial->ref_count--;
+    serial->rtscts = false;
+
+    return 0;
+}
+
+static int usbh_serial_tiocmset(struct usbh_serial *serial, uint32_t set, uint32_t clear)
+{
+    int ret;
+    uint16_t line_state;
+    bool dtr;
+    bool rts;
+
+    if (!serial || !serial->hport || !serial->hport->connected) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->ref_count == 0) {
+        return -USB_ERR_NODEV;
+    }
+
+    line_state = serial->line_state;
+    clear &= ~set; /* 'set' takes precedence over 'clear' */
+
+    if (set & USBH_SERIAL_TIOCM_DTR) {
+        line_state |= USBH_SERIAL_TIOCM_DTR;
+    }
+    if (set & USBH_SERIAL_TIOCM_RTS) {
+        line_state |= USBH_SERIAL_TIOCM_RTS;
+    }
+    if (clear & USBH_SERIAL_TIOCM_DTR) {
+        line_state &= ~USBH_SERIAL_TIOCM_DTR;
+    }
+    if (clear & USBH_SERIAL_TIOCM_RTS) {
+        line_state &= ~USBH_SERIAL_TIOCM_RTS;
+    }
+
+    dtr = (line_state & USBH_SERIAL_TIOCM_RTS) ? true : false;
+    rts = (line_state & USBH_SERIAL_TIOCM_RTS) ? true : false;
+
+    if (serial && serial->driver && serial->driver->set_line_state) {
+        ret = serial->driver->set_line_state(serial, dtr, rts);
+    } else {
+        return -USB_ERR_NOTSUPP;
+    }
+    serial->line_state = line_state;
+
+    return ret;
+}
+
+int usbh_serial_control(struct usbh_serial *serial, int cmd, void *arg)
+{
+    int ret;
+
+    if (!serial || !serial->hport || !serial->hport->connected) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->ref_count == 0) {
+        return -USB_ERR_NODEV;
+    }
+
+    switch (cmd) {
+        case USBH_SERIAL_CMD_SET_ATTR: {
+            struct usbh_serial_termios *termios = (struct usbh_serial_termios *)arg;
+            struct cdc_line_coding line_coding;
+
+            line_coding.dwDTERate = termios->baudrate;
+            line_coding.bCharFormat = termios->stopbits;
+            line_coding.bParityType = termios->parity;
+            line_coding.bDataBits = termios->databits;
+
+            if (serial->bulkin) {
+                usbh_kill_urb(&serial->bulkin_urb);
+            }
+            if (serial->bulkout) {
+                usbh_kill_urb(&serial->bulkout_urb);
+            }
+
+            if (serial && serial->driver && serial->driver->set_line_coding) {
+                ret = serial->driver->set_line_coding(serial, &line_coding);
+                if (ret < 0) {
+                    return ret;
+                }
+            } else {
+                return -USB_ERR_NOTSUPP;
+            }
+
+            memcpy(&serial->line_coding, &line_coding, sizeof(struct cdc_line_coding));
+
+            if (serial && serial->driver && serial->driver->set_flow_control) {
+                ret = serial->driver->set_flow_control(serial, termios->rtscts);
+            }
+
+            serial->rtscts = termios->rtscts;
+            serial->rx_timeout_ms = termios->rx_timeout;
+
+            ret = usbh_serial_tiocmset(serial, USBH_SERIAL_TIOCM_DTR | USBH_SERIAL_TIOCM_RTS, 0);
+            if (ret < 0) {
+                return ret;
+            }
+
+            usbh_serial_ringbuffer_reset(&serial->rx_rb);
+            usb_osal_sem_reset(serial->rx_complete_sem);
+            usbh_bulk_urb_fill(&serial->bulkin_urb, serial->hport, serial->bulkin, &serial->iobuffer[USBH_SERIAL_RX_NOCACHE_OFFSET], serial->bulkin->wMaxPacketSize,
+                               0, usbh_serial_callback, serial);
+            ret = usbh_submit_urb(&serial->bulkin_urb);
+
+            return ret;
+        } break;
+        case USBH_SERIAL_CMD_GET_ATTR: {
+            struct usbh_serial_termios *termios = (struct usbh_serial_termios *)arg;
+            struct cdc_line_coding line_coding;
+
+            if (serial && serial->driver && serial->driver->get_line_coding) {
+                return serial->driver->get_line_coding(serial, &line_coding);
+            } else {
+                memcpy(&line_coding, &serial->line_coding, sizeof(struct cdc_line_coding));
+            }
+
+            termios->baudrate = line_coding.dwDTERate;
+            termios->stopbits = line_coding.bCharFormat;
+            termios->parity = line_coding.bParityType;
+            termios->databits = line_coding.bDataBits;
+            termios->rtscts = serial->rtscts;
+            termios->rx_timeout = serial->rx_timeout_ms;
+            return 0;
+        } break;
+        case USBH_SERIAL_CMD_IOCMBIS: {
+            uint32_t flags = *(uint32_t *)arg;
+
+            return usbh_serial_tiocmset(serial, flags, 0);
+        } break;
+        case USBH_SERIAL_CMD_IOCMBIC: {
+            uint32_t flags = *(uint32_t *)arg;
+
+            return usbh_serial_tiocmset(serial, 0, flags);
+        } break;
+        case USBH_SERIAL_CMD_TIOCMSET: {
+            uint32_t flags = *(uint32_t *)arg;
+
+            uint32_t set = 0;
+            uint32_t clear = 0;
+
+            set |= (flags & USBH_SERIAL_TIOCM_DTR) ? USBH_SERIAL_TIOCM_DTR : 0;
+            set |= (flags & USBH_SERIAL_TIOCM_RTS) ? USBH_SERIAL_TIOCM_RTS : 0;
+            clear |= !(flags & USBH_SERIAL_TIOCM_DTR) ? USBH_SERIAL_TIOCM_DTR : 0;
+            clear |= !(flags & USBH_SERIAL_TIOCM_RTS) ? USBH_SERIAL_TIOCM_RTS : 0;
+
+            return usbh_serial_tiocmset(serial, set, clear);
+        } break;
+        case USBH_SERIAL_CMD_TIOCMGET: {
+            uint32_t *flags = (uint32_t *)arg;
+            int status;
+
+            if (serial && serial->driver && serial->driver->get_modem_status) {
+                status = serial->driver->get_modem_status(serial);
+                if (status < 0) {
+                    return status;
+                }
+            } else {
+                return -USB_ERR_NOTSUPP;
+            }
+            *flags = status;
+        } break;
+        default:
+            break;
+    }
+
+    return -USB_ERR_NOTSUPP;
+}
+
+int usbh_serial_write(struct usbh_serial *serial, const void *buffer, uint32_t buflen)
+{
+    int ret;
+    struct usbh_urb *urb;
+
+    if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkout) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->ref_count == 0) {
+        return -USB_ERR_NODEV;
+    }
+
+    urb = &serial->bulkout_urb;
+
+    usbh_bulk_urb_fill(urb, serial->hport, serial->bulkout, (uint8_t *)buffer, serial->line_coding.dwDTERate ? MIN(buflen, serial->bulkout->wMaxPacketSize) : buflen, 0xffffffff, NULL, NULL);
+    ret = usbh_submit_urb(urb);
+    if (ret == 0) {
+        ret = urb->actual_length;
+    }
+    return ret;
+}
+
+int usbh_serial_read(struct usbh_serial *serial, void *buffer, uint32_t buflen)
+{
+    int ret;
+
+    if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkin || !serial->line_coding.dwDTERate) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->ref_count == 0) {
+        return -USB_ERR_NODEV;
+    }
+
+    if (serial->open_flags & USBH_SERIAL_O_NONBLOCK) {
+        return usbh_serial_ringbuffer_read(&serial->rx_rb, buffer, buflen);
+    } else {
+        if (usbh_serial_ringbuffer_get_used(&serial->rx_rb) == 0) {
+            ret = usb_osal_sem_take(serial->rx_complete_sem, serial->rx_timeout_ms == 0 ? USB_OSAL_WAITING_FOREVER : serial->rx_timeout_ms);
+            if (ret < 0) {
+                return ret;
+            }
+            if (serial->rx_errorcode < 0) {
+                return serial->rx_errorcode;
+            }
+        }
+        return usbh_serial_ringbuffer_read(&serial->rx_rb, buffer, buflen);
+    }
+}
+
+int usbh_serial_cdc_write_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg)
+{
+    struct usbh_urb *urb;
+
+    if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkout || !complete || serial->line_coding.dwDTERate) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->ref_count > 0) {
+        return -USB_ERR_NODEV;
+    }
+
+    urb = &serial->bulkout_urb;
+
+    usbh_bulk_urb_fill(urb, serial->hport, serial->bulkout, buffer, buflen,
+                       0, complete, serial);
+    return usbh_submit_urb(urb);
+}
+
+int usbh_serial_cdc_read_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg)
+{
+    struct usbh_urb *urb;
+
+    if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkin || !complete || serial->line_coding.dwDTERate) {
+        return -USB_ERR_INVAL;
+    }
+
+    if (serial->ref_count > 0) {
+        return -USB_ERR_NODEV;
+    }
+
+    if (buflen % serial->bulkin->wMaxPacketSize) {
+        return -USB_ERR_INVAL;
+    }
+
+    urb = &serial->bulkin_urb;
+
+    usbh_bulk_urb_fill(urb, serial->hport, serial->bulkin, buffer, MIN(buflen, serial->bulkin->wMaxPacketSize),
+                       0, complete, serial);
+    return usbh_submit_urb(urb);
+}
+
+void usbh_serial_help(void)
+{
+    USB_LOG_RAW("USB host serial test\r\n"
+                "Usage: usbh_serial <ttypath> [options]...\r\n"
+                "\r\n"
+                "-b <baud>             set serial baudrate\r\n"
+                "-t <dtr> <rts>        set rts and dtr\r\n"
+                "-w string             write string\r\n"
+                "-r                    read data and dump\r\n"
+                "-x                    close serial device\r\n"
+                "\r\n");
+}
+
+static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_serial_testbuffer[512];
+
+int usbh_serial(int argc, char **argv)
+{
+    static struct usbh_serial *serial;
+    int ret;
+
+    if (argc < 3) {
+        usbh_serial_help();
+        return 0;
+    }
+
+    if (!serial) {
+        serial = usbh_serial_open(argv[1], USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
+        if (!serial) {
+            USB_LOG_ERR("Fail to open serial device: %s\r\n", argv[1]);
+            return -USB_ERR_INVAL;
+        }
+    }
+
+    if (strncmp(argv[2], "-b", 2) == 0 && argc >= 4) {
+        struct usbh_serial_termios termios;
+
+        memset(&termios, 0, sizeof(termios));
+        termios.baudrate = atoi(argv[3]);
+        termios.stopbits = 0;
+        termios.parity = 0;
+        termios.databits = 8;
+        termios.rtscts = false;
+        termios.rx_timeout = 0;
+        usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);
+    } else if (strncmp(argv[2], "-t", 2) == 0 && argc >= 5) {
+        uint32_t flags;
+
+        flags = atoi(argv[3]) ? USBH_SERIAL_TIOCM_DTR : 0;
+        flags |= atoi(argv[4]) ? USBH_SERIAL_TIOCM_RTS : 0;
+
+        usbh_serial_control(serial, USBH_SERIAL_CMD_TIOCMSET, &flags);
+        USB_LOG_INFO("Set DTR: %d, RTS: %d\r\n", atoi(argv[3]), atoi(argv[4]));
+    } else if (strncmp(argv[2], "-w", 2) == 0 && argc >= 4) {
+        memcpy(g_serial_testbuffer, argv[3], MIN(strlen(argv[3]), sizeof(g_serial_testbuffer)));
+        uint32_t len = snprintf((char *)g_serial_testbuffer, sizeof(g_serial_testbuffer), "%s\r\n", argv[3]);
+        ret = usbh_serial_write(serial, g_serial_testbuffer, len);
+        if (ret >= 0) {
+            USB_LOG_INFO("Write %d bytes\r\n", ret);
+        } else {
+            USB_LOG_ERR("Write failed: %d\r\n", ret);
+        }
+    } else if (strncmp(argv[2], "-r", 2) == 0) {
+        ret = usbh_serial_read(serial, g_serial_testbuffer, sizeof(g_serial_testbuffer));
+        if (ret >= 0) {
+            usb_hexdump(g_serial_testbuffer, ret);
+            USB_LOG_INFO("Read %d bytes\r\n", ret);
+        } else {
+            USB_LOG_ERR("Read failed: %d\r\n", ret);
+        }
+    } else if (strncmp(argv[2], "-x", 2) == 0) {
+        usbh_serial_close(serial);
+        serial = NULL;
+    } else {
+        usbh_serial_help();
+    }
+
+    return 0;
+}
+
+__WEAK void usbh_serial_run(struct usbh_serial *serial)
+{
+    (void)serial;
+}
+
+__WEAK void usbh_serial_stop(struct usbh_serial *serial)
+{
+    (void)serial;
+}
+
+static int usbh_cdc_data_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+    (void)hport;
+    (void)intf;
+    return 0;
+}
+
+static int usbh_cdc_data_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+    (void)hport;
+    (void)intf;
+    return 0;
+}
+
+const struct usbh_class_driver cdc_data_class_driver = {
+    .driver_name = "cdc_data",
+    .connect = usbh_cdc_data_connect,
+    .disconnect = usbh_cdc_data_disconnect
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info cdc_data_class_info = {
+    .match_flags = USB_CLASS_MATCH_INTF_CLASS,
+    .bInterfaceClass = USB_DEVICE_CLASS_CDC_DATA,
+    .bInterfaceSubClass = 0x00,
+    .bInterfaceProtocol = 0x00,
+    .id_table = NULL,
+    .class_driver = &cdc_data_class_driver
+};

+ 179 - 0
class/serial/usbh_serial.h

@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2025, sakumisu
+ * Copyright (c) 2025, MDLZCOOL
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBH_SERIAL_H
+#define USBH_SERIAL_H
+
+#include "usb_cdc.h"
+
+#define USBH_SERIAL_CTRL_NOCACHE_SIZE   32
+#define USBH_SERIAL_CTRL_NOCACHE_OFFSET 0
+#define USBH_SERIAL_INT_NOCACHE_SIZE    32
+#define USBH_SERIAL_INT_NOCACHE_OFFSET  USB_ALIGN_UP(USBH_SERIAL_CTRL_NOCACHE_SIZE, CONFIG_USB_ALIGN_SIZE)
+#define USBH_SERIAL_RX_NOCACHE_OFFSET   USB_ALIGN_UP((USBH_SERIAL_INT_NOCACHE_OFFSET + USBH_SERIAL_INT_NOCACHE_SIZE), CONFIG_USB_ALIGN_SIZE)
+#define USBH_SERIAL_RX_NOCACHE_SIZE     512
+
+#define USBH_SERIAL_DATABITS_5 5
+#define USBH_SERIAL_DATABITS_6 6
+#define USBH_SERIAL_DATABITS_7 7
+#define USBH_SERIAL_DATABITS_8 8
+
+#define USBH_SERIAL_PARITY_NONE  0
+#define USBH_SERIAL_PARITY_ODD   1
+#define USBH_SERIAL_PARITY_EVEN  2
+#define USBH_SERIAL_PARITY_MARK  3
+#define USBH_SERIAL_PARITY_SPACE 4
+
+#define USBH_SERIAL_STOPBITS_1   0
+#define USBH_SERIAL_STOPBITS_1_5 1
+#define USBH_SERIAL_STOPBITS_2   2
+
+/* modem lines */
+#define USBH_SERIAL_TIOCM_LE   0x001 /* line enable */
+#define USBH_SERIAL_TIOCM_DTR  0x002 /* data terminal ready */
+#define USBH_SERIAL_TIOCM_RTS  0x004 /* request to send */
+#define USBH_SERIAL_TIOCM_ST   0x010 /* secondary transmit */
+#define USBH_SERIAL_TIOCM_SR   0x020 /* secondary receive */
+#define USBH_SERIAL_TIOCM_CTS  0x040 /* clear to send */
+#define USBH_SERIAL_TIOCM_CAR  0x100 /* carrier detect */
+#define USBH_SERIAL_TIOCM_CD   USBH_SERIAL_TIOCM_CAR
+#define USBH_SERIAL_TIOCM_RNG  0x200 /* ring */
+#define USBH_SERIAL_TIOCM_RI   USBH_SERIAL_TIOCM_RNG
+#define USBH_SERIAL_TIOCM_DSR  0x400 /* data set ready */
+#define USBH_SERIAL_TIOCM_OUT1 0x2000
+#define USBH_SERIAL_TIOCM_OUT2 0x4000
+#define USBH_SERIAL_TIOCM_LOOP 0x8000
+
+#define USBH_SERIAL_O_RDONLY 0x0000 /* open for reading only */
+#define USBH_SERIAL_O_WRONLY 0x0001 /* open for writing only */
+#define USBH_SERIAL_O_RDWR   0x0002 /* open for reading and writing */
+
+#define USBH_SERIAL_O_ACCMODE  0x0003 /* mask for above modes, from 4.4BSD https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/include/sys/fcntl.h */
+#define USBH_SERIAL_O_NONBLOCK 0x0004 /* non blocking I/O, from BSD apple https://opensource.apple.com/source/xnu/xnu-1228.0.2/bsd/sys/fcntl.h */
+
+#define USBH_SERIAL_CMD_SET_ATTR 0
+#define USBH_SERIAL_CMD_GET_ATTR 1
+#define USBH_SERIAL_CMD_IOCMBIS  2
+#define USBH_SERIAL_CMD_IOCMBIC  3
+#define USBH_SERIAL_CMD_TIOCMSET 4
+#define USBH_SERIAL_CMD_TIOCMGET 5
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+    uint32_t in;   /*!< Define the write pointer.               */
+    uint32_t out;  /*!< Define the read pointer.                */
+    uint32_t mask; /*!< Define the write and read pointer mask. */
+    void *pool;    /*!< Define the memory pointer.              */
+} usbh_serial_ringbuf_t;
+
+/*
+ * Counters of the input lines (CTS, DSR, RI, CD) interrupts
+ */
+
+struct usbh_serial_async_icount {
+    uint32_t cts, dsr, rng, dcd, tx, rx;
+    uint32_t frame, parity, overrun, brk;
+    uint32_t buf_overrun;
+};
+
+struct usbh_serial_termios {
+    uint32_t baudrate;
+    uint8_t databits;
+    uint8_t parity;
+    uint8_t stopbits;
+    bool rtscts; /* hardware flow control */
+    uint32_t rx_timeout;
+};
+
+struct usbh_serial;
+
+typedef void (*usbh_serial_rx_complete_callback_t)(struct usbh_serial *serial, int nbytes);
+
+/**
+ * @brief Serial Driver Operations
+ */
+struct usbh_serial_driver {
+    const char *driver_name;
+
+    uint8_t ignore_tx_header;
+    uint8_t ignore_rx_header;
+
+    int (*attach)(struct usbh_serial *serial);
+    void (*detach)(struct usbh_serial *serial);
+
+    int (*open)(struct usbh_serial *serial);
+    void (*close)(struct usbh_serial *serial);
+    int (*set_flow_control)(struct usbh_serial *serial, bool enable);
+    int (*set_line_coding)(struct usbh_serial *serial, struct cdc_line_coding *line_coding);
+    int (*get_line_coding)(struct usbh_serial *serial, struct cdc_line_coding *line_coding);
+    int (*set_line_state)(struct usbh_serial *serial, bool dtr, bool rts);
+    int (*get_modem_status)(struct usbh_serial *serial);
+};
+
+/**
+ * @brief Serial Instance
+ */
+struct usbh_serial {
+    struct usbh_hubport *hport;
+    uint8_t intf;      /* Interface Number */
+    int minor;         /* Serial Port Number (/dev/ttyUSBx or /dev/ttyACMx) */
+    int cdc_minor;     /* Serial Port Number (/dev/ttyACMx) */
+    uint8_t *iobuffer; /* I/O buffer for serial transfers */
+    uint8_t ref_count; /* Reference Count */
+    uint32_t open_flags;
+    uint32_t rx_timeout_ms;
+
+    struct cdc_line_coding line_coding;
+    uint16_t line_state;
+    bool rtscts; /* hardware flow control */
+    struct usbh_serial_async_icount iocount;
+
+    struct usb_endpoint_descriptor *bulkin;  /* Bulk IN endpoint */
+    struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
+    struct usbh_urb bulkout_urb;
+    struct usbh_urb bulkin_urb;
+
+    const struct usbh_serial_driver *driver;
+
+    usbh_serial_ringbuf_t rx_rb;
+    uint8_t rx_rb_pool[CONFIG_USBHOST_SERIAL_RX_SIZE];
+    usb_osal_sem_t rx_complete_sem;
+    int rx_errorcode;
+    usbh_serial_rx_complete_callback_t rx_complete_callback;
+
+    void *priv;      /* Private Data */
+    void *user_data; /* User Data */
+};
+
+/* internal api */
+struct usbh_serial *usbh_serial_probe(struct usbh_hubport *hport, uint8_t intf, const struct usbh_serial_driver *driver);
+void usbh_serial_remove(struct usbh_serial *serial);
+
+/* public api */
+struct usbh_serial *usbh_serial_open(const char *devname, uint32_t open_flags);
+int usbh_serial_close(struct usbh_serial *serial);
+int usbh_serial_control(struct usbh_serial *serial, int cmd, void *arg);
+int usbh_serial_write(struct usbh_serial *serial, const void *buffer, uint32_t buflen);
+int usbh_serial_read(struct usbh_serial *serial, void *buffer, uint32_t buflen);
+
+/* cdc only api */
+int usbh_serial_cdc_write_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg);
+int usbh_serial_cdc_read_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg);
+
+/* public weak api */
+void usbh_serial_run(struct usbh_serial *serial);
+void usbh_serial_stop(struct usbh_serial *serial);
+
+int usbh_serial(int argc, char **argv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USBH_SERIAL_H */

+ 0 - 379
class/vendor/serial/usbh_ch34x.c

@@ -1,379 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#include "usbh_core.h"
-#include "usbh_ch34x.h"
-
-#define DEV_FORMAT "/dev/ttyUSB%d"
-
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_ch34x_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
-
-#define CONFIG_USBHOST_MAX_CP210X_CLASS 1
-
-static struct usbh_ch34x g_ch34x_class[CONFIG_USBHOST_MAX_CP210X_CLASS];
-static uint32_t g_devinuse = 0;
-
-static struct usbh_ch34x *usbh_ch34x_class_alloc(void)
-{
-    uint8_t devno;
-
-    for (devno = 0; devno < CONFIG_USBHOST_MAX_CP210X_CLASS; devno++) {
-        if ((g_devinuse & (1U << devno)) == 0) {
-            g_devinuse |= (1U << devno);
-            memset(&g_ch34x_class[devno], 0, sizeof(struct usbh_ch34x));
-            g_ch34x_class[devno].minor = devno;
-            return &g_ch34x_class[devno];
-        }
-    }
-    return NULL;
-}
-
-static void usbh_ch34x_class_free(struct usbh_ch34x *ch34x_class)
-{
-    uint8_t devno = ch34x_class->minor;
-
-    if (devno < 32) {
-        g_devinuse &= ~(1U << devno);
-    }
-    memset(ch34x_class, 0, sizeof(struct usbh_ch34x));
-}
-
-static int usbh_ch34x_get_baudrate_div(uint32_t baudrate, uint8_t *factor, uint8_t *divisor)
-{
-    uint8_t a;
-    uint8_t b;
-    uint32_t c;
-
-    switch (baudrate) {
-        case 921600:
-            a = 0xf3;
-            b = 7;
-            break;
-
-        case 307200:
-            a = 0xd9;
-            b = 7;
-            break;
-
-        default:
-            if (baudrate > 6000000 / 255) {
-                b = 3;
-                c = 6000000;
-            } else if (baudrate > 750000 / 255) {
-                b = 2;
-                c = 750000;
-            } else if (baudrate > 93750 / 255) {
-                b = 1;
-                c = 93750;
-            } else {
-                b = 0;
-                c = 11719;
-            }
-            a = (uint8_t)(c / baudrate);
-            if (a == 0 || a == 0xFF) {
-                return -USB_ERR_INVAL;
-            }
-            if ((c / a - baudrate) > (baudrate - c / (a + 1))) {
-                a++;
-            }
-            a = (uint8_t)(256 - a);
-            break;
-    }
-
-    *factor = a;
-    *divisor = b;
-
-    return 0;
-}
-
-static int usbh_ch34x_get_version(struct usbh_ch34x *ch34x_class)
-{
-    struct usb_setup_packet *setup;
-    int ret;
-
-    if (!ch34x_class || !ch34x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ch34x_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = CH34X_READ_VERSION;
-    setup->wValue = 0;
-    setup->wIndex = 0;
-    setup->wLength = 2;
-
-    ret = usbh_control_transfer(ch34x_class->hport, setup, g_ch34x_buf);
-    if (ret < 0) {
-        return ret;
-    }
-
-    USB_LOG_INFO("Ch34x chip version %02x:%02x\r\n", g_ch34x_buf[0], g_ch34x_buf[1]);
-    return ret;
-}
-
-static int usbh_ch34x_flow_ctrl(struct usbh_ch34x *ch34x_class)
-{
-    struct usb_setup_packet *setup;
-
-    if (!ch34x_class || !ch34x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ch34x_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = CH34X_WRITE_REG;
-    setup->wValue = 0x2727;
-    setup->wIndex = 0;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ch34x_class->hport, setup, NULL);
-}
-
-int usbh_ch34x_set_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding)
-{
-    struct usb_setup_packet *setup;
-    uint16_t reg_value = 0;
-    uint16_t value = 0;
-    uint8_t factor = 0;
-    uint8_t divisor = 0;
-
-    if (!ch34x_class || !ch34x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ch34x_class->hport->setup;
-
-    memcpy((uint8_t *)&ch34x_class->line_coding, line_coding, sizeof(struct cdc_line_coding));
-
-    /* refer to https://github.com/WCHSoftGroup/ch341ser_linux/blob/main/driver/ch341.c */
-
-    switch (line_coding->bParityType) {
-        case 0:
-            break;
-        case 1:
-            reg_value |= CH341_L_PO;
-            break;
-        case 2:
-            reg_value |= CH341_L_PE;
-            break;
-        case 3:
-            reg_value |= CH341_L_PM;
-            break;
-        case 4:
-            reg_value |= CH341_L_PS;
-            break;
-        default:
-            return -USB_ERR_INVAL;
-    }
-
-    switch (line_coding->bDataBits) {
-        case 5:
-            reg_value |= CH341_L_D5;
-            break;
-        case 6:
-            reg_value |= CH341_L_D6;
-            break;
-        case 7:
-            reg_value |= CH341_L_D7;
-            break;
-        case 8:
-            reg_value |= CH341_L_D8;
-            break;
-        default:
-            return -USB_ERR_INVAL;
-    }
-
-    if (line_coding->bCharFormat == 2) {
-        reg_value |= CH341_L_SB;
-    }
-
-    reg_value |= 0xC0;
-
-    value |= 0x9c;
-    value |= reg_value << 8;
-
-    usbh_ch34x_get_baudrate_div(line_coding->dwDTERate, &factor, &divisor);
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = CH34X_SERIAL_INIT;
-    setup->wValue = value;
-    setup->wIndex = (factor << 8) | 0x80 | divisor;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ch34x_class->hport, setup, NULL);
-}
-
-int usbh_ch34x_get_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding)
-{
-    memcpy(line_coding, (uint8_t *)&ch34x_class->line_coding, sizeof(struct cdc_line_coding));
-    return 0;
-}
-
-int usbh_ch34x_set_line_state(struct usbh_ch34x *ch34x_class, bool dtr, bool rts)
-{
-    struct usb_setup_packet *setup;
-
-    if (!ch34x_class || !ch34x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ch34x_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = CH34X_MODEM_CTRL;
-    setup->wValue = 0x0f | (dtr << 5) | (rts << 6);
-    setup->wIndex = 0;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ch34x_class->hport, setup, NULL);
-}
-
-static int usbh_ch34x_connect(struct usbh_hubport *hport, uint8_t intf)
-{
-    struct usb_endpoint_descriptor *ep_desc;
-    int ret = 0;
-
-    struct usbh_ch34x *ch34x_class = usbh_ch34x_class_alloc();
-    if (ch34x_class == NULL) {
-        USB_LOG_ERR("Fail to alloc ch34x_class\r\n");
-        return -USB_ERR_NOMEM;
-    }
-
-    ch34x_class->hport = hport;
-    ch34x_class->intf = intf;
-
-    hport->config.intf[intf].priv = ch34x_class;
-
-    usbh_ch34x_get_version(ch34x_class);
-    usbh_ch34x_flow_ctrl(ch34x_class);
-
-    for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
-        ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
-        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
-            continue;
-        } else {
-            if (ep_desc->bEndpointAddress & 0x80) {
-                USBH_EP_INIT(ch34x_class->bulkin, ep_desc);
-            } else {
-                USBH_EP_INIT(ch34x_class->bulkout, ep_desc);
-            }
-        }
-    }
-
-    snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, ch34x_class->minor);
-
-    USB_LOG_INFO("Register CH34X Class:%s\r\n", hport->config.intf[intf].devname);
-
-#if 0
-    USB_LOG_INFO("Test ch34x rx and tx and rx for 5 times, baudrate is 115200\r\n");
-
-    struct cdc_line_coding linecoding;
-    uint8_t count = 5;
-
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_ch34x_set_line_coding(ch34x_class, &linecoding);
-    usbh_ch34x_set_line_state(ch34x_class, true, false);
-
-    memset(g_ch34x_buf, 'a', sizeof(g_ch34x_buf));
-    ret = usbh_ch34x_bulk_out_transfer(ch34x_class, g_ch34x_buf, sizeof(g_ch34x_buf), 0xfffffff);
-    USB_LOG_RAW("out ret:%d\r\n", ret);
-    while (count--) {
-        ret = usbh_ch34x_bulk_in_transfer(ch34x_class, g_ch34x_buf, sizeof(g_ch34x_buf), 0xfffffff);
-        USB_LOG_RAW("in ret:%d\r\n", ret);
-        if (ret > 0) {
-            for (uint32_t i = 0; i < ret; i++) {
-                USB_LOG_RAW("%02x ", g_ch34x_buf[i]);
-            }
-            USB_LOG_RAW("\r\n");
-        }
-    }
-#endif
-    usbh_ch34x_run(ch34x_class);
-    return ret;
-}
-
-static int usbh_ch34x_disconnect(struct usbh_hubport *hport, uint8_t intf)
-{
-    int ret = 0;
-
-    struct usbh_ch34x *ch34x_class = (struct usbh_ch34x *)hport->config.intf[intf].priv;
-
-    if (ch34x_class) {
-        if (ch34x_class->bulkin) {
-            usbh_kill_urb(&ch34x_class->bulkin_urb);
-        }
-
-        if (ch34x_class->bulkout) {
-            usbh_kill_urb(&ch34x_class->bulkout_urb);
-        }
-
-        if (hport->config.intf[intf].devname[0] != '\0') {
-            usb_osal_thread_schedule_other();
-            USB_LOG_INFO("Unregister CH34X Class:%s\r\n", hport->config.intf[intf].devname);
-            usbh_ch34x_stop(ch34x_class);
-        }
-
-        usbh_ch34x_class_free(ch34x_class);
-    }
-
-    return ret;
-}
-
-int usbh_ch34x_bulk_in_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &ch34x_class->bulkin_urb;
-
-    usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkin, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-int usbh_ch34x_bulk_out_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &ch34x_class->bulkout_urb;
-
-    usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkout, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-__WEAK void usbh_ch34x_run(struct usbh_ch34x *ch34x_class)
-{
-    (void)ch34x_class;
-}
-
-__WEAK void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class)
-{
-    (void)ch34x_class;
-}
-
-static const uint16_t ch34x_id_table[][2] = {
-    { 0x1A86, 0x7523 },
-    { 0, 0 },
-};
-
-const struct usbh_class_driver ch34x_class_driver = {
-    .driver_name = "ch34x",
-    .connect = usbh_ch34x_connect,
-    .disconnect = usbh_ch34x_disconnect
-};
-
-CLASS_INFO_DEFINE const struct usbh_class_info ch34x_class_info = {
-    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
-    .bInterfaceClass = 0xff,
-    .bInterfaceSubClass = 0x00,
-    .bInterfaceProtocol = 0x00,
-    .id_table = ch34x_id_table,
-    .class_driver = &ch34x_class_driver
-};

+ 0 - 76
class/vendor/serial/usbh_ch34x.h

@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#ifndef USBH_CH34X_H
-#define USBH_CH34X_H
-
-#include "usb_cdc.h"
-
-/* Requests */
-#define CH34X_READ_VERSION 0x5F
-#define CH34X_WRITE_REG    0x9A
-#define CH34X_READ_REG     0x95
-#define CH34X_SERIAL_INIT  0xA1
-#define CH34X_MODEM_CTRL   0xA4
-
-// modem control bits
-#define CH34X_BIT_RTS (1 << 6)
-#define CH34X_BIT_DTR (1 << 5)
-
-#define CH341_CTO_O   0x10
-#define CH341_CTO_D   0x20
-#define CH341_CTO_R   0x40
-#define CH341_CTI_C   0x01
-#define CH341_CTI_DS  0x02
-#define CH341_CTRL_RI 0x04
-#define CH341_CTI_DC  0x08
-#define CH341_CTI_ST  0x0f
-
-#define CH341_L_ER 0x80
-#define CH341_L_ET 0x40
-#define CH341_L_PS 0x38
-#define CH341_L_PM 0x28
-#define CH341_L_PE 0x18
-#define CH341_L_PO 0x08
-#define CH341_L_SB 0x04
-#define CH341_L_D8 0x03
-#define CH341_L_D7 0x02
-#define CH341_L_D6 0x01
-#define CH341_L_D5 0x00
-
-struct usbh_ch34x {
-    struct usbh_hubport *hport;
-    struct usb_endpoint_descriptor *bulkin;  /* Bulk IN endpoint */
-    struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
-    struct usbh_urb bulkout_urb;
-    struct usbh_urb bulkin_urb;
-
-    struct cdc_line_coding line_coding;
-
-    uint8_t intf;
-    uint8_t minor;
-
-    void *user_data;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int usbh_ch34x_set_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding);
-int usbh_ch34x_get_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding);
-int usbh_ch34x_set_line_state(struct usbh_ch34x *ch34x_class, bool dtr, bool rts);
-
-int usbh_ch34x_bulk_in_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-int usbh_ch34x_bulk_out_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-
-void usbh_ch34x_run(struct usbh_ch34x *ch34x_class);
-void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* USBH_CH34X_H */

+ 0 - 328
class/vendor/serial/usbh_cp210x.c

@@ -1,328 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#include "usbh_core.h"
-#include "usbh_cp210x.h"
-
-#define DEV_FORMAT "/dev/ttyUSB%d"
-
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cp210x_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
-
-#define CONFIG_USBHOST_MAX_CP210X_CLASS 1
-
-static struct usbh_cp210x g_cp210x_class[CONFIG_USBHOST_MAX_CP210X_CLASS];
-static uint32_t g_devinuse = 0;
-
-static struct usbh_cp210x *usbh_cp210x_class_alloc(void)
-{
-    uint8_t devno;
-
-    for (devno = 0; devno < CONFIG_USBHOST_MAX_CP210X_CLASS; devno++) {
-        if ((g_devinuse & (1U << devno)) == 0) {
-            g_devinuse |= (1U << devno);
-            memset(&g_cp210x_class[devno], 0, sizeof(struct usbh_cp210x));
-            g_cp210x_class[devno].minor = devno;
-            return &g_cp210x_class[devno];
-        }
-    }
-    return NULL;
-}
-
-static void usbh_cp210x_class_free(struct usbh_cp210x *cp210x_class)
-{
-    uint8_t devno = cp210x_class->minor;
-
-    if (devno < 32) {
-        g_devinuse &= ~(1U << devno);
-    }
-    memset(cp210x_class, 0, sizeof(struct usbh_cp210x));
-}
-
-static int usbh_cp210x_enable(struct usbh_cp210x *cp210x_class)
-{
-    struct usb_setup_packet *setup;
-
-    if (!cp210x_class || !cp210x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cp210x_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CP210X_IFC_ENABLE;
-    setup->wValue = 1;
-    setup->wIndex = cp210x_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(cp210x_class->hport, setup, NULL);
-}
-
-static int usbh_cp210x_set_flow(struct usbh_cp210x *cp210x_class)
-{
-    struct usb_setup_packet *setup;
-
-    if (!cp210x_class || !cp210x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cp210x_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CP210X_SET_FLOW;
-    setup->wValue = 0;
-    setup->wIndex = cp210x_class->intf;
-    setup->wLength = 16;
-
-    memset(g_cp210x_buf, 0, 16);
-    g_cp210x_buf[13] = 0x20;
-    return usbh_control_transfer(cp210x_class->hport, setup, g_cp210x_buf);
-}
-
-static int usbh_cp210x_set_chars(struct usbh_cp210x *cp210x_class)
-{
-    struct usb_setup_packet *setup;
-
-    if (!cp210x_class || !cp210x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cp210x_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CP210X_SET_CHARS;
-    setup->wValue = 0;
-    setup->wIndex = cp210x_class->intf;
-    setup->wLength = 6;
-
-    memset(g_cp210x_buf, 0, 6);
-    g_cp210x_buf[0] = 0x80;
-    g_cp210x_buf[4] = 0x88;
-    g_cp210x_buf[5] = 0x28;
-    return usbh_control_transfer(cp210x_class->hport, setup, g_cp210x_buf);
-}
-
-static int usbh_cp210x_set_baudrate(struct usbh_cp210x *cp210x_class, uint32_t baudrate)
-{
-    struct usb_setup_packet *setup;
-
-    if (!cp210x_class || !cp210x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cp210x_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CP210X_SET_BAUDRATE;
-    setup->wValue = 0;
-    setup->wIndex = cp210x_class->intf;
-    setup->wLength = 4;
-
-    memcpy(g_cp210x_buf, (uint8_t *)&baudrate, 4);
-    return usbh_control_transfer(cp210x_class->hport, setup, g_cp210x_buf);
-}
-
-static int usbh_cp210x_set_data_format(struct usbh_cp210x *cp210x_class, uint8_t databits, uint8_t parity, uint8_t stopbits)
-{
-    struct usb_setup_packet *setup;
-    uint16_t value;
-
-    if (!cp210x_class || !cp210x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cp210x_class->hport->setup;
-
-    value = ((databits & 0x0F) << 8) | ((parity & 0x0f) << 4) | ((stopbits & 0x03) << 0);
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CP210X_SET_LINE_CTL;
-    setup->wValue = value;
-    setup->wIndex = cp210x_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(cp210x_class->hport, setup, NULL);
-}
-
-static int usbh_cp210x_set_mhs(struct usbh_cp210x *cp210x_class, uint8_t dtr, uint8_t rts, uint8_t dtr_mask, uint8_t rts_mask)
-{
-    struct usb_setup_packet *setup;
-    uint16_t value;
-
-    if (!cp210x_class || !cp210x_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = cp210x_class->hport->setup;
-
-    value = ((dtr & 0x01) << 0) | ((rts & 0x01) << 1) | ((dtr_mask & 0x01) << 8) | ((rts_mask & 0x01) << 9);
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CP210X_SET_MHS;
-    setup->wValue = value;
-    setup->wIndex = cp210x_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(cp210x_class->hport, setup, NULL);
-}
-
-int usbh_cp210x_set_line_coding(struct usbh_cp210x *cp210x_class, struct cdc_line_coding *line_coding)
-{
-    memcpy((uint8_t *)&cp210x_class->line_coding, line_coding, sizeof(struct cdc_line_coding));
-    usbh_cp210x_set_baudrate(cp210x_class, line_coding->dwDTERate);
-    return usbh_cp210x_set_data_format(cp210x_class, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat);
-}
-
-int usbh_cp210x_get_line_coding(struct usbh_cp210x *cp210x_class, struct cdc_line_coding *line_coding)
-{
-    memcpy(line_coding, (uint8_t *)&cp210x_class->line_coding, sizeof(struct cdc_line_coding));
-    return 0;
-}
-
-int usbh_cp210x_set_line_state(struct usbh_cp210x *cp210x_class, bool dtr, bool rts)
-{
-    return usbh_cp210x_set_mhs(cp210x_class, dtr, rts, 1, 1);
-}
-
-static int usbh_cp210x_connect(struct usbh_hubport *hport, uint8_t intf)
-{
-    struct usb_endpoint_descriptor *ep_desc;
-    int ret = 0;
-
-    struct usbh_cp210x *cp210x_class = usbh_cp210x_class_alloc();
-    if (cp210x_class == NULL) {
-        USB_LOG_ERR("Fail to alloc cp210x_class\r\n");
-        return -USB_ERR_NOMEM;
-    }
-
-    cp210x_class->hport = hport;
-    cp210x_class->intf = intf;
-
-    hport->config.intf[intf].priv = cp210x_class;
-
-    usbh_cp210x_enable(cp210x_class);
-    usbh_cp210x_set_flow(cp210x_class);
-    usbh_cp210x_set_chars(cp210x_class);
-
-    for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
-        ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
-
-        if (ep_desc->bEndpointAddress & 0x80) {
-            USBH_EP_INIT(cp210x_class->bulkin, ep_desc);
-        } else {
-            USBH_EP_INIT(cp210x_class->bulkout, ep_desc);
-        }
-    }
-
-    snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, cp210x_class->minor);
-
-    USB_LOG_INFO("Register CP210X Class:%s\r\n", hport->config.intf[intf].devname);
-
-#if 0
-    USB_LOG_INFO("Test cp2102 rx and tx and rx for 5 times, baudrate is 115200\r\n");
-
-    struct cdc_line_coding linecoding;
-    uint8_t count = 5;
-
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_cp210x_set_line_coding(cp210x_class, &linecoding);
-    usbh_cp210x_set_line_state(cp210x_class, true, false);
-
-    memset(g_cp210x_buf, 'a', sizeof(g_cp210x_buf));
-    ret = usbh_cp210x_bulk_out_transfer(cp210x_class, g_cp210x_buf, sizeof(g_cp210x_buf), 0xfffffff);
-    USB_LOG_RAW("out ret:%d\r\n", ret);
-    while (count--) {
-        ret = usbh_cp210x_bulk_in_transfer(cp210x_class, g_cp210x_buf, sizeof(g_cp210x_buf), 0xfffffff);
-        USB_LOG_RAW("in ret:%d\r\n", ret);
-        if (ret > 0) {
-            for (uint32_t i = 0; i < ret; i++) {
-                USB_LOG_RAW("%02x ", g_cp210x_buf[i]);
-            }
-            USB_LOG_RAW("\r\n");
-        }
-    }
-#endif
-    usbh_cp210x_run(cp210x_class);
-    return ret;
-}
-
-static int usbh_cp210x_disconnect(struct usbh_hubport *hport, uint8_t intf)
-{
-    int ret = 0;
-
-    struct usbh_cp210x *cp210x_class = (struct usbh_cp210x *)hport->config.intf[intf].priv;
-
-    if (cp210x_class) {
-        if (cp210x_class->bulkin) {
-            usbh_kill_urb(&cp210x_class->bulkin_urb);
-        }
-
-        if (cp210x_class->bulkout) {
-            usbh_kill_urb(&cp210x_class->bulkout_urb);
-        }
-
-        if (hport->config.intf[intf].devname[0] != '\0') {
-            usb_osal_thread_schedule_other();
-            USB_LOG_INFO("Unregister CP210X Class:%s\r\n", hport->config.intf[intf].devname);
-            usbh_cp210x_stop(cp210x_class);
-        }
-
-        usbh_cp210x_class_free(cp210x_class);
-    }
-
-    return ret;
-}
-
-int usbh_cp210x_bulk_in_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &cp210x_class->bulkin_urb;
-
-    usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkin, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-int usbh_cp210x_bulk_out_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &cp210x_class->bulkout_urb;
-
-    usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkout, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-__WEAK void usbh_cp210x_run(struct usbh_cp210x *cp210x_class)
-{
-    (void)cp210x_class;
-}
-
-__WEAK void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class)
-{
-    (void)cp210x_class;
-}
-
-static const uint16_t cp210x_id_table[][2] = {
-    { 0x10C4, 0xEA60 },
-    { 0, 0 },
-};
-
-const struct usbh_class_driver cp210x_class_driver = {
-    .driver_name = "cp210x",
-    .connect = usbh_cp210x_connect,
-    .disconnect = usbh_cp210x_disconnect
-};
-
-CLASS_INFO_DEFINE const struct usbh_class_info cp210x_class_info = {
-    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
-    .bInterfaceClass = 0xff,
-    .bInterfaceSubClass = 0x00,
-    .bInterfaceProtocol = 0x00,
-    .id_table = cp210x_id_table,
-    .class_driver = &cp210x_class_driver
-};

+ 0 - 73
class/vendor/serial/usbh_cp210x.h

@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#ifndef USBH_CP210X_H
-#define USBH_CP210X_H
-
-#include "usb_cdc.h"
-
-/* Requests */
-#define CP210X_IFC_ENABLE      0x00
-#define CP210X_SET_BAUDDIV     0x01
-#define CP210X_GET_BAUDDIV     0x02
-#define CP210X_SET_LINE_CTL    0x03 // Set parity, data bits, stop bits
-#define CP210X_GET_LINE_CTL    0x04
-#define CP210X_SET_BREAK       0x05
-#define CP210X_IMM_CHAR        0x06
-#define CP210X_SET_MHS         0x07 // Set DTR, RTS
-#define CP210X_GET_MDMSTS      0x08
-#define CP210X_SET_XON         0x09
-#define CP210X_SET_XOFF        0x0A
-#define CP210X_SET_EVENTMASK   0x0B
-#define CP210X_GET_EVENTMASK   0x0C
-#define CP210X_SET_CHAR        0x0D
-#define CP210X_GET_CHARS       0x0E
-#define CP210X_GET_PROPS       0x0F
-#define CP210X_GET_COMM_STATUS 0x10
-#define CP210X_RESET           0x11
-#define CP210X_PURGE           0x12
-#define CP210X_SET_FLOW        0x13
-#define CP210X_GET_FLOW        0x14
-#define CP210X_EMBED_EVENTS    0x15
-#define CP210X_GET_EVENTSTATE  0x16
-#define CP210X_SET_CHARS       0x19
-#define CP210X_GET_BAUDRATE    0x1D
-#define CP210X_SET_BAUDRATE    0x1E // Set baudrate
-#define CP210X_VENDOR_SPECIFIC 0xFF
-
-struct usbh_cp210x {
-    struct usbh_hubport *hport;
-    struct usb_endpoint_descriptor *bulkin;  /* Bulk IN endpoint */
-    struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
-    struct usbh_urb bulkout_urb;
-    struct usbh_urb bulkin_urb;
-
-    struct cdc_line_coding line_coding;
-
-    uint8_t intf;
-    uint8_t minor;
-
-    void *user_data;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int usbh_cp210x_set_line_coding(struct usbh_cp210x *ftdi_class, struct cdc_line_coding *line_coding);
-int usbh_cp210x_get_line_coding(struct usbh_cp210x *ftdi_class, struct cdc_line_coding *line_coding);
-int usbh_cp210x_set_line_state(struct usbh_cp210x *ftdi_class, bool dtr, bool rts);
-
-int usbh_cp210x_bulk_in_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-int usbh_cp210x_bulk_out_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-
-void usbh_cp210x_run(struct usbh_cp210x *cp210x_class);
-void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* USBH_CP210X_H */

+ 0 - 510
class/vendor/serial/usbh_ftdi.c

@@ -1,510 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#include "usbh_core.h"
-#include "usbh_ftdi.h"
-
-#define DEV_FORMAT "/dev/ttyUSB%d"
-
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_ftdi_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
-
-#define CONFIG_USBHOST_MAX_FTDI_CLASS 1
-
-static struct usbh_ftdi g_ftdi_class[CONFIG_USBHOST_MAX_FTDI_CLASS];
-static uint32_t g_devinuse = 0;
-
-static const char *ftdi_chip_name[] = {
-    [SIO] = "SIO", /* the serial part of FT8U100AX */
-    [FT232A] = "FT232A",
-    [FT232B] = "FT232B",
-    [FT2232C] = "FT2232C/D",
-    [FT232R] = "FT232R",
-    [FT232H] = "FT232H",
-    [FT2232H] = "FT2232H",
-    [FT4232H] = "FT4232H",
-    [FT4232HA] = "FT4232HA",
-    [FT232HP] = "FT232HP",
-    [FT233HP] = "FT233HP",
-    [FT2232HP] = "FT2232HP",
-    [FT2233HP] = "FT2233HP",
-    [FT4232HP] = "FT4232HP",
-    [FT4233HP] = "FT4233HP",
-    [FTX] = "FT-X",
-};
-
-static struct usbh_ftdi *usbh_ftdi_class_alloc(void)
-{
-    uint8_t devno;
-
-    for (devno = 0; devno < CONFIG_USBHOST_MAX_FTDI_CLASS; devno++) {
-        if ((g_devinuse & (1U << devno)) == 0) {
-            g_devinuse |= (1U << devno);
-            memset(&g_ftdi_class[devno], 0, sizeof(struct usbh_ftdi));
-            g_ftdi_class[devno].minor = devno;
-            return &g_ftdi_class[devno];
-        }
-    }
-    return NULL;
-}
-
-static void usbh_ftdi_class_free(struct usbh_ftdi *ftdi_class)
-{
-    uint8_t devno = ftdi_class->minor;
-
-    if (devno < 32) {
-        g_devinuse &= ~(1U << devno);
-    }
-    memset(ftdi_class, 0, sizeof(struct usbh_ftdi));
-}
-
-/*
- * Divide positive or negative dividend by positive or negative divisor
- * and round to closest integer. Result is undefined for negative
- * divisors if the dividend variable type is unsigned and for negative
- * dividends if the divisor variable type is unsigned.
- */
-#define DIV_ROUND_CLOSEST(x, divisor) (       \
-    {                                         \
-        typeof(x) __x = x;                    \
-        typeof(divisor) __d = divisor;        \
-        (((typeof(x))-1) > 0 ||               \
-         ((typeof(divisor))-1) > 0 ||         \
-         (((__x) > 0) == ((__d) > 0))) ?      \
-            (((__x) + ((__d) / 2)) / (__d)) : \
-            (((__x) - ((__d) / 2)) / (__d));  \
-    })
-
-static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, int base)
-{
-    static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
-    uint32_t divisor;
-    /* divisor shifted 3 bits to the left */
-    int divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud);
-    divisor = divisor3 >> 3;
-    divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
-    /* Deal with special cases for highest baud rates. */
-    if (divisor == 1) /* 1.0 */
-        divisor = 0;
-    else if (divisor == 0x4001) /* 1.5 */
-        divisor = 1;
-    return divisor;
-}
-
-static uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud)
-{
-    return ftdi_232bm_baud_base_to_divisor(baud, 48000000);
-}
-
-static uint32_t ftdi_2232h_baud_base_to_divisor(uint32_t baud, int base)
-{
-    static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
-    uint32_t divisor;
-    int divisor3;
-
-    /* hi-speed baud rate is 10-bit sampling instead of 16-bit */
-    divisor3 = DIV_ROUND_CLOSEST(8 * base, 10 * baud);
-
-    divisor = divisor3 >> 3;
-    divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
-    /* Deal with special cases for highest baud rates. */
-    if (divisor == 1) /* 1.0 */
-        divisor = 0;
-    else if (divisor == 0x4001) /* 1.5 */
-        divisor = 1;
-    /*
-	 * Set this bit to turn off a divide by 2.5 on baud rate generator
-	 * This enables baud rates up to 12Mbaud but cannot reach below 1200
-	 * baud with this bit set
-	 */
-    divisor |= 0x00020000;
-    return divisor;
-}
-
-static uint32_t ftdi_2232h_baud_to_divisor(uint32_t baud)
-{
-    return ftdi_2232h_baud_base_to_divisor(baud, 120000000);
-}
-
-int usbh_ftdi_reset(struct usbh_ftdi *ftdi_class)
-{
-    struct usb_setup_packet *setup;
-
-    if (!ftdi_class || !ftdi_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ftdi_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = SIO_RESET_REQUEST;
-    setup->wValue = 0;
-    setup->wIndex = ftdi_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ftdi_class->hport, setup, NULL);
-}
-
-static int usbh_ftdi_set_modem(struct usbh_ftdi *ftdi_class, uint16_t value)
-{
-    struct usb_setup_packet *setup;
-
-    if (!ftdi_class || !ftdi_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ftdi_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = SIO_SET_MODEM_CTRL_REQUEST;
-    setup->wValue = value;
-    setup->wIndex = ftdi_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ftdi_class->hport, setup, NULL);
-}
-
-static int usbh_ftdi_set_baudrate(struct usbh_ftdi *ftdi_class, uint32_t baudrate)
-{
-    struct usb_setup_packet *setup;
-    uint32_t div_value;
-    uint16_t value;
-    uint8_t baudrate_high;
-
-    if (!ftdi_class || !ftdi_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ftdi_class->hport->setup;
-
-    switch (ftdi_class->chip_type) {
-        case FT232B:
-        case FT2232C:
-        case FT232R:
-            if (baudrate > 3000000) {
-                return -USB_ERR_INVAL;
-            }
-            div_value = ftdi_232bm_baud_to_divisor(baudrate);
-            break;
-        default:
-            if ((baudrate <= 12000000) && (baudrate >= 1200)) {
-                div_value = ftdi_2232h_baud_to_divisor(baudrate);
-            } else {
-                return -USB_ERR_INVAL;
-            }
-            break;
-    }
-
-    value = div_value & 0xFFFF;
-    baudrate_high = (div_value >> 16) & 0xff;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = SIO_SET_BAUDRATE_REQUEST;
-    setup->wValue = value;
-    setup->wIndex = (baudrate_high << 8) | ftdi_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ftdi_class->hport, setup, NULL);
-}
-
-static int usbh_ftdi_set_data_format(struct usbh_ftdi *ftdi_class, uint8_t databits, uint8_t parity, uint8_t stopbits, uint8_t isbreak)
-{
-    /**
-     * D0-D7 databits  BITS_7=7, BITS_8=8
-     * D8-D10 parity  NONE=0, ODD=1, EVEN=2, MARK=3, SPACE=4
-     * D11-D12 		STOP_BIT_1=0, STOP_BIT_15=1, STOP_BIT_2=2
-     * D14  		BREAK_OFF=0, BREAK_ON=1
-     **/
-    struct usb_setup_packet *setup;
-    uint16_t value;
-
-    if (!ftdi_class || !ftdi_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ftdi_class->hport->setup;
-
-    value = ((isbreak & 0x01) << 14) | ((stopbits & 0x03) << 11) | ((parity & 0x0f) << 8) | (databits & 0x0f);
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = SIO_SET_DATA_REQUEST;
-    setup->wValue = value;
-    setup->wIndex = ftdi_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ftdi_class->hport, setup, NULL);
-}
-
-static int usbh_ftdi_set_latency_timer(struct usbh_ftdi *ftdi_class, uint16_t value)
-{
-    struct usb_setup_packet *setup;
-
-    if (!ftdi_class || !ftdi_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ftdi_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = SIO_SET_LATENCY_TIMER_REQUEST;
-    setup->wValue = value;
-    setup->wIndex = ftdi_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ftdi_class->hport, setup, NULL);
-}
-
-static int usbh_ftdi_set_flow_ctrl(struct usbh_ftdi *ftdi_class, uint16_t value)
-{
-    struct usb_setup_packet *setup;
-
-    if (!ftdi_class || !ftdi_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ftdi_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = SIO_SET_FLOW_CTRL_REQUEST;
-    setup->wValue = value;
-    setup->wIndex = ftdi_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(ftdi_class->hport, setup, NULL);
-}
-
-static int usbh_ftdi_read_modem_status(struct usbh_ftdi *ftdi_class)
-{
-    struct usb_setup_packet *setup;
-    int ret;
-
-    if (!ftdi_class || !ftdi_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = ftdi_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
-    setup->bRequest = SIO_POLL_MODEM_STATUS_REQUEST;
-    setup->wValue = 0x0000;
-    setup->wIndex = ftdi_class->intf;
-    setup->wLength = 2;
-
-    ret = usbh_control_transfer(ftdi_class->hport, setup, g_ftdi_buf);
-    if (ret < 0) {
-        return ret;
-    }
-    memcpy(ftdi_class->modem_status, g_ftdi_buf, 2);
-    return ret;
-}
-
-int usbh_ftdi_set_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding)
-{
-    memcpy((uint8_t *)&ftdi_class->line_coding, line_coding, sizeof(struct cdc_line_coding));
-
-    int ret = usbh_ftdi_set_baudrate(ftdi_class, line_coding->dwDTERate);
-    if (ret < 0) {
-        return ret;
-    }
-    return usbh_ftdi_set_data_format(ftdi_class, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat, 0);
-}
-
-int usbh_ftdi_get_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding)
-{
-    memcpy(line_coding, (uint8_t *)&ftdi_class->line_coding, sizeof(struct cdc_line_coding));
-    return 0;
-}
-
-int usbh_ftdi_set_line_state(struct usbh_ftdi *ftdi_class, bool dtr, bool rts)
-{
-    int ret;
-
-    if (dtr) {
-        usbh_ftdi_set_modem(ftdi_class, SIO_SET_DTR_HIGH);
-    } else {
-        usbh_ftdi_set_modem(ftdi_class, SIO_SET_DTR_LOW);
-    }
-
-    if (rts) {
-        ret = usbh_ftdi_set_modem(ftdi_class, SIO_SET_RTS_HIGH);
-    } else {
-        ret = usbh_ftdi_set_modem(ftdi_class, SIO_SET_RTS_LOW);
-    }
-
-    return ret;
-}
-
-static int usbh_ftdi_connect(struct usbh_hubport *hport, uint8_t intf)
-{
-    struct usb_endpoint_descriptor *ep_desc;
-    int ret = 0;
-    uint16_t version;
-
-    struct usbh_ftdi *ftdi_class = usbh_ftdi_class_alloc();
-    if (ftdi_class == NULL) {
-        USB_LOG_ERR("Fail to alloc ftdi_class\r\n");
-        return -USB_ERR_NOMEM;
-    }
-
-    ftdi_class->hport = hport;
-    ftdi_class->intf = intf;
-
-    hport->config.intf[intf].priv = ftdi_class;
-
-    version = hport->device_desc.bcdDevice;
-
-    switch (version) {
-        case 0x400:
-            ftdi_class->chip_type = FT232B;
-            break;
-        case 0x500:
-            ftdi_class->chip_type = FT2232C;
-            break;
-        case 0x600:
-            ftdi_class->chip_type = FT232R;
-            break;
-        case 0x700:
-            ftdi_class->chip_type = FT2232H;
-            break;
-        case 0x800:
-            ftdi_class->chip_type = FT4232H;
-            break;
-        case 0x900:
-            ftdi_class->chip_type = FT232H;
-            break;
-
-        default:
-            USB_LOG_ERR("Unknown FTDI chip version:%04x\r\n", version);
-            return -USB_ERR_NOTSUPP;
-    }
-
-    USB_LOG_INFO("FTDI chip name:%s\r\n", ftdi_chip_name[ftdi_class->chip_type]);
-
-    usbh_ftdi_reset(ftdi_class);
-    usbh_ftdi_set_flow_ctrl(ftdi_class, SIO_DISABLE_FLOW_CTRL);
-    usbh_ftdi_set_latency_timer(ftdi_class, 0x10);
-    usbh_ftdi_read_modem_status(ftdi_class);
-    USB_LOG_INFO("modem status:%02x:%02x\r\n", ftdi_class->modem_status[0], ftdi_class->modem_status[1]);
-
-    for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
-        ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
-
-        if (ep_desc->bEndpointAddress & 0x80) {
-            USBH_EP_INIT(ftdi_class->bulkin, ep_desc);
-        } else {
-            USBH_EP_INIT(ftdi_class->bulkout, ep_desc);
-        }
-    }
-
-    snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, ftdi_class->minor);
-
-    USB_LOG_INFO("Register FTDI Class:%s\r\n", hport->config.intf[intf].devname);
-
-#if 0
-    USB_LOG_INFO("Test ftdi rx and tx and rx for 5 times, baudrate is 115200\r\n");
-
-    struct cdc_line_coding linecoding;
-    uint8_t count = 5;
-
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_ftdi_set_line_coding(ftdi_class, &linecoding);
-    usbh_ftdi_set_line_state(ftdi_class, true, false);
-
-    memset(g_ftdi_buf, 'a', sizeof(g_ftdi_buf));
-    ret = usbh_ftdi_bulk_out_transfer(ftdi_class, g_ftdi_buf, sizeof(g_ftdi_buf), 0xfffffff);
-    USB_LOG_RAW("out ret:%d\r\n", ret);
-    while (count--) {
-        ret = usbh_ftdi_bulk_in_transfer(ftdi_class, g_ftdi_buf, sizeof(g_ftdi_buf), 0xfffffff);
-        USB_LOG_RAW("in ret:%d\r\n", ret);
-        if (ret > 0) {
-            for (uint32_t i = 0; i < ret; i++) {
-                USB_LOG_RAW("%02x ", g_ftdi_buf[i]);
-            }
-        }
-        USB_LOG_RAW("\r\n");
-    }
-#endif
-    usbh_ftdi_run(ftdi_class);
-    return ret;
-}
-
-static int usbh_ftdi_disconnect(struct usbh_hubport *hport, uint8_t intf)
-{
-    int ret = 0;
-
-    struct usbh_ftdi *ftdi_class = (struct usbh_ftdi *)hport->config.intf[intf].priv;
-
-    if (ftdi_class) {
-        if (ftdi_class->bulkin) {
-            usbh_kill_urb(&ftdi_class->bulkin_urb);
-        }
-
-        if (ftdi_class->bulkout) {
-            usbh_kill_urb(&ftdi_class->bulkout_urb);
-        }
-
-        if (hport->config.intf[intf].devname[0] != '\0') {
-            usb_osal_thread_schedule_other();
-            USB_LOG_INFO("Unregister FTDI Class:%s\r\n", hport->config.intf[intf].devname);
-            usbh_ftdi_stop(ftdi_class);
-        }
-
-        usbh_ftdi_class_free(ftdi_class);
-    }
-
-    return ret;
-}
-
-int usbh_ftdi_bulk_in_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &ftdi_class->bulkin_urb;
-
-    usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkin, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-int usbh_ftdi_bulk_out_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &ftdi_class->bulkout_urb;
-
-    usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkout, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-__WEAK void usbh_ftdi_run(struct usbh_ftdi *ftdi_class)
-{
-    (void)ftdi_class;
-}
-
-__WEAK void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class)
-{
-    (void)ftdi_class;
-}
-
-static const uint16_t ftdi_id_table[][2] = {
-    { 0x0403, 0x6001 },
-    { 0x0403, 0x6010 },
-    { 0, 0 },
-};
-
-const struct usbh_class_driver ftdi_class_driver = {
-    .driver_name = "ftdi",
-    .connect = usbh_ftdi_connect,
-    .disconnect = usbh_ftdi_disconnect
-};
-
-CLASS_INFO_DEFINE const struct usbh_class_info ftdi_class_info = {
-    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
-    .bInterfaceClass = 0xff,
-    .bInterfaceSubClass = 0x00,
-    .bInterfaceProtocol = 0x00,
-    .id_table = ftdi_id_table,
-    .class_driver = &ftdi_class_driver
-};

+ 0 - 96
class/vendor/serial/usbh_ftdi.h

@@ -1,96 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#ifndef USBH_FTDI_H
-#define USBH_FTDI_H
-
-#include "usb_cdc.h"
-
-/* Requests */
-#define SIO_RESET_REQUEST             0x00 /* Reset the port */
-#define SIO_SET_MODEM_CTRL_REQUEST    0x01 /* Set the modem control register */
-#define SIO_SET_FLOW_CTRL_REQUEST     0x02 /* Set flow control register */
-#define SIO_SET_BAUDRATE_REQUEST      0x03 /* Set baud rate */
-#define SIO_SET_DATA_REQUEST          0x04 /* Set the data characteristics of the port */
-#define SIO_POLL_MODEM_STATUS_REQUEST 0x05
-#define SIO_SET_EVENT_CHAR_REQUEST    0x06
-#define SIO_SET_ERROR_CHAR_REQUEST    0x07
-#define SIO_SET_LATENCY_TIMER_REQUEST 0x09
-#define SIO_GET_LATENCY_TIMER_REQUEST 0x0A
-#define SIO_SET_BITMODE_REQUEST       0x0B
-#define SIO_READ_PINS_REQUEST         0x0C
-#define SIO_READ_EEPROM_REQUEST       0x90
-#define SIO_WRITE_EEPROM_REQUEST      0x91
-#define SIO_ERASE_EEPROM_REQUEST      0x92
-
-#define SIO_DISABLE_FLOW_CTRL 0x0
-#define SIO_RTS_CTS_HS        (0x1 << 8)
-#define SIO_DTR_DSR_HS        (0x2 << 8)
-#define SIO_XON_XOFF_HS       (0x4 << 8)
-
-#define SIO_SET_DTR_MASK 0x1
-#define SIO_SET_DTR_HIGH (1 | (SIO_SET_DTR_MASK << 8))
-#define SIO_SET_DTR_LOW  (0 | (SIO_SET_DTR_MASK << 8))
-#define SIO_SET_RTS_MASK 0x2
-#define SIO_SET_RTS_HIGH (2 | (SIO_SET_RTS_MASK << 8))
-#define SIO_SET_RTS_LOW  (0 | (SIO_SET_RTS_MASK << 8))
-
-#define SIO_RTS_CTS_HS (0x1 << 8)
-
-enum ftdi_chip_type {
-    SIO,
-    FT232A,
-    FT232B,
-    FT2232C,
-    FT232R,
-    FT232H,
-    FT2232H,
-    FT4232H,
-    FT4232HA,
-    FT232HP,
-    FT233HP,
-    FT2232HP,
-    FT2233HP,
-    FT4232HP,
-    FT4233HP,
-    FTX,
-};
-
-struct usbh_ftdi {
-    struct usbh_hubport *hport;
-    struct usb_endpoint_descriptor *bulkin;  /* Bulk IN endpoint */
-    struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
-    struct usbh_urb bulkout_urb;
-    struct usbh_urb bulkin_urb;
-
-    struct cdc_line_coding line_coding;
-
-    uint8_t intf;
-    uint8_t minor;
-    uint8_t modem_status[2];
-    enum ftdi_chip_type chip_type;
-
-    void *user_data;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int usbh_ftdi_set_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding);
-int usbh_ftdi_get_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding);
-int usbh_ftdi_set_line_state(struct usbh_ftdi *ftdi_class, bool dtr, bool rts);
-
-int usbh_ftdi_bulk_in_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-int usbh_ftdi_bulk_out_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-
-void usbh_ftdi_run(struct usbh_ftdi *ftdi_class);
-void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* USBH_FTDI_H */

+ 0 - 449
class/vendor/serial/usbh_pl2303.c

@@ -1,449 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- * Copyright (c) 2024, Derek Konigsberg
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#include "usbh_core.h"
-#include "usbh_pl2303.h"
-
-#undef USB_DBG_TAG
-#define USB_DBG_TAG "usbh_pl2303"
-#include "usb_log.h"
-
-#define DEV_FORMAT "/dev/ttyUSB%d"
-
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_pl2303_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
-
-#define CONFIG_USBHOST_MAX_PL2303_CLASS 1
-
-#define UT_WRITE_VENDOR_DEVICE   (USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE)
-#define UT_READ_VENDOR_DEVICE    (USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE)
-
-static struct usbh_pl2303 g_pl2303_class[CONFIG_USBHOST_MAX_PL2303_CLASS];
-static uint32_t g_devinuse = 0;
-
-static struct usbh_pl2303 *usbh_pl2303_class_alloc(void)
-{
-    uint8_t devno;
-
-    for (devno = 0; devno < CONFIG_USBHOST_MAX_PL2303_CLASS; devno++) {
-        if ((g_devinuse & (1U << devno)) == 0) {
-            g_devinuse |= (1U << devno);
-            memset(&g_pl2303_class[devno], 0, sizeof(struct usbh_pl2303));
-            g_pl2303_class[devno].minor = devno;
-            return &g_pl2303_class[devno];
-        }
-    }
-    return NULL;
-}
-
-static void usbh_pl2303_class_free(struct usbh_pl2303 *pl2303_class)
-{
-    uint8_t devno = pl2303_class->minor;
-
-    if (devno < 32) {
-        g_devinuse &= ~(1U << devno);
-    }
-    memset(pl2303_class, 0, sizeof(struct usbh_pl2303));
-}
-
-static int usbh_pl2303_get_chiptype(struct usbh_pl2303 *pl2303_class)
-{
-    int ret = 0;
-
-    switch (pl2303_class->hport->device_desc.bcdDevice) {
-        case 0x0300:
-            pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HX;
-            /* or TA, that is HX with external crystal */
-            break;
-        case 0x0400:
-            pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HXD;
-            /* or EA, that is HXD with ESD protection */
-            /* or RA, that has internal voltage level converter that works only up to 1Mbaud (!) */
-            break;
-        case 0x0500:
-            pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HXD;
-            /* in fact it's TB, that is HXD with external crystal */
-            break;
-        default:
-            /* NOTE: I have no info about the bcdDevice for the base PL2303 (up to 1.2Mbaud,
-           only fixed rates) and for PL2303SA (8-pin chip, up to 115200 baud */
-            /* Determine the chip type.  This algorithm is taken from Linux. */
-            if (pl2303_class->hport->device_desc.bDeviceClass == 0x02) {
-                pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303;
-            } else if (pl2303_class->hport->device_desc.bMaxPacketSize0 == 0x40) {
-                pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HX;
-            } else {
-                pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303;
-            }
-            break;
-    }
-
-    /*
-     * The new chip revision PL2303HXN is only compatible with the new
-     * PLCOM_SET_REQUEST_PL2303HXN command. Issuing the old command
-     * PLCOM_SET_REQUEST to the new chip raises an error. Thus, PL2303HX
-     * and PL2303HXN can be distinguished by issuing an old-style request
-     * (on a status register) to the new chip and checking the error.
-     */
-    if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HX) {
-        struct usb_setup_packet *setup = pl2303_class->hport->setup;
-
-        setup->bmRequestType = UT_READ_VENDOR_DEVICE;
-        setup->bRequest = PL2303_SET_REQUEST;
-        setup->wValue = PL2303_STATUS_REG_PL2303HX;
-        setup->wIndex = 0;
-        setup->wLength = 1;
-
-        ret = usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
-        if (ret == -USB_ERR_STALL) {
-            pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HXN;
-            ret = 0;
-        } else if (ret < 0) {
-            USB_LOG_WRN("Error checking chip type: %d\r\n", ret);
-            return ret;
-        }
-    }
-
-    switch (pl2303_class->chiptype) {
-        case USBH_PL2303_TYPE_PL2303:
-            USB_LOG_INFO("chiptype = 2303\r\n");
-            break;
-        case USBH_PL2303_TYPE_PL2303HX:
-            USB_LOG_INFO("chiptype = 2303HX/TA\r\n");
-            break;
-        case USBH_PL2303_TYPE_PL2303HXN:
-            USB_LOG_INFO("chiptype = 2303HXN\r\n");
-            break;
-        case USBH_PL2303_TYPE_PL2303HXD:
-            USB_LOG_INFO("chiptype = 2303HXD/TB/RA/EA\r\n");
-            break;
-        default:
-            USB_LOG_INFO("chiptype = [%d]\r\n", pl2303_class->chiptype);
-            break;
-    }
-
-    return ret;
-}
-
-static int usbh_pl2303_do(struct usbh_pl2303 *pl2303_class,
-                          uint8_t req_type, uint8_t request, uint16_t value, uint16_t index,
-                          uint16_t length)
-{
-    struct usb_setup_packet *setup;
-
-    if (!pl2303_class || !pl2303_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = pl2303_class->hport->setup;
-
-    setup->bmRequestType = req_type;
-    setup->bRequest = request;
-    setup->wValue = value;
-    setup->wIndex = index;
-    setup->wLength = length;
-
-    return usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
-}
-
-int usbh_pl2303_set_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding)
-{
-    struct usb_setup_packet *setup;
-
-    if (!pl2303_class || !pl2303_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = pl2303_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
-    setup->wValue = 0;
-    setup->wIndex = pl2303_class->intf;
-    setup->wLength = 7;
-
-    memcpy(g_pl2303_buf, line_coding, sizeof(struct cdc_line_coding));
-
-    return usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
-}
-
-int usbh_pl2303_get_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding)
-{
-    struct usb_setup_packet *setup;
-    int ret;
-
-    if (!pl2303_class || !pl2303_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = pl2303_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
-    setup->wValue = 0;
-    setup->wIndex = pl2303_class->intf;
-    setup->wLength = 7;
-
-    ret = usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
-    if (ret < 0) {
-        return ret;
-    }
-    memcpy(line_coding, g_pl2303_buf, sizeof(struct cdc_line_coding));
-    return ret;
-}
-
-int usbh_pl2303_set_line_state(struct usbh_pl2303 *pl2303_class, bool dtr, bool rts)
-{
-    struct usb_setup_packet *setup;
-
-    if (!pl2303_class || !pl2303_class->hport) {
-        return -USB_ERR_INVAL;
-    }
-    setup = pl2303_class->hport->setup;
-
-    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
-    setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
-    setup->wValue = (dtr << 0) | (rts << 1);
-    setup->wIndex = pl2303_class->intf;
-    setup->wLength = 0;
-
-    return usbh_control_transfer(pl2303_class->hport, setup, NULL);
-}
-
-static int usbh_pl2303_connect(struct usbh_hubport *hport, uint8_t intf)
-{
-    struct usb_endpoint_descriptor *ep_desc;
-    int ret = 0;
-
-    struct usbh_pl2303 *pl2303_class = usbh_pl2303_class_alloc();
-    if (pl2303_class == NULL) {
-        USB_LOG_ERR("Fail to alloc pl2303_class\r\n");
-        return -USB_ERR_NOMEM;
-    }
-
-    pl2303_class->hport = hport;
-    pl2303_class->intf = intf;
-
-    hport->config.intf[intf].priv = pl2303_class;
-
-    do {
-        ret = usbh_pl2303_get_chiptype(pl2303_class);
-        if (ret < 0) {
-            break;
-        }
-
-        /* Startup reset sequence, if necessary for the chip type */
-        if (pl2303_class->chiptype != USBH_PL2303_TYPE_PL2303HXN) {
-            struct usb_setup_packet *setup = pl2303_class->hport->setup;
-
-            setup->bmRequestType = UT_WRITE_VENDOR_DEVICE;
-            setup->bRequest = PL2303_SET_REQUEST;
-            setup->wValue = 0;
-            setup->wIndex = pl2303_class->intf;
-            setup->wLength = 0;
-
-            ret = usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
-            if (ret < 0) {
-                USB_LOG_WRN("Initialization reset failed: %d\r\n", ret);
-                break;
-            }
-        }
-
-        if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303) {
-            /* HX variants seem to lock up after a clear stall request. */
-            /*
-             * The FreeBSD code sets the stall flags on the in and out pipes
-             * here. Have no idea exactly how to do this, or if it is necessary.
-             * May just leave this code unwritten until test hardware is available.
-             */
-        } else if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HX || pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HXD) {
-            /* Reset upstream data pipes */
-            ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 8, 0, 0);
-            if (ret < 0) {
-                USB_LOG_WRN("Could not reset upstream data pipes (8,0): %d\r\n", ret);
-                break;
-            }
-            ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 9, 0, 0);
-            if (ret < 0) {
-                USB_LOG_WRN("Could not reset upstream data pipes (9,0): %d\r\n", ret);
-                break;
-            }
-        } else if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HXN) {
-            /* Reset upstream data pipes */
-            ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST_PL2303HXN, 0x07, 0x03, 0);
-            if (ret < 0) {
-                USB_LOG_WRN("Could not reset upstream data pipes (7,3): %d\r\n", ret);
-                break;
-            }
-        }
-
-        /* Final device initialization, if necessary for the chip type */
-        if (pl2303_class->chiptype != USBH_PL2303_TYPE_PL2303HXN) {
-            if (usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x0404, 0, 0) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8383, 0, 1) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x0404, 1, 0) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8383, 0, 1) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 0, 1, 0) < 0 ||
-                usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 1, 0, 0) < 0) {
-                USB_LOG_WRN("Could not complete init sequence\r\n");
-                ret = -USB_ERR_INVAL;
-                break;
-            }
-
-            if (pl2303_class->chiptype != USBH_PL2303_TYPE_PL2303) {
-                ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 2, 0x44, 0);
-            } else {
-                ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 2, 0x24, 0);
-            }
-            if (ret < 0) {
-                USB_LOG_WRN("Could not complete final init request: %d\r\n", ret);
-                break;
-            }
-        }
-    } while (0);
-
-    if (ret < 0) {
-        USB_LOG_ERR("Failed to initialize PL2303 device: %d\r\n", ret);
-        return ret;
-    }
-
-    for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
-        ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
-        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
-            continue;
-        } else {
-            if (ep_desc->bEndpointAddress & 0x80) {
-                USBH_EP_INIT(pl2303_class->bulkin, ep_desc);
-            } else {
-                USBH_EP_INIT(pl2303_class->bulkout, ep_desc);
-            }
-        }
-    }
-
-    snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, pl2303_class->minor);
-
-    USB_LOG_INFO("Register PL2303 Class:%s\r\n", hport->config.intf[intf].devname);
-
-#if 0
-    USB_LOG_INFO("Test pl2303 rx and tx and rx for 5 times, baudrate is 115200\r\n");
-
-    struct cdc_line_coding linecoding;
-    uint8_t count = 5;
-
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_pl2303_set_line_coding(pl2303_class, &linecoding);
-    usbh_pl2303_set_line_state(pl2303_class, true, false);
-
-    memset(g_pl2303_buf, 'a', sizeof(g_pl2303_buf));
-    ret = usbh_pl2303_bulk_out_transfer(pl2303_class, g_pl2303_buf, sizeof(g_pl2303_buf), 0xfffffff);
-    USB_LOG_RAW("out ret:%d\r\n", ret);
-    while (count--) {
-        ret = usbh_pl2303_bulk_in_transfer(pl2303_class, g_pl2303_buf, sizeof(g_pl2303_buf), 0xfffffff);
-        USB_LOG_RAW("in ret:%d\r\n", ret);
-        if (ret > 0) {
-            for (uint32_t i = 0; i < ret; i++) {
-                USB_LOG_RAW("%02x ", g_pl2303_buf[i]);
-            }
-        }
-        USB_LOG_RAW("\r\n");
-    }
-#endif
-
-    usbh_pl2303_run(pl2303_class);
-    return ret;
-}
-
-static int usbh_pl2303_disconnect(struct usbh_hubport *hport, uint8_t intf)
-{
-    int ret = 0;
-
-    struct usbh_pl2303 *pl2303_class = (struct usbh_pl2303 *)hport->config.intf[intf].priv;
-
-    if (pl2303_class) {
-        if (pl2303_class->bulkin) {
-            usbh_kill_urb(&pl2303_class->bulkin_urb);
-        }
-
-        if (pl2303_class->bulkout) {
-            usbh_kill_urb(&pl2303_class->bulkout_urb);
-        }
-
-        if (hport->config.intf[intf].devname[0] != '\0') {
-            usb_osal_thread_schedule_other();
-            USB_LOG_INFO("Unregister PL2303 Class:%s\r\n", hport->config.intf[intf].devname);
-            usbh_pl2303_stop(pl2303_class);
-        }
-
-        usbh_pl2303_class_free(pl2303_class);
-    }
-
-    return ret;
-}
-
-int usbh_pl2303_bulk_in_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &pl2303_class->bulkin_urb;
-
-    usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkin, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-int usbh_pl2303_bulk_out_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
-{
-    int ret;
-    struct usbh_urb *urb = &pl2303_class->bulkout_urb;
-
-    usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkout, buffer, buflen, timeout, NULL, NULL);
-    ret = usbh_submit_urb(urb);
-    if (ret == 0) {
-        ret = urb->actual_length;
-    }
-    return ret;
-}
-
-__WEAK void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class)
-{
-    (void)pl2303_class;
-}
-
-__WEAK void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class)
-{
-    (void)pl2303_class;
-}
-
-static const uint16_t pl2303_id_table[][2] = {
-    { 0x067B, 0x2303 }, // PL2303 Serial (ATEN/IOGEAR UC232A)
-    { 0x067B, 0x23A3 }, // PL2303HXN Serial, type GC
-    { 0x067B, 0x23B3 }, // PL2303HXN Serial, type GB
-    { 0x067B, 0x23C3 }, // PL2303HXN Serial, type GT
-    { 0x067B, 0x23D3 }, // PL2303HXN Serial, type GL
-    { 0x067B, 0x23E3 }, // PL2303HXN Serial, type GE
-    { 0x067B, 0x23F3 }, // PL2303HXN Serial, type GS
-    { 0, 0 },
-};
-
-const struct usbh_class_driver pl2303_class_driver = {
-    .driver_name = "pl2303",
-    .connect = usbh_pl2303_connect,
-    .disconnect = usbh_pl2303_disconnect
-};
-
-CLASS_INFO_DEFINE const struct usbh_class_info pl2303_class_info = {
-    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
-    .bInterfaceClass = 0xff,
-    .bInterfaceSubClass = 0x00,
-    .bInterfaceProtocol = 0x00,
-    .id_table = pl2303_id_table,
-    .class_driver = &pl2303_class_driver
-};

+ 0 - 62
class/vendor/serial/usbh_pl2303.h

@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2024, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#ifndef USBH_PL2303_H
-#define USBH_PL2303_H
-
-#include "usb_cdc.h"
-
-#define PL2303_SET_REQUEST             0x01
-#define PL2303_SET_REQUEST_PL2303HXN   0x80
-#define PL2303_SET_CRTSCTS             0x41
-#define PL2303_SET_CRTSCTS_PL2303X     0x61
-#define PL2303_SET_CRTSCTS_PL2303HXN   0xFA
-#define PL2303_CLEAR_CRTSCTS_PL2303HXN 0xFF
-#define PL2303_CRTSCTS_REG_PL2303HXN   0x0A
-#define PL2303_STATUS_REG_PL2303HX     0x8080
-
-/* Different PL2303 IC types */
-#define USBH_PL2303_TYPE_UNKNOWN   0
-#define USBH_PL2303_TYPE_PL2303    1
-#define USBH_PL2303_TYPE_PL2303HX  2
-#define USBH_PL2303_TYPE_PL2303HXD 3
-#define USBH_PL2303_TYPE_PL2303HXN 4
-
-struct usbh_pl2303 {
-    struct usbh_hubport *hport;
-    struct usb_endpoint_descriptor *bulkin;  /* Bulk IN endpoint */
-    struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
-
-    struct usbh_urb bulkout_urb;
-    struct usbh_urb bulkin_urb;
-
-    struct cdc_line_coding linecoding;
-
-    uint8_t intf;
-    uint8_t minor;
-    uint8_t chiptype;
-
-    void *user_data;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int usbh_pl2303_set_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding);
-int usbh_pl2303_get_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding);
-int usbh_pl2303_set_line_state(struct usbh_pl2303 *pl2303_class, bool dtr, bool rts);
-
-int usbh_pl2303_bulk_in_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-int usbh_pl2303_bulk_out_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
-
-void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class);
-void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* USBH_PL2303_H */

+ 17 - 0
common/usb_util.h

@@ -209,6 +209,23 @@
         19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \
         9, 8, 7, 6, 5, 4, 3, 2, 1, 0
 
+/*
+ * Divide positive or negative dividend by positive or negative divisor
+ * and round to closest integer. Result is undefined for negative
+ * divisors if the dividend variable type is unsigned and for negative
+ * dividends if the divisor variable type is unsigned.
+ */
+#define DIV_ROUND_CLOSEST(x, divisor) (       \
+    {                                         \
+        typeof(x) __x = x;                    \
+        typeof(divisor) __d = divisor;        \
+        (((typeof(x))-1) > 0 ||               \
+         ((typeof(divisor))-1) > 0 ||         \
+         (((__x) > 0) == ((__d) > 0))) ?      \
+            (((__x) + ((__d) / 2)) / (__d)) : \
+            (((__x) - ((__d) / 2)) / (__d));  \
+    })
+
 #define USB_MEM_ALIGNX __attribute__((aligned(CONFIG_USB_ALIGN_SIZE)))
 
 #define USB_ALIGN_UP(size, align) (((size) + (align)-1) & ~((align)-1))

+ 104 - 47
demo/usb_host.c

@@ -4,20 +4,20 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 #include "usbh_core.h"
-#include "usbh_cdc_acm.h"
+#include "usbh_serial.h"
 #include "usbh_hid.h"
 #include "usbh_msc.h"
 #include "usbh_video.h"
 #include "usbh_audio.h"
 
-#ifndef CONFIG_TEST_USBH_CDC_ACM
-#define CONFIG_TEST_USBH_CDC_ACM 1
+#ifndef CONFIG_TEST_USBH_SERIAL
+#define CONFIG_TEST_USBH_SERIAL 1
 #endif
 #ifndef TEST_USBH_CDC_SPEED
 #define TEST_USBH_CDC_SPEED 0
 #endif
 #ifndef CONFIG_TEST_USBH_HID
-#define CONFIG_TEST_USBH_HID 1
+#define CONFIG_TEST_USBH_HID 0
 #endif
 #ifndef CONFIG_TEST_USBH_MSC
 #define CONFIG_TEST_USBH_MSC 1
@@ -39,44 +39,62 @@
 #error we have move those class implements into platform/none/usbh_lwip.c, and you should call tcpip_init(NULL, NULL) in your app
 #endif
 
-#if CONFIG_TEST_USBH_CDC_ACM
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t cdc_buffer[4096];
+#if CONFIG_TEST_USBH_SERIAL
+#define SERIAL_TEST_LEN (2 * 1024)
+
+volatile uint32_t serial_tx_bytes = 0;
+volatile uint32_t serial_rx_bytes = 0;
+volatile bool serial_is_opened = false;
+
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t serial_tx_buffer[64];
+uint8_t serial_rx_data[SERIAL_TEST_LEN];
 
 #if TEST_USBH_CDC_SPEED
 #define TEST_LEN   (16 * 1024)
 #define TEST_COUNT (10240)
 
-USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t cdc_speed_buffer[TEST_LEN];
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t serial_speed_buffer[TEST_LEN];
 #endif
 
-void usbh_cdc_acm_callback(void *arg, int nbytes)
+static void usbh_serial_thread(CONFIG_USB_OSAL_THREAD_SET_ARGV)
 {
-    //struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)arg;
-
-    if (nbytes > 0) {
-        for (size_t i = 0; i < nbytes; i++) {
-            USB_LOG_RAW("0x%02x ", cdc_buffer[i]);
+    int ret;
+    struct usbh_serial *serial;
+
+    serial = usbh_serial_open("/dev/ttyACM0", USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
+    if (serial == NULL) {
+        serial = usbh_serial_open("/dev/ttyUSB0", USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
+        if (serial == NULL) {
+            USB_LOG_RAW("no serial device found\r\n");
+            goto delete;
         }
-        USB_LOG_RAW("nbytes:%d\r\n", (unsigned int)nbytes);
     }
-}
 
-static void usbh_cdc_acm_thread(CONFIG_USB_OSAL_THREAD_SET_ARGV)
-{
-    int ret;
-    struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)CONFIG_USB_OSAL_THREAD_GET_ARGV;
+    struct usbh_serial_termios termios;
+
+    memset(&termios, 0, sizeof(termios));
+    termios.baudrate = 115200;
+    termios.stopbits = 0;
+    termios.parity = 0;
+    termios.databits = 8;
+    termios.rtscts = false;
+    termios.rx_timeout = 0;
+    ret = usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);
+    if (ret < 0) {
+        USB_LOG_RAW("set serial attr error, ret:%d\r\n", ret);
+        goto delete_with_close;
+    }
 
     /* test with only one buffer, if you have more cdc acm class, modify by yourself */
 #if TEST_USBH_CDC_SPEED
     const uint32_t test_len[] = { 512, 1 * 1024, 2 * 1024, 4 * 1024, 8 * 1024, 16 * 1024 };
 
-    memset(cdc_speed_buffer, 0xAA, TEST_LEN);
+    memset(serial_speed_buffer, 0xAA, TEST_LEN);
 
     for (uint8_t j = 0; j < 6; j++) {
         uint32_t start_time = (uint32_t)xTaskGetTickCount();
         for (uint32_t i = 0; i < TEST_COUNT; i++) {
-            usbh_bulk_urb_fill(&cdc_acm_class->bulkout_urb, cdc_acm_class->hport, cdc_acm_class->bulkout, cdc_speed_buffer, test_len[j], 0XFFFFFFF, NULL, NULL);
-            ret = usbh_submit_urb(&cdc_acm_class->bulkout_urb);
+            usbh_serial_write(serialize, serial_speed_buffer, test_len[j]);
             if (ret < 0) {
                 USB_LOG_RAW("bulk out error,ret:%d\r\n", ret);
                 while (1) {
@@ -88,27 +106,62 @@ static void usbh_cdc_acm_thread(CONFIG_USB_OSAL_THREAD_SET_ARGV)
         USB_LOG_RAW("per packet len:%d, out speed:%f MB/S\r\n", (unsigned int)test_len[j], (test_len[j] * TEST_COUNT / 1024 / 1024) * 1000 / ((float)time_ms));
     }
 #endif
-    memset(cdc_buffer, 0x55, 4096);
+    memset(serial_tx_buffer, 0xA5, sizeof(serial_tx_buffer));
+    USB_LOG_RAW("start serial loopback test, len: %d\r\n", SERIAL_TEST_LEN);
+
+    serial_tx_bytes = 0;
+    while (1) {
+        /* for common, we use timeout with 0xffffffff, this is just a test */
+        ret = usbh_serial_write(serial, serial_tx_buffer, sizeof(serial_tx_buffer));
+        if (ret < 0) {
+            USB_LOG_RAW("serial write error, ret:%d\r\n", ret);
+            goto delete_with_close;
+        } else {
+            serial_tx_bytes += ret;
+            usb_osal_msleep(10); // 11.52 Byte/ms at 115200bps --> 64Byte/5.5ms
 
-    /* for common, we use timeout with 0xffffffff, this is just a test */
-    usbh_bulk_urb_fill(&cdc_acm_class->bulkout_urb, cdc_acm_class->hport, cdc_acm_class->bulkout, cdc_buffer, sizeof(cdc_buffer), 3000, NULL, NULL);
-    ret = usbh_submit_urb(&cdc_acm_class->bulkout_urb);
-    if (ret < 0) {
-        USB_LOG_RAW("bulk out error,ret:%d\r\n", ret);
-        goto delete;
-    } else {
-        USB_LOG_RAW("send over:%d\r\n", (unsigned int)cdc_acm_class->bulkout_urb.actual_length);
+            if (serial_tx_bytes == SERIAL_TEST_LEN) {
+                USB_LOG_RAW("send over\r\n");
+                break;
+            }
+        }
     }
 
-    /* we can change cdc_acm_class->bulkin->wMaxPacketSize with 4096 for testing zlp, default is ep mps  */
-    usbh_bulk_urb_fill(&cdc_acm_class->bulkin_urb, cdc_acm_class->hport, cdc_acm_class->bulkin, cdc_buffer, cdc_acm_class->bulkin->wMaxPacketSize, 0xffffffff, usbh_cdc_acm_callback, cdc_acm_class);
-    ret = usbh_submit_urb(&cdc_acm_class->bulkin_urb);
-    if (ret < 0) {
-        USB_LOG_RAW("bulk in error,ret:%d\r\n", ret);
-        goto delete;
-    } else {
+    volatile uint32_t wait_timeout = 0;
+    serial_rx_bytes = 0;
+    while (1) {
+        ret = usbh_serial_read(serial, &serial_rx_data[serial_rx_bytes], SERIAL_TEST_LEN);
+        if (ret < 0) {
+            USB_LOG_RAW("serial read error, ret:%d\r\n", ret);
+            goto delete_with_close;
+        } else {
+            serial_rx_bytes += ret;
+
+            if (serial_rx_bytes == SERIAL_TEST_LEN) {
+                USB_LOG_RAW("receive over\r\n");
+                for (uint32_t i = 0; i < SERIAL_TEST_LEN; i++) {
+                    if (serial_rx_data[i] != 0xa5) {
+                        USB_LOG_RAW("serial loopback data error at index %d, data: 0x%02x\r\n", (unsigned int)i, serial_rx_data[i]);
+                        goto delete_with_close;
+                    }
+                }
+                USB_LOG_RAW("serial loopback test success\r\n");
+                break;
+            }
+        }
+        wait_timeout++;
+
+        if (wait_timeout > 500) { // 5s
+            USB_LOG_RAW("serial read timeout\r\n");
+            goto delete_with_close;
+        }
+
+        usb_osal_msleep(10);
     }
+
     // clang-format off
+delete_with_close:
+    usbh_serial_close(serial);
 delete:
     usb_osal_thread_delete(NULL);
     // clang-format on
@@ -140,7 +193,6 @@ static void usbh_hid_thread(CONFIG_USB_OSAL_THREAD_SET_ARGV)
 {
     int ret;
     struct usbh_hid *hid_class = (struct usbh_hid *)CONFIG_USB_OSAL_THREAD_GET_ARGV;
-    ;
 
     /* test with only one buffer, if you have more hid class, modify by yourself */
 
@@ -164,8 +216,8 @@ delete:
 
 #if TEST_USBH_MSC_FATFS_SPEED
 #define WRITE_SIZE_MB (128UL)
-#define WRITE_SIZE (1024UL * 1024UL * WRITE_SIZE_MB)
-#define BUF_SIZE (1024UL * 128UL)
+#define WRITE_SIZE    (1024UL * 1024UL * WRITE_SIZE_MB)
+#define BUF_SIZE      (1024UL * 128UL)
 USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_write_buffer[BUF_SIZE];
 #else
 USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_write_buffer[25 * 100];
@@ -234,7 +286,7 @@ int usb_msc_fatfs_test()
         uint32_t write_size = WRITE_SIZE;
         uint32_t start_time = (uint32_t)xTaskGetTickCount();
         while (write_size > 0) {
-            res_sd = f_write(&fnew, read_write_buffer, BUF_SIZE, (UINT*)&fnum);
+            res_sd = f_write(&fnew, read_write_buffer, BUF_SIZE, (UINT *)&fnum);
             if (res_sd != FR_OK) {
                 USB_LOG_RAW("Write file failed, cause: %s\n", res_sd);
                 goto unmount;
@@ -260,7 +312,7 @@ int usb_msc_fatfs_test()
         uint32_t write_size = WRITE_SIZE;
         uint32_t start_time = (uint32_t)xTaskGetTickCount();
         while (write_size > 0) {
-            res_sd = f_read(&fnew, read_write_buffer, BUF_SIZE, (UINT*)&fnum);
+            res_sd = f_read(&fnew, read_write_buffer, BUF_SIZE, (UINT *)&fnum);
             if (res_sd != FR_OK) {
                 USB_LOG_RAW("Read file failed, cause: %s\n", res_sd);
                 goto unmount;
@@ -326,14 +378,19 @@ delete:
 }
 #endif
 
-#if CONFIG_TEST_USBH_CDC_ACM
-void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class)
+#if CONFIG_TEST_USBH_SERIAL
+void usbh_serial_run(struct usbh_serial *serial)
 {
-    usb_osal_thread_create("usbh_cdc", 2048, CONFIG_USBHOST_PSC_PRIO + 1, usbh_cdc_acm_thread, cdc_acm_class);
+    if (serial_is_opened) {
+        return;
+    }
+    serial_is_opened = true;
+    usb_osal_thread_create("usbh_serial", 2048, CONFIG_USBHOST_PSC_PRIO + 1, usbh_serial_thread, serial);
 }
 
-void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class)
+void usbh_serial_stop(struct usbh_serial *serial)
 {
+    serial_is_opened = false;
 }
 #endif
 

+ 103 - 2
docs/source/api/api_host.rst

@@ -154,14 +154,115 @@ lsusb
 
     int lsusb(int argc, char **argv);
 
-CDC ACM
+SERIAL
 -----------------
 
+usbh_serial_open
+""""""""""""""""""""""""""""""""""""
+
+``usbh_serial_open`` 根据路径打开一个串口设备。
+
+.. code-block:: C
+
+    struct usbh_serial *usbh_serial_open(const char *devname, uint32_t open_flags);
+
+- **devname**  串口路径
+- **open_flags**  打开标志,参考 `USBH_SERIAL_OFLAG_*` 定义
+- **return**  serial 结构体句柄
+
+usbh_serial_close
+""""""""""""""""""""""""""""""""""""
+
+``usbh_serial_close`` 关闭串口设备。
+
+.. code-block:: C
+
+    void usbh_serial_close(struct usbh_serial *serial);
+
+- **serial**  serial 结构体句柄
+
+usbh_serial_control
+""""""""""""""""""""""""""""""""""""
+
+``usbh_serial_control`` 对串口进行配置。
+
+.. code-block:: C
+
+    int usbh_serial_control(struct usbh_serial *serial, int cmd, void *arg);
+
+- **serial**  serial 结构体句柄
+- **cmd**  控制命令,参考 `USBH_SERIAL_CMD_*` 定义
+- **arg**  控制参数指针
+- **return**  0 表示正常其他表示错误
+
+usbh_serial_write
+""""""""""""""""""""""""""""""""""""
+
+``usbh_serial_write`` 向串口写数据。 **串口设备如果是 USB2TTL 类型,必须按照波特率发送,否则会丢包**
+
+.. code-block:: C
+
+    int usbh_serial_write(struct usbh_serial *serial, const void *buffer, uint32_t buflen);
+
+- **serial**  serial 结构体句柄
+- **buffer**  数据缓冲区指针
+- **buflen**  要写入的数据长度,如果是 USB2TTL 设备,一次最高 wMaxPacketSize
+- **return**  实际写入的数据长度或者错误码
+
+.. note:: 有无设置波特率都可以使用该 API,当未设置波特率时,长度无限制,如果设置了波特率则为 wMaxPacketSize。
+
+usbh_serial_read
+""""""""""""""""""""""""""""""""""""
+
+``usbh_serial_read`` 从串口读数据。 **如果没有设置波特率,不允许使用该 API**。
+
+.. code-block:: C
+
+    int usbh_serial_read(struct usbh_serial *serial, void *buffer, uint32_t buflen);
+
+- **serial**  serial 结构体句柄
+- **buffer**  数据缓冲区指针
+- **buflen**  要读取的最大数据长度
+- **return**  实际读取的数据长度或者错误码
+
+usbh_serial_cdc_write_async
+""""""""""""""""""""""""""""""""""""
+
+``usbh_serial_cdc_write_async`` 异步从串口读数据。 **如果设置了波特率,不允许使用该 API**。
+
+.. code-block:: C
+
+    int usbh_serial_cdc_write_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg);
+
+- **serial**  serial 结构体句柄
+- **buffer**  数据缓冲区指针
+- **buflen**  要发送的数据长度
+- **complete**  读数据完成回调函数
+- **arg**  回调函数参数
+- **return**  0 表示正常其他表示错误
+
+usbh_serial_cdc_read_async
+""""""""""""""""""""""""""""""""""""
+
+``usbh_serial_cdc_read_async`` 异步从串口读数据。 **如果设置了波特率,不允许使用该 API**。
+
+.. code-block:: C
+
+    int usbh_serial_cdc_read_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg);
+
+- **serial**  serial 结构体句柄
+- **buffer**  数据缓冲区指针
+- **buflen**  要读取的最大数据长度,一次最高 16K。并且需要是 wMaxPacketSize 的整数倍
+- **complete**  读数据完成回调函数
+- **arg**  回调函数参数
+- **return**  0 表示正常其他表示错误
+
+
 HID
 -----------------
 
 MSC
 -----------------
 
-RNDIS
+NETWORK
 -----------------

+ 93 - 1
docs/source/demo/usbh_serial.rst

@@ -1,4 +1,96 @@
 usbh_serial
 ===============
 
-当前仅支持 rt-thread device 框架,包括 cdc acm, ftdi, cp210x, ch34x, pl2303, 具体使用方式参考 rt-thread device api 即可。
+Serial 框架当前支持 cdc acm, ftdi, cp210x, ch34x, pl2303,gsm 驱动。当前支持两种使用方式,
+一种是使用源生 CherryUSB usbhost serial API 进行操作,另一种是基于平台封装的 API 操作,比如 rt-thread device API。,nuttx posix API。
+
+下面演示的是使用 CherryUSB usbhost serial API 进行串口回环测试,并且使用阻塞发送,异步读取的方式:
+
+.. code-block:: C
+
+    struct usbh_serial *serial;
+
+    serial = usbh_serial_open("/dev/ttyACM0", USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
+    if (serial == NULL) {
+        serial = usbh_serial_open("/dev/ttyUSB0", USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
+        if (serial == NULL) {
+            USB_LOG_RAW("no serial device found\r\n");
+            goto delete;
+        }
+    }
+
+    struct usbh_serial_termios termios;
+
+    memset(&termios, 0, sizeof(termios));
+    termios.baudrate = 115200;
+    termios.stopbits = 0;
+    termios.parity = 0;
+    termios.databits = 8;
+    termios.rtscts = false;
+    termios.rx_timeout = 0;
+    ret = usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);
+    if (ret < 0) {
+        USB_LOG_RAW("set serial attr error, ret:%d\r\n", ret);
+        goto delete_with_close;
+    }
+
+    serial_tx_bytes = 0;
+    while (1) {
+        /* for common, we use timeout with 0xffffffff, this is just a test */
+        ret = usbh_serial_write(serial, serial_tx_buffer, sizeof(serial_tx_buffer));
+        if (ret < 0) {
+            USB_LOG_RAW("serial write error, ret:%d\r\n", ret);
+            goto delete_with_close;
+        } else {
+            serial_tx_bytes += ret;
+            usb_osal_msleep(10); // 11.52 Byte/ms at 115200bps --> 64Byte/5.5ms
+
+            if (serial_tx_bytes == SERIAL_TEST_LEN) {
+                USB_LOG_RAW("send over\r\n");
+                break;
+            }
+        }
+    }
+
+    volatile uint32_t wait_timeout = 0;
+    serial_rx_bytes = 0;
+    while (1) {
+        ret = usbh_serial_read(serial, &serial_rx_data[serial_rx_bytes], SERIAL_TEST_LEN);
+        if (ret < 0) {
+            USB_LOG_RAW("serial read error, ret:%d\r\n", ret);
+            goto delete_with_close;
+        } else {
+            serial_rx_bytes += ret;
+
+            if (serial_rx_bytes == SERIAL_TEST_LEN) {
+                USB_LOG_RAW("receive over\r\n");
+                for (uint32_t i = 0; i < SERIAL_TEST_LEN; i++) {
+                    if (serial_rx_data[i] != 0xa5) {
+                        USB_LOG_RAW("serial loopback data error at index %d, data: 0x%02x\r\n", (unsigned int)i, serial_rx_data[i]);
+                        goto delete_with_close;
+                    }
+                }
+                USB_LOG_RAW("serial loopback test success\r\n");
+                break;
+            }
+        }
+        wait_timeout++;
+
+        if (wait_timeout > 500) { // 5s
+            USB_LOG_RAW("serial read timeout\r\n");
+            goto delete_with_close;
+        }
+
+        usb_osal_msleep(10);
+    }
+
+    usbh_serial_close(serial);
+
+
+用户需要考虑以下三种场景:
+
+- USB2TTL 设备 + 启用了波特率,这种情况下需要使用 `usbh_serial_write` 和 `usbh_serial_read` 进行收发数据, **并且需要根据波特率控制发送频率,防止对端丢包**;
+
+- 纯 USB 设备 + 未启动波特率,这种情况下可以使用 `usbh_serial_cdc_write_async` 和 `usbh_serial_cdc_read_async` 进行异步收发数据,阻塞则用 `usbh_serial_write` 并且不需要控制发送频率。不可以使用 `usbh_serial_read`。
+
+- 纯 USB 设备 + 启动波特率,同 1,但是速率打折扣。不可以使用 `usbh_serial_cdc_write_async` 和 `usbh_serial_cdc_read_async`。如果是 GSM 设备需要使用第一种。

+ 6 - 2
osal/idf/usb_config.h

@@ -28,7 +28,7 @@
 
 // #define CONFIG_USB_DCACHE_ENABLE
 
-/* attribute data into no cache ram 
+/* attribute data into no cache ram
 * DRAM_DMA_ALIGNED_ATTR was introduced in IDF 5.3. If not defined, it falls back to DMA_ATTR
 */
 #ifndef DRAM_DMA_ALIGNED_ATTR
@@ -158,7 +158,7 @@
 #define CONFIG_USBHOST_MAX_INTF_ALTSETTINGS 2
 #define CONFIG_USBHOST_MAX_ENDPOINTS        4
 
-#define CONFIG_USBHOST_MAX_CDC_ACM_CLASS 4
+#define CONFIG_USBHOST_MAX_SERIAL_CLASS  4
 #define CONFIG_USBHOST_MAX_HID_CLASS     4
 #define CONFIG_USBHOST_MAX_MSC_CLASS     2
 #define CONFIG_USBHOST_MAX_AUDIO_CLASS   1
@@ -193,6 +193,10 @@
 #define CONFIG_USBHOST_MSC_TIMEOUT 5000
 #endif
 
+#ifndef CONFIG_USBHOST_SERIAL_RX_SIZE
+#define CONFIG_USBHOST_SERIAL_RX_SIZE 2048
+#endif
+
 /* This parameter affects usb performance, and depends on (TCP_WND)tcp eceive windows size,
  * you can change to 2K ~ 16K and must be larger than TCP RX windows size in order to avoid being overflow.
  */

+ 6 - 0
platform/rtthread/usb_msh.c

@@ -44,4 +44,10 @@ int usbh_deinit(int argc, char **argv)
 MSH_CMD_EXPORT(usbh_init, init usb host);
 MSH_CMD_EXPORT(usbh_deinit, deinit usb host);
 MSH_CMD_EXPORT(lsusb, ls usb devices);
+
+#ifdef CONFIG_USBHOST_SERIAL
+#include "usbh_serial.h"
+MSH_CMD_EXPORT(usbh_serial, usbh_serial test);
+#endif
+
 #endif

+ 209 - 0
platform/rtthread/usbh_rtserial.c

@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2025, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include "usbh_core.h"
+#include "usbh_serial.h"
+
+static rt_err_t rt_usbh_serial_open(struct rt_device *dev, rt_uint16_t oflag)
+{
+    struct usbh_serial *serial;
+
+    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);
+
+    serial = (struct usbh_serial *)dev->user_data;
+
+    serial = usbh_serial_open(serial->hport->config.intf[serial->intf].devname, USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
+    if (serial == RT_NULL) {
+        USB_LOG_ERR("serial open failed\n");
+        return -RT_ERROR;
+    }
+
+    struct usbh_serial_termios termios;
+
+    memset(&termios, 0, sizeof(termios));
+    termios.baudrate = 115200;
+    termios.stopbits = 0;
+    termios.parity = 0;
+    termios.databits = 8;
+    termios.rtscts = false;
+    termios.rx_timeout = 0;
+
+    usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);
+
+    return RT_EOK;
+}
+
+static rt_err_t rt_usbh_serial_close(struct rt_device *dev)
+{
+    struct usbh_serial *serial;
+
+    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);
+
+    serial = (struct usbh_serial *)dev->user_data;
+
+    usbh_serial_close(serial);
+
+    return RT_EOK;
+}
+
+static rt_ssize_t rt_usbh_serial_read(struct rt_device *dev,
+                                      rt_off_t pos,
+                                      void *buffer,
+                                      rt_size_t size)
+{
+    struct usbh_serial *serial;
+
+    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);
+
+    serial = (struct usbh_serial *)dev->user_data;
+
+    return usbh_serial_read(serial, buffer, size);
+}
+
+static rt_ssize_t rt_usbh_serial_write(struct rt_device *dev,
+                                       rt_off_t pos,
+                                       const void *buffer,
+                                       rt_size_t size)
+{
+    struct usbh_serial *serial;
+    int ret = 0;
+    rt_uint8_t *align_buf;
+
+    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);
+
+    serial = (struct usbh_serial *)dev->user_data;
+
+    align_buf = (rt_uint8_t *)buffer;
+
+    if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) {
+        align_buf = rt_malloc_align(USB_ALIGN_UP(size, CONFIG_USB_ALIGN_SIZE), CONFIG_USB_ALIGN_SIZE);
+        if (!align_buf) {
+            USB_LOG_ERR("serial get align buf failed\n");
+            return 0;
+        }
+
+        usb_memcpy(align_buf, buffer, size);
+    }
+
+    ret = usbh_serial_write(serial, align_buf, size);
+
+    if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) {
+        rt_free_align(align_buf);
+    }
+
+    return ret;
+}
+
+static rt_err_t rt_usbh_serial_control(struct rt_device *dev,
+                                       int cmd,
+                                       void *args)
+{
+    struct usbh_serial *serial;
+    struct serial_configure *config;
+    int ret = -RT_EINVAL;
+
+    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);
+
+    serial = (struct usbh_serial *)dev->user_data;
+
+    switch (cmd) {
+        case RT_DEVICE_CTRL_CONFIG: {
+            config = (struct serial_configure *)args;
+
+            struct usbh_serial_termios termios;
+
+            memset(&termios, 0, sizeof(termios));
+            termios.baudrate = config->baud_rate;
+            termios.stopbits = 0;
+            termios.parity = config->parity;
+            termios.databits = config->data_bits;
+            termios.rtscts = false;
+            termios.rx_timeout = 0;
+
+            usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);
+        } break;
+
+        default:
+            break;
+    }
+
+    return ret;
+}
+
+#ifdef RT_USING_DEVICE_OPS
+const static struct rt_device_ops usbh_serial_ops = {
+    NULL,
+    rt_usbh_serial_open,
+    rt_usbh_serial_close,
+    rt_usbh_serial_read,
+    rt_usbh_serial_write,
+    rt_usbh_serial_control
+};
+#endif
+
+rt_err_t usbh_serial_register(struct usbh_serial *serial)
+{
+    rt_err_t ret;
+    struct rt_device *device;
+    RT_ASSERT(serial != RT_NULL);
+
+    device = rt_malloc(sizeof(struct rt_device));
+    if (device == RT_NULL) {
+        USB_LOG_ERR("serial device malloc failed\n");
+        return -RT_ENOMEM;
+    }
+    memset(device, 0, sizeof(struct rt_device));
+
+    device->type = RT_Device_Class_Char;
+    device->rx_indicate = RT_NULL;
+    device->tx_complete = RT_NULL;
+
+#ifdef RT_USING_DEVICE_OPS
+    device->ops = &usbh_serial_ops;
+#else
+    device->init = NULL;
+    device->open = rt_usbh_serial_open;
+    device->close = rt_usbh_serial_close;
+    device->read = rt_usbh_serial_read;
+    device->write = rt_usbh_serial_write;
+    device->control = rt_usbh_serial_control;
+#endif
+    device->user_data = serial;
+    serial->user_data = device;
+
+    /* register a character device */
+    ret = rt_device_register(device, serial->hport->config.intf[serial->intf].devname, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_REMOVABLE);
+
+#ifdef RT_USING_POSIX_DEVIO
+    /* set fops */
+    device->fops = &usbh_serial_fops;
+#endif
+    return ret;
+}
+
+void usbh_serial_unregister(struct usbh_serial *serial)
+{
+    struct rt_device *device;
+
+    RT_ASSERT(serial != NULL && serial->user_data != NULL);
+
+    device = (struct rt_device *)serial->user_data;
+
+    rt_device_unregister(device);
+    rt_free(device);
+}
+
+void usbh_serial_run(struct usbh_serial *serial)
+{
+    usbh_serial_register(serial);
+}
+
+void usbh_serial_stop(struct usbh_serial *serial)
+{
+    usbh_serial_unregister(serial);
+}

+ 0 - 899
platform/rtthread/usbh_serial.c

@@ -1,899 +0,0 @@
-/*
- * Copyright (c) 2025, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#include <rtthread.h>
-#include <rtdevice.h>
-
-#include "usbh_core.h"
-#include "usbh_cdc_acm.h"
-#include "usbh_ftdi.h"
-#include "usbh_cp210x.h"
-#include "usbh_ch34x.h"
-#include "usbh_pl2303.h"
-
-#define DEV_FORMAT_VENDOR  "ttyUSB%d"
-#define DEV_FORMAT_CDC_ACM "ttyACM%d"
-
-#define USBH_RX_MAX_SIZE 2048
-
-#ifndef CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS
-#define CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS (4)
-#endif
-
-#ifndef CONFIG_USBHOST_SERIAL_RX_BUFSIZE
-#define CONFIG_USBHOST_SERIAL_RX_BUFSIZE (USBH_RX_MAX_SIZE * 2)
-#endif
-
-enum usbh_serial_type {
-    USBH_SERIAL_TYPE_CDC_ACM = 0,
-    USBH_SERIAL_TYPE_FTDI,
-    USBH_SERIAL_TYPE_CP210X,
-    USBH_SERIAL_TYPE_CH34X,
-    USBH_SERIAL_TYPE_PL2303,
-};
-
-struct usbh_serial {
-    struct rt_device parent;
-    enum usbh_serial_type type;
-    uint8_t minor;
-    char name[CONFIG_USBHOST_DEV_NAMELEN];
-    struct rt_ringbuffer rx_rb;
-    rt_uint8_t rx_rb_buffer[CONFIG_USBHOST_SERIAL_RX_BUFSIZE];
-};
-
-static uint32_t g_devinuse_vendor = 0;
-static uint32_t g_devinuse_cdc_acm = 0;
-
-static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_usbh_serial_vendor_rx_buf[CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS][USB_ALIGN_UP(USBH_RX_MAX_SIZE, CONFIG_USB_ALIGN_SIZE)];
-static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_usbh_serial_cdc_acm_rx_buf[CONFIG_USBHOST_MAX_CDC_ACM_CLASS][USB_ALIGN_UP(USBH_RX_MAX_SIZE, CONFIG_USB_ALIGN_SIZE)];
-
-static struct usbh_serial *usbh_serial_alloc(uint8_t type)
-{
-    uint8_t devno;
-    struct usbh_serial *serial;
-
-    for (devno = 0; devno < CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS; devno++) {
-        if ((g_devinuse_vendor & (1U << devno)) == 0) {
-            g_devinuse_vendor |= (1U << devno);
-
-            serial = rt_malloc(sizeof(struct usbh_serial));
-            memset(serial, 0, sizeof(struct usbh_serial));
-            serial->type = type;
-            serial->minor = devno;
-            snprintf(serial->name, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_VENDOR, serial->minor);
-            return serial;
-        }
-    }
-    return NULL;
-}
-
-static void usbh_serial_free(struct usbh_serial *serial)
-{
-    uint8_t devno = serial->minor;
-
-    if (devno < 32) {
-        g_devinuse_vendor &= ~(1U << devno);
-    }
-    memset(serial, 0, sizeof(struct usbh_serial));
-    rt_free(serial);
-}
-
-static struct usbh_serial *usbh_serial_cdc_acm_alloc(uint8_t type)
-{
-    uint8_t devno;
-    struct usbh_serial *serial;
-
-    for (devno = 0; devno < CONFIG_USBHOST_MAX_CDC_ACM_CLASS; devno++) {
-        if ((g_devinuse_cdc_acm & (1U << devno)) == 0) {
-            g_devinuse_cdc_acm |= (1U << devno);
-
-            serial = rt_malloc(sizeof(struct usbh_serial));
-            memset(serial, 0, sizeof(struct usbh_serial));
-            serial->type = type;
-            serial->minor = devno;
-            snprintf(serial->name, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_CDC_ACM, serial->minor);
-            return serial;
-        }
-    }
-    return NULL;
-}
-
-static void usbh_serial_cdc_acm_free(struct usbh_serial *serial)
-{
-    uint8_t devno = serial->minor;
-
-    if (devno < 32) {
-        g_devinuse_cdc_acm &= ~(1U << devno);
-    }
-    memset(serial, 0, sizeof(struct usbh_serial));
-    rt_free(serial);
-}
-
-static rt_err_t usbh_serial_open(struct rt_device *dev, rt_uint16_t oflag)
-{
-    struct usbh_serial *serial;
-
-    RT_ASSERT(dev != RT_NULL);
-
-    serial = (struct usbh_serial *)dev;
-
-    switch (serial->type) {
-        case USBH_SERIAL_TYPE_CDC_ACM:
-            break;
-        case USBH_SERIAL_TYPE_FTDI:
-            break;
-        case USBH_SERIAL_TYPE_CP210X:
-            break;
-        case USBH_SERIAL_TYPE_CH34X:
-            break;
-        case USBH_SERIAL_TYPE_PL2303:
-            break;
-
-        default:
-            break;
-    }
-
-    return RT_EOK;
-}
-
-static rt_err_t usbh_serial_close(struct rt_device *dev)
-{
-    struct usbh_serial *serial;
-
-    RT_ASSERT(dev != RT_NULL);
-
-    serial = (struct usbh_serial *)dev;
-
-    switch (serial->type) {
-        case USBH_SERIAL_TYPE_CDC_ACM:
-            break;
-        case USBH_SERIAL_TYPE_FTDI:
-            break;
-        case USBH_SERIAL_TYPE_CP210X:
-            break;
-        case USBH_SERIAL_TYPE_CH34X:
-            break;
-        case USBH_SERIAL_TYPE_PL2303:
-            break;
-
-        default:
-            break;
-    }
-
-    return RT_EOK;
-}
-
-static rt_ssize_t usbh_serial_read(struct rt_device *dev,
-                                   rt_off_t pos,
-                                   void *buffer,
-                                   rt_size_t size)
-{
-    struct usbh_serial *serial;
-
-    RT_ASSERT(dev != RT_NULL);
-
-    serial = (struct usbh_serial *)dev;
-
-    return rt_ringbuffer_get(&serial->rx_rb, (rt_uint8_t *)buffer, size);
-}
-
-static rt_ssize_t usbh_serial_write(struct rt_device *dev,
-                                    rt_off_t pos,
-                                    const void *buffer,
-                                    rt_size_t size)
-{
-    struct usbh_serial *serial;
-    int ret = 0;
-    rt_uint8_t *align_buf;
-
-    RT_ASSERT(dev != RT_NULL);
-
-    serial = (struct usbh_serial *)dev;
-
-    align_buf = (rt_uint8_t *)buffer;
-
-    if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) {
-        align_buf = rt_malloc_align(USB_ALIGN_UP(size, CONFIG_USB_ALIGN_SIZE), CONFIG_USB_ALIGN_SIZE);
-        if (!align_buf) {
-            USB_LOG_ERR("serial get align buf failed\n");
-            return 0;
-        }
-
-        usb_memcpy(align_buf, buffer, size);
-    }
-
-    switch (serial->type) {
-#if defined(PKG_CHERRYUSB_HOST_CDC_ACM) || defined(RT_CHERRYUSB_HOST_CDC_ACM)
-        case USBH_SERIAL_TYPE_CDC_ACM:
-            ret = usbh_cdc_acm_bulk_out_transfer((struct usbh_cdc_acm *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER);
-            if (ret < 0) {
-                USB_LOG_ERR("usbh_cdc_acm_bulk_out_transfer failed: %d\n", ret);
-                ret = 0;
-            }
-            break;
-#endif
-#if defined(PKG_CHERRYUSB_HOST_FTDI) || defined(RT_CHERRYUSB_HOST_FTDI)
-        case USBH_SERIAL_TYPE_FTDI:
-            ret = usbh_ftdi_bulk_out_transfer((struct usbh_ftdi *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER);
-            if (ret < 0) {
-                USB_LOG_ERR("usbh_ftdi_bulk_out_transfer failed: %d\n", ret);
-                ret = 0;
-            }
-            break;
-#endif
-#if defined(PKG_CHERRYUSB_HOST_CH34X) || defined(RT_CHERRYUSB_HOST_CH34X)
-        case USBH_SERIAL_TYPE_CH34X:
-            ret = usbh_ch34x_bulk_out_transfer((struct usbh_ch34x *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER);
-            if (ret < 0) {
-                USB_LOG_ERR("usbh_ch34x_bulk_out_transfer failed: %d\n", ret);
-                ret = 0;
-            }
-            break;
-#endif
-#if defined(PKG_CHERRYUSB_HOST_PL2303) || defined(RT_CHERRYUSB_HOST_PL2303)
-        case USBH_SERIAL_TYPE_PL2303:
-            ret = usbh_pl2303_bulk_out_transfer((struct usbh_pl2303 *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER);
-            if (ret < 0) {
-                USB_LOG_ERR("usbh_pl2303_bulk_out_transfer failed: %d\n", ret);
-                ret = 0;
-            }
-            break;
-#endif
-        default:
-            break;
-    }
-
-    if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) {
-        rt_free_align(align_buf);
-    }
-
-    return ret;
-}
-
-static rt_err_t usbh_serial_control(struct rt_device *dev,
-                                    int cmd,
-                                    void *args)
-{
-    struct usbh_serial *serial;
-    struct serial_configure *config;
-    struct cdc_line_coding line_coding;
-    int ret = -RT_EINVAL;
-
-    RT_ASSERT(dev != RT_NULL);
-
-    serial = (struct usbh_serial *)dev;
-
-    switch (serial->type) {
-#if defined(PKG_CHERRYUSB_HOST_CDC_ACM) || defined(RT_CHERRYUSB_HOST_CDC_ACM)
-        case USBH_SERIAL_TYPE_CDC_ACM:
-            if (cmd == RT_DEVICE_CTRL_CONFIG) {
-                struct usbh_cdc_acm *cdc_acm_class;
-                cdc_acm_class = (struct usbh_cdc_acm *)dev->user_data;
-
-                config = (struct serial_configure *)args;
-
-                line_coding.dwDTERate = config->baud_rate;
-                line_coding.bDataBits = config->data_bits;
-                line_coding.bCharFormat = 0; // STOP_BITS_1
-                line_coding.bParityType = config->parity;
-
-                usbh_cdc_acm_set_line_coding(cdc_acm_class, &line_coding);
-            }
-
-            ret = RT_EOK;
-            break;
-#endif
-#if defined(PKG_CHERRYUSB_HOST_FTDI) || defined(RT_CHERRYUSB_HOST_FTDI)
-        case USBH_SERIAL_TYPE_FTDI:
-            if (cmd == RT_DEVICE_CTRL_CONFIG) {
-                struct usbh_ftdi *ftdi_class;
-                ftdi_class = (struct usbh_ftdi *)dev->user_data;
-
-                config = (struct serial_configure *)args;
-
-                line_coding.dwDTERate = config->baud_rate;
-                line_coding.bDataBits = config->data_bits;
-                line_coding.bCharFormat = 0; // STOP_BITS_1
-                line_coding.bParityType = config->parity;
-
-                usbh_ftdi_set_line_coding(ftdi_class, &line_coding);
-            }
-
-            ret = RT_EOK;
-            break;
-#endif
-#if defined(PKG_CHERRYUSB_HOST_CP210X) || defined(RT_CHERRYUSB_HOST_CP210X)
-        case USBH_SERIAL_TYPE_CP210X:
-            if (cmd == RT_DEVICE_CTRL_CONFIG) {
-                struct usbh_cp210x *cp210x_class;
-                cp210x_class = (struct usbh_cp210x *)dev->user_data;
-
-                config = (struct serial_configure *)args;
-
-                line_coding.dwDTERate = config->baud_rate;
-                line_coding.bDataBits = config->data_bits;
-                line_coding.bCharFormat = 0; // STOP_BITS_1
-                line_coding.bParityType = config->parity;
-
-                usbh_cp210x_set_line_coding(cp210x_class, &line_coding);
-            }
-
-            ret = RT_EOK;
-            break;
-#endif
-#if defined(PKG_CHERRYUSB_HOST_CH34X) || defined(RT_CHERRYUSB_HOST_CH34X)
-        case USBH_SERIAL_TYPE_CH34X:
-            if (cmd == RT_DEVICE_CTRL_CONFIG) {
-                struct usbh_ch34x *ch34x_class;
-                ch34x_class = (struct usbh_ch34x *)dev->user_data;
-
-                config = (struct serial_configure *)args;
-
-                line_coding.dwDTERate = config->baud_rate;
-                line_coding.bDataBits = config->data_bits;
-                line_coding.bCharFormat = 0; // STOP_BITS_1
-                line_coding.bParityType = config->parity;
-
-                usbh_ch34x_set_line_coding(ch34x_class, &line_coding);
-            }
-
-            ret = RT_EOK;
-            break;
-#endif
-#if defined(PKG_CHERRYUSB_HOST_PL2303) || defined(RT_CHERRYUSB_HOST_PL2303)
-        case USBH_SERIAL_TYPE_PL2303:
-            if (cmd == RT_DEVICE_CTRL_CONFIG) {
-                struct usbh_pl2303 *pl2303_class;
-                pl2303_class = (struct usbh_pl2303 *)dev->user_data;
-
-                config = (struct serial_configure *)args;
-
-                line_coding.dwDTERate = config->baud_rate;
-                line_coding.bDataBits = config->data_bits;
-                line_coding.bCharFormat = 0; // STOP_BITS_1
-                line_coding.bParityType = config->parity;
-
-                usbh_pl2303_set_line_coding(pl2303_class, &line_coding);
-            }
-
-            ret = RT_EOK;
-            break;
-#endif
-        default:
-            break;
-    }
-
-    return ret;
-}
-
-#ifdef RT_USING_DEVICE_OPS
-const static struct rt_device_ops usbh_serial_ops = {
-    NULL,
-    usbh_serial_open,
-    usbh_serial_close,
-    usbh_serial_read,
-    usbh_serial_write,
-    usbh_serial_control
-};
-#endif
-
-#ifdef RT_USING_POSIX_DEVIO
-#include <unistd.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <dfs_file.h>
-
-#ifdef RT_USING_POSIX_TERMIOS
-#include <termios.h>
-#endif
-
-static rt_err_t usbh_serial_fops_rx_ind(rt_device_t dev, rt_size_t size)
-{
-    rt_wqueue_wakeup(&(dev->wait_queue), (void*)POLLIN);
-
-    return RT_EOK;
-}
-
-/* fops for serial */
-static int usbh_serial_fops_open(struct dfs_file *fd)
-{
-    rt_err_t ret = 0;
-    rt_uint16_t flags = 0;
-    rt_device_t device;
-
-    device = (rt_device_t)fd->vnode->data;
-    RT_ASSERT(device != RT_NULL);
-
-    switch (fd->flags & O_ACCMODE)
-    {
-    case O_RDONLY:
-        USB_LOG_DBG("fops open: O_RDONLY!");
-        flags = RT_DEVICE_FLAG_RDONLY;
-        break;
-    case O_WRONLY:
-        USB_LOG_DBG("fops open: O_WRONLY!");
-        flags = RT_DEVICE_FLAG_WRONLY;
-        break;
-    case O_RDWR:
-        USB_LOG_DBG("fops open: O_RDWR!");
-        flags = RT_DEVICE_FLAG_RDWR;
-        break;
-    default:
-        USB_LOG_ERR("fops open: unknown mode - %d!", fd->flags & O_ACCMODE);
-        break;
-    }
-
-    if ((fd->flags & O_ACCMODE) != O_WRONLY)
-        rt_device_set_rx_indicate(device, usbh_serial_fops_rx_ind);
-    ret = rt_device_open(device, flags);
-    if (ret == RT_EOK) return 0;
-
-    return ret;
-}
-
-static int usbh_serial_fops_close(struct dfs_file *fd)
-{
-    rt_device_t device;
-
-    device = (rt_device_t)fd->vnode->data;
-
-    rt_device_set_rx_indicate(device, RT_NULL);
-    rt_device_close(device);
-
-    return 0;
-}
-
-static int usbh_serial_fops_ioctl(struct dfs_file *fd, int cmd, void *args)
-{
-    rt_device_t device;
-    int flags = (int)(rt_base_t)args;
-    int mask  = O_NONBLOCK | O_APPEND;
-
-    device = (rt_device_t)fd->vnode->data;
-    switch (cmd)
-    {
-    case FIONREAD:
-        break;
-    case FIONWRITE:
-        break;
-    case F_SETFL:
-        flags &= mask;
-        fd->flags &= ~mask;
-        fd->flags |= flags;
-        break;
-    }
-
-    return rt_device_control(device, cmd, args);
-}
-
-static int usbh_serial_fops_read(struct dfs_file *fd, void *buf, size_t count)
-{
-    int size = 0;
-    rt_device_t device;
-
-    device = (rt_device_t)fd->vnode->data;
-
-    do
-    {
-        size = rt_device_read(device, -1,  buf, count);
-        if (size <= 0)
-        {
-            if (fd->flags & O_NONBLOCK)
-            {
-                size = -EAGAIN;
-                break;
-            }
-
-            rt_wqueue_wait(&(device->wait_queue), 0, RT_WAITING_FOREVER);
-        }
-    }while (size <= 0);
-
-    return size;
-}
-
-static int usbh_serial_fops_write(struct dfs_file *fd, const void *buf, size_t count)
-{
-    rt_device_t device;
-
-    device = (rt_device_t)fd->vnode->data;
-    return rt_device_write(device, -1, buf, count);
-}
-
-static int usbh_serial_fops_poll(struct dfs_file *fd, struct rt_pollreq *req)
-{
-    int mask = 0;
-    int flags = 0;
-    rt_device_t device;
-    struct usbh_serial *serial;
-
-    device = (rt_device_t)fd->vnode->data;
-    RT_ASSERT(device != RT_NULL);
-
-    serial = (struct usbh_serial *)device;
-
-    /* only support POLLIN */
-    flags = fd->flags & O_ACCMODE;
-    if (flags == O_RDONLY || flags == O_RDWR)
-    {
-        rt_base_t level;
-
-        rt_poll_add(&(device->wait_queue), req);
-
-        level = rt_hw_interrupt_disable();
-
-        if (rt_ringbuffer_data_len(&serial->rx_rb))
-            mask |= POLLIN;
-        rt_hw_interrupt_enable(level);
-    }
-    // mask|=POLLOUT;
-   return mask;
-}
-
-const static struct dfs_file_ops usbh_serial_fops =
-{
-    usbh_serial_fops_open,
-    usbh_serial_fops_close,
-    usbh_serial_fops_ioctl,
-    usbh_serial_fops_read,
-    usbh_serial_fops_write,
-    RT_NULL, /* flush */
-    RT_NULL, /* lseek */
-    RT_NULL, /* getdents */
-    usbh_serial_fops_poll,
-};
-#endif /* RT_USING_POSIX_DEVIO */
-
-rt_err_t usbh_serial_register(struct usbh_serial *serial,
-                              void *data)
-{
-    rt_err_t ret;
-    struct rt_device *device;
-    RT_ASSERT(serial != RT_NULL);
-
-    device = &(serial->parent);
-
-    device->type = RT_Device_Class_Char;
-    device->rx_indicate = RT_NULL;
-    device->tx_complete = RT_NULL;
-
-#ifdef RT_USING_DEVICE_OPS
-    device->ops = &usbh_serial_ops;
-#else
-    device->init = NULL;
-    device->open = usbh_serial_open;
-    device->close = usbh_serial_close;
-    device->read = usbh_serial_read;
-    device->write = usbh_serial_write;
-    device->control = usbh_serial_control;
-#endif
-    device->user_data = data;
-
-    /* register a character device */
-    ret = rt_device_register(device, serial->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_REMOVABLE);
-
-#ifdef RT_USING_POSIX_DEVIO
-    /* set fops */
-    device->fops = &usbh_serial_fops;
-#endif
-    rt_ringbuffer_init(&serial->rx_rb, serial->rx_rb_buffer, sizeof(serial->rx_rb_buffer));
-
-    return ret;
-}
-
-void usbh_serial_unregister(struct usbh_serial *serial)
-{
-    RT_ASSERT(serial != NULL);
-
-    rt_device_unregister(&serial->parent);
-
-    if (serial->type == USBH_SERIAL_TYPE_CDC_ACM) {
-        usbh_serial_cdc_acm_free(serial);
-    } else {
-        usbh_serial_free(serial);
-    }
-}
-
-#if defined(PKG_CHERRYUSB_HOST_CDC_ACM) || defined(RT_CHERRYUSB_HOST_CDC_ACM)
-void usbh_cdc_acm_callback(void *arg, int nbytes)
-{
-    struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)arg;
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &cdc_acm_class->bulkin_urb;
-
-    if (nbytes > 0) {
-        serial = (struct usbh_serial *)cdc_acm_class->user_data;
-        rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_cdc_acm_rx_buf[serial->minor], nbytes);
-
-        if (serial->parent.rx_indicate) {
-            serial->parent.rx_indicate(&serial->parent, nbytes);
-        }
-
-        usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkin, g_usbh_serial_cdc_acm_rx_buf[serial->minor], sizeof(g_usbh_serial_cdc_acm_rx_buf[serial->minor]), 0, usbh_cdc_acm_callback, cdc_acm_class);
-        ret = usbh_submit_urb(urb);
-        if (ret < 0) {
-            USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        }
-    }
-}
-
-void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class)
-{
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &cdc_acm_class->bulkin_urb;
-
-    serial = usbh_serial_cdc_acm_alloc(USBH_SERIAL_TYPE_CDC_ACM);
-    cdc_acm_class->user_data = serial;
-
-    usbh_serial_register(serial, cdc_acm_class);
-
-    struct cdc_line_coding linecoding;
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_cdc_acm_set_line_coding(cdc_acm_class, &linecoding);
-
-    usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkin, g_usbh_serial_cdc_acm_rx_buf[serial->minor], sizeof(g_usbh_serial_cdc_acm_rx_buf[serial->minor]), 0, usbh_cdc_acm_callback, cdc_acm_class);
-    ret = usbh_submit_urb(urb);
-    if (ret < 0) {
-        USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        usbh_serial_unregister(serial);
-        return;
-    }
-}
-
-void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class)
-{
-    struct usbh_serial *serial;
-
-    serial = (struct usbh_serial *)cdc_acm_class->user_data;
-    usbh_serial_unregister(serial);
-}
-#endif
-
-#if defined(PKG_CHERRYUSB_HOST_FTDI) || defined(RT_CHERRYUSB_HOST_FTDI)
-void usbh_ftdi_callback(void *arg, int nbytes)
-{
-    struct usbh_ftdi *ftdi_class = (struct usbh_ftdi *)arg;
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &ftdi_class->bulkin_urb;
-
-    if (nbytes >= 2) {
-        serial = (struct usbh_serial *)ftdi_class->user_data;
-
-        nbytes -= 2; // Skip the first two bytes (header)
-        rt_ringbuffer_put(&serial->rx_rb, &g_usbh_serial_vendor_rx_buf[serial->minor][2], nbytes);
-
-        if (serial->parent.rx_indicate && nbytes) {
-            serial->parent.rx_indicate(&serial->parent, nbytes);
-        }
-
-        usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ftdi_callback, ftdi_class);
-        ret = usbh_submit_urb(urb);
-        if (ret < 0) {
-            USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        }
-    }
-}
-
-void usbh_ftdi_run(struct usbh_ftdi *ftdi_class)
-{
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &ftdi_class->bulkin_urb;
-
-    serial = usbh_serial_alloc(USBH_SERIAL_TYPE_FTDI);
-    ftdi_class->user_data = serial;
-
-    usbh_serial_register(serial, ftdi_class);
-
-    struct cdc_line_coding linecoding;
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_ftdi_set_line_coding(ftdi_class, &linecoding);
-
-    usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ftdi_callback, ftdi_class);
-    ret = usbh_submit_urb(urb);
-    if (ret < 0) {
-        USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        usbh_serial_unregister(serial);
-        return;
-    }
-}
-
-void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class)
-{
-    struct usbh_serial *serial;
-
-    serial = (struct usbh_serial *)ftdi_class->user_data;
-    usbh_serial_unregister(serial);
-}
-#endif
-
-#if defined(PKG_CHERRYUSB_HOST_CH34X) || defined(RT_CHERRYUSB_HOST_CH34X)
-void usbh_ch34x_callback(void *arg, int nbytes)
-{
-    struct usbh_ch34x *ch34x_class = (struct usbh_ch34x *)arg;
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &ch34x_class->bulkin_urb;
-
-    if (nbytes > 0) {
-        serial = (struct usbh_serial *)ch34x_class->user_data;
-        rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_vendor_rx_buf[serial->minor], nbytes);
-
-        if (serial->parent.rx_indicate) {
-            serial->parent.rx_indicate(&serial->parent, nbytes);
-        }
-
-        usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ch34x_callback, ch34x_class);
-        ret = usbh_submit_urb(urb);
-        if (ret < 0) {
-            USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        }
-    }
-}
-
-void usbh_ch34x_run(struct usbh_ch34x *ch34x_class)
-{
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &ch34x_class->bulkin_urb;
-
-    serial = usbh_serial_alloc(USBH_SERIAL_TYPE_CH34X);
-    ch34x_class->user_data = serial;
-
-    usbh_serial_register(serial, ch34x_class);
-
-    struct cdc_line_coding linecoding;
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_ch34x_set_line_coding(ch34x_class, &linecoding);
-
-    usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ch34x_callback, ch34x_class);
-    ret = usbh_submit_urb(urb);
-    if (ret < 0) {
-        USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        usbh_serial_unregister(serial);
-        return;
-    }
-}
-
-void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class)
-{
-    struct usbh_serial *serial;
-
-    serial = (struct usbh_serial *)ch34x_class->user_data;
-    usbh_serial_unregister(serial);
-}
-#endif
-
-#if defined(PKG_CHERRYUSB_HOST_CP210X) || defined(RT_CHERRYUSB_HOST_CP210X)
-void usbh_cp210x_callback(void *arg, int nbytes)
-{
-    struct usbh_cp210x *cp210x_class = (struct usbh_cp210x *)arg;
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &cp210x_class->bulkin_urb;
-
-    if (nbytes > 0) {
-        serial = (struct usbh_serial *)cp210x_class->user_data;
-        rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_vendor_rx_buf[serial->minor], nbytes);
-
-        if (serial->parent.rx_indicate) {
-            serial->parent.rx_indicate(&serial->parent, nbytes);
-        }
-
-        usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_cp210x_callback, cp210x_class);
-        ret = usbh_submit_urb(urb);
-        if (ret < 0) {
-            USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        }
-    }
-}
-
-void usbh_cp210x_run(struct usbh_cp210x *cp210x_class)
-{
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &cp210x_class->bulkin_urb;
-
-    serial = usbh_serial_alloc(USBH_SERIAL_TYPE_CP210X);
-    cp210x_class->user_data = serial;
-
-    usbh_serial_register(serial, cp210x_class);
-
-    struct cdc_line_coding linecoding;
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_cp210x_set_line_coding(cp210x_class, &linecoding);
-
-    usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_cp210x_callback, cp210x_class);
-    ret = usbh_submit_urb(urb);
-    if (ret < 0) {
-        USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        usbh_serial_unregister(serial);
-        return;
-    }
-}
-
-void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class)
-{
-    struct usbh_serial *serial;
-
-    serial = (struct usbh_serial *)cp210x_class->user_data;
-    usbh_serial_unregister(serial);
-}
-#endif
-
-#if defined(PKG_CHERRYUSB_HOST_PL2303) || defined(RT_CHERRYUSB_HOST_PL2303)
-void usbh_pl2303_callback(void *arg, int nbytes)
-{
-    struct usbh_pl2303 *pl2303_class = (struct usbh_pl2303 *)arg;
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &pl2303_class->bulkin_urb;
-
-    if (nbytes > 0) {
-        serial = (struct usbh_serial *)pl2303_class->user_data;
-        rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_vendor_rx_buf[serial->minor], nbytes);
-
-        if (serial->parent.rx_indicate) {
-            serial->parent.rx_indicate(&serial->parent, nbytes);
-        }
-
-        usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_pl2303_callback, pl2303_class);
-        ret = usbh_submit_urb(urb);
-        if (ret < 0) {
-            USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        }
-    }
-}
-
-void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class)
-{
-    struct usbh_serial *serial;
-    int ret;
-    struct usbh_urb *urb = &pl2303_class->bulkin_urb;
-
-    serial = usbh_serial_alloc(USBH_SERIAL_TYPE_PL2303);
-    pl2303_class->user_data = serial;
-
-    usbh_serial_register(serial, pl2303_class);
-
-    struct cdc_line_coding linecoding;
-    linecoding.dwDTERate = 115200;
-    linecoding.bDataBits = 8;
-    linecoding.bParityType = 0;
-    linecoding.bCharFormat = 0;
-    usbh_pl2303_set_line_coding(pl2303_class, &linecoding);
-
-    usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_pl2303_callback, pl2303_class);
-    ret = usbh_submit_urb(urb);
-    if (ret < 0) {
-        USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret);
-        usbh_serial_unregister(serial);
-        return;
-    }
-}
-
-void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class)
-{
-    struct usbh_serial *serial;
-
-    serial = (struct usbh_serial *)pl2303_class->user_data;
-    usbh_serial_unregister(serial);
-}
-#endif

+ 5 - 1
tests/bouffalolab/inc/usb_config.h

@@ -158,7 +158,7 @@
 #define CONFIG_USBHOST_MAX_INTF_ALTSETTINGS 2
 #define CONFIG_USBHOST_MAX_ENDPOINTS        4
 
-#define CONFIG_USBHOST_MAX_CDC_ACM_CLASS 4
+#define CONFIG_USBHOST_MAX_SERIAL_CLASS  4
 #define CONFIG_USBHOST_MAX_HID_CLASS     4
 #define CONFIG_USBHOST_MAX_MSC_CLASS     2
 #define CONFIG_USBHOST_MAX_AUDIO_CLASS   1
@@ -193,6 +193,10 @@
 #define CONFIG_USBHOST_MSC_TIMEOUT 5000
 #endif
 
+#ifndef CONFIG_USBHOST_SERIAL_RX_SIZE
+#define CONFIG_USBHOST_SERIAL_RX_SIZE 2048
+#endif
+
 /* This parameter affects usb performance, and depends on (TCP_WND)tcp eceive windows size,
  * you can change to 2K ~ 16K and must be larger than TCP RX windows size in order to avoid being overflow.
  */

+ 6 - 2
tests/hpmicro/inc/usb_config.h

@@ -47,7 +47,7 @@
 #define USBD_MAX_POWER     200
 
 /* attribute data into no cache ram */
-#define USB_NOCACHE_RAM_SECTION __attribute__((section(".fast_ram.non_init")))
+#define USB_NOCACHE_RAM_SECTION __attribute__((section(".noncacheable.non_init")))
 
 /* use usb_memcpy default for high performance but cost more flash memory.
  * And, arm libc has a bug that memcpy() may cause data misalignment when the size is not a multiple of 4.
@@ -174,7 +174,7 @@
 #define CONFIG_USBHOST_MAX_INTF_ALTSETTINGS 2
 #define CONFIG_USBHOST_MAX_ENDPOINTS        4
 
-#define CONFIG_USBHOST_MAX_CDC_ACM_CLASS 4
+#define CONFIG_USBHOST_MAX_SERIAL_CLASS  4
 #define CONFIG_USBHOST_MAX_HID_CLASS     4
 #define CONFIG_USBHOST_MAX_MSC_CLASS     2
 #define CONFIG_USBHOST_MAX_AUDIO_CLASS   1
@@ -209,6 +209,10 @@
 #define CONFIG_USBHOST_MSC_TIMEOUT 5000
 #endif
 
+#ifndef CONFIG_USBHOST_SERIAL_RX_SIZE
+#define CONFIG_USBHOST_SERIAL_RX_SIZE 2048
+#endif
+
 /* This parameter affects usb performance, and depends on (TCP_WND)tcp eceive windows size,
  * you can change to 2K ~ 16K and must be larger than TCP RX windows size in order to avoid being overflow.
  */

+ 2 - 0
tests/hpmicro/src/main.c

@@ -17,6 +17,7 @@
 #include "hpm_gpio_drv.h"
 #include "shell.h"
 #include "usbh_core.h"
+#include "usbh_serial.h"
 #include "lwip/tcpip.h"
 #ifdef CONFIG_USB_EHCI_ISO
 #include "usbh_uvc_stream.h"
@@ -116,6 +117,7 @@ static void task_start(void *param)
 }
 
 CSH_CMD_EXPORT(lsusb, );
+CSH_CMD_EXPORT(usbh_serial, );
 
 #ifdef CONFIG_USB_EHCI_ISO
 // clang-format off