USB: Change IRP to URBs

This commit changes the IRP (I/O Request Packet) structure to
the URB (USB Request Block) in order to represent USB transfers
in the host stack. The new URB strcuture:

- Add extra safety with const fields
- Allows each layer of the stack to add their own overhead variables

The HCD layer API and tests have been updated to use this new URB structure
This commit is contained in:
Darian Leung 2021-06-09 21:49:08 +08:00
parent 1d1060e25b
commit 18f9dabce1
12 changed files with 887 additions and 743 deletions

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@ The HAL layer abstracts the DWC_OTG operating in Host Mode using Internal Scatte
The HCD (Host Controller Driver) abstracts the DWC_OTG as N number of ports and an arbitrary number of pipes that can be routed through one of the ports to a device. However note that **the underlying hardware controller only has a single port, so technically only one port can ever be enabled**.
- In other words, the HCD essentially implements a root hub (not fully behavioral compliant) that contains a single port.
- Pipes are "an association between an endpoint on a device and software on the host". IRPs (I/O Request Packets) that each represent a USB transfer can be enqueued into a pipe for transmission, and dequeued from a pipe when completed.
- Pipes are "an association between an endpoint on a device and software on the host". URBs (USB Request Block) represent a USB transfer that can be enqueued into a pipe for transmission, and dequeued from a pipe when completed.
The HCD currently has the following limitations:
@ -72,12 +72,11 @@ The HCD currently has the following limitations:
## HCD Pipes
- Pipes can be opened to a particular endpoint based on a descriptor provided on allocation. If opening a default pipe, a `NULL` descriptor can be provided.
- IRPs can be enqueued into a pipe. Pipes use a linked list internally, so there is in-theory no limit to the number of IRPs that can be enqueued.
- IRPs need to be dequeued once they are completed.
- IRPs need to have the transfer information (such as data buffer, transfer length in bytes) filled before they should be enqueued.
- IRPs will be owned by the HCD until they are dequeued. Thus, users should not attempt to modify an IRP object (and the IRP's data buffer) until the IRP is dequeued.
- The IRP is defined in `usb.h` instead of `hcd.h` so that it can be used throughout the entire Host stack. Each layer simply needs to pass the pointer of the IRP to the next layer thus minimizing the amount of copying required.
- URBs can be enqueued into a pipe. Pipes use a linked list internally, so there is in-theory no limit to the number of URBs that can be enqueued.
- URBs need to be dequeued once they are completed.
- URBs need to have the transfer information (such as data buffer, transfer length in bytes) filled before they should be enqueued.
- URBs will be owned by the HCD until they are dequeued. Thus, users should not attempt to modify an URB object (and the URB's data buffer) until the URB is dequeued.
- The URB is defined in `usb_private.h` instead of `hcd.h` so that the same structure can shared throughout the entire Host stack. Each layer simply needs to pass the pointer of the URB to the next layer thus minimizing the amount of copying required.
## HCD SW Arch

View File

@ -22,6 +22,7 @@ extern "C" {
#include <stdbool.h>
#include <sys/queue.h>
#include "esp_err.h"
#include "usb_private.h"
#include "usb.h"
// ------------------------------------------------- Macros & Types ----------------------------------------------------
@ -51,14 +52,14 @@ typedef enum {
* @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.
* - Pipe is able to transmit data. URBs can be enqueued.
* - Event if pipe has no URBs enqueued, it can still be in the active state.
* Halted:
* - An error has occurred on the pipe. IRPs will no longer be executed.
* - An error has occurred on the pipe. URBs 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.
* - Pending URBs should be dequeued and the pipe should be freed.
*/
typedef enum {
HCD_PIPE_STATE_ACTIVE, /**< The pipe is active */
@ -91,10 +92,10 @@ typedef enum {
*/
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_URB_DONE, /**< The pipe has completed an URB. The URB can be dequeued */
HCD_PIPE_EVENT_INVALID, /**< The pipe is invalid because the underlying device is no longer valid */
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_URB_NOT_AVAIL, /**< URB 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 */
@ -107,11 +108,12 @@ typedef enum {
*/
typedef enum {
HCD_PORT_CMD_POWER_ON, /**< Power ON the port */
HCD_PORT_CMD_POWER_OFF, /**< Power OFF the port */
HCD_PORT_CMD_POWER_OFF, /**< Power OFF the port. If the port is enabled, this will cause a HCD_PORT_EVENT_SUDDEN_DISCONN event.
If the port is disabled, this will cause a HCD_PORT_EVENT_DISCONNECTION event. */
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_DISABLE, /**< Disable the port (stops the SOFs or keep alive). Any created pipes will receive a HCD_PIPE_EVENT_INVALID event */
} hcd_port_cmd_t;
/**
@ -120,8 +122,8 @@ typedef enum {
* 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_ABORT, /**< Retire all scheduled URBs. Pipe's state remains unchanged */
HCD_PIPE_CMD_RESET, /**< Retire all scheduled URBs. 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;
@ -143,14 +145,14 @@ typedef void * hcd_pipe_handle_t;
*
* 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);
typedef bool (*hcd_port_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 bool (*hcd_pipe_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 */
@ -169,7 +171,7 @@ typedef struct {
* @brief Port configuration structure
*/
typedef struct {
hcd_port_isr_callback_t callback; /**< HCD port event callback */
hcd_port_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;
@ -180,7 +182,7 @@ typedef struct {
* @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 */
hcd_pipe_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 */
@ -236,7 +238,7 @@ esp_err_t hcd_uninstall(void);
* @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);
esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl);
/**
* @brief Deinitialize a particular port
@ -376,7 +378,7 @@ esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pi
*
* 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
* - All URBs have been dequeued
*
* @param pipe_hdl Pipe handle
*
@ -392,7 +394,7 @@ esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl);
* 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
* - All URBs have been dequeued from the pipe
*
* @param pipe_hdl Pipe handle
* @param mps New Maximum Packet Size
@ -409,7 +411,7 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps);
* 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
* - All URBs have been dequeued from the pipe
*
* @param pipe_hdl Pipe handle
* @param dev_addr New device address
@ -438,19 +440,19 @@ 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
* Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all URBs 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
* @note Some pipe commands will block until the pipe's current in-flight URB 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
* @retval ESP_ERR_INVALID_RESPONSE: The pipe's state changed unexpectedly
*/
esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command);
@ -465,47 +467,47 @@ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command);
*/
hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl);
// ---------------------------------------------------- HCD IRPs -------------------------------------------------------
// ---------------------------------------------------- HCD URBs -------------------------------------------------------
/**
* @brief Enqueue an IRP to a particular pipe
* @brief Enqueue an URB 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 following conditions must be met before an URB can be enqueued:
* - The URB is properly initialized (data buffer and transfer length are set)
* - The URB 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
* @param urb URB to enqueue
* @retval ESP_OK: URB enqueued successfully
* @retval ESP_ERR_INVALID_STATE: Conditions not met to enqueue URB
*/
esp_err_t hcd_irp_enqueue(hcd_pipe_handle_t pipe_hdl, usb_irp_t *irp);
esp_err_t hcd_urb_enqueue(hcd_pipe_handle_t pipe_hdl, urb_t *urb);
/**
* @brief Dequeue an IRP from a particular pipe
* @brief Dequeue an URB 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.
* This function should be called on a pipe after a pipe receives a HCD_PIPE_EVENT_URB_DONE event. If a pipe has
* multiple URBs that can be dequeued, this function should be called repeatedly until all URBs are dequeued. If a pipe
* has no more URBs 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
* @return urb_t* Dequeued URB, or NULL if no more URBs to dequeue
*/
usb_irp_t *hcd_irp_dequeue(hcd_pipe_handle_t pipe_hdl);
urb_t *hcd_urb_dequeue(hcd_pipe_handle_t pipe_hdl);
/**
* @brief Abort an enqueued IRP
* @brief Abort an enqueued URB
*
* 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
* This function will attempt to abort an URB that is already enqueued. If the URB has yet to be executed, it will be
* "cancelled" and can then be dequeued. If the URB is currenty in-flight or has already completed, the URB 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
* @param urb URB to abort
* @retval ESP_OK: URB successfully aborted, or was not affected by this function
* @retval ESP_ERR_INVALID_STATE: URB was never enqueued
*/
esp_err_t hcd_irp_abort(usb_irp_t *irp);
esp_err_t hcd_urb_abort(urb_t *urb);
#ifdef __cplusplus
}

View File

@ -15,8 +15,8 @@
/*
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.
types and macros that are HW implementation specific (i.e., HAL layer and below), add them to the
"hal/usb_types_private.h" header instead.
*/
#pragma once
@ -28,9 +28,6 @@ extern "C"
{
#endif
#include <stdint.h>
#include <sys/queue.h>
#define USB_CTRL_REQ_ATTR __attribute__((packed))
#define USB_DESC_ATTR __attribute__((packed))
@ -71,72 +68,110 @@ typedef enum {
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_SKIPPED, /**< ISOC packets only. The packet was skipped due to system latency or bus overload */
} usb_transfer_status_t;
#define USB_TRANSFER_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 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.
* If the number of bytes in an Isochronous transfer is larger than the MPS of the endpoint, the transfer is split
* into multiple packets transmitted at the endpoint's specified interval. An array of Isochronous packet descriptors
* describes how an Isochronous transfer should be split into 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 */
int num_bytes; /**< Number of bytes to transmit/receive in the packet */
int actual_num_bytes; /**< 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 */
} usb_isoc_packet_desc_t;
/**
* @brief USB IRP (I/O Request Packet). See USB2.0 Spec
* @brief USB transfer structure
*
* 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.
* This structure is used to represent a transfer from a software client to an endopint over the USB bus. Some of the
* fields are made const on purpose as they are fixed on allocation. Users should call the appropriate USB Host Driver
* function to allocate a USB transfer structure instead of allocating this structure themselves.
*
* 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
* The transfer type is inferred from the endpoint this transfer is sent to. Depending on the transfer type, users
* should note the following:
*
* @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.
* - Bulk: This structure represents a single bulk transfer. If the number of bytes exceeds the endpoint's MPS, the
* transfer will be split into multiple MPS sized packets followed by a short packet.
* - Control: This structure represents a single control transfer. This first 8 bytes of the data_buffer must be filled
* with the setup packet. The num_bytes field should exclude the size of the setup packet (i.e., set to 0 if
* the control transfer has no data stage).
* - Interrupt: Represents an interrupt transfer. If num_bytes exceeds the MPS of the endpoint, the transfer will be
* split into multiple packets, and each packet is transferred at the endpoint's specified interval.
* - Isochronous: Represents a stream of bytes that should be transferred to an endpoint at a fixed rate. The transfer
* is split into packets according to the each isoc_packet_desc. A packet is transferred at each interval
* of the endpoint.
*
* @note For Bulk/Control/Interrupt IN transfers, the num_bytes must be a integer multiple of the endpoint's MPS
* @note This structure should be allocated via __insert_func_name__()
* @note Once the transfer has be submitted, users should not modify the structure until the transfer has completed
*/
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 */
typedef struct usb_transfer_obj usb_transfer_t;
/**
* @brief USB transfer completion callback
*/
typedef void (*usb_transfer_cb_t)(usb_transfer_t *transfer);
struct usb_transfer_obj{
uint8_t *const data_buffer; /**< Pointer to data buffer */
const size_t data_buffer_size; /**< Size of the data buffer in bytes */
int num_bytes; /**< Number of bytes to transfer. Control transfers should exclude size of setup packet. IN transfers should be integer multiple of MPS */
int actual_num_bytes; /**< Actual number of bytes transferred */
uint32_t flags; /**< Transfer 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 */
usb_transfer_cb_t callback; /**< Transfer callback */
void *context; /**< Context variable for transfer to associate transfer with something */
const int num_isoc_packets; /**< Only relevant to Isochronous. Number of service periods (i.e., intervals) to transfer data buffer over. */
usb_isoc_packet_desc_t isoc_packet_desc[0]; /**< Descriptors for each Isochronous 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
/**
* @brief Descriptor types from USB2.0 specification table 9.5
*/
#define USB_B_DESCRIPTOR_TYPE_DEVICE 0x01
#define USB_B_DESCRIPTOR_TYPE_CONFIGURATION 0x02
#define USB_B_DESCRIPTOR_TYPE_STRING 0x03
#define USB_B_DESCRIPTOR_TYPE_INTERFACE 0x04
#define USB_B_DESCRIPTOR_TYPE_ENDPOINT 0x05
#define USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER 0x06
#define USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION 0x07
#define USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER 0x08
/**
* @brief Descriptor types from USB 2.0 ECN
*/
#define USB_B_DESCRIPTOR_TYPE_OTG 0x09
#define USB_B_DESCRIPTOR_TYPE_DEBUG 0x0a
#define USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION 0x0b
/**
* @brief Descriptor types from Wireless USB spec
*/
#define USB_B_DESCRIPTOR_TYPE_SECURITY 0x0c
#define USB_B_DESCRIPTOR_TYPE_KEY 0x0d
#define USB_B_DESCRIPTOR_TYPE_ENCRYPTION_TYPE 0x0e
#define USB_B_DESCRIPTOR_TYPE_BOS 0x0f
#define USB_B_DESCRIPTOR_TYPE_DEVICE_CAPABILITY 0x10
#define USB_B_DESCRIPTOR_TYPE_WIRELESS_ENDPOINT_COMP 0x11
#define USB_B_DESCRIPTOR_TYPE_WIRE_ADAPTER 0x21
#define USB_B_DESCRIPTOR_TYPE_RPIPE 0x22
#define USB_B_DESCRIPTOR_TYPE_CS_RADIO_CONTROL 0x23
/**
* @brief Descriptor types from UAS specification
*/
#define USB_B_DESCRIPTOR_TYPE_PIPE_USAGE 0x24
// ------------------- Control Request ---------------------
@ -219,7 +254,7 @@ _Static_assert(sizeof(usb_ctrl_req_t) == USB_CTRL_REQ_SIZE, "Size of usb_ctrl_re
/**
* @brief Initializer for a request to get a device's device descriptor
*/
#define USB_CTRL_REQ_INIT_GET_DEVC_DESC(ctrl_req_ptr) ({ \
#define USB_CTRL_REQ_INIT_GET_DEVICE_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); \
@ -244,7 +279,7 @@ _Static_assert(sizeof(usb_ctrl_req_t) == USB_CTRL_REQ_SIZE, "Size of usb_ctrl_re
* - 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) ({ \
#define USB_CTRL_REQ_INIT_GET_CONFIG_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); \
@ -274,12 +309,33 @@ _Static_assert(sizeof(usb_ctrl_req_t) == USB_CTRL_REQ_SIZE, "Size of usb_ctrl_re
(ctrl_req_ptr)->wLength = 0; \
})
// ---------------- Standard Descriptor --------------------
/**
* @brief Size of dummy USB standard descriptor
*/
#define USB_DESC_STANDARD_SIZE 2
/**
* @brief Dummy USB standard descriptor
*
* All USB standard descriptors start with these two bytes. Use this type traversing over descriptors
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_STANDARD_SIZE];
} usb_desc_standard_t;
_Static_assert(sizeof(usb_desc_standard_t) == USB_DESC_STANDARD_SIZE, "Size of usb_desc_standard_t incorrect");
// ------------------ Device Descriptor --------------------
/**
* @brief Size of a USB device descriptor in bytes
*/
#define USB_DESC_DEVC_SIZE 18
#define USB_DESC_DEVICE_SIZE 18
/**
* @brief Structure representing a USB device descriptor
@ -301,9 +357,9 @@ typedef union {
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");
uint8_t val[USB_DESC_DEVICE_SIZE];
} usb_desc_device_t;
_Static_assert(sizeof(usb_desc_device_t) == USB_DESC_DEVICE_SIZE, "Size of usb_desc_device_t incorrect");
/**
* @brief Possible base class values of the bDeviceClass field of a USB device descriptor
@ -343,7 +399,7 @@ _Static_assert(sizeof(usb_desc_devc_t) == USB_DESC_DEVC_SIZE, "Size of usb_desc_
* @note The size of a full USB configuration includes all the interface and endpoint
* descriptors of that configuration.
*/
#define USB_DESC_CFG_SIZE 9
#define USB_DESC_CONFIG_SIZE 9
/**
* @brief Structure representing a short USB configuration descriptor
@ -362,9 +418,9 @@ typedef union {
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");
uint8_t val[USB_DESC_CONFIG_SIZE];
} usb_desc_config_t;
_Static_assert(sizeof(usb_desc_config_t) == USB_DESC_CONFIG_SIZE, "Size of usb_desc_config_t incorrect");
/**
* @brief Bit masks belonging to the bmAttributes field of a configuration descriptor
@ -374,6 +430,31 @@ _Static_assert(sizeof(usb_desc_cfg_t) == USB_DESC_CFG_SIZE, "Size of usb_desc_cf
#define USB_BM_ATTRIBUTES_WAKEUP (1 << 5) //Can wake-up
#define USB_BM_ATTRIBUTES_BATTERY (1 << 4) //Battery powered
// ---------- Interface Association Descriptor -------------
/**
* @brief Size of a USB interface association descriptor in bytes
*/
#define USB_DESC_INTF_ASSOC_SIZE 9
/**
* @brief Structure representing a USB interface association descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bFirstInterface;
uint8_t bInterfaceCount;
uint8_t bFunctionClass;
uint8_t bFunctionSubClass;
uint8_t bFunctionProtocol;
uint8_t iFunction;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_INTF_ASSOC_SIZE];
} usb_desc_iad_t;
_Static_assert(sizeof(usb_desc_iad_t) == USB_DESC_INTF_ASSOC_SIZE, "Size of usb_desc_iad_t incorrect");
// ---------------- Interface Descriptor -------------------
/**

View File

@ -0,0 +1,55 @@
// Copyright 2021 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
#include <stddef.h>
#include <stdint.h>
#include <sys/queue.h>
#include "usb.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint8_t *data_buffer;
size_t data_buffer_size;
int num_bytes;
int actual_num_bytes;
uint32_t flags;
usb_transfer_status_t status;
uint32_t timeout;
usb_transfer_cb_t callback;
void *context;
int num_isoc_packets;
usb_isoc_packet_desc_t isoc_packet_desc[0];
} usb_transfer_dummy_t;
_Static_assert(sizeof(usb_transfer_dummy_t) == sizeof(usb_transfer_t), "usb_transfer_dummy_t does not match usb_transfer_t");
struct urb_obj{
TAILQ_ENTRY(urb_obj) tailq_entry;
//HCD context pointer and variables. Must be initialized to NULL and 0 respectively
void *hcd_ptr;
uint32_t hcd_var;
//Host Driver layer will add its fields here.
//Public transfer structure. Must be last due to variable length array
usb_transfer_t transfer;
};
typedef struct urb_obj urb_t;
#ifdef __cplusplus
}
#endif

View File

@ -72,7 +72,7 @@ static const usb_desc_ep_t bulk_in_ep_desc = {
#define MOCK_MSC_SCSI_SECTOR_SIZE 512
#define MOCK_MSC_SCSI_LUN 0
#define MSC_SCSI_INTR_NUMBER 0
#define MOCK_MSC_SCSI_INTF_NUMBER 0
#define MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req_ptr, intf_num) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_CLASS | USB_B_REQUEST_TYPE_RECIP_INTERFACE; \
@ -116,22 +116,18 @@ typedef struct __attribute__((packed)) {
static void mock_msc_reset_req(hcd_pipe_handle_t default_pipe)
{
//Create IRP
usb_irp_t *irp = heap_caps_calloc(1, sizeof(usb_irp_t), MALLOC_CAP_DEFAULT);
TEST_ASSERT_NOT_EQUAL(NULL, irp);
irp->data_buffer = heap_caps_malloc(sizeof(usb_ctrl_req_t), MALLOC_CAP_DMA);
TEST_ASSERT_NOT_EQUAL(NULL, irp->data_buffer);
usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)irp->data_buffer;
MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req, MSC_SCSI_INTR_NUMBER);
irp->num_bytes = 0;
//Enqueue, wait, dequeue, and check IRP
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
//Free IRP
heap_caps_free(irp->data_buffer);
heap_caps_free(irp);
//Create URB
urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_ctrl_req_t));
usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)urb->transfer.data_buffer;
MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req, MOCK_MSC_SCSI_INTF_NUMBER);
urb->transfer.num_bytes = 0;
//Enqueue, wait, dequeue, and check URB
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//Free URB
test_hcd_free_urb(urb);
}
static void mock_msc_scsi_init_cbw(mock_msc_bulk_cbw_t *cbw, bool is_read, int offset, int num_sectors, uint32_t tag)
@ -180,28 +176,28 @@ static bool mock_msc_scsi_check_csw(mock_msc_bulk_csw_t *csw, uint32_t tag_expec
// --------------------------------------------------- Test Cases ------------------------------------------------------
/*
Test HCD bulk pipe IRPs
Test HCD bulk pipe URBs
Purpose:
- Test that a bulk pipe can be created
- IRPs can be created and enqueued to the bulk pipe pipe
- Bulk pipe returns HCD_PIPE_EVENT_IRP_DONE for completed IRPs
- URBs can be created and enqueued to the bulk pipe pipe
- Bulk pipe returns HCD_PIPE_EVENT_URB_DONE for completed URBs
- Test utilizes a bare bones (i.e., mock) MSC class using SCSI commands
Procedure:
- Setup HCD and wait for connection
- Allocate default pipe and enumerate the device
- Allocate separate IRPS for CBW, Data, and CSW transfers of the MSC class
- Read TEST_NUM_SECTORS number of sectors for the mass storage device
- Expect HCD_PIPE_EVENT_IRP_DONE for each IRP
- Deallocate IRPs
- Allocate separate URBS for CBW, Data, and CSW transfers of the MSC class
- Read TEST_NUM_SECTORS_TOTAL number of sectors for the mass storage device
- Expect HCD_PIPE_EVENT_URB_DONE for each URB
- Deallocate URBs
- Teardown
*/
#define TEST_NUM_SECTORS 10
#define TEST_NUM_SECTORS_PER_ITER 2
#define TEST_NUM_SECTORS_TOTAL 10
#define TEST_NUM_SECTORS_PER_XFER 2
TEST_CASE("Test HCD bulk pipe IRPs", "[hcd][ignore]")
TEST_CASE("Test HCD bulk pipe URBs", "[hcd][ignore]")
{
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
@ -209,50 +205,50 @@ TEST_CASE("Test HCD bulk pipe IRPs", "[hcd][ignore]")
//Enumerate and reset MSC SCSI device
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); //Create a default pipe (using a NULL EP descriptor)
uint8_t dev_addr = test_hcd_enum_devc(default_pipe);
uint8_t dev_addr = test_hcd_enum_device(default_pipe);
mock_msc_reset_req(default_pipe);
//Create BULK IN and BULK OUT pipes for SCSI
hcd_pipe_handle_t bulk_out_pipe = test_hcd_pipe_alloc(port_hdl, &bulk_out_ep_desc, dev_addr, port_speed);
hcd_pipe_handle_t bulk_in_pipe = test_hcd_pipe_alloc(port_hdl, &bulk_in_ep_desc, dev_addr, port_speed);
//Create IRPs for CBW, Data, and CSW transport. IN Buffer sizes are rounded up to nearest MPS
usb_irp_t *irp_cbw = test_hcd_alloc_irp(0, sizeof(mock_msc_bulk_cbw_t));
usb_irp_t *irp_data = test_hcd_alloc_irp(0, TEST_NUM_SECTORS_PER_ITER * MOCK_MSC_SCSI_SECTOR_SIZE);
usb_irp_t *irp_csw = test_hcd_alloc_irp(0, sizeof(mock_msc_bulk_csw_t) + (bulk_in_ep_desc.wMaxPacketSize - (sizeof(mock_msc_bulk_csw_t) % bulk_in_ep_desc.wMaxPacketSize)));
irp_cbw->num_bytes = sizeof(mock_msc_bulk_cbw_t);
irp_data->num_bytes = TEST_NUM_SECTORS_PER_ITER * MOCK_MSC_SCSI_SECTOR_SIZE;
irp_csw->num_bytes = sizeof(mock_msc_bulk_csw_t) + (bulk_in_ep_desc.wMaxPacketSize - (sizeof(mock_msc_bulk_csw_t) % bulk_in_ep_desc.wMaxPacketSize));
//Create URBs for CBW, Data, and CSW transport. IN Buffer sizes are rounded up to nearest MPS
urb_t *urb_cbw = test_hcd_alloc_urb(0, sizeof(mock_msc_bulk_cbw_t));
urb_t *urb_data = test_hcd_alloc_urb(0, TEST_NUM_SECTORS_PER_XFER * MOCK_MSC_SCSI_SECTOR_SIZE);
urb_t *urb_csw = test_hcd_alloc_urb(0, sizeof(mock_msc_bulk_csw_t) + (bulk_in_ep_desc.wMaxPacketSize - (sizeof(mock_msc_bulk_csw_t) % bulk_in_ep_desc.wMaxPacketSize)));
urb_cbw->transfer.num_bytes = sizeof(mock_msc_bulk_cbw_t);
urb_data->transfer.num_bytes = TEST_NUM_SECTORS_PER_XFER * MOCK_MSC_SCSI_SECTOR_SIZE;
urb_csw->transfer.num_bytes = sizeof(mock_msc_bulk_csw_t) + (bulk_in_ep_desc.wMaxPacketSize - (sizeof(mock_msc_bulk_csw_t) % bulk_in_ep_desc.wMaxPacketSize));
for (int block_num = 0; block_num < TEST_NUM_SECTORS; block_num += TEST_NUM_SECTORS_PER_ITER) {
//Initialize CBW IRP, then send it on the BULK OUT pipe
mock_msc_scsi_init_cbw((mock_msc_bulk_cbw_t *)irp_cbw->data_buffer, true, block_num, TEST_NUM_SECTORS_PER_ITER, 0xAAAAAAAA);
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(bulk_out_pipe, irp_cbw));
test_hcd_expect_pipe_event(bulk_out_pipe, HCD_PIPE_EVENT_IRP_DONE);
TEST_ASSERT_EQUAL(irp_cbw, hcd_irp_dequeue(bulk_out_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp_cbw->status);
for (int block_num = 0; block_num < TEST_NUM_SECTORS_TOTAL; block_num += TEST_NUM_SECTORS_PER_XFER) {
//Initialize CBW URB, then send it on the BULK OUT pipe
mock_msc_scsi_init_cbw((mock_msc_bulk_cbw_t *)urb_cbw->transfer.data_buffer, true, block_num, TEST_NUM_SECTORS_PER_XFER, 0xAAAAAAAA);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(bulk_out_pipe, urb_cbw));
test_hcd_expect_pipe_event(bulk_out_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb_cbw, hcd_urb_dequeue(bulk_out_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb_cbw->transfer.status);
//Read data through BULK IN pipe
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(bulk_in_pipe, irp_data));
test_hcd_expect_pipe_event(bulk_in_pipe, HCD_PIPE_EVENT_IRP_DONE);
TEST_ASSERT_EQUAL(irp_data, hcd_irp_dequeue(bulk_in_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp_data->status);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(bulk_in_pipe, urb_data));
test_hcd_expect_pipe_event(bulk_in_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb_data, hcd_urb_dequeue(bulk_in_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb_data->transfer.status);
//Read the CSW through BULK IN pipe
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(bulk_in_pipe, irp_csw));
test_hcd_expect_pipe_event(bulk_in_pipe, HCD_PIPE_EVENT_IRP_DONE);
TEST_ASSERT_EQUAL(irp_csw, hcd_irp_dequeue(bulk_in_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp_data->status);
TEST_ASSERT_EQUAL(sizeof(mock_msc_bulk_csw_t), irp_csw->actual_num_bytes);
TEST_ASSERT_EQUAL(true, mock_msc_scsi_check_csw((mock_msc_bulk_csw_t *)irp_csw->data_buffer, 0xAAAAAAAA));
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(bulk_in_pipe, urb_csw));
test_hcd_expect_pipe_event(bulk_in_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb_csw, hcd_urb_dequeue(bulk_in_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb_data->transfer.status);
TEST_ASSERT_EQUAL(sizeof(mock_msc_bulk_csw_t), urb_csw->transfer.actual_num_bytes);
TEST_ASSERT_EQUAL(true, mock_msc_scsi_check_csw((mock_msc_bulk_csw_t *)urb_csw->transfer.data_buffer, 0xAAAAAAAA));
//Print the read data
printf("Block %d to %d:\n", block_num, block_num + TEST_NUM_SECTORS_PER_ITER);
for (int i = 0; i < irp_data->actual_num_bytes; i++) {
printf("0x%02x,", ((char *)irp_data->data_buffer)[i]);
printf("Block %d to %d:\n", block_num, block_num + TEST_NUM_SECTORS_PER_XFER);
for (int i = 0; i < urb_data->transfer.actual_num_bytes; i++) {
printf("0x%02x,", ((char *)urb_data->transfer.data_buffer)[i]);
}
printf("\n\n");
}
test_hcd_free_irp(irp_cbw);
test_hcd_free_irp(irp_data);
test_hcd_free_irp(irp_csw);
test_hcd_free_urb(urb_cbw);
test_hcd_free_urb(urb_data);
test_hcd_free_urb(urb_csw);
test_hcd_pipe_free(bulk_out_pipe);
test_hcd_pipe_free(bulk_in_pipe);
test_hcd_pipe_free(default_pipe);

View File

@ -31,9 +31,11 @@
#include "esp_err.h"
#include "esp_attr.h"
#include "esp_rom_gpio.h"
#include "hal/usbh_ll.h"
#include "usb.h"
#include "soc/usb_wrap_struct.h"
#include "hcd.h"
#include "usb_private.h"
#include "usb.h"
#include "test_hcd_common.h"
#define PORT_NUM 1
#define EVENT_QUEUE_LEN 5
@ -281,70 +283,66 @@ void test_hcd_pipe_free(hcd_pipe_handle_t pipe_hdl)
vQueueDelete(pipe_evt_queue);
}
usb_irp_t *test_hcd_alloc_irp(int num_iso_packets, size_t data_buffer_size)
urb_t *test_hcd_alloc_urb(int num_isoc_packets, size_t data_buffer_size)
{
//Allocate list of IRPs
usb_irp_t *irp = heap_caps_calloc(1, sizeof(usb_irp_t) + (num_iso_packets * sizeof(usb_iso_packet_desc_t)), MALLOC_CAP_DEFAULT);
TEST_ASSERT_NOT_EQUAL(NULL, irp);
//Allocate data buffer for each IRP and assign them
//Allocate a URB and data buffer
urb_t *urb = heap_caps_calloc(1, sizeof(urb_t) + (num_isoc_packets * sizeof(usb_isoc_packet_desc_t)), MALLOC_CAP_DEFAULT);
uint8_t *data_buffer = heap_caps_malloc(data_buffer_size, MALLOC_CAP_DMA);
TEST_ASSERT_NOT_EQUAL(NULL, urb);
TEST_ASSERT_NOT_EQUAL(NULL, data_buffer);
irp->data_buffer = data_buffer;
irp->num_iso_packets = num_iso_packets;
return irp;
//Initialize URB and underlying transfer structure. Need to cast to dummy due to const fields
usb_transfer_dummy_t *transfer_dummy = (usb_transfer_dummy_t *)&urb->transfer;
transfer_dummy->data_buffer = data_buffer;
transfer_dummy->num_isoc_packets = num_isoc_packets;
return urb;
}
void test_hcd_free_irp(usb_irp_t *irp)
void test_hcd_free_urb(urb_t *urb)
{
//Free data buffers of each IRP
heap_caps_free(irp->data_buffer);
//Free the IRP list
heap_caps_free(irp);
//Free data buffer of the transfer
heap_caps_free(urb->transfer.data_buffer);
//Free the URB
heap_caps_free(urb);
}
uint8_t test_hcd_enum_devc(hcd_pipe_handle_t default_pipe)
uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe)
{
//We need to create an IRP for the enumeration control transfers
usb_irp_t *irp = heap_caps_calloc(1, sizeof(usb_irp_t), MALLOC_CAP_DEFAULT);
TEST_ASSERT_NOT_EQUAL(NULL, irp);
//We use a single data buffer for all control transfers during enumerations. 256 bytes should be large enough for most descriptors
irp->data_buffer = heap_caps_malloc(sizeof(usb_ctrl_req_t) + 256, MALLOC_CAP_DMA);
TEST_ASSERT_NOT_EQUAL(NULL, irp->data_buffer);
usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)irp->data_buffer;
//We need to create a URB for the enumeration control transfers
urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_ctrl_req_t) + 256);
usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)urb->transfer.data_buffer;
//Get the device descriptor (note that device might only return 8 bytes)
USB_CTRL_REQ_INIT_GET_DEVC_DESC(ctrl_req);
irp->num_bytes = sizeof(usb_desc_devc_t);
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
USB_CTRL_REQ_INIT_GET_DEVICE_DESC(ctrl_req);
urb->transfer.num_bytes = sizeof(usb_desc_device_t);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//Update the MPS of the default pipe
usb_desc_devc_t *devc_desc = (usb_desc_devc_t *)(irp->data_buffer + sizeof(usb_ctrl_req_t));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_mps(default_pipe, devc_desc->bMaxPacketSize0));
usb_desc_device_t *device_desc = (usb_desc_device_t *)(urb->transfer.data_buffer + sizeof(usb_ctrl_req_t));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_mps(default_pipe, device_desc->bMaxPacketSize0));
//Send a set address request
USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req, ENUM_ADDR); //We only support one device for now so use address 1
irp->num_bytes = 0;
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
urb->transfer.num_bytes = 0;
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//Update address of default pipe
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_dev_addr(default_pipe, ENUM_ADDR));
//Send a set configuration request
USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req, ENUM_CONFIG);
irp->num_bytes = 0;
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
urb->transfer.num_bytes = 0;
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//Free IRP
heap_caps_free(irp->data_buffer);
heap_caps_free(irp);
//Free URB
test_hcd_free_urb(urb);
return ENUM_ADDR;
}

View File

@ -14,10 +14,11 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "usb.h"
#include "hcd.h"
#include "usb_private.h"
#include "usb.h"
#define IRP_CONTEXT_VAL ((void *)0xDEADBEEF)
#define URB_CONTEXT_VAL ((void *)0xDEADBEEF)
// ------------------------------------------------- HCD Event Test ----------------------------------------------------
@ -118,20 +119,20 @@ hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_desc
void test_hcd_pipe_free(hcd_pipe_handle_t pipe_hdl);
/**
* @brief Allocate an IRP
* @brief Allocate a URB
*
* @param num_iso_packets Number of isochronous packets
* @param data_buffer_size Size of the data buffer of the IRP
* @return usb_irp_t* IRP
* @param num_isoc_packets Number of isochronous packets
* @param data_buffer_size Size of the data buffer of the URB
* @return urb_t* URB
*/
usb_irp_t *test_hcd_alloc_irp(int num_iso_packets, size_t data_buffer_size);
urb_t *test_hcd_alloc_urb(int num_isoc_packets, size_t data_buffer_size);
/**
* @brief Free an IRP
* @brief Free a URB
*
* @param irp IRP
* @param urb URB
*/
void test_hcd_free_irp(usb_irp_t *irp);
void test_hcd_free_urb(urb_t *urb);
// --------------------------------------------------- Enumeration -----------------------------------------------------
@ -148,4 +149,4 @@ void test_hcd_free_irp(usb_irp_t *irp);
* @param default_pipe The connected device's default pipe
* @return uint8_t The address of the device after enumeration
*/
uint8_t test_hcd_enum_devc(hcd_pipe_handle_t default_pipe);
uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe);

View File

@ -20,88 +20,94 @@
#include "test_hcd_common.h"
#define TEST_DEV_ADDR 0
#define NUM_IRPS 3
#define NUM_URBS 3
#define TRANSFER_MAX_BYTES 256
#define IRP_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
#define URB_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
/*
Test HCD control pipe IRPs (normal completion and early abort)
Test HCD control pipe URBs (normal completion and early abort)
Purpose:
- Test that a control pipe can be created
- IRPs can be created and enqueued to the control pipe
- Control pipe returns HCD_PIPE_EVENT_IRP_DONE
- Test that IRPs can be aborted when enqueued
- URBs can be created and enqueued to the control pipe
- Control pipe returns HCD_PIPE_EVENT_URB_DONE
- Test that URBs can be aborted when enqueued
Procedure:
- Setup HCD and wait for connection
- Setup default pipe and allocate IRPs
- Enqueue IRPs
- Expect HCD_PIPE_EVENT_IRP_DONE
- Requeue IRPs, but abort them immediately
- Expect IRP to be USB_TRANSFER_STATUS_CANCELED or USB_TRANSFER_STATUS_COMPLETED
- Setup default pipe and allocate URBs
- Enqueue URBs
- Expect HCD_PIPE_EVENT_URB_DONE
- Requeue URBs, but abort them immediately
- Expect URB to be USB_TRANSFER_STATUS_CANCELED or USB_TRANSFER_STATUS_COMPLETED
- Teardown
*/
TEST_CASE("Test HCD control pipe IRPs", "[hcd][ignore]")
TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]")
{
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
//Allocate some IRPs and initialize their data buffers with control transfers
//Allocate some URBs and initialize their data buffers with control transfers
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
usb_irp_t *irp_list[NUM_IRPS];
for (int i = 0; i < NUM_IRPS; i++) {
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
urb_t *urb_list[NUM_URBS];
for (int i = 0; i < NUM_URBS; i++) {
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
//Initialize with a "Get Config Descriptor request"
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
irp_list[i]->context = IRP_CONTEXT_VAL;
urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
urb_list[i]->transfer.context = URB_CONTEXT_VAL;
}
//Enqueue IRPs but immediately suspend the port
printf("Enqueuing IRPs\n");
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Enqueue URBs but immediately suspend the port
printf("Enqueuing URBs\n");
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
//Wait for each done event of each IRP
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
//Wait for each done event of each URB
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
//Dequeue IRPs
for (int i = 0; i < NUM_IRPS; i++) {
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
TEST_ASSERT_EQUAL(irp_list[i], irp);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context);
//Dequeue URBs
for (int i = 0; i < NUM_URBS; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
}
//Enqueue IRPs again but abort them short after
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Print config desc
for (int i = 0; i < urb_list[0]->transfer.actual_num_bytes; i++) {
printf("%d\t0x%x\n", i, urb_list[0]->transfer.data_buffer[sizeof(usb_ctrl_req_t) + i]);
}
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_abort(irp_list[i]));
//Enqueue URBs again but abort them short after
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_abort(urb_list[i]));
}
vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for any inflight transfers to complete
//Wait for the IRPs to complete and dequeue them, then check results
//Dequeue IRPs
for (int i = 0; i < NUM_IRPS; i++) {
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
//No need to check for IRP pointer address as they may be out of order
TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_CANCELED);
if (irp->status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
//Wait for the URBs to complete and dequeue them, then check results
//Dequeue URBs
for (int i = 0; i < NUM_URBS; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe);
//No need to check for URB pointer address as they may be out of order
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
} else {
TEST_ASSERT_EQUAL(0, irp->actual_num_bytes);
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
}
TEST_ASSERT_EQUAL(irp->context, IRP_CONTEXT_VAL);
TEST_ASSERT_EQUAL(urb->transfer.context, URB_CONTEXT_VAL);
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(default_pipe);
//Cleanup
@ -114,18 +120,18 @@ Test HCD control pipe STALL condition, abort, and clear
Purpose:
- Test that a control pipe can react to a STALL (i.e., a HCD_PIPE_EVENT_HALTED event)
- The HCD_PIPE_CMD_ABORT can retire all IRPs
- The HCD_PIPE_CMD_ABORT can retire all URBs
- Pipe clear command can return the pipe to being active
Procedure:
- Setup HCD and wait for connection
- Setup default pipe and allocate IRPs
- Corrupt the first IRP so that it will trigger a STALL, then enqueue all the IRPs
- Setup default pipe and allocate URBs
- Corrupt the first URB so that it will trigger a STALL, then enqueue all the URBs
- Check that a HCD_PIPE_EVENT_ERROR_STALL event is triggered
- Check that all IRPs can be retired using HCD_PIPE_CMD_ABORT
- Check that all URBs can be retired using HCD_PIPE_CMD_ABORT
- Check that the STALL can be cleared by using HCD_PIPE_CMD_CLEAR
- Fix the corrupt first IRP and retry the IRPs
- Dequeue IRPs
- Fix the corrupt first URB and retry the URBs
- Dequeue URBs
- Teardown
*/
TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
@ -134,39 +140,45 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
//Allocate some IRPs and initialize their data buffers with control transfers
//Allocate some URBs and initialize their data buffers with control transfers
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
usb_irp_t *irp_list[NUM_IRPS];
for (int i = 0; i < NUM_IRPS; i++) {
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
urb_t *urb_list[NUM_URBS];
for (int i = 0; i < NUM_URBS; i++) {
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
//Initialize with a "Get Config Descriptor request"
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
irp_list[i]->context = IRP_CONTEXT_VAL;
urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
urb_list[i]->transfer.context = URB_CONTEXT_VAL;
}
//Corrupt the first IRP so that it triggers a STALL
((usb_ctrl_req_t *)irp_list[0]->data_buffer)->bRequest = 0xAA;
//Corrupt the first URB so that it triggers a STALL
((usb_ctrl_req_t *)urb_list[0]->transfer.data_buffer)->bRequest = 0xAA;
//Enqueue IRPs. A STALL should occur
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Enqueue URBs. A STALL should occur
int num_enqueued = 0;
for (int i = 0; i < NUM_URBS; i++) {
if (hcd_urb_enqueue(default_pipe, urb_list[i]) != ESP_OK) {
//STALL may occur before we are done enqueing
break;
}
num_enqueued++;
}
TEST_ASSERT_GREATER_THAN(0, num_enqueued);
printf("Expecting STALL\n");
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_ERROR_STALL);
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
//Call the pipe abort command to retire all IRPs then dequeue them all
//Call the pipe abort command to retire all URBs then dequeue them all
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_ABORT));
for (int i = 0; i < NUM_IRPS; i++) {
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
TEST_ASSERT_EQUAL(irp_list[i], irp);
TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_STALL || irp->status == USB_TRANSFER_STATUS_CANCELED);
if (irp->status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
for (int i = 0; i < num_enqueued; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_STALL || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
} else {
TEST_ASSERT_EQUAL(0, irp->actual_num_bytes);
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
}
TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
}
//Call the clear command to un-stall the pipe
@ -174,26 +186,26 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
printf("Retrying\n");
//Correct first IRP then requeue
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[0]->data_buffer, 0, TRANSFER_MAX_BYTES);
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Correct first URB then requeue
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[0]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
//Wait for each IRP to be done, deequeue, and check results
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
//expect_pipe_event(pipe_evt_queue, default_pipe, HCD_PIPE_EVENT_IRP_DONE);
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
TEST_ASSERT_EQUAL(irp_list[i], irp);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context);
//Wait for each URB to be done, deequeue, and check results
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
//expect_pipe_event(pipe_evt_queue, default_pipe, HCD_PIPE_EVENT_URB_DONE);
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(default_pipe);
//Cleanup
@ -205,18 +217,18 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
Test control pipe run-time halt and clear
Purpose:
- Test that a control pipe can be halted with HCD_PIPE_CMD_HALT whilst there are ongoing IRPs
- Test that a control pipe can be halted with HCD_PIPE_CMD_HALT whilst there are ongoing URBs
- Test that a control pipe can be un-halted with a HCD_PIPE_CMD_CLEAR
- Test that enqueued IRPs are resumed when pipe is un-halted
- Test that enqueued URBs are resumed when pipe is un-halted
Procedure:
- Setup HCD and wait for connection
- Setup default pipe and allocate IRPs
- Enqqueue IRPs but execute a HCD_PIPE_CMD_HALT command immediately after. Halt command should let on
the current going IRP finish before actually halting the pipe.
- Un-halt the pipe a HCD_PIPE_CMD_HALT command. Enqueued IRPs will be resumed
- Check that all IRPs have completed successfully
- Dequeue IRPs and teardown
- Setup default pipe and allocate URBs
- Enqqueue URBs but execute a HCD_PIPE_CMD_HALT command immediately after. Halt command should let on
the current going URB finish before actually halting the pipe.
- Un-halt the pipe a HCD_PIPE_CMD_HALT command. Enqueued URBs will be resumed
- Check that all URBs have completed successfully
- Dequeue URBs and teardown
*/
TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]")
{
@ -224,21 +236,21 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]")
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
//Allocate some IRPs and initialize their data buffers with control transfers
//Allocate some URBs and initialize their data buffers with control transfers
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
usb_irp_t *irp_list[NUM_IRPS];
for (int i = 0; i < NUM_IRPS; i++) {
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
urb_t *urb_list[NUM_URBS];
for (int i = 0; i < NUM_URBS; i++) {
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
//Initialize with a "Get Config Descriptor request"
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
irp_list[i]->context = IRP_CONTEXT_VAL;
urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
urb_list[i]->transfer.context = URB_CONTEXT_VAL;
}
//Enqueue IRPs but immediately halt the pipe
printf("Enqueuing IRPs\n");
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Enqueue URBs but immediately halt the pipe
printf("Enqueuing URBs\n");
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
@ -250,19 +262,19 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]")
printf("Pipe cleared\n");
vTaskDelay(pdMS_TO_TICKS(100)); //Give some time pending for transfers to restart and complete
//Wait for each IRP to be done, dequeue, and check results
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
TEST_ASSERT_EQUAL(irp_list[i], irp);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context);
//Wait for each URB to be done, dequeue, and check results
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(default_pipe);
//Cleanup

View File

@ -84,32 +84,31 @@ static void mock_hid_process_report(mock_hid_mouse_report_t *report, int iter)
// --------------------------------------------------- Test Cases ------------------------------------------------------
/*
Test HCD interrupt pipe IRPs
Test HCD interrupt pipe URBs
Purpose:
- Test that an interrupt pipe can be created
- IRPs can be created and enqueued to the interrupt pipe
- Interrupt pipe returns HCD_PIPE_EVENT_IRP_DONE
- Test that IRPs can be aborted when enqueued
- URBs can be created and enqueued to the interrupt pipe
- Interrupt pipe returns HCD_PIPE_EVENT_URB_DONE
- Test that URBs can be aborted when enqueued
Procedure:
- Setup HCD and wait for connection
- Allocate default pipe and enumerate the device
- Setup interrupt pipe and allocate IRPs
- Enqueue IRPs, expect HCD_PIPE_EVENT_IRP_DONE, and requeue
- Setup interrupt pipe and allocate URBs
- Enqueue URBs, expect HCD_PIPE_EVENT_URB_DONE, and requeue
- Stop after fixed number of iterations
- Deallocate IRPs
- Deallocate URBs
- Teardown
Note: Some mice will NAK until it is moved, so try moving the mouse around if this test case gets stuck.
*/
#define TEST_HID_DEV_SPEED USB_SPEED_LOW
#define NUM_IRPS 3
#define IRP_DATA_BUFF_SIZE 4 //MPS is 4
#define MOCK_HID_NUM_REPORT_PER_IRP 2
#define NUM_IRP_ITERS (NUM_IRPS * 100)
#define NUM_URBS 3
#define URB_DATA_BUFF_SIZE 4 //MPS is 4
#define NUM_URB_ITERS (NUM_URBS * 100)
TEST_CASE("Test HCD interrupt pipe IRPs", "[hcd][ignore]")
TEST_CASE("Test HCD interrupt pipe URBs", "[hcd][ignore]")
{
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
@ -117,39 +116,39 @@ TEST_CASE("Test HCD interrupt pipe IRPs", "[hcd][ignore]")
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); //Create a default pipe (using a NULL EP descriptor)
uint8_t dev_addr = test_hcd_enum_devc(default_pipe);
uint8_t dev_addr = test_hcd_enum_device(default_pipe);
//Allocate interrupt pipe and IRPS
//Allocate interrupt pipe and URBS
hcd_pipe_handle_t intr_pipe = test_hcd_pipe_alloc(port_hdl, &in_ep_desc, dev_addr, port_speed);
usb_irp_t *irp_list[NUM_IRPS];
for (int i = 0; i < NUM_IRPS; i++) {
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
irp_list[i]->num_bytes = IRP_DATA_BUFF_SIZE;
irp_list[i]->context = IRP_CONTEXT_VAL;
urb_t *urb_list[NUM_URBS];
for (int i = 0; i < NUM_URBS; i++) {
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
urb_list[i]->transfer.num_bytes = URB_DATA_BUFF_SIZE;
urb_list[i]->transfer.context = URB_CONTEXT_VAL;
}
//Enqueue IRPs
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(intr_pipe, irp_list[i]));
//Enqueue URBs
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(intr_pipe, urb_list[i]));
}
int iter_count = NUM_IRP_ITERS;
for (iter_count = NUM_IRP_ITERS; iter_count > 0; iter_count--) {
//Wait for an IRP to be done
test_hcd_expect_pipe_event(intr_pipe, HCD_PIPE_EVENT_IRP_DONE);
//Dequeue the IRP and check results
usb_irp_t *irp = hcd_irp_dequeue(intr_pipe);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status);
TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context);
mock_hid_process_report((mock_hid_mouse_report_t *)irp->data_buffer, iter_count);
//Requeue IRP
if (iter_count > NUM_IRPS) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(intr_pipe, irp));
int iter_count = NUM_URB_ITERS;
for (iter_count = NUM_URB_ITERS; iter_count > 0; iter_count--) {
//Wait for an URB to be done
test_hcd_expect_pipe_event(intr_pipe, HCD_PIPE_EVENT_URB_DONE);
//Dequeue the URB and check results
urb_t *urb = hcd_urb_dequeue(intr_pipe);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
mock_hid_process_report((mock_hid_mouse_report_t *)urb->transfer.data_buffer, iter_count);
//Requeue URB
if (iter_count > NUM_URBS) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(intr_pipe, urb));
}
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(intr_pipe);
test_hcd_pipe_free(default_pipe);

View File

@ -24,10 +24,10 @@
#define MOCK_ISOC_EP_NUM 2
#define MOCK_ISOC_EP_MPS 512
#define NUM_IRPS 3
#define NUM_PACKETS_PER_IRP 3
#define NUM_URBS 3
#define NUM_PACKETS_PER_URB 3
#define ISOC_PACKET_SIZE MOCK_ISOC_EP_MPS
#define IRP_DATA_BUFF_SIZE (NUM_PACKETS_PER_IRP * ISOC_PACKET_SIZE)
#define URB_DATA_BUFF_SIZE (NUM_PACKETS_PER_URB * ISOC_PACKET_SIZE)
static const usb_desc_ep_t isoc_out_ep_desc = {
.bLength = sizeof(usb_desc_ep_t),
@ -39,26 +39,26 @@ static const usb_desc_ep_t isoc_out_ep_desc = {
};
/*
Test HCD ISOC pipe IRPs
Test HCD ISOC pipe URBs
Purpose:
- Test that an isochronous pipe can be created
- IRPs can be created and enqueued to the isoc pipe pipe
- isoc pipe returns HCD_PIPE_EVENT_IRP_DONE for completed IRPs
- URBs can be created and enqueued to the isoc pipe pipe
- isoc pipe returns HCD_PIPE_EVENT_URB_DONE for completed URBs
- Test utilizes ISOC OUT transfers and do not require ACKs. So the isoc pipe will target a non existing endpoint
Procedure:
- Setup HCD and wait for connection
- Allocate default pipe and enumerate the device
- Allocate an isochronous pipe and multiple IRPs. Each IRP should contain multiple packets to test HCD's ability to
schedule an IRP across multiple intervals.
- Enqueue those IRPs
- Expect HCD_PIPE_EVENT_IRP_DONE for each IRP. Verify that data is correct using logic analyzer
- Deallocate IRPs
- Allocate an isochronous pipe and multiple URBs. Each URB should contain multiple packets to test HCD's ability to
schedule an URB across multiple intervals.
- Enqueue those URBs
- Expect HCD_PIPE_EVENT_URB_DONE for each URB. Verify that data is correct using logic analyzer
- Deallocate URBs
- Teardown
*/
TEST_CASE("Test HCD isochronous pipe IRPs", "[hcd][ignore]")
TEST_CASE("Test HCD isochronous pipe URBs", "[hcd][ignore]")
{
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
@ -68,43 +68,43 @@ TEST_CASE("Test HCD isochronous pipe IRPs", "[hcd][ignore]")
//Enumerate and reset device
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); //Create a default pipe (using a NULL EP descriptor)
uint8_t dev_addr = test_hcd_enum_devc(default_pipe);
uint8_t dev_addr = test_hcd_enum_device(default_pipe);
//Create ISOC OUT pipe to non-existent device
hcd_pipe_handle_t isoc_out_pipe = test_hcd_pipe_alloc(port_hdl, &isoc_out_ep_desc, dev_addr + 1, port_speed);
//Create IRPs
usb_irp_t *irp_list[NUM_IRPS];
//Initialize IRPs
for (int irp_idx = 0; irp_idx < NUM_IRPS; irp_idx++) {
irp_list[irp_idx] = test_hcd_alloc_irp(NUM_PACKETS_PER_IRP, IRP_DATA_BUFF_SIZE);
irp_list[irp_idx]->num_bytes = 0; //num_bytes is not used for ISOC
irp_list[irp_idx]->context = IRP_CONTEXT_VAL;
for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_IRP; pkt_idx++) {
irp_list[irp_idx]->iso_packet_desc[pkt_idx].length = ISOC_PACKET_SIZE;
//Create URBs
urb_t *urb_list[NUM_URBS];
//Initialize URBs
for (int urb_idx = 0; urb_idx < NUM_URBS; urb_idx++) {
urb_list[urb_idx] = test_hcd_alloc_urb(NUM_PACKETS_PER_URB, URB_DATA_BUFF_SIZE);
urb_list[urb_idx]->transfer.num_bytes = 0; //num_bytes is not used for ISOC
urb_list[urb_idx]->transfer.context = URB_CONTEXT_VAL;
for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_URB; pkt_idx++) {
urb_list[urb_idx]->transfer.isoc_packet_desc[pkt_idx].num_bytes = ISOC_PACKET_SIZE;
//Each packet will consist of the same byte, but each subsequent packet's byte will increment (i.e., packet 0 transmits all 0x0, packet 1 transmits all 0x1)
memset(&irp_list[irp_idx]->data_buffer[pkt_idx * ISOC_PACKET_SIZE], (irp_idx * NUM_IRPS) + pkt_idx, ISOC_PACKET_SIZE);
memset(&urb_list[urb_idx]->transfer.data_buffer[pkt_idx * ISOC_PACKET_SIZE], (urb_idx * NUM_URBS) + pkt_idx, ISOC_PACKET_SIZE);
}
}
//Enqueue IRPs
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(isoc_out_pipe, irp_list[i]));
//Enqueue URBs
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(isoc_out_pipe, urb_list[i]));
}
//Wait for each done event from each IRP
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_expect_pipe_event(isoc_out_pipe, HCD_PIPE_EVENT_IRP_DONE);
//Wait for each done event from each URB
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_expect_pipe_event(isoc_out_pipe, HCD_PIPE_EVENT_URB_DONE);
}
//Dequeue IRPs
for (int irp_idx = 0; irp_idx < NUM_IRPS; irp_idx++) {
usb_irp_t *irp = hcd_irp_dequeue(isoc_out_pipe);
TEST_ASSERT_EQUAL(irp_list[irp_idx], irp);
TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context);
for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_IRP; pkt_idx++) {
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->iso_packet_desc[pkt_idx].status);
//Dequeue URBs
for (int urb_idx = 0; urb_idx < NUM_URBS; urb_idx++) {
urb_t *urb = hcd_urb_dequeue(isoc_out_pipe);
TEST_ASSERT_EQUAL(urb_list[urb_idx], urb);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_URB; pkt_idx++) {
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.isoc_packet_desc[pkt_idx].status);
}
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(isoc_out_pipe);
test_hcd_pipe_free(default_pipe);

View File

@ -20,16 +20,16 @@
#include "test_hcd_common.h"
#define TEST_DEV_ADDR 0
#define NUM_IRPS 3
#define NUM_URBS 3
#define TRANSFER_MAX_BYTES 256
#define IRP_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
#define URB_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
/*
Test a port sudden disconnect and port recovery
Purpose: Test that when sudden disconnection happens on an HCD port, the port will
- Generate the HCD_PORT_EVENT_SUDDEN_DISCONN and be put into the HCD_PORT_STATE_RECOVERY state
- Ongoing IRPs and pipes are handled correctly
- Ongoing URBs and pipes are handled correctly
Procedure:
- Setup the HCD and a port
@ -37,7 +37,7 @@ Procedure:
- Create a default pipe
- Start transfers but immediately trigger a disconnect
- Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle the event.
- Check that default pipe received a HCD_PIPE_EVENT_INVALID event. Pipe state should be invalid. Dequeue IRPs
- Check that default pipe received a HCD_PIPE_EVENT_INVALID event. Pipe state should be invalid. Dequeue URBs
- Free default pipe
- Recover the port
- Trigger connection and disconnection again (to make sure the port works post recovery)
@ -50,21 +50,21 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
//Allocate some IRPs and initialize their data buffers with control transfers
//Allocate some URBs and initialize their data buffers with control transfers
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
usb_irp_t *irp_list[NUM_IRPS];
for (int i = 0; i < NUM_IRPS; i++) {
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
urb_t *urb_list[NUM_URBS];
for (int i = 0; i < NUM_URBS; i++) {
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
//Initialize with a "Get Config Descriptor request"
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
irp_list[i]->context = (void *)0xDEADBEEF;
urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
urb_list[i]->transfer.context = (void *)0xDEADBEEF;
}
//Enqueue IRPs but immediately trigger a disconnect
printf("Enqueuing IRPs\n");
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Enqueue URBs but immediately trigger a disconnect
printf("Enqueuing URBs\n");
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
test_hcd_force_conn_state(false, 0);
//Disconnect event should have occurred. Handle the event
@ -73,29 +73,29 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
printf("Sudden disconnect\n");
//Pipe should have received (zero or more HCD_PIPE_EVENT_IRP_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
//Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe);
for (int i = 0; i < num_pipe_events - 1; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID);
TEST_ASSERT_EQUAL(hcd_pipe_get_state(default_pipe), HCD_PIPE_STATE_INVALID);
//Dequeue IRPs
for (int i = 0; i < NUM_IRPS; i++) {
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
TEST_ASSERT_EQUAL(irp_list[i], irp);
TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_NO_DEVICE);
if (irp->status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
//Dequeue URBs
for (int i = 0; i < NUM_URBS; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
} else {
TEST_ASSERT_EQUAL(0, irp->actual_num_bytes);
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
}
TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context);
TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(default_pipe);
@ -123,8 +123,8 @@ Procedure:
- Create a default pipe
- Start transfers but suspend the port immediately
- Resume the port
- Check that all the IRPs have completed successfully
- Cleanup IRPs and default pipe
- Check that all the URBs have completed successfully
- Cleanup URBs and default pipe
- Trigger disconnection and teardown
*/
TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
@ -133,21 +133,21 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
//Allocate some IRPs and initialize their data buffers with control transfers
//Allocate some URBs and initialize their data buffers with control transfers
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
usb_irp_t *irp_list[NUM_IRPS];
for (int i = 0; i < NUM_IRPS; i++) {
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
urb_t *urb_list[NUM_URBS];
for (int i = 0; i < NUM_URBS; i++) {
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
//Initialize with a "Get Config Descriptor request"
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
irp_list[i]->context = (void *)0xDEADBEEF;
urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
urb_list[i]->transfer.context = (void *)0xDEADBEEF;
}
//Enqueue IRPs but immediately suspend the port
printf("Enqueuing IRPs\n");
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Enqueue URBs but immediately suspend the port
printf("Enqueuing URBs\n");
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl));
@ -158,19 +158,19 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl));
printf("Resumed\n");
vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for resumed IRPs to complete
//Dequeue IRPs
for (int i = 0; i < NUM_IRPS; i++) {
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
TEST_ASSERT_EQUAL(irp_list[i], irp);
TEST_ASSERT_EQUAL(irp->status, USB_TRANSFER_STATUS_COMPLETED);
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context);
vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for resumed URBs to complete
//Dequeue URBs
for (int i = 0; i < NUM_URBS; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT_EQUAL(urb->transfer.status, USB_TRANSFER_STATUS_COMPLETED);
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(default_pipe);
//Cleanup
@ -187,7 +187,7 @@ Purpose:
- After disabling the port, all pipes should become invalid.
Procedure:
- Setup HCD, a default pipe, and multiple IRPs
- Setup HCD, a default pipe, and multiple URBs
- Start transfers but immediately disable the port
- Check pipe received invalid event
- Check that transfer are either done or not executed
@ -199,49 +199,49 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]")
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
//Allocate some IRPs and initialize their data buffers with control transfers
//Allocate some URBs and initialize their data buffers with control transfers
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
usb_irp_t *irp_list[NUM_IRPS];
for (int i = 0; i < NUM_IRPS; i++) {
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
urb_t *urb_list[NUM_URBS];
for (int i = 0; i < NUM_URBS; i++) {
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
//Initialize with a "Get Config Descriptor request"
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
irp_list[i]->context = (void *)0xDEADBEEF;
urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
urb_list[i]->transfer.context = (void *)0xDEADBEEF;
}
//Enqueue IRPs but immediately disable the port
printf("Enqueuing IRPs\n");
for (int i = 0; i < NUM_IRPS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i]));
//Enqueue URBs but immediately disable the port
printf("Enqueuing URBs\n");
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl));
printf("Disabled\n");
//Pipe should have received (zero or more HCD_PIPE_EVENT_IRP_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
//Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe);
for (int i = 0; i < num_pipe_events - 1; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE);
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID);
//Dequeue IRPs
for (int i = 0; i < NUM_IRPS; i++) {
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
TEST_ASSERT_EQUAL(irp_list[i], irp);
TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_NO_DEVICE);
if (irp->status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
//Dequeue URBs
for (int i = 0; i < NUM_URBS; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
} else {
TEST_ASSERT_EQUAL(0, irp->actual_num_bytes);
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
}
TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context);
TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
}
//Free IRP list and pipe
for (int i = 0; i < NUM_IRPS; i++) {
test_hcd_free_irp(irp_list[i]);
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(default_pipe);
//Cleanup