USB Host Driver (USBH)
Introduction
The USB Host Driver (henceforth referred to as USBH) provides a USB Host software interface which abstracts USB devices. The USBH interface provides APIs to...
manage the device pool (i.e., adding and removing devices)
address and configure a device (i.e., setting device and configuration descriptors)
submit transfers to a particular endpoint of a device
Requirements
USB Specification Requirements
Chapter 10 of the USB 2.0 specification outlines some requirements of the USBH (referred to as USBD in the specification). The design of the USBH takes into consideration these requirements from the specification.
Default pipe of a device is owned by the USBH
All other pipes are owned and managed by clients of the USBH
USBH interface must provide the following services
Configuration and command mechanism
Transfer services via both command and pipe mechanisms
Event notification
Status reporting and error recovery
Host Stack Requirements
In addition to the USB 2.0 specification requirements, the USBH also takes into consideration the requirements set for the overall Host Stack (see USB Host Maintainers Notes (Design Guidelines)):
USBH must not instantiate any tasks/threads
USBH must be event driven, providing event callbacks and an event processing function
Implementation & Usage
Events & Processing
The USBH is completely event driven and all event handling is done via then usbh_process()
function. The usbh_config_t.proc_req_cb
callback provided on USBH installation will be called when processing is required. Typically, usbh_process()
will be called from a dedicated thread/task.
The USBH exposes the following event callbacks:
usbh_event_cb_t
used to indicate various events regarding a particular device and control transfers to EP0. This callback is called from the context ofusbh_process()
usbh_ep_cb_t
used to indicate events for all other endpoints. This callback is not called from theusbh_process()
context (currently called from an HCD interrupt context).
Device Pool
The USBH keeps track of all currently connected devices by internally maintaining a device pool (simply a linked list) where each device is represented by a device object.
The USB 2.0 specification "assumes a specialized client of the USBD, called a hub driver, that acts as a clearinghouse for the addition and removal of devices from a particular hub". As a result, the USBH is completely reliant on an external client(s) (typically a Hub Driver) to inform the USBH of device addition and removal. The USBH provides the following APIs for device addition and removal:
usbh_devs_add()
which will allocate a new device object and add it to the device pool. The newly added device will be unenumerated, meaning the device object will...be assigned to address 0
have no device and configuration descriptor
usbh_devs_remove()
which will indicate to the USBH that a device has been removed (such as due to a disconnection or a port error).If the device is not currently opened (i.e., used by one or more clients), the USBH will free the underlying device object immediately.
If the device is currently opened, a
USBH_EVENT_DEV_GONE
event will be propagated and the device will be flagged for removal. The last client to close the device will free the device object.When a device object is freed, a
USBH_EVENT_DEV_FREE
event will be propagated. This event is used to indicate that the device's upstream port can be recycled.
Device Enumeration
Newly added devices will need to be enumerated. The USBH provides various usbh_dev_set_...()
functions to enumerate the device, such as assigning the device's address and setting device/configuration/string descriptors. Given that USBH devices can be shared by multiple clients, attempting to enumerate a device while another client has opened the device can cause issues.
Thus, before calling any usbh_dev_set_...()
enumeration function, a device must be locked for enumeration by calling usbh_dev_enum_lock()
. This prevents the device from being opened by any other client but the enumerating client.
After enumeration is complete, the enumerating client can call usbh_devs_trigger_new_dev_event()
to propagate a USBH_EVENT_NEW_DEV
event.
Device Usage
Clients that want to use a device must open the device by calling usbh_devs_open()
and providing the device's address. The device's address can either be obtained from a USBH_EVENT_NEW_DEV
event or by calling usbh_devs_addr_list_fill()
.
Opening a device will do the following:
Return a
usb_device_handle_t
device handle which can be used to refer to the device in various USBH functionsIncrement the device's internal
open_count
which indicates how many clients have opened the device. As long asopen_count > 0
, the underlying device object will not be freed, thus guaranteeing that the device handle refers to a valid device object.
Once a client no longer needs to use a device, the client should call usbh_devs_close()
thus invalidating the device handle.
备注
Most device related APIs accept usb_device_handle_t
as an argument, which means that the calling client must have previously opened the device to obtain the device handle beforehand. This design choice is intentional in order to enforce an "open before use" pattern.
However, a limited set of APIs (e.g., usbh_devs_remove()
) refer to devices using a Unique Identifier (uid
) which is assigned on device addition (see usbh_devs_add()
). The use of uid
in these functions allows their callers to refer to a device without needing to open it due to the lack of a usb_device_handle_t
.
As a result, it is possible that a caller of a uid
function may refer to a device that has already been freed. Thus, callers should account for a fact that these functions may return ESP_ERR_NOT_FOUND
.
Endpoints & Transfers
USBH supports transfer to default (i.e., EP0) and non-default endpoints.
For non-default endpoints:
A client must first allocate the endpoint by calling
usbh_ep_alloc()
which assigns ausbh_ep_cb_t
callback and returns ausbh_ep_handle_t
endpoint handle so that the endpoint can be referred to.A client can then enqueue a
urb_t
transfer to the endpoint by callingusbh_ep_enqueue_urb()
.The
usbh_ep_cb_t
callback is called to indicate transfer completionThe client must then dequeue the transfer using
usbh_ep_dequeue_urb()
Default endpoints are owned and managed by the USBH, thus API for control transfers are different:
EP0 is always allocated for each device, thus clients do no need to allocate EP0 or provide an endpoint callback.
Clients call should call
usbh_dev_submit_ctrl_urb()
to submit a control transfer to a device's EP0.A
USBH_EVENT_CTRL_XFER
event will be propagated when the transfer is completeControl transfers do not need to be dequeued