chegewara пре 3 година
родитељ
комит
0fe097bf70
16 измењених фајлова са 5567 додато и 0 уклоњено
  1. 21 0
      LICENSE
  2. 59 0
      examples/cdc/cdc.ino
  3. 11 0
      library.properties
  4. 326 0
      src/cdchost.cpp
  5. 140 0
      src/cdchost.h
  6. 236 0
      src/configparse.cpp
  7. 19 0
      src/configparse.h
  8. 2620 0
      src/hcd.c
  9. 512 0
      src/hcd.h
  10. 214 0
      src/parse_data.cpp
  11. 95 0
      src/parse_hid.h
  12. 293 0
      src/pipe.cpp
  13. 163 0
      src/pipe.h
  14. 239 0
      src/port.cpp
  15. 139 0
      src/port.h
  16. 480 0
      src/usb.h

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 chegewara
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 59 - 0
examples/cdc/cdc.ino

@@ -0,0 +1,59 @@
+#include "cdchost.h"
+#define DEVICE_ADDRESS 2
+USBHostCDC port(DEVICE_ADDRESS);
+
+void ctrl_pipe_cb(usb_host_even_t event, void *data)
+{
+  Serial.printf("CTRL EVENT: 0x%x\n", event);
+}
+
+void port_cb(port_event_msg_t msg, USBHostPort* port)
+{
+  Serial.printf("PORT EVENT: 0x%x\n", msg.port_event);
+}
+
+void cdc_datain_cb(usb_host_even_t event, void *data)
+{
+  usb_irp_t *irp = (usb_irp_t *)data;
+  Serial.printf("IN EVENT: 0x%x, buffer_len: %d, received: %d\n", event, irp->num_bytes, irp->actual_num_bytes);
+  Serial.print("DATA: ");
+  for (size_t i = 0; i < irp->actual_num_bytes; i++)
+  {
+    Serial.printf("%c", irp->data_buffer[i]);
+  }
+  Serial.println();
+}
+
+void cdc_dataout_cb(usb_host_even_t event, void *data)
+{
+  usb_irp_t *irp = (usb_irp_t *)data;
+  Serial.printf("OUT EVENT: 0x%x, buffer_len: %d, sent: %d\n", event, irp->num_bytes, irp->actual_num_bytes);
+  Serial.print("DATA: ");
+  for (size_t i = 0; i < irp->actual_num_bytes; i++)
+  {
+    Serial.printf("%c", irp->data_buffer[i]);
+  }
+  Serial.println();
+}
+
+void setup()
+{
+  Serial.begin(115200);
+  port.init();
+  port.onPortEvent(port_cb);
+  port.onControlEvent(ctrl_pipe_cb);
+  port.onDataIn(cdc_datain_cb);
+  port.onDataOut(cdc_dataout_cb);
+}
+
+void loop()
+{
+  delay(10);
+  while (Serial.available())
+  {
+    size_t l = Serial.available();
+    uint8_t b[l];
+    l = Serial.read(b, l);
+    port.sendData(b, l);
+  }
+}

+ 11 - 0
library.properties

@@ -0,0 +1,11 @@
+name=ESP32USBhost
+version=0.2.0
+author=Dariusz Krempa <esp32@esp32.eu.org>
+maintainer=Dariusz Krempa <esp32@esp32.eu.org>
+sentence=USB host for ESP32-S2
+paragraph=This library provides a wrapper around esp-idf USB host for the ESP32-S2 on Arduino.
+category=Communication
+architectures=esp32
+includes=
+depends=
+url=https://github.com/chegewara/esp32S2-usb-host-library

+ 326 - 0
src/cdchost.cpp

@@ -0,0 +1,326 @@
+#include <cstring>
+#include "cdchost.h"
+#include "configparse.h"
+#include "Arduino.h"
+
+/**
+ * ADD set of custom USB host events
+ * add callbacks with custom events in ctrl pipe
+ * 
+ * 
+ * 
+ * 
+ * 
+ * dont change order of callbacks
+ * port and EPs/pipes local callbacks
+ */
+IRAM_ATTR static void ctrl_pipe_cb(pipe_event_msg_t msg, usb_irp_t *irp, USBHostPipe *pipe)
+{
+    usb_ctrl_req_t *ctrl = (usb_ctrl_req_t *)irp->data_buffer;
+    usb_host_even_t event = msg.event + BASE_PIPE_EVENT;
+    switch (msg.event)
+    {
+    case HCD_PIPE_EVENT_NONE:
+        break;
+    case HCD_PIPE_EVENT_IRP_DONE:
+    {
+        ESP_LOGD("Pipe: ", "XFER status: %d, num bytes: %d, actual bytes: %d", irp->status, irp->num_bytes, irp->actual_num_bytes);
+        usb_ctrl_req_t *ctrl = (usb_ctrl_req_t *)irp->data_buffer;
+
+        if (ctrl->bRequest == USB_B_REQUEST_GET_DESCRIPTOR)
+        {
+            event = BASE_PIPE_EVENT + ctrl->bRequest + (ctrl->wValue);
+            if (ctrl->wValue == USB_W_VALUE_DT_CONFIG << 8)
+            {
+                if (irp->actual_num_bytes)
+                {
+                    USBHostCDC *p = (USBHostCDC *)pipe->port;
+                    p->begin(irp->data_buffer);
+                }
+            }
+        }
+        else if (ctrl->bRequest == USB_B_REQUEST_GET_CONFIGURATION)
+        {
+            event = BASE_PIPE_EVENT + ctrl->bRequest;
+            ESP_LOGD("", "get current configuration: %d", irp->data_buffer[8]);
+        }
+        else if (ctrl->bRequest == USB_B_REQUEST_SET_CONFIGURATION)
+        {
+            event = BASE_PIPE_EVENT + ctrl->bRequest;
+            ESP_LOGD("", "set current configuration: %d", ctrl->wValue);
+            pipe->getConfigDescriptor();
+        }
+        else if (ctrl->bRequest == USB_B_REQUEST_SET_ADDRESS)
+        {
+            event = BASE_PIPE_EVENT + ctrl->bRequest;
+            ESP_LOGD("", "address set: %d", ctrl->wValue);
+            pipe->updateAddress(ctrl->wValue);
+            pipe->setConfiguration(1);
+        }
+        else if (ctrl->bRequestType == SET_VALUE && ctrl->bRequest == SET_LINE_CODING) // set line coding
+        {
+            event = BASE_PIPE_EVENT + (ctrl->bRequestType << 4) + ctrl->bRequest;
+            line_coding_t *data = (line_coding_t *)(irp->data_buffer + sizeof(usb_ctrl_req_t));
+            ESP_LOGD("Set line coding", "Bitrate: %d, stop bits: %d, parity: %d, bits: %d",
+                     data->dwDTERate, data->bCharFormat, data->bParityType, data->bDataBits);
+        }
+        else if (ctrl->bRequestType == GET_VALUE && ctrl->bRequest == GET_LINE_CODING) // get line coding
+        {
+            event = BASE_PIPE_EVENT + (ctrl->bRequestType << 4) + ctrl->bRequest;
+            line_coding_t *data = (line_coding_t *)(irp->data_buffer + sizeof(usb_ctrl_req_t));
+            ESP_LOGD("Get line coding", "Bitrate: %d, stop bits: %d, parity: %d, bits: %d",
+                     data->dwDTERate, data->bCharFormat, data->bParityType, data->bDataBits);
+        }
+        else if (ctrl->bRequestType == SET_VALUE && ctrl->bRequest == SET_CONTROL_LINE_STATE) // set line coding
+        {
+            event = BASE_PIPE_EVENT + (ctrl->bRequestType << 4) + ctrl->bRequest;
+            ESP_LOGD("Set control line state", "");
+        }
+        else
+        {
+            ESP_LOG_BUFFER_HEX_LEVEL("Ctrl data", ctrl, sizeof(usb_ctrl_req_t), ESP_LOG_WARN);
+            ESP_LOGD("", "unknown request handled");
+        }
+
+        break;
+    }
+    case HCD_PIPE_EVENT_ERROR_XFER:
+        ESP_LOGW("", "XFER error: %d", irp->status);
+        pipe->reset();
+        break;
+
+    case HCD_PIPE_EVENT_ERROR_STALL:
+        ESP_LOG_BUFFER_HEX_LEVEL("Ctrl data", ctrl, sizeof(usb_ctrl_req_t), ESP_LOG_INFO);
+        pipe->reset();
+        break;
+
+    default:
+        ESP_LOGW("", "not handled pipe event: %d", msg.event);
+        break;
+    }
+
+    if (pipe->port->ctrl_callback != NULL)
+    {
+        pipe->port->ctrl_callback(event, irp);
+    }
+}
+
+IRAM_ATTR static void port_cb(port_event_msg_t msg, USBHostPort *p)
+{
+    static USBHostCDC *port = (USBHostCDC *)p;
+    hcd_port_state_t state;
+    switch (msg.port_event)
+    {
+    case HCD_PORT_EVENT_CONNECTION:
+    {
+        ESP_LOGD("", "physical connection detected");
+        // we are ready to establish connection with device
+        if (port->connect())
+        {
+            // create ctrl callback
+            USBHostPipe *pipe = new USBHostPipe();
+            pipe->port = port;
+            port->addCTRLpipe(pipe);
+            pipe->onPipeEvent(ctrl_pipe_cb);
+            pipe->getDeviceDescriptor();
+            pipe->setDeviceAddress(port->address);
+        }
+        break;
+    }
+    case HCD_PORT_EVENT_SUDDEN_DISCONN:
+        // this is called before event in port.cpp to delete all pipes here to properly recover port later in port.cpp
+        if (port->inpipe)
+            delete (port->inpipe);
+        if (port->outpipe)
+            delete (port->outpipe);
+        break;
+
+    default:
+        ESP_LOGW("", "port event: %d", msg.port_event);
+        break;
+    }
+}
+
+static void in_pipe_cb(pipe_event_msg_t msg, usb_irp_t *irp, USBHostPipe *pipe)
+{
+    if (((USBHostCDC *)pipe->port)->data_in != NULL)
+    {
+        ESP_LOGV("", "callback IN: %d", irp->status);
+        ((USBHostCDC *)pipe->port)->data_in(msg.event, irp);
+    }
+    switch (msg.event)
+    {
+    case HCD_PIPE_EVENT_IRP_DONE:
+        ESP_LOGD("Pipe cdc: ", "XFER status: %d, num bytes: %d, actual bytes: %d", irp->status, irp->num_bytes, irp->actual_num_bytes);
+        break;
+
+    case HCD_PIPE_EVENT_ERROR_XFER:
+        ESP_LOGW("", "XFER error: %d", irp->status);
+        pipe->reset();
+        break;
+
+    case HCD_PIPE_EVENT_ERROR_STALL:
+        ESP_LOGW("", "Device stalled CDC pipe, state: %d", pipe->getState());
+        pipe->reset();
+        break;
+
+    default:
+        break;
+    }
+    // previous IRP dequeued, time to send another request
+    pipe->inData();
+}
+
+static void out_pipe_cb(pipe_event_msg_t msg, usb_irp_t *irp, USBHostPipe *pipe)
+{
+    if (((USBHostCDC *)pipe->port)->data_out != NULL)
+    {
+        ((USBHostCDC *)pipe->port)->data_out(msg.event, irp);
+        ESP_LOGV("", "callback OUT: %d", irp->status);
+    }
+    switch (msg.event)
+    {
+    case HCD_PIPE_EVENT_IRP_DONE:
+        ESP_LOGD("Pipe cdc: ", "XFER status: %d, num bytes: %d, actual bytes: %d", irp->status, irp->num_bytes, irp->actual_num_bytes);
+        break;
+
+    case HCD_PIPE_EVENT_ERROR_XFER:
+        ESP_LOGW("", "XFER error: %d", irp->status);
+        pipe->reset();
+        break;
+
+    case HCD_PIPE_EVENT_ERROR_STALL:
+        ESP_LOGW("", "Device stalled CDC pipe, state: %d", pipe->getState());
+        pipe->reset();
+        break;
+
+    default:
+        break;
+    }
+}
+
+void USBHostCDC::init()
+{
+    USBHostPort::init(port_cb);
+}
+
+// initialize endpoints after getting config descriptor
+void USBHostCDC::begin(uint8_t *irp)
+{
+    USBHostConfigParser parser;
+    uint8_t *itf0 = parser.getInterfaceByClass(irp, 0x02);
+    uint8_t *itf1 = parser.getInterfaceByClass(irp, 0x0A);
+    if (itf1)
+    {
+        uint8_t *ep = parser.getEndpointByDirection(itf1, 1);
+        if (ep)
+        {
+            inpipe = new USBHostPipe(handle);
+            inpipe->onPipeEvent(in_pipe_cb);
+            inpipe->init((usb_desc_ep_t *)ep, address);
+            inpipe->port = this;
+        }
+
+        ep = parser.getEndpointByDirection(itf1, 0);
+        if (ep)
+        {
+            outpipe = new USBHostPipe(handle);
+            outpipe->onPipeEvent(out_pipe_cb);
+            outpipe->init((usb_desc_ep_t *)ep, address);
+            outpipe->port = this;
+        }
+
+        setControlLine(1, 1);
+        setLineCoding(115200, 0, 0, 5);
+        inpipe->inData();
+    }
+}
+
+// CDC class control pipe requests
+void USBHostCDC::setLineCoding(uint32_t bitrate, uint8_t cf, uint8_t parity, uint8_t bits)
+{
+    if (ctrlPipe == NULL)
+    {
+        ESP_LOGE("", "CTRL pipe is not set");
+        return;
+    }
+    usb_irp_t *irp = ctrlPipe->allocateIRP(7);
+    if (irp == NULL)
+    {
+        ESP_LOGE("", "CTRL pipe IRP is not set");
+        return;
+    }
+
+    USB_CTRL_REQ_CDC_SET_LINE_CODING((cdc_ctrl_line_t *)irp->data_buffer, 0, bitrate, cf, parity, bits);
+
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(ctrlPipe->getHandle(), irp)))
+    {
+        ESP_LOGW("", "SET_LINE_CODING enqueue err: 0x%x", err);
+    }
+}
+
+bool USBHostCDC::setControlLine(bool dtr, bool rts)
+{
+    if (ctrlPipe == nullptr)
+    {
+        return false;
+    }
+
+    usb_irp_t *irp = ctrlPipe->allocateIRP(0);
+    if (irp == NULL)
+    {
+        return false;
+    }
+
+    USB_CTRL_REQ_CDC_SET_CONTROL_LINE_STATE((usb_ctrl_req_t *)irp->data_buffer, 0, dtr, rts);
+
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(ctrlPipe->getHandle(), irp)))
+    {
+        ESP_LOGW("", "SET_CONTROL_LINE_STATE enqueue err: 0x%x", err);
+    }
+    return true;
+}
+
+void USBHostCDC::getLineCoding()
+{
+    usb_irp_t *irp = ctrlPipe->allocateIRP(7);
+    USB_CTRL_REQ_CDC_GET_LINE_CODING((usb_ctrl_req_t *)irp->data_buffer, 0);
+
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(ctrlPipe->getHandle(), irp)))
+    {
+        ESP_LOGW("", "GET_LINE_CODING enqueue err: 0x%x", err);
+    }
+}
+
+void USBHostCDC::sendData(uint8_t *data, size_t len)
+{
+    outpipe->outData(data, len);
+}
+
+// IN/OUT callbacks
+void USBHostCDC::onDataIn(usb_host_event_cb_t cb)
+{
+    data_in = cb;
+}
+
+void USBHostCDC::onDataOut(usb_host_event_cb_t cb)
+{
+    data_out = cb;
+}
+
+/**
+ * TODO
+ */
+void USBHostCDC::intrData()
+{
+    // usb_irp_t *irp = allocateIRP(70);
+    // USB_CTRL_GET_HID_REPORT_DESC((usb_ctrl_req_t *)irp->data_buffer, 0, 70);
+
+    // esp_err_t err;
+    // if(ESP_OK != (err = hcd_irp_enqueue(handle, irp))) {
+    //     ESP_LOGW("", "INTR enqueue err: 0x%x", err);
+    // }
+}

+ 140 - 0
src/cdchost.h

@@ -0,0 +1,140 @@
+#pragma once
+#include "hcd.h"
+#include "usb.h"
+#include "port.h"
+#include "pipe.h"
+
+#define SET_VALUE 0x21
+#define GET_VALUE 0xA1
+
+// DTR/RTS control in SET_CONTROL_LINE_STATE
+#define ENABLE_DTR(val) (val << 0)
+#define ENABLE_RTS(val) (val << 1)
+
+#define SET_LINE_CODING 0x20
+#define GET_LINE_CODING 0x21
+#define SET_CONTROL_LINE_STATE 0x22
+#define SERIAL_STATE 0x20
+
+typedef struct
+{
+    uint32_t dwDTERate;
+    uint8_t bCharFormat;
+    uint8_t bParityType;
+    uint8_t bDataBits;
+} line_coding_t;
+
+typedef union
+{
+    struct
+    {
+        uint8_t bRequestType;
+        uint8_t bRequest;
+        uint16_t wValue;
+        uint16_t wIndex;
+        uint16_t wLength;
+        line_coding_t data;
+    } USB_CTRL_REQ_ATTR;
+    uint8_t val[USB_CTRL_REQ_SIZE + 7];
+} cdc_ctrl_line_t;
+
+/**
+ * @brief Initializer for a SET_LINE_CODING request
+ *
+ * Sets the address of a connected device
+ */
+#define USB_CTRL_REQ_CDC_SET_LINE_CODING(ctrl_req_ptr, index, bitrate, cf, parity, bits) ( \
+    {                                                                                      \
+        (ctrl_req_ptr)->bRequestType = SET_VALUE;                                          \
+        (ctrl_req_ptr)->bRequest = SET_LINE_CODING;                                        \
+        (ctrl_req_ptr)->wValue = 0;                                                        \
+        (ctrl_req_ptr)->wIndex = (index);                                                  \
+        (ctrl_req_ptr)->wLength = (7);                                                     \
+        (ctrl_req_ptr)->data.dwDTERate = (bitrate);                                        \
+        (ctrl_req_ptr)->data.bCharFormat = (cf);                                           \
+        (ctrl_req_ptr)->data.bParityType = (parity);                                       \
+        (ctrl_req_ptr)->data.bDataBits = (bits);                                           \
+    })
+/**
+ * @brief Initializer for a GET_LINE_CODING request
+ *
+ * Sets the address of a connected device
+ */
+#define USB_CTRL_REQ_CDC_GET_LINE_CODING(ctrl_req_ptr, index) ( \
+    {                                                           \
+        (ctrl_req_ptr)->bRequestType = GET_VALUE;               \
+        (ctrl_req_ptr)->bRequest = GET_LINE_CODING;             \
+        (ctrl_req_ptr)->wValue = 0;                             \
+        (ctrl_req_ptr)->wIndex = (index);                       \
+        (ctrl_req_ptr)->wLength = (7);                          \
+    })
+
+/**
+ * @brief Initializer for a SET_CONTROL_LINE_STATE request
+ *
+ * Sets the address of a connected device
+ */
+#define USB_CTRL_REQ_CDC_SET_CONTROL_LINE_STATE(ctrl_req_ptr, index, dtr, rts) ( \
+    {                                                                            \
+        (ctrl_req_ptr)->bRequestType = SET_VALUE;                                \
+        (ctrl_req_ptr)->bRequest = SET_CONTROL_LINE_STATE;                       \
+        (ctrl_req_ptr)->wValue = ENABLE_DTR(dtr) | ENABLE_RTS(rts);              \
+        (ctrl_req_ptr)->wIndex = (index);                                        \
+        (ctrl_req_ptr)->wLength = (0);                                           \
+    })
+
+class USBHostCDC : public USBHostPort
+{
+private:
+public:
+    USBHostCDC(uint8_t addr) : USBHostPort(addr) {}
+    void begin(uint8_t *);
+    void init();
+
+    // ----------- CDC related requests ----------- //
+
+    /**
+     * @brief Enqueue set line codding on control pipe
+     */
+    void setLineCoding(uint32_t bitrate, uint8_t cf, uint8_t parity, uint8_t bits);
+
+    /**
+     * @brief Enqueue set control line request on control pipe
+     */
+    bool setControlLine(bool dtr, bool rts);
+
+    /**
+     * @brief Enqueue get line codding request on control pipe
+     */
+    void getLineCoding();
+
+    // ---------------- IN/OUT data related functions -------------------- //
+    /**
+     * @brief Enqueue INTR in request
+     */
+    void intrData();
+
+    /**
+     * @brief Enqueue send data requests on OUT endpoint pipe
+     */
+    void sendData(uint8_t *, size_t);
+
+    /**
+     * @brief Register on data IN callback
+     */
+    void onDataIn(usb_host_event_cb_t);
+
+    /**
+     * @brief Register on data OUT callback
+     */
+    void onDataOut(usb_host_event_cb_t);
+
+    // class related callbacks and pipes
+    usb_host_event_cb_t data_in;
+    usb_host_event_cb_t data_out;
+    USBHostPipe *inpipe;
+    USBHostPipe *outpipe;
+};
+
+
+

+ 236 - 0
src/configparse.cpp

@@ -0,0 +1,236 @@
+
+#include <stdio.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "esp_intr_alloc.h"
+#include "esp_err.h"
+#include "esp_attr.h"
+#include "esp_rom_gpio.h"
+#include "soc/gpio_pins.h"
+#include "soc/gpio_sig_map.h"
+#include "hal/usbh_ll.h"
+#include "hcd.h"
+#include "esp_log.h"
+#include "configparse.h"
+
+#define USB_W_VALUE_DT_HID 0x22
+#define USB_W_VALUE_DT_CS_INTERFACE 0x24
+
+static char *class_to_str(uint8_t cls)
+{
+    switch (cls)
+    {
+    case 0x00:
+        return ">ifc";
+    case 0x01:
+        return "Audio";
+    case 0x02:
+        return "CDC";
+    case 0x03:
+        return "HID";
+    case 0x05:
+        return "Physical";
+    case 0x06:
+        return "Image";
+    case 0x07:
+        return "Printer";
+    case 0x08:
+        return "Mass Storage";
+    case 0x09:
+        return "Hub";
+    case 0x0a:
+        return "CDC-data";
+    case 0x0b:
+        return "Smart card";
+    case 0x0d:
+        return "Content security";
+    case 0x0e:
+        return "Video";
+    case 0x0f:
+        return "Personal heathcare";
+    case 0x10:
+        return "Audio/Vdeo devices";
+    case 0x11:
+        return "Bilboard";
+    case 0x12:
+        return "USB-C bridge";
+    case 0xdc:
+        return "Diagnostic device";
+    case 0xe0:
+        return "Wireless controller";
+    case 0xef:
+        return "Miscellaneous";
+    case 0xfe:
+        return "Application specific";
+    case 0xff:
+        return "Vendor specific";
+
+    default:
+        return "Wrong class type";
+    }
+}
+
+static void utf16_to_utf8(char *in, char *out, uint8_t len)
+{
+    for (size_t i = 0; i < len; i++)
+    {
+        out[i / 2] = in[i];
+        i++;
+    }
+}
+
+static uint8_t *parse_cfg_descriptor(uint8_t *data_buffer, uint8_t **out, uint8_t *_type)
+{
+    uint8_t offset = 0;
+    uint8_t type = *(&data_buffer[0] + offset + 1);
+    do
+    {
+        ESP_LOGD("", "type: %d", type);
+        *_type = type;
+        switch (type)
+        {
+        case 0x0:
+            break;
+        case USB_W_VALUE_DT_DEVICE:
+            break;
+
+        case USB_W_VALUE_DT_CONFIG:
+        {
+            ESP_LOGV("", "Config:");
+            usb_desc_cfg_t *data = (usb_desc_cfg_t *)(data_buffer + offset);
+            ESP_LOGV("", "Number of Interfaces: %d", data->bNumInterfaces);
+            ESP_LOGV("", "bConfig: %d", data->bConfigurationValue);
+            ESP_LOGV("", "iConfig: %d", data->iConfiguration);
+            ESP_LOGV("", "Attributes: 0x%02x", data->bmAttributes);
+            ESP_LOGV("", "Max power: %d mA", data->bMaxPower * 2);
+            offset += data->bLength;
+            break;
+        }
+        case USB_W_VALUE_DT_STRING:
+        {
+            usb_desc_str_t *data = (usb_desc_str_t *)(data_buffer + offset);
+            uint8_t len = 0;
+            len = data->bLength;
+            offset += len;
+            char *str = (char *)calloc(1, len);
+            utf16_to_utf8((char *)&data->val[2], str, len);
+            ESP_LOGV("", "strings: %s", str);
+            free(str);
+            break;
+        }
+        case USB_W_VALUE_DT_INTERFACE:
+        {
+            ESP_LOGV("", "Interface:");
+            usb_desc_intf_t *data = (usb_desc_intf_t *)(data_buffer + offset);
+            offset += data->bLength;
+            ESP_LOGV("", "bInterfaceNumber: %d", data->bInterfaceNumber);
+            ESP_LOGV("", "bAlternateSetting: %d", data->bAlternateSetting);
+            ESP_LOGV("", "bNumEndpoints: %d", data->bNumEndpoints);
+            ESP_LOGV("", "bInterfaceClass: 0x%02x (%s)", data->bInterfaceClass, class_to_str(data->bInterfaceClass));
+            ESP_LOGV("", "bInterfaceSubClass: 0x%02x", data->bInterfaceSubClass);
+            ESP_LOGV("", "bInterfaceProtocol: 0x%02x", data->bInterfaceProtocol);
+            *out = (uint8_t *)data;
+            break;
+        }
+        case USB_W_VALUE_DT_ENDPOINT:
+        {
+            ESP_LOGV("", "Endpoint:");
+            usb_desc_ep_t *data = (usb_desc_ep_t *)(data_buffer + offset);
+            offset += data->bLength;
+            ESP_LOGV("", "bEndpointAddress: 0x%02x", data->bEndpointAddress);
+            ESP_LOGV("", "bmAttributes: 0x%02x", data->bmAttributes);
+            ESP_LOGV("", "bDescriptorType: %d", data->bDescriptorType);
+            ESP_LOGV("", "wMaxPacketSize: %d", data->wMaxPacketSize);
+            ESP_LOGV("", "bInterval: %d ms", data->bInterval);
+            *out = (uint8_t *)data;
+            break;
+        }
+        case 0x21:
+        {
+            ESP_LOGD("", "HID descriptor");
+            uint8_t *data = (data_buffer + offset);
+            offset += data[0];
+            ESP_LOGD("Report map size", "0x%x", (uint16_t)data[7]);
+            // report_map_size = (uint16_t)data[7];
+            *out = (uint8_t *)data;
+            break;
+        }
+        case USB_W_VALUE_DT_CS_INTERFACE:
+        {
+            ESP_LOGV("", "CS_Interface:");
+            usb_desc_intf_t *data = (usb_desc_intf_t *)(data_buffer + offset);
+            offset += data->bLength;
+
+            break;
+        }
+        default:
+            ESP_LOGD("", "unknown descriptor: %d", type);
+
+            offset += *(data_buffer + offset);
+            break;
+        }
+    } while (0);
+    return (uint8_t *)data_buffer + offset;
+}
+
+USBHostConfigParser::USBHostConfigParser()
+{
+}
+
+USBHostConfigParser::~USBHostConfigParser()
+{
+}
+
+uint8_t* USBHostConfigParser::getInterfaceByClass(uint8_t *data, uint8_t cls)
+{
+    uint8_t *ep;
+    uint8_t type;
+    uint8_t *next = data + sizeof(usb_ctrl_req_t);
+    do
+    {
+        next = parse_cfg_descriptor(next, &ep, &type);
+        if (type == 0x0)
+            break;
+        if (ep && type == USB_W_VALUE_DT_INTERFACE)
+        {
+            ESP_LOGV("", "find interface class: %s", class_to_str(cls));
+            if (!cls || ((usb_desc_intf_t *)ep)->bInterfaceClass == cls) // IN EP
+            {
+                ESP_LOGV("", "found interface class: %s", class_to_str(((usb_desc_intf_t *)ep)->bInterfaceClass));
+                return ep;
+            }
+        }
+    } while (1);
+
+    return NULL;
+}
+
+uint8_t* USBHostConfigParser::getEndpointByDirection(uint8_t *interface, uint8_t dir)
+{
+    uint8_t *ep;
+    uint8_t type;
+    uint8_t *next = interface;
+    do
+    {
+        next = parse_cfg_descriptor(next, &ep, &type);
+        if (type == 0x0)
+            break;
+        if (ep && type == USB_W_VALUE_DT_ENDPOINT)
+        {
+            ESP_LOGV("", "we have new endpoint");
+            if (!dir || (USB_DESC_EP_GET_EP_DIR((usb_desc_ep_t *)ep) == dir)) // IN EP == 1
+            {
+                return ep;
+            }
+        }
+    } while (1);
+
+    return NULL;
+}
+
+void USBHostConfigParser::getString()
+{
+}
+
+
+

+ 19 - 0
src/configparse.h

@@ -0,0 +1,19 @@
+#pragma once
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+#endif
+class USBHostConfigParser
+{
+private:
+    
+public:
+    USBHostConfigParser();
+    ~USBHostConfigParser();
+
+    uint8_t* getInterfaceByClass(uint8_t *data, uint8_t cls = 0);
+    uint8_t* getEndpointByDirection(uint8_t* interface, uint8_t dir = 0);
+    void getString();
+};
+

+ 2620 - 0
src/hcd.c

@@ -0,0 +1,2620 @@
+// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/queue.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "esp_heap_caps.h"
+#include "esp_intr_alloc.h"
+#include "esp_timer.h"
+#include "esp_err.h"
+#include "esp_rom_gpio.h"
+#include "hal/usbh_hal.h"
+#include "hal/usb_types_private.h"
+#include "soc/gpio_pins.h"
+#include "soc/gpio_sig_map.h"
+#include "driver/periph_ctrl.h"
+#include "usb.h"
+#include "hcd.h"
+
+// ----------------------------------------------------- Macros --------------------------------------------------------
+
+// --------------------- Constants -------------------------
+
+#define INIT_DELAY_MS                           30  //A delay of at least 25ms to enter Host mode. Make it 30ms to be safe
+#define DEBOUNCE_DELAY_MS                       250 //A debounce delay of 250ms
+#define RESET_HOLD_MS                           30  //Spec requires at least 10ms. Make it 30ms to be safe
+#define RESET_RECOVERY_MS                       30  //Reset recovery delay of 10ms (make it 30 ms to be safe) to allow for connected device to recover (and for port enabled interrupt to occur)
+#define RESUME_HOLD_MS                          30  //Spec requires at least 20ms, Make it 30ms to be safe
+#define RESUME_RECOVERY_MS                      20  //Resume recovery of at least 10ms. Make it 20 ms to be safe. This will include the 3 LS bit times of the EOP
+
+#define CTRL_EP_MAX_MPS_LS                      8   //Largest Maximum Packet Size for Low Speed control endpoints
+#define CTRL_EP_MAX_MPS_FS                      64  //Largest Maximum Packet Size for Full Speed control endpoints
+
+#define NUM_PORTS                               1   //The controller only has one port.
+
+// ----------------------- Configs -------------------------
+
+typedef struct {
+    int in_mps;
+    int non_periodic_out_mps;
+    int periodic_out_mps;
+} fifo_mps_limits_t;
+
+/**
+ * @brief Default FIFO sizes (see 2.1.2.4 for programming guide)
+ *
+ * RXFIFO
+ * - Recommended: ((LPS/4) * 2) + 2
+ * - Actual: Whatever leftover size: USBH_HAL_FIFO_TOTAL_USABLE_LINES(200) - 48 - 48 = 104
+ * - Worst case can accommodate two packets of 204 bytes, or one packet of 408
+ * NPTXFIFO
+ * - Recommended: (LPS/4) * 2
+ * - Actual: Assume LPS is 64, and 3 packets: (64/4) * 3 = 48
+ * - Worst case can accommodate three packets of 64 bytes or one packet of 192
+ * PTXFIFO
+ * - Recommended: (LPS/4) * 2
+ * - Actual: Assume LPS is 64, and 3 packets: (64/4) * 3 = 48
+ * - Worst case can accommodate three packets of 64 bytes or one packet of 192
+ */
+const usbh_hal_fifo_config_t fifo_config_default = {
+    .rx_fifo_lines = 104,
+    .nptx_fifo_lines = 48,
+    .ptx_fifo_lines = 48,
+};
+
+const fifo_mps_limits_t mps_limits_default = {
+    .in_mps = 408,
+    .non_periodic_out_mps = 192,
+    .periodic_out_mps = 192,
+};
+
+/**
+ * @brief FIFO sizes that bias to giving RX FIFO more capacity
+ *
+ * RXFIFO
+ * - Recommended: ((LPS/4) * 2) + 2
+ * - Actual: Whatever leftover size: USBH_HAL_FIFO_TOTAL_USABLE_LINES(200) - 32 - 16 = 152
+ * - Worst case can accommodate two packets of 300 bytes or one packet of 600 bytes
+ * NPTXFIFO
+ * - Recommended: (LPS/4) * 2
+ * - Actual: Assume LPS is 64, and 1 packets: (64/4) * 1 = 16
+ * - Worst case can accommodate one packet of 64 bytes
+ * PTXFIFO
+ * - Recommended: (LPS/4) * 2
+ * - Actual: Assume LPS is 64, and 3 packets: (64/4) * 2 = 32
+ * - Worst case can accommodate two packets of 64 bytes or one packet of 128
+ */
+const usbh_hal_fifo_config_t fifo_config_bias_rx = {
+    .rx_fifo_lines = 152,
+    .nptx_fifo_lines = 16,
+    .ptx_fifo_lines = 32,
+};
+
+const fifo_mps_limits_t mps_limits_bias_rx = {
+    .in_mps = 600,
+    .non_periodic_out_mps = 64,
+    .periodic_out_mps = 128,
+};
+
+/**
+ * @brief FIFO sizes that bias to giving Periodic TX FIFO more capacity (i.e., ISOC OUT)
+ *
+ * RXFIFO
+ * - Recommended: ((LPS/4) * 2) + 2
+ * - Actual: Assume LPS is 64, and 2 packets: ((64/4) * 2) + 2 = 34
+ * - Worst case can accommodate two packets of 64 bytes or one packet of 128
+ * NPTXFIFO
+ * - Recommended: (LPS/4) * 2
+ * - Actual: Assume LPS is 64, and 1 packets: (64/4) * 1 = 16
+ * - Worst case can accommodate one packet of 64 bytes
+ * PTXFIFO
+ * - Recommended: (LPS/4) * 2
+ * - Actual: Whatever leftover size: USBH_HAL_FIFO_TOTAL_USABLE_LINES(200) - 34 - 16 = 150
+ * - Worst case can accommodate two packets of 300 bytes or one packet of 600 bytes
+ */
+const usbh_hal_fifo_config_t fifo_config_bias_ptx = {
+    .rx_fifo_lines = 34,
+    .nptx_fifo_lines = 16,
+    .ptx_fifo_lines = 150,
+};
+
+const fifo_mps_limits_t mps_limits_bias_ptx = {
+    .in_mps = 128,
+    .non_periodic_out_mps = 64,
+    .periodic_out_mps = 600,
+};
+
+#define FRAME_LIST_LEN                          USB_HAL_FRAME_LIST_LEN_32
+#define NUM_BUFFERS                             2
+
+#define XFER_LIST_LEN_CTRL                      3   //One descriptor for each stage
+#define XFER_LIST_LEN_BULK                      2   //One descriptor for transfer, one to support an extra zero length packet
+#define XFER_LIST_LEN_INTR                      32
+#define XFER_LIST_LEN_ISOC                      FRAME_LIST_LEN  //Same length as the frame list makes it easier to schedule. Must be power of 2
+
+// ------------------------ Flags --------------------------
+
+/**
+ * @brief Bit masks for the HCD to use in the IRPs reserved_flags field
+ *
+ * The IRP object has a reserved_flags member for host stack's internal use. The following flags will be set in
+ * reserved_flags in order to keep track of state of an IRP within the HCD.
+ */
+#define IRP_STATE_IDLE                          0x0 //The IRP is not enqueued in an HCD pipe
+#define IRP_STATE_PENDING                       0x1 //The IRP is enqueued and pending execution
+#define IRP_STATE_INFLIGHT                      0x2 //The IRP is currently in flight
+#define IRP_STATE_DONE                          0x3 //The IRP has completed execution or is retired, and is waiting to be dequeued
+#define IRP_STATE_MASK                          0x3 //Bit mask of all the IRP state flags
+#define IRP_STATE_SET(reserved_flags, state)    (reserved_flags = (reserved_flags & ~IRP_STATE_MASK) | state)
+#define IRP_STATE_GET(reserved_flags)           (reserved_flags & IRP_STATE_MASK)
+
+// -------------------- Convenience ------------------------
+
+#define HCD_ENTER_CRITICAL_ISR()                portENTER_CRITICAL_ISR(&hcd_lock)
+#define HCD_EXIT_CRITICAL_ISR()                 portEXIT_CRITICAL_ISR(&hcd_lock)
+#define HCD_ENTER_CRITICAL()                    portENTER_CRITICAL(&hcd_lock)
+#define HCD_EXIT_CRITICAL()                     portEXIT_CRITICAL(&hcd_lock)
+
+#define HCD_CHECK(cond, ret_val) ({                                         \
+            if (!(cond)) {                                                  \
+                return (ret_val);                                           \
+            }                                                               \
+})
+#define HCD_CHECK_FROM_CRIT(cond, ret_val) ({                               \
+            if (!(cond)) {                                                  \
+                HCD_EXIT_CRITICAL();                                        \
+                return ret_val;                                             \
+            }                                                               \
+})
+
+// ------------------------------------------------------ Types --------------------------------------------------------
+
+typedef struct pipe_obj pipe_t;
+typedef struct port_obj port_t;
+
+/**
+ * @brief Object representing a single buffer of a pipe's multi buffer implementation
+ */
+typedef struct {
+    void *xfer_desc_list;
+    usb_irp_t *irp;
+    union {
+        struct {
+            uint32_t data_stg_in: 1;        //Data stage of the control transfer is IN
+            uint32_t data_stg_skip: 1;      //Control transfer has no data stage
+            uint32_t cur_stg: 2;            //Index of the current stage (e.g., 0 is setup stage, 2 is status stage)
+            uint32_t reserved28: 28;
+        } ctrl;                             //Control transfer related
+        struct {
+            uint32_t zero_len_packet: 1;    //Bulk transfer should add a zero length packet at the end regardless
+            uint32_t reserved31: 31;
+        } bulk;                             //Bulk transfer related
+        struct {
+            uint32_t num_qtds: 8;           //Number of transfer descriptors filled
+            uint32_t reserved24: 24;
+        } intr;                             //Interrupt transfer related
+        struct {
+            uint32_t num_qtds: 8;           //Number of transfer descriptors filled (including NULL descriptors)
+            uint32_t interval: 8;           //Interval (in number of SOF i.e., ms)
+            uint32_t irp_start_idx: 8;      //Index of the first transfer descriptor in the list
+            uint32_t next_irp_start_idx: 8; //Index for the first descriptor of the next buffer
+        } isoc;
+        uint32_t val;
+    } flags;
+    union {
+        struct {
+            uint32_t stop_idx: 8;           //The descriptor index when the channel was halted
+            uint32_t executing: 1;          //The buffer is currently executing
+            uint32_t error_occurred: 1;     //An error occurred
+            uint32_t cancelled: 1;          //The buffer was actively cancelled
+            uint32_t reserved5: 5;
+            hcd_pipe_state_t pipe_state: 8; //The pipe's state when the error occurred
+            hcd_pipe_event_t pipe_event: 8; //The pipe event when the error occurred
+        };
+        uint32_t val;
+    } status_flags;                         //Status flags for the buffer
+} dma_buffer_block_t;
+
+/**
+ * @brief Object representing a pipe in the HCD layer
+ */
+struct pipe_obj {
+    //IRP queueing related
+    TAILQ_HEAD(tailhead_irp_pending, usb_irp_obj) pending_irp_tailq;
+    TAILQ_HEAD(tailhead_irp_done, usb_irp_obj) done_irp_tailq;
+    int num_irp_pending;
+    int num_irp_done;
+    //Multi-buffer control
+    dma_buffer_block_t *buffers[NUM_BUFFERS];  //Double buffering scheme
+    union {
+        struct {
+            uint32_t buffer_num_to_fill: 2; //Number of buffers that can be filled
+            uint32_t buffer_num_to_exec: 2; //Number of buffers that are filled and need to be executed
+            uint32_t buffer_num_to_parse: 2;//Number of buffers completed execution and waiting to be parsed
+            uint32_t reserved2: 2;
+            uint32_t wr_idx: 1;             //Index of the next buffer to fill. Bit width must allow NUM_BUFFERS to wrap automatically
+            uint32_t rd_idx: 1;             //Index of the current buffer in-flight. Bit width must allow NUM_BUFFERS to wrap automatically
+            uint32_t fr_idx: 1;             //Index of the next buffer to parse. Bit width must allow NUM_BUFFERS to wrap automatically
+            uint32_t buffer_is_executing: 1;//One of the buffers is in flight
+            uint32_t reserved20: 20;
+        };
+        uint32_t val;
+    } multi_buffer_control;
+    //HAL related
+    usbh_hal_chan_t *chan_obj;
+    usbh_hal_ep_char_t ep_char;
+    //Port related
+    port_t *port;                           //The port to which this pipe is routed through
+    TAILQ_ENTRY(pipe_obj) tailq_entry;      //TailQ entry for port's list of pipes
+    //Pipe status/state/events related
+    hcd_pipe_state_t state;
+    hcd_pipe_event_t last_event;
+    TaskHandle_t task_waiting_pipe_notif;   //Task handle used for internal pipe events
+    union {
+        struct {
+            uint32_t waiting_xfer_done: 1;
+            uint32_t paused: 1;
+            uint32_t pipe_cmd_processing: 1;
+            uint32_t is_active: 1;
+            uint32_t reserved28: 28;
+        };
+        uint32_t val;
+    } cs_flags;
+    //Pipe callback and context
+    hcd_pipe_isr_callback_t callback;
+    void *callback_arg;
+    void *context;
+};
+
+/**
+ * @brief Object representing a port in the HCD layer
+ */
+struct port_obj {
+    usbh_hal_context_t *hal;
+    void *frame_list;
+    //Pipes routed through this port
+    TAILQ_HEAD(tailhead_pipes_idle, pipe_obj) pipes_idle_tailq;
+    TAILQ_HEAD(tailhead_pipes_queued, pipe_obj) pipes_active_tailq;
+    int num_pipes_idle;
+    int num_pipes_queued;
+    //Port status, state, and events
+    hcd_port_state_t state;
+    usb_speed_t speed;
+    hcd_port_event_t last_event;
+    TaskHandle_t task_waiting_port_notif;           //Task handle used for internal port events
+    union {
+        struct {
+            uint32_t event_pending: 1;              //The port has an event that needs to be handled
+            uint32_t event_processing: 1;           //The port is current processing (handling) an event
+            uint32_t cmd_processing: 1;             //Used to indicate command handling is ongoing
+            uint32_t waiting_all_pipes_pause: 1;    //Waiting for all pipes routed through this port to be paused
+            uint32_t disable_requested: 1;
+            uint32_t conn_devc_ena: 1;              //Used to indicate the port is connected to a device that has been reset
+            uint32_t periodic_scheduling_enabled: 1;
+            uint32_t reserved9: 9;
+            uint32_t num_pipes_waiting_pause: 16;
+        };
+        uint32_t val;
+    } flags;
+    bool initialized;
+    hcd_port_fifo_bias_t fifo_bias;
+    //Port callback and context
+    hcd_port_isr_callback_t callback;
+    void *callback_arg;
+    SemaphoreHandle_t port_mux;
+    void *context;
+};
+
+/**
+ * @brief Object representing the HCD
+ */
+typedef struct {
+    //Ports (Hardware only has one)
+    port_t *port_obj;
+    intr_handle_t isr_hdl;
+} hcd_obj_t;
+
+static portMUX_TYPE hcd_lock = portMUX_INITIALIZER_UNLOCKED;
+static hcd_obj_t *s_hcd_obj = NULL;     //Note: "s_" is for the static pointer
+
+// ------------------------------------------------- Forward Declare ---------------------------------------------------
+
+// ------------------- Buffer Control ----------------------
+
+/**
+ * @brief Check if an inactive buffer can be filled with a pending IRP
+ *
+ * @param pipe Pipe object
+ * @return true There are one or more pending IRPs, and the inactive buffer is yet to be filled
+ * @return false Otherwise
+ */
+static inline bool _buffer_can_fill(pipe_t *pipe)
+{
+    //We can only fill if there are pending IRPs and at least one unfilled buffer
+    if (pipe->num_irp_pending > 0 && pipe->multi_buffer_control.buffer_num_to_fill > 0) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/**
+ * @brief Fill an empty buffer with
+ *
+ * This function will:
+ * - Remove an IRP from the pending tailq
+ * - Fill that IRP into the inactive buffer
+ *
+ * @note _buffer_can_fill() must return true before calling this function
+ *
+ * @param pipe Pipe object
+ */
+static void _buffer_fill(pipe_t *pipe);
+
+/**
+ * @brief Check if there are more filled buffers than can be executed
+ *
+ * @param pipe Pipe object
+ * @return true There are more filled buffers to be executed
+ * @return false No more buffers to execute
+ */
+static inline bool _buffer_can_exec(pipe_t *pipe)
+{
+    //We can only execute if there is not already a buffer executing and if there are filled buffers awaiting execution
+    if (!pipe->multi_buffer_control.buffer_is_executing && pipe->multi_buffer_control.buffer_num_to_exec > 0) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/**
+ * @brief Execute the next filled buffer
+ *
+ * - Must have called _buffer_can_exec() before calling this function
+ * - Will start the execution of the buffer
+ *
+ * @param pipe Pipe object
+ */
+static void _buffer_exec(pipe_t *pipe);
+
+/**
+ * @brief Check if a buffer as completed execution
+ *
+ * This should only be called after receiving a USBH_HAL_CHAN_EVENT_CPLT event to check if a buffer is actually
+ * done. Buffers that aren't complete (such as Control transfers) will be continued automatically.
+ *
+ * @param pipe Pipe object
+ * @return true Buffer complete
+ * @return false Buffer not complete
+ */
+static bool _buffer_check_done(pipe_t *pipe);
+
+/**
+ * @brief Marks the last executed buffer as complete
+ *
+ * This should be called on a pipe that has confirmed that a buffer is completed via _buffer_check_done()
+ *
+ * @param pipe Pipe object
+ * @param stop_idx Descriptor index when the buffer stopped execution
+ */
+static inline void _buffer_done(pipe_t *pipe, int stop_idx)
+{
+    //Store the stop_idx for later parsing
+    dma_buffer_block_t *buffer_done = pipe->buffers[pipe->multi_buffer_control.rd_idx];
+    buffer_done->status_flags.executing = 0;
+    buffer_done->status_flags.error_occurred = 0;
+    buffer_done->status_flags.stop_idx = stop_idx;
+    pipe->multi_buffer_control.rd_idx++;
+    pipe->multi_buffer_control.buffer_num_to_exec--;
+    pipe->multi_buffer_control.buffer_num_to_parse++;
+    pipe->multi_buffer_control.buffer_is_executing = 0;
+}
+
+/**
+ * @brief Marks the last executed buffer as complete due to an error
+ *
+ * This should be called on a pipe that has received a USBH_HAL_CHAN_EVENT_ERROR event
+ *
+ * @param pipe Pipe object
+ * @param stop_idx Descriptor index when the buffer stopped execution
+ * @param pipe_state State of the pipe after the error
+ * @param pipe_event Error event
+ * @param cancelled Whether the pipe stopped due to cancellation
+ */
+static inline void _buffer_done_error(pipe_t *pipe, int stop_idx, hcd_pipe_state_t pipe_state, hcd_pipe_event_t pipe_event, bool cancelled)
+{
+    //Mark the buffer as erroneous for later parsing
+    dma_buffer_block_t *buffer_done = pipe->buffers[pipe->multi_buffer_control.rd_idx];
+    buffer_done->status_flags.executing = 0;
+    buffer_done->status_flags.error_occurred = 1;
+    buffer_done->status_flags.cancelled = cancelled;
+    buffer_done->status_flags.stop_idx = stop_idx;
+    buffer_done->status_flags.pipe_state = pipe_state;
+    buffer_done->status_flags.pipe_event = pipe_event;
+    pipe->multi_buffer_control.rd_idx++;
+    pipe->multi_buffer_control.buffer_num_to_exec--;
+    pipe->multi_buffer_control.buffer_num_to_parse++;
+    pipe->multi_buffer_control.buffer_is_executing = 0;
+}
+
+/**
+ * @brief Checks if a pipe has one or more completed buffers to parse
+ *
+ * @param pipe Pipe object
+ * @return true There are one or more buffers to parse
+ * @return false There are no more buffers to parse
+ */
+static inline bool _buffer_can_parse(pipe_t *pipe)
+{
+    if (pipe->multi_buffer_control.buffer_num_to_parse > 0) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/**
+ * @brief Parse a completed buffer
+ *
+ * This function will:
+ * - Parse the results of an IRP from a completed buffer
+ * - Put the IRP into the done tailq
+ *
+ * @note This function should only be called on the completion of a buffer
+ *
+ * @param pipe Pipe object
+ * @param stop_idx (For INTR pipes only) The index of the descriptor that follows the last descriptor of the IRP. Set to 0 otherwise
+ */
+static void _buffer_parse(pipe_t *pipe);
+
+/**
+ * @brief Marks all buffers pending execution as completed, then parses those buffers
+ *
+ * @note This should only be called on pipes do not have any currently executing buffers.
+ *
+ * @param pipe Pipe object
+ * @param cancelled Whether this flush is due to cancellation
+ */
+static void _buffer_flush_all(pipe_t *pipe, bool cancelled);
+
+// ------------------------ Pipe ---------------------------
+
+/**
+ * @brief Wait until a pipe's in-flight IRP is done
+ *
+ * If the pipe has an in-flight IRP, this function will block until it is done (via a internal pipe event).
+ * If the pipe has no in-flight IRP, this function do nothing and return immediately.
+ * If the pipe's state changes unexpectedly, this function will return false.
+ *
+ * Also parses all buffers on exit
+ *
+ * @note This function is blocking (will exit and re-enter the critical section to do so)
+ *
+ * @param pipe Pipe object
+ * @return true Pipes in-flight IRP is done
+ * @return false Pipes state unexpectedly changed
+ */
+static bool _pipe_wait_done(pipe_t *pipe);
+
+/**
+ * @brief Retires all IRPs (those that were previously in-flight or pending)
+ *
+ * Retiring all IRPs will result in any pending IRP being moved to the done tailq. This function will update the IPR
+ * status of each IRP.
+ *  - If the retiring is self-initiated (i.e., due to a pipe command), the IRP status will be set to USB_TRANSFER_STATUS_CANCELED.
+ *  - If the retiring is NOT self-initiated (i.e., the pipe is no longer valid), the IRP status will be set to USB_TRANSFER_STATUS_NO_DEVICE
+ *
+ * Entry:
+ * - There can be no in-flight IRP (must already be parsed and returned to done queue)
+ * - All buffers must be parsed
+ * Exit:
+ * - If there was an in-flight IRP, it is parsed and returned to the done queue
+ * - If there are any pending IRPs:
+ *      - They are moved to the done tailq
+ *
+ * @param pipe Pipe object
+ * @param cancelled Are we actively Pipe retire is initialized by the user due to a command, thus IRP are
+ *                  actively cancelled.
+ */
+static void _pipe_retire(pipe_t *pipe, bool self_initiated);
+
+/**
+ * @brief Decode a HAL channel error to the corresponding pipe event
+ *
+ * @param chan_error The HAL channel error
+ * @return hcd_pipe_event_t The corresponding pipe error event
+ */
+static inline hcd_pipe_event_t pipe_decode_error_event(usbh_hal_chan_error_t chan_error);
+
+// ------------------------ Port ---------------------------
+
+/**
+ * @brief Invalidates all the pipes routed through a port
+ *
+ * This should be called when port or its connected device is no longer valid (e.g., the port is suddenly reset/disabled
+ * or the device suddenly disconnects)
+ *
+ * @note This function may run one or more callbacks, and will exit and enter the critical section to do so
+ *
+ * Entry:
+ *  - The port or its connected device is no longer valid. This guarantees that none of the pipes will be transferring
+ * Exit:
+ *  - Each pipe will have any pending IRPs moved to their respective done tailq
+ *  - Each pipe will be put into the invalid state
+ *  - Generate a HCD_PIPE_EVENT_INVALID event on each pipe and run their respective callbacks
+ *
+ * @param port Port object
+ */
+static void _port_invalidate_all_pipes(port_t *port);
+
+/**
+ * @brief Pause all pipes routed through a port
+ *
+ * Call this before attempting to reset or suspend a port
+ *
+ * Entry:
+ *  - The port is in the HCD_PORT_STATE_ENABLED state (i.e., there is a connected device which has been reset)
+ * Exit:
+ *  - All pipes routed through the port have either paused, or are waiting to complete their in-flight IRPs before pausing
+ *  - If waiting for one or more pipes to pause, _internal_port_event_wait() must be called after this function returns
+ *
+ * @param port Port object
+ * @return true All pipes have been paused
+ * @return false Need to wait for one or more pipes to pause. Call _internal_port_event_wait() afterwards
+ */
+static bool _port_pause_all_pipes(port_t *port);
+
+/**
+ * @brief Un-pause all pipes routed through a port
+ *
+ * Call this before after coming out of a port reset or resume.
+ *
+ * Entry:
+ *  - The port is in the HCD_PORT_STATE_ENABLED state
+ *  - All pipes are paused
+ * Exit:
+ *  - All pipes un-paused. If those pipes have pending IRPs, they will be started.
+ *
+ * @param port Port object
+ */
+static void _port_unpause_all_pipes(port_t *port);
+
+/**
+ * @brief Send a reset condition on a port's bus
+ *
+ * Entry:
+ *  - The port must be in the HCD_PORT_STATE_ENABLED or HCD_PORT_STATE_DISABLED state
+ * Exit:
+ * - Reset condition sent on the port's bus
+ *
+ * @note This function is blocking (will exit and re-enter the critical section to do so)
+ *
+ * @param port Port object
+ * @return true Reset condition successfully sent
+ * @return false Failed to send reset condition due to unexpected port state
+ */
+static bool _port_bus_reset(port_t *port);
+
+/**
+ * @brief Send a suspend condition on a port's bus
+ *
+ * This function will first pause pipes routed through a port, and then send a suspend condition.
+ *
+ * Entry:
+ *  - The port must be in the HCD_PORT_STATE_ENABLED state
+ * Exit:
+ *  - All pipes paused and the port is put into the suspended state
+ *
+ * @note This function is blocking (will exit and re-enter the critical section to do so)
+ *
+ * @param port Port object
+ * @return true Suspend condition successfully sent. Port is now in the HCD_PORT_STATE_SUSPENDED state
+ * @return false Failed to send a suspend condition due to unexpected port state
+ */
+static bool _port_bus_suspend(port_t *port);
+
+/**
+ * @brief Send a resume condition on a port's bus
+ *
+ * This function will send a resume condition, and then un-pause all the pipes routed through a port
+ *
+ * Entry:
+ *  - The port must be in the HCD_PORT_STATE_SUSPENDED state
+ * Exit:
+ *  - The port is put into the enabled state and all pipes un-paused
+ *
+ * @note This function is blocking (will exit and re-enter the critical section to do so)
+ *
+ * @param port Port object
+ * @return true Resume condition successfully sent. Port is now in the HCD_PORT_STATE_ENABLED state
+ * @return false Failed to send a resume condition due to unexpected port state.
+ */
+static bool _port_bus_resume(port_t *port);
+
+/**
+ * @brief Disable a port
+ *
+ * Entry:
+ *  - The port must be in the HCD_PORT_STATE_ENABLED or HCD_PORT_STATE_SUSPENDED state
+ * Exit:
+ *  - All pipes paused (should already be paused if port was suspended), and the port is put into the disabled state.
+ *
+ * @note This function is blocking (will exit and re-enter the critical section to do so)
+ *
+ * @param port Port object
+ * @return true Port successfully disabled
+ * @return false Port to disable port due to unexpected port state
+ */
+static bool _port_disable(port_t *port);
+
+/**
+ * @brief Debounce port after a connection or disconnection event
+ *
+ * This function should be called after a port connection or disconnect event. This function will execute a debounce
+ * delay then check the actual connection/disconnections state.
+ *
+ * @param port Port object
+ * @return true A device is connected
+ * @return false No device connected
+ */
+static bool _port_debounce(port_t *port);
+
+// ----------------------- Events --------------------------
+
+/**
+ * @brief Wait for an internal event from a port
+ *
+ * @note For each port, there can only be one thread/task waiting for an internal port event
+ * @note This function is blocking (will exit and re-enter the critical section to do so)
+ *
+ * @param port Port object
+ */
+static void _internal_port_event_wait(port_t *port);
+
+/**
+ * @brief Notify (from an ISR context) the thread/task waiting for the internal port event
+ *
+ * @param port Port object
+ * @return true A yield is required
+ * @return false Whether a yield is required or not
+ */
+static bool _internal_port_event_notify_from_isr(port_t *port);
+
+/**
+ * @brief Wait for an internal event from a particular pipe
+ *
+ * @note For each pipe, there can only be one thread/task waiting for an internal port event
+ * @note This function is blocking (will exit and re-enter the critical section to do so)
+ *
+ * @param pipe Pipe object
+ */
+static void _internal_pipe_event_wait(pipe_t *pipe);
+
+/**
+ * @brief Notify (from an ISR context) the thread/task waiting for an internal pipe event
+ *
+ * @param pipe Pipe object
+ * @param from_isr Whether this is called from an ISR or not
+ * @return true A yield is required
+ * @return false Whether a yield is required or not. Always false when from_isr is also false
+ */
+static bool _internal_pipe_event_notify(pipe_t *pipe, bool from_isr);
+
+// ----------------------------------------------- Interrupt Handling --------------------------------------------------
+
+// ------------------- Internal Event ----------------------
+
+static void _internal_port_event_wait(port_t *port)
+{
+    //There must NOT be another thread/task already waiting for an internal event
+    assert(port->task_waiting_port_notif == NULL);
+    port->task_waiting_port_notif = xTaskGetCurrentTaskHandle();
+    HCD_EXIT_CRITICAL();
+    //Wait to be notified from ISR
+    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+    HCD_ENTER_CRITICAL();
+    port->task_waiting_port_notif = NULL;
+}
+
+static bool _internal_port_event_notify_from_isr(port_t *port)
+{
+    //There must be a thread/task waiting for an internal event
+    assert(port->task_waiting_port_notif != NULL);
+    BaseType_t xTaskWoken = pdFALSE;
+    //Unblock the thread/task waiting for the notification
+    HCD_EXIT_CRITICAL_ISR();
+    vTaskNotifyGiveFromISR(port->task_waiting_port_notif, &xTaskWoken);
+    HCD_ENTER_CRITICAL_ISR();
+    return (xTaskWoken == pdTRUE);
+}
+
+static void _internal_pipe_event_wait(pipe_t *pipe)
+{
+    //There must NOT be another thread/task already waiting for an internal event
+    assert(pipe->task_waiting_pipe_notif == NULL);
+    pipe->task_waiting_pipe_notif = xTaskGetCurrentTaskHandle();
+    HCD_EXIT_CRITICAL();
+    //Wait to be notified from ISR
+    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+    HCD_ENTER_CRITICAL();
+    pipe->task_waiting_pipe_notif = NULL;
+}
+
+static bool _internal_pipe_event_notify(pipe_t *pipe, bool from_isr)
+{
+    //There must be a thread/task waiting for an internal event
+    assert(pipe->task_waiting_pipe_notif != NULL);
+    bool ret;
+    if (from_isr) {
+        BaseType_t xTaskWoken = pdFALSE;
+        HCD_EXIT_CRITICAL_ISR();
+        //Unblock the thread/task waiting for the pipe notification
+        vTaskNotifyGiveFromISR(pipe->task_waiting_pipe_notif, &xTaskWoken);
+        HCD_ENTER_CRITICAL_ISR();
+        ret = (xTaskWoken == pdTRUE);
+    } else {
+        HCD_EXIT_CRITICAL();
+        xTaskNotifyGive(pipe->task_waiting_pipe_notif);
+        HCD_ENTER_CRITICAL();
+        ret = false;
+    }
+    return ret;
+}
+
+// ----------------- Interrupt Handlers --------------------
+
+/**
+ * @brief Handle a HAL port interrupt and obtain the corresponding port event
+ *
+ * @param[in] port Port object
+ * @param[in] hal_port_event The HAL port event
+ * @param[out] yield Set to true if a yield is required as a result of handling the interrupt
+ * @return hcd_port_event_t  Returns a port event, or HCD_PORT_EVENT_NONE if no port event occurred
+ */
+static hcd_port_event_t _intr_hdlr_hprt(port_t *port, usbh_hal_port_event_t hal_port_event, bool *yield)
+{
+    hcd_port_event_t port_event = HCD_PORT_EVENT_NONE;
+    switch (hal_port_event) {
+        case USBH_HAL_PORT_EVENT_CONN: {
+            //Don't update state immediately, we still need to debounce.
+            port_event = HCD_PORT_EVENT_CONNECTION;
+            break;
+        }
+        case USBH_HAL_PORT_EVENT_DISCONN: {
+            if (port->flags.conn_devc_ena) {
+                //The port was previously enabled, so this is a sudden disconnection
+                port->state = HCD_PORT_STATE_RECOVERY;
+                port_event = HCD_PORT_EVENT_SUDDEN_DISCONN;
+            } else {
+                //For normal disconnections, don't update state immediately as we still need to debounce.
+                port_event = HCD_PORT_EVENT_DISCONNECTION;
+            }
+            port->flags.conn_devc_ena = 0;
+            break;
+        }
+        case USBH_HAL_PORT_EVENT_ENABLED: {
+            usbh_hal_port_enable(port->hal);  //Initialize remaining host port registers
+            port->speed = (usbh_hal_port_get_conn_speed(port->hal) == USB_PRIV_SPEED_FULL) ? USB_SPEED_FULL : USB_SPEED_LOW;
+            port->state = HCD_PORT_STATE_ENABLED;
+            port->flags.conn_devc_ena = 1;
+            //This was triggered by a command, so no event needs to be propagated.
+            break;
+        }
+        case USBH_HAL_PORT_EVENT_DISABLED: {
+            port->flags.conn_devc_ena = 0;
+            //Disabled could be due to a disable request or reset request, or due to a port error
+            if (port->state != HCD_PORT_STATE_RESETTING) {  //Ignore the disable event if it's due to a reset request
+                port->state = HCD_PORT_STATE_DISABLED;
+                if (port->flags.disable_requested) {
+                    //Disabled by request (i.e. by port command). Generate an internal event
+                    port->flags.disable_requested = 0;
+                    *yield |= _internal_port_event_notify_from_isr(port);
+                } else {
+                    //Disabled due to a port error
+                    port_event = HCD_PORT_EVENT_ERROR;
+                }
+            }
+            break;
+        }
+        case USBH_HAL_PORT_EVENT_OVRCUR:
+        case USBH_HAL_PORT_EVENT_OVRCUR_CLR: {  //Could occur if a quick overcurrent then clear happens
+            if (port->state != HCD_PORT_STATE_NOT_POWERED) {
+                //We need to power OFF the port to protect it
+                usbh_hal_port_toggle_power(port->hal, false);
+                port->state = HCD_PORT_STATE_NOT_POWERED;
+                port_event = HCD_PORT_EVENT_OVERCURRENT;
+            }
+            port->flags.conn_devc_ena = 0;
+            break;
+        }
+        default: {
+            abort();
+            break;
+        }
+    }
+    return port_event;
+}
+
+/**
+ * @brief Handles a HAL channel interrupt
+ *
+ * This function should be called on a HAL channel when it has an interrupt. Most HAL channel events will correspond to
+ * to a pipe event, but not always. This function will store the pipe event and return a pipe object pointer if a pipe
+ * event occurred, or return NULL otherwise.
+ *
+ * @param[in] chan_obj Pointer to HAL channel object with interrupt
+ * @param[out] yield Set to true if a yield is required as a result of handling the interrupt
+ * @return hcd_pipe_event_t The pipe event
+ */
+static hcd_pipe_event_t _intr_hdlr_chan(pipe_t *pipe, usbh_hal_chan_t *chan_obj, bool *yield)
+{
+    usbh_hal_chan_event_t chan_event = usbh_hal_chan_decode_intr(chan_obj);
+    hcd_pipe_event_t event = HCD_PIPE_EVENT_NONE;
+    //Check the the pipe's port still has a connected and enabled device before processing the interrupt
+    if (!pipe->port->flags.conn_devc_ena) {
+        return event;   //Treat as a no event.
+    }
+    bool handle_waiting_xfer_done = false;
+    switch (chan_event) {
+        case USBH_HAL_CHAN_EVENT_CPLT: {
+            if (!_buffer_check_done(pipe)) {
+                break;
+            }
+            pipe->last_event = HCD_PIPE_EVENT_IRP_DONE;
+            event = pipe->last_event;
+            //Mark the buffer as done
+            int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj);
+            _buffer_done(pipe, stop_idx);
+            //First check if there is another buffer we can execute
+            if (_buffer_can_exec(pipe) && !pipe->cs_flags.waiting_xfer_done) {
+                //If the next buffer is filled and ready to execute, execute it
+                _buffer_exec(pipe);
+            }
+            //Handle the previously done buffer
+            _buffer_parse(pipe);
+            if (pipe->cs_flags.waiting_xfer_done) {
+                handle_waiting_xfer_done = true;
+            } else if (_buffer_can_fill(pipe)) {
+                //Now that we've parsed a buffer, see if another IRP can be filled in its place
+                _buffer_fill(pipe);
+            }
+            break;
+        }
+        case USBH_HAL_CHAN_EVENT_ERROR: {
+            //Get and store the pipe error event
+            usbh_hal_chan_error_t chan_error = usbh_hal_chan_get_error(chan_obj);
+            usbh_hal_chan_clear_error(chan_obj);
+            pipe->last_event = pipe_decode_error_event(chan_error);
+            event = pipe->last_event;
+            pipe->state = HCD_PIPE_STATE_HALTED;
+            //Mark the buffer as done with an error
+            int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj);
+            _buffer_done_error(pipe, stop_idx, pipe->state, pipe->last_event, false);
+            //Parse the buffer
+            _buffer_parse(pipe);
+            if (pipe->cs_flags.waiting_xfer_done) {
+                handle_waiting_xfer_done = true;
+            }
+            break;
+        }
+        case USBH_HAL_CHAN_EVENT_NONE: {
+            break;  //Nothing to do
+        }
+        case USBH_HAL_CHAN_EVENT_HALT_REQ:  //We currently don't halt request so this event should never occur
+        default:
+            abort();
+            break;
+    }
+    if (handle_waiting_xfer_done) {
+        //A port/pipe command is waiting for this pipe to complete its transfer. So don't load the next transfer
+        pipe->cs_flags.waiting_xfer_done = 0;
+        if (pipe->port->flags.waiting_all_pipes_pause) {
+            //Port command is waiting for all pipes to be paused
+            pipe->cs_flags.paused = 1;
+            pipe->port->flags.num_pipes_waiting_pause--;
+            if (pipe->port->flags.num_pipes_waiting_pause == 0) {
+                //All pipes have finished pausing, Notify the blocked port command
+                pipe->port->flags.waiting_all_pipes_pause = 0;
+                *yield |= _internal_port_event_notify_from_isr(pipe->port);
+            }
+        } else {
+            //Pipe command is waiting for transfer to complete
+            *yield |= _internal_pipe_event_notify(pipe, true);
+        }
+    }
+    return event;
+}
+
+/**
+ * @brief Main interrupt handler
+ *
+ * - Handle all HPRT (Host Port) related interrupts first as they may change the
+ *   state of the driver (e.g., a disconnect event)
+ * - If any channels (pipes) have pending interrupts, handle them one by one
+ * - The HCD has not blocking functions, so the user's ISR callback is run to
+ *   allow the users to send whatever OS primitives they need.
+ *
+ * @param arg Interrupt handler argument
+ */
+static void intr_hdlr_main(void *arg)
+{
+    port_t *port = (port_t *) arg;
+    bool yield = false;
+
+    HCD_ENTER_CRITICAL_ISR();
+    usbh_hal_port_event_t hal_port_evt = usbh_hal_decode_intr(port->hal);
+    if (hal_port_evt == USBH_HAL_PORT_EVENT_CHAN) {
+        //Channel event. Cycle through each pending channel
+        usbh_hal_chan_t *chan_obj = usbh_hal_get_chan_pending_intr(port->hal);
+        while (chan_obj != NULL) {
+            pipe_t *pipe = (pipe_t *)usbh_hal_chan_get_context(chan_obj);
+            hcd_pipe_event_t event = _intr_hdlr_chan(pipe, chan_obj, &yield);
+            //Run callback if a pipe event has occurred and the pipe also has a callback
+            if (event != HCD_PIPE_EVENT_NONE && pipe->callback != NULL) {
+                HCD_EXIT_CRITICAL_ISR();
+                yield |= pipe->callback((hcd_pipe_handle_t)pipe, event, pipe->callback_arg, true);
+                HCD_ENTER_CRITICAL_ISR();
+            }
+            //Check for more channels with pending interrupts. Returns NULL if there are no more
+            chan_obj = usbh_hal_get_chan_pending_intr(port->hal);
+        }
+    } else if (hal_port_evt != USBH_HAL_PORT_EVENT_NONE) {  //Port event
+        hcd_port_event_t port_event = _intr_hdlr_hprt(port, hal_port_evt, &yield);
+        if (port_event != HCD_PORT_EVENT_NONE) {
+            port->last_event = port_event;
+            port->flags.event_pending = 1;
+            if (port->callback != NULL) {
+                HCD_EXIT_CRITICAL_ISR();
+                yield |= port->callback((hcd_port_handle_t)port, port_event, port->callback_arg, true);
+                HCD_ENTER_CRITICAL_ISR();
+            }
+        }
+    }
+    HCD_EXIT_CRITICAL_ISR();
+
+    if (yield) {
+        portYIELD_FROM_ISR();
+    }
+}
+
+// --------------------------------------------- Host Controller Driver ------------------------------------------------
+
+static port_t *port_obj_alloc(void)
+{
+    port_t *port = calloc(1, sizeof(port_t));
+    usbh_hal_context_t *hal = malloc(sizeof(usbh_hal_context_t));
+    void *frame_list = heap_caps_aligned_calloc(USBH_HAL_FRAME_LIST_MEM_ALIGN, FRAME_LIST_LEN,sizeof(uint32_t), MALLOC_CAP_DMA);
+    SemaphoreHandle_t port_mux = xSemaphoreCreateMutex();
+    if (port == NULL || hal == NULL || frame_list == NULL || port_mux == NULL) {
+        free(port);
+        free(hal);
+        free(frame_list);
+        if (port_mux != NULL) {
+            vSemaphoreDelete(port_mux);
+        }
+        return NULL;
+    }
+    port->hal = hal;
+    port->frame_list = frame_list;
+    port->port_mux = port_mux;
+    return port;
+}
+
+static void port_obj_free(port_t *port)
+{
+    if (port == NULL) {
+        return;
+    }
+    vSemaphoreDelete(port->port_mux);
+    free(port->frame_list);
+    free(port->hal);
+    free(port);
+}
+
+// ----------------------- Public --------------------------
+
+esp_err_t hcd_install(const hcd_config_t *config)
+{
+    HCD_ENTER_CRITICAL();
+    HCD_CHECK_FROM_CRIT(s_hcd_obj == NULL, ESP_ERR_INVALID_STATE);
+    HCD_EXIT_CRITICAL();
+
+    esp_err_t err_ret;
+    //Allocate memory and resources for driver object and all port objects
+    hcd_obj_t *p_hcd_obj_dmy = calloc(1, sizeof(hcd_obj_t));
+    if (p_hcd_obj_dmy == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    //Allocate resources for each port (there's only one)
+    p_hcd_obj_dmy->port_obj = port_obj_alloc();
+    esp_err_t intr_alloc_ret = esp_intr_alloc(ETS_USB_INTR_SOURCE,
+                                              config->intr_flags | ESP_INTR_FLAG_INTRDISABLED,  //The interrupt must be disabled until the port is initialized
+                                              intr_hdlr_main,
+                                              (void *)p_hcd_obj_dmy->port_obj,
+                                              &p_hcd_obj_dmy->isr_hdl);
+    if (p_hcd_obj_dmy->port_obj == NULL) {
+        err_ret = ESP_ERR_NO_MEM;
+    }
+    if (intr_alloc_ret != ESP_OK) {
+        err_ret = intr_alloc_ret;
+        goto err;
+    }
+
+    HCD_ENTER_CRITICAL();
+    if (s_hcd_obj != NULL) {
+        HCD_EXIT_CRITICAL();
+        err_ret = ESP_ERR_INVALID_STATE;
+        goto err;
+    }
+    s_hcd_obj = p_hcd_obj_dmy;
+    //Set HW prerequisites for each port (there's only one)
+    periph_module_enable(PERIPH_USB_MODULE);
+    periph_module_reset(PERIPH_USB_MODULE);
+    /*
+    Configure GPIOS for Host mode operation using internal PHY
+        - Forces ID to GND for A side
+        - Forces B Valid to GND as we are A side host
+        - Forces VBUS Valid to HIGH
+        - Forces A Valid to HIGH
+    */
+    esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_OTG_IDDIG_IN_IDX, false);
+    esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false);
+    esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_VBUSVALID_IN_IDX, false);
+    esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_AVALID_IN_IDX, false);
+    HCD_EXIT_CRITICAL();
+    return ESP_OK;
+
+err:
+    if (intr_alloc_ret == ESP_OK) {
+        esp_intr_free(p_hcd_obj_dmy->isr_hdl);
+    }
+    port_obj_free(p_hcd_obj_dmy->port_obj);
+    free(p_hcd_obj_dmy);
+    return err_ret;
+}
+
+esp_err_t hcd_uninstall(void)
+{
+    HCD_ENTER_CRITICAL();
+    //Check that all ports have been disabled (there's only one port)
+    if (s_hcd_obj == NULL || s_hcd_obj->port_obj->initialized) {
+        HCD_EXIT_CRITICAL();
+        return ESP_ERR_INVALID_STATE;
+    }
+    periph_module_disable(PERIPH_USB_MODULE);
+    hcd_obj_t *p_hcd_obj_dmy = s_hcd_obj;
+    s_hcd_obj = NULL;
+    HCD_EXIT_CRITICAL();
+
+    //Free resources
+    port_obj_free(p_hcd_obj_dmy->port_obj);
+    esp_intr_free(p_hcd_obj_dmy->isr_hdl);
+    free(p_hcd_obj_dmy);
+    return ESP_OK;
+}
+
+// ------------------------------------------------------ Port ---------------------------------------------------------
+
+// ----------------------- Private -------------------------
+
+static void _port_invalidate_all_pipes(port_t *port)
+{
+    //This function should only be called when the port is invalid
+    assert(!port->flags.conn_devc_ena);
+    pipe_t *pipe;
+    //Process all pipes that have queued IRPs
+    TAILQ_FOREACH(pipe, &port->pipes_active_tailq, tailq_entry) {
+        //Mark the pipe as invalid and set an invalid event
+        pipe->state = HCD_PIPE_STATE_INVALID;
+        pipe->last_event = HCD_PIPE_EVENT_INVALID;
+        //Flush all buffers that are still awaiting exec
+        _buffer_flush_all(pipe, false);
+        //Retire any remaining IRPs in the pending tailq
+        _pipe_retire(pipe, false);
+        if (pipe->task_waiting_pipe_notif != NULL) {
+            //Unblock the thread/task waiting for a notification from the pipe as the pipe is no longer valid.
+            _internal_pipe_event_notify(pipe, false);
+        }
+        if (pipe->callback != NULL) {
+            HCD_EXIT_CRITICAL();
+            (void) pipe->callback((hcd_pipe_handle_t)pipe, HCD_PIPE_EVENT_INVALID, pipe->callback_arg, false);
+            HCD_ENTER_CRITICAL();
+        }
+    }
+    //Process all idle pipes
+    TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) {
+        //Mark pipe as invalid and call its callback
+        pipe->state = HCD_PIPE_STATE_INVALID;
+        pipe->last_event = HCD_PIPE_EVENT_INVALID;
+        if (pipe->callback != NULL) {
+            HCD_EXIT_CRITICAL();
+            (void) pipe->callback((hcd_pipe_handle_t)pipe, HCD_PIPE_EVENT_INVALID, pipe->callback_arg, false);
+            HCD_ENTER_CRITICAL();
+        }
+    }
+}
+
+static bool _port_pause_all_pipes(port_t *port)
+{
+    assert(port->state == HCD_PORT_STATE_ENABLED);
+    pipe_t *pipe;
+    int num_pipes_waiting_done = 0;
+    //Process all pipes that have queued IRPs
+    TAILQ_FOREACH(pipe, &port->pipes_active_tailq, tailq_entry) {
+        //Check if pipe is currently executing
+        if (pipe->multi_buffer_control.buffer_is_executing) {
+            //Pipe is executing a buffer. Indicate to the pipe we are waiting the buffer's transfer to complete
+            pipe->cs_flags.waiting_xfer_done = 1;
+            num_pipes_waiting_done++;
+        } else {
+            //No buffer is being executed so need to wait
+            pipe->cs_flags.paused = 1;
+        }
+    }
+    //Process all idle pipes. They don't have queue transfer so just mark them as paused
+    TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) {
+        pipe->cs_flags.paused = 1;
+    }
+    if (num_pipes_waiting_done > 0) {
+        //Indicate we need to wait for one or more pipes to complete their transfers
+        port->flags.num_pipes_waiting_pause = num_pipes_waiting_done;
+        port->flags.waiting_all_pipes_pause = 1;
+        return false;
+    }
+    return true;
+}
+
+static void _port_unpause_all_pipes(port_t *port)
+{
+    assert(port->state == HCD_PORT_STATE_ENABLED);
+    pipe_t *pipe;
+    //Process all idle pipes. They don't have queue transfer so just mark them as un-paused
+    TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) {
+        pipe->cs_flags.paused = 0;
+    }
+    //Process all pipes that have queued IRPs
+    TAILQ_FOREACH(pipe, &port->pipes_active_tailq, tailq_entry) {
+        pipe->cs_flags.paused = 0;
+        if (_buffer_can_fill(pipe)) {
+            _buffer_fill(pipe);
+        }
+        if (_buffer_can_exec(pipe)) {
+            _buffer_exec(pipe);
+        }
+    }
+}
+
+static bool _port_bus_reset(port_t *port)
+{
+    assert(port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED);
+    //Put and hold the bus in the reset state. If the port was previously enabled, a disabled event will occur after this
+    port->state = HCD_PORT_STATE_RESETTING;
+    usbh_hal_port_toggle_reset(port->hal, true);
+    HCD_EXIT_CRITICAL();
+    vTaskDelay(pdMS_TO_TICKS(RESET_HOLD_MS));
+    HCD_ENTER_CRITICAL();
+    if (port->state != HCD_PORT_STATE_RESETTING) {
+        //The port state has unexpectedly changed
+        goto bailout;
+    }
+    //Return the bus to the idle state and hold it for the required reset recovery time. Port enabled event should occur
+    usbh_hal_port_toggle_reset(port->hal, false);
+    HCD_EXIT_CRITICAL();
+    vTaskDelay(pdMS_TO_TICKS(RESET_RECOVERY_MS));
+    HCD_ENTER_CRITICAL();
+    if (port->state != HCD_PORT_STATE_ENABLED || !port->flags.conn_devc_ena) {
+        //The port state has unexpectedly changed
+        goto bailout;
+    }
+    return true;
+bailout:
+    return false;
+}
+
+static bool _port_bus_suspend(port_t *port)
+{
+    assert(port->state == HCD_PORT_STATE_ENABLED);
+    //Pause all pipes before suspending the bus
+    if (!_port_pause_all_pipes(port)) {
+        //Need to wait for some pipes to pause. Wait for notification from ISR
+        _internal_port_event_wait(port);
+        if (port->state != HCD_PORT_STATE_ENABLED || !port->flags.conn_devc_ena) {
+            //Port state unexpectedly changed
+            goto bailout;
+        }
+    }
+    //All pipes are guaranteed paused at this point. Proceed to suspend the port
+    usbh_hal_port_suspend(port->hal);
+    port->state = HCD_PORT_STATE_SUSPENDED;
+    return true;
+bailout:
+    return false;
+}
+
+static bool _port_bus_resume(port_t *port)
+{
+    assert(port->state == HCD_PORT_STATE_SUSPENDED);
+    //Put and hold the bus in the K state.
+    usbh_hal_port_toggle_resume(port->hal, true);
+    port->state = HCD_PORT_STATE_RESUMING;
+    HCD_EXIT_CRITICAL();
+    vTaskDelay(pdMS_TO_TICKS(RESUME_HOLD_MS));
+    HCD_ENTER_CRITICAL();
+    //Return and hold the bus to the J state (as port of the LS EOP)
+    usbh_hal_port_toggle_resume(port->hal, false);
+    if (port->state != HCD_PORT_STATE_RESUMING || !port->flags.conn_devc_ena) {
+        //Port state unexpectedly changed
+        goto bailout;
+    }
+    HCD_EXIT_CRITICAL();
+    vTaskDelay(pdMS_TO_TICKS(RESUME_RECOVERY_MS));
+    HCD_ENTER_CRITICAL();
+    if (port->state != HCD_PORT_STATE_RESUMING || !port->flags.conn_devc_ena) {
+        //Port state unexpectedly changed
+        goto bailout;
+    }
+    port->state = HCD_PORT_STATE_ENABLED;
+    _port_unpause_all_pipes(port);
+    return true;
+bailout:
+    return false;
+}
+
+static bool _port_disable(port_t *port)
+{
+    assert(port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_SUSPENDED);
+    if (port->state == HCD_PORT_STATE_ENABLED) {
+        //There may be pipes that are still transferring, so pause them.
+        if (!_port_pause_all_pipes(port)) {
+            //Need to wait for some pipes to pause. Wait for notification from ISR
+            _internal_port_event_wait(port);
+            if (port->state != HCD_PORT_STATE_ENABLED || !port->flags.conn_devc_ena) {
+                //Port state unexpectedly changed
+                goto bailout;
+            }
+        }
+    }
+    //All pipes are guaranteed paused at this point. Proceed to suspend the port. This should trigger an internal event
+    port->flags.disable_requested = 1;
+    usbh_hal_port_disable(port->hal);
+    _internal_port_event_wait(port);
+    if (port->state != HCD_PORT_STATE_DISABLED) {
+        //Port state unexpectedly changed
+        goto bailout;
+    }
+    _port_invalidate_all_pipes(port);
+    return true;
+bailout:
+    return false;
+}
+
+static bool _port_debounce(port_t *port)
+{
+    if (port->state == HCD_PORT_STATE_NOT_POWERED) {
+        //Disconnect event due to power off, no need to debounce or update port state.
+        return false;
+    }
+    HCD_EXIT_CRITICAL();
+    vTaskDelay(pdMS_TO_TICKS(DEBOUNCE_DELAY_MS));
+    HCD_ENTER_CRITICAL();
+    //Check the post-debounce state of the bus (i.e., whether it's actually connected/disconnected)
+    bool is_connected = usbh_hal_port_check_if_connected(port->hal);
+    if (is_connected) {
+        port->state = HCD_PORT_STATE_DISABLED;
+    } else {
+        port->state = HCD_PORT_STATE_DISCONNECTED;
+    }
+    //Disable debounce lock
+    usbh_hal_disable_debounce_lock(port->hal);
+    return is_connected;
+}
+
+// ----------------------- Public --------------------------
+
+esp_err_t hcd_port_init(int port_number, hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl)
+{
+    HCD_CHECK(port_number > 0 && port_config != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG);
+    HCD_CHECK(port_number <= NUM_PORTS, ESP_ERR_NOT_FOUND);
+
+    HCD_ENTER_CRITICAL();
+    HCD_CHECK_FROM_CRIT(s_hcd_obj != NULL && !s_hcd_obj->port_obj->initialized, ESP_ERR_INVALID_STATE);
+    //Port object memory and resources (such as the mutex) already be allocated. Just need to initialize necessary fields only
+    port_t *port_obj = s_hcd_obj->port_obj;
+    TAILQ_INIT(&port_obj->pipes_idle_tailq);
+    TAILQ_INIT(&port_obj->pipes_active_tailq);
+    port_obj->state = HCD_PORT_STATE_NOT_POWERED;
+    port_obj->last_event = HCD_PORT_EVENT_NONE;
+    port_obj->callback = port_config->callback;
+    port_obj->callback_arg = port_config->callback_arg;
+    port_obj->context = port_config->context;
+    usbh_hal_init(port_obj->hal);
+    port_obj->initialized = true;
+    esp_intr_enable(s_hcd_obj->isr_hdl);
+    *port_hdl = (hcd_port_handle_t)port_obj;
+    HCD_EXIT_CRITICAL();
+
+    vTaskDelay(pdMS_TO_TICKS(INIT_DELAY_MS));    //Need a short delay before host mode takes effect
+    return ESP_OK;
+}
+
+esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl)
+{
+    port_t *port = (port_t *)port_hdl;
+
+    HCD_ENTER_CRITICAL();
+    HCD_CHECK_FROM_CRIT(s_hcd_obj != NULL && port->initialized
+                        && port->num_pipes_idle == 0 && port->num_pipes_queued == 0
+                        && (port->state == HCD_PORT_STATE_NOT_POWERED || port->state == HCD_PORT_STATE_RECOVERY)
+                        && port->flags.val == 0 && port->task_waiting_port_notif == NULL,
+                        ESP_ERR_INVALID_STATE);
+    port->initialized = false;
+    esp_intr_disable(s_hcd_obj->isr_hdl);
+    usbh_hal_deinit(port->hal);
+    HCD_EXIT_CRITICAL();
+
+    return ESP_OK;
+}
+
+esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command)
+{
+    esp_err_t ret = ESP_ERR_INVALID_STATE;
+    port_t *port = (port_t *)port_hdl;
+    xSemaphoreTake(port->port_mux, portMAX_DELAY);
+    HCD_ENTER_CRITICAL();
+    if (port->initialized && !port->flags.event_pending) { //Port events need to be handled first before issuing a command
+        port->flags.cmd_processing = 1;
+        switch (command) {
+            case HCD_PORT_CMD_POWER_ON: {
+                //Port can only be powered on if currently unpowered
+                if (port->state == HCD_PORT_STATE_NOT_POWERED) {
+                    port->state = HCD_PORT_STATE_DISCONNECTED;
+                    usbh_hal_port_init(port->hal);
+                    usbh_hal_port_toggle_power(port->hal, true);
+                    ret = ESP_OK;
+                }
+                break;
+            }
+            case HCD_PORT_CMD_POWER_OFF: {
+                //Port can only be unpowered if already powered
+                if (port->state != HCD_PORT_STATE_NOT_POWERED) {
+                    port->state = HCD_PORT_STATE_NOT_POWERED;
+                    usbh_hal_port_deinit(port->hal);
+                    usbh_hal_port_toggle_power(port->hal, false);
+                    //If a device is currently connected, this should trigger a disconnect event
+                    ret = ESP_OK;
+                }
+                break;
+            }
+            case HCD_PORT_CMD_RESET: {
+                //Port can only a reset when it is in the enabled or disabled states (in case of new connection)
+                if (port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED) {
+                    if (_port_bus_reset(port)) {
+                        //Set FIFO sizes to default
+                        usbh_hal_set_fifo_size(port->hal, &fifo_config_default);
+                        port->fifo_bias = HCD_PORT_FIFO_BIAS_BALANCED;
+                        //Reset frame list and enable periodic scheduling
+                        memset(port->frame_list, 0, FRAME_LIST_LEN * sizeof(uint32_t));
+                        usbh_hal_port_set_frame_list(port->hal, port->frame_list, FRAME_LIST_LEN);
+                        usbh_hal_port_periodic_enable(port->hal);
+                        ret = ESP_OK;
+                    } else {
+                        ret = ESP_ERR_INVALID_RESPONSE;
+                    }
+                }
+                break;
+            }
+            case HCD_PORT_CMD_SUSPEND: {
+                //Port can only be suspended if already in the enabled state
+                if (port->state == HCD_PORT_STATE_ENABLED) {
+                    ret = (_port_bus_suspend(port)) ? ESP_OK : ESP_ERR_INVALID_RESPONSE;
+                }
+                break;
+            }
+            case HCD_PORT_CMD_RESUME: {
+                //Port can only be resumed if already suspended
+                if (port->state == HCD_PORT_STATE_SUSPENDED) {
+                    ret = (_port_bus_resume(port)) ? ESP_OK : ESP_ERR_INVALID_RESPONSE;
+                }
+                break;
+            }
+            case HCD_PORT_CMD_DISABLE: {
+                //Can only disable the port when already enabled or suspended
+                if (port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_SUSPENDED) {
+                    ret = (_port_disable(port)) ? ESP_OK : ESP_ERR_INVALID_RESPONSE;
+                }
+                break;
+            }
+        }
+        port->flags.cmd_processing = 0;
+    }
+    HCD_EXIT_CRITICAL();
+    xSemaphoreGive(port->port_mux);
+    return ret;
+}
+
+hcd_port_state_t hcd_port_get_state(hcd_port_handle_t port_hdl)
+{
+    port_t *port = (port_t *)port_hdl;
+    hcd_port_state_t ret;
+    HCD_ENTER_CRITICAL();
+    ret = port->state;
+    HCD_EXIT_CRITICAL();
+    return ret;
+}
+
+esp_err_t hcd_port_get_speed(hcd_port_handle_t port_hdl, usb_speed_t *speed)
+{
+    port_t *port = (port_t *)port_hdl;
+    HCD_CHECK(speed != NULL, ESP_ERR_INVALID_ARG);
+    HCD_ENTER_CRITICAL();
+    //Device speed is only valid if there is device connected to the port that has been reset
+    HCD_CHECK_FROM_CRIT(port->flags.conn_devc_ena, ESP_ERR_INVALID_STATE);
+    usb_priv_speed_t hal_speed = usbh_hal_port_get_conn_speed(port->hal);
+    if (hal_speed == USB_PRIV_SPEED_FULL) {
+        *speed = USB_SPEED_FULL;
+    } else {
+        *speed = USB_SPEED_LOW;
+    }
+    HCD_EXIT_CRITICAL();
+    return ESP_OK;
+}
+
+hcd_port_event_t hcd_port_handle_event(hcd_port_handle_t port_hdl)
+{
+    port_t *port = (port_t *)port_hdl;
+    hcd_port_event_t ret = HCD_PORT_EVENT_NONE;
+    xSemaphoreTake(port->port_mux, portMAX_DELAY);
+    HCD_ENTER_CRITICAL();
+    if (port->initialized && port->flags.event_pending) {
+        port->flags.event_pending = 0;
+        port->flags.event_processing = 1;
+        ret = port->last_event;
+        switch (ret) {
+            case HCD_PORT_EVENT_CONNECTION: {
+                if (_port_debounce(port)) {
+                    ret = HCD_PORT_EVENT_CONNECTION;
+                }
+                break;
+            }
+            case HCD_PORT_EVENT_DISCONNECTION:
+                if (_port_debounce(port)) {
+                    //A device is still connected, so it was just a debounce
+                    port->state = HCD_PORT_STATE_DISABLED;
+                    ret = HCD_PORT_EVENT_NONE;
+                } else {
+                    //No device connected after debounce delay. This is an actual disconnection
+                    port->state = HCD_PORT_STATE_DISCONNECTED;
+                    ret = HCD_PORT_EVENT_DISCONNECTION;
+                }
+                break;
+            case HCD_PORT_EVENT_ERROR:
+            case HCD_PORT_EVENT_OVERCURRENT:
+            case HCD_PORT_EVENT_SUDDEN_DISCONN: {
+                _port_invalidate_all_pipes(port);
+                break;
+            }
+            default: {
+                break;
+            }
+        }
+        port->flags.event_processing = 0;
+    } else {
+        ret = HCD_PORT_EVENT_NONE;
+    }
+    HCD_EXIT_CRITICAL();
+    xSemaphoreGive(port->port_mux);
+    return ret;
+}
+
+esp_err_t hcd_port_recover(hcd_port_handle_t port_hdl)
+{
+    port_t *port = (port_t *)port_hdl;
+    HCD_ENTER_CRITICAL();
+    HCD_CHECK_FROM_CRIT(s_hcd_obj != NULL && port->initialized && port->state == HCD_PORT_STATE_RECOVERY
+                        && port->num_pipes_idle == 0 && port->num_pipes_queued == 0
+                        && port->flags.val == 0 && port->task_waiting_port_notif == NULL,
+                        ESP_ERR_INVALID_STATE);
+    //We are about to do a soft reset on the peripheral. Disable the peripheral throughout
+    esp_intr_disable(s_hcd_obj->isr_hdl);
+    usbh_hal_core_soft_reset(port->hal);
+    port->state = HCD_PORT_STATE_NOT_POWERED;
+    port->last_event = HCD_PORT_EVENT_NONE;
+    port->flags.val = 0;
+    esp_intr_enable(s_hcd_obj->isr_hdl);
+    HCD_EXIT_CRITICAL();
+    return ESP_OK;
+}
+
+void *hcd_port_get_context(hcd_port_handle_t port_hdl)
+{
+    port_t *port = (port_t *)port_hdl;
+    void *ret;
+    HCD_ENTER_CRITICAL();
+    ret = port->context;
+    HCD_EXIT_CRITICAL();
+    return ret;
+}
+
+esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_t bias)
+{
+    esp_err_t ret;
+    port_t *port = (port_t *)port_hdl;
+    xSemaphoreTake(port->port_mux, portMAX_DELAY);
+    HCD_ENTER_CRITICAL();
+    //Check that port is in the correct state to update FIFO sizes
+    if (port->initialized && !port->flags.event_pending && port->num_pipes_idle == 0 && port->num_pipes_queued == 0) {
+        const usbh_hal_fifo_config_t *fifo_config;
+        switch (bias) {
+            case HCD_PORT_FIFO_BIAS_BALANCED:
+                fifo_config = &fifo_config_default;
+                break;
+            case HCD_PORT_FIFO_BIAS_RX:
+                fifo_config = &fifo_config_bias_rx;
+                break;
+            case HCD_PORT_FIFO_BIAS_PTX:
+                fifo_config = &fifo_config_bias_ptx;
+                break;
+            default:
+                fifo_config = NULL;
+                abort();
+        }
+        usbh_hal_set_fifo_size(port->hal, fifo_config);
+        port->fifo_bias = bias;
+        ret = ESP_OK;
+    } else {
+        ret = ESP_ERR_INVALID_STATE;
+    }
+    HCD_EXIT_CRITICAL();
+    xSemaphoreGive(port->port_mux);
+    return ret;
+}
+
+// --------------------------------------------------- HCD Pipes -------------------------------------------------------
+
+// ----------------------- Private -------------------------
+
+static bool _pipe_wait_done(pipe_t *pipe)
+{
+    //Check if the pipe has a currently executing buffer
+    if (pipe->multi_buffer_control.buffer_is_executing) {
+        //Wait for pipe to complete its transfer
+        pipe->cs_flags.waiting_xfer_done = 1;
+        _internal_pipe_event_wait(pipe);
+        if (pipe->state == HCD_PIPE_STATE_INVALID) {
+            //The pipe become invalid whilst waiting for its internal event
+            pipe->cs_flags.waiting_xfer_done = 0;  //Need to manually reset this bit in this case
+            return false;
+        }
+        bool chan_halted = usbh_hal_chan_request_halt(pipe->chan_obj);
+        assert(chan_halted);
+        (void) chan_halted;
+    }
+    return true;
+}
+
+static void _pipe_retire(pipe_t *pipe, bool self_initiated)
+{
+    //Cannot have a currently executing buffer
+    assert(!pipe->multi_buffer_control.buffer_is_executing);
+    if (pipe->num_irp_pending > 0) {
+        //Process all remaining pending IRPs
+        usb_irp_t *irp;
+        TAILQ_FOREACH(irp, &pipe->pending_irp_tailq, tailq_entry) {
+            //Update the IRP's current state
+            IRP_STATE_SET(irp->reserved_flags, IRP_STATE_DONE);
+            //If we are initiating the retire, mark the IRP as canceled
+            irp->status = (self_initiated) ? USB_TRANSFER_STATUS_CANCELED : USB_TRANSFER_STATUS_NO_DEVICE;
+        }
+        //Concatenated pending tailq to the done tailq
+        TAILQ_CONCAT(&pipe->done_irp_tailq, &pipe->pending_irp_tailq, tailq_entry);
+        pipe->num_irp_done += pipe->num_irp_pending;
+        pipe->num_irp_pending = 0;
+    }
+}
+
+static inline hcd_pipe_event_t pipe_decode_error_event(usbh_hal_chan_error_t chan_error)
+{
+    hcd_pipe_event_t event = HCD_PIPE_EVENT_NONE;
+    switch (chan_error) {
+        case USBH_HAL_CHAN_ERROR_XCS_XACT:
+            event = HCD_PIPE_EVENT_ERROR_XFER;
+            break;
+        case USBH_HAL_CHAN_ERROR_BNA:
+            event = HCD_PIPE_EVENT_ERROR_IRP_NOT_AVAIL;
+            break;
+        case USBH_HAL_CHAN_ERROR_PKT_BBL:
+            event = HCD_PIPE_EVENT_ERROR_OVERFLOW;
+            break;
+        case USBH_HAL_CHAN_ERROR_STALL:
+            event = HCD_PIPE_EVENT_ERROR_STALL;
+            break;
+    }
+    return event;
+}
+
+static dma_buffer_block_t *buffer_block_alloc(usb_transfer_type_t type)
+{
+    int desc_list_len;
+    switch (type) {
+    case USB_TRANSFER_TYPE_CTRL:
+        desc_list_len = XFER_LIST_LEN_CTRL;
+        break;
+    case USB_TRANSFER_TYPE_ISOCHRONOUS:
+        desc_list_len = XFER_LIST_LEN_ISOC;
+        break;
+    case USB_TRANSFER_TYPE_BULK:
+        desc_list_len = XFER_LIST_LEN_BULK;
+        break;
+    default:    //USB_TRANSFER_TYPE_INTR:
+        desc_list_len = XFER_LIST_LEN_INTR;
+        break;
+    }
+    dma_buffer_block_t *buffer = calloc(1, sizeof(dma_buffer_block_t));
+    void *xfer_desc_list = heap_caps_aligned_calloc(USBH_HAL_DMA_MEM_ALIGN, desc_list_len, sizeof(usbh_ll_dma_qtd_t), MALLOC_CAP_DMA);
+    if (buffer == NULL || xfer_desc_list == NULL) {
+        free(buffer);
+        heap_caps_free(xfer_desc_list);
+        return NULL;
+    }
+    buffer->xfer_desc_list = xfer_desc_list;
+    return buffer;
+}
+
+static void buffer_block_free(dma_buffer_block_t *buffer)
+{
+    if (buffer == NULL) {
+        return;
+    }
+    heap_caps_free(buffer->xfer_desc_list);
+    free(buffer);
+}
+
+static bool pipe_alloc_check_args(const hcd_pipe_config_t *pipe_config, usb_speed_t port_speed, hcd_port_fifo_bias_t fifo_bias, usb_transfer_type_t type, bool is_default_pipe)
+{
+    //Check if pipe can be supported
+    if (port_speed == USB_SPEED_LOW && pipe_config->dev_speed == USB_SPEED_FULL) {
+        //Low speed port does not supported full speed pipe
+        return false;
+    }
+    if (pipe_config->dev_speed == USB_SPEED_LOW && (type == USB_TRANSFER_TYPE_BULK || type == USB_TRANSFER_TYPE_ISOCHRONOUS)) {
+        //Low speed does not support Bulk or Isochronous pipes
+        return false;
+    }
+    //Check interval of pipe
+    if (type == USB_TRANSFER_TYPE_INTR &&
+        (pipe_config->ep_desc->bInterval > 0 && pipe_config->ep_desc->bInterval > 32)) {
+        //Interval not supported for interrupt pipe
+        return false;
+    }
+    if (type == USB_TRANSFER_TYPE_ISOCHRONOUS &&
+        (pipe_config->ep_desc->bInterval > 0 && pipe_config->ep_desc->bInterval > 6)) {
+        //Interval not supported for isochronous pipe (where 0 < 2^(bInterval - 1) <= 32)
+        return false;
+    }
+
+    if (is_default_pipe) {
+        return true;
+    }
+    //Check if MPS is within FIFO limits
+    const fifo_mps_limits_t *mps_limits;
+    switch (fifo_bias) {
+        case HCD_PORT_FIFO_BIAS_BALANCED:
+            mps_limits = &mps_limits_default;
+            break;
+        case HCD_PORT_FIFO_BIAS_RX:
+            mps_limits = &mps_limits_bias_rx;
+            break;
+        default:    //HCD_PORT_FIFO_BIAS_PTX
+            mps_limits = &mps_limits_bias_ptx;
+            break;
+    }
+    int limit;
+    if (USB_DESC_EP_GET_EP_DIR(pipe_config->ep_desc)) { //IN
+        limit = mps_limits->in_mps;
+    } else {    //OUT
+        if (type == USB_TRANSFER_TYPE_CTRL || type == USB_TRANSFER_TYPE_BULK) {
+            limit = mps_limits->non_periodic_out_mps;
+        } else {
+            limit = mps_limits->periodic_out_mps;
+        }
+    }
+    return (pipe_config->ep_desc->wMaxPacketSize <= limit);
+}
+
+static void pipe_set_ep_char(const hcd_pipe_config_t *pipe_config, usb_transfer_type_t type, bool is_default_pipe, int pipe_idx, usb_speed_t port_speed, usbh_hal_ep_char_t *ep_char)
+{
+    //Initialize EP characteristics
+    usb_priv_xfer_type_t hal_xfer_type;
+    switch (type) {
+        case USB_TRANSFER_TYPE_CTRL:
+            hal_xfer_type = USB_PRIV_XFER_TYPE_CTRL;
+            break;
+        case USB_TRANSFER_TYPE_ISOCHRONOUS:
+            hal_xfer_type = USB_PRIV_XFER_TYPE_ISOCHRONOUS;
+            break;
+        case USB_TRANSFER_TYPE_BULK:
+            hal_xfer_type = USB_PRIV_XFER_TYPE_BULK;
+            break;
+        default:    //USB_TRANSFER_TYPE_INTR
+            hal_xfer_type = USB_PRIV_XFER_TYPE_INTR;
+            break;
+    }
+    ep_char->type = hal_xfer_type;
+    if (is_default_pipe) {
+        ep_char->bEndpointAddress = 0;
+        //Set the default pipe's MPS to the worst case MPS for the device's speed
+        ep_char->mps = (pipe_config->dev_speed == USB_SPEED_FULL) ? CTRL_EP_MAX_MPS_FS : CTRL_EP_MAX_MPS_LS;
+    } else {
+        ep_char->bEndpointAddress = pipe_config->ep_desc->bEndpointAddress;
+        ep_char->mps = pipe_config->ep_desc->wMaxPacketSize;
+    }
+    ep_char->dev_addr = pipe_config->dev_addr;
+    ep_char->ls_via_fs_hub = (port_speed == USB_SPEED_FULL && pipe_config->dev_speed == USB_SPEED_LOW);
+    //Calculate the pipe's interval in terms of USB frames
+    if (type == USB_TRANSFER_TYPE_INTR || type == USB_TRANSFER_TYPE_ISOCHRONOUS) {
+        int interval_frames;
+        if (type == USB_TRANSFER_TYPE_INTR) {
+            interval_frames = pipe_config->ep_desc->bInterval;
+        } else {
+            interval_frames = (1 << (pipe_config->ep_desc->bInterval - 1));
+        }
+        //Round down interval to nearest power of 2
+        if (interval_frames >= 32) {
+            interval_frames = 32;
+        } else if (interval_frames >= 16) {
+            interval_frames = 16;
+        } else if (interval_frames >= 8) {
+            interval_frames = 8;
+        } else if (interval_frames >= 4) {
+            interval_frames = 4;
+        } else if (interval_frames >= 2) {
+            interval_frames = 2;
+        } else if (interval_frames >= 1) {
+            interval_frames = 1;
+        }
+        ep_char->periodic.interval = interval_frames;
+        //We are the Nth pipe to be allocated. Use N as a phase offset
+        ep_char->periodic.phase_offset_frames = pipe_idx & (XFER_LIST_LEN_ISOC - 1);
+    }else {
+        ep_char->periodic.interval = 0;
+        ep_char->periodic.phase_offset_frames = 0;
+    }
+}
+
+// ----------------------- Public --------------------------
+
+esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pipe_config, hcd_pipe_handle_t *pipe_hdl)
+{
+    HCD_CHECK(port_hdl != NULL && pipe_config != NULL && pipe_hdl != NULL, ESP_ERR_INVALID_ARG);
+    port_t *port = (port_t *)port_hdl;
+    HCD_ENTER_CRITICAL();
+    //Can only allocate a pipe if the target port is initialized and connected to an enabled device
+    HCD_CHECK_FROM_CRIT(port->initialized && port->flags.conn_devc_ena, ESP_ERR_INVALID_STATE);
+    usb_speed_t port_speed = port->speed;
+    hcd_port_fifo_bias_t port_fifo_bias = port->fifo_bias;
+    int pipe_idx = port->num_pipes_idle + port->num_pipes_queued;
+    HCD_EXIT_CRITICAL();
+
+    usb_transfer_type_t type;
+    bool is_default;
+    if (pipe_config->ep_desc == NULL) {
+        type = USB_TRANSFER_TYPE_CTRL;
+        is_default = true;
+    } else {
+        type = USB_DESC_EP_GET_XFERTYPE(pipe_config->ep_desc);
+        is_default = false;
+    }
+    //Check if pipe configuration can be supported
+    if (!pipe_alloc_check_args(pipe_config, port_speed, port_fifo_bias, type, is_default)) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    esp_err_t ret;
+    //Allocate the pipe resources
+    pipe_t *pipe = calloc(1, sizeof(pipe_t));
+    usbh_hal_chan_t *chan_obj = calloc(1, sizeof(usbh_hal_chan_t));
+    dma_buffer_block_t *buffers[NUM_BUFFERS] = {0};
+    if (pipe == NULL|| chan_obj == NULL) {
+        ret = ESP_ERR_NO_MEM;
+        goto err;
+    }
+    for (int i = 0; i < NUM_BUFFERS; i++) {
+        buffers[i] = buffer_block_alloc(type);
+        if (buffers[i] == NULL) {
+            ret = ESP_ERR_NO_MEM;
+            goto err;
+        }
+    }
+
+    //Initialize pipe object
+    TAILQ_INIT(&pipe->pending_irp_tailq);
+    TAILQ_INIT(&pipe->done_irp_tailq);
+    for (int i = 0; i < NUM_BUFFERS; i++) {
+        pipe->buffers[i] = buffers[i];
+    }
+    pipe->multi_buffer_control.buffer_num_to_fill = NUM_BUFFERS;
+    pipe->port = port;
+    pipe->chan_obj = chan_obj;
+    usbh_hal_ep_char_t ep_char;
+    pipe_set_ep_char(pipe_config, type, is_default, pipe_idx, port_speed, &ep_char);
+    memcpy(&pipe->ep_char, &ep_char, sizeof(usbh_hal_ep_char_t));
+    pipe->state = HCD_PIPE_STATE_ACTIVE;
+    pipe->callback = pipe_config->callback;
+    pipe->callback_arg = pipe_config->callback_arg;
+    pipe->context = pipe_config->context;
+
+    //Allocate channel
+    HCD_ENTER_CRITICAL();
+    if (!port->initialized || !port->flags.conn_devc_ena) {
+        HCD_EXIT_CRITICAL();
+        ret = ESP_ERR_INVALID_STATE;
+        goto err;
+    }
+    bool chan_allocated = usbh_hal_chan_alloc(port->hal, pipe->chan_obj, (void *) pipe);
+    if (!chan_allocated) {
+        HCD_EXIT_CRITICAL();
+        ret = ESP_ERR_NOT_SUPPORTED;
+        goto err;
+    }
+    usbh_hal_chan_set_ep_char(port->hal, pipe->chan_obj, &pipe->ep_char);
+    //Add the pipe to the list of idle pipes in the port object
+    TAILQ_INSERT_TAIL(&port->pipes_idle_tailq, pipe, tailq_entry);
+    port->num_pipes_idle++;
+    HCD_EXIT_CRITICAL();
+
+    *pipe_hdl = (hcd_pipe_handle_t)pipe;
+    return ESP_OK;
+
+err:
+    for (int i = 0; i < NUM_BUFFERS; i++) {
+        buffer_block_free(buffers[i]);
+    }
+    free(chan_obj);
+    free(pipe);
+    return ret;
+}
+
+esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl)
+{
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    HCD_ENTER_CRITICAL();
+    //Check that all IRPs have been removed and pipe has no pending events
+    HCD_CHECK_FROM_CRIT(!pipe->multi_buffer_control.buffer_is_executing
+                        && pipe->multi_buffer_control.buffer_num_to_parse == 0
+                        && pipe->multi_buffer_control.buffer_num_to_exec == 0
+                        && pipe->num_irp_pending == 0
+                        && pipe->num_irp_done == 0,
+                        ESP_ERR_INVALID_STATE);
+    //Remove pipe from the list of idle pipes (it must be in the idle list because it should have no queued IRPs)
+    TAILQ_REMOVE(&pipe->port->pipes_idle_tailq, pipe, tailq_entry);
+    pipe->port->num_pipes_idle--;
+    usbh_hal_chan_free(pipe->port->hal, pipe->chan_obj);
+    HCD_EXIT_CRITICAL();
+
+    //Free pipe resources
+    for (int i = 0; i < NUM_BUFFERS; i++) {
+        buffer_block_free(pipe->buffers[i]);
+    }
+    free(pipe->chan_obj);
+    free(pipe);
+    return ESP_OK;
+}
+
+esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps)
+{
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    HCD_ENTER_CRITICAL();
+    //Check if pipe is in the correct state to be updated
+    HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID
+                        && !pipe->cs_flags.pipe_cmd_processing
+                        && pipe->num_irp_pending == 0
+                        && pipe->num_irp_done == 0,
+                        ESP_ERR_INVALID_STATE);
+    pipe->ep_char.mps = mps;
+    //Update the underlying channel's registers
+    usbh_hal_chan_set_ep_char(pipe->port->hal, pipe->chan_obj, &pipe->ep_char);
+    HCD_EXIT_CRITICAL();
+    return ESP_OK;
+}
+
+esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr)
+{
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    HCD_ENTER_CRITICAL();
+    //Check if pipe is in the correct state to be updated
+    HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID
+                        && !pipe->cs_flags.pipe_cmd_processing
+                        && pipe->num_irp_pending == 0
+                        && pipe->num_irp_done == 0,
+                        ESP_ERR_INVALID_STATE);
+    pipe->ep_char.dev_addr = dev_addr;
+    //Update the underlying channel's registers
+    usbh_hal_chan_set_ep_char(pipe->port->hal, pipe->chan_obj, &pipe->ep_char);
+    HCD_EXIT_CRITICAL();
+    return ESP_OK;
+}
+
+void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl)
+{
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    void *ret;
+    HCD_ENTER_CRITICAL();
+    ret = pipe->context;
+    HCD_EXIT_CRITICAL();
+    return ret;
+}
+
+hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl)
+{
+    hcd_pipe_state_t ret;
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    HCD_ENTER_CRITICAL();
+    //If there is no enabled device, all existing pipes are invalid.
+    if (pipe->port->state != HCD_PORT_STATE_ENABLED
+        && pipe->port->state != HCD_PORT_STATE_SUSPENDED
+        && pipe->port->state != HCD_PORT_STATE_RESUMING) {
+            ret = HCD_PIPE_STATE_INVALID;
+    } else {
+        ret = pipe->state;
+    }
+    HCD_EXIT_CRITICAL();
+    return ret;
+}
+
+esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command)
+{
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    bool ret = ESP_OK;
+
+    HCD_ENTER_CRITICAL();
+    //Cannot execute pipe commands the pipe is already executing a command, or if the pipe or its port are no longer valid
+    if (pipe->cs_flags.pipe_cmd_processing || !pipe->port->flags.conn_devc_ena || pipe->state == HCD_PIPE_STATE_INVALID) {
+        ret = ESP_ERR_INVALID_STATE;
+    } else {
+        pipe->cs_flags.pipe_cmd_processing = 1;
+        switch (command) {
+            case HCD_PIPE_CMD_ABORT: {
+                //Retire all scheduled IRPs. Pipe's state remains unchanged
+                if (!_pipe_wait_done(pipe)) {   //Stop any on going transfers
+                    ret = ESP_ERR_INVALID_RESPONSE;
+                }
+                _buffer_flush_all(pipe, true);  //Some buffers might still be filled. Flush them
+                _pipe_retire(pipe, true);  //Retire any pending transfers
+                break;
+            }
+            case HCD_PIPE_CMD_RESET: {
+                //Retire all scheduled IRPs. Pipe's state moves to active
+                if (!_pipe_wait_done(pipe)) {   //Stop any on going transfers
+                    ret = ESP_ERR_INVALID_RESPONSE;
+                    break;
+                }
+                _buffer_flush_all(pipe, true);  //Some buffers might still be filled. Flush them
+                _pipe_retire(pipe, true);  //Retire any pending transfers
+                pipe->state = HCD_PIPE_STATE_ACTIVE;
+                break;
+            }
+            case HCD_PIPE_CMD_CLEAR: {  //Can only do this if port is still active
+                //Pipe's state moves from halted to active
+                if (pipe->state == HCD_PIPE_STATE_HALTED) {
+                    pipe->state = HCD_PIPE_STATE_ACTIVE;
+                    //Start the next pending transfer if it exists
+                    if (_buffer_can_fill(pipe)) {
+                        _buffer_fill(pipe);
+                    }
+                    if (_buffer_can_exec(pipe)) {
+                        _buffer_exec(pipe);
+                    }
+                }
+                break;
+            }
+            case HCD_PIPE_CMD_HALT: {
+                //Pipe's state moves to halted
+                if (!_pipe_wait_done(pipe)) {   //Stop any on going transfers
+                    ret = ESP_ERR_INVALID_RESPONSE;
+                    break;
+                }
+                pipe->state = HCD_PIPE_STATE_HALTED;
+                break;
+            }
+        }
+        pipe->cs_flags.pipe_cmd_processing = 0;
+    }
+    HCD_EXIT_CRITICAL();
+    return ret;
+}
+
+hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl)
+{
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    hcd_pipe_event_t ret;
+    HCD_ENTER_CRITICAL();
+    ret = pipe->last_event;
+    pipe->last_event = HCD_PIPE_EVENT_NONE;
+    HCD_EXIT_CRITICAL();
+    return ret;
+}
+
+// ------------------------------------------------- Buffer Control ----------------------------------------------------
+
+static inline void _buffer_fill_ctrl(dma_buffer_block_t *buffer, usb_irp_t *irp)
+{
+    //Get information about the control transfer by analyzing the setup packet (the first 8 bytes of the IRP's data)
+    usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)irp->data_buffer;
+    bool data_stg_in = (ctrl_req->bRequestType & USB_B_REQUEST_TYPE_DIR_IN);
+    bool data_stg_skip = (irp->num_bytes == 0);
+    //Fill setup stage
+    usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, irp->data_buffer, sizeof(usb_ctrl_req_t),
+                            USBH_HAL_XFER_DESC_FLAG_SETUP | USBH_HAL_XFER_DESC_FLAG_HOC);
+    //Fill data stage
+    if (data_stg_skip) {
+        //Not data stage. Fill with an empty descriptor
+        usbh_hal_xfer_desc_clear(buffer->xfer_desc_list, 1);
+    } else {
+        //Fill data stage
+        usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, irp->data_buffer + sizeof(usb_ctrl_req_t), irp->num_bytes,
+                                ((data_stg_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0) | USBH_HAL_XFER_DESC_FLAG_HOC);
+    }
+    //Fill status stage (i.e., a zero length packet). If data stage is skipped, the status stage is always IN.
+    usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 2, NULL, 0,
+                            ((data_stg_in && !data_stg_skip) ? 0 : USBH_HAL_XFER_DESC_FLAG_IN) | USBH_HAL_XFER_DESC_FLAG_HOC);
+    //Update buffer flags
+    buffer->flags.ctrl.data_stg_in = data_stg_in;
+    buffer->flags.ctrl.data_stg_skip = data_stg_skip;
+    buffer->flags.ctrl.cur_stg = 0;
+}
+
+static inline void _buffer_fill_bulk(dma_buffer_block_t *buffer, usb_irp_t *irp, bool is_in)
+{
+    if (is_in) {
+        usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, irp->data_buffer, irp->num_bytes,
+                                USBH_HAL_XFER_DESC_FLAG_IN | USBH_HAL_XFER_DESC_FLAG_HOC);
+    } else if (irp->flags & USB_IRP_FLAG_ZERO_PACK) {
+        //We need to add an extra zero length packet, so two descriptors are used
+        usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, irp->data_buffer, irp->num_bytes, 0);
+        usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, NULL, 0, USBH_HAL_XFER_DESC_FLAG_HOC);
+    } else {
+        usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, irp->data_buffer, irp->num_bytes, USBH_HAL_XFER_DESC_FLAG_HOC);
+    }
+    //Update buffer flags
+    buffer->flags.bulk.zero_len_packet = (is_in && (irp->flags & USB_IRP_FLAG_ZERO_PACK)) ? 1 : 0;
+}
+
+static inline void _buffer_fill_intr(dma_buffer_block_t *buffer, usb_irp_t *irp, bool is_in, int mps)
+{
+    int num_qtds;
+    if (is_in) {
+        assert(irp->num_bytes % mps == 0);  //IN transfers MUST be integer multiple of MPS
+        num_qtds = irp->num_bytes / mps;
+    } else {
+        num_qtds = irp->num_bytes / mps;    //Floor division for number of MPS packets
+        if (irp->num_bytes % irp->num_bytes > 0) {
+            num_qtds++; //For the last shot packet
+        }
+    }
+    assert(num_qtds <= XFER_LIST_LEN_INTR);
+    //Fill all but last descriptor
+    int bytes_filled = 0;
+    for (int i = 0; i < num_qtds - 1; i++) {
+        usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, i, &irp->data_buffer[bytes_filled], mps, (is_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0);
+        bytes_filled += mps;
+    }
+    //Fill in the last descriptor with HOC flag
+    usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, num_qtds - 1, &irp->data_buffer[bytes_filled], irp->num_bytes - bytes_filled,
+                            ((is_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0) | USBH_HAL_XFER_DESC_FLAG_HOC);
+    //Update buffer members and flags
+    buffer->flags.intr.num_qtds = num_qtds;
+}
+
+static inline void _buffer_fill_isoc(dma_buffer_block_t *buffer, usb_irp_t *irp, bool is_in, int mps, int interval, int start_idx)
+{
+    assert(interval > 0);
+    int total_num_desc = irp->num_iso_packets * interval;
+    assert(total_num_desc <= XFER_LIST_LEN_ISOC);
+    int desc_idx = start_idx;
+    int bytes_filled = 0;
+    //For each packet, fill in a descriptor and a interval-1 blank descriptor after it
+    for (int pkt_idx = 0; pkt_idx < irp->num_iso_packets; pkt_idx++) {
+        int xfer_len = irp->iso_packet_desc[pkt_idx].length;
+        uint32_t flags = (is_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0;
+        if (pkt_idx == irp->num_iso_packets - 1) {
+            //Last packet, set the the HOC flag
+            flags |= USBH_HAL_XFER_DESC_FLAG_HOC;
+        }
+        usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, desc_idx, &irp->data_buffer[bytes_filled], xfer_len, flags);
+        bytes_filled += xfer_len;
+        if (++desc_idx >= XFER_LIST_LEN_ISOC) {
+            desc_idx = 0;
+        }
+        //Clear descriptors for unscheduled frames
+        for (int i = 0; i < interval - 1; i++) {
+            usbh_hal_xfer_desc_clear(buffer->xfer_desc_list, desc_idx);
+            if (++desc_idx >= XFER_LIST_LEN_ISOC) {
+                desc_idx = 0;
+            }
+        }
+    }
+    //Update buffer members and flags
+    buffer->flags.isoc.num_qtds = total_num_desc;
+    buffer->flags.isoc.interval = interval;
+    buffer->flags.isoc.irp_start_idx = start_idx;
+    buffer->flags.isoc.next_irp_start_idx = desc_idx;
+}
+
+static void _buffer_fill(pipe_t *pipe)
+{
+    //Get an IRP from the pending tailq
+    usb_irp_t *irp = TAILQ_FIRST(&pipe->pending_irp_tailq);
+    assert(pipe->num_irp_pending > 0 && irp != NULL);
+    TAILQ_REMOVE(&pipe->pending_irp_tailq, irp, tailq_entry);
+    pipe->num_irp_pending--;
+
+    //Select the inactive buffer
+    assert(pipe->multi_buffer_control.buffer_num_to_exec <= NUM_BUFFERS);
+    dma_buffer_block_t *buffer_to_fill = pipe->buffers[pipe->multi_buffer_control.wr_idx];
+    assert(buffer_to_fill->irp == NULL);
+    bool is_in = pipe->ep_char.bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
+    int mps = pipe->ep_char.mps;
+    switch (pipe->ep_char.type) {
+        case USB_PRIV_XFER_TYPE_CTRL: {
+            _buffer_fill_ctrl(buffer_to_fill, irp);
+            break;
+        }
+        case USB_PRIV_XFER_TYPE_ISOCHRONOUS: {
+            uint32_t start_idx;
+            if (pipe->multi_buffer_control.buffer_num_to_exec == 0) {
+                //There are no more previously filled buffers to execute. We need to calculate a new start index based on HFNUM and the pipe's schedule
+                uint32_t cur_frame_num = usbh_hal_port_get_cur_frame_num(pipe->port->hal);
+                uint32_t cur_mod_idx_no_offset = (cur_frame_num - pipe->ep_char.periodic.phase_offset_frames) & (XFER_LIST_LEN_ISOC - 1);    //Get the modulated index (i.e., the Nth desc in the descriptor list)
+                //This is the non-offset modulated QTD index of the last scheduled interval
+                uint32_t last_interval_mod_idx_no_offset = (cur_mod_idx_no_offset / pipe->ep_char.periodic.interval) * pipe->ep_char.periodic.interval; //Floor divide and the multiply again
+                uint32_t next_interval_idx_no_offset = (last_interval_mod_idx_no_offset + pipe->ep_char.periodic.interval);
+                //We want at least a half interval or 2 frames of buffer space
+                if (next_interval_idx_no_offset - cur_mod_idx_no_offset > (pipe->ep_char.periodic.interval / 2)
+                    && next_interval_idx_no_offset - cur_mod_idx_no_offset >= 2) {
+                        start_idx = (next_interval_idx_no_offset + pipe->ep_char.periodic.phase_offset_frames) & (XFER_LIST_LEN_ISOC - 1);
+                } else {
+                    //Not enough time until the next schedule, add another interval to it.
+                        start_idx =  (next_interval_idx_no_offset + pipe->ep_char.periodic.interval + pipe->ep_char.periodic.phase_offset_frames) & (XFER_LIST_LEN_ISOC - 1);
+                }
+            } else {
+                //Start index is based on previously filled buffer
+                uint32_t prev_buffer_idx = (pipe->multi_buffer_control.wr_idx - 1) & (NUM_BUFFERS - 1);
+                dma_buffer_block_t *prev_filled_buffer = pipe->buffers[prev_buffer_idx];
+                start_idx = prev_filled_buffer->flags.isoc.next_irp_start_idx;
+            }
+            _buffer_fill_isoc(buffer_to_fill, irp, is_in, mps, (int)pipe->ep_char.periodic.interval, start_idx);
+            break;
+        }
+        case USB_PRIV_XFER_TYPE_BULK: {
+            _buffer_fill_bulk(buffer_to_fill, irp, is_in);
+            break;
+        }
+        case USB_PRIV_XFER_TYPE_INTR: {
+            _buffer_fill_intr(buffer_to_fill, irp, is_in, mps);
+            break;
+        }
+        default: {
+            abort();
+            break;
+        }
+    }
+    buffer_to_fill->irp = irp;
+    IRP_STATE_SET(irp->reserved_flags, IRP_STATE_INFLIGHT);
+    //Update multi buffer flags
+    pipe->multi_buffer_control.wr_idx++;
+    pipe->multi_buffer_control.buffer_num_to_fill--;
+    pipe->multi_buffer_control.buffer_num_to_exec++;
+}
+
+static void _buffer_exec(pipe_t *pipe)
+{
+    assert(pipe->multi_buffer_control.rd_idx != pipe->multi_buffer_control.wr_idx || pipe->multi_buffer_control.buffer_num_to_exec > 0);
+    dma_buffer_block_t *buffer_to_exec = pipe->buffers[pipe->multi_buffer_control.rd_idx];
+    assert(buffer_to_exec->irp != NULL);
+
+    uint32_t start_idx;
+    int desc_list_len;
+    switch (pipe->ep_char.type) {
+        case USB_PRIV_XFER_TYPE_CTRL: {
+            start_idx = 0;
+            desc_list_len = XFER_LIST_LEN_CTRL;
+            //Set the channel's direction to OUT and PID to 0 respectively for the the setup stage
+            usbh_hal_chan_set_dir(pipe->chan_obj, false);   //Setup stage is always OUT
+            usbh_hal_chan_set_pid(pipe->chan_obj, 0);   //Setup stage always has a PID of DATA0
+            break;
+        }
+        case USB_PRIV_XFER_TYPE_ISOCHRONOUS: {
+            start_idx = buffer_to_exec->flags.isoc.irp_start_idx;
+            desc_list_len = XFER_LIST_LEN_ISOC;
+            break;
+        }
+        case USB_PRIV_XFER_TYPE_BULK: {
+            start_idx = 0;
+            desc_list_len = (buffer_to_exec->flags.bulk.zero_len_packet) ? XFER_LIST_LEN_BULK : 1;
+            break;
+        }
+        case USB_PRIV_XFER_TYPE_INTR: {
+            start_idx = 0;
+            desc_list_len = buffer_to_exec->flags.intr.num_qtds;
+            break;
+        }
+        default: {
+            start_idx = 0;
+            desc_list_len = 0;
+            abort();
+            break;
+        }
+    }
+    //Update buffer and multi buffer flags
+    buffer_to_exec->status_flags.executing = 1;
+    pipe->multi_buffer_control.buffer_is_executing = 1;
+    usbh_hal_chan_activate(pipe->chan_obj, buffer_to_exec->xfer_desc_list, desc_list_len, start_idx);
+}
+
+static bool _buffer_check_done(pipe_t *pipe)
+{
+    if (pipe->ep_char.type != USB_PRIV_XFER_TYPE_CTRL) {
+        return true;
+    }
+    //Only control transfers need to be continued
+    dma_buffer_block_t *buffer_inflight = pipe->buffers[pipe->multi_buffer_control.rd_idx];
+    bool next_dir_is_in;
+    int next_pid;
+    if (buffer_inflight->flags.ctrl.cur_stg == 0) { //Just finished control stage
+        if (buffer_inflight->flags.ctrl.data_stg_skip) {
+            //Skipping data stage. Go straight to status stage
+            next_dir_is_in = true;     //With no data stage, status stage must be IN
+            next_pid = 1;       //Status stage always has a PID of DATA1
+            buffer_inflight->flags.ctrl.cur_stg = 2;    //Skip over the null descriptor representing the skipped data stage
+        } else {
+            //Go to data stage
+            next_dir_is_in = buffer_inflight->flags.ctrl.data_stg_in;
+            next_pid = 1;   //Data stage always starts with a PID of DATA1
+            buffer_inflight->flags.ctrl.cur_stg = 1;
+        }
+    } else if (buffer_inflight->flags.ctrl.cur_stg == 1) {  //Just finished data stage. Go to status stage
+        next_dir_is_in = !buffer_inflight->flags.ctrl.data_stg_in;  //Status stage is always the opposite direction of data stage
+        next_pid = 1;   //Status stage always has a PID of DATA1
+        buffer_inflight->flags.ctrl.cur_stg = 2;
+    } else {    //Just finished status stage. Transfer is complete
+        return true;
+    }
+    //Continue the control transfer
+    usbh_hal_chan_set_dir(pipe->chan_obj, next_dir_is_in);
+    usbh_hal_chan_set_pid(pipe->chan_obj, next_pid);
+    usbh_hal_chan_activate(pipe->chan_obj, buffer_inflight->xfer_desc_list, XFER_LIST_LEN_CTRL, buffer_inflight->flags.ctrl.cur_stg);
+    return false;
+}
+
+static inline void _buffer_parse_ctrl(dma_buffer_block_t *buffer)
+{
+    usb_irp_t *irp = buffer->irp;
+    //Update IRP's actual number of bytes
+    if (buffer->flags.ctrl.data_stg_skip)     {
+        //There was no data stage. Just set the actual length to zero
+        irp->actual_num_bytes = 0;
+    } else {
+        //Parse the data stage for the remaining length
+        int rem_len;
+        int desc_status;
+        usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, 1, &rem_len, &desc_status);
+        assert(desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS);
+        assert(rem_len <= irp->num_bytes);
+        irp->actual_num_bytes = irp->num_bytes - rem_len;
+    }
+    //Update IRP status
+    irp->status = USB_TRANSFER_STATUS_COMPLETED;
+    //Clear the descriptor list
+    memset(buffer->xfer_desc_list, XFER_LIST_LEN_CTRL, sizeof(usbh_ll_dma_qtd_t));
+}
+
+static inline void _buffer_parse_bulk(dma_buffer_block_t *buffer)
+{
+    usb_irp_t *irp = buffer->irp;
+    //Update IRP's actual number of bytes
+    int rem_len;
+    int desc_status;
+    usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, 0, &rem_len, &desc_status);
+    assert(desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS);
+    assert(rem_len <= irp->num_bytes);
+    irp->actual_num_bytes = irp->num_bytes - rem_len;
+    //Update IRP's status
+    irp->status = USB_TRANSFER_STATUS_COMPLETED;
+    //Clear the descriptor list
+    memset(buffer->xfer_desc_list, XFER_LIST_LEN_BULK, sizeof(usbh_ll_dma_qtd_t));
+}
+
+static inline void _buffer_parse_intr(dma_buffer_block_t *buffer, bool is_in, int mps)
+{
+    usb_irp_t *irp = buffer->irp;
+    int intr_stop_idx = buffer->status_flags.stop_idx;
+    if (is_in) {
+        if (intr_stop_idx > 0) { //This is an early stop (short packet)
+            assert(intr_stop_idx <= buffer->flags.intr.num_qtds);
+            int rem_len;
+            int desc_status;
+            for (int i = 0; i < intr_stop_idx - 1; i++) {    //Check all packets before the short
+                usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, i, &rem_len, &desc_status);
+                assert(rem_len == 0 && desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS);
+            }
+            //Check the short packet
+            usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, intr_stop_idx - 1, &rem_len, &desc_status);
+            assert(rem_len > 0 && desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS);
+            //Update actual bytes
+            irp->actual_num_bytes = (mps * intr_stop_idx - 2) + (mps - rem_len);
+        } else {
+            //Check that all but the last packet transmitted MPS
+            for (int i = 0; i < buffer->flags.intr.num_qtds - 1; i++) {
+                int rem_len;
+                int desc_status;
+                usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, i, &rem_len, &desc_status);
+                assert(rem_len == 0 && desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS);
+            }
+            //Check the last packet
+            int last_packet_rem_len;
+            int last_packet_desc_status;
+            usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, buffer->flags.intr.num_qtds - 1, &last_packet_rem_len, &last_packet_desc_status);
+            assert(last_packet_desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS);
+            //All packets except last MUST be MPS. So just deduct the remaining length of the last packet to get actual number of bytes
+            irp->actual_num_bytes = irp->num_bytes - last_packet_rem_len;
+        }
+    } else {
+        //OUT INTR transfers can only complete successfully if all MPS packets have been transmitted. Double check
+        for (int i = 0 ; i < buffer->flags.intr.num_qtds; i++) {
+            int rem_len;
+            int desc_status;
+            usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, i, &rem_len, &desc_status);
+            assert(rem_len == 0 && desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS);
+        }
+        irp->actual_num_bytes = irp->num_bytes;
+    }
+    //Update IRP's status
+    irp->status = USB_TRANSFER_STATUS_COMPLETED;
+    //Clear the descriptor list
+    memset(buffer->xfer_desc_list, XFER_LIST_LEN_INTR, sizeof(usbh_ll_dma_qtd_t));
+}
+
+static inline void _buffer_parse_isoc(dma_buffer_block_t *buffer, bool is_in)
+{
+    usb_irp_t *irp = buffer->irp;
+    int desc_idx = buffer->flags.isoc.irp_start_idx;    //Descriptor index tracks which descriptor in the QTD list
+    for (int pkt_idx = 0; pkt_idx < irp->num_iso_packets; pkt_idx++) {
+        //Clear the filled descriptor
+        int rem_len;
+        int desc_status;
+        usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, desc_idx, &rem_len, &desc_status);
+        usbh_hal_xfer_desc_clear(buffer->xfer_desc_list, desc_idx);
+        assert(rem_len == 0 || is_in);
+        assert(desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS || USBH_HAL_XFER_DESC_STS_NOT_EXECUTED);
+        assert(rem_len <= irp->iso_packet_desc[pkt_idx].length);    //Check for DMA errata
+        //Update ISO packet actual length and status
+        irp->iso_packet_desc[pkt_idx].actual_length = irp->iso_packet_desc[pkt_idx].length - rem_len;
+        irp->iso_packet_desc[pkt_idx].status = (desc_status == USBH_HAL_XFER_DESC_STS_NOT_EXECUTED) ? USB_TRANSFER_STATUS_SKIPPED : USB_TRANSFER_STATUS_COMPLETED;
+        //A descriptor is also allocated for unscheduled frames. We need to skip over them
+        desc_idx += buffer->flags.isoc.interval;
+        if (desc_idx >= XFER_LIST_LEN_INTR) {
+            desc_idx -= XFER_LIST_LEN_INTR;
+        }
+    }
+}
+
+static inline void _buffer_parse_error(dma_buffer_block_t *buffer)
+{
+    //The IRP had an error, so we consider that NO bytes were transferred
+    usb_irp_t *irp = buffer->irp;
+    irp->actual_num_bytes = 0;
+    for (int i = 0; i < irp->num_iso_packets; i++) {
+        irp->iso_packet_desc[i].actual_length = 0;
+    }
+    //Update status of IRP
+    if (buffer->status_flags.cancelled) {
+        irp->status = USB_TRANSFER_STATUS_CANCELED;
+    } else if (buffer->status_flags.pipe_state == HCD_PIPE_STATE_INVALID) {
+        irp->status = USB_TRANSFER_STATUS_NO_DEVICE;
+    } else {
+        switch (buffer->status_flags.pipe_event) {
+            case HCD_PIPE_EVENT_ERROR_XFER: //Excessive transaction error
+                irp->status = USB_TRANSFER_STATUS_ERROR;
+                break;
+            case HCD_PIPE_EVENT_ERROR_OVERFLOW:
+                irp->status = USB_TRANSFER_STATUS_OVERFLOW;
+                break;
+            case HCD_PIPE_EVENT_ERROR_STALL:
+                irp->status = USB_TRANSFER_STATUS_STALL;
+                break;
+            case HCD_PIPE_EVENT_IRP_DONE:   //Special case where we are cancelling an IRP due to pipe_retire
+                irp->status = USB_TRANSFER_STATUS_CANCELED;
+                break;
+            default:
+                //HCD_PIPE_EVENT_ERROR_IRP_NOT_AVAIL should never occur
+                abort();
+                break;
+        }
+    }
+    //Clear error flags
+    buffer->status_flags.val = 0;
+}
+
+static void _buffer_parse(pipe_t *pipe)
+{
+    assert(pipe->multi_buffer_control.buffer_num_to_parse > 0);
+    dma_buffer_block_t *buffer_to_parse = pipe->buffers[pipe->multi_buffer_control.fr_idx];
+    assert(buffer_to_parse->irp != NULL);
+    bool is_in = pipe->ep_char.bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
+    int mps = pipe->ep_char.mps;
+
+    //Parsing the buffer will update the buffer's corresponding IRP
+    if (buffer_to_parse->status_flags.error_occurred) {
+        _buffer_parse_error(buffer_to_parse);
+    } else {
+        switch (pipe->ep_char.type) {
+            case USB_PRIV_XFER_TYPE_CTRL: {
+                _buffer_parse_ctrl(buffer_to_parse);
+                break;
+            }
+            case USB_PRIV_XFER_TYPE_ISOCHRONOUS: {
+                _buffer_parse_isoc(buffer_to_parse, is_in);
+                break;
+            }
+            case USB_PRIV_XFER_TYPE_BULK: {
+                _buffer_parse_bulk(buffer_to_parse);
+                break;
+            }
+            case USB_PRIV_XFER_TYPE_INTR: {
+                _buffer_parse_intr(buffer_to_parse, is_in, mps);
+                break;
+            }
+            default: {
+                abort();
+                break;
+            }
+        }
+    }
+    usb_irp_t *irp = buffer_to_parse->irp;
+    IRP_STATE_SET(irp->reserved_flags, IRP_STATE_DONE);
+    buffer_to_parse->irp = NULL;
+    buffer_to_parse->flags.val = 0; //Clear flags
+    //Move the IRP to the done tailq
+    TAILQ_INSERT_TAIL(&pipe->done_irp_tailq, irp, tailq_entry);
+    pipe->num_irp_done++;
+    //Update multi buffer flags
+    pipe->multi_buffer_control.fr_idx++;
+    pipe->multi_buffer_control.buffer_num_to_parse--;
+    pipe->multi_buffer_control.buffer_num_to_fill++;
+}
+
+static void _buffer_flush_all(pipe_t *pipe, bool cancelled)
+{
+    int cur_num_to_mark_done =  pipe->multi_buffer_control.buffer_num_to_exec;
+    for (int i = 0; i < cur_num_to_mark_done; i++) {
+        //Mark any filled buffers as done
+        _buffer_done_error(pipe, 0, pipe->state, pipe->last_event, cancelled);
+    }
+    int cur_num_to_parse = pipe->multi_buffer_control.buffer_num_to_parse;
+    for (int i = 0; i < cur_num_to_parse; i++) {
+        _buffer_parse(pipe);
+    }
+    //At this point, there should be no more filled buffers. Only IRPs in the pending or done tailq
+}
+
+// ---------------------------------------------- HCD Transfer Descriptors ---------------------------------------------
+
+// ----------------------- Public --------------------------
+
+esp_err_t hcd_irp_enqueue(hcd_pipe_handle_t pipe_hdl, usb_irp_t *irp)
+{
+    //Check that IRP has not already been enqueued
+    HCD_CHECK(irp->reserved_ptr == NULL
+              && IRP_STATE_GET(irp->reserved_flags) == IRP_STATE_IDLE,
+              ESP_ERR_INVALID_STATE);
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+
+    HCD_ENTER_CRITICAL();
+    //Check that pipe and port are in the correct state to receive IRPs
+    HCD_CHECK_FROM_CRIT(pipe->port->state == HCD_PORT_STATE_ENABLED         //The pipe's port must be in the correct state
+                        && pipe->state == HCD_PIPE_STATE_ACTIVE             //The pipe must be in the correct state
+                        && !pipe->cs_flags.pipe_cmd_processing,            //Pipe cannot currently be processing a pipe command
+                        ESP_ERR_INVALID_STATE);
+    //Use the IRP's reserved_ptr to store the pipe's
+    irp->reserved_ptr = (void *)pipe;
+    //Add the IRP to the pipe's pending tailq
+    IRP_STATE_SET(irp->reserved_flags, IRP_STATE_PENDING);
+    TAILQ_INSERT_TAIL(&pipe->pending_irp_tailq, irp, tailq_entry);
+    pipe->num_irp_pending++;
+    //use the IRP's reserved_flags to store the IRP's current state
+    if (_buffer_can_fill(pipe)) {
+        _buffer_fill(pipe);
+    }
+    if (_buffer_can_exec(pipe)) {
+        _buffer_exec(pipe);
+    }
+    if (!pipe->cs_flags.is_active) {
+        //This is the first IRP to be enqueued into the pipe. Move the pipe to the list of active pipes
+        TAILQ_REMOVE(&pipe->port->pipes_idle_tailq, pipe, tailq_entry);
+        TAILQ_INSERT_TAIL(&pipe->port->pipes_active_tailq, pipe, tailq_entry);
+        pipe->port->num_pipes_idle--;
+        pipe->port->num_pipes_queued++;
+        pipe->cs_flags.is_active = 1;
+    }
+    HCD_EXIT_CRITICAL();
+    return ESP_OK;
+}
+
+usb_irp_t *hcd_irp_dequeue(hcd_pipe_handle_t pipe_hdl)
+{
+    pipe_t *pipe = (pipe_t *)pipe_hdl;
+    usb_irp_t *irp;
+
+    HCD_ENTER_CRITICAL();
+    if (pipe->num_irp_done > 0) {
+        irp = TAILQ_FIRST(&pipe->done_irp_tailq);
+        TAILQ_REMOVE(&pipe->done_irp_tailq, irp, tailq_entry);
+        pipe->num_irp_done--;
+        //Check the IRP's reserved fields then reset them
+        assert(irp->reserved_ptr == (void *)pipe && IRP_STATE_GET(irp->reserved_flags) == IRP_STATE_DONE);  //The IRP's reserved field should have been set to this pipe
+        irp->reserved_ptr = NULL;
+        IRP_STATE_SET(irp->reserved_flags, IRP_STATE_IDLE);
+        if (pipe->cs_flags.is_active
+            && pipe->num_irp_pending == 0 && pipe->num_irp_done == 0
+            && pipe->multi_buffer_control.buffer_num_to_exec == 0 && pipe->multi_buffer_control.buffer_num_to_parse == 0) {
+            //This pipe has no more enqueued IRPs. Move the pipe to the list of idle pipes
+            TAILQ_REMOVE(&pipe->port->pipes_active_tailq, pipe, tailq_entry);
+            TAILQ_INSERT_TAIL(&pipe->port->pipes_idle_tailq, pipe, tailq_entry);
+            pipe->port->num_pipes_idle++;
+            pipe->port->num_pipes_queued--;
+            pipe->cs_flags.is_active = 0;
+        }
+    } else {
+        //No more IRPs to dequeue from this pipe
+        irp = NULL;
+    }
+    HCD_EXIT_CRITICAL();
+    return irp;
+}
+
+esp_err_t hcd_irp_abort(usb_irp_t *irp)
+{
+    HCD_ENTER_CRITICAL();
+    //Check that the IRP was enqueued to begin with
+    HCD_CHECK_FROM_CRIT(irp->reserved_ptr != NULL
+                        && IRP_STATE_GET(irp->reserved_flags) != IRP_STATE_IDLE,
+                        ESP_ERR_INVALID_STATE);
+    if (IRP_STATE_GET(irp->reserved_flags) == IRP_STATE_PENDING) {
+        //IRP has not been executed so it can be aborted
+        pipe_t *pipe = (pipe_t *)irp->reserved_ptr;
+        //Remove it form the pending queue
+        TAILQ_REMOVE(&pipe->pending_irp_tailq, irp, tailq_entry);
+        pipe->num_irp_pending--;
+        //Add it to the done queue
+        TAILQ_INSERT_TAIL(&pipe->done_irp_tailq, irp, tailq_entry);
+        pipe->num_irp_done++;
+        //Update the IRP's current state, status, and actual length
+        IRP_STATE_SET(irp->reserved_flags, IRP_STATE_DONE);
+        irp->actual_num_bytes = 0;
+        irp->status = USB_TRANSFER_STATUS_CANCELED;
+        //If this is an ISOC IRP, update the ISO packet descriptors as well
+        for (int i = 0; i < irp->num_iso_packets; i++) {
+            irp->iso_packet_desc[i].actual_length = 0;
+            irp->iso_packet_desc[i].status = USB_TRANSFER_STATUS_CANCELED;
+        }
+    }// Otherwise, the IRP is in-flight or already done thus cannot be aborted
+    HCD_EXIT_CRITICAL();
+    return ESP_OK;
+}

+ 512 - 0
src/hcd.h

@@ -0,0 +1,512 @@
+// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/queue.h>
+#include "esp_err.h"
+#include "usb.h"
+
+// ------------------------------------------------- Macros & Types ----------------------------------------------------
+
+// ----------------------- States --------------------------
+
+/**
+ * @brief States of the HCD port
+ *
+ * @note The port can be thought of as an abstraction of the Root Hub that contains
+ *       a single port.
+ * @note These states roughly match the port states outlined in 11.5.1 of the
+ *       USB2.0 specification.
+ */
+typedef enum {
+    HCD_PORT_STATE_NOT_POWERED,     /**< The port is not powered */
+    HCD_PORT_STATE_DISCONNECTED,    /**< The port is powered but no device is connected */
+    HCD_PORT_STATE_DISABLED,        /**< A device has connected to the port but has not been reset. SOF/keep alive are not being sent */
+    HCD_PORT_STATE_RESETTING,       /**< The port is issuing a reset condition */
+    HCD_PORT_STATE_SUSPENDED,       /**< The port has been suspended. */
+    HCD_PORT_STATE_RESUMING,        /**< The port is issuing a resume condition */
+    HCD_PORT_STATE_ENABLED,         /**< The port has been enabled. SOF/keep alive are being sent */
+    HCD_PORT_STATE_RECOVERY,        /**< Port needs to be recovered from a fatal error (port error, overcurrent, or sudden disconnection) */
+} hcd_port_state_t;
+
+/**
+ * @brief States of an HCD pipe
+ *
+ * Active:
+ *  - Pipe is able to transmit data. IRPs can be enqueued.
+ *  - Event if pipe has no IRPs enqueued, it can still be in the active state.
+ * Halted:
+ *  - An error has occurred on the pipe. IRPs will no longer be executed.
+ *  - Halt should be cleared using the clear command
+ * Invalid:
+ *  - The underlying device that the pipe connects is not longer valid, thus making the pipe invalid.
+ *  - Pending IRPs should be dequeued and the pipe should be freed.
+ */
+typedef enum {
+    HCD_PIPE_STATE_ACTIVE,          /**< The pipe is active */
+    HCD_PIPE_STATE_HALTED,          /**< The pipe is halted */
+    HCD_PIPE_STATE_INVALID,         /**< The pipe no longer exists and should be freed */
+} hcd_pipe_state_t;
+
+// ----------------------- Events --------------------------
+
+/**
+ * @brief HCD port events
+ *
+ * On receiving a port event, hcd_port_handle_event() should be called to handle that event
+ */
+typedef enum {
+    HCD_PORT_EVENT_NONE,            /**< No event has occurred. Or the previous event is no longer valid */
+    HCD_PORT_EVENT_CONNECTION,      /**< A device has been connected to the port */
+    HCD_PORT_EVENT_DISCONNECTION,   /**< A device disconnection has been detected */
+    HCD_PORT_EVENT_ERROR,           /**< A port error has been detected. Port is now HCD_PORT_STATE_RECOVERY  */
+    HCD_PORT_EVENT_OVERCURRENT,     /**< Overcurrent detected on the port. Port is now HCD_PORT_STATE_RECOVERY */
+    HCD_PORT_EVENT_SUDDEN_DISCONN,  /**< The port has suddenly disconnected (i.e., there was an enabled device connected
+                                         to the port when the disconnection occurred. Port is now HCD_PORT_STATE_RECOVERY. */
+} hcd_port_event_t;
+
+/**
+ * @brief HCD pipe events
+ *
+ * @note Pipe error events will put the pipe into the HCD_PIPE_STATE_HALTED state
+ * @note The HCD_PIPE_EVENT_INVALID will put the pipe in the HCD_PIPE_STATE_INVALID state
+ */
+typedef enum {
+    HCD_PIPE_EVENT_NONE,                    /**< The pipe has no events (used to indicate no events when polling) */
+    HCD_PIPE_EVENT_IRP_DONE,                /**< The pipe has completed an IRP. The IRP can be dequeued */
+    HCD_PIPE_EVENT_INVALID,                 /**< The pipe is invalid because  */
+    HCD_PIPE_EVENT_ERROR_XFER,              /**< Excessive (three consecutive) transaction errors (e.g., no ACK, bad CRC etc) */
+    HCD_PIPE_EVENT_ERROR_IRP_NOT_AVAIL,     /**< IRP was not available */
+    HCD_PIPE_EVENT_ERROR_OVERFLOW,          /**< Received more data than requested. Usually a Packet babble error
+                                                 (i.e., an IN packet has exceeded the endpoint's MPS) */
+    HCD_PIPE_EVENT_ERROR_STALL,             /**< Pipe received a STALL response received */
+} hcd_pipe_event_t;
+
+// ---------------------- Commands -------------------------
+
+/**
+ * @brief HCD port commands
+ */
+typedef enum {
+    HCD_PORT_CMD_POWER_ON,          /**< Power ON the port */
+    HCD_PORT_CMD_POWER_OFF,         /**< Power OFF the port */
+    HCD_PORT_CMD_RESET,             /**< Issue a reset on the port */
+    HCD_PORT_CMD_SUSPEND,           /**< Suspend the port */
+    HCD_PORT_CMD_RESUME,            /**< Resume the port */
+    HCD_PORT_CMD_DISABLE,           /**< Disable the port (stops the SOFs or keep alive) */
+} hcd_port_cmd_t;
+
+/**
+ * @brief HCD pipe commands
+ *
+ * The pipe commands represent the list of pipe manipulations outlined in 10.5.2.2. of USB2.0 specification.
+ */
+typedef enum {
+    HCD_PIPE_CMD_ABORT,             /**< Retire all scheduled IRPs. Pipe's state remains unchanged */
+    HCD_PIPE_CMD_RESET,             /**< Retire all scheduled IRPs. Pipe's state moves to active */
+    HCD_PIPE_CMD_CLEAR,             /**< Pipe's state moves from halted to active */
+    HCD_PIPE_CMD_HALT               /**< Pipe's state moves to halted */
+} hcd_pipe_cmd_t;
+
+// -------------------- Object Types -----------------------
+
+/**
+ * @brief Port handle type
+ */
+typedef void * hcd_port_handle_t;
+
+/**
+ * @brief Pipe handle type
+ */
+typedef void * hcd_pipe_handle_t;
+
+/**
+ * @brief Port event callback type
+ *
+ * This callback is run when a port event occurs
+ */
+typedef bool (*hcd_port_isr_callback_t)(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr);
+
+/**
+ * @brief Pipe event callback
+ *
+ * This callback is run when a pipe event occurs
+ */
+typedef bool (*hcd_pipe_isr_callback_t)(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr);
+
+typedef enum {
+    HCD_PORT_FIFO_BIAS_BALANCED,    /**< Balanced FIFO sizing for RX, Non-periodic TX, and periodic TX */
+    HCD_PORT_FIFO_BIAS_RX,          /**< Bias towards a large RX FIFO */
+    HCD_PORT_FIFO_BIAS_PTX,         /**< Bias towards periodic TX FIFO */
+} hcd_port_fifo_bias_t;
+
+/**
+ * @brief HCD configuration structure
+ */
+typedef struct {
+    int intr_flags;                         /**< Interrupt flags for HCD interrupt */
+} hcd_config_t;
+
+/**
+ * @brief Port configuration structure
+ */
+typedef struct {
+    hcd_port_isr_callback_t callback;       /**< HCD port event callback */
+    void *callback_arg;                     /**< User argument for HCD port callback */
+    void *context;                          /**< Context variable used to associate the port with upper layer object */
+} hcd_port_config_t;
+
+/**
+ * @brief Pipe configuration structure
+ *
+ * @note The callback can be set to NULL if no callback is required (e.g., using HCD in a polling manner).
+ */
+typedef struct {
+    hcd_pipe_isr_callback_t callback;       /**< HCD pipe event ISR callback */
+    void *callback_arg;                     /**< User argument for HCD pipe callback */
+    void *context;                          /**< Context variable used to associate the pipe with upper layer object */
+    const usb_desc_ep_t *ep_desc;                 /**< Pointer to endpoint descriptor of the pipe */
+    usb_speed_t dev_speed;                  /**< Speed of the device */
+    uint8_t dev_addr;                       /**< Device address of the pipe */
+} hcd_pipe_config_t;
+
+// --------------------------------------------- Host Controller Driver ------------------------------------------------
+
+/**
+ * @brief Installs the Host Controller Driver
+ *
+ * - Allocates memory and interrupt resources for the HCD and underlying ports
+ * - Setups up HCD to use internal PHY
+ *
+ * @note This function must be called before any other HCD function is called
+ *
+ * @param config HCD configuration
+ * @retval ESP_OK: HCD successfully installed
+ * @retval ESP_ERR_NO_MEM: Insufficient memory
+ * @retval ESP_ERR_INVALID_STATE: HCD is already installed
+ * @retval ESP_ERR_NOT_FOUND: HCD could not allocate interrupt
+ * @retval ESP_ERR_INVALID_ARG: Arguments are invalid
+ */
+esp_err_t hcd_install(const hcd_config_t *config);
+
+/**
+ * @brief Uninstalls the HCD
+ *
+ * Before uninstalling the HCD, the following conditions should be met:
+ * - All ports must be uninitialized, all pipes freed
+ *
+ * @retval ESP_OK: HCD successfully uninstalled
+ * @retval ESP_ERR_INVALID_STATE: HCD is not in the right condition to be uninstalled
+ */
+esp_err_t hcd_uninstall(void);
+
+// ---------------------------------------------------- HCD Port -------------------------------------------------------
+
+/**
+ * @brief Initialize a particular port of the HCD
+ *
+ * After a port is initialized, it will be put into the HCD_PORT_STATE_NOT_POWERED state.
+ *
+ * @note The host controller only has one port, thus the only valid port_number is 1
+ *
+ * @param[in] port_number Port number
+ * @param[in] port_config Port configuration
+ * @param[out] port_hdl Port handle
+ * @retval ESP_OK: Port enabled
+ * @retval ESP_ERR_NO_MEM: Insufficient memory
+ * @retval ESP_ERR_INVALID_STATE: The port is already enabled
+ * @retval ESP_ERR_NOT_FOUND: Port number not found
+ * @retval ESP_ERR_INVALID_ARG: Arguments are invalid
+ */
+esp_err_t hcd_port_init(int port_number, hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl);
+
+/**
+ * @brief Deinitialize a particular port
+ *
+ * The port must be placed in the HCD_PORT_STATE_NOT_POWERED or HCD_PORT_STATE_RECOVERY state before it can be
+ * deinitialized.
+ *
+ * @param port_hdl Port handle
+ * @retval ESP_OK: Port disabled
+ * @retval ESP_ERR_INVALID_STATE: The port is not in a condition to be disabled (not unpowered)
+ */
+esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl);
+
+/**
+ * @brief Execute a port command
+ *
+ * Call this function to manipulate a port (e.g., powering it ON, sending a reset etc). The following conditions
+ * must be met when calling this function:
+ * - The port is in the correct state for the command (e.g., port must be suspend in order to use the resume command)
+ * - The port does not have any pending events
+ *
+ * @note This function is internally protected by a mutex. If multiple threads call this function, this function will
+ *       can block.
+ * @note For some of the commands that involve a blocking delay (e.g., reset and resume), if the port's state changes
+ *       unexpectedly (e.g., a disconnect during a resume), this function will return ESP_ERR_INVALID_RESPONSE.
+ *
+ * @param port_hdl Port handle
+ * @param command Command for the HCD port
+ * @retval ESP_OK: Command executed successfully
+ * @retval ESP_ERR_INVALID_STATE: Conditions have not been met to call this function
+ * @retval ESP_ERR_INVALID_RESPONSE: The command is no longer valid due to a change in the port's state
+ */
+esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command);
+
+/**
+ * @brief Get the port's current state
+ *
+ * @param port_hdl Port handle
+ * @return hcd_port_state_t Current port state
+ */
+hcd_port_state_t hcd_port_get_state(hcd_port_handle_t port_hdl);
+
+/**
+ * @brief Get the speed of the port
+ *
+ * The speed of the port is determined by the speed of the device connected to it.
+ *
+ * @note This function is only valid after a device directly to the port and has been reset
+ *
+ * @param[in port_hdl Port handle
+ * @param[out] speed Speed of the port
+ * @retval ESP_OK Device speed obtained
+ * @retval ESP_ERR_INVALID_STATE: No valid device connected to the port
+ * @retval ESP_ERR_INVALID_ARG: Invalid arguments
+ */
+esp_err_t hcd_port_get_speed(hcd_port_handle_t port_hdl, usb_speed_t *speed);
+
+/**
+ * @brief Handle a ports event
+ *
+ * When an port event occurs (as indicated by a callback), this function should be called the handle this event. A
+ * port's event should always be handled before attempting to execute a port command. Note that is actually handled
+ * may be different than the event reflected in the callback.
+ *
+ * If the port has no events, this function will return HCD_PORT_EVENT_NONE.
+ *
+ * @note If callbacks are not used, this function can also be used in a polling manner to repeatedly check for and
+ *       handle a port's events.
+ * @note This function is internally protected by a mutex. If multiple threads call this function, this function will
+ *       can block.
+ *
+ * @param port_hdl Port handle
+ * @return hcd_port_event_t The port event that was handled
+ */
+hcd_port_event_t hcd_port_handle_event(hcd_port_handle_t port_hdl);
+
+/**
+ * @brief Recover a port after a fatal error has occurred on it
+ *
+ * The port must be in the HCD_PORT_STATE_RECOVERY state to be called. Recovering the port will involve issuing a soft
+ * reset on the underlying USB controller. The port will be returned to the HCD_PORT_STATE_NOT_POWERED state.
+ *
+ * @param port_hdl Port handle
+ * @retval ESP_OK Port recovered successfully
+ * @retval ESP_ERR_INVALID_STATE Port is not in the HCD_PORT_STATE_RECOVERY state
+ */
+esp_err_t hcd_port_recover(hcd_port_handle_t port_hdl);
+
+/**
+ * @brief Get the context variable of a port
+ *
+ * @param port_hdl Port handle
+ * @return void* Context variable
+ */
+void *hcd_port_get_context(hcd_port_handle_t port_hdl);
+
+/**
+ * @brief Set the bias of the HCD port's internal FIFO
+ *
+ * @note This function can only be called when the following conditions are met:
+ *  - Port is initialized
+ *  - Port does not have any pending events
+ *  - Port does not have any allocated pipes
+ *
+ * @param port_hdl Port handle
+ * @param bias Fifo bias
+ * @retval ESP_OK FIFO sizing successfully set
+ * @retval ESP_ERR_INVALID_STATE Incorrect state for FIFO sizes to be set
+ */
+esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_t bias);
+
+// --------------------------------------------------- HCD Pipes -------------------------------------------------------
+
+/**
+ * @brief Allocate a pipe
+ *
+ * When allocating a pipe, the HCD will assess whether there are sufficient resources (i.e., bus time, and controller
+ * channels). If sufficient, the pipe will be allocated.
+ *
+ * @note Currently, Interrupt and Isochronous pipes are not supported yet
+ * @note The host port must be in the enabled state before a pipe can be allcoated
+ *
+ * @param[in] port_hdl Handle of the port this pipe will be routed through
+ * @param[in] pipe_config Pipe configuration
+ * @param[out] pipe_hdl Pipe handle
+ *
+ * @retval ESP_OK: Pipe successfully allocated
+ * @retval ESP_ERR_NO_MEM: Insufficient memory
+ * @retval ESP_ERR_INVALID_ARG: Arguments are invalid
+ * @retval ESP_ERR_INVALID_STATE: Host port is not in the correct state to allocate a pipe
+ * @retval ESP_ERR_NOT_SUPPORTED: The pipe's configuration cannot be supported
+ */
+esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pipe_config, hcd_pipe_handle_t *pipe_hdl);
+
+/**
+ * @brief Free a pipe
+ *
+ * Frees the resources used by an HCD pipe. The pipe's handle should be discarded after calling this function. The pipe
+ * must be in following condition before it can be freed:
+ * - All IRPs have been dequeued
+ *
+ * @param pipe_hdl Pipe handle
+ *
+ * @retval ESP_OK: Pipe successfully freed
+ * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be freed
+ */
+esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl);
+
+/**
+ * @brief Update a pipe's maximum packet size
+ *
+ * This function is intended to be called on default pipes during enumeration in order to update the pipe's maximum
+ * packet size. This function can only be called on a pipe that has met the following conditions:
+ * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state)
+ * - Pipe is not currently processing a command
+ * - All IRPs have been dequeued from the pipe
+ *
+ * @param pipe_hdl Pipe handle
+ * @param mps New Maximum Packet Size
+ *
+ * @retval ESP_OK: Pipe successfully updated
+ * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be updated
+ */
+esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps);
+
+/**
+ * @brief Update a pipe's device address
+ *
+ * This function is intended to be called on default pipes during enumeration in order to update the pipe's device
+ * address. This function can only be called on a pipe that has met the following conditions:
+ * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state)
+ * - Pipe is not currently processing a command
+ * - All IRPs have been dequeued from the pipe
+ *
+ * @param pipe_hdl Pipe handle
+ * @param dev_addr New device address
+ *
+ * @retval ESP_OK: Pipe successfully updated
+ * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be updated
+ */
+esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr);
+
+/**
+ * @brief Get the context variable of a pipe from its handle
+ *
+ * @param pipe_hdl Pipe handle
+ * @return void* Context variable
+ */
+void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl);
+
+/**
+ * @brief Get the current sate of the pipe
+ *
+ * @param pipe_hdl Pipe handle
+ * @return hcd_pipe_state_t Current state of the pipe
+ */
+hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl);
+
+/**
+ * @brief Execute a command on a particular pipe
+ *
+ * Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all IRPs etc). The following
+ * conditions must for a pipe command to be issued:
+ * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID)
+ * - No other thread/task processing a command on the pipe concurrently (will return)
+ *
+ * @note Some pipe commands will block until the pipe's current in-flight IRP is complete. If the pipe's state
+ *       changes unexpectedly, this function will return ESP_ERR_INVALID_RESPONSE
+ *
+ * @param pipe_hdl Pipe handle
+ * @param command Pipe command
+ * @retval ESP_OK: Command executed successfully
+ * @retval ESP_ERR_INVALID_STATE: The pipe is not in the correct state/condition too execute the command
+ * @retval ESP_ERR_INVALID_RESPONSE: The pipe's state changed unexpectedley
+ */
+esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command);
+
+/**
+ * @brief Get the last event that occurred on a pipe
+ *
+ * This function allows a pipe to be polled for events (i.e., when callbacks are not used). Once an event has been
+ * obtained, this function reset the last event of the pipe to HCD_PIPE_EVENT_NONE.
+ *
+ * @param pipe_hdl Pipe handle
+ * @return hcd_pipe_event_t Last pipe event to occur
+ */
+hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl);
+
+// ---------------------------------------------------- HCD IRPs -------------------------------------------------------
+
+/**
+ * @brief Enqueue an IRP to a particular pipe
+ *
+ * The following conditions must be met before an IRP can be enqueued:
+ * - The IRP is properly initialized (data buffer and transfer length are set)
+ * - The IRP must not already be enqueued
+ * - The pipe must be in the HCD_PIPE_STATE_ACTIVE state
+ *
+ * @param pipe_hdl Pipe handle
+ * @param irp I/O Request Packet to enqueue
+ * @retval ESP_OK: IRP enqueued successfully
+ * @retval ESP_ERR_INVALID_STATE: Conditions not met to enqueue IRP
+ */
+esp_err_t hcd_irp_enqueue(hcd_pipe_handle_t pipe_hdl, usb_irp_t *irp);
+
+/**
+ * @brief Dequeue an IRP from a particular pipe
+ *
+ * This function should be called on a pipe after a pipe receives a HCD_PIPE_EVENT_IRP_DONE event. If a pipe has
+ * multiple IRPs that can be dequeued, this function should be called repeatedly until all IRPs are dequeued. If a pipe
+ * has no more IRPs to dequeue, this function will return NULL.
+ *
+ * @param pipe_hdl Pipe handle
+ * @return usb_irp_t* Dequeued I/O Request Packet, or NULL if no more IRPs to dequeue
+ */
+usb_irp_t *hcd_irp_dequeue(hcd_pipe_handle_t pipe_hdl);
+
+/**
+ * @brief Abort an enqueued IRP
+ *
+ * This function will attempt to abort an IRP that is already enqueued. If the IRP has yet to be executed, it will be
+ * "cancelled" and can then be dequeued. If the IRP is currenty in-flight or has already completed, the IRP will not be
+ * affected by this function.
+ *
+ * @param irp I/O Request Packet to abort
+ * @retval ESP_OK: IRP successfully aborted, or was not affected by this function
+ * @retval ESP_ERR_INVALID_STATE: IRP was never enqueued
+ */
+esp_err_t hcd_irp_abort(usb_irp_t *irp);
+
+#ifdef __cplusplus
+}
+#endif

+ 214 - 0
src/parse_data.cpp

@@ -0,0 +1,214 @@
+
+#include <stdio.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "esp_intr_alloc.h"
+#include "esp_err.h"
+#include "esp_attr.h"
+#include "esp_rom_gpio.h"
+#include "soc/gpio_pins.h"
+#include "soc/gpio_sig_map.h"
+#include "hal/usbh_ll.h"
+#include "hcd.h"
+#include "esp_log.h"
+
+#define USB_W_VALUE_DT_HID                  0x22
+#define USB_W_VALUE_DT_CS_INTERFACE         0x24
+
+static uint8_t itf = 0;
+uint16_t report_map_size = 0;
+
+static void create_pipe(usb_desc_ep_t* ep)
+{
+    switch (itf)
+    {
+        case 0x03:
+            break;
+        
+        default:
+            break;
+    }
+}
+
+static char* class_to_str(uint8_t cls)
+{
+    itf = cls;
+    switch (cls)
+    {
+        case 0x00:
+            return ">ifc";
+        case 0x01:
+            return "Audio";
+        case 0x02:
+            return "CDC";
+        case 0x03:
+            return "HID";
+        case 0x05:
+            return "Physical";
+        case 0x06:
+            return "Image";
+        case 0x07:
+            return "Printer";
+        case 0x08:
+            return "Mass Storage";
+        case 0x09:
+            return "Hub";
+        case 0x0a:
+            return "CDC-data";
+        case 0x0b:
+            return "Smart card";
+        case 0x0d:
+            return "Content security";
+        case 0x0e:
+            return "Video";
+        case 0x0f:
+            return "Personal heathcare";
+        case 0x10:
+            return "Audio/Vdeo devices";
+        case 0x11:
+            return "Bilboard";
+        case 0x12:
+            return "USB-C bridge";
+        case 0xdc:
+            return "Diagnostic device";
+        case 0xe0:
+            return "Wireless controller";
+        case 0xef:
+            return "Miscellaneous";
+        case 0xfe:
+            return "Application specific";
+        case 0xff:
+            return "Vendor specific";
+        
+        default:
+            return "Wrong class type";
+    }
+}
+
+static inline int bcd_to_decimal(unsigned char x) {
+    return x - 6 * (x >> 4);
+}
+
+static void utf16_to_utf8(char* in, char* out, uint8_t len)
+{
+    for (size_t i = 0; i < len; i++)
+    {
+        out[i/2] = in[i];
+        i++;
+    }
+}
+
+static void parse_device_descriptor(uint8_t* data_buffer, usb_transfer_status_t status, uint8_t* num)
+{
+    if(status == USB_TRANSFER_STATUS_COMPLETED){
+        printf("\nDevice descriptor:\n");
+
+        usb_desc_devc_t* desc = (usb_desc_devc_t*)data_buffer;
+        extern uint8_t bMaxPacketSize0;
+        bMaxPacketSize0 = desc->bMaxPacketSize0;
+
+        printf("Length: %d\n", desc->bLength);
+        printf("Descriptor type: %d\n", desc->bDescriptorType);
+        printf("USB version: %d.%02d\n", bcd_to_decimal(desc->bcdUSB >> 8), bcd_to_decimal(desc->bcdUSB & 0xff));
+        printf("Device class: 0x%02x (%s)\n", desc->bDeviceClass, class_to_str(desc->bDeviceClass));
+        printf("Device subclass: 0x%02x\n", desc->bDeviceSubClass);
+        printf("Device protocol: 0x%02x\n", desc->bDeviceProtocol);
+        printf("EP0 max packet size: %d\n", desc->bMaxPacketSize0);
+        printf("VID: 0x%04x\n", desc->idVendor);
+        printf("PID: 0x%04x\n", desc->idProduct);
+        printf("Revision number: %d.%02d\n", bcd_to_decimal(desc->bcdDevice >> 8), bcd_to_decimal(desc->bcdDevice & 0xff));
+        printf("Manufacturer id: %d\n", desc->iManufacturer);
+        printf("Product id: %d\n", desc->iProduct);
+        printf("Serial id: %d\n", desc->iSerialNumber);
+        printf("Configurations num: %d\n", desc->bNumConfigurations);
+        *num = desc->bNumConfigurations;
+    } else {
+        ESP_LOGW("", "status: %d", status);
+    }
+}
+
+uint8_t* parse_cfg_descriptor(uint8_t* data_buffer, uint8_t** out, uint8_t* _type)
+{
+    uint8_t offset = 0;
+    uint8_t type = *(&data_buffer[0] + offset + 1);
+    do{
+        ESP_LOGE("", "type: %d\n", type);
+        *_type = type;
+        switch (type)
+        {
+            case USB_W_VALUE_DT_DEVICE:
+                break;
+
+            case USB_W_VALUE_DT_CONFIG:{
+                printf("\nConfig:\n");
+                usb_desc_cfg_t* data = (usb_desc_cfg_t*)(data_buffer + offset);
+                printf("Number of Interfaces: %d\n", data->bNumInterfaces);
+                // printf("type: %d\n", data->bConfigurationValue);
+                // printf("type: %d\n", data->iConfiguration);
+                printf("Attributes: 0x%02x\n", data->bmAttributes);
+                printf("Max power: %d mA\n", data->bMaxPower * 2);
+                offset += data->bLength;
+                break;
+            }
+            case USB_W_VALUE_DT_STRING:{
+                usb_desc_str_t* data = (usb_desc_str_t*)(data_buffer + offset);
+                uint8_t len = 0;
+                len = data->bLength;
+                offset += len;
+                char* str = (char*)calloc(1, len);
+                utf16_to_utf8((char*)&data->val[2], str, len);
+                printf("strings: %s\n", str);
+                free(str);
+                break;
+            }
+            case USB_W_VALUE_DT_INTERFACE:{
+                printf("\nInterface:\n");
+                usb_desc_intf_t* data = (usb_desc_intf_t*)(data_buffer + offset);
+                offset += data->bLength;
+                printf("bInterfaceNumber: %d\n", data->bInterfaceNumber);
+                printf("bAlternateSetting: %d\n", data->bAlternateSetting);
+                printf("bNumEndpoints: %d\n", data->bNumEndpoints);
+                printf("bInterfaceClass: 0x%02x (%s)\n", data->bInterfaceClass, class_to_str(data->bInterfaceClass));
+                printf("bInterfaceSubClass: 0x%02x\n", data->bInterfaceSubClass);
+                printf("bInterfaceProtocol: 0x%02x\n", data->bInterfaceProtocol);
+                break;
+            }
+            case USB_W_VALUE_DT_ENDPOINT:{
+                printf("\nEndpoint:\n");
+                usb_desc_ep_t* data = (usb_desc_ep_t*)(data_buffer + offset);
+                offset += data->bLength;
+                printf("bEndpointAddress: 0x%02x\n", data->bEndpointAddress);
+                printf("bmAttributes: 0x%02x\n", data->bmAttributes);
+                printf("bDescriptorType: %d\n", data->bDescriptorType);
+                printf("wMaxPacketSize: %d\n", data->wMaxPacketSize);
+                printf("bInterval: %d ms\n", data->bInterval);
+                create_pipe(data);
+                *out = (uint8_t*)data;
+                break;
+            }
+            case 0x21:{
+                ESP_LOGI("", "HID descriptor");
+                uint8_t* data = (data_buffer + offset);
+                offset += data[0];
+                ESP_LOGI("Report map size", "0x%x", (uint16_t)data[7]);
+                report_map_size = (uint16_t)data[7];
+                break;
+            }
+            case USB_W_VALUE_DT_CS_INTERFACE:{
+                printf("\nCS_Interface:\n");
+                usb_desc_intf_t* data = (usb_desc_intf_t*)(data_buffer + offset);
+                offset += data->bLength;
+
+                break;
+            }
+            default:
+                ESP_LOGI("", "unknown descriptor: %d", type);
+
+                offset += *(data_buffer + offset);
+                break;
+        }
+    }while(0);
+    return (uint8_t*)data_buffer + offset;
+}
+
+

+ 95 - 0
src/parse_hid.h

@@ -0,0 +1,95 @@
+#pragma once
+
+#define HIDINPUT(size) (0x80 | size)
+#define HIDOUTPUT(size) (0x90 | size)
+
+#define FEATURE(size) (0xb0 | size)
+#define COLLECTION(size) (0xa0 | size)
+#define END_COLLECTION(size) (0xc0 | size)
+
+/* Global items */
+#define USAGE_PAGE(size) (0x04 | size)
+#define LOGICAL_MINIMUM(size) (0x14 | size)
+#define LOGICAL_MAXIMUM(size) (0x24 | size)
+#define PHYSICAL_MINIMUM(size) (0x34 | size)
+#define PHYSICAL_MAXIMUM(size) (0x44 | size)
+#define UNIT_EXPONENT(size) (0x54 | size)
+#define UNIT(size) (0x64 | size)
+#define REPORT_SIZE(size) (0x74 | size) //bits
+#define REPORT_ID(size) (0x84 | size)
+#define REPORT_COUNT(size) (0x94 | size) //bytes
+#define PUSH(size) (0xa4 | size)
+#define POP(size) (0xb4 | size)
+
+/* Local items */
+#define USAGE(size) (0x08 | size)
+#define USAGE_MINIMUM(size) (0x18 | size)
+#define USAGE_MAXIMUM(size) (0x28 | size)
+#define DESIGNATOR_INDEX(size) (0x38 | size)
+#define DESIGNATOR_MINIMUM(size) (0x48 | size)
+#define DESIGNATOR_MAXIMUM(size) (0x58 | size)
+#define STRING_INDEX(size) (0x78 | size)
+#define STRING_MINIMUM(size) (0x88 | size)
+#define STRING_MAXIMUM(size) (0x98 | size)
+#define DELIMITER(size) (0xa8 | size)
+
+#define GENERIC_DESKTOP_POINTER         0x01
+#define GENERIC_DESKTOP_MOUSE           0x02
+#define GENERIC_DESKTOP_JOYSTICK        0x04
+#define GENERIC_DESKTOP_GAMEPAD         0x05
+#define GENERIC_DESKTOP_KEYBAORD        0x06
+#define GENERIC_DESKTOP_KEYPAD          0x07
+#define GENERIC_DESKTOP_MULTIAXIS       0x08
+#define GENERIC_DESKTOP_BUTTON          0x09
+
+
+#define GENERIC_DESKTOP_X               0x30
+#define GENERIC_DESKTOP_Y               0x31
+#define GENERIC_DESKTOP_WHEEL           0x38
+
+
+typedef struct {
+    struct 
+    {
+        uint16_t bits_start;
+        uint8_t count;
+        uint8_t size;       // bits
+        uint16_t value;     // up to 16 buttons
+        uint8_t reserved;   // number of dummy bits
+    }buttons __attribute__((packed));
+    struct 
+    {
+        uint16_t bits_start;
+        uint8_t count;
+        uint8_t size;       // bits
+        uint8_t* value;
+    }axis_x __attribute__((packed));
+    struct 
+    {
+        uint16_t bits_start;
+        uint8_t count;
+        uint8_t size;       // bits
+        uint8_t* value;
+    }axis_y __attribute__((packed));
+    struct 
+    {
+        uint16_t bits_start;
+        uint8_t count;
+        uint8_t size;       // bits
+        uint8_t* value;
+    }axes __attribute__((packed));
+    struct 
+    {
+        uint16_t bits_start;
+        uint8_t count;
+        uint8_t size;       // bits
+        uint8_t* value;
+    }wheel __attribute__((packed));
+}hid_mouse_t;
+
+
+hid_mouse_t* get_mouse_struct();
+uint8_t get_buttons(uint8_t* data);
+int16_t get_x_axis(uint8_t* data);
+int16_t get_y_axis(uint8_t* data);
+int8_t get_wheel(uint8_t* data);

+ 293 - 0
src/pipe.cpp

@@ -0,0 +1,293 @@
+#include <cstring>
+#include "pipe.h"
+#include "usb.h"
+
+static void pipe_event_task(void *p)
+{
+    USBHostPipe *ctx = (USBHostPipe *)p;
+
+    pipe_event_msg_t msg;
+    while (1)
+    {
+        xQueueReceive(ctx->pipeEvtQueue, &msg, portMAX_DELAY);
+        usb_irp_t *irp = hcd_irp_dequeue(msg.handle);
+        if (irp == NULL)
+            continue;
+
+        if (ctx && ctx->callback != NULL)
+        {
+            ctx->callback(msg, irp, ctx);
+        }
+
+        ctx->freeIRP(irp);
+    }
+}
+
+USBHostPipe::USBHostPipe(hcd_port_handle_t handle)
+{
+    port_hdl = handle;
+}
+
+USBHostPipe::~USBHostPipe()
+{
+    freePipe();
+    vTaskDelete(taskHandle);
+    free(pipeEvtQueue);
+    ESP_LOGI("", "delete pipe");
+}
+
+static bool pipeCallback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr)
+{
+    QueueHandle_t pipe_evt_queue = (QueueHandle_t)user_arg;
+    pipe_event_msg_t msg = {
+        .handle = pipe_hdl,
+        .event = pipe_event,
+    };
+    if (in_isr)
+    {
+        BaseType_t xTaskWoken = pdFALSE;
+        xQueueSendFromISR(pipe_evt_queue, &msg, &xTaskWoken);
+        return (xTaskWoken == pdTRUE);
+    }
+    else
+    {
+        xQueueSend(pipe_evt_queue, &msg, portMAX_DELAY);
+        return false;
+    }
+}
+
+void USBHostPipe::onPipeEvent(pipe_cb_t cb)
+{
+    callback = cb;
+}
+
+void USBHostPipe::init(usb_desc_ep_t *ep_desc, uint8_t addr)
+{
+    // TODO add config parse object to check if it is actually endpoint data
+    usb_speed_t port_speed;
+    if(port_hdl == NULL)ESP_LOGE("", "FAILED");
+    if (ESP_OK != hcd_port_get_speed(port_hdl, &port_speed))
+    {
+        return;
+    }
+    xTaskCreate(pipe_event_task, "pipe_task", 2 * 1024, this, 15, &taskHandle);
+    pipeEvtQueue = xQueueCreate(5, sizeof(pipe_event_msg_t));
+
+    if (pipeEvtQueue == NULL)
+        ESP_LOGE("", "pipe queue not initialized");
+
+    //Create default pipe
+    ESP_LOGV("", "Creating pipe\n");
+    hcd_pipe_config_t config = {
+        .callback = pipeCallback,
+        .callback_arg = (void *)pipeEvtQueue,
+        .context = (void *)this,
+        .ep_desc = ep_desc,
+        .dev_speed = port_speed,
+        .dev_addr = addr,
+    };
+
+    if (ESP_OK != hcd_pipe_alloc(port_hdl, &config, &handle))
+        ESP_LOGE("", "cant allocate pipe");
+
+    if (ep_desc != NULL)
+        memcpy(&endpoint, ep_desc, sizeof(usb_desc_ep_t));
+}
+
+void USBHostPipe::freePipe()
+{
+    //Dequeue transfer requests
+    do
+    {
+        usb_irp_t *irp = hcd_irp_dequeue(handle);
+        if (irp == NULL)
+            break;
+        heap_caps_free(irp->data_buffer);
+        heap_caps_free(irp);
+    } while (1);
+
+    ESP_LOGD("", "Freeing transfer requets\n");
+    //Free transfer requests (and their associated objects such as IRPs and data buffers)
+    ESP_LOGD("", "Freeing pipe\n");
+    //Delete the pipe
+    if (ESP_OK != hcd_pipe_free(handle))
+    {
+        ESP_LOGE("", "err to free pipe");
+    }
+}
+
+void USBHostPipe::updateAddress(uint8_t addr)
+{
+    hcd_pipe_update_dev_addr(handle, addr);
+}
+
+void USBHostPipe::updatePort(hcd_port_handle_t handle)
+{
+    port_hdl = handle;
+}
+
+void USBHostPipe::reset()
+{
+    hcd_pipe_command(handle, HCD_PIPE_CMD_RESET);
+}
+
+hcd_pipe_state_t USBHostPipe::getState()
+{
+    return hcd_pipe_get_state(handle);
+}
+
+void USBHostPipe::getDeviceDescriptor()
+{
+    usb_irp_t *irp = allocateIRP(18);
+    if (irp == NULL)
+        return;
+    USB_CTRL_REQ_INIT_GET_DEVC_DESC((usb_ctrl_req_t *)irp->data_buffer);
+
+    //Enqueue those transfer requests
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(handle, irp)))
+    {
+        ESP_LOGE("", "Get device desc: %d", err);
+    }
+
+    return;
+}
+
+void USBHostPipe::setDeviceAddress(uint8_t addr)
+{
+    usb_irp_t *irp = allocateIRP(0);
+    if (irp == NULL)
+        return;
+
+    USB_CTRL_REQ_INIT_SET_ADDR((usb_ctrl_req_t *)irp->data_buffer, addr);
+
+    //Enqueue those transfer requests
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(handle, irp)))
+    {
+        ESP_LOGE("", "Set address: %d", err);
+    }
+}
+
+void USBHostPipe::getCurrentConfiguration()
+{
+    usb_irp_t *irp = allocateIRP(1);
+    if (irp == NULL)
+        return;
+
+    USB_CTRL_REQ_INIT_GET_CONFIG((usb_ctrl_req_t *)irp->data_buffer);
+
+    //Enqueue those transfer requests
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(handle, irp)))
+    {
+        ESP_LOGE("", "Get current config: %d", err);
+    }
+}
+
+void USBHostPipe::setConfiguration(uint8_t cfg)
+{
+    usb_irp_t *irp = allocateIRP(0);
+    if (irp == NULL)
+        return;
+
+    USB_CTRL_REQ_INIT_SET_CONFIG((usb_ctrl_req_t *)irp->data_buffer, cfg);
+
+    //Enqueue those transfer requests
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(handle, irp)))
+    {
+        ESP_LOGE("", "Set current config: %d", err);
+    }
+}
+
+void USBHostPipe::getConfigDescriptor()
+{
+    usb_irp_t *irp = allocateIRP(TRANSFER_DATA_MAX_BYTES);
+    if (irp == NULL)
+        return;
+    USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp->data_buffer, 0, TRANSFER_DATA_MAX_BYTES);
+
+    //Enqueue those transfer requests
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(handle, irp)))
+    {
+        ESP_LOGE("", "Get config desc: %d", err);
+    }
+}
+
+void USBHostPipe::getString(uint8_t num)
+{
+    usb_irp_t *irp = allocateIRP(TRANSFER_DATA_MAX_BYTES);
+    if (irp == NULL)
+        return;
+    USB_CTRL_REQ_INIT_GET_STRING((usb_ctrl_req_t *)irp->data_buffer, 0, num, TRANSFER_DATA_MAX_BYTES);
+
+    //Enqueue those transfer requests
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(handle, irp)))
+    {
+        ESP_LOGE("", "Get string: %d, err: %d", num, err);
+    }
+}
+
+usb_irp_t *USBHostPipe::allocateIRP(size_t size)
+{
+    // ESP_LOGI("", "Creating new IRP, free memory: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
+    usb_irp_t *irp = (usb_irp_t *)heap_caps_calloc(1, sizeof(usb_irp_t), MALLOC_CAP_DMA);
+    if (NULL == irp)
+    {
+        ESP_LOGE("", "err to alloc IRP");
+        return irp;
+    }
+    //Allocate data buffer
+    uint8_t *data_buffer = (uint8_t *)heap_caps_calloc(1, sizeof(usb_ctrl_req_t) + size, MALLOC_CAP_DMA);
+    if (NULL == data_buffer)
+    {
+        ESP_LOGE("", "err to alloc data buffer");
+        // free(irp);
+        return NULL;
+    }
+    //Initialize IRP and IRP list
+    irp->data_buffer = data_buffer;
+    irp->num_iso_packets = 0;
+    irp->num_bytes = size;
+
+    return irp;
+}
+
+void USBHostPipe::freeIRP(usb_irp_t *irp)
+{
+    free(irp->data_buffer);
+    free(irp);
+}
+
+void USBHostPipe::inData(size_t size)
+{
+    ESP_LOGV("", "EP: 0x%02x", USB_DESC_EP_GET_ADDRESS(endpoint));
+    size_t len = endpoint.wMaxPacketSize;
+    usb_irp_t *irp = allocateIRP(size? size:len);
+
+    esp_err_t err;
+    if(ESP_OK != (err = hcd_irp_enqueue(handle, irp))) {
+        ESP_LOGW("", "IN endpoint 0x%02x enqueue err: 0x%x", USB_DESC_EP_GET_ADDRESS(endpoint), err);
+    }
+}
+
+void USBHostPipe::outData(uint8_t *data, size_t len)
+{
+    usb_irp_t *irp = allocateIRP(len);
+    ESP_LOGV("", "EP: 0x%02x", USB_DESC_EP_GET_ADDRESS(endpoint));
+    memcpy(irp->data_buffer, data, len);
+
+    esp_err_t err;
+    if (ESP_OK != (err = hcd_irp_enqueue(handle, irp)))
+    {
+        ESP_LOGW("", "BULK OUT enqueue err: 0x%x", err);
+    }
+}
+
+hcd_pipe_handle_t USBHostPipe::getHandle()
+{
+    return handle;
+}

+ 163 - 0
src/pipe.h

@@ -0,0 +1,163 @@
+
+#pragma once
+#include <stdio.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "esp_err.h"
+#include "esp_attr.h"
+#include "hal/usbh_ll.h"
+#include "hcd.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+#endif
+#define TRANSFER_DATA_MAX_BYTES 256 //Just assume that will only IN/OUT 64 bytes for now
+#define USB_DESC_EP_GET_ADDRESS(desc_ptr) ((desc_ptr).bEndpointAddress & 0x7F)
+#define BASE_PIPE_EVENT 0x1000
+#define CDC_BASE_PIPE_EVENT 0x2000
+
+/**
+ * @brief Build get string request
+ */
+#define USB_CTRL_REQ_INIT_GET_STRING(ctrl_req_ptr, lang, desc_index, len) (                                                            \
+    {                                                                                                                                  \
+        (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \
+        (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR;                                                                       \
+        (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_STRING << 8) | ((desc_index)&0xFF);                                                   \
+        (ctrl_req_ptr)->wIndex = (lang);                                                                                               \
+        (ctrl_req_ptr)->wLength = (len);                                                                                               \
+    })
+
+typedef struct
+{
+    hcd_pipe_handle_t handle;
+    hcd_pipe_event_t event;
+} pipe_event_msg_t;
+
+class USBHostPipe;
+class USBHostPort;
+
+/**
+ * @brief Pipe callback definition to pass events to class callback
+ */
+typedef void (*pipe_cb_t)(pipe_event_msg_t msg, usb_irp_t *irp, USBHostPipe *context);
+
+class USBHostPipe
+{
+protected:
+    //
+    hcd_pipe_handle_t handle;
+    // 
+    hcd_port_handle_t port_hdl;
+    // 
+    xTaskHandle taskHandle;
+
+public:
+    // 
+    usb_desc_ep_t endpoint;
+    // 
+    pipe_cb_t callback = nullptr;
+    // 
+    QueueHandle_t pipeEvtQueue;
+
+    USBHostPipe(hcd_port_handle_t handle = nullptr);
+    ~USBHostPipe();
+    // master port which this pipe belongs to
+    USBHostPort *port;
+
+    /**
+     * @brief Register pipe event from class space
+     */
+    void onPipeEvent(pipe_cb_t cb);
+
+    /**
+     * @brief Initialize pipe from endpoint data
+     */
+    void init(usb_desc_ep_t *ep_desc = nullptr, uint8_t addr = 0);
+
+    /**
+     * @brief Free all queues and pipe belongs to this object
+     */
+    void freePipe();
+
+    /**
+     * @brief Update address, usually used with control pipe
+     */
+    void updateAddress(uint8_t);
+
+    /**
+     * @brief Update port handle, do we need it???
+     */
+    void updatePort(hcd_port_handle_t handle);
+
+    /**
+     * @brief Reset pipe
+     */
+    void reset();
+
+    /**
+     * @brief Get pipe state
+     */
+    hcd_pipe_state_t getState();
+
+    /**
+     * @brief Allocate IRP before enqueue new request
+     */
+    usb_irp_t *allocateIRP(size_t size);
+
+    /**
+     * @brief Free IRP and data buffer
+     */
+    void freeIRP(usb_irp_t *);
+
+    /**
+     * @brief Get pipe handle
+     */
+    hcd_pipe_handle_t getHandle();
+
+    // pipes are IN or OUT, but it is better to have functions in base class
+    // laterr need to add assert if pipe is IN/OUT
+
+    /**
+     * @brief Send data IN request
+     */
+    void inData(size_t size = 0);
+
+    /**
+     * @brief Send data OUT request
+     */
+    void outData(uint8_t *data, size_t len);
+
+// ------------------- standard USB requests ------------------------ //
+    /**
+     * @brief Prepare and enqueue get device descriptor request
+     */
+    void getDeviceDescriptor();
+
+    /**
+     * @brief Prepare and enqueue set device address request
+     */
+    void setDeviceAddress(uint8_t addr);
+
+    /**
+     * @brief Prepare and enqueue get configuration descriptor request
+     */
+    void getCurrentConfiguration();
+
+    /**
+     * @brief Prepare and enqueue set configuration request
+     */
+    void setConfiguration(uint8_t);
+
+    /**
+     * @brief Prepare and enqueue get configuration descriptor request
+     */
+    void getConfigDescriptor();
+
+    /**
+     * @brief Prepare and enqueue get string by id request
+     */
+    void getString(uint8_t);
+};

+ 239 - 0
src/port.cpp

@@ -0,0 +1,239 @@
+#include "port.h"
+static QueueHandle_t port_evt_queue;
+
+// -------------------------------------------------- PHY Control ------------------------------------------------------
+
+static void phy_force_conn_state(bool connected, TickType_t delay_ticks)
+{
+    vTaskDelay(delay_ticks);
+    usb_wrap_dev_t *wrap = &USB_WRAP;
+    if (connected)
+    {
+        //Swap back to internal PHY that is connected to a devicee
+        wrap->otg_conf.phy_sel = 0;
+    }
+    else
+    {
+        //Set externa PHY input signals to fixed voltage levels mimicing a disconnected state
+        esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VP_IDX, false);
+        esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VM_IDX, false);
+        esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_EXTPHY_RCV_IDX, false);
+        //Swap to the external PHY
+        wrap->otg_conf.phy_sel = 1;
+    }
+}
+
+// ------------------------------------------------ Helper Functions ---------------------------------------------------
+
+static bool port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr)
+{
+    QueueHandle_t port_evt_queue = (QueueHandle_t)user_arg;
+    port_event_msg_t msg = {
+        .port_hdl = port_hdl,
+        .port_event = port_event,
+    };
+
+    BaseType_t xTaskWoken = pdFALSE;
+    xQueueSendFromISR(port_evt_queue, &msg, &xTaskWoken);
+    return (xTaskWoken == pdTRUE);
+}
+
+static void port_event_task(void *p)
+{
+    USBHostPort *port = (USBHostPort *)p;
+    port_event_msg_t msg;
+    while (1)
+    {
+        xQueueReceive(port_evt_queue, &msg, portMAX_DELAY);
+        hcd_port_handle_event(msg.port_hdl);
+        // class callback
+        if (port->callback != NULL)
+        {
+            port->callback(msg, port);
+        }
+        // user callback
+        if (port->_port_cb != NULL)
+        {
+            port->_port_cb(msg, port);
+        }
+
+        switch (msg.port_event)
+        {
+        case HCD_PORT_EVENT_NONE:
+        case HCD_PORT_EVENT_CONNECTION:
+        case HCD_PORT_EVENT_ERROR:
+        case HCD_PORT_EVENT_OVERCURRENT:
+            break;
+        case HCD_PORT_EVENT_DISCONNECTION:
+            ESP_LOGD("", "physical disconnection detected");
+            port->powerOFF();
+            break;
+
+        case HCD_PORT_EVENT_SUDDEN_DISCONN:
+            ESP_LOGD("", "physical disconnection detected");
+            port->suddenDisconnect();
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------ CLASS ---------------------------------------------------
+
+USBHostPort::USBHostPort()
+{
+    address = 1;
+}
+
+USBHostPort::USBHostPort(uint8_t addr) : address(addr)
+{
+    port_evt_queue = xQueueCreate(5, sizeof(port_event_msg_t));
+}
+
+USBHostPort::~USBHostPort() {}
+
+void USBHostPort::init(port_evt_cb_t cb)
+{
+    hcd_config_t config = {
+        .intr_flags = ESP_INTR_FLAG_LEVEL1,
+    };
+    if (hcd_install(&config) == ESP_OK)
+    {
+        //Initialize a port
+        hcd_port_config_t port_config = {
+            .callback = port_callback,
+            .callback_arg = (void *)port_evt_queue,
+            .context = NULL,
+        };
+        esp_err_t err;
+        if (ESP_OK == (err = hcd_port_init(PORT_NUM, &port_config, &handle)))
+        {
+            if (HCD_PORT_STATE_NOT_POWERED == hcd_port_get_state(handle))
+                ESP_LOGI("", "USB host setup properly");
+
+            phy_force_conn_state(false, 0); //Force disconnected state on PHY
+            if (ESP_OK != powerON())
+                return;
+            ESP_LOGI("", "Port is power ON now");
+            phy_force_conn_state(true, pdMS_TO_TICKS(10)); //Allow for connected state on PHY
+            // return true;
+        }
+        else
+        {
+            ESP_LOGE("", "Error to init port: %d!!!", err);
+            return;
+        }
+    }
+    else
+    {
+        ESP_LOGE("", "Error to install HCD!!!");
+        return;
+    }
+
+    xTaskCreate(port_event_task, "port_task", 3 * 1024, this, 16, NULL);
+    callback = cb;
+}
+
+bool USBHostPort::connect()
+{
+    hcd_port_state_t state;
+    if (HCD_PORT_STATE_DISABLED == getState())
+        ESP_LOGD("", "HCD_PORT_STATE_DISABLED");
+    if (ESP_OK == reset())
+        ESP_LOGD("", "USB device reset");
+    else
+        return false;
+    if (HCD_PORT_STATE_ENABLED == getState())
+    {
+        ESP_LOGD("", "HCD_PORT_STATE_ENABLED");
+        return true;
+    }
+    return false;
+}
+
+void USBHostPort::onPortEvent(port_evt_cb_t cb)
+{
+    _port_cb = cb;
+}
+
+void USBHostPort::onControlEvent(usb_host_event_cb_t cb)
+{
+    ctrl_callback = cb;
+}
+
+
+hcd_port_handle_t USBHostPort::getHandle()
+{
+    return handle;
+}
+
+esp_err_t USBHostPort::reset()
+{
+    ESP_LOGD(__func__, "");
+    return hcd_port_command(handle, HCD_PORT_CMD_RESET);
+}
+
+hcd_port_state_t USBHostPort::getState()
+{
+    return hcd_port_get_state(handle);
+}
+
+esp_err_t USBHostPort::getSpeed(usb_speed_t *port_speed)
+{
+    return hcd_port_get_speed(handle, port_speed);
+}
+
+USBHostPipe *USBHostPort::getCTRLpipe()
+{
+    return ctrlPipe;
+}
+
+void USBHostPort::freeCTRLpipe()
+{
+    delete (ctrlPipe);
+    ctrlPipe = NULL;
+}
+
+esp_err_t USBHostPort::powerON()
+{
+    ESP_LOGD(__func__, "");
+    return hcd_port_command(handle, HCD_PORT_CMD_POWER_ON);
+}
+
+void USBHostPort::powerOFF()
+{
+    ESP_LOGD(__func__, "%d", hcd_port_command(handle, HCD_PORT_CMD_POWER_OFF));
+}
+
+void USBHostPort::suddenDisconnect()
+{
+    esp_err_t err = reset();
+    ESP_LOGI("", "reset status: %d", err);
+    freeCTRLpipe();
+    recover();
+    err = powerON();
+    ESP_LOGI("", "powerON status: %d", err);
+}
+
+void USBHostPort::recover()
+{
+    hcd_port_state_t state;
+    if (HCD_PORT_STATE_RECOVERY == (state = hcd_port_get_state(handle)))
+    {
+        esp_err_t err = hcd_port_recover(handle);
+        ESP_LOGI(__func__, "%d", err);
+    }
+    else
+    {
+        ESP_LOGE("", "hcd_port_state_t: %d", state);
+    }
+}
+
+void USBHostPort::addCTRLpipe(USBHostPipe *pipe)
+{
+    ctrlPipe = pipe;
+    if (ctrlPipe == nullptr)
+        ctrlPipe = new USBHostPipe(handle);
+
+    ctrlPipe->updatePort(handle);
+    ctrlPipe->init();
+}

+ 139 - 0
src/port.h

@@ -0,0 +1,139 @@
+#pragma once
+#include <stdio.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "esp_intr_alloc.h"
+#include "esp_err.h"
+#include "esp_attr.h"
+#include "esp_rom_gpio.h"
+#include "soc/gpio_pins.h"
+#include "soc/gpio_sig_map.h"
+#include "hal/usbh_ll.h"
+#include "hcd.h"
+#include "pipe.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+#endif
+
+#define USBH_WEAK_CB __attribute__((weak))
+#define PORT_NUM 1 // we have only 1 port
+
+typedef uint16_t usb_host_even_t;
+
+typedef struct
+{
+    hcd_port_handle_t port_hdl;
+    hcd_port_event_t port_event;
+} port_event_msg_t;
+
+class USBHostPort;
+
+/**
+ * @brief Port events callback for user space
+ */
+typedef void (*port_evt_cb_t)(port_event_msg_t msg, USBHostPort *port);
+/**
+ * @brief Pipe events callback for user space
+ */
+typedef void (*usb_host_event_cb_t)(usb_host_even_t event, void *data);
+
+class USBHostPort
+{
+protected:
+    // port handle
+    hcd_port_handle_t handle;
+    // control pipe
+    USBHostPipe *ctrlPipe;
+
+public:
+    uint8_t address = 0;
+    port_evt_cb_t callback;
+    USBHostPort();
+    USBHostPort(uint8_t addr);
+    ~USBHostPort();
+
+    /**
+     * @brief Initialize USB host port and create low level port callback task
+     */
+    void init(port_evt_cb_t cb);
+
+    /**
+     * @brief Call this function when physical connection is detected to properly reset port
+     */
+    bool connect();
+
+    /**
+     * @brief Get port state
+     */
+    hcd_port_state_t getState();
+
+    /**
+     * @brief Get speed of connected device
+     */
+    esp_err_t getSpeed(usb_speed_t *port_speed);
+
+    /**
+     * @brief Power ON port
+     */
+    esp_err_t powerON();
+
+    /**
+     * @brief Power OFF port
+     */
+    void powerOFF();
+
+    /**
+     * @brief Reset port 
+     */
+    esp_err_t reset();
+
+    /**
+     * @brief Recover port when state is 
+     */
+    void recover();
+
+    /**
+     * @brief Call when port event is detected 
+     */
+    void suddenDisconnect();
+
+    /**
+     * @brief Add control pipe to port/device
+     */
+    void addCTRLpipe(USBHostPipe *pipe = nullptr);
+
+    /**
+     * @brief Get control pipe
+     */
+    USBHostPipe *getCTRLpipe();
+
+    /**
+     * @brief Delete control pipe when device disconnect detected
+     */
+    void freeCTRLpipe();
+
+    /**
+     * @brief Register port events callback for class space, user space callback 
+     */
+    void onPortEvent(port_evt_cb_t cb);
+
+    /**
+     * @brief Add control pipe callback for user space
+     */
+    void onControlEvent(usb_host_event_cb_t cb);
+
+    /**
+     * @brief Get port handle
+     */
+    hcd_port_handle_t getHandle();
+
+
+    /**
+     * @brief cllbacks
+     */
+    usb_host_event_cb_t ctrl_callback;
+    port_evt_cb_t _port_cb;
+};

+ 480 - 0
src/usb.h

@@ -0,0 +1,480 @@
+// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/*
+Note: This header file contains the types and macros belong/relate to the USB2.0 protocol and are HW implementation
+agnostic. In other words, this header is only meant to be used in the HCD layer and above of the USB Host stack. For
+types and macros that are HW implementation specific (i.e., HAL layer and below), add them to the "usb_types.h" header
+instead.
+*/
+
+#pragma once
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdint.h>
+#include <sys/queue.h>
+
+#define USB_CTRL_REQ_ATTR       __attribute__((packed))
+#define USB_DESC_ATTR           __attribute__((packed))
+
+// ------------------------------------------------ Common USB Types ---------------------------------------------------
+
+// --------------------- Bus Related -----------------------
+
+/**
+ * @brief USB Standard Speeds
+ */
+typedef enum {
+    USB_SPEED_LOW = 0,                  /**< USB Low Speed (1.5 Mbit/s) */
+    USB_SPEED_FULL,                     /**< USB Full Speed (12 Mbit/s) */
+} usb_speed_t;
+
+// ------------------ Transfer Related ---------------------
+
+/**
+ * @brief The type of USB transfer
+ *
+ * @note The enum values need to match the bmAttributes field of an EP descriptor
+ */
+typedef enum {
+    USB_TRANSFER_TYPE_CTRL = 0,
+    USB_TRANSFER_TYPE_ISOCHRONOUS,
+    USB_TRANSFER_TYPE_BULK,
+    USB_TRANSFER_TYPE_INTR,
+} usb_transfer_type_t;
+
+/**
+ * @brief The status of a particular transfer
+ */
+typedef enum {
+    USB_TRANSFER_STATUS_COMPLETED,      /**< The transfer was successful (but may be short) */
+    USB_TRANSFER_STATUS_ERROR,          /**< The transfer failed because due to excessive errors (e.g. no response or CRC error) */
+    USB_TRANSFER_STATUS_TIMED_OUT,      /**< The transfer failed due to a time out */
+    USB_TRANSFER_STATUS_CANCELED,      /**< The transfer was canceled */
+    USB_TRANSFER_STATUS_STALL,          /**< The transfer was stalled */
+    USB_TRANSFER_STATUS_NO_DEVICE,      /**< The transfer failed because the device is no longer valid (e.g., disconnected */
+    USB_TRANSFER_STATUS_OVERFLOW,       /**< The transfer as more data was sent than was requested */
+    USB_TRANSFER_STATUS_SKIPPED,        /**< ISOC only. The packet was skipped due to system latency */
+} usb_transfer_status_t;
+
+/**
+ * @brief Isochronous packet descriptor
+ *
+ * If the number of bytes in an IRP (I/O Request Packet, see USB2.0 Spec) is
+ * larger than the MPS of the endpoint, the IRP is split over multiple packets
+ * (one packet per bInterval of the endpoint). An array of Isochronous packet
+ * descriptors describes how an IRP should be split over multiple packets.
+ */
+typedef struct {
+    int length;                         /**< Number of bytes to transmit/receive in the packet */
+    int actual_length;                  /**< Actual number of bytes transmitted/received in the packet */
+    usb_transfer_status_t status;       /**< Status of the packet */
+} usb_iso_packet_desc_t;
+
+#define USB_IRP_FLAG_ZERO_PACK  0x01    /**< (For bulk OUT only). Indicates that a bulk OUT transfers should always terminate with a short packet, even if it means adding an extra zero length packet */
+
+/**
+ * @brief USB IRP (I/O Request Packet). See USB2.0 Spec
+ *
+ * An IRP is used to represent data transfer request form a software client to and endpoint over the USB bus. The same
+ * IRP object type is used at each layer of the USB stack. This minimizes copying/conversion across the different layers
+ * of the stack as each layer will pass a pointer to this type of object.
+ *
+ * See 10.5.3.1 os USB2.0 specification
+ * Bulk: Represents a single bulk transfer which a pipe will transparently split into multiple MPS transactions (until
+ *       the last)
+ * Control: Represents a single control transfer with the setup packet at the first 8 bytes of the buffer.
+ * Interrupt: Represents a single interrupt transaction
+ * Isochronous: Represents a buffer of a stream of bytes which the pipe will transparently transfer the stream of bytes
+ *              one or more service periods
+ *
+ * @note The tailq_entry and reserved variables are used by the USB Host stack internally. Users should not modify those fields.
+ * @note Once an IRP is submitted, users should not modify the IRP as the Host stack takes ownership of the IRP.
+ */
+struct usb_irp_obj {
+    //Internal members
+    TAILQ_ENTRY(usb_irp_obj) tailq_entry;   /**< TAILQ entry that allows this object to be added to linked lists. Users should NOT modify this field */
+    void *reserved_ptr;                     /**< Reserved pointer variable for internal use in the stack. Users should set this to NULL on allocation and NOT modify this afterwards */
+    uint32_t reserved_flags;                   /**< Reserved variable for flags used internally in the stack. Users should set this to 0 on allocation and NOT modify this afterwards */
+    //Public members
+    uint8_t *data_buffer;                   /**< Pointer to data buffer. Must be DMA capable memory */
+    int num_bytes;                          /**< Number of bytes in IRP. Control should exclude size of setup. IN should be integer multiple of MPS */
+    int actual_num_bytes;                   /**< Actual number of bytes transmitted/receives in the IRP */
+    uint32_t flags;                         /**< IRP flags */
+    usb_transfer_status_t status;           /**< Status of the transfer */
+    uint32_t timeout;                       /**< Timeout (in milliseconds) of the packet (currently not supported yet) */
+    void *context;                          /**< Context variable used to associate the IRP object with another object */
+    int num_iso_packets;                    /**< Only relevant to Isochronous. Number of service periods to transfer data buffer over. Set to 0 for non-iso transfers */
+    usb_iso_packet_desc_t iso_packet_desc[0];   /**< Descriptors for each ISO packet */
+};
+
+typedef struct usb_irp_obj usb_irp_t;
+
+// ---------------------------------------------------- Chapter 9 ------------------------------------------------------
+
+#define USB_B_DESCRIPTOR_TYPE_DEVICE                        1
+#define USB_B_DESCRIPTOR_TYPE_CONFIGURATION                 2
+#define USB_B_DESCRIPTOR_TYPE_STRING                        3
+#define USB_B_DESCRIPTOR_TYPE_INTERFACE                     4
+#define USB_B_DESCRIPTOR_TYPE_ENDPOINT                      5
+#define USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER              6
+#define USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION     7
+#define USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER               8
+
+// ------------------- Control Request ---------------------
+
+/**
+ * @brief Size of a USB control transfer setup packet in bytes
+ */
+#define USB_CTRL_REQ_SIZE       8
+
+/**
+ * @brief Structure representing a USB control transfer setup packet
+ */
+typedef union {
+    struct {
+        uint8_t bRequestType;
+        uint8_t bRequest;
+        uint16_t wValue;
+        uint16_t wIndex;
+        uint16_t wLength;
+    } USB_CTRL_REQ_ATTR;
+    uint8_t val[USB_CTRL_REQ_SIZE];
+} usb_ctrl_req_t;
+_Static_assert(sizeof(usb_ctrl_req_t) == USB_CTRL_REQ_SIZE, "Size of usb_ctrl_req_t incorrect");
+
+/**
+ * @brief Bit masks belonging to the bRequestType field of a setup packet
+ */
+#define USB_B_REQUEST_TYPE_DIR_OUT          (0X00 << 7)
+#define USB_B_REQUEST_TYPE_DIR_IN           (0x01 << 7)
+#define USB_B_REQUEST_TYPE_TYPE_STANDARD    (0x00 << 5)
+#define USB_B_REQUEST_TYPE_TYPE_CLASS       (0x01 << 5)
+#define USB_B_REQUEST_TYPE_TYPE_VENDOR      (0x02 << 5)
+#define USB_B_REQUEST_TYPE_TYPE_RESERVED    (0x03 << 5)
+#define USB_B_REQUEST_TYPE_TYPE_MASK        (0x03 << 5)
+#define USB_B_REQUEST_TYPE_RECIP_DEVICE     (0x00 << 0)
+#define USB_B_REQUEST_TYPE_RECIP_INTERFACE  (0x01 << 0)
+#define USB_B_REQUEST_TYPE_RECIP_ENDPOINT   (0x02 << 0)
+#define USB_B_REQUEST_TYPE_RECIP_OTHER      (0x03 << 0)
+#define USB_B_REQUEST_TYPE_RECIP_MASK       (0x1f << 0)
+
+/**
+ * @brief Bit masks belonging to the bRequest field of a setup packet
+ */
+#define USB_B_REQUEST_GET_STATUS            0x00
+#define USB_B_REQUEST_CLEAR_FEATURE         0x01
+#define USB_B_REQUEST_SET_FEATURE           0x03
+#define USB_B_REQUEST_SET_ADDRESS           0x05
+#define USB_B_REQUEST_GET_DESCRIPTOR        0x06
+#define USB_B_REQUEST_SET_DESCRIPTOR        0x07
+#define USB_B_REQUEST_GET_CONFIGURATION     0x08
+#define USB_B_REQUEST_SET_CONFIGURATION     0x09
+#define USB_B_REQUEST_GET_INTERFACE         0x0A
+#define USB_B_REQUEST_SET_INTERFACE         0x0B
+#define USB_B_REQUEST_SYNCH_FRAME           0x0C
+
+/**
+ * @brief Bit masks belonging to the wValue field of a setup packet
+ */
+#define USB_W_VALUE_DT_DEVICE               0x01
+#define USB_W_VALUE_DT_CONFIG               0x02
+#define USB_W_VALUE_DT_STRING               0x03
+#define USB_W_VALUE_DT_INTERFACE            0x04
+#define USB_W_VALUE_DT_ENDPOINT             0x05
+#define USB_W_VALUE_DT_DEVICE_QUALIFIER     0x06
+#define USB_W_VALUE_DT_OTHER_SPEED_CONFIG   0x07
+#define USB_W_VALUE_DT_INTERFACE_POWER      0x08
+
+/**
+ * @brief Initializer for a SET_ADDRESS request
+ *
+ * Sets the address of a connected device
+ */
+#define USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req_ptr, addr) ({  \
+    (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD |USB_B_REQUEST_TYPE_RECIP_DEVICE;   \
+    (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_ADDRESS;  \
+    (ctrl_req_ptr)->wValue = (addr);   \
+    (ctrl_req_ptr)->wIndex = 0;    \
+    (ctrl_req_ptr)->wLength = 0;   \
+})
+
+/**
+ * @brief Initializer for a request to get a device's device descriptor
+ */
+#define USB_CTRL_REQ_INIT_GET_DEVC_DESC(ctrl_req_ptr) ({ \
+    (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE;   \
+    (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR;   \
+    (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_DEVICE << 8); \
+    (ctrl_req_ptr)->wIndex = 0;    \
+    (ctrl_req_ptr)->wLength = 18;  \
+})
+
+/**
+ * @brief Initializer for a request to get a device's current configuration number
+ */
+#define USB_CTRL_REQ_INIT_GET_CONFIG(ctrl_req_ptr) ({  \
+    (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE;   \
+    (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_CONFIGURATION;    \
+    (ctrl_req_ptr)->wValue = 0;    \
+    (ctrl_req_ptr)->wIndex = 0;    \
+    (ctrl_req_ptr)->wLength = 1;   \
+})
+
+/**
+ * @brief Initializer for a request to get one of the device's current configuration descriptor
+ *
+ * - desc_index indicates the configuration's index number
+ * - Number of bytes of the configuration descriptor to get
+ */
+#define USB_CTRL_REQ_INIT_GET_CFG_DESC(ctrl_req_ptr, desc_index, desc_len) ({  \
+    (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE;   \
+    (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR;   \
+    (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_CONFIG << 8) | ((desc_index) & 0xFF); \
+    (ctrl_req_ptr)->wIndex = 0;    \
+    (ctrl_req_ptr)->wLength = (desc_len);  \
+})
+
+/**
+ * @brief Initializer for a request to set a device's current configuration number
+ */
+#define USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req_ptr, config_num) ({  \
+    (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE;   \
+    (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_CONFIGURATION;    \
+    (ctrl_req_ptr)->wValue = (config_num); \
+    (ctrl_req_ptr)->wIndex = 0;    \
+    (ctrl_req_ptr)->wLength = 0;   \
+})
+
+/**
+ * @brief Initializer for a request to set an interface's alternate setting
+ */
+#define USB_CTRL_REQ_INIT_SET_INTERFACE(ctrl_req_ptr, intf_num, alt_setting_num) ({    \
+    (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_INTERFACE;  \
+    (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_INTERFACE; \
+    (ctrl_req_ptr)->wValue = (alt_setting_num); \
+    (ctrl_req_ptr)->wIndex = (intf_num);  \
+    (ctrl_req_ptr)->wLength = 0;   \
+})
+
+// ------------------ Device Descriptor --------------------
+
+/**
+ * @brief Size of a USB device descriptor in bytes
+ */
+#define USB_DESC_DEVC_SIZE       18
+
+/**
+ * @brief Structure representing a USB device descriptor
+ */
+typedef union {
+    struct {
+        uint8_t bLength;
+        uint8_t bDescriptorType;
+        uint16_t bcdUSB;
+        uint8_t bDeviceClass;
+        uint8_t bDeviceSubClass;
+        uint8_t bDeviceProtocol;
+        uint8_t bMaxPacketSize0;
+        uint16_t idVendor;
+        uint16_t idProduct;
+        uint16_t bcdDevice;
+        uint8_t iManufacturer;
+        uint8_t iProduct;
+        uint8_t iSerialNumber;
+        uint8_t bNumConfigurations;
+    } USB_DESC_ATTR;
+    uint8_t val[USB_DESC_DEVC_SIZE];
+} usb_desc_devc_t;
+_Static_assert(sizeof(usb_desc_devc_t) == USB_DESC_DEVC_SIZE, "Size of usb_desc_devc_t incorrect");
+
+/**
+ * @brief Possible base class values of the bDeviceClass field of a USB device descriptor
+ */
+#define USB_CLASS_PER_INTERFACE             0x00
+#define USB_CLASS_AUDIO                     0x01
+#define USB_CLASS_COMM                      0x02
+#define USB_CLASS_HID                       0x03
+#define USB_CLASS_PHYSICAL                  0x05
+#define USB_CLASS_STILL_IMAGE               0x06
+#define USB_CLASS_PRINTER                   0x07
+#define USB_CLASS_MASS_STORAGE              0x08
+#define USB_CLASS_HUB                       0x09
+#define USB_CLASS_CDC_DATA                  0x0a
+#define USB_CLASS_CSCID                     0x0b
+#define USB_CLASS_CONTENT_SEC               0x0d
+#define USB_CLASS_VIDEO                     0x0e
+#define USB_CLASS_WIRELESS_CONTROLLER       0xe0
+#define USB_CLASS_PERSONAL_HEALTHCARE       0x0f
+#define USB_CLASS_AUDIO_VIDEO               0x10
+#define USB_CLASS_BILLBOARD                 0x11
+#define USB_CLASS_USB_TYPE_C_BRIDGE         0x12
+#define USB_CLASS_MISC                      0xef
+#define USB_CLASS_APP_SPEC                  0xfe
+#define USB_CLASS_VENDOR_SPEC               0xff
+
+/**
+ * @brief Vendor specific subclass code
+ */
+#define USB_SUBCLASS_VENDOR_SPEC            0xff
+
+// -------------- Configuration Descriptor -----------------
+
+/**
+ * @brief Size of a short USB configuration descriptor in bytes
+ *
+ * @note The size of a full USB configuration includes all the interface and endpoint
+ *       descriptors of that configuration.
+ */
+#define USB_DESC_CFG_SIZE       9
+
+/**
+ * @brief Structure representing a short USB configuration descriptor
+ *
+ * @note The full USB configuration includes all the interface and endpoint
+ *       descriptors of that configuration.
+ */
+typedef union {
+    struct {
+        uint8_t bLength;
+        uint8_t bDescriptorType;
+        uint16_t wTotalLength;
+        uint8_t bNumInterfaces;
+        uint8_t bConfigurationValue;
+        uint8_t iConfiguration;
+        uint8_t bmAttributes;
+        uint8_t bMaxPower;
+    } USB_DESC_ATTR;
+    uint8_t val[USB_DESC_CFG_SIZE];
+} usb_desc_cfg_t;
+_Static_assert(sizeof(usb_desc_cfg_t) == USB_DESC_CFG_SIZE, "Size of usb_desc_cfg_t incorrect");
+
+/**
+ * @brief Bit masks belonging to the bmAttributes field of a configuration descriptor
+ */
+#define USB_BM_ATTRIBUTES_ONE               (1 << 7)    //Must be set
+#define USB_BM_ATTRIBUTES_SELFPOWER         (1 << 6)    //Self powered
+#define USB_BM_ATTRIBUTES_WAKEUP            (1 << 5)    //Can wake-up
+#define USB_BM_ATTRIBUTES_BATTERY           (1 << 4)    //Battery powered
+
+// ---------------- Interface Descriptor -------------------
+
+/**
+ * @brief Size of a USB interface descriptor in bytes
+ */
+#define USB_DESC_INTF_SIZE      9
+
+/**
+ * @brief Structure representing a USB interface descriptor
+ */
+typedef union {
+    struct {
+        uint8_t bLength;
+        uint8_t bDescriptorType;
+        uint8_t bInterfaceNumber;
+        uint8_t bAlternateSetting;
+        uint8_t bNumEndpoints;
+        uint8_t bInterfaceClass;
+        uint8_t bInterfaceSubClass;
+        uint8_t bInterfaceProtocol;
+        uint8_t iInterface;
+    } USB_DESC_ATTR;
+    uint8_t val[USB_DESC_INTF_SIZE];
+} usb_desc_intf_t;
+_Static_assert(sizeof(usb_desc_intf_t) == USB_DESC_INTF_SIZE, "Size of usb_desc_intf_t incorrect");
+
+// ----------------- Endpoint Descriptor -------------------
+
+/**
+ * @brief Size of a USB endpoint descriptor in bytes
+ */
+#define USB_DESC_EP_SIZE        7
+
+/**
+ * @brief Structure representing a USB endpoint descriptor
+ */
+typedef union {
+    struct {
+        uint8_t bLength;
+        uint8_t bDescriptorType;
+        uint8_t bEndpointAddress;
+        uint8_t bmAttributes;
+        uint16_t wMaxPacketSize;
+        uint8_t bInterval;
+    } USB_DESC_ATTR;
+    uint8_t val[USB_DESC_EP_SIZE];
+} usb_desc_ep_t;
+_Static_assert(sizeof(usb_desc_ep_t) == USB_DESC_EP_SIZE, "Size of usb_desc_ep_t incorrect");
+
+/**
+ * @brief Bit masks belonging to the bEndpointAddress field of an endpoint descriptor
+ */
+#define USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK              0x0f
+#define USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK              0x80
+
+/**
+ * @brief Bit masks belonging to the bmAttributes field of an endpoint descriptor
+ */
+#define USB_BM_ATTRIBUTES_XFERTYPE_MASK                 0x03
+#define USB_BM_ATTRIBUTES_XFER_CONTROL                  (0 << 0)
+#define USB_BM_ATTRIBUTES_XFER_ISOC                     (1 << 0)
+#define USB_BM_ATTRIBUTES_XFER_BULK                     (2 << 0)
+#define USB_BM_ATTRIBUTES_XFER_INT                      (3 << 0)
+#define USB_BM_ATTRIBUTES_SYNCTYPE_MASK                 0x0C    /* in bmAttributes */
+#define USB_BM_ATTRIBUTES_SYNC_NONE                     (0 << 2)
+#define USB_BM_ATTRIBUTES_SYNC_ASYNC                    (1 << 2)
+#define USB_BM_ATTRIBUTES_SYNC_ADAPTIVE                 (2 << 2)
+#define USB_BM_ATTRIBUTES_SYNC_SYNC                     (3 << 2)
+#define USB_BM_ATTRIBUTES_USAGETYPE_MASK                0x30
+#define USB_BM_ATTRIBUTES_USAGE_DATA                    (0 << 4)
+#define USB_BM_ATTRIBUTES_USAGE_FEEDBACK                (1 << 4)
+#define USB_BM_ATTRIBUTES_USAGE_IMPLICIT_FB             (2 << 4)
+
+/**
+ * @brief Macro helpers to get information about an endpoint from its descriptor
+ */
+#define USB_DESC_EP_GET_XFERTYPE(desc_ptr) ((usb_transfer_type_t) ((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK))
+#define USB_DESC_EP_GET_EP_NUM(desc_ptr) ((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK)
+#define USB_DESC_EP_GET_EP_DIR(desc_ptr) (((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) ? 1 : 0)
+#define USB_DESC_EP_GET_MPS(desc_ptr) ((desc_ptr)->wMaxPacketSize & 0x7FF)
+
+// ------------------ String Descriptor --------------------
+
+/**
+ * @brief Size of a short USB string descriptor in bytes
+ */
+#define USB_DESC_STR_SIZE       4
+
+/**
+ * @brief Structure representing a USB string descriptor
+ */
+typedef union {
+    struct {
+        uint8_t bLength;
+        uint8_t bDescriptorType;
+        uint16_t wData[1];        /* UTF-16LE encoded */
+    } USB_DESC_ATTR;
+    uint8_t val[USB_DESC_STR_SIZE];
+} usb_desc_str_t;
+_Static_assert(sizeof(usb_desc_str_t) == USB_DESC_STR_SIZE, "Size of usb_desc_str_t incorrect");
+
+#ifdef __cplusplus
+}
+#endif