Просмотр исходного кода

usb: Add USB host CDC-ACM class driver

Tomas Rezucha 4 лет назад
Родитель
Сommit
dd1b698075
23 измененных файлов с 2747 добавлено и 1 удалено
  1. 1 0
      components/usb/include/usb/usb_host.h
  2. 4 1
      components/usb/usb_host.c
  3. 9 0
      examples/peripherals/usb/host/cdc/cdc_acm_bg96/CMakeLists.txt
  4. 99 0
      examples/peripherals/usb/host/cdc/cdc_acm_bg96/README.md
  5. 2 0
      examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/CMakeLists.txt
  6. 84 0
      examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/bg96_usb.hpp
  7. 207 0
      examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/cdc_acm_host_bg96.cpp
  8. 3 0
      examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/idf_component.yml
  9. 9 0
      examples/peripherals/usb/host/cdc/cdc_acm_host/CMakeLists.txt
  10. 64 0
      examples/peripherals/usb/host/cdc/cdc_acm_host/README.md
  11. 2 0
      examples/peripherals/usb/host/cdc/cdc_acm_host/main/CMakeLists.txt
  12. 112 0
      examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb-cdc.c
  13. 3 0
      examples/peripherals/usb/host/cdc/common/cdc_acm_host/CMakeLists.txt
  14. 46 0
      examples/peripherals/usb/host/cdc/common/cdc_acm_host/README.md
  15. 1170 0
      examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c
  16. 305 0
      examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h
  17. 206 0
      examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/usb_types_cdc.h
  18. 3 0
      examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/CMakeLists.txt
  19. 375 0
      examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c
  20. 10 0
      tools/test_apps/peripherals/usb/CMakeLists.txt
  21. 14 0
      tools/test_apps/peripherals/usb/README.md
  22. 3 0
      tools/test_apps/peripherals/usb/main/CMakeLists.txt
  23. 16 0
      tools/test_apps/peripherals/usb/main/usb_test_main.c

+ 1 - 0
components/usb/include/usb/usb_host.h

@@ -413,6 +413,7 @@ esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets,
  *
  * - Free a transfer object previously allocated using usb_host_transfer_alloc()
  * - The transfer must not be in-flight when attempting to free it
+ * - If a NULL pointer is passed, this function will simply return ESP_OK
  *
  * @param[in] transfer Transfer object
  * @return esp_err_t

+ 4 - 1
components/usb/usb_host.c

@@ -571,6 +571,7 @@ static void _handle_pending_ep(client_t *client_obj)
 
 esp_err_t usb_host_client_register(const usb_host_client_config_t *client_config, usb_host_client_handle_t *client_hdl_ret)
 {
+    HOST_CHECK(p_host_lib_obj, ESP_ERR_INVALID_STATE);
     HOST_CHECK(client_config != NULL && client_hdl_ret != NULL, ESP_ERR_INVALID_ARG);
     HOST_CHECK(client_config->max_num_event_msg > 0, ESP_ERR_INVALID_ARG);
     if (!client_config->is_synchronous) {
@@ -1229,7 +1230,9 @@ esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets,
 
 esp_err_t usb_host_transfer_free(usb_transfer_t *transfer)
 {
-    HOST_CHECK(transfer != NULL, ESP_ERR_INVALID_ARG);
+    if (transfer == NULL) {
+        return ESP_OK;
+    }
     urb_t *urb = __containerof(transfer, urb_t, transfer);
     urb_free(urb);
     return ESP_OK;

+ 9 - 0
examples/peripherals/usb/host/cdc/cdc_acm_bg96/CMakeLists.txt

@@ -0,0 +1,9 @@
+# For more information about build system see
+# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
+# The following five lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(cdc_acm_host_bg96)

+ 99 - 0
examples/peripherals/usb/host/cdc/cdc_acm_bg96/README.md

@@ -0,0 +1,99 @@
+| Supported Targets | ESP32-S2 | ESP32-S3 |
+| ----------------- | -------- | -------- |
+
+# USB CDC-ACM Host Driver BG96 Example
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+This example shows how to set up ESP chip to interface with CDC-like device by using the CDC-ACM Host Driver. CDC-like devices implement a Vendor-specific class, and support a subset of the functions of a fully compliant CDC-ACM device.
+
+## How to use example
+
+### Hardware Required
+
+Any ESP board with USB-OTG supported and a Quectel BG96 LTE/GPS modem.
+
+Connect USB_D+, USB_D-, GND and +5V signals of ESP board to BG96.
+
+_Note:_ Quectel BG96 modem must be started after power-up by applying low pulse on PWRKEY (pin 15).
+
+#### Pin Assignment
+
+See common pin assignments for USB Device examples from [upper level](../../../README.md#common-pin-assignments).
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```bash
+idf.py -p PORT flash monitor
+```
+
+(Replace PORT with the name of the serial port to use.)
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+After the flashing you should see the output at idf monitor:
+
+```
+I (276) BG96: USB Host installed
+I (24446) AT: ATE0
+I (24446) AT: 
+OK
+
+I (24526) AT: 
++QIND: SMS DONE
+
+I (24646) AT: 
+APP RDY
+
+I (25446) BG96: Sending AT
+I (25446) AT: 
+OK
+
+I (26446) BG96: Enabling GNSS
+I (26446) AT: 
+OK
+
+GPVTG Sentence:
+  Track [deg]:   0.00
+  Speed [kmph]:  0.00
+  Speed [knots]: 0.00
+GPGSA Sentence:
+  Mode: A
+  Fix:  1
+  PDOP: 0.00
+  HDOP: 0.00
+  VDOP: 0.00
+GPGGA sentence
+Number of satellites: 0
+Altitude: 0.000000
+GPRMC sentence
+Longitude:
+  Degrees: 0
+  Minutes: 0.000000
+  Cardinal:
+Latitude:
+  Degrees: 0
+  Minutes: 0.000000
+  Cardinal: 
+Date & Time: 00 Jan 00:00:00 1900
+Speed, in Knots: 0.000000
+Track, in degrees: 0.000000
+Magnetic Variation:
+  Degrees: 0.000000
+  Cardinal: 
+Invalid Magnetic Variation Direction!
+Adjusted Track (heading): 0.000000
+I (27446) BG96: Sending AT+GSN
+I (27446) AT: 
+860517045660414
+
+OK
+...
+
+```

+ 2 - 0
examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/CMakeLists.txt

@@ -0,0 +1,2 @@
+idf_component_register(SRCS "cdc_acm_host_bg96.cpp"
+                    INCLUDE_DIRS ".")

+ 84 - 0
examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/bg96_usb.hpp

@@ -0,0 +1,84 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+#pragma once
+
+#include "usb/cdc_acm_host.h"
+#include "esp_log.h"
+
+#define BG96_VID            (0x2C7C)
+#define BG96_PID            (0x0296)
+#define BG96_AT_INTERFACE   (2)
+#define BG96_NMEA_INTERFACE (1)
+
+class Bg96Usb {
+public:
+
+    explicit  Bg96Usb() : at_opened(false) {
+    };
+
+    esp_err_t at_start(cdc_acm_data_callback_t data_cb, void *user_arg)
+    {
+        // This driver doesn't support CDC notifications. This can lead to silent failures
+        const cdc_acm_host_device_config_t dev_config = {
+            .connection_timeout_ms = 10000,
+            .out_buffer_size = 64,
+            .event_cb = NULL,
+            .data_cb = data_cb,
+            .user_arg = user_arg,
+        };
+        ESP_ERROR_CHECK(this->at_port.open_vendor_specific(BG96_VID, BG96_PID, BG96_AT_INTERFACE, &dev_config));
+        this->at_opened = true;
+
+        // Some FW versions have Echo enabled by default. Disable it with ATE0 command
+        ESP_LOGD("BG96_USB", "Turning off echo with ATE0");
+        ESP_ERROR_CHECK(this->at_port.tx_blocking((uint8_t *)"ATE0\r", 5, 1000));
+        vTaskDelay(100);
+        return ESP_OK;
+    }
+
+    void at_stop()
+    {
+        this->at_port.close();
+        this->at_opened = false;
+    }
+
+    esp_err_t at_write(uint8_t *data, size_t len)
+    {
+        ESP_LOG_BUFFER_HEXDUMP("BG96_USB", data, len, ESP_LOG_DEBUG);
+        return this->at_port.tx_blocking(data, len, 1000);
+    }
+
+    esp_err_t gnss_start(cdc_acm_data_callback_t data_cb)
+    {
+        if (!this->at_opened) {
+            return  ESP_ERR_INVALID_STATE;
+        }
+
+        const cdc_acm_host_device_config_t dev_config = {
+            .connection_timeout_ms = 1000,
+            .out_buffer_size = 0, // Read-only
+            .event_cb = NULL,
+            .data_cb = data_cb,
+            .user_arg = this,
+        };
+        ESP_ERROR_CHECK(this->nmea_port.open_vendor_specific(BG96_VID, BG96_PID, BG96_NMEA_INTERFACE, &dev_config));
+        return this->at_port.tx_blocking((uint8_t*)"AT+QGPS=1\r", 10, 1000);
+    }
+
+    esp_err_t gnss_stop()
+    {
+        esp_err_t ret = this->at_port.tx_blocking((uint8_t*)"AT+QGPSEND\r", 11, 1000);
+        this->nmea_port.close();
+        return ret;
+    }
+
+protected:
+    CdcAcmDevice at_port;   // Main control port for AT commands
+    CdcAcmDevice nmea_port; // Read only port for NMEA messages
+private:
+    bool at_opened;
+};

+ 207 - 0
examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/cdc_acm_host_bg96.cpp

@@ -0,0 +1,207 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+
+#include "usb/usb_host.h"
+#include "bg96_usb.hpp"
+
+#include "nmea.h"
+#include "gpgll.h"
+#include "gpgga.h"
+#include "gprmc.h"
+#include "gpgsa.h"
+#include "gpvtg.h"
+#include "gptxt.h"
+#include "gpgsv.h"
+
+#define EXAMPLE_USB_HOST_PRIORITY 20
+
+static const char* TAG = "BG96";
+
+static char fmt_buf[32];
+
+/* ------------------------------- Callbacks -------------------------------- */
+
+static void handle_rx(uint8_t *data, size_t data_len, void *user_arg)
+{
+    data[data_len] = '\0';
+    ESP_LOGI("AT", "%s", data);
+}
+
+static void handle_gps(uint8_t* data, size_t data_len, void *user_arg)
+{
+    // handle nmea_data
+    nmea_s *nmea_data = nmea_parse((char *)data, data_len, 0);
+    if (nmea_data == NULL) {
+        printf("Failed to parse the sentence!\n");
+        printf("  Type: %.5s (%d)\n", data + 1, nmea_get_type((const char *)data));
+    } else {
+        if (nmea_data->errors != 0) {
+            printf("WARN: The sentence struct contains parse errors!\n");
+        }
+
+        if (NMEA_GPGGA == nmea_data->type) {
+            printf("GPGGA sentence\n");
+            nmea_gpgga_s *gpgga = (nmea_gpgga_s *)nmea_data;
+            printf("Number of satellites: %d\n", gpgga->n_satellites);
+            printf("Altitude: %f %c\n", gpgga->altitude, gpgga->altitude_unit);
+        }
+
+        if (NMEA_GPGLL == nmea_data->type) {
+            printf("GPGLL sentence\n");
+            nmea_gpgll_s *pos = (nmea_gpgll_s *)nmea_data;
+            printf("Longitude:\n");
+            printf("  Degrees: %d\n", pos->longitude.degrees);
+            printf("  Minutes: %f\n", pos->longitude.minutes);
+            printf("  Cardinal: %c\n", (char)pos->longitude.cardinal);
+            printf("Latitude:\n");
+            printf("  Degrees: %d\n", pos->latitude.degrees);
+            printf("  Minutes: %f\n", pos->latitude.minutes);
+            printf("  Cardinal: %c\n", (char)pos->latitude.cardinal);
+            strftime(fmt_buf, sizeof(fmt_buf), "%H:%M:%S", &pos->time);
+            printf("Time: %s\n", fmt_buf);
+        }
+
+        if (NMEA_GPRMC == nmea_data->type) {
+            printf("GPRMC sentence\n");
+            nmea_gprmc_s *pos = (nmea_gprmc_s *)nmea_data;
+            printf("Longitude:\n");
+            printf("  Degrees: %d\n", pos->longitude.degrees);
+            printf("  Minutes: %f\n", pos->longitude.minutes);
+            printf("  Cardinal: %c\n", (char)pos->longitude.cardinal);
+            printf("Latitude:\n");
+            printf("  Degrees: %d\n", pos->latitude.degrees);
+            printf("  Minutes: %f\n", pos->latitude.minutes);
+            printf("  Cardinal: %c\n", (char)pos->latitude.cardinal);
+            strftime(fmt_buf, sizeof(fmt_buf), "%d %b %T %Y", &pos->date_time);
+            printf("Date & Time: %s\n", fmt_buf);
+            printf("Speed, in Knots: %f\n", pos->gndspd_knots);
+            printf("Track, in degrees: %f\n", pos->track_deg);
+            printf("Magnetic Variation:\n");
+            printf("  Degrees: %f\n", pos->magvar_deg);
+            printf("  Cardinal: %c\n", (char)pos->magvar_cardinal);
+            double adjusted_course = pos->track_deg;
+            if (NMEA_CARDINAL_DIR_EAST == pos->magvar_cardinal) {
+                adjusted_course -= pos->magvar_deg;
+            } else if (NMEA_CARDINAL_DIR_WEST == pos->magvar_cardinal) {
+                adjusted_course += pos->magvar_deg;
+            } else {
+                printf("Invalid Magnetic Variation Direction!\n");
+            }
+
+            printf("Adjusted Track (heading): %f\n", adjusted_course);
+        }
+
+        if (NMEA_GPGSA == nmea_data->type) {
+            nmea_gpgsa_s *gpgsa = (nmea_gpgsa_s *)nmea_data;
+
+            printf("GPGSA Sentence:\n");
+            printf("  Mode: %c\n", gpgsa->mode);
+            printf("  Fix:  %d\n", gpgsa->fixtype);
+            printf("  PDOP: %.2lf\n", gpgsa->pdop);
+            printf("  HDOP: %.2lf\n", gpgsa->hdop);
+            printf("  VDOP: %.2lf\n", gpgsa->vdop);
+        }
+
+        if (NMEA_GPGSV == nmea_data->type) {
+            nmea_gpgsv_s *gpgsv = (nmea_gpgsv_s *)nmea_data;
+
+            printf("GPGSV Sentence:\n");
+            printf("  Num: %d\n", gpgsv->sentences);
+            printf("  ID:  %d\n", gpgsv->sentence_number);
+            printf("  SV:  %d\n", gpgsv->satellites);
+            printf("  #1:  %d %d %d %d\n", gpgsv->sat[0].prn, gpgsv->sat[0].elevation, gpgsv->sat[0].azimuth,
+                   gpgsv->sat[0].snr);
+            printf("  #2:  %d %d %d %d\n", gpgsv->sat[1].prn, gpgsv->sat[1].elevation, gpgsv->sat[1].azimuth,
+                   gpgsv->sat[1].snr);
+            printf("  #3:  %d %d %d %d\n", gpgsv->sat[2].prn, gpgsv->sat[2].elevation, gpgsv->sat[2].azimuth,
+                   gpgsv->sat[2].snr);
+            printf("  #4:  %d %d %d %d\n", gpgsv->sat[3].prn, gpgsv->sat[3].elevation, gpgsv->sat[3].azimuth,
+                   gpgsv->sat[3].snr);
+        }
+
+        if (NMEA_GPTXT == nmea_data->type) {
+            nmea_gptxt_s *gptxt = (nmea_gptxt_s *)nmea_data;
+
+            printf("GPTXT Sentence:\n");
+            printf("  ID: %d %d %d\n", gptxt->id_00, gptxt->id_01, gptxt->id_02);
+            printf("  %s\n", gptxt->text);
+        }
+
+        if (NMEA_GPVTG == nmea_data->type) {
+            nmea_gpvtg_s *gpvtg = (nmea_gpvtg_s *)nmea_data;
+
+            printf("GPVTG Sentence:\n");
+            printf("  Track [deg]:   %.2lf\n", gpvtg->track_deg);
+            printf("  Speed [kmph]:  %.2lf\n", gpvtg->gndspd_kmph);
+            printf("  Speed [knots]: %.2lf\n", gpvtg->gndspd_knots);
+        }
+
+        nmea_free(nmea_data);
+    }
+}
+
+void usb_lib_task(void *arg)
+{
+    while (1) {
+        //Start handling system events
+        uint32_t event_flags;
+        usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
+            printf("No more clients\n");
+            ESP_ERROR_CHECK(usb_host_device_free_all());
+        }
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
+            break;
+        }
+    }
+
+    //Short delay to allow task to be cleaned up
+    vTaskDelay(10);
+    //Clean up USB Host
+    ESP_ERROR_CHECK(usb_host_uninstall());
+    vTaskDelete(NULL);
+}
+
+/* ---------------------------------- Main ---------------------------------- */
+extern "C" void app_main(void)
+{
+    //Install USB Host driver. Should only be called once in entire application
+    ESP_LOGI(TAG, "Installing USB Host");
+    usb_host_config_t host_config = {
+        .intr_flags = ESP_INTR_FLAG_LEVEL1,
+    };
+    ESP_ERROR_CHECK(usb_host_install(&host_config));
+
+    // Create a task that will handle USB library events
+    xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, EXAMPLE_USB_HOST_PRIORITY, NULL);
+
+    ESP_LOGI(TAG, "Installing CDC-ACM driver");
+    ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
+
+    Bg96Usb *bg96 = new Bg96Usb();
+    bg96->at_start(handle_rx, NULL);
+
+    static char text1[] = "AT\r";
+    static char text2[] = "AT+GSN\r";
+
+    ESP_LOGI(TAG, "Sending AT");
+    bg96->at_write((uint8_t *)text1, strlen(text1));
+    vTaskDelay(100);
+
+    ESP_LOGI(TAG, "Enabling GNSS");
+    bg96->gnss_start(handle_gps);
+
+    vTaskDelay(100);
+    ESP_LOGI(TAG, "Sending AT+GSN");
+    bg96->at_write((uint8_t *)text2, strlen(text2));
+}

+ 3 - 0
examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/idf_component.yml

@@ -0,0 +1,3 @@
+dependencies:
+  idf: ">=4.4"
+  igrr/libnmea: ">=0.1.1"

+ 9 - 0
examples/peripherals/usb/host/cdc/cdc_acm_host/CMakeLists.txt

@@ -0,0 +1,9 @@
+# For more information about build system see
+# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
+# The following five lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(cdc_acm_host)

+ 64 - 0
examples/peripherals/usb/host/cdc/cdc_acm_host/README.md

@@ -0,0 +1,64 @@
+| Supported Targets | ESP32-S2 | ESP32-S3 |
+| ----------------- | -------- | -------- |
+
+# USB CDC-ACM Host Driver Example
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+This example shows how to use the CDC-ACM Host Driver to allow an ESP chip to communicate with a USB CDC-ACM device.
+
+## How to use example
+
+### Hardware Required
+
+Two ESP boards that have USB-OTG supported. One will act as USB host and the other as USB device.  
+Connect USB_D+, USB_D-, GND and +5V signals of USB host to USB device.
+
+#### Pin Assignment
+
+See common pin assignments for USB Device examples from [upper level](../../../README.md#common-pin-assignments).
+
+### Build and Flash
+
+1. Build and flash [tusb_serial_device example](../../../tusb_serial_device) to USB device board.
+2. Build this project and flash it to the USB host board, then run monitor tool to view serial output:
+
+```bash
+idf.py -p PORT flash monitor
+```
+
+(Replace PORT with the name of the serial port to use.)
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+After the flashing you should see the output at idf monitor:
+
+```
+I (256) USB-CDC: USB Host installed
+I (256) USB-CDC: Opening CDC ACM device 0x303A:0x4001
+CDC Header Descriptor:
+        bcdCDC: 1.20
+CDC Call Descriptor:
+        bmCapabilities: 0x00
+        bDataInterface: 1
+CDC ACM Descriptor:
+        bmCapabilities: 0x02
+CDC Union Descriptor:
+        bControlInterface: 0
+        bSubordinateInterface[0]: 1
+I (1666) USB-CDC: Data received
+I (1666) USB-CDC: 0x3ffc4c20   41 54 0d                                          |AT.|
+I (2666) USB-CDC: Data received
+I (2666) USB-CDC: 0x3ffc4c20   41 54 2b 47 53 4e 0d                              |AT+GSN.|
+I (3666) USB-CDC: Setting up line coding
+I (3666) USB-CDC: Line Get: Rate: 115200, Stop bits: 0, Parity: 0, Databits: 8
+I (3666) USB-CDC: Line Set: Rate: 9600, Stop bits: 1, Parity: 1, Databits: 7
+I (3666) USB-CDC: Line Get: Rate: 9600, Stop bits: 1, Parity: 1, Databits: 7
+I (3676) Example finished successfully!
+...
+
+```

+ 2 - 0
examples/peripherals/usb/host/cdc/cdc_acm_host/main/CMakeLists.txt

@@ -0,0 +1,2 @@
+idf_component_register(SRCS "usb-cdc.c"
+                    INCLUDE_DIRS ".")

+ 112 - 0
examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb-cdc.c

@@ -0,0 +1,112 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+#include "esp_err.h"
+#include "usb/usb_host.h"
+#include "usb/cdc_acm_host.h"
+
+#define EXAMPLE_USB_HOST_PRIORITY 20
+#define EXAMPLE_USB_DEVICE_VID    0x303A  // 0x303A:0x4001 (TinyUSB CDC device)
+#define EXAMPLE_USB_DEVICE_PID    0x4001
+
+static const char *TAG = "USB-CDC";
+
+/* ------------------------------- Callbacks -------------------------------- */
+static void handle_rx(uint8_t *data, size_t data_len, void *arg)
+{
+    ESP_LOGI(TAG, "Data received");
+    ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_len, ESP_LOG_INFO);
+}
+
+void usb_lib_task(void *arg)
+{
+    while (1) {
+        //Start handling system events
+        uint32_t event_flags;
+        usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
+            ESP_LOGI(TAG, "All clients deregistered");
+            ESP_ERROR_CHECK(usb_host_device_free_all());
+        }
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
+            break;
+        }
+    }
+
+    //Clean up USB Host
+    ESP_ERROR_CHECK(usb_host_uninstall());
+    vTaskDelete(NULL);
+}
+
+/* ---------------------------------- Main ---------------------------------- */
+void app_main(void)
+{
+    //Install USB Host driver. Should only be called once in entire application
+    ESP_LOGI(TAG, "Installing USB Host");
+    usb_host_config_t host_config = {
+        .intr_flags = ESP_INTR_FLAG_LEVEL1,
+    };
+    ESP_ERROR_CHECK(usb_host_install(&host_config));
+
+    // Create a task that will handle USB library events
+    xTaskCreate(usb_lib_task, "usb_lib", 4096, xTaskGetCurrentTaskHandle(), EXAMPLE_USB_HOST_PRIORITY, NULL);
+
+    ESP_LOGI(TAG, "Installing CDC-ACM driver");
+    ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
+
+    ESP_LOGI(TAG, "Opening CDC ACM device 0x%04X:0x%04X", EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID);
+    cdc_acm_dev_hdl_t cdc_dev;
+    const cdc_acm_host_device_config_t dev_config = {
+        .connection_timeout_ms = 5000,
+        .out_buffer_size = 64,
+        .user_arg = NULL,
+        .event_cb = NULL,
+        .data_cb = handle_rx
+    };
+    ESP_ERROR_CHECK(cdc_acm_host_open(EXAMPLE_USB_DEVICE_VID, EXAMPLE_USB_DEVICE_PID, 0, &dev_config, &cdc_dev));
+    assert(cdc_dev);
+    cdc_acm_host_desc_print(cdc_dev);
+    vTaskDelay(100);
+
+    // Test sending and receiving: Send AT commands, responses are handled in handle_rx callback
+    static char text1[] = "AT\r";
+    ESP_ERROR_CHECK(cdc_acm_host_data_tx_blocking(cdc_dev, (uint8_t *)text1, strlen(text1), 1000));
+    vTaskDelay(100);
+
+    static char text2[] = "AT+GSN\r";
+    ESP_ERROR_CHECK(cdc_acm_host_data_tx_blocking(cdc_dev, (uint8_t *)text2, strlen(text2), 1000));
+    vTaskDelay(100);
+
+    // Test Line Coding commands: Get current line coding, change it 9600 7N1 and read again
+    ESP_LOGI(TAG, "Setting up line coding");
+
+    cdc_acm_line_coding_t line_coding;
+    ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
+    ESP_LOGI(TAG, "Line Get: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding.dwDTERate,
+             line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
+
+    line_coding.dwDTERate = 9600;
+    line_coding.bDataBits = 7;
+    line_coding.bParityType = 1;
+    line_coding.bCharFormat = 1;
+    ESP_ERROR_CHECK(cdc_acm_host_line_coding_set(cdc_dev, &line_coding));
+    ESP_LOGI(TAG, "Line Set: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding.dwDTERate,
+             line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
+
+    ESP_ERROR_CHECK(cdc_acm_host_line_coding_get(cdc_dev, &line_coding));
+    ESP_LOGI(TAG, "Line Get: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding.dwDTERate,
+             line_coding.bCharFormat, line_coding.bParityType, line_coding.bDataBits);
+
+    ESP_ERROR_CHECK(cdc_acm_host_set_control_line_state(cdc_dev, true, false));
+
+    ESP_LOGI(TAG, "Example finished successfully!");
+}

+ 3 - 0
examples/peripherals/usb/host/cdc/common/cdc_acm_host/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRCS "cdc_acm_host.c"
+                    INCLUDE_DIRS "include"
+                    REQUIRES usb)

+ 46 - 0
examples/peripherals/usb/host/cdc/common/cdc_acm_host/README.md

@@ -0,0 +1,46 @@
+# USB Host CDC-ACM Class Driver
+
+This directory contains an implementation of a USB CDC-ACM Host Class Driver that is implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html).
+
+## Supported Devices
+
+The CDC-ACM Host driver supports the following types of CDC devices:
+
+1. CDC-ACM devices
+2. CDC-like vendor specific devices (usually found on USB to UART bridge devices)
+
+### CDC-ACM Devices
+
+The CDC-ACM Class driver supports CDC-ACM devices that meet the following requirements:
+- The device class code must be set to the CDC class `0x02` or implement Interface Association Descriptor (IAD)
+- The CDC-ACM must contain the following interfaces:
+    - A Communication Class Interface containing a management element (EP0) and may also contain a notification element (an interrupt endpoint). The driver will check this interface for CDC Functional Descriptors.
+    - A Data Class Interface with two BULK endpoints (IN and OUT). Other transfer types are not supported by the driver
+
+### CDC-Like Vendor Specific Devices
+
+The CDC-ACM Class driver supports CDC-like devices that meet the following requirements:
+- The device class code must be set to the vendor specific class code `0xFF`
+- The device needs to provide and interface containing the following endpoints:
+    - (Mandatory) Two Bulk endpoints (IN and OUT) for data
+    - (Optional) An interrupt endpoint (IN) for the notification element
+
+For CDC-like devices, users are responsible for ensuring that they only call APIs (e.g., `cdc_acm_host_send_break()`) that are supported by the target device.
+
+
+## Usage
+
+The following steps outline the typical API call pattern of the CDC-ACM Class Driver
+
+1. Install the USB Host Library via `usb_host_install()`
+2. Install the CDC-ACM driver via `cdc_acm_host_install()`
+3. Call `cdc_acm_host_open()`/`cdc_acm_host_open_vendor_specific()` to open a target CDC-ACM/CDC-like device. These functions will block until the target device is connected
+4. To transmit data, call `cdc_acm_host_data_tx_blocking()`
+5. When data is received, the driver will automatically run the receive data callback
+6. An opened device can be closed via `cdc_acm_host_close()`
+7. The CDC-ACM driver can be uninstalled via `cdc_acm_host_uninstall()`
+
+## Examples
+
+- For an example with a CDC-ACM device, refer to [cdc_acm_host](../../cdc_acm_host)
+- For an example with a CDC-like device, refer to [cdc_acm_host_bg96](../../cdc_acm_bg96)

+ 1170 - 0
examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c

@@ -0,0 +1,1170 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "esp_log.h"
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include "usb/usb_host.h"
+#include "usb/cdc_acm_host.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "freertos/event_groups.h"
+#include "esp_check.h"
+#include "esp_system.h"
+
+#define TAG "cdc_acm"
+
+// CDC devices often implement Interface Association Descriptor (IAD). Parse IAD only when
+// bDeviceClass = 0xEF (Miscellaneous Device Class), bDeviceSubClass = 0x02 (Common Class), bDeviceProtocol = 0x01 (Interface Association Descriptor)
+// @see USB Interface Association Descriptor: Device Class Code and Use Model rev 1.0, Table 1-1
+#define USB_SUBCLASS_COMMON        0x02
+#define USB_DEVICE_PROTOCOL_IAD    0x01
+
+// CDC-ACM spinlock
+static portMUX_TYPE cdc_acm_lock = portMUX_INITIALIZER_UNLOCKED;
+#define CDC_ACM_ENTER_CRITICAL()   portENTER_CRITICAL(&cdc_acm_lock)
+#define CDC_ACM_EXIT_CRITICAL()    portEXIT_CRITICAL(&cdc_acm_lock)
+
+// CDC-ACM events
+#define CDC_ACM_TEARDOWN          BIT0
+#define CDC_ACM_TEARDOWN_COMPLETE BIT1
+
+// CDC-ACM check macros
+#define CDC_ACM_CHECK(cond, ret_val) ({                                     \
+            if (!(cond)) {                                                  \
+                return (ret_val);                                           \
+            }                                                               \
+})
+
+#define CDC_ACM_CHECK_FROM_CRIT(cond, ret_val) ({                           \
+            if (!(cond)) {                                                  \
+                CDC_ACM_EXIT_CRITICAL();                                    \
+                return ret_val;                                             \
+            }                                                               \
+})
+
+// CDC-ACM driver object
+typedef struct {
+    usb_host_client_handle_t cdc_acm_client_hdl;        /*!< USB Host handle reused for all CDC-ACM devices in the system */
+    SemaphoreHandle_t open_close_mutex;
+    EventGroupHandle_t event_group;
+    SLIST_HEAD(list_dev, cdc_dev_s) cdc_devices_list;   /*!< List of open pseudo devices */
+} cdc_acm_obj_t;
+
+static cdc_acm_obj_t *p_cdc_acm_obj = NULL;
+
+/**
+ * @brief Default CDC-ACM driver configuration
+ *
+ * This configuration is used when user passes NULL to config pointer during device open.
+ */
+static const cdc_acm_host_driver_config_t cdc_acm_driver_config_default = {
+    .driver_task_stack_size = 4096,
+    .driver_task_priority = 10,
+    .xCoreID = 0
+};
+
+/**
+ * @brief USB CDC PSTN Call Descriptor
+ *
+ * @see Table 3, USB CDC-PSTN specification rev. 1.2
+ */
+typedef struct {
+    uint8_t bFunctionLength;
+    const uint8_t bDescriptorType;
+    const cdc_desc_subtype_t bDescriptorSubtype;
+    union {
+        struct {
+            uint8_t call_management:   1; // Device handles call management itself
+            uint8_t call_over_data_if: 1; // Device sends/receives call management information over Data Class interface
+            uint8_t reserved: 6;
+        };
+        uint8_t val;
+    } bmCapabilities;
+    uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management
+} __attribute__((packed)) cdc_acm_call_desc_t;
+
+/**
+ * @brief USB CDC PSTN Abstract Control Model Descriptor
+ *
+ * @see Table 4, USB CDC-PSTN specification rev. 1.2
+ */
+typedef struct {
+    uint8_t bFunctionLength;
+    const uint8_t bDescriptorType;
+    const cdc_desc_subtype_t bDescriptorSubtype;
+    union {
+        struct {
+            uint8_t feature:    1; // Device supports Set/Clear/Get_Comm_Feature requests
+            uint8_t serial:     1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications
+            uint8_t send_break: 1; // Device supports Send_Break request
+            uint8_t network:    1; // Device supports Network_Connection notification
+            uint8_t reserved:   4;
+        };
+        uint8_t val;
+    } bmCapabilities;
+} __attribute__((packed)) cdc_acm_acm_desc_t;
+
+typedef struct cdc_dev_s cdc_dev_t;
+struct cdc_dev_s{
+    usb_device_handle_t dev_hdl;          // USB device handle
+    void *cb_arg;                         // Common argument for user's callbacks (data IN and Notification)
+    struct {
+        usb_transfer_t *out_xfer;         // OUT data transfer
+        usb_transfer_t *in_xfer;          // IN data transfer
+        cdc_acm_data_callback_t in_cb;    // User's callback for async (non-blocking) data IN
+        const usb_intf_desc_t *intf_desc; // Pointer to data interface descriptor
+        SemaphoreHandle_t out_mux;        // OUT mutex
+    } data;
+
+    struct {
+        usb_transfer_t *xfer;             // IN notification transfer
+        const usb_intf_desc_t *intf_desc; // Pointer to notification interface descriptor, can be NULL if there is no notification channel in the device
+        cdc_acm_host_dev_callback_t cb;   // User's callback for device events
+    } notif;                              // Structure with Notif pipe data
+
+    usb_transfer_t *ctrl_transfer;        // CTRL (endpoint 0) transfer
+    SemaphoreHandle_t ctrl_mux;           // CTRL mutex
+    cdc_acm_uart_state_t serial_state;    // Serial State
+    cdc_comm_protocol_t comm_protocol;
+    cdc_data_protocol_t data_protocol;
+    int             num_cdc_intf_desc;    // Number of CDC Interface descriptors in following array
+    const usb_standard_desc_t **cdc_intf_desc;   // CDC Interface descriptors
+    SLIST_ENTRY(cdc_dev_s) list_entry;
+};
+
+/**
+ * @brief Notification received callback
+ *
+ * Notification (interrupt) IN transfer is submitted at the end of this function to ensure periodic poll of IN endpoint.
+ *
+ * @param[in] transfer Transfer that triggered the callback
+ */
+static void notif_xfer_cb(usb_transfer_t *transfer);
+
+/**
+ * @brief Data received callback
+ *
+ * Data (bulk) IN transfer is submitted at the end of this function to ensure continuous poll of IN endpoint.
+ *
+ * @param[in] transfer Transfer that triggered the callback
+ */
+static void in_xfer_cb(usb_transfer_t *transfer);
+
+/**
+ * @brief Data send callback
+ *
+ * Reused for bulk OUT and CTRL transfers
+ *
+ * @param[in] transfer Transfer that triggered the callback
+ */
+static void out_xfer_cb(usb_transfer_t *transfer);
+
+/**
+ * @brief USB Host Client event callback
+ *
+ * Handling of USB device connection/disconnection to/from root HUB.
+ *
+ * @param[in] event_msg Event message type
+ * @param[in] arg Caller's argument (not used in this driver)
+ */
+static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg);
+
+/**
+ * @brief Send CDC specific request
+ *
+ * Helper function that will send CDC specific request to default endpoint.
+ * Both IN and OUT requests are sent through this API, depending on the in_transfer parameter.
+ *
+ * @see  Chapter 6.2, USB CDC specification rev. 1.2
+ * @note CDC specific requests are only supported by devices that have dedicated management element.
+ *
+ * @param[in] cdc_dev Pointer to CDC device
+ * @param[in] in_transfer Direction of data phase. true: IN, false: OUT
+ * @param[in] request CDC request code
+ * @param[inout] data Pointer to data buffer. Input for OUT transfers, output for IN transfers.
+ * @param[in] data_len Length of data buffer
+ * @param[in] value Value to be set in bValue of Setup packet
+ * @return esp_err_t
+ */
+static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value);
+
+/**
+ * @brief CDC-ACM driver handling task
+ *
+ * USB host client registration and deregistration is handled here.
+ *
+ * @param[in] arg User's argument. Handle of a task that started this task.
+ */
+static void cdc_acm_client_task(void *arg)
+{
+    vTaskSuspend(NULL); // Task will be resumed from cdc_acm_host_install()
+    cdc_acm_obj_t *cdc_acm_obj = p_cdc_acm_obj; // Make local copy of the driver's handle
+    assert(cdc_acm_obj->cdc_acm_client_hdl);
+
+    // Start handling client's events
+    while (1) {
+        usb_host_client_handle_events(cdc_acm_obj->cdc_acm_client_hdl, portMAX_DELAY);
+        EventBits_t events = xEventGroupGetBits(cdc_acm_obj->event_group);
+        if (events & CDC_ACM_TEARDOWN) {
+            break;
+        }
+    }
+
+    ESP_LOGD(TAG, "Deregistering client");
+    ESP_ERROR_CHECK(usb_host_client_deregister(cdc_acm_obj->cdc_acm_client_hdl));
+    xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE);
+    vTaskDelete(NULL);
+}
+
+/**
+ * @brief Cancel transfer and reset endpoint
+ *
+ * This function will cancel ongoing transfer a reset its endpoint to ready state.
+ *
+ * @param[in] dev_hdl USB device handle
+ * @param[in] transfer Transfer to be cancelled
+ * @return esp_err_t
+ */
+static esp_err_t cdc_acm_reset_transfer_endpoint(usb_device_handle_t dev_hdl, usb_transfer_t *transfer)
+{
+    assert(dev_hdl);
+    assert(transfer);
+
+    ESP_RETURN_ON_ERROR(usb_host_endpoint_halt(dev_hdl, transfer->bEndpointAddress), TAG,);
+    ESP_RETURN_ON_ERROR(usb_host_endpoint_flush(dev_hdl, transfer->bEndpointAddress), TAG,);
+    usb_host_endpoint_clear(dev_hdl, transfer->bEndpointAddress);
+    return ESP_OK;
+}
+
+/**
+ * @brief Start CDC device
+ *
+ * After this call, USB host peripheral will continuously poll IN endpoints.
+ *
+ * @param cdc_dev
+ * @param[in] event_cb  Device event callback
+ * @param[in] in_cb     Data received callback
+ * @param[in] user_arg  Optional user's argument, that will be passed to the callbacks
+ * @return esp_err_t
+ */
+static esp_err_t cdc_acm_start(cdc_dev_t *cdc_dev, cdc_acm_host_dev_callback_t event_cb, cdc_acm_data_callback_t in_cb, void *user_arg)
+{
+    esp_err_t ret = ESP_OK;
+    assert(cdc_dev);
+
+    CDC_ACM_ENTER_CRITICAL();
+    cdc_dev->notif.cb = event_cb;
+    cdc_dev->data.in_cb = in_cb;
+    cdc_dev->cb_arg = user_arg;
+    CDC_ACM_EXIT_CRITICAL();
+
+    // Claim data interface and start polling its IN endpoint
+    ESP_GOTO_ON_ERROR(usb_host_interface_claim(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber, 0), err, TAG,);
+    ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer");
+    ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->data.in_xfer));
+
+    // If notification are supported, claim its interface and start polling its IN endpoint
+    if (cdc_dev->notif.intf_desc != NULL) {
+        if (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc) {
+            ESP_GOTO_ON_ERROR(usb_host_interface_claim(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl,
+                                                     cdc_dev->notif.intf_desc->bInterfaceNumber, 0), err, TAG,);
+        }
+        ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer");
+        ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->notif.xfer));
+    }
+
+    // Everything OK, add the device into list and return
+    CDC_ACM_ENTER_CRITICAL();
+    SLIST_INSERT_HEAD(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, list_entry);
+    CDC_ACM_EXIT_CRITICAL();
+    return ret;
+
+err:
+    usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber);
+    if (cdc_dev->notif.intf_desc != NULL) {
+        usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber);
+    }
+    return ret;
+}
+
+static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev);
+/**
+ * @brief Helper function that releases resources claimed by CDC device
+ *
+ * Close underlying USB device, free device driver memory
+ *
+ * @note All interfaces claimed by this device must be release before calling this function
+ * @param cdc_dev CDC device handle to be removed
+ */
+static void cdc_acm_device_remove(cdc_dev_t *cdc_dev)
+{
+    assert(cdc_dev);
+    cdc_acm_transfers_free(cdc_dev);
+    free(cdc_dev->cdc_intf_desc);
+    // We don't check the error code of usb_host_device_close, as the close might fail, if someone else is still using the device (not all interfaces are released)
+    usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl); // Gracefully continue on error
+    free(cdc_dev);
+}
+
+/**
+ * @brief Open USB device with requested VID/PID
+ *
+ * This function has two regular return paths:
+ * 1. USB device with matching VID/PID is already opened by this driver: allocate new CDC device on top of the already opened USB device.
+ * 2. USB device with matching VID/PID is NOT opened by this driver yet: poll USB connected devices until it is found.
+ *
+ * @note This function will block for timeout_ms, if the device is not enumerated at the moment of calling this function.
+ * @param[in] vid Vendor ID
+ * @param[in] pid Product ID
+ * @param[in] timeout_ms Connection timeout [ms]
+ * @param[out] dev CDC-ACM device
+ * @return esp_err_t
+ */
+static esp_err_t cdc_acm_find_and_open_usb_device(uint16_t vid, uint16_t pid, int timeout_ms, cdc_dev_t **dev)
+{
+    assert(p_cdc_acm_obj);
+    assert(dev);
+
+    *dev = calloc(1, sizeof(cdc_dev_t));
+    if (*dev == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    // First, check list of already opened CDC devices
+    ESP_LOGD(TAG, "Checking list of opened USB devices");
+    cdc_dev_t *cdc_dev;
+    SLIST_FOREACH(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry) {
+        const usb_device_desc_t *device_desc;
+        ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc));
+        if (device_desc->idVendor == vid && device_desc->idProduct == pid) {
+            // Return path 1:
+            (*dev)->dev_hdl = cdc_dev->dev_hdl;
+            return ESP_OK;
+        }
+    }
+
+    // Second, poll connected devices until new device is connected or timeout
+    TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
+    TimeOut_t connection_timeout;
+    vTaskSetTimeOutState(&connection_timeout);
+
+    while (true) {
+        ESP_LOGD(TAG, "Checking list of connected USB devices");
+        uint8_t dev_addr_list[10];
+        int num_of_devices;
+        ESP_ERROR_CHECK(usb_host_device_addr_list_fill(sizeof(dev_addr_list), dev_addr_list, &num_of_devices));
+
+        // Go through device address list and find the one we are looking for
+        for (int i = 0; i < num_of_devices; i++) {
+            usb_device_handle_t current_device;
+            // Open USB device
+            if (usb_host_device_open(p_cdc_acm_obj->cdc_acm_client_hdl, dev_addr_list[i], &current_device) != ESP_OK) {
+                continue; // In case we failed to open this device, continue with next one in the list
+            }
+            assert(current_device);
+            const usb_device_desc_t *device_desc;
+            ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc));
+            if (device_desc->idVendor == vid && device_desc->idProduct == pid) {
+                // Return path 2:
+                (*dev)->dev_hdl = current_device;
+                return ESP_OK;
+            }
+            usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, current_device);
+        }
+
+        if (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) != pdFALSE) {
+            break; // Timeout elapsed and the device is not connected
+        }
+        vTaskDelay(pdMS_TO_TICKS(50));
+    }
+
+    // Timeout was reached, clean-up
+    free(*dev);
+    *dev = NULL;
+    return ESP_ERR_NOT_FOUND;
+}
+
+esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config)
+{
+    CDC_ACM_CHECK(!p_cdc_acm_obj, ESP_ERR_INVALID_STATE);
+
+    // Check driver configuration, use default if NULL is passed
+    if (driver_config == NULL) {
+        driver_config = &cdc_acm_driver_config_default;
+    }
+
+    // Allocate all we need for this driver
+    esp_err_t ret;
+    cdc_acm_obj_t *cdc_acm_obj = heap_caps_calloc(1, sizeof(cdc_acm_obj_t), MALLOC_CAP_DEFAULT);
+    EventGroupHandle_t event_group = xEventGroupCreate();
+    SemaphoreHandle_t mutex = xSemaphoreCreateMutex();
+    TaskHandle_t driver_task_h = NULL;
+    xTaskCreatePinnedToCore(
+            cdc_acm_client_task, "USB-CDC", driver_config->driver_task_stack_size, NULL,
+            driver_config->driver_task_priority, &driver_task_h, driver_config->xCoreID);
+
+    if (cdc_acm_obj == NULL || driver_task_h == NULL || event_group == NULL || mutex == NULL) {
+        ret = ESP_ERR_NO_MEM;
+        goto err;
+    }
+
+    // Register USB Host client
+    usb_host_client_handle_t usb_client = NULL;
+    const usb_host_client_config_t client_config = {
+        .is_synchronous = false,
+        .max_num_event_msg = 3,
+        .async.client_event_callback = usb_event_cb,
+        .async.callback_arg = NULL
+    };
+    ESP_GOTO_ON_ERROR(usb_host_client_register(&client_config, &usb_client), err, TAG, "Failed to register USB host client");
+
+    // Initialize CDC-ACM driver structure
+    SLIST_INIT(&(cdc_acm_obj->cdc_devices_list));
+    cdc_acm_obj->event_group = event_group;
+    cdc_acm_obj->open_close_mutex = mutex;
+    cdc_acm_obj->cdc_acm_client_hdl = usb_client;
+
+    // Between 1st call of this function and following section, another task might try to install this driver:
+    // Make sure that there is only one instance of this driver in the system
+    CDC_ACM_ENTER_CRITICAL();
+    if (p_cdc_acm_obj) {
+        // Already created
+        ret = ESP_ERR_INVALID_STATE;
+        CDC_ACM_EXIT_CRITICAL();
+        goto client_err;
+    } else {
+        p_cdc_acm_obj = cdc_acm_obj;
+    }
+    CDC_ACM_EXIT_CRITICAL();
+
+    // Everything OK: Start CDC-Driver task and return
+    vTaskResume(driver_task_h);
+    return ESP_OK;
+
+client_err:
+    usb_host_client_deregister(usb_client);
+err: // Clean-up
+    free(cdc_acm_obj);
+    if (event_group) {
+        vEventGroupDelete(event_group);
+    }
+    if (driver_task_h) {
+        vTaskDelete(driver_task_h);
+    }
+    if (mutex) {
+        vSemaphoreDelete(mutex);
+    }
+    return ret;
+}
+
+esp_err_t cdc_acm_host_uninstall()
+{
+    esp_err_t ret;
+
+    CDC_ACM_ENTER_CRITICAL();
+    CDC_ACM_CHECK_FROM_CRIT(p_cdc_acm_obj, ESP_ERR_INVALID_STATE);
+    cdc_acm_obj_t *cdc_acm_obj = p_cdc_acm_obj; // Save Driver's handle to temporary handle
+    CDC_ACM_EXIT_CRITICAL();
+
+    xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); // Wait for all open/close calls to finish
+
+    CDC_ACM_ENTER_CRITICAL();
+    if (SLIST_EMPTY(&p_cdc_acm_obj->cdc_devices_list)) { // Check that device list is empty (all devices closed)
+        p_cdc_acm_obj = NULL; // NULL static driver pointer: No open/close calls form this point
+    } else {
+        ret = ESP_ERR_INVALID_STATE;
+        CDC_ACM_EXIT_CRITICAL();
+        goto unblock;
+    }
+    CDC_ACM_EXIT_CRITICAL();
+
+    // Signal to CDC task to stop, unblock it and wait for its deletion
+    xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN);
+    usb_host_client_unblock(cdc_acm_obj->cdc_acm_client_hdl);
+    ESP_GOTO_ON_FALSE(
+        xEventGroupWaitBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE, pdFALSE, pdFALSE, pdMS_TO_TICKS(100)),
+        ESP_ERR_NOT_FINISHED, unblock, TAG,);
+
+    // Free remaining resources and return
+    vEventGroupDelete(cdc_acm_obj->event_group);
+    xSemaphoreGive(cdc_acm_obj->open_close_mutex);
+    vSemaphoreDelete(cdc_acm_obj->open_close_mutex);
+    free(cdc_acm_obj);
+    return ESP_OK;
+
+unblock:
+    xSemaphoreGive(cdc_acm_obj->open_close_mutex);
+    return ret;
+}
+
+/**
+ * @brief Free USB transfers used by this device
+ *
+ * @note There can be no transfers in flight, at the moment of calling this function.
+ * @param[in] cdc_dev Pointer to CDC device
+ */
+static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev)
+{
+    assert(cdc_dev);
+    usb_host_transfer_free(cdc_dev->notif.xfer);
+    usb_host_transfer_free(cdc_dev->data.in_xfer);
+    if (cdc_dev->data.out_xfer != NULL) {
+        if (cdc_dev->data.out_xfer->context != NULL) {
+            vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->data.out_xfer->context);
+        }
+        if (cdc_dev->data.out_mux != NULL) {
+            vSemaphoreDelete(cdc_dev->data.out_mux);
+        }
+        usb_host_transfer_free(cdc_dev->data.out_xfer);
+    }
+    if (cdc_dev->ctrl_transfer != NULL) {
+        if (cdc_dev->ctrl_transfer->context != NULL) {
+            vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context);
+        }
+        if (cdc_dev->ctrl_mux != NULL) {
+            vSemaphoreDelete(cdc_dev->ctrl_mux);
+        }
+        usb_host_transfer_free(cdc_dev->ctrl_transfer);
+    }
+}
+
+/**
+ * @brief Allocate CDC transfers
+ *
+ * @param[in] cdc_dev       Pointer to CDC device
+ * @param[in] notif_ep_desc Pointer to notification EP descriptor
+ * @param[in] in_ep_desc-   Pointer to data IN EP descriptor
+ * @param[in] out_ep_desc   Pointer to data OUT EP descriptor
+ * @param[in] out_buf_len   Length of data OUT buffer
+ * @return esp_err_t
+ */
+static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_desc_t *notif_ep_desc, const usb_ep_desc_t *in_ep_desc, const usb_ep_desc_t *out_ep_desc, size_t out_buf_len)
+{
+    esp_err_t ret;
+
+    // 1. Setup notification and control transfers if they are supported
+    if (notif_ep_desc) {
+        ESP_GOTO_ON_ERROR(
+            usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(notif_ep_desc), 0, &cdc_dev->notif.xfer),
+            err, TAG,);
+        cdc_dev->notif.xfer->device_handle = cdc_dev->dev_hdl;
+        cdc_dev->notif.xfer->bEndpointAddress = notif_ep_desc->bEndpointAddress;
+        cdc_dev->notif.xfer->callback = notif_xfer_cb;
+        cdc_dev->notif.xfer->context = cdc_dev;
+        cdc_dev->notif.xfer->num_bytes = USB_EP_DESC_GET_MPS(notif_ep_desc);
+
+        usb_device_info_t dev_info;
+        ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info));
+        ESP_GOTO_ON_ERROR(
+            usb_host_transfer_alloc(dev_info.bMaxPacketSize0, 0, &cdc_dev->ctrl_transfer),
+            err, TAG,);
+        cdc_dev->ctrl_transfer->timeout_ms = 1000;
+        cdc_dev->ctrl_transfer->bEndpointAddress = 0;
+        cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl;
+        cdc_dev->ctrl_transfer->context = cdc_dev;
+        cdc_dev->ctrl_transfer->callback = out_xfer_cb;
+        cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary();
+        ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, TAG,);
+        cdc_dev->ctrl_mux = xSemaphoreCreateMutex();
+        ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG,);
+    }
+
+    // 2. Setup IN data transfer
+    ESP_GOTO_ON_ERROR(
+        usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(in_ep_desc), 0, &cdc_dev->data.in_xfer),
+        err, TAG,
+    );
+    assert(cdc_dev->data.in_xfer);
+    cdc_dev->data.in_xfer->callback = in_xfer_cb;
+    cdc_dev->data.in_xfer->num_bytes = USB_EP_DESC_GET_MPS(in_ep_desc);
+    cdc_dev->data.in_xfer->bEndpointAddress = in_ep_desc->bEndpointAddress;
+    cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl;
+    cdc_dev->data.in_xfer->context = cdc_dev;
+
+    // 3. Setup OUT bulk transfer (if it is required (out_buf_len > 0))
+    if (out_buf_len != 0) {
+        ESP_GOTO_ON_ERROR(
+            usb_host_transfer_alloc(out_buf_len, 0, &cdc_dev->data.out_xfer),
+            err, TAG,
+        );
+        assert(cdc_dev->data.out_xfer);
+        cdc_dev->data.out_xfer->device_handle = cdc_dev->dev_hdl;
+        cdc_dev->data.out_xfer->context = xSemaphoreCreateBinary();
+        ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->context, ESP_ERR_NO_MEM, err, TAG,);
+        cdc_dev->data.out_mux = xSemaphoreCreateMutex();
+        ESP_GOTO_ON_FALSE(cdc_dev->data.out_mux, ESP_ERR_NO_MEM, err, TAG,);
+        cdc_dev->data.out_xfer->bEndpointAddress = out_ep_desc->bEndpointAddress;
+        cdc_dev->data.out_xfer->callback = out_xfer_cb;
+    }
+    return ESP_OK;
+
+err:
+    cdc_acm_transfers_free(cdc_dev);
+    return ret;
+}
+
+/**
+ * @brief Find CDC interface descriptor and its endpoint descriptors
+ *
+ * @note This function is called in open procedure of CDC compliant devices only.
+ * @param[in]  cdc_dev  Pointer to CDC device
+ * @param[in]  intf_idx Index of CDC interface that should be used for this device
+ * @param[out] notif_ep Pointer to notification EP descriptor
+ * @param[out] in_ep    Pointer to data IN EP descriptor
+ * @param[out] out_ep   Pointer to data OUT EP descriptor
+ * @return esp_err_t
+ */
+static esp_err_t cdc_acm_find_intf_and_ep_desc(cdc_dev_t *cdc_dev, uint8_t intf_idx, const usb_ep_desc_t **notif_ep, const usb_ep_desc_t **in_ep, const usb_ep_desc_t **out_ep)
+{
+    bool interface_found = false;
+    const usb_config_desc_t *config_desc;
+    const usb_device_desc_t *device_desc;
+    int data_intf_idx, notif_intf_idx;
+    int desc_offset = 0;
+
+    // Get required descriptors
+    ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc));
+    ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc));
+
+    if ((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
+        (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) {
+        // This is a composite device, that uses Interface Association Descriptor
+        const usb_standard_desc_t *this_desc = (const usb_standard_desc_t *)config_desc;
+        do {
+            this_desc = usb_parse_next_descriptor_of_type(
+                this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset);
+
+            if (this_desc == NULL)
+                break; // Reached end of configuration descriptor
+
+            const usb_iad_desc_t *iad_desc = (const usb_iad_desc_t *)this_desc;
+            if (iad_desc->bFirstInterface == intf_idx) {
+                // IAD with correct interface number was found: Check Class/Subclass codes, save Interface indexes
+                assert(iad_desc->bInterfaceCount == 2);
+                assert(iad_desc->bFunctionClass == USB_CLASS_COMM);
+                assert(iad_desc->bFunctionSubClass == CDC_SUBCLASS_ACM);
+                notif_intf_idx = iad_desc->bFirstInterface;
+                data_intf_idx = iad_desc->bFirstInterface + 1;
+                interface_found = true;
+            }
+        } while (!interface_found);
+    } else if ((device_desc->bDeviceClass == USB_CLASS_COMM) && (intf_idx == 0)) {
+        // This is a Communication Device Class
+        notif_intf_idx = 0;
+        data_intf_idx = 1;
+        interface_found = true;
+    }
+
+    // Save found interfaces descriptors:
+    if (interface_found) {
+        // Notification IF and EP
+        cdc_dev->notif.intf_desc = usb_parse_interface_descriptor(config_desc, notif_intf_idx, 0, &desc_offset);
+        assert(cdc_dev->notif.intf_desc);
+
+        // CDC specific descriptors should be right after CDC-Communication interface descriptor
+        // Note: That's why we use usb_parse_next_descriptor instead of usb_parse_next_descriptor_of_type.
+        // The latter could return CDC specific descriptors that don't belong to this interface
+        const usb_standard_desc_t *cdc_desc = (usb_standard_desc_t *)cdc_dev->notif.intf_desc;
+        do {
+            cdc_desc = usb_parse_next_descriptor(cdc_desc, config_desc->wTotalLength, &desc_offset);
+            if ((cdc_desc == NULL) || (cdc_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_W_VALUE_DT_INTERFACE)))
+                break; // We found all CDC specific descriptors
+            cdc_dev->num_cdc_intf_desc++;
+            cdc_dev->cdc_intf_desc =
+                realloc(cdc_dev->cdc_intf_desc, cdc_dev->num_cdc_intf_desc * (sizeof(usb_standard_desc_t *)));
+            assert(cdc_dev->cdc_intf_desc);
+            cdc_dev->cdc_intf_desc[cdc_dev->num_cdc_intf_desc - 1] = cdc_desc;
+        } while (1);
+        *notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->notif.intf_desc, 0, config_desc->wTotalLength, &desc_offset);
+        assert(notif_ep);
+
+        // Data IF and EP
+        cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, data_intf_idx, 0, &desc_offset);
+        assert(cdc_dev->data.intf_desc);
+        int temp_offset = desc_offset;
+        for (int i = 0; i < 2; i++) {
+            const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset);
+            assert(this_ep);
+            if (USB_EP_DESC_GET_EP_DIR(this_ep)) {
+                *in_ep = this_ep;
+            } else {
+                *out_ep = this_ep;
+            }
+            desc_offset = temp_offset;
+        }
+        return ESP_OK;
+    }
+    return ESP_ERR_NOT_FOUND;
+}
+
+esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret)
+{
+    esp_err_t ret;
+    CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE);
+    CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG);
+    CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG);
+
+    xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY);
+    // Find underlying USB device
+    cdc_dev_t *cdc_dev;
+    ESP_GOTO_ON_ERROR(
+        cdc_acm_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev),
+        exit, TAG, "USB device with VID: 0x%04X, PID: 0x%04X not found", vid, pid);
+
+    // Find and save relevant interface and endpoint descriptors
+    const usb_ep_desc_t *notif_ep = NULL;
+    const usb_ep_desc_t *in_ep = NULL;
+    const usb_ep_desc_t *out_ep = NULL;
+    ESP_GOTO_ON_ERROR(
+        cdc_acm_find_intf_and_ep_desc(cdc_dev, interface_idx, &notif_ep, &in_ep, &out_ep),
+        err, TAG, "Could not find required interface");
+
+    // Check whether found Interfaces are really CDC-ACM
+    assert(cdc_dev->notif.intf_desc->bInterfaceClass == USB_CLASS_COMM);
+    assert(cdc_dev->notif.intf_desc->bInterfaceSubClass == CDC_SUBCLASS_ACM);
+    assert(cdc_dev->notif.intf_desc->bNumEndpoints == 1);
+    assert(cdc_dev->data.intf_desc->bInterfaceClass == USB_CLASS_CDC_DATA);
+    assert(cdc_dev->data.intf_desc->bNumEndpoints == 2);
+
+    // Save Communication and Data protocols
+    cdc_dev->comm_protocol = (cdc_comm_protocol_t)cdc_dev->notif.intf_desc->bInterfaceProtocol;
+    cdc_dev->data_protocol = (cdc_data_protocol_t)cdc_dev->data.intf_desc->bInterfaceProtocol;
+
+    // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle
+    ESP_GOTO_ON_ERROR(cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, out_ep, dev_config->out_buffer_size), err, TAG,);
+    ESP_GOTO_ON_ERROR(cdc_acm_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG,);
+    *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev;
+    xSemaphoreGive(p_cdc_acm_obj->open_close_mutex);
+    return ESP_OK;
+
+err:
+    cdc_acm_device_remove(cdc_dev);
+exit:
+    xSemaphoreGive(p_cdc_acm_obj->open_close_mutex);
+    *cdc_hdl_ret = NULL;
+    return ret;
+}
+
+esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret)
+{
+    esp_err_t ret;
+    CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE);
+    CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG);
+    CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG);
+
+    xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY);
+
+    // Find underlying USB device
+    cdc_dev_t *cdc_dev;
+    ESP_GOTO_ON_ERROR(
+        cdc_acm_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev),
+        exit, TAG, "USB device with VID: 0x%04X, PID: 0x%04X not found", vid, pid);
+
+    // Open procedure for CDC-ACM non-compliant devices:
+    const usb_config_desc_t *config_desc;
+    int desc_offset;
+    ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc));
+    cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, interface_num, 0, &desc_offset);
+    const int temp_offset = desc_offset; // Save this offset for later
+    assert(cdc_dev->data.intf_desc);
+
+    // The interface can have 2-3 endpoints. 2 for data and 1 optional for notifications
+    const usb_ep_desc_t *in_ep = NULL;
+    const usb_ep_desc_t *out_ep = NULL;
+    const usb_ep_desc_t *notif_ep = NULL;
+    int ep_idx = 0;
+    if (cdc_dev->data.intf_desc->bNumEndpoints == 3) {
+        // Notification channel does not have its dedicated interface (data and notif interface is the same)
+        // First endpoint of this interface is used as notification channel
+        cdc_dev->notif.intf_desc = cdc_dev->data.intf_desc;
+        notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, 0, config_desc->wTotalLength, &desc_offset);
+        desc_offset = temp_offset;
+        ep_idx++;
+    }
+
+    for (int i = ep_idx; i < ep_idx + 2; i++) {
+        const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset);
+        assert(this_ep);
+        if (USB_EP_DESC_GET_EP_DIR(this_ep)) {
+            in_ep = this_ep;
+        } else {
+            out_ep = this_ep;
+        }
+        desc_offset = temp_offset;
+    }
+
+    // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle
+    ESP_GOTO_ON_ERROR(cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, out_ep, dev_config->out_buffer_size), err, TAG, );
+    ESP_GOTO_ON_ERROR(cdc_acm_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG,);
+    *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev;
+    xSemaphoreGive(p_cdc_acm_obj->open_close_mutex);
+    return ESP_OK;
+err:
+    cdc_acm_device_remove(cdc_dev);
+exit:
+    xSemaphoreGive(p_cdc_acm_obj->open_close_mutex);
+    return ret;
+}
+
+esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl)
+{
+    CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE);
+    CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG);
+
+    xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY);
+
+    cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl;
+
+    // Cancel polling of BULK IN and INTERRUPT IN endpoints
+    cdc_dev->notif.cb = NULL;
+    cdc_dev->data.in_cb = NULL;
+    ESP_ERROR_CHECK(cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.in_xfer));
+    if (cdc_dev->notif.intf_desc != NULL) {
+        ESP_ERROR_CHECK(cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->notif.xfer));
+    }
+
+    // Release all interfaces
+    ESP_ERROR_CHECK(usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber));
+    if ((cdc_dev->notif.intf_desc != NULL) && (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) {
+        ESP_ERROR_CHECK(usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber));
+    }
+
+    CDC_ACM_ENTER_CRITICAL();
+    SLIST_REMOVE(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, cdc_dev_s, list_entry);
+    CDC_ACM_EXIT_CRITICAL();
+
+    cdc_acm_device_remove(cdc_dev);
+    xSemaphoreGive(p_cdc_acm_obj->open_close_mutex);
+    return ESP_OK;
+}
+
+void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl)
+{
+    assert(cdc_hdl);
+    cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl;
+
+    ESP_RETURN_ON_FALSE(cdc_dev->num_cdc_intf_desc > 0,, TAG, "No CDC-ACM specific descriptors found");
+
+    for (int i = 0; i < cdc_dev->num_cdc_intf_desc; i++) {
+        switch (((cdc_header_desc_t *)cdc_dev->cdc_intf_desc[i])->bDescriptorSubtype) {
+        case CDC_DESC_SUBTYPE_HEADER: {
+            cdc_header_desc_t *desc = (cdc_header_desc_t *)cdc_dev->cdc_intf_desc[i];
+            printf("CDC Header Descriptor:\n");
+            printf("\tbcdCDC: %d.%d0\n", ((desc->bcdCDC >> 8) & 0xF), ((desc->bcdCDC >> 4) & 0xF));
+            break;
+        }
+        case CDC_DESC_SUBTYPE_CALL: {
+            cdc_acm_call_desc_t *desc = (cdc_acm_call_desc_t *)cdc_dev->cdc_intf_desc[i];
+            printf("CDC Call Descriptor:\n");
+            printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val);
+            printf("\tbDataInterface: %d\n", desc->bDataInterface);
+            break;
+        }
+        case CDC_DESC_SUBTYPE_ACM: {
+            cdc_acm_acm_desc_t *desc = (cdc_acm_acm_desc_t *)cdc_dev->cdc_intf_desc[i];
+            printf("CDC ACM Descriptor:\n");
+            printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val);
+            break;
+        }
+        case CDC_DESC_SUBTYPE_UNION: {
+            cdc_union_desc_t *desc = (cdc_union_desc_t *)cdc_dev->cdc_intf_desc[i];
+            printf("CDC Union Descriptor:\n");
+            printf("\tbControlInterface: %d\n", desc->bControlInterface);
+            printf("\tbSubordinateInterface[0]: %d\n", desc->bSubordinateInterface[0]);
+            break;
+        }
+        default:
+            ESP_LOGW(TAG, "Unsupported CDC specific descriptor");
+            break;
+        }
+    }
+}
+
+/**
+ * @brief Check finished transfer status
+ *
+ * Return to on transfer completed OK.
+ * Cancel the transfer and issue user's callback in case of an error.
+ *
+ * @param[in] transfer Transfer to be checked
+ * @return true Transfer completed
+ * @return false Transfer NOT completed
+ */
+static bool cdc_acm_is_transfer_completed(usb_transfer_t *transfer)
+{
+    cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context;
+    bool completed = false;
+
+    switch (transfer->status) {
+    case USB_TRANSFER_STATUS_COMPLETED:
+        completed = true;
+        break;
+    case USB_TRANSFER_STATUS_NO_DEVICE: // User is notified about device disconnection from usb_event_cb
+    case USB_TRANSFER_STATUS_CANCELED:
+        break;
+    case USB_TRANSFER_STATUS_ERROR:
+    case USB_TRANSFER_STATUS_TIMED_OUT:
+    case USB_TRANSFER_STATUS_STALL:
+    case USB_TRANSFER_STATUS_OVERFLOW:
+    case USB_TRANSFER_STATUS_SKIPPED:
+    default:
+        // Transfer was not completed or cancelled by user. Inform user about this
+        if (cdc_dev->notif.cb) {
+            const cdc_acm_host_dev_event_data_t error_event = {
+                .type = CDC_ACM_HOST_ERROR,
+                .data.error = (int) transfer->status
+            };
+            cdc_dev->notif.cb((cdc_acm_dev_hdl_t) cdc_dev, &error_event, cdc_dev->cb_arg);
+        }
+    }
+    return completed;
+}
+
+static void in_xfer_cb(usb_transfer_t *transfer)
+{
+    ESP_LOGD("CDC_ACM", "in xfer cb");
+    cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context;
+
+    if (cdc_acm_is_transfer_completed(transfer)) {
+        if (cdc_dev->data.in_cb) {
+            cdc_dev->data.in_cb(transfer->data_buffer, transfer->actual_num_bytes, cdc_dev->cb_arg);
+        }
+
+        ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer");
+        usb_host_transfer_submit(cdc_dev->data.in_xfer);
+    }
+}
+
+static void notif_xfer_cb(usb_transfer_t *transfer)
+{
+    ESP_LOGD("CDC_ACM", "notif xfer cb");
+    cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context;
+
+    if (cdc_acm_is_transfer_completed(transfer)) {
+        cdc_notification_t *notif = (cdc_notification_t *)transfer->data_buffer;
+        switch (notif->bNotificationCode) {
+        case CDC_NOTIF_NETWORK_CONNECTION: {
+            if (cdc_dev->notif.cb) {
+                const cdc_acm_host_dev_event_data_t net_conn_event = {
+                    .type = CDC_ACM_HOST_NETWORK_CONNECTION,
+                    .data.network_connected = (bool) notif->wValue
+                };
+                cdc_dev->notif.cb((cdc_acm_dev_hdl_t) cdc_dev, &net_conn_event, cdc_dev->cb_arg);
+            }
+            break;
+        }
+        case CDC_NOTIF_SERIAL_STATE: {
+            cdc_dev->serial_state.val = *((uint16_t *)notif->Data);
+            if (cdc_dev->notif.cb) {
+                const cdc_acm_host_dev_event_data_t serial_state_event = {
+                    .type = CDC_ACM_HOST_SERIAL_STATE,
+                    .data.serial_state = cdc_dev->serial_state
+                };
+                cdc_dev->notif.cb((cdc_acm_dev_hdl_t) cdc_dev, &serial_state_event, cdc_dev->cb_arg);
+            }
+            break;
+        }
+        case CDC_NOTIF_RESPONSE_AVAILABLE: // Encapsulated commands not implemented - fallthrough
+        default:
+            ESP_LOGW("CDC_ACM", "Unsupported notification type 0x%02X", notif->bNotificationCode);
+            ESP_LOG_BUFFER_HEX("CDC_ACM", transfer->data_buffer, transfer->actual_num_bytes);
+            break;
+        }
+
+        // Start polling for new data again
+        ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer");
+        usb_host_transfer_submit(cdc_dev->notif.xfer);
+    }
+}
+
+static void out_xfer_cb(usb_transfer_t *transfer)
+{
+    ESP_LOGD("CDC_ACM", "out/ctrl xfer cb");
+    assert(transfer->context);
+    xSemaphoreGive((SemaphoreHandle_t)transfer->context);
+}
+
+static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg)
+{
+    switch (event_msg->event) {
+    case USB_HOST_CLIENT_EVENT_NEW_DEV:
+        ESP_LOGD(TAG, "New device connected");
+        break;
+    case USB_HOST_CLIENT_EVENT_DEV_GONE: {
+        ESP_LOGD(TAG, "Device suddenly disconnected");
+        // Find CDC pseudo-devices associated with this USB device and close them
+        cdc_dev_t *cdc_dev;
+        cdc_dev_t *tcdc_dev;
+        // We are using 'SAFE' version of 'SLIST_FOREACH' which enables user to close the disconnected device in the callback
+        SLIST_FOREACH_SAFE(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry, tcdc_dev) {
+            if (cdc_dev->dev_hdl == event_msg->dev_gone.dev_hdl && cdc_dev->notif.cb) {
+                // The suddenly disconnected device was opened by this driver: inform user about this
+                const cdc_acm_host_dev_event_data_t disconn_event = {
+                    .type = CDC_ACM_HOST_DEVICE_DISCONNECTED,
+                };
+                cdc_dev->notif.cb((cdc_acm_dev_hdl_t) cdc_dev, &disconn_event, cdc_dev->cb_arg);
+            }
+        }
+        break;
+    }
+    default:
+        assert(false);
+        break;
+    }
+}
+
+esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms)
+{
+    esp_err_t ret;
+    CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG);
+    cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl;
+    CDC_ACM_CHECK(data && (data_len > 0), ESP_ERR_INVALID_ARG);
+    CDC_ACM_CHECK(cdc_dev->data.out_xfer, ESP_ERR_NOT_SUPPORTED); // Device was opened as read-only.
+    CDC_ACM_CHECK(data_len <= cdc_dev->data.out_xfer->data_buffer_size, ESP_ERR_INVALID_SIZE);
+
+    // Take OUT mutex and fill the OUT transfer
+    BaseType_t taken = xSemaphoreTake(cdc_dev->data.out_mux, pdMS_TO_TICKS(timeout_ms));
+    if (taken != pdTRUE) {
+        return ESP_ERR_TIMEOUT;
+    }
+
+    ESP_LOGD("CDC_ACM", "Submitting BULK OUT transfer");
+    memcpy(cdc_dev->data.out_xfer->data_buffer, data, data_len);
+    cdc_dev->data.out_xfer->num_bytes = data_len;
+    cdc_dev->data.out_xfer->timeout_ms = timeout_ms;
+    ESP_GOTO_ON_ERROR(usb_host_transfer_submit(cdc_dev->data.out_xfer), unblock, TAG,);
+
+    // Wait for OUT transfer completion
+    taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->data.out_xfer->context, pdMS_TO_TICKS(timeout_ms));
+    if (!taken) {
+        // Reset the endpoint
+        cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.out_xfer);
+        ret = ESP_ERR_TIMEOUT;
+        goto unblock;
+    }
+
+    ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Bulk OUT transfer error");
+    ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->actual_num_bytes == data_len, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred");
+    ret = ESP_OK;
+
+unblock:
+    xSemaphoreGive(cdc_dev->data.out_mux);
+    return ret;
+}
+
+esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding)
+{
+    CDC_ACM_CHECK(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG);
+
+    ESP_RETURN_ON_ERROR(
+        send_cdc_request((cdc_dev_t *)cdc_hdl, true, CDC_REQ_GET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0),
+        TAG,);
+    ESP_LOGD(TAG, "Line Get: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding->dwDTERate,
+             line_coding->bCharFormat, line_coding->bParityType, line_coding->bDataBits);
+    return ESP_OK;
+}
+
+esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding)
+{
+    CDC_ACM_CHECK(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG);
+
+    ESP_RETURN_ON_ERROR(
+        send_cdc_request((cdc_dev_t *)cdc_hdl, false, CDC_REQ_SET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0),
+        TAG,);
+    ESP_LOGD(TAG, "Line Set: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding->dwDTERate,
+             line_coding->bCharFormat, line_coding->bParityType, line_coding->bDataBits);
+    return ESP_OK;
+}
+
+esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts)
+{
+    CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG);
+
+    const uint16_t ctrl_bitmap = (uint16_t)dtr | ((uint16_t)rts << 1);
+
+    ESP_RETURN_ON_ERROR(
+        send_cdc_request((cdc_dev_t *)cdc_hdl, false, CDC_REQ_SET_CONTROL_LINE_STATE, NULL, 0, ctrl_bitmap),
+        TAG,);
+    ESP_LOGD(TAG, "Control Line Set: DTR: %d, RTS: %d", dtr, rts);
+    return ESP_OK;
+}
+
+esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms)
+{
+    CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG);
+
+    ESP_RETURN_ON_ERROR(
+        send_cdc_request((cdc_dev_t *)cdc_hdl, false, CDC_REQ_SEND_BREAK, NULL, 0, duration_ms),
+        TAG,);
+
+    // Block until break is deasserted
+    vTaskDelay(pdMS_TO_TICKS(duration_ms + 1));
+    return ESP_OK;
+}
+
+static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value)
+{
+    esp_err_t ret;
+    CDC_ACM_CHECK(cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED);
+    CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= data_len, ESP_ERR_INVALID_SIZE);
+
+    // Take Mutex and fill the CTRL request
+    BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000));
+    if (!taken) {
+        return ESP_ERR_TIMEOUT;
+    }
+    usb_setup_packet_t *req = (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer);
+    uint8_t *start_of_data = (uint8_t *)req + sizeof(usb_setup_packet_t);
+    req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE;
+    req->bRequest = request;
+    req->wValue = value;
+    req->wIndex = cdc_dev->notif.intf_desc->bInterfaceNumber;
+    req->wLength = data_len;
+
+    if (in_transfer) {
+        req->bmRequestType |= USB_BM_REQUEST_TYPE_DIR_IN;
+    } else {
+        memcpy(start_of_data, data, data_len);
+    }
+
+    cdc_dev->ctrl_transfer->num_bytes = data_len + sizeof(usb_setup_packet_t);
+    ESP_GOTO_ON_ERROR(
+        usb_host_transfer_submit_control(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->ctrl_transfer),
+        unblock, TAG, "CTRL transfer failed");
+
+    taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(1000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second
+    if (!taken) {
+        // Transfer was not finished, error in USB LIB. Reset the endpoint
+        cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer);
+        ret = ESP_ERR_TIMEOUT;
+        goto unblock;
+    }
+
+    ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error");
+    ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == cdc_dev->ctrl_transfer->num_bytes, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred");
+
+    if (in_transfer) {
+        memcpy(data, start_of_data, data_len);
+    }
+    ret = ESP_OK;
+
+unblock:
+    xSemaphoreGive(cdc_dev->ctrl_mux);
+    return ret;
+}
+
+esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data)
+{
+    CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG);
+    cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl;
+
+    if (comm != NULL) *comm = cdc_dev->comm_protocol;
+    if (data != NULL) *data = cdc_dev->data_protocol;
+    return ESP_OK;
+}

+ 305 - 0
examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h

@@ -0,0 +1,305 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include "usb_types_cdc.h"
+#include "esp_err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct cdc_dev_s *cdc_acm_dev_hdl_t;
+
+/**
+ * @brief Line Coding structure
+ * @see Table 17, USB CDC-PSTN specification rev. 1.2
+ */
+typedef struct {
+    uint32_t dwDTERate;  // in bits per second
+    uint8_t bCharFormat; // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits
+    uint8_t bParityType; // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space
+    uint8_t bDataBits;   // 5, 6, 7, 8 or 16
+} __attribute__((packed)) cdc_acm_line_coding_t;
+
+/**
+ * @brief UART State Bitmap
+ * @see Table 31, USB CDC-PSTN specification rev. 1.2
+ */
+typedef union {
+    struct {
+        uint16_t bRxCarrier : 1;  // State of receiver carrier detection mechanism of device. This signal corresponds to V.24 signal 109 and RS-232 signal DCD.
+        uint16_t bTxCarrier : 1;  // State of transmission carrier. This signal corresponds to V.24 signal 106 and RS-232 signal DSR.
+        uint16_t bBreak : 1;      // State of break detection mechanism of the device.
+        uint16_t bRingSignal : 1; // State of ring signal detection of the device.
+        uint16_t bFraming : 1;    // A framing error has occurred.
+        uint16_t bParity : 1;     // A parity error has occurred.
+        uint16_t bOverRun : 1;    // Received data has been discarded due to overrun in the device.
+        uint16_t reserved : 9;
+    };
+    uint16_t val;
+} cdc_acm_uart_state_t;
+
+/**
+ * @brief CDC-ACM Device Event types to upper layer
+ *
+ */
+typedef enum {
+    CDC_ACM_HOST_ERROR,
+    CDC_ACM_HOST_SERIAL_STATE,
+    CDC_ACM_HOST_NETWORK_CONNECTION,
+    CDC_ACM_HOST_DEVICE_DISCONNECTED
+} cdc_acm_host_dev_event_t;
+
+/**
+ * @brief CDC-ACM Device Event data structure
+ *
+ */
+typedef struct {
+    cdc_acm_host_dev_event_t type;
+    union {
+        int error;                         // Error code from USB Host
+        cdc_acm_uart_state_t serial_state; // Serial (UART) state
+        bool network_connected;            // Network connection event
+    } data;
+} cdc_acm_host_dev_event_data_t;
+
+/**
+ * @brief Data receive callback type
+ */
+typedef void (*cdc_acm_data_callback_t)(uint8_t* data, size_t data_len, void *user_arg);
+
+/**
+ * @brief Device event callback type
+ * @see cdc_acm_host_dev_event_t
+ */
+typedef void (*cdc_acm_host_dev_callback_t)(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx);
+
+/**
+ * @brief Configuration structure of USB Host CDC-ACM driver
+ *
+ */
+typedef struct {
+    size_t driver_task_stack_size;         /**< Stack size of the driver's task */
+    unsigned driver_task_priority;         /**< Priority of the driver's task */
+    int  xCoreID;                          /**< Core affinity of the driver's task */
+} cdc_acm_host_driver_config_t;
+
+/**
+ * @brief Configuration structure of CDC-ACM device
+ *
+ */
+typedef struct {
+    uint32_t connection_timeout_ms;       /**< Timeout for USB device connection in [ms] */
+    size_t out_buffer_size;               /**< Maximum size of USB bulk out transfer, set to 0 for read-only devices */
+    cdc_acm_host_dev_callback_t event_cb; /**< Device's event callback function. Can be NULL */
+    cdc_acm_data_callback_t data_cb;      /**< Device's data RX callback function. Can be NULL for write-only devices */
+    void *user_arg;                       /**< User's argument that will be passed to the callbacks */
+} cdc_acm_host_device_config_t;
+
+/**
+ * @brief Install CDC-ACM driver
+ *
+ * - USB Host Library must already be installed before calling this function (via usb_host_install())
+ * - This function should be called before calling any other CDC driver functions
+ *
+ * @param[in] driver_config Driver configuration structure. If set to NULL, a default configuration will be used.
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config);
+
+/**
+ * @brief Uninstall CDC-ACM driver
+ *
+ * - Users must ensure that all CDC devices must be closed via cdc_acm_host_close() before calling this function
+ *
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_uninstall(void);
+
+/**
+ * @brief Open CDC-ACM compliant device
+ *
+ * CDC-ACM compliant device must contain either an Interface Association Descriptor or CDC-Union descriptor,
+ * which are used for the driver's configuration.
+ *
+ * @param[in] vid           Device's Vendor ID
+ * @param[in] pid           Device's Product ID
+ * @param[in] interface_idx Index of device's interface used for CDC-ACM communication
+ * @param[in] dev_config    Configuration structure of the device
+ * @param[out] cdc_hdl_ret  CDC device handle
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret);
+
+/**
+ * @brief Open CDC-ACM non-compliant device
+ *
+ * CDC-ACM non-compliant device acts as CDC-ACM device but doesn't support all its features.
+ * User must provide the interface index that will be used (zero for non-composite devices).
+ *
+ * @param[in] vid           Device's Vendor ID
+ * @param[in] pid           Device's Product ID
+ * @param[in] interface_idx Index of device's interface used for CDC-ACM like communication
+ * @param[in] dev_config    Configuration structure of the device
+ * @param[out] cdc_hdl_ret  CDC device handle
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret);
+
+/**
+ * @brief Close CDC device and release its resources
+ *
+ * @note All in-flight transfers will be prematurely canceled.
+ * @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl);
+
+/**
+ * @brief Transmit data - blocking mode
+ *
+ * @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
+ * @param[in] data       Data to be sent
+ * @param[in] data_len   Data length
+ * @param[in] timeout_ms Timeout in [ms]
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms);
+
+/**
+ * @brief SetLineCoding function
+ *
+ * @see Chapter 6.3.10, USB CDC-PSTN specification rev. 1.2
+ *
+ * @param     cdc_hdl     CDC handle obtained from cdc_acm_host_open()
+ * @param[in] line_coding Line Coding structure
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding);
+
+/**
+ * @brief GetLineCoding function
+ *
+ * @see Chapter 6.3.11, USB CDC-PSTN specification rev. 1.2
+ *
+ * @param      cdc_hdl     CDC handle obtained from cdc_acm_host_open()
+ * @param[out] line_coding Line Coding structure to be filled
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding);
+
+/**
+ * @brief SetControlLineState function
+ *
+ * @see Chapter 6.3.12, USB CDC-PSTN specification rev. 1.2
+ *
+ * @param     cdc_hdl CDC handle obtained from cdc_acm_host_open()
+ * @param[in] dtr     Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready.
+ * @param[in] rts     Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send.
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts);
+
+/**
+ * @brief SendBreak function
+ *
+ * This function will block until the duration_ms has passed.
+ *
+ * @see Chapter 6.3.13, USB CDC-PSTN specification rev. 1.2
+ *
+ * @param     cdc_hdl     CDC handle obtained from cdc_acm_host_open()
+ * @param[in] duration_ms Duration of the Break signal in [ms]
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms);
+
+/**
+ * @brief Print CDC-ACM specific descriptors
+ *
+ * Descriptors are printed in human readable format to stdout.
+ * Intended for debugging and for CDC-ACM compliant devices only.
+ *
+ * @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
+ */
+void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl);
+
+/**
+ * @brief Get protocols defined in USB-CDC interface descriptors
+ *
+ * @param cdc_hdl   CDC handle obtained from cdc_acm_host_open()
+ * @param[out] comm Communication protocol
+ * @param[out] data Data protocol
+ * @return esp_err_t
+ */
+esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data);
+
+#ifdef __cplusplus
+}
+class CdcAcmDevice
+{
+public:
+    // Operators
+    CdcAcmDevice() : cdc_hdl(NULL){};
+    ~CdcAcmDevice()
+    {
+        // Close CDC-ACM device, if it wasn't explicitly closed
+        if (this->cdc_hdl != NULL) {
+            this->close();
+        }
+    }
+
+    inline esp_err_t tx_blocking(uint8_t *data, size_t len, uint32_t timeout_ms = 100)
+    {
+        return cdc_acm_host_data_tx_blocking(this->cdc_hdl, data, len, timeout_ms);
+    }
+
+    inline esp_err_t open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t* dev_config)
+    {
+        return cdc_acm_host_open(vid, pid, interface_idx, dev_config, &this->cdc_hdl);
+    }
+
+    inline esp_err_t open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t* dev_config)
+    {
+        return cdc_acm_host_open_vendor_specific(vid, pid, interface_idx, dev_config, &this->cdc_hdl);
+    }
+
+    inline void close()
+    {
+        cdc_acm_host_close(this->cdc_hdl);
+        this->cdc_hdl = NULL;
+    }
+
+    inline esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding)
+    {
+        return cdc_acm_host_line_coding_get(this->cdc_hdl, line_coding);
+    }
+
+    inline esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding)
+    {
+        return cdc_acm_host_line_coding_set(this->cdc_hdl, line_coding);
+    }
+
+    inline esp_err_t set_control_line_state(bool dtr, bool rts)
+    {
+        return cdc_acm_host_set_control_line_state(this->cdc_hdl, dtr, rts);
+    }
+
+    inline esp_err_t send_break(uint16_t duration_ms)
+    {
+        return cdc_acm_host_send_break(this->cdc_hdl, duration_ms);
+    }
+
+private:
+    CdcAcmDevice(const CdcAcmDevice &Copy);
+    CdcAcmDevice &operator= (const CdcAcmDevice &Copy);
+    bool operator== (const CdcAcmDevice &param) const;
+    bool operator!= (const CdcAcmDevice &param) const;
+    cdc_acm_dev_hdl_t cdc_hdl;
+};
+#endif

+ 206 - 0
examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/usb_types_cdc.h

@@ -0,0 +1,206 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+#include <inttypes.h>
+
+/**
+ * @brief USB CDC Descriptor Subtypes
+ *
+ * @see Table 13, USB CDC specification rev. 1.2
+ */
+typedef enum {
+    CDC_DESC_SUBTYPE_HEADER = 0x00,             // Header Functional Descriptor
+    CDC_DESC_SUBTYPE_CALL = 0x01,               // Call Management Functional Descriptor
+    CDC_DESC_SUBTYPE_ACM = 0x02,                // Abstract Control Management Functional Descriptor
+    CDC_DESC_SUBTYPE_DLM = 0x03,                // Direct Line Management Functional Descriptor
+    CDC_DESC_SUBTYPE_TEL_RINGER = 0x04,         // Telephone Ringer Functional Descriptor
+    CDC_DESC_SUBTYPE_TEL_CLSR = 0x05,           // Telephone Call and Line State Reporting Capabilities Functional Descriptor
+    CDC_DESC_SUBTYPE_UNION = 0x06,              // Union Functional Descriptor
+    CDC_DESC_SUBTYPE_COUNTRY = 0x07,            // Country Selection Functional Descriptor
+    CDC_DESC_SUBTYPE_TEL_MODE = 0x08,           // Telephone Operational Modes Functional Descriptor
+    CDC_DESC_SUBTYPE_TERMINAL = 0x09,           // USB Terminal
+    CDC_DESC_SUBTYPE_NCHT = 0x0A,               // Network Channel Terminal
+    CDC_DESC_SUBTYPE_PROTOCOL = 0x08,           // Protocol Unit
+    CDC_DESC_SUBTYPE_EXTENSION = 0x0C,          // Extension Unit
+    CDC_DESC_SUBTYPE_MULTI_CHAN = 0x0D,         // Multi-Channel Management Functional Descriptor
+    CDC_DESC_SUBTYPE_CAPI = 0x0E,               // CAPI Control
+    CDC_DESC_SUBTYPE_ETH = 0x0F,                // Ethernet Networking
+    CDC_DESC_SUBTYPE_ATM = 0x10,                // ATM Networking
+    CDC_DESC_SUBTYPE_WHANDSET = 0x11,           // Wireless Handset Control Model Functional Descriptor
+    CDC_DESC_SUBTYPE_MDLM = 0x12,               // Mobile Direct Line Model
+    CDC_DESC_SUBTYPE_MDLM_DETAIL = 0x13,        // MDLM Detail
+    CDC_DESC_SUBTYPE_DMM = 0x14,                // Device Management Model
+    CDC_DESC_SUBTYPE_OBEX = 0x15,               // OBEX Functional
+    CDC_DESC_SUBTYPE_COMMAND_SET = 0x16,        // Command Set
+    CDC_DESC_SUBTYPE_COMMAND_SET_DETAIL = 0x17, // Command Set Detail Functional Descriptor
+    CDC_DESC_SUBTYPE_TEL_CM = 0x18,             // Telephone Control Model Functional Descriptor
+    CDC_DESC_SUBTYPE_OBEX_SERVICE = 0x19,       // OBEX Service Identifier Functional Descriptor
+    CDC_DESC_SUBTYPE_NCM = 0x1A                 // NCM Functional Descriptor
+} __attribute__((packed)) cdc_desc_subtype_t;
+
+/**
+ * @brief USB CDC Subclass codes
+ *
+ * @see Table 4, USB CDC specification rev. 1.2
+ */
+typedef enum {
+    CDC_SUBCLASS_DLCM = 0x01,    // Direct Line Control Model
+    CDC_SUBCLASS_ACM = 0x02,     // Abstract Control Model
+    CDC_SUBCLASS_TCM = 0x03,     // Telephone Control Model
+    CDC_SUBCLASS_MCHCM = 0x04,   // Multi-Channel Control Model
+    CDC_SUBCLASS_CAPI = 0x05,    // CAPI Control Model
+    CDC_SUBCLASS_ECM = 0x06,     // Ethernet Networking Control Model
+    CDC_SUBCLASS_ATM = 0x07,     // ATM Networking Model
+    CDC_SUBCLASS_HANDSET = 0x08, // Wireless Handset Control Model
+    CDC_SUBCLASS_DEV_MAN = 0x09, // Device Management
+    CDC_SUBCLASS_MOBILE = 0x0A,  // Mobile Direct Line Model
+    CDC_SUBCLASS_OBEX = 0x0B,    // OBEX
+    CDC_SUBCLASS_EEM = 0x0C,     // Ethernet Emulation Model
+    CDC_SUBCLASS_NCM = 0x0D      // Network Control Model
+} __attribute__((packed)) cdc_subclass_t;
+
+/**
+ * @brief USB CDC Communications Protocol Codes
+ *
+ * @see Table 5, USB CDC specification rev. 1.2
+ */
+typedef enum {
+    CDC_COMM_PROTOCOL_NONE = 0x00,   // No class specific protocol required
+    CDC_COMM_PROTOCOL_V250 = 0x01,   // AT Commands: V.250 etc
+    CDC_COMM_PROTOCOL_PCAA = 0x02,   // AT Commands defined by PCCA-101
+    CDC_COMM_PROTOCOL_PCAA_A = 0x03, // AT Commands defined by PCAA-101 & Annex O
+    CDC_COMM_PROTOCOL_GSM = 0x04,    // AT Commands defined by GSM 07.07
+    CDC_COMM_PROTOCOL_3GPP = 0x05,   // AT Commands defined by 3GPP 27.007
+    CDC_COMM_PROTOCOL_TIA = 0x06,    // AT Commands defined by TIA for CDMA
+    CDC_COMM_PROTOCOL_EEM = 0x07,    // Ethernet Emulation Model
+    CDC_COMM_PROTOCOL_EXT = 0xFE,    // External Protocol: Commands defined by Command Set Functional Descriptor
+    CDC_COMM_PROTOCOL_VENDOR = 0xFF  // Vendor-specific
+} __attribute__((packed)) cdc_comm_protocol_t;
+
+/**
+ * @brief USB CDC Data Protocol Codes
+ *
+ * @see Table 7, USB CDC specification rev. 1.2
+ */
+typedef enum {
+    CDC_DATA_PROTOCOL_NONE = 0x00,   // No class specific protocol required
+    CDC_DATA_PROTOCOL_NCM = 0x01,    // Network Transfer Block
+    CDC_DATA_PROTOCOL_I430 = 0x30,   // Physical interface protocol for ISDN BRI
+    CDC_DATA_PROTOCOL_HDLC = 0x31,   // HDLC
+    CDC_DATA_PROTOCOL_Q921M = 0x50,  // Management protocol for Q.921 data link protocol
+    CDC_DATA_PROTOCOL_Q921  = 0x51,  // Data link protocol for Q.931
+    CDC_DATA_PROTOCOL_Q921TM = 0x52, // TEI-multiplexor for Q.921 data link protocol
+    CDC_DATA_PROTOCOL_V42BIS = 0x90, // Data compression procedures
+    CDC_DATA_PROTOCOL_Q931 = 0x91,   // Euro-ISDN protocol control
+    CDC_DATA_PROTOCOL_V120 = 0x92,   // V.24 rate adaptation to ISDN
+    CDC_DATA_PROTOCOL_CAPI = 0x93,   // CAPI Commands
+    CDC_DATA_PROTOCOL_VENDOR = 0xFF  // Vendor-specific
+} __attribute__((packed)) cdc_data_protocol_t;
+
+/**
+ * @brief USB CDC Request Codes
+ *
+ * @see Table 19, USB CDC specification rev. 1.2
+ */
+typedef enum {
+    CDC_REQ_SEND_ENCAPSULATED_COMMAND = 0x00,
+    CDC_REQ_GET_ENCAPSULATED_RESPONSE = 0x01,
+    CDC_REQ_SET_COMM_FEATURE = 0x02,
+    CDC_REQ_GET_COMM_FEATURE = 0x03,
+    CDC_REQ_CLEAR_COMM_FEATURE = 0x04,
+    CDC_REQ_SET_AUX_LINE_STATE = 0x10,
+    CDC_REQ_SET_HOOK_STATE = 0x11,
+    CDC_REQ_PULSE_SETUP = 0x12,
+    CDC_REQ_SEND_PULSE = 0x13,
+    CDC_REQ_SET_PULSE_TIME = 0x14,
+    CDC_REQ_RING_AUX_JACK = 0x15,
+    CDC_REQ_SET_LINE_CODING = 0x20,
+    CDC_REQ_GET_LINE_CODING = 0x21,
+    CDC_REQ_SET_CONTROL_LINE_STATE = 0x22,
+    CDC_REQ_SEND_BREAK = 0x23,
+    CDC_REQ_SET_RINGER_PARMS = 0x30,
+    CDC_REQ_GET_RINGER_PARMS = 0x31,
+    CDC_REQ_SET_OPERATION_PARMS = 0x32,
+    CDC_REQ_GET_OPERATION_PARMS = 0x33,
+    CDC_REQ_SET_LINE_PARMS = 0x34,
+    CDC_REQ_GET_LINE_PARMS = 0x35,
+    CDC_REQ_DIAL_DIGITS = 0x36,
+    CDC_REQ_SET_UNIT_PARAMETER = 0x37,
+    CDC_REQ_GET_UNIT_PARAMETER = 0x38,
+    CDC_REQ_CLEAR_UNIT_PARAMETER = 0x39,
+    CDC_REQ_GET_PROFILE = 0x3A,
+    CDC_REQ_SET_ETHERNET_MULTICAST_FILTERS = 0x40,
+    CDC_REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41,
+    CDC_REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42,
+    CDC_REQ_SET_ETHERNET_PACKET_FILTER = 0x43,
+    CDC_REQ_GET_ETHERNET_STATISTIC = 0x44,
+    CDC_REQ_SET_ATM_DATA_FORMAT = 0x50,
+    CDC_REQ_GET_ATM_DEVICE_STATISTICS = 0x51,
+    CDC_REQ_SET_ATM_DEFAULT_VC = 0x52,
+    CDC_REQ_GET_ATM_VC_STATISTICS = 0x53,
+    CDC_REQ_GET_NTB_PARAMETERS = 0x80,
+    CDC_REQ_GET_NET_ADDRESS = 0x81,
+    CDC_REQ_SET_NET_ADDRESS = 0x82,
+    CDC_REQ_GET_NTB_FORMAT = 0x83,
+    CDC_REQ_SET_NTB_FORMAT = 0x84,
+    CDC_REQ_GET_NTB_INPUT_SIZE = 0x85,
+    CDC_REQ_SET_NTB_INPUT_SIZE = 0x86,
+    CDC_REQ_GET_MAX_DATAGRAM_SIZE = 0x87,
+    CDC_REQ_SET_MAX_DATAGRAM_SIZE = 0x88,
+    CDC_REQ_GET_CRC_MODE = 0x89,
+    CDC_REQ_SET_CRC_MODE = 0x8A
+} __attribute__((packed)) cdc_request_code_t;
+
+/**
+ * @brief USB CDC Notification Codes
+ *
+ * @see Table 20, USB CDC specification rev. 1.2
+ */
+typedef enum {
+    CDC_NOTIF_NETWORK_CONNECTION = 0x00,
+    CDC_NOTIF_RESPONSE_AVAILABLE = 0x01,
+    CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08,
+    CDC_NOTIF_RING_DETECT = 0x09,
+    CDC_NOTIF_SERIAL_STATE = 0x20,
+    CDC_NOTIF_CALL_STATE_CHANGE = 0x28,
+    CDC_NOTIF_LINE_STATE_CHANGE = 0x29,
+    CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A
+} __attribute__((packed)) cdc_notification_code_t;
+
+typedef struct {
+    uint8_t bmRequestType;
+    cdc_notification_code_t bNotificationCode;
+    uint16_t wValue;
+    uint16_t wIndex;
+    uint16_t wLength;
+    uint8_t Data[];
+} __attribute__((packed)) cdc_notification_t;
+
+/**
+ * @brief USB CDC Header Functional Descriptor
+ *
+ * @see Table 15, USB CDC specification rev. 1.2
+ */
+typedef struct {
+    uint8_t bFunctionLength;
+    const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05
+    const cdc_desc_subtype_t bDescriptorSubtype;
+    uint16_t bcdCDC; // CDC version as binary-coded decimal. This driver is written for version 1.2
+} __attribute__((packed)) cdc_header_desc_t;
+
+/**
+ * @brief USB CDC Union Functional Descriptor
+ *
+ * @see Table 16, USB CDC specification rev. 1.2
+ */
+typedef struct {
+    uint8_t bFunctionLength;
+    const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05
+    const cdc_desc_subtype_t bDescriptorSubtype;
+    const uint8_t bControlInterface; // Master/controlling interface
+    uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces
+} __attribute__((packed)) cdc_union_desc_t;

+ 3 - 0
examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRCS "test_cdc_acm_host.c"
+                       INCLUDE_DIRS "."
+                       REQUIRES cdc_acm_host unity)

+ 375 - 0
examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c

@@ -0,0 +1,375 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "soc/soc_caps.h"
+#if SOC_USB_OTG_SUPPORTED
+
+#include <stdio.h>
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+#include "esp_err.h"
+
+#include "usb/usb_host.h"
+#include "usb/cdc_acm_host.h"
+#include <string.h>
+
+#include "esp_intr_alloc.h"
+
+#include "unity.h"
+#include "soc/usb_wrap_struct.h"
+
+static uint8_t tx_buf[] = "HELLO";
+static uint8_t tx_buf2[] = "WORLD";
+static int nb_of_responses;
+static int nb_of_responses2;
+
+void test_usb_force_conn_state(bool connected, TickType_t delay_ticks)
+{
+    if (delay_ticks > 0) {
+        //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
+        vTaskDelay(delay_ticks);
+    }
+    usb_wrap_dev_t *wrap = &USB_WRAP;
+    if (connected) {
+        //Disable test mode to return to previous internal PHY configuration
+        wrap->test_conf.test_enable = 0;
+    } else {
+        /*
+        Mimic a disconnection by using the internal PHY's test mode.
+        Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0,
+        this will look like a disconnection.
+        */
+        wrap->test_conf.val = 0;
+        wrap->test_conf.test_usb_wrap_oe = 1;
+        wrap->test_conf.test_enable = 1;
+    }
+}
+
+void usb_lib_task(void *arg)
+{
+    // Install USB Host driver. Should only be called once in entire application
+    const usb_host_config_t host_config = {
+        .intr_flags = ESP_INTR_FLAG_LEVEL1,
+    };
+    TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config));
+    printf("USB Host installed\n");
+    xTaskNotifyGive(arg);
+
+    while (1) {
+        // Start handling system events
+        uint32_t event_flags;
+        usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
+            printf("No more clients: clean up\n");
+            // The device should not have been freed yet, so we expect an ESP_ERR_NOT_FINISHED
+            TEST_ASSERT_EQUAL(ESP_ERR_NOT_FINISHED, usb_host_device_free_all());
+        }
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
+            printf("All free: uninstall USB lib\n");
+            break;
+        }
+    }
+
+    // Clean up USB Host
+    vTaskDelay(10); // Short delay to allow clients clean-up
+    usb_host_lib_handle_events(0, NULL); // Make sure there are now pending events
+    TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall());
+    vTaskDelete(NULL);
+}
+
+void test_install_cdc_driver(void)
+{
+    // Create a task that will handle USB library events
+    TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(usb_lib_task, "usb_lib", 4*4096, xTaskGetCurrentTaskHandle(), 10, NULL));
+    ulTaskNotifyTake(false, 1000);
+
+    printf("Installing CDC-ACM driver\n");
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_install(NULL));
+}
+
+/* ------------------------------- Callbacks -------------------------------- */
+static void handle_rx(uint8_t *data, size_t data_len, void *arg)
+{
+    printf("Data received\n");
+    nb_of_responses++;
+    TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len);
+}
+
+static void handle_rx2(uint8_t *data, size_t data_len, void *arg)
+{
+    printf("Data received 2\n");
+    nb_of_responses2++;
+    TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len);
+}
+
+static void notif_cb(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
+{
+    switch (event->type) {
+    case CDC_ACM_HOST_ERROR:
+        printf("Error event %d\n", event->data.error);
+        break;
+    case CDC_ACM_HOST_SERIAL_STATE:
+        break;
+    case CDC_ACM_HOST_NETWORK_CONNECTION:
+        break;
+    case CDC_ACM_HOST_DEVICE_DISCONNECTED:
+        printf("Disconnection event\n");
+        TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_hdl));
+        xTaskNotifyGive(user_ctx);
+        break;
+    default:
+        assert(false);
+    }
+}
+
+/* Basic test to check CDC communication:
+ * open/read/write/close device
+ * CDC-ACM specific commands: set/get_line_coding, set_control_line_state */
+TEST_CASE("USB Host CDC-ACM driver: Basic test", "[cdc_acm][ignore]")
+{
+    nb_of_responses = 0;
+    cdc_acm_dev_hdl_t cdc_dev = NULL;
+
+    test_install_cdc_driver();
+
+    const cdc_acm_host_device_config_t dev_config = {
+        .connection_timeout_ms = 500,
+        .out_buffer_size = 64,
+        .event_cb = notif_cb,
+        .data_cb = handle_rx,
+        .user_arg = tx_buf,
+    };
+
+    printf("Opening CDC-ACM device\n");
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
+    TEST_ASSERT_NOT_NULL(cdc_dev);
+    cdc_acm_host_desc_print(cdc_dev);
+    vTaskDelay(100);
+
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
+    vTaskDelay(100); // Wait until responses are processed
+
+    // We sent two messages, should get two responses
+    TEST_ASSERT_EQUAL(2, nb_of_responses);
+
+    cdc_acm_line_coding_t line_coding_get;
+    const cdc_acm_line_coding_t line_coding_set = {
+     .dwDTERate = 9600,
+     .bDataBits = 7,
+     .bParityType = 1,
+     .bCharFormat = 1,
+    };
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_set(cdc_dev, &line_coding_set));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get));
+    TEST_ASSERT_EQUAL_MEMORY(&line_coding_set, &line_coding_get, sizeof(cdc_acm_line_coding_t));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false));
+
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
+
+    vTaskDelay(20); //Short delay to allow task to be cleaned up
+}
+
+/* Test communication with multiple CDC-ACM devices from one thread */
+TEST_CASE("USB Host CDC-ACM driver: Multiple devices test", "[cdc_acm][ignore]")
+{
+    nb_of_responses = 0;
+    nb_of_responses2 = 0;
+
+    test_install_cdc_driver();
+
+    printf("Opening 2 CDC-ACM devices\n");
+    cdc_acm_dev_hdl_t cdc_dev1, cdc_dev2;
+    cdc_acm_host_device_config_t dev_config = {
+        .connection_timeout_ms = 1000,
+        .out_buffer_size = 64,
+        .event_cb = notif_cb,
+        .data_cb = handle_rx,
+        .user_arg = tx_buf,
+    };
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev1)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
+    dev_config.data_cb = handle_rx2;
+    dev_config.user_arg = tx_buf2;
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 2, &dev_config, &cdc_dev2)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
+    TEST_ASSERT_NOT_NULL(cdc_dev1);
+    TEST_ASSERT_NOT_NULL(cdc_dev2);
+
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev1, tx_buf, sizeof(tx_buf), 1000));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev2, tx_buf2, sizeof(tx_buf2), 1000));
+
+    vTaskDelay(100); // Wait for RX callbacks
+
+    // We sent two messages, should get two responses
+    TEST_ASSERT_EQUAL(1, nb_of_responses);
+    TEST_ASSERT_EQUAL(1, nb_of_responses2);
+
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev1));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev2));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
+
+    //Short delay to allow task to be cleaned up
+    vTaskDelay(20);
+}
+
+#define MULTIPLE_THREADS_TRANSFERS_NUM 5
+#define MULTIPLE_THREADS_TASKS_NUM 4
+void tx_task(void *arg)
+{
+    cdc_acm_dev_hdl_t cdc_dev = (cdc_acm_dev_hdl_t) arg;
+    // Send multiple transfers to make sure that some of them will run at the same time
+    for (int i = 0; i < MULTIPLE_THREADS_TRANSFERS_NUM; i++) {
+        // BULK endpoints
+        TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
+
+        // CTRL endpoints
+        cdc_acm_line_coding_t line_coding_get;
+        TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get));
+        TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false));
+    }
+    vTaskDelete(NULL);
+}
+
+/**
+ * @brief Multiple threads test
+ *
+ * In this test, one CDC device is accessed from multiple threads.
+ * It has to be opened/closed just once, though.
+ */
+TEST_CASE("USB Host CDC-ACM driver: Multiple threads test", "[cdc_acm][ignore]")
+{
+    nb_of_responses = 0;
+    cdc_acm_dev_hdl_t cdc_dev;
+    test_install_cdc_driver();
+
+    const cdc_acm_host_device_config_t dev_config = {
+        .connection_timeout_ms = 5000,
+        .out_buffer_size = 64,
+        .event_cb = notif_cb,
+        .data_cb = handle_rx,
+        .user_arg = tx_buf,
+    };
+
+    printf("Opening CDC-ACM device\n");
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device)
+    TEST_ASSERT_NOT_NULL(cdc_dev);
+
+    // Create two tasks that will try to access cdc_dev
+    for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) {
+        TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(tx_task, "CDC TX", 4096, cdc_dev, i + 3, NULL));
+    }
+
+    // Wait until all tasks finish
+    vTaskDelay(pdMS_TO_TICKS(500));
+    TEST_ASSERT_EQUAL(MULTIPLE_THREADS_TASKS_NUM * MULTIPLE_THREADS_TRANSFERS_NUM, nb_of_responses);
+
+    // Clean-up
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
+    vTaskDelay(20);
+}
+
+/* Test CDC driver reaction to USB device sudden disconnection */
+TEST_CASE("USB Host CDC-ACM driver: Sudden disconnection test", "[cdc_acm][ignore]")
+{
+    test_install_cdc_driver();
+
+    cdc_acm_dev_hdl_t cdc_dev;
+    cdc_acm_host_device_config_t dev_config = {
+        .connection_timeout_ms = 1000,
+        .out_buffer_size = 64,
+        .event_cb = notif_cb,
+        .data_cb = handle_rx
+    };
+    dev_config.user_arg = xTaskGetCurrentTaskHandle();
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
+    TEST_ASSERT_NOT_NULL(cdc_dev);
+
+    test_usb_force_conn_state(false, pdMS_TO_TICKS(10));
+    // Notify will succeed only if CDC_ACM_HOST_DEVICE_DISCONNECTED notification was generated
+    TEST_ASSERT_EQUAL(1, ulTaskNotifyTake(false, pdMS_TO_TICKS(100)));
+
+    test_usb_force_conn_state(true, 0); // Switch back to real PHY
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
+    vTaskDelay(20); //Short delay to allow task to be cleaned up
+}
+
+/**
+ * @brief CDC-ACM error handling test
+ *
+ * There are multiple erroneous scenarios checked in this test:
+ *
+ * -# Install CDC-ACM driver without USB Host
+ * -# Open device without installed driver
+ * -# Uninstall driver before installing it
+ * -# Open non-existent device
+ * -# Open the same device twice
+ * -# Uninstall driver with open devices
+ * -# Send data that is too large
+ * -# Send unsupported CDC request
+ * -# Write to read-only device
+ */
+TEST_CASE("USB Host CDC-ACM driver: Error handling", "[cdc_acm][ignore]")
+{
+    cdc_acm_dev_hdl_t cdc_dev;
+    cdc_acm_host_device_config_t dev_config = {
+        .connection_timeout_ms = 500,
+        .out_buffer_size = 64,
+        .event_cb = notif_cb,
+        .data_cb = handle_rx
+    };
+
+    // Install CDC-ACM driver without USB Host
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_install(NULL));
+
+    // Open device without installed driver
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
+
+    // Uninstall driver before installing it
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall());
+
+    // Properly install USB and CDC drivers
+    test_install_cdc_driver();
+
+    // Open non-existent device
+    TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, cdc_acm_host_open(0x303A, 0x1234, 0, &dev_config, &cdc_dev)); // 0x303A:0x1234 this device is not connected to USB Host
+    TEST_ASSERT_NULL(cdc_dev);
+
+    // Open regular device
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
+    TEST_ASSERT_NOT_NULL(cdc_dev);
+
+    // Open one CDC-ACM device twice //@todo this test is commented out due to bug in usb_host
+    //cdc_acm_dev_hdl_t cdc_dev_test;
+    //TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev_test));
+    //TEST_ASSERT_NULL(cdc_dev_test);
+
+    // Uninstall driver with open devices
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall());
+
+    // Send data that is too large and NULL data
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, 1024, 1000));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, cdc_acm_host_data_tx_blocking(cdc_dev, NULL, 10, 1000));
+
+    // Change mode to read-only and try to write to it
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
+    dev_config.out_buffer_size = 0; // Read-only device
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
+    TEST_ASSERT_NOT_NULL(cdc_dev);
+    TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000));
+
+    // Send unsupported CDC request (TinyUSB accepts SendBreak command, eventhough it doesn't support it)
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_send_break(cdc_dev, 100));
+
+    // Clean-up
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
+    TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
+    vTaskDelay(20);
+}
+
+#endif

+ 10 - 0
tools/test_apps/peripherals/usb/CMakeLists.txt

@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common)
+
+# Set the components to include the tests for.
+set(TEST_COMPONENTS "cdc_acm_host" CACHE STRING "List of components to test")
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(usb_test_app)

+ 14 - 0
tools/test_apps/peripherals/usb/README.md

@@ -0,0 +1,14 @@
+| Supported Targets | ESP32-S2 | ESP32-S3 |
+| ----------------- | -------- | -------- |
+
+# USB Host CDC-ACM driver test project
+
+Main purpose of this application is to test the USB Host CDC-ACM driver.
+It tests basic functionality of the driver like open/close/read/write operations,
+advanced features like CDC control request, multi-threaded or multi-device access,
+as well as reaction to sudden disconnection and other error states.
+
+## Hardware Required
+
+This test expects that TinyUSB dual CDC device with VID = 0x303A and PID = 0x4002
+is connected to the USB host.

+ 3 - 0
tools/test_apps/peripherals/usb/main/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRCS "usb_test_main.c"
+                       INCLUDE_DIRS ""
+                       REQUIRES unity)

+ 16 - 0
tools/test_apps/peripherals/usb/main/usb_test_main.c

@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "unity.h"
+
+void app_main(void)
+{
+    UNITY_BEGIN();
+    unity_run_all_tests();
+    UNITY_END();
+}