diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index 25ce3dc43d..81029b3de3 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -2045,6 +2045,16 @@ hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl) return ret; } +unsigned int hcd_pipe_get_num_urbs(hcd_pipe_handle_t pipe_hdl) +{ + unsigned int ret; + pipe_t *pipe = (pipe_t *)pipe_hdl; + HCD_ENTER_CRITICAL(); + ret = pipe->num_urb_pending + pipe->num_urb_done; + 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; diff --git a/components/usb/hub.c b/components/usb/hub.c index 148c4820f7..5574921283 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -187,8 +187,8 @@ typedef struct { //Constant members do no change after installation thus do not require a critical section struct { hcd_port_handle_t root_port_hdl; - usb_notif_cb_t notif_cb; - void *notif_cb_arg; + usb_proc_req_cb_t proc_req_cb; + void *proc_req_cb_arg; } constant; } hub_driver_t; @@ -223,7 +223,7 @@ const char *HUB_DRIVER_TAG = "HUB"; * * - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process() * - Under the current HCD implementation, this callback should only be ever be called in an ISR - * - This callback needs to call the notification to ensure hub_process() gets a chance to run + * - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run * * @param port_hdl HCD port handle * @param port_event HCD port event @@ -237,7 +237,7 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port * @brief HCD pipe callback for the default pipe of the device under enumeration * * - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process() - * - This callback needs to call the notification to ensure hub_process() gets a chance to run + * - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run * * @param pipe_hdl HCD pipe handle * @param pipe_event Pipe event @@ -251,7 +251,7 @@ static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t * @brief USBH Hub driver request callback * * - This callback is called from the context of the USBH, so so any event handling should be deferred to hub_process() - * - This callback needs to call the notification to ensure hub_process() gets a chance to run + * - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run * * @param port_hdl HCD port handle * @param hub_req Hub driver request @@ -756,7 +756,7 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ROOT_EVENT; HUB_DRIVER_EXIT_CRITICAL_SAFE(); assert(in_isr); //Currently, this callback should only ever be called from an ISR context - return p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg);; + return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);; } static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) @@ -765,7 +765,7 @@ static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t HUB_DRIVER_ENTER_CRITICAL_SAFE(); p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; HUB_DRIVER_EXIT_CRITICAL_SAFE(); - return p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg); + return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } static void usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg) @@ -788,7 +788,7 @@ static void usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub } HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, false, p_hub_driver_obj->constant.notif_cb_arg); + p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); } // ---------------------- Handlers ------------------------- @@ -953,8 +953,8 @@ esp_err_t hub_install(hub_config_t *hub_config) hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE; hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb; hub_driver_obj->constant.root_port_hdl = port_hdl; - hub_driver_obj->constant.notif_cb = hub_config->notif_cb; - hub_driver_obj->constant.notif_cb_arg = hub_config->notif_cb_arg; + hub_driver_obj->constant.proc_req_cb = hub_config->proc_req_cb; + hub_driver_obj->constant.proc_req_cb_arg = hub_config->proc_req_cb_arg; HUB_DRIVER_ENTER_CRITICAL(); if (p_hub_driver_obj != NULL) { HUB_DRIVER_EXIT_CRITICAL(); diff --git a/components/usb/private_include/hcd.h b/components/usb/private_include/hcd.h index de0746461d..77380b8ddc 100644 --- a/components/usb/private_include/hcd.h +++ b/components/usb/private_include/hcd.h @@ -448,13 +448,24 @@ esp_err_t hcd_pipe_set_persist_reset(hcd_pipe_handle_t pipe_hdl); void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl); /** - * @brief Get the current sate of the pipe + * @brief Get the current state 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 Get the number of in-flight URBs in the pipe + * + * Returns the current number of URBs that have been enqueued (via hcd_urb_enqueue()) and have yet to be dequeued (via + * hcd_urb_dequeue()). + * + * @param pipe_hdl Pipe handle + * @return Number of in-flight URBs + */ +unsigned int hcd_pipe_get_num_urbs(hcd_pipe_handle_t pipe_hdl); + /** * @brief Execute a command on a particular pipe * diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index fad62817b8..1a8807bf51 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -22,8 +22,8 @@ extern "C" { * @brief Hub driver configuration */ typedef struct { - usb_notif_cb_t notif_cb; /**< Notification callback */ - void *notif_cb_arg; /**< Notification callback argument */ + usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ + void *proc_req_cb_arg; /**< Processing request callback argument */ } hub_config_t; // ---------------------------------------------- Hub Driver Functions ------------------------------------------------- @@ -77,7 +77,7 @@ esp_err_t hub_root_stop(void); * @brief Hub driver's processing function * * Hub driver handling function that must be called repeatdly to process the Hub driver's events. If blocking, the - * caller can block on the notification callback of source USB_NOTIF_SOURCE_HUB to run this function. + * caller can block on the notification callback of source USB_PROC_REQ_SOURCE_HUB to run this function. * * @return esp_err_t */ diff --git a/components/usb/private_include/usb_private.h b/components/usb/private_include/usb_private.h index aaa34a1667..1d177c5159 100644 --- a/components/usb/private_include/usb_private.h +++ b/components/usb/private_include/usb_private.h @@ -50,12 +50,24 @@ struct urb_s{ }; typedef struct urb_s urb_t; +/** + * @brief Processing request source + * + * Enum to indicate which layer of the USB Host stack requires processing. The main handling loop should then call that + * layer's processing function (i.e., xxx_process()). + */ typedef enum { - USB_NOTIF_SOURCE_USBH = 0x01, - USB_NOTIF_SOURCE_HUB = 0x02, -} usb_notif_source_t; + USB_PROC_REQ_SOURCE_USBH = 0x01, + USB_PROC_REQ_SOURCE_HUB = 0x02, +} usb_proc_req_source_t; -typedef bool (*usb_notif_cb_t)(usb_notif_source_t source, bool in_isr, void *context); +/** + * @brief Processing request callback + * + * Callback function provided to each layer of the USB Host stack so that each layer can request calls to their + * processing function. + */ +typedef bool (*usb_proc_req_cb_t)(usb_proc_req_source_t source, bool in_isr, void *context); // --------------------------------------------------- Allocation ------------------------------------------------------ diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 35f0562130..1d033e4390 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -20,6 +20,13 @@ extern "C" { // ------------------------------------------------------ Types -------------------------------------------------------- +// ----------------------- Handles ------------------------- + +/** + * @brief Handle of a allocated endpoint + */ +typedef struct usbh_ep_handle_s * usbh_ep_handle_t; + // ----------------------- Events -------------------------- typedef enum { @@ -29,16 +36,18 @@ typedef enum { } usbh_event_t; /** - * @brief Hub driver requests + * @brief Endpoint events * - * Various requests of the Hub driver that the USBH can make. + * @note Optimization: Keep this identical to hcd_pipe_event_t */ typedef enum { - USBH_HUB_REQ_PORT_DISABLE, /**< Request that the Hub driver disable a particular port (occurs after a device - has been freed). Hub driver should respond with a USBH_HUB_EVENT_PORT_DISABLED */ - USBH_HUB_REQ_PORT_RECOVER, /**< Request that the Hub driver recovers a particular port (occurs after a gone - device has been freed). */ -} usbh_hub_req_t; + USBH_EP_EVENT_NONE, /**< The EP has no events (used to indicate no events when polling) */ + USBH_EP_EVENT_URB_DONE, /**< The EP has completed a URB. The URB can be dequeued */ + USBH_EP_EVENT_ERROR_XFER, /**< The EP encountered excessive errors when transferring a URB i.e., three three consecutive transaction errors (e.g., no ACK, bad CRC etc) */ + USBH_EP_EVENT_ERROR_URB_NOT_AVAIL, /**< The EP tried to execute a transfer but no URB was available */ + USBH_EP_EVENT_ERROR_OVERFLOW, /**< The EP received more data than requested. Usually a Packet babble error (i.e., an IN packet has exceeded the EP's MPS) */ + USBH_EP_EVENT_ERROR_STALL, /**< EP received a STALL response */ +} usbh_ep_event_t; /** * @brief Hub driver events for the USBH @@ -63,6 +72,31 @@ typedef enum { USBH_HUB_EVENT_PORT_DISABLED, /**< Previous USBH_HUB_REQ_PORT_DISABLE request completed */ } usbh_hub_event_t; +// ------------------ Requests/Commands -------------------- + +/** + * @brief Hub driver requests + * + * Various requests of the Hub driver that the USBH can make. + */ +typedef enum { + USBH_HUB_REQ_PORT_DISABLE, /**< Request that the Hub driver disable a particular port (occurs after a device + has been freed). Hub driver should respond with a USBH_HUB_EVENT_PORT_DISABLED */ + USBH_HUB_REQ_PORT_RECOVER, /**< Request that the Hub driver recovers a particular port (occurs after a gone + device has been freed). */ +} usbh_hub_req_t; + +/** + * @brief Endpoint commands + * + * @note Optimization: Keep this identical to hcd_pipe_cmd_t + */ +typedef enum { + USBH_EP_CMD_HALT, /**< Halt an active endpoint. Any currently executing URB will be canceled. Enqueued URBs are left untouched */ + USBH_EP_CMD_FLUSH, /**< Can only be called when halted. Will cause all enqueued URBs to be canceled */ + USBH_EP_CMD_CLEAR, /**< Causes a halted endpoint to become active again. Any enqueued URBs will being executing.*/ +} usbh_ep_cmd_t; + // ---------------------- Callbacks ------------------------ /** @@ -87,29 +121,37 @@ typedef void (*usbh_event_cb_t)(usb_device_handle_t dev_hdl, usbh_event_t usbh_e */ typedef void (*usbh_hub_req_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); +/** + * @brief Callback used to indicate an event on an endpoint + * + * Return whether to yield or not if called from an ISR. Always return false if not called from an ISR + */ +typedef bool (*usbh_ep_cb_t)(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *arg, bool in_isr); + // ----------------------- Objects ------------------------- /** * @brief Configuration for an endpoint being allocated using usbh_ep_alloc() */ typedef struct { - const usb_ep_desc_t *ep_desc; /**< Endpoint descriptor */ - hcd_pipe_callback_t pipe_cb; /**< Endpoint's pipe callback */ - void *pipe_cb_arg; /**< Pipe callback argument */ - void *context; /**< Pipe context */ + uint8_t bInterfaceNumber; /**< Interface number */ + uint8_t bAlternateSetting; /**< Alternate setting number of the interface */ + uint8_t bEndpointAddress; /**< Endpoint address */ + usbh_ep_cb_t ep_cb; /**< Endpoint event callback */ + void *ep_cb_arg; /**< Endpoint callback argument */ + void *context; /**< Endpoint context */ } usbh_ep_config_t; /** * @brief USBH configuration used in usbh_install() */ typedef struct { - usb_notif_cb_t notif_cb; /**< Notification callback */ - void *notif_cb_arg; /**< Notification callback argument */ + usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ + void *proc_req_cb_arg; /**< Processing request callback argument */ usbh_ctrl_xfer_cb_t ctrl_xfer_cb; /**< Control transfer callback */ void *ctrl_xfer_cb_arg; /**< Control transfer callback argument */ usbh_event_cb_t event_cb; /**< USBH event callback */ void *event_cb_arg; /**< USBH event callback argument */ - hcd_config_t hcd_config; /**< HCD configuration */ } usbh_config_t; // ------------------------------------------------- USBH Functions ---------------------------------------------------- @@ -143,8 +185,8 @@ esp_err_t usbh_uninstall(void); * @brief USBH processing function * * - USBH processing function that must be called repeatedly to process USBH events - * - If blocking, the caller can block until a USB_NOTIF_SOURCE_USBH notification is received before running this - * function + * - If blocking, the caller can block until the proc_req_cb() is called with USB_PROC_REQ_SOURCE_USBH as the request + * source. The USB_PROC_REQ_SOURCE_USBH source indicates that this function should be called. * * @note This function can block * @return esp_err_t @@ -271,31 +313,84 @@ esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb); /** * @brief Allocate an endpoint on a device * - * Clients that have opened a device must call this function to allocate all endpoints in an interface that is claimed. - * The pipe handle of the endpoint is returned so that clients can use and control the pipe directly. + * This function allows clients to allocate a non-default endpoint (i.e., not EP0) on a connected device + * + * - A client must have opened the device using usbh_dev_open() before attempting to allocate an endpoint on the device + * - A client should call this function to allocate all endpoints in an interface that the client has claimed. + * - A client must allocate an endpoint using this function before attempting to communicate with it + * - Once the client allocates an endpoint, the client is now owns/manages the endpoint. No other client should use or + * deallocte the endpoint. * * @note This function can block - * @note Default pipes are owned by the USBH. For control transfers, use usbh_dev_submit_ctrl_urb() instead - * @note Device must be opened by the client first + * @note Default endpoints (EP0) are owned by the USBH. For control transfers, use usbh_dev_submit_ctrl_urb() instead * * @param[in] dev_hdl Device handle - * @param[in] ep_config - * @param[out] pipe_hdl_ret Pipe handle + * @param[in] ep_config Endpoint configuration + * @param[out] ep_hdl_ret Endpoint handle * @return esp_err_t */ -esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, hcd_pipe_handle_t *pipe_hdl_ret); +esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, usbh_ep_handle_t *ep_hdl_ret); /** * @brief Free and endpoint on a device * - * Free an endpoint previously opened by usbh_ep_alloc() + * This function frees an endpoint previously allocated by the client using usbh_ep_alloc() + * + * - Only the client that allocated the endpoint should free it + * - The client must have halted and flushed the endpoint using usbh_ep_command() before attempting to free it + * - The client must ensure that there are no more function calls to the endpoint before freeing it * * @note This function can block - * @param[in] dev_hdl Device handle - * @param[in] bEndpointAddress Endpoint's address + * @param[in] ep_hdl Endpoint handle * @return esp_err_t */ -esp_err_t usbh_ep_free(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress); +esp_err_t usbh_ep_free(usbh_ep_handle_t ep_hdl); + +/** + * @brief Get the handle of an endpoint using its address + * + * The endpoint must have been previously allocated using usbh_ep_alloc() + * + * @param[in] dev_hdl Device handle + * @param[in] bEndpointAddress Endpoint address + * @param[out] ep_hdl_ret Endpoint handle + * @return esp_err_t + */ +esp_err_t usbh_ep_get_handle(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, usbh_ep_handle_t *ep_hdl_ret); + +/** + * @brief Enqueue a URB to an endpoint + * + * The URB will remain enqueued until it completes (successfully or errors out). Use usbh_ep_dequeue_urb() to dequeue + * a completed URB. + * + * @param[in] ep_hdl Endpoint handle + * @param[in] urb URB to enqueue + * @return esp_err_t + */ +esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb); + +/** + * @brief Dequeue a URB from an endpoint + * + * Dequeue a completed URB from an endpoint. The USBH_EP_EVENT_URB_DONE indicates that URBs can be dequeued + * + * @param[in] ep_hdl Endpoint handle + * @param[out] urb_ret Dequeued URB, or NULL if no more URBs to dequeue + * @return esp_err_t + */ +esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret); + +/** + * @brief Execute a command on a particular endpoint + * + * Endpoint commands allows executing a certain action on an endpoint (e.g., halting, flushing, clearing etc) + * + * @param[in] ep_hdl Endpoint handle + * @param[in] command Endpoint command + * @return esp_err_t + */ +esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command); /** * @brief Get the context of an endpoint @@ -303,12 +398,10 @@ esp_err_t usbh_ep_free(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress); * Get the context variable assigned to and endpoint on allocation. * * @note This function can block - * @param[in] dev_hdl Device handle - * @param[in] bEndpointAddress Endpoint's address - * @param[out] context_ret Context variable - * @return esp_err_t + * @param[in] ep_hdl Endpoint handle + * @return Endpoint context */ -esp_err_t usbh_ep_get_context(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, void **context_ret); +void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl); // -------------------------------------------------- Hub Functions ---------------------------------------------------- diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 5550711724..7e507a47f4 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -19,6 +19,7 @@ Warning: The USB Host Library API is still a beta version and may be subject to #include "esp_heap_caps.h" #include "hub.h" #include "usbh.h" +#include "hcd.h" #include "esp_private/usb_phy.h" #include "usb/usb_host.h" @@ -43,18 +44,17 @@ static portMUX_TYPE host_lock = portMUX_INITIALIZER_UNLOCKED; } \ }) -#define PROCESS_PENDING_FLAG_USBH 0x01 -#define PROCESS_PENDING_FLAG_HUB 0x02 -#define PROCESS_PENDING_FLAG_EVENT 0x04 +#define PROCESS_REQUEST_PENDING_FLAG_USBH 0x01 +#define PROCESS_REQUEST_PENDING_FLAG_HUB 0x02 -typedef struct endpoint_s endpoint_t; +typedef struct ep_wrapper_s ep_wrapper_t; typedef struct interface_s interface_t; typedef struct client_s client_t; -struct endpoint_s { +struct ep_wrapper_s { //Dynamic members require a critical section struct { - TAILQ_ENTRY(endpoint_s) tailq_entry; + TAILQ_ENTRY(ep_wrapper_s) tailq_entry; union { struct { uint32_t pending: 1; @@ -62,12 +62,11 @@ struct endpoint_s { }; } flags; uint32_t num_urb_inflight; - hcd_pipe_event_t last_event; + usbh_ep_event_t last_event; } dynamic; //Constant members do no change after claiming the interface thus do not require a critical section struct { - hcd_pipe_handle_t pipe_hdl; - const usb_ep_desc_t *ep_desc; + usbh_ep_handle_t ep_hdl; interface_t *intf_obj; } constant; }; @@ -82,7 +81,7 @@ struct interface_s { const usb_intf_desc_t *intf_desc; usb_device_handle_t dev_hdl; client_t *client_obj; - endpoint_t *endpoints[0]; + ep_wrapper_t *endpoints[0]; } constant; }; @@ -90,8 +89,8 @@ struct client_s { //Dynamic members require a critical section struct { TAILQ_ENTRY(client_s) tailq_entry; - TAILQ_HEAD(tailhead_pending_ep, endpoint_s) pending_ep_tailq; - TAILQ_HEAD(tailhead_idle_ep, endpoint_s) idle_ep_tailq; + TAILQ_HEAD(tailhead_pending_ep, ep_wrapper_s) pending_ep_tailq; + TAILQ_HEAD(tailhead_idle_ep, ep_wrapper_s) idle_ep_tailq; TAILQ_HEAD(tailhead_done_ctrl_xfers, urb_s) done_ctrl_xfer_tailq; union { struct { @@ -260,16 +259,16 @@ static void send_event_msg_to_clients(const usb_host_client_event_msg_t *event_m // ------------------- Library Related --------------------- -static bool notif_callback(usb_notif_source_t source, bool in_isr, void *arg) +static bool proc_req_callback(usb_proc_req_source_t source, bool in_isr, void *arg) { HOST_ENTER_CRITICAL_SAFE(); - //Store notification source + //Store the processing request source switch (source) { - case USB_NOTIF_SOURCE_USBH: - p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_PENDING_FLAG_USBH; + case USB_PROC_REQ_SOURCE_USBH: + p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_USBH; break; - case USB_NOTIF_SOURCE_HUB: - p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_PENDING_FLAG_HUB; + case USB_PROC_REQ_SOURCE_HUB: + p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_HUB; break; } bool yield = _unblock_lib(in_isr); @@ -333,19 +332,19 @@ static void dev_event_callback(usb_device_handle_t dev_hdl, usbh_event_t usbh_ev // ------------------- Client Related ---------------------- -static bool pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +static bool endpoint_callback(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr) { - endpoint_t *ep_obj = (endpoint_t *)user_arg; - client_t *client_obj = (client_t *)ep_obj->constant.intf_obj->constant.client_obj; + ep_wrapper_t *ep_wrap = (ep_wrapper_t *)user_arg; + client_t *client_obj = (client_t *)ep_wrap->constant.intf_obj->constant.client_obj; HOST_ENTER_CRITICAL_SAFE(); //Store the event to be handled later. Note that we allow overwriting of events because more severe will halt the pipe prevent any further events. - ep_obj->dynamic.last_event = pipe_event; + ep_wrap->dynamic.last_event = ep_event; //Add the EP to the client's pending list if it's not in the list already - if (!ep_obj->dynamic.flags.pending) { - ep_obj->dynamic.flags.pending = 1; - TAILQ_REMOVE(&client_obj->dynamic.idle_ep_tailq, ep_obj, dynamic.tailq_entry); - TAILQ_INSERT_TAIL(&client_obj->dynamic.pending_ep_tailq, ep_obj, dynamic.tailq_entry); + if (!ep_wrap->dynamic.flags.pending) { + ep_wrap->dynamic.flags.pending = 1; + TAILQ_REMOVE(&client_obj->dynamic.idle_ep_tailq, ep_wrap, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&client_obj->dynamic.pending_ep_tailq, ep_wrap, dynamic.tailq_entry); } bool yield = _unblock_client(client_obj, in_isr); HOST_EXIT_CRITICAL_SAFE(); @@ -376,7 +375,16 @@ esp_err_t usb_host_install(const usb_host_config_t *config) TAILQ_INIT(&host_lib_obj->mux_protected.client_tailq); host_lib_obj->constant.event_sem = event_sem; host_lib_obj->constant.mux_lock = mux_lock; - //Setup the USB PHY if necessary (USB PHY driver will also enable the underlying Host Controller) + + /* + Install each layer of the Host stack (listed below) from the lowest layer to the highest + - USB PHY + - HCD + - USBH + - Hub + */ + + //Install USB PHY (if necessary). USB PHY driver will also enable the underlying Host Controller if (!config->skip_phy_setup) { //Host Library defaults to internal PHY usb_phy_config_t phy_config = { @@ -392,26 +400,34 @@ esp_err_t usb_host_install(const usb_host_config_t *config) goto phy_err; } } + + //Install HCD + hcd_config_t hcd_config = { + .intr_flags = config->intr_flags + }; + ret = hcd_install(&hcd_config); + if (ret != ESP_OK) { + goto hcd_err; + } + //Install USBH usbh_config_t usbh_config = { - .notif_cb = notif_callback, - .notif_cb_arg = NULL, + .proc_req_cb = proc_req_callback, + .proc_req_cb_arg = NULL, .ctrl_xfer_cb = ctrl_xfer_callback, .ctrl_xfer_cb_arg = NULL, .event_cb = dev_event_callback, .event_cb_arg = NULL, - .hcd_config = { - .intr_flags = config->intr_flags, - }, }; ret = usbh_install(&usbh_config); if (ret != ESP_OK) { goto usbh_err; } + //Install Hub hub_config_t hub_config = { - .notif_cb = notif_callback, - .notif_cb_arg = NULL, + .proc_req_cb = proc_req_callback, + .proc_req_cb_arg = NULL, }; ret = hub_install(&hub_config); if (ret != ESP_OK) { @@ -438,6 +454,8 @@ assign_err: hub_err: ESP_ERROR_CHECK(usbh_uninstall()); usbh_err: + ESP_ERROR_CHECK(hcd_uninstall()); +hcd_err: if (host_lib_obj->constant.phy_handle) { ESP_ERROR_CHECK(usb_del_phy(host_lib_obj->constant.phy_handle)); } @@ -466,19 +484,28 @@ esp_err_t usb_host_uninstall(void) //Stop the root hub ESP_ERROR_CHECK(hub_root_stop()); - //Uninstall Hub and USBH - ESP_ERROR_CHECK(hub_uninstall()); - ESP_ERROR_CHECK(usbh_uninstall()); + //Unassign the host library object HOST_ENTER_CRITICAL(); host_lib_t *host_lib_obj = p_host_lib_obj; p_host_lib_obj = NULL; HOST_EXIT_CRITICAL(); + /* + Uninstall each layer of the Host stack (listed below) from the highest layer to the lowest + - Hub + - USBH + - HCD + - USB PHY + */ + ESP_ERROR_CHECK(hub_uninstall()); + ESP_ERROR_CHECK(usbh_uninstall()); + ESP_ERROR_CHECK(hcd_uninstall()); //If the USB PHY was setup, then delete it if (host_lib_obj->constant.phy_handle) { ESP_ERROR_CHECK(usb_del_phy(host_lib_obj->constant.phy_handle)); } + //Free memory objects vSemaphoreDelete(host_lib_obj->constant.mux_lock); vSemaphoreDelete(host_lib_obj->constant.event_sem); @@ -508,10 +535,10 @@ esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_f p_host_lib_obj->dynamic.flags.handling_events = 1; while (process_pending_flags) { HOST_EXIT_CRITICAL(); - if (process_pending_flags & PROCESS_PENDING_FLAG_USBH) { + if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_USBH) { ESP_ERROR_CHECK(usbh_process()); } - if (process_pending_flags & PROCESS_PENDING_FLAG_HUB) { + if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_HUB) { ESP_ERROR_CHECK(hub_process()); } HOST_ENTER_CRITICAL(); @@ -569,33 +596,34 @@ static void _handle_pending_ep(client_t *client_obj) //Handle each EP on the pending list while (!TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq)) { //Get the next pending EP. - endpoint_t *ep_obj = TAILQ_FIRST(&client_obj->dynamic.pending_ep_tailq); - TAILQ_REMOVE(&client_obj->dynamic.pending_ep_tailq, ep_obj, dynamic.tailq_entry); - TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, ep_obj, dynamic.tailq_entry); - ep_obj->dynamic.flags.pending = 0; - hcd_pipe_event_t last_event = ep_obj->dynamic.last_event; + ep_wrapper_t *ep_wrap = TAILQ_FIRST(&client_obj->dynamic.pending_ep_tailq); + TAILQ_REMOVE(&client_obj->dynamic.pending_ep_tailq, ep_wrap, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, ep_wrap, dynamic.tailq_entry); + ep_wrap->dynamic.flags.pending = 0; + usbh_ep_event_t last_event = ep_wrap->dynamic.last_event; uint32_t num_urb_dequeued = 0; HOST_EXIT_CRITICAL(); //Handle pipe event switch (last_event) { - case HCD_PIPE_EVENT_ERROR_XFER: - case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL: - case HCD_PIPE_EVENT_ERROR_OVERFLOW: - case HCD_PIPE_EVENT_ERROR_STALL: - //The pipe is now stalled. Flush all pending URBs - ESP_ERROR_CHECK(hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_FLUSH)); + case USBH_EP_EVENT_ERROR_XFER: + case USBH_EP_EVENT_ERROR_URB_NOT_AVAIL: + case USBH_EP_EVENT_ERROR_OVERFLOW: + case USBH_EP_EVENT_ERROR_STALL: + //The endpoint is now stalled. Flush all pending URBs + ESP_ERROR_CHECK(usbh_ep_command(ep_wrap->constant.ep_hdl, USBH_EP_CMD_FLUSH)); //All URBs in this pipe are now retired waiting to be dequeued. Fall through to dequeue them __attribute__((fallthrough)); - case HCD_PIPE_EVENT_URB_DONE: { + case USBH_EP_EVENT_URB_DONE: { //Dequeue all URBs and run their transfer callback - urb_t *urb = hcd_urb_dequeue(ep_obj->constant.pipe_hdl); + urb_t *urb; + usbh_ep_dequeue_urb(ep_wrap->constant.ep_hdl, &urb); while (urb != NULL) { //Clear the transfer's inflight flag to indicate the transfer is no longer inflight urb->usb_host_inflight = false; urb->transfer.callback(&urb->transfer); num_urb_dequeued++; - urb = hcd_urb_dequeue(ep_obj->constant.pipe_hdl); + usbh_ep_dequeue_urb(ep_wrap->constant.ep_hdl, &urb); } break; } @@ -606,8 +634,8 @@ static void _handle_pending_ep(client_t *client_obj) HOST_ENTER_CRITICAL(); //Update the endpoint's number of URB's inflight - assert(num_urb_dequeued <= ep_obj->dynamic.num_urb_inflight); - ep_obj->dynamic.num_urb_inflight -= num_urb_dequeued; + assert(num_urb_dequeued <= ep_wrap->dynamic.num_urb_inflight); + ep_wrap->dynamic.num_urb_inflight -= num_urb_dequeued; } } @@ -920,52 +948,53 @@ esp_err_t usb_host_get_active_config_descriptor(usb_device_handle_t dev_hdl, con // ----------------------- Private ------------------------- -static esp_err_t endpoint_alloc(usb_device_handle_t dev_hdl, const usb_ep_desc_t *ep_desc, interface_t *intf_obj, endpoint_t **ep_obj_ret) +static esp_err_t ep_wrapper_alloc(usb_device_handle_t dev_hdl, const usb_ep_desc_t *ep_desc, interface_t *intf_obj, ep_wrapper_t **ep_wrap_ret) { - endpoint_t *ep_obj = heap_caps_calloc(1, sizeof(endpoint_t), MALLOC_CAP_DEFAULT); - if (ep_obj == NULL) { + ep_wrapper_t *ep_wrap = heap_caps_calloc(1, sizeof(ep_wrapper_t), MALLOC_CAP_DEFAULT); + if (ep_wrap == NULL) { return ESP_ERR_NO_MEM; } esp_err_t ret; + usbh_ep_handle_t ep_hdl; usbh_ep_config_t ep_config = { - .ep_desc = ep_desc, - .pipe_cb = pipe_callback, - .pipe_cb_arg = (void *)ep_obj, - .context = (void *)ep_obj, + .bInterfaceNumber = intf_obj->constant.intf_desc->bInterfaceNumber, + .bAlternateSetting = intf_obj->constant.intf_desc->bAlternateSetting, + .bEndpointAddress = ep_desc->bEndpointAddress, + .ep_cb = endpoint_callback, + .ep_cb_arg = (void *)ep_wrap, + .context = (void *)ep_wrap, }; - hcd_pipe_handle_t pipe_hdl; - ret = usbh_ep_alloc(dev_hdl, &ep_config, &pipe_hdl); + ret = usbh_ep_alloc(dev_hdl, &ep_config, &ep_hdl); if (ret != ESP_OK) { - goto ep_alloc_err; + goto alloc_err; } - //Initialize endpoint object - ep_obj->constant.pipe_hdl = pipe_hdl; - ep_obj->constant.ep_desc = ep_desc; - ep_obj->constant.intf_obj = intf_obj; + //Initialize endpoint wrapper item + ep_wrap->constant.ep_hdl = ep_hdl; + ep_wrap->constant.intf_obj = intf_obj; //Write back result - *ep_obj_ret = ep_obj; + *ep_wrap_ret = ep_wrap; ret = ESP_OK; return ret; -ep_alloc_err: - heap_caps_free(ep_obj); +alloc_err: + heap_caps_free(ep_wrap); return ret; } -static void endpoint_free(usb_device_handle_t dev_hdl, endpoint_t *ep_obj) +static void ep_wrapper_free(usb_device_handle_t dev_hdl, ep_wrapper_t *ep_wrap) { - if (ep_obj == NULL) { + if (ep_wrap == NULL) { return; } //Free the underlying endpoint - ESP_ERROR_CHECK(usbh_ep_free(dev_hdl, ep_obj->constant.ep_desc->bEndpointAddress)); - //Free the endpoint object - heap_caps_free(ep_obj); + ESP_ERROR_CHECK(usbh_ep_free(ep_wrap->constant.ep_hdl)); + //Free the endpoint wrapper item + heap_caps_free(ep_wrap); } static interface_t *interface_alloc(client_t *client_obj, usb_device_handle_t dev_hdl, const usb_intf_desc_t *intf_desc) { - interface_t *intf_obj = heap_caps_calloc(1, sizeof(interface_t) + (sizeof(endpoint_t *) * intf_desc->bNumEndpoints), MALLOC_CAP_DEFAULT); + interface_t *intf_obj = heap_caps_calloc(1, sizeof(interface_t) + (sizeof(ep_wrapper_t *) * intf_desc->bNumEndpoints), MALLOC_CAP_DEFAULT); if (intf_obj == NULL) { return NULL; } @@ -1011,18 +1040,18 @@ static esp_err_t interface_claim(client_t *client_obj, usb_device_handle_t dev_h ret = ESP_ERR_NOT_FOUND; goto ep_alloc_err; } - //Allocate the endpoint - endpoint_t *ep_obj; - ret = endpoint_alloc(dev_hdl, ep_desc, intf_obj, &ep_obj); + //Allocate the endpoint wrapper item + ep_wrapper_t *ep_wrap; + ret = ep_wrapper_alloc(dev_hdl, ep_desc, intf_obj, &ep_wrap); if (ret != ESP_OK) { goto ep_alloc_err; } //Fill the interface object with the allocated endpoints - intf_obj->constant.endpoints[i] = ep_obj; + intf_obj->constant.endpoints[i] = ep_wrap; } //Add interface object to client (safe because we have already taken the mutex) TAILQ_INSERT_TAIL(&client_obj->mux_protected.interface_tailq, intf_obj, mux_protected.tailq_entry); - //Add each endpoint to the client's endpoint list + //Add each endpoint wrapper item to the client's endpoint list HOST_ENTER_CRITICAL(); for (int i = 0; i < intf_desc->bNumEndpoints; i++) { TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, intf_obj->constant.endpoints[i], dynamic.tailq_entry); @@ -1035,7 +1064,7 @@ static esp_err_t interface_claim(client_t *client_obj, usb_device_handle_t dev_h ep_alloc_err: for (int i = 0; i < intf_desc->bNumEndpoints; i++) { - endpoint_free(dev_hdl, intf_obj->constant.endpoints[i]); + ep_wrapper_free(dev_hdl, intf_obj->constant.endpoints[i]); intf_obj->constant.endpoints[i] = NULL; } interface_free(intf_obj); @@ -1061,12 +1090,13 @@ static esp_err_t interface_release(client_t *client_obj, usb_device_handle_t dev } //Check that all endpoints in the interface are in a state to be freed + //Todo: Check that each EP is halted before allowing them to be freed (IDF-7273) HOST_ENTER_CRITICAL(); bool can_free = true; for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { - endpoint_t *ep_obj = intf_obj->constant.endpoints[i]; + ep_wrapper_t *ep_wrap = intf_obj->constant.endpoints[i]; //Endpoint must not be on the pending list and must not have inflight URBs - if (ep_obj->dynamic.num_urb_inflight != 0 || ep_obj->dynamic.flags.pending) { + if (ep_wrap->dynamic.num_urb_inflight != 0 || ep_wrap->dynamic.flags.pending) { can_free = false; break; } @@ -1076,7 +1106,7 @@ static esp_err_t interface_release(client_t *client_obj, usb_device_handle_t dev ret = ESP_ERR_INVALID_STATE; goto exit; } - //Proceed to remove all endpoint objects from list + //Proceed to remove all endpoint wrapper items from the list for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { TAILQ_REMOVE(&client_obj->dynamic.idle_ep_tailq, intf_obj->constant.endpoints[i], dynamic.tailq_entry); } @@ -1087,7 +1117,7 @@ static esp_err_t interface_release(client_t *client_obj, usb_device_handle_t dev //Free each endpoint in the interface for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { - endpoint_free(dev_hdl, intf_obj->constant.endpoints[i]); + ep_wrapper_free(dev_hdl, intf_obj->constant.endpoints[i]); intf_obj->constant.endpoints[i] = NULL; } //Free the interface object itself @@ -1167,13 +1197,14 @@ esp_err_t usb_host_interface_release(usb_host_client_handle_t client_hdl, usb_de esp_err_t usb_host_endpoint_halt(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) { esp_err_t ret; - endpoint_t *ep_obj = NULL; - ret = usbh_ep_get_context(dev_hdl, bEndpointAddress, (void **)&ep_obj); + usbh_ep_handle_t ep_hdl; + + ret = usbh_ep_get_handle(dev_hdl, bEndpointAddress, &ep_hdl); if (ret != ESP_OK) { goto exit; } - assert(ep_obj != NULL); - ret = hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_HALT); + ret = usbh_ep_command(ep_hdl, USBH_EP_CMD_HALT); + exit: return ret; } @@ -1181,13 +1212,14 @@ exit: esp_err_t usb_host_endpoint_flush(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) { esp_err_t ret; - endpoint_t *ep_obj = NULL; - ret = usbh_ep_get_context(dev_hdl, bEndpointAddress, (void **)&ep_obj); + usbh_ep_handle_t ep_hdl; + + ret = usbh_ep_get_handle(dev_hdl, bEndpointAddress, &ep_hdl); if (ret != ESP_OK) { goto exit; } - assert(ep_obj != NULL); - ret = hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_FLUSH); + ret = usbh_ep_command(ep_hdl, USBH_EP_CMD_FLUSH); + exit: return ret; } @@ -1195,73 +1227,20 @@ exit: esp_err_t usb_host_endpoint_clear(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) { esp_err_t ret; - endpoint_t *ep_obj = NULL; - ret = usbh_ep_get_context(dev_hdl, bEndpointAddress, (void **)&ep_obj); + usbh_ep_handle_t ep_hdl; + + ret = usbh_ep_get_handle(dev_hdl, bEndpointAddress, &ep_hdl); if (ret != ESP_OK) { goto exit; } - assert(ep_obj != NULL); - ret = hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_CLEAR); + ret = usbh_ep_command(ep_hdl, USBH_EP_CMD_CLEAR); + exit: return ret; } // ------------------------------------------------ Asynchronous I/O --------------------------------------------------- -// ----------------------- Private ------------------------- - -static bool transfer_check(usb_transfer_t *transfer, usb_transfer_type_t type, int mps, bool is_in) -{ - if (transfer->callback == NULL) { - ESP_LOGE(USB_HOST_TAG, "Transfer callback is NULL"); - return false; - } - //Check that the total transfer length does not exceed data buffer size - if (transfer->num_bytes > transfer->data_buffer_size) { - ESP_LOGE(USB_HOST_TAG, "Transfer num_bytes > data_buffer_size"); - return false; - } - if (type == USB_TRANSFER_TYPE_CTRL) { - //Check that num_bytes and wLength are set correctly - usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)transfer->data_buffer; - if (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength) { - ESP_LOGE(USB_HOST_TAG, "Control transfer num_bytes wLength mismatch"); - return false; - } - } else if (type == USB_TRANSFER_TYPE_ISOCHRONOUS) { - //Check that there is at least one isochronous packet descriptor - if (transfer->num_isoc_packets <= 0) { - ESP_LOGE(USB_HOST_TAG, "ISOC transfer has 0 packet descriptors"); - return false; - } - //Check that sum of all packet lengths add up to transfer length - //If IN, check that each packet length is integer multiple of MPS - int total_num_bytes = 0; - bool mod_mps_all_zero = true; - for (int i = 0; i < transfer->num_isoc_packets; i++) { - total_num_bytes += transfer->isoc_packet_desc[i].num_bytes; - if (transfer->isoc_packet_desc[i].num_bytes % mps != 0) { - mod_mps_all_zero = false; - } - } - if (transfer->num_bytes != total_num_bytes) { - ESP_LOGE(USB_HOST_TAG, "ISOC transfer num_bytes not equal to total num_bytes of all packets"); - return false; - } - if (is_in && !mod_mps_all_zero) { - ESP_LOGE(USB_HOST_TAG, "ISOC IN transfer all packets num_bytes must be integer multiple of MPS"); - return false; - } - } else { - //Check that IN transfers are integer multiple of MPS - if (is_in && (transfer->num_bytes % mps != 0)) { - ESP_LOGE(USB_HOST_TAG, "IN transfer num_bytes must be integer multiple of MPS"); - return false; - } - } - return true; -} - // ----------------------- Public -------------------------- esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets, usb_transfer_t **transfer) @@ -1290,39 +1269,34 @@ esp_err_t usb_host_transfer_submit(usb_transfer_t *transfer) //Check that transfer and target endpoint are valid HOST_CHECK(transfer->device_handle != NULL, ESP_ERR_INVALID_ARG); //Target device must be set HOST_CHECK((transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) != 0, ESP_ERR_INVALID_ARG); - endpoint_t *ep_obj = NULL; + + usbh_ep_handle_t ep_hdl; + ep_wrapper_t *ep_wrap = NULL; urb_t *urb_obj = __containerof(transfer, urb_t, transfer); esp_err_t ret; - ret = usbh_ep_get_context(transfer->device_handle, transfer->bEndpointAddress, (void **)&ep_obj); + + ret = usbh_ep_get_handle(transfer->device_handle, transfer->bEndpointAddress, &ep_hdl); if (ret != ESP_OK) { goto err; } - assert(ep_obj != NULL); - HOST_CHECK(transfer_check(transfer, - USB_EP_DESC_GET_XFERTYPE(ep_obj->constant.ep_desc), - USB_EP_DESC_GET_MPS(ep_obj->constant.ep_desc), - transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK), ESP_ERR_INVALID_ARG); + ep_wrap = usbh_ep_get_context(ep_hdl); + assert(ep_wrap != NULL); //Check that we are not submitting a transfer already inflight HOST_CHECK(!urb_obj->usb_host_inflight, ESP_ERR_NOT_FINISHED); urb_obj->usb_host_inflight = true; HOST_ENTER_CRITICAL(); - ep_obj->dynamic.num_urb_inflight++; + ep_wrap->dynamic.num_urb_inflight++; HOST_EXIT_CRITICAL(); - //Check if pipe is in a state to enqueue URBs - if (hcd_pipe_get_state(ep_obj->constant.pipe_hdl) != HCD_PIPE_STATE_ACTIVE) { - ret = ESP_ERR_INVALID_STATE; - goto hcd_err; - } - ret = hcd_urb_enqueue(ep_obj->constant.pipe_hdl, urb_obj); + + ret = usbh_ep_enqueue_urb(ep_hdl, urb_obj); if (ret != ESP_OK) { - goto hcd_err; + goto submit_err; } - ret = ESP_OK; return ret; -hcd_err: +submit_err: HOST_ENTER_CRITICAL(); - ep_obj->dynamic.num_urb_inflight--; + ep_wrap->dynamic.num_urb_inflight--; HOST_EXIT_CRITICAL(); urb_obj->usb_host_inflight = false; err: @@ -1332,17 +1306,12 @@ err: esp_err_t usb_host_transfer_submit_control(usb_host_client_handle_t client_hdl, usb_transfer_t *transfer) { HOST_CHECK(client_hdl != NULL && transfer != NULL, ESP_ERR_INVALID_ARG); - //Check that control transfer is valid HOST_CHECK(transfer->device_handle != NULL, ESP_ERR_INVALID_ARG); //Target device must be set - usb_device_handle_t dev_hdl = transfer->device_handle; - bool xfer_is_in = ((usb_setup_packet_t *)transfer->data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; - usb_device_info_t dev_info; - ESP_ERROR_CHECK(usbh_dev_get_info(dev_hdl, &dev_info)); - HOST_CHECK(transfer_check(transfer, USB_TRANSFER_TYPE_CTRL, dev_info.bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG); //Control transfers must be targeted at EP 0 HOST_CHECK((transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) == 0, ESP_ERR_INVALID_ARG); + usb_device_handle_t dev_hdl = transfer->device_handle; urb_t *urb_obj = __containerof(transfer, urb_t, transfer); //Check that we are not submitting a transfer already inflight HOST_CHECK(!urb_obj->usb_host_inflight, ESP_ERR_NOT_FINISHED); diff --git a/components/usb/usbh.c b/components/usb/usbh.c index aad2f54def..d2bd9e4816 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -21,21 +21,35 @@ #include "usb/usb_helpers.h" #include "usb/usb_types_ch9.h" -//Device action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within usbh_process(). Some actions are mutually exclusive -#define DEV_FLAG_ACTION_PIPE_HALT_AND_FLUSH 0x0001 //Halt all non-default pipes then flush them (called after a device gone is gone) -#define DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH 0x0002 //Retire all URBS in the default pipe -#define DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE 0x0004 //Dequeue all URBs from default pipe -#define DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR 0x0008 //Move the default pipe to the active state -#define DEV_FLAG_ACTION_SEND_GONE_EVENT 0x0010 //Send a USB_HOST_CLIENT_EVENT_DEV_GONE event -#define DEV_FLAG_ACTION_FREE 0x0020 //Free the device object -#define DEV_FLAG_ACTION_FREE_AND_RECOVER 0x0040 //Free the device object, but send a USBH_HUB_REQ_PORT_RECOVER request afterwards. -#define DEV_FLAG_ACTION_PORT_DISABLE 0x0080 //Request the hub driver to disable the port of the device -#define DEV_FLAG_ACTION_SEND_NEW 0x0100 //Send a new device event +#define EP_NUM_MIN 1 // The smallest possible non-default endpoint number +#define EP_NUM_MAX 16 // The largest possible non-default endpoint number +#define NUM_NON_DEFAULT_EP ((EP_NUM_MAX - 1) * 2) // The total number of non-default endpoints a device can have. -#define EP_NUM_MIN 1 -#define EP_NUM_MAX 16 +//Device action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within usbh_process(). Some actions are mutually exclusive +typedef enum { + DEV_ACTION_EPn_HALT_FLUSH = (1 << 0), //Halt all non-default endpoints then flush them (called after a device gone is gone) + DEV_ACTION_EP0_FLUSH = (1 << 1), //Retire all URBS submitted to EP0 + DEV_ACTION_EP0_DEQUEUE = (1 << 2), //Dequeue all URBs from EP0 + DEV_ACTION_EP0_CLEAR = (1 << 3), //Move EP0 to the the active state + DEV_ACTION_PROP_GONE_EVT = (1 << 4), //Propagate a USBH_EVENT_DEV_GONE event + DEV_ACTION_FREE_AND_RECOVER = (1 << 5), //Free the device object, but send a USBH_HUB_REQ_PORT_RECOVER request afterwards. + DEV_ACTION_FREE = (1 << 6), //Free the device object + DEV_ACTION_PORT_DISABLE = (1 << 7), //Request the hub driver to disable the port of the device + DEV_ACTION_PROP_NEW = (1 << 8), //Propagate a USBH_EVENT_DEV_NEW event +} dev_action_t; typedef struct device_s device_t; + +typedef struct { + struct { + usbh_ep_cb_t ep_cb; + void *ep_cb_arg; + hcd_pipe_handle_t pipe_hdl; + device_t *dev; //Pointer to the device object that this endpoint is contained in + const usb_ep_desc_t *ep_desc; //This just stores a pointer endpoint descriptor inside the device's "config_desc" + } constant; +} endpoint_t; + struct device_s { //Dynamic members require a critical section struct { @@ -58,10 +72,13 @@ struct device_s { } dynamic; //Mux protected members must be protected by the USBH mux_lock when accessed struct { - hcd_pipe_handle_t ep_in[EP_NUM_MAX - 1]; //IN EP owner contexts. -1 to exclude the default endpoint - hcd_pipe_handle_t ep_out[EP_NUM_MAX - 1]; //OUT EP owner contexts. -1 to exclude the default endpoint + /* + - Endpoint object pointers for each possible non-default endpoint + - All OUT EPs are listed before IN EPs (i.e., EP_NUM_MIN OUT ... EP_NUM_MAX OUT ... EP_NUM_MIN IN ... EP_NUM_MAX) + */ + endpoint_t *endpoints[NUM_NON_DEFAULT_EP]; } mux_protected; - //Constant members do no change after device allocation and enumeration thus do not require a critical section + //Constant members do not change after device allocation and enumeration thus do not require a critical section struct { hcd_pipe_handle_t default_pipe; hcd_port_handle_t port_hdl; @@ -87,8 +104,8 @@ typedef struct { } mux_protected; //Constant members do no change after installation thus do not require a critical section struct { - usb_notif_cb_t notif_cb; - void *notif_cb_arg; + usb_proc_req_cb_t proc_req_cb; + void *proc_req_cb_arg; usbh_hub_req_cb_t hub_req_cb; void *hub_req_cb_arg; usbh_event_cb_t event_cb; @@ -124,8 +141,173 @@ const char *USBH_TAG = "USBH"; } \ }) +// ------------------------------------------------- Forward Declare --------------------------------------------------- + +static bool ep0_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); + +static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); + +static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags); + +// ----------------------------------------------------- Helpers ------------------------------------------------------- + +static inline bool check_ep_addr(uint8_t bEndpointAddress) +{ + /* + Check that the bEndpointAddress is valid + - Must be <= EP_NUM_MAX (e.g., 16) + - Must be >= EP_NUM_MIN (e.g., 1). + - EP0 is the owned/managed by USBH, thus must never by directly addressed by users (see USB 2.0 section 10.5.1.2) + */ + uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK; + return (addr >= EP_NUM_MIN) && (addr <= EP_NUM_MAX); +} + +static endpoint_t *get_ep_from_addr(device_t *dev_obj, uint8_t bEndpointAddress) +{ + /* + CALLER IS RESPONSIBLE FOR TAKING THE mux_lock + */ + + //Calculate index to the device's endpoint object list + int index; + // EP_NUM_MIN should map to an index of 0 + index = (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) - EP_NUM_MIN; + assert(index >= 0); // Endpoint address is not supported + if (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { + //OUT EPs are listed before IN EPs, so add an offset + index += (EP_NUM_MAX - EP_NUM_MIN); + } + return dev_obj->mux_protected.endpoints[index]; +} + +static inline void set_ep_from_addr(device_t *dev_obj, uint8_t bEndpointAddress, endpoint_t *ep_obj) +{ + /* + CALLER IS RESPONSIBLE FOR TAKING THE mux_lock + */ + + //Calculate index to the device's endpoint object list + int index; + // EP_NUM_MIN should map to an index of 0 + index = (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) - EP_NUM_MIN; + assert(index >= 0); // Endpoint address is not supported + if (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { + //OUT EPs are listed before IN EPs, so add an offset + index += (EP_NUM_MAX - EP_NUM_MIN); + } + dev_obj->mux_protected.endpoints[index] = ep_obj; +} + +static bool urb_check_args(urb_t *urb) +{ + if (urb->transfer.callback == NULL) { + ESP_LOGE(USBH_TAG, "usb_transfer_t callback is NULL"); + return false; + } + if (urb->transfer.num_bytes > urb->transfer.data_buffer_size) { + ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes > data_buffer_size"); + return false; + } + return true; +} + +static bool transfer_check_usb_compliance(usb_transfer_t *transfer, usb_transfer_type_t type, int mps, bool is_in) +{ + if (type == USB_TRANSFER_TYPE_CTRL) { + //Check that num_bytes and wLength are set correctly + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)transfer->data_buffer; + if (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength) { + ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes and usb_setup_packet_t wLength mismatch"); + return false; + } + } else if (type == USB_TRANSFER_TYPE_ISOCHRONOUS) { + //Check that there is at least one isochronous packet descriptor + if (transfer->num_isoc_packets <= 0) { + ESP_LOGE(USBH_TAG, "usb_transfer_t num_isoc_packets is 0"); + return false; + } + //Check that sum of all packet lengths add up to transfer length + //If IN, check that each packet length is integer multiple of MPS + int total_num_bytes = 0; + bool mod_mps_all_zero = true; + for (int i = 0; i < transfer->num_isoc_packets; i++) { + total_num_bytes += transfer->isoc_packet_desc[i].num_bytes; + if (transfer->isoc_packet_desc[i].num_bytes % mps != 0) { + mod_mps_all_zero = false; + } + } + if (transfer->num_bytes != total_num_bytes) { + ESP_LOGE(USBH_TAG, "ISOC transfer num_bytes != num_bytes of all packets"); + return false; + } + if (is_in && !mod_mps_all_zero) { + ESP_LOGE(USBH_TAG, "ISOC IN num_bytes not integer multiple of MPS"); + return false; + } + } else { + //Check that IN transfers are integer multiple of MPS + if (is_in && (transfer->num_bytes % mps != 0)) { + ESP_LOGE(USBH_TAG, "IN transfer num_bytes not integer multiple of MPS"); + return false; + } + } + return true; +} + // --------------------------------------------------- Allocation ------------------------------------------------------ +static esp_err_t endpoint_alloc(device_t *dev_obj, const usb_ep_desc_t *ep_desc, usbh_ep_config_t *ep_config, endpoint_t **ep_obj_ret) +{ + esp_err_t ret; + endpoint_t *ep_obj; + hcd_pipe_handle_t pipe_hdl; + + ep_obj = heap_caps_calloc(1, sizeof(endpoint_t), MALLOC_CAP_DEFAULT); + if (ep_obj == NULL) { + return ESP_ERR_NO_MEM; + } + //Allocate the EP's underlying pipe + hcd_pipe_config_t pipe_config = { + .callback = epN_pipe_callback, + .callback_arg = (void *)ep_obj, + .context = ep_config->context, + .ep_desc = ep_desc, + .dev_speed = dev_obj->constant.speed, + .dev_addr = dev_obj->constant.address, + }; + ret = hcd_pipe_alloc(dev_obj->constant.port_hdl, &pipe_config, &pipe_hdl); + if (ret != ESP_OK) { + goto pipe_err; + } + //Initialize the endpoint object + ep_obj->constant.pipe_hdl = pipe_hdl; + ep_obj->constant.ep_cb = ep_config->ep_cb; + ep_obj->constant.ep_cb_arg = ep_config->ep_cb_arg; + ep_obj->constant.dev = dev_obj; + ep_obj->constant.ep_desc = ep_desc; + //Return the endpoint object + *ep_obj_ret = ep_obj; + + ret = ESP_OK; + return ret; + +pipe_err: + heap_caps_free(ep_obj); + return ret; +} + +static void endpoint_free(endpoint_t *ep_obj) +{ + if (ep_obj == NULL) { + return; + } + //Deallocate the EP's underlying pipe + ESP_ERROR_CHECK(hcd_pipe_free(ep_obj->constant.pipe_hdl)); + //Free the heap object + heap_caps_free(ep_obj); +} + static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, device_t **dev_obj_ret) { esp_err_t ret; @@ -135,12 +317,12 @@ static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, dev ret = ESP_ERR_NO_MEM; goto err; } - //Allocate default pipe. We set the pipe callback to NULL for now + //Allocate a pipe for EP0. We set the pipe callback to NULL for now hcd_pipe_config_t pipe_config = { .callback = NULL, .callback_arg = NULL, .context = (void *)dev_obj, - .ep_desc = NULL, //No endpoint descriptor means we're allocating a default pipe + .ep_desc = NULL, //No endpoint descriptor means we're allocating a pipe for EP0 .dev_speed = speed, .dev_addr = 0, }; @@ -190,44 +372,24 @@ static void device_free(device_t *dev_obj) heap_caps_free(dev_obj); } -// -------------------------------------------------- Event Related ---------------------------------------------------- +// ---------------------------------------------------- Callbacks ------------------------------------------------------ -static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags) -{ - if (action_flags == 0) { - return false; - } - bool call_notif_cb; - //Check if device is already on the callback list - if (!dev_obj->dynamic.flags.in_pending_list) { - //Move device form idle device list to callback device list - TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); - TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry); - dev_obj->dynamic.action_flags |= action_flags; - dev_obj->dynamic.flags.in_pending_list = 1; - call_notif_cb = true; - } else { - call_notif_cb = false; - } - return call_notif_cb; -} - -static bool default_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +static bool ep0_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) { uint32_t action_flags; device_t *dev_obj = (device_t *)user_arg; switch (pipe_event) { case HCD_PIPE_EVENT_URB_DONE: - //A control transfer completed on the default pipe. We need to dequeue it - action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE; + //A control transfer completed on EP0's pipe . We need to dequeue it + action_flags = DEV_ACTION_EP0_DEQUEUE; break; case HCD_PIPE_EVENT_ERROR_XFER: case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL: case HCD_PIPE_EVENT_ERROR_OVERFLOW: - //The default pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again - action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH | - DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | - DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR; + //EP0's pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again + action_flags = DEV_ACTION_EP0_FLUSH | + DEV_ACTION_EP0_DEQUEUE | + DEV_ACTION_EP0_CLEAR; if (in_isr) { ESP_EARLY_LOGE(USBH_TAG, "Dev %d EP 0 Error", dev_obj->constant.address); } else { @@ -235,8 +397,8 @@ static bool default_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t p } break; case HCD_PIPE_EVENT_ERROR_STALL: - //The default pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again - action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR; + //EP0's pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again + action_flags = DEV_ACTION_EP0_DEQUEUE | DEV_ACTION_EP0_CLEAR; if (in_isr) { ESP_EARLY_LOGE(USBH_TAG, "Dev %d EP 0 STALL", dev_obj->constant.address); } else { @@ -249,36 +411,103 @@ static bool default_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t p } USBH_ENTER_CRITICAL_SAFE(); - bool call_notif_cb = _dev_set_actions(dev_obj, action_flags); + bool call_proc_req_cb = _dev_set_actions(dev_obj, action_flags); USBH_EXIT_CRITICAL_SAFE(); bool yield = false; - if (call_notif_cb) { - yield = p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, in_isr, p_usbh_obj->constant.notif_cb_arg); + if (call_proc_req_cb) { + yield = p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, in_isr, p_usbh_obj->constant.proc_req_cb_arg); } return yield; } -static void handle_pipe_halt_and_flush(device_t *dev_obj) +static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +{ + endpoint_t *ep_obj = (endpoint_t *)user_arg; + return ep_obj->constant.ep_cb((usbh_ep_handle_t)ep_obj, + (usbh_ep_event_t)pipe_event, + ep_obj->constant.ep_cb_arg, + in_isr); +} + +// -------------------------------------------------- Event Related ---------------------------------------------------- + +static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags) +{ + if (action_flags == 0) { + return false; + } + bool call_proc_req_cb; + //Check if device is already on the callback list + if (!dev_obj->dynamic.flags.in_pending_list) { + //Move device form idle device list to callback device list + TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry); + dev_obj->dynamic.action_flags |= action_flags; + dev_obj->dynamic.flags.in_pending_list = 1; + call_proc_req_cb = true; + } else { + call_proc_req_cb = false; + } + return call_proc_req_cb; +} + +static inline void handle_epn_halt_flush(device_t *dev_obj) { //We need to take the mux_lock to access mux_protected members xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - //Halt then flush all non-default IN pipes - for (int i = 0; i < (EP_NUM_MAX - 1); i++) { - if (dev_obj->mux_protected.ep_in[i] != NULL) { - ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.ep_in[i], HCD_PIPE_CMD_HALT)); - ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.ep_in[i], HCD_PIPE_CMD_FLUSH)); - } - if (dev_obj->mux_protected.ep_out[i] != NULL) { - ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.ep_out[i], HCD_PIPE_CMD_HALT)); - ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.ep_out[i], HCD_PIPE_CMD_FLUSH)); + //Halt then flush all non-default EPs + for (int i = 0; i < NUM_NON_DEFAULT_EP; i++) { + if (dev_obj->mux_protected.endpoints[i] != NULL) { + ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.endpoints[i]->constant.pipe_hdl, HCD_PIPE_CMD_HALT)); + ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.endpoints[i]->constant.pipe_hdl, HCD_PIPE_CMD_FLUSH)); } } xSemaphoreGive(p_usbh_obj->constant.mux_lock); } -static bool handle_dev_free(device_t *dev_obj) +static inline void handle_ep0_flush(device_t *dev_obj) { + ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_HALT)); + ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_FLUSH)); +} + +static inline void handle_ep0_dequeue(device_t *dev_obj) +{ + //Empty URBs from EP0's pipe and call the control transfer callback + ESP_LOGD(USBH_TAG, "Default pipe device %d", dev_obj->constant.address); + int num_urbs = 0; + urb_t *urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); + while (urb != NULL) { + num_urbs++; + p_usbh_obj->constant.ctrl_xfer_cb((usb_device_handle_t)dev_obj, urb, p_usbh_obj->constant.ctrl_xfer_cb_arg); + urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); + } + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.num_ctrl_xfers_inflight -= num_urbs; + USBH_EXIT_CRITICAL(); +} + +static inline void handle_ep0_clear(device_t *dev_obj) +{ + //We allow the pipe command to fail just in case the pipe becomes invalid mid command + hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_CLEAR); +} + +static inline void handle_prop_gone_evt(device_t *dev_obj) +{ + //Flush EP0's pipe. Then propagate a USBH_EVENT_DEV_GONE event + ESP_LOGE(USBH_TAG, "Device %d gone", dev_obj->constant.address); + p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_GONE, p_usbh_obj->constant.event_cb_arg); +} + +static void handle_free_and_recover(device_t *dev_obj, bool recover_port) +{ + //Cache a copy of the port handle as we are about to free the device object + bool all_free; + hcd_port_handle_t port_hdl = dev_obj->constant.port_hdl; + ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); + //We need to take the mux_lock to access mux_protected members xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); USBH_ENTER_CRITICAL(); @@ -291,10 +520,32 @@ static bool handle_dev_free(device_t *dev_obj) } USBH_EXIT_CRITICAL(); p_usbh_obj->mux_protected.num_device--; - bool all_free = (p_usbh_obj->mux_protected.num_device == 0); + all_free = (p_usbh_obj->mux_protected.num_device == 0); xSemaphoreGive(p_usbh_obj->constant.mux_lock); device_free(dev_obj); - return all_free; + + //If all devices have been freed, propagate a USBH_EVENT_DEV_ALL_FREE event + if (all_free) { + ESP_LOGD(USBH_TAG, "Device all free"); + p_usbh_obj->constant.event_cb((usb_device_handle_t)NULL, USBH_EVENT_DEV_ALL_FREE, p_usbh_obj->constant.event_cb_arg); + } + //Check if we need to recover the device's port + if (recover_port) { + p_usbh_obj->constant.hub_req_cb(port_hdl, USBH_HUB_REQ_PORT_RECOVER, p_usbh_obj->constant.hub_req_cb_arg); + } +} + +static inline void handle_port_disable(device_t *dev_obj) +{ + //Request that the HUB disables this device's port + ESP_LOGD(USBH_TAG, "Disable device port %d", dev_obj->constant.address); + p_usbh_obj->constant.hub_req_cb(dev_obj->constant.port_hdl, USBH_HUB_REQ_PORT_DISABLE, p_usbh_obj->constant.hub_req_cb_arg); +} + +static inline void handle_prop_new_evt(device_t *dev_obj) +{ + ESP_LOGD(USBH_TAG, "New device %d", dev_obj->constant.address); + p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_NEW, p_usbh_obj->constant.event_cb_arg); } // ------------------------------------------------- USBH Functions ---------------------------------------------------- @@ -311,30 +562,25 @@ esp_err_t usbh_install(const usbh_config_t *usbh_config) SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex(); if (usbh_obj == NULL || mux_lock == NULL) { ret = ESP_ERR_NO_MEM; - goto alloc_err; + goto err; } - //Install HCD - ret = hcd_install(&usbh_config->hcd_config); - if (ret != ESP_OK) { - goto hcd_install_err; - } - //Initialize usbh object + //Initialize USBH object TAILQ_INIT(&usbh_obj->dynamic.devs_idle_tailq); TAILQ_INIT(&usbh_obj->dynamic.devs_pending_tailq); - usbh_obj->constant.notif_cb = usbh_config->notif_cb; - usbh_obj->constant.notif_cb_arg = usbh_config->notif_cb_arg; + usbh_obj->constant.proc_req_cb = usbh_config->proc_req_cb; + usbh_obj->constant.proc_req_cb_arg = usbh_config->proc_req_cb_arg; usbh_obj->constant.event_cb = usbh_config->event_cb; usbh_obj->constant.event_cb_arg = usbh_config->event_cb_arg; usbh_obj->constant.ctrl_xfer_cb = usbh_config->ctrl_xfer_cb; usbh_obj->constant.ctrl_xfer_cb_arg = usbh_config->ctrl_xfer_cb_arg; usbh_obj->constant.mux_lock = mux_lock; - //Assign usbh object pointer + //Assign USBH object pointer USBH_ENTER_CRITICAL(); if (p_usbh_obj != NULL) { USBH_EXIT_CRITICAL(); ret = ESP_ERR_INVALID_STATE; - goto assign_err; + goto err; } p_usbh_obj = usbh_obj; USBH_EXIT_CRITICAL(); @@ -342,10 +588,7 @@ esp_err_t usbh_install(const usbh_config_t *usbh_config) ret = ESP_OK; return ret; -assign_err: - ESP_ERROR_CHECK(hcd_uninstall()); -hcd_install_err: -alloc_err: +err: if (mux_lock != NULL) { vSemaphoreDelete(mux_lock); } @@ -376,8 +619,7 @@ esp_err_t usbh_uninstall(void) USBH_EXIT_CRITICAL(); xSemaphoreGive(usbh_obj->constant.mux_lock); - //Uninstall HCD, free resources - ESP_ERROR_CHECK(hcd_uninstall()); + //Free resources vSemaphoreDelete(usbh_obj->constant.mux_lock); heap_caps_free(usbh_obj); ret = ESP_OK; @@ -392,7 +634,7 @@ esp_err_t usbh_process(void) { USBH_ENTER_CRITICAL(); USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE); - //Keep clearing devices with events + //Keep processing until all device's with pending events have been handled while (!TAILQ_EMPTY(&p_usbh_obj->dynamic.devs_pending_tailq)){ //Move the device back into the idle device list, device_t *dev_obj = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_pending_tailq); @@ -409,37 +651,22 @@ esp_err_t usbh_process(void) USBH_EXIT_CRITICAL(); ESP_LOGD(USBH_TAG, "Processing actions 0x%"PRIx32"", action_flags); //Sanity check. If the device is being freed, there must not be any other action flags set - assert(!(action_flags & DEV_FLAG_ACTION_FREE) || action_flags == DEV_FLAG_ACTION_FREE); + assert(!(action_flags & DEV_ACTION_FREE) || action_flags == DEV_ACTION_FREE); - if (action_flags & DEV_FLAG_ACTION_PIPE_HALT_AND_FLUSH) { - handle_pipe_halt_and_flush(dev_obj); + if (action_flags & DEV_ACTION_EPn_HALT_FLUSH) { + handle_epn_halt_flush(dev_obj); } - if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH) { - ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_HALT)); - ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_FLUSH)); + if (action_flags & DEV_ACTION_EP0_FLUSH) { + handle_ep0_flush(dev_obj); } - if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE) { - //Empty URBs from default pipe and trigger a control transfer callback - ESP_LOGD(USBH_TAG, "Default pipe device %d", dev_obj->constant.address); - int num_urbs = 0; - urb_t *urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); - while (urb != NULL) { - num_urbs++; - p_usbh_obj->constant.ctrl_xfer_cb((usb_device_handle_t)dev_obj, urb, p_usbh_obj->constant.ctrl_xfer_cb_arg); - urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); - } - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.num_ctrl_xfers_inflight -= num_urbs; - USBH_EXIT_CRITICAL(); + if (action_flags & DEV_ACTION_EP0_DEQUEUE) { + handle_ep0_dequeue(dev_obj); } - if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR) { - //We allow the pipe command to fail just in case the pipe becomes invalid mid command - hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_CLEAR); + if (action_flags & DEV_ACTION_EP0_CLEAR) { + handle_ep0_clear(dev_obj); } - if (action_flags & DEV_FLAG_ACTION_SEND_GONE_EVENT) { - //Flush the default pipe. Then do an event gone - ESP_LOGE(USBH_TAG, "Device %d gone", dev_obj->constant.address); - p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_GONE, p_usbh_obj->constant.event_cb_arg); + if (action_flags & DEV_ACTION_PROP_GONE_EVT) { + handle_prop_gone_evt(dev_obj); } /* Note: We make these action flags mutually exclusive in case they happen in rapid succession. They are handled @@ -448,25 +675,14 @@ esp_err_t usbh_process(void) - New device event is requested followed immediately by a disconnection - Port disable requested followed immediately by a disconnection */ - if (action_flags & (DEV_FLAG_ACTION_FREE | DEV_FLAG_ACTION_FREE_AND_RECOVER)) { - //Cache a copy of the port handle as we are about to free the device object - hcd_port_handle_t port_hdl = dev_obj->constant.port_hdl; - ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); - if (handle_dev_free(dev_obj)) { - ESP_LOGD(USBH_TAG, "Device all free"); - p_usbh_obj->constant.event_cb((usb_device_handle_t)NULL, USBH_EVENT_DEV_ALL_FREE, p_usbh_obj->constant.event_cb_arg); - } - //Check if we need to recover the device's port - if (action_flags & DEV_FLAG_ACTION_FREE_AND_RECOVER) { - p_usbh_obj->constant.hub_req_cb(port_hdl, USBH_HUB_REQ_PORT_RECOVER, p_usbh_obj->constant.hub_req_cb_arg); - } - } else if (action_flags & DEV_FLAG_ACTION_PORT_DISABLE) { - //Request that the HUB disables this device's port - ESP_LOGD(USBH_TAG, "Disable device port %d", dev_obj->constant.address); - p_usbh_obj->constant.hub_req_cb(dev_obj->constant.port_hdl, USBH_HUB_REQ_PORT_DISABLE, p_usbh_obj->constant.hub_req_cb_arg); - } else if (action_flags & DEV_FLAG_ACTION_SEND_NEW) { - ESP_LOGD(USBH_TAG, "New device %d", dev_obj->constant.address); - p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_NEW, p_usbh_obj->constant.event_cb_arg); + if (action_flags & DEV_ACTION_FREE_AND_RECOVER) { + handle_free_and_recover(dev_obj, true); + } else if (action_flags & DEV_ACTION_FREE) { + handle_free_and_recover(dev_obj, false); + } else if (action_flags & DEV_ACTION_PORT_DISABLE) { + handle_port_disable(dev_obj); + } else if (action_flags & DEV_ACTION_PROP_NEW) { + handle_prop_new_evt(dev_obj); } USBH_ENTER_CRITICAL(); /* --------------------------------------------------------------------- @@ -567,7 +783,7 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) USBH_ENTER_CRITICAL(); dev_obj->dynamic.ref_count--; - bool call_notif_cb = false; + bool call_proc_req_cb = false; if (dev_obj->dynamic.ref_count == 0) { //Sanity check. assert(dev_obj->dynamic.num_ctrl_xfers_inflight == 0); //There cannot be any control transfer inflight @@ -575,18 +791,18 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) if (dev_obj->dynamic.flags.is_gone) { //Device is already gone so it's port is already disabled. Trigger the USBH process to free the device dev_obj->dynamic.flags.waiting_free = 1; - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE_AND_RECOVER); //Port error occurred so we need to recover it + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); //Port error occurred so we need to recover it } else if (dev_obj->dynamic.flags.waiting_close) { //Device is still connected but is no longer needed. Trigger the USBH process to request device's port be disabled dev_obj->dynamic.flags.waiting_port_disable = 1; - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_PORT_DISABLE); + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PORT_DISABLE); } //Else, there's nothing to do. Leave the device allocated } USBH_EXIT_CRITICAL(); - if (call_notif_cb) { - p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); } return ESP_OK; } @@ -599,7 +815,7 @@ esp_err_t usbh_dev_mark_all_free(void) disable it immediately. Note: We manually traverse the list because we need to add/remove items while traversing */ - bool call_notif_cb = false; + bool call_proc_req_cb = false; bool wait_for_free = false; for (int i = 0; i < 2; i++) { device_t *dev_obj_cur; @@ -617,7 +833,7 @@ esp_err_t usbh_dev_mark_all_free(void) if (dev_obj_cur->dynamic.ref_count == 0 && !dev_obj_cur->dynamic.flags.is_gone) { //Device is not opened as is not gone, so we can disable it now dev_obj_cur->dynamic.flags.waiting_port_disable = 1; - call_notif_cb |= _dev_set_actions(dev_obj_cur, DEV_FLAG_ACTION_PORT_DISABLE); + call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_PORT_DISABLE); } else { //Device is still opened. Just mark it as waiting to be closed dev_obj_cur->dynamic.flags.waiting_close = 1; @@ -628,8 +844,8 @@ esp_err_t usbh_dev_mark_all_free(void) } USBH_EXIT_CRITICAL(); - if (call_notif_cb) { - p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); } return (wait_for_free) ? ESP_ERR_NOT_FINISHED : ESP_OK; } @@ -716,6 +932,9 @@ esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb) { USBH_CHECK(dev_hdl != NULL && urb != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; + USBH_CHECK(urb_check_args(urb), ESP_ERR_INVALID_ARG); + bool xfer_is_in = ((usb_setup_packet_t *)urb->transfer.data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; + USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, dev_obj->constant.desc->bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG); USBH_ENTER_CRITICAL(); USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); @@ -744,141 +963,160 @@ hcd_err: // ----------------------------------------------- Interface Functions ------------------------------------------------- -esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, hcd_pipe_handle_t *pipe_hdl_ret) +esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, usbh_ep_handle_t *ep_hdl_ret) { - USBH_CHECK(dev_hdl != NULL && ep_config != NULL && pipe_hdl_ret != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; + USBH_CHECK(dev_hdl != NULL && ep_config != NULL && ep_hdl_ret != NULL, ESP_ERR_INVALID_ARG); + uint8_t bEndpointAddress = ep_config->bEndpointAddress; + USBH_CHECK(check_ep_addr(bEndpointAddress), ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + endpoint_t *ep_obj; - //Allocate HCD pipe - hcd_pipe_config_t pipe_config = { - .callback = ep_config->pipe_cb, - .callback_arg = ep_config->pipe_cb_arg, - .context = ep_config->context, - .ep_desc = ep_config->ep_desc, - .dev_speed = dev_obj->constant.speed, - .dev_addr = dev_obj->constant.address, - }; - hcd_pipe_handle_t pipe_hdl; - ret = hcd_pipe_alloc(dev_obj->constant.port_hdl, &pipe_config, &pipe_hdl); - if (ret != ESP_OK) { - goto pipe_alloc_err; + //Find the endpoint descriptor from the device's current configuration descriptor + const usb_ep_desc_t *ep_desc = usb_parse_endpoint_descriptor_by_address(dev_obj->constant.config_desc, ep_config->bInterfaceNumber, ep_config->bAlternateSetting, ep_config->bEndpointAddress, NULL); + if (ep_desc == NULL) { + return ESP_ERR_NOT_FOUND; + } + //Allocate the endpoint object + ret = endpoint_alloc(dev_obj, ep_desc, ep_config, &ep_obj); + if (ret != ESP_OK) { + goto alloc_err; } - - bool is_in = ep_config->ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; - uint8_t addr = ep_config->ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK; - bool assigned = false; //We need to take the mux_lock to access mux_protected members xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); USBH_ENTER_CRITICAL(); - //Check the device's state before we assign the pipes to the endpoint + //Check the device's state before we assign the a pipe to the allocated endpoint if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) { USBH_EXIT_CRITICAL(); ret = ESP_ERR_INVALID_STATE; - goto assign_err; + goto dev_state_err; } USBH_EXIT_CRITICAL(); - //Assign the allocated pipe to the correct endpoint - if (is_in && dev_obj->mux_protected.ep_in[addr - 1] == NULL) { //Is an IN EP - dev_obj->mux_protected.ep_in[addr - 1] = pipe_hdl; - assigned = true; - } else if (!is_in && dev_obj->mux_protected.ep_out[addr - 1] == NULL) { //Is an OUT EP - dev_obj->mux_protected.ep_out[addr - 1] = pipe_hdl; - assigned = true; - } - xSemaphoreGive(p_usbh_obj->constant.mux_lock); - - if (!assigned) { + //Check if the endpoint has already been allocated + if (get_ep_from_addr(dev_obj, bEndpointAddress) == NULL) { + set_ep_from_addr(dev_obj, bEndpointAddress, ep_obj); + //Write back the endpoint handle + *ep_hdl_ret = (usbh_ep_handle_t)ep_obj; + ret = ESP_OK; + } else { + //Endpoint is already allocated ret = ESP_ERR_INVALID_STATE; - goto assign_err; - } - //Write back output - *pipe_hdl_ret = pipe_hdl; - ret = ESP_OK; - return ret; - -assign_err: - ESP_ERROR_CHECK(hcd_pipe_free(pipe_hdl)); -pipe_alloc_err: - return ret; -} - -esp_err_t usbh_ep_free(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - - esp_err_t ret; - bool is_in = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; - uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK; - hcd_pipe_handle_t pipe_hdl; - - //We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - //Check if the EP was previously allocated. If so, set it to NULL - if (is_in) { - if (dev_obj->mux_protected.ep_in[addr - 1] != NULL) { - pipe_hdl = dev_obj->mux_protected.ep_in[addr - 1]; - dev_obj->mux_protected.ep_in[addr - 1] = NULL; - ret = ESP_OK; - } else { - ret = ESP_ERR_INVALID_STATE; - } - } else { - //EP must have been previously allocated - if (dev_obj->mux_protected.ep_out[addr - 1] != NULL) { - pipe_hdl = dev_obj->mux_protected.ep_out[addr - 1]; - dev_obj->mux_protected.ep_out[addr - 1] = NULL; - ret = ESP_OK; - } else { - ret = ESP_ERR_INVALID_STATE; - } } +dev_state_err: xSemaphoreGive(p_usbh_obj->constant.mux_lock); - if (ret == ESP_OK) { - ESP_ERROR_CHECK(hcd_pipe_free(pipe_hdl)); + //If the endpoint was not assigned, free it + if (ret != ESP_OK) { + endpoint_free(ep_obj); } +alloc_err: return ret; } -esp_err_t usbh_ep_get_context(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, void **context_ret) +esp_err_t usbh_ep_free(usbh_ep_handle_t ep_hdl) { - bool is_in = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; - uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK; - USBH_CHECK(dev_hdl != NULL && - addr >= EP_NUM_MIN && //Control endpoints are owned by the USBH - addr <= EP_NUM_MAX && - context_ret != NULL, - ESP_ERR_INVALID_ARG); + USBH_CHECK(ep_hdl != NULL, ESP_ERR_INVALID_ARG); esp_err_t ret; - device_t *dev_obj = (device_t *)dev_hdl; - hcd_pipe_handle_t pipe_hdl; + endpoint_t *ep_obj = (endpoint_t *)ep_hdl; + device_t *dev_obj = (device_t *)ep_obj->constant.dev; + uint8_t bEndpointAddress = ep_obj->constant.ep_desc->bEndpointAddress; - //We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - //Get the endpoint's corresponding pipe - if (is_in) { - pipe_hdl = dev_obj->mux_protected.ep_in[addr - 1]; - } else { - pipe_hdl = dev_obj->mux_protected.ep_out[addr - 1]; - } - //Check if the EP was allocated to begin with - if (pipe_hdl == NULL) { - ret = ESP_ERR_NOT_FOUND; + //Todo: Check that the EP's underlying pipe is halted before allowing the EP to be freed (IDF-7273) + //Check that the the EP's underlying pipe has no more in-flight URBs + if (hcd_pipe_get_num_urbs(ep_obj->constant.pipe_hdl) != 0) { + ret = ESP_ERR_INVALID_STATE; goto exit; } - //Return the context of the pipe - void *context = hcd_pipe_get_context(pipe_hdl); - *context_ret = context; - ret = ESP_OK; -exit: + + //We need to take the mux_lock to access mux_protected members + xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); + //Check if the endpoint was allocated on this device + if (ep_obj == get_ep_from_addr(dev_obj, bEndpointAddress)) { + //Clear the endpoint from the device's endpoint object list + set_ep_from_addr(dev_obj, bEndpointAddress, NULL); + ret = ESP_OK; + } else { + ret = ESP_ERR_NOT_FOUND; + } xSemaphoreGive(p_usbh_obj->constant.mux_lock); + + //Finally, we free the endpoint object + if (ret == ESP_OK) { + endpoint_free(ep_obj); + } +exit: return ret; } +esp_err_t usbh_ep_get_handle(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, usbh_ep_handle_t *ep_hdl_ret) +{ + USBH_CHECK(dev_hdl != NULL && ep_hdl_ret != NULL, ESP_ERR_INVALID_ARG); + USBH_CHECK(check_ep_addr(bEndpointAddress), ESP_ERR_INVALID_ARG); + + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + endpoint_t *ep_obj; + + //We need to take the mux_lock to access mux_protected members + xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); + ep_obj = get_ep_from_addr(dev_obj, bEndpointAddress); + xSemaphoreGive(p_usbh_obj->constant.mux_lock); + if (ep_obj) { + *ep_hdl_ret = (usbh_ep_handle_t)ep_obj; + ret = ESP_OK; + } else { + ret = ESP_ERR_NOT_FOUND; + } + + return ret; +} + +esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb) +{ + USBH_CHECK(ep_hdl != NULL && urb != NULL, ESP_ERR_INVALID_ARG); + /* + Todo: Here would be a good place to check that the URB is filled correctly according to the USB 2.0 specification. + This is currently done by the USB host library layer, but is more appropriate here. + */ + endpoint_t *ep_obj = (endpoint_t *)ep_hdl; + + //Check that the EP's underlying pipe is in the active state before submitting the URB + if (hcd_pipe_get_state(ep_obj->constant.pipe_hdl) != HCD_PIPE_STATE_ACTIVE) { + return ESP_ERR_INVALID_STATE; + } + //Enqueue the URB to the EP's underlying pipe + return hcd_urb_enqueue(ep_obj->constant.pipe_hdl, urb); +} + +esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret) +{ + USBH_CHECK(ep_hdl != NULL && urb_ret != NULL, ESP_ERR_INVALID_ARG); + + endpoint_t *ep_obj = (endpoint_t *)ep_hdl; + //Enqueue the URB to the EP's underlying pipe + *urb_ret = hcd_urb_dequeue(ep_obj->constant.pipe_hdl); + return ESP_OK; +} + +esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command) +{ + USBH_CHECK(ep_hdl != NULL, ESP_ERR_INVALID_ARG); + + endpoint_t *ep_obj = (endpoint_t *)ep_hdl; + //Send the command to the EP's underlying pipe + return hcd_pipe_command(ep_obj->constant.pipe_hdl, (hcd_pipe_cmd_t)command); +} + +void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl) +{ + assert(ep_hdl); + endpoint_t *ep_obj = (endpoint_t *)ep_hdl; + return hcd_pipe_get_context(ep_obj->constant.pipe_hdl); +} + // -------------------------------------------------- Hub Functions ---------------------------------------------------- // ------------------- Device Related ---------------------- @@ -921,7 +1159,7 @@ esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_ USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - bool call_notif_cb; + bool call_proc_req_cb; switch (hub_event) { case USBH_HUB_EVENT_PORT_ERROR: { USBH_ENTER_CRITICAL(); @@ -929,13 +1167,14 @@ esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_ //Check if the device can be freed now if (dev_obj->dynamic.ref_count == 0) { dev_obj->dynamic.flags.waiting_free = 1; - //Device is already waiting free so none of it's pipes will be in use. Can free immediately. - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE_AND_RECOVER); //Port error occurred so we need to recover it + //Device is already waiting free so none of it's EP's will be in use. Can free immediately. + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); //Port error occurred so we need to recover it } else { - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_PIPE_HALT_AND_FLUSH | - DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH | - DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | - DEV_FLAG_ACTION_SEND_GONE_EVENT); + call_proc_req_cb = _dev_set_actions(dev_obj, + DEV_ACTION_EPn_HALT_FLUSH | + DEV_ACTION_EP0_FLUSH | + DEV_ACTION_EP0_DEQUEUE | + DEV_ACTION_PROP_GONE_EVT); } USBH_EXIT_CRITICAL(); break; @@ -944,7 +1183,7 @@ esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_ USBH_ENTER_CRITICAL(); assert(dev_obj->dynamic.ref_count == 0); //At this stage, the device should have been closed by all users dev_obj->dynamic.flags.waiting_free = 1; - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); USBH_EXIT_CRITICAL(); break; } @@ -952,8 +1191,8 @@ esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_ return ESP_ERR_INVALID_ARG; } - if (call_notif_cb) { - p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); } return ESP_OK; } @@ -1040,16 +1279,16 @@ esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl) dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED; //Add the device to list of devices, then trigger a device event TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); //Add it to the idle device list first - bool call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_SEND_NEW); + bool call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW); USBH_EXIT_CRITICAL(); p_usbh_obj->mux_protected.num_device++; xSemaphoreGive(p_usbh_obj->constant.mux_lock); - //Update the default pipe callback - ESP_ERROR_CHECK(hcd_pipe_update_callback(dev_obj->constant.default_pipe, default_pipe_callback, (void *)dev_obj)); - //Call the notification callback - if (call_notif_cb) { - p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + //Update the EP0's underlying pipe's callback + ESP_ERROR_CHECK(hcd_pipe_update_callback(dev_obj->constant.default_pipe, ep0_pipe_callback, (void *)dev_obj)); + //Call the processing request callback + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); } return ESP_OK; }