USB Libraries Help > USB Common Driver Interface > Common Interface > USB Driver Host Mode Operation
MPLAB Harmony USB Stack
USB Driver Host Mode Operation

The USB Driver operates or can operate in the Host mode when it is initialized for Host mode or Dual Role operation. When operating in Host mode, the USB Driver is also referred to as the Host Controller Driver (HCD). In Dual Role mode, the USB Driver will switch to Host mode when the USB Driver Host Root Hub Operation Enable function is called. 

The USB Driver Client must perform these steps to operate the USB Driver in Host mode.

  1. Open the USB Driver to obtain the driver handle.
  2. Set the event handler.
  3. Call the Root Hub Control function to obtain the speed of the root hub, the number of ports that the root hub supports, and the maximum current that the root hub VBUS can supply.
  4. Calls the Root Hub Initialize function with an identifier parameter. This identifier parameter allows the Host Stack to uniquely identify the root hub when where there are multiple root hubs.
  5. The Driver Client will then enable the root hub operation and will wait until the root hub operation is enabled.
  6. The Driver Client can now call the USB Driver Host mode functions.

The following sections explain Steps 2 through 6 in more detail.

Handling Host Mode Driver Events

Currently, the HCD does not provide any events to the client. The client can optionally register an event handler through the eventHandlerSet function pointer in the DRV_USB_HOST_INTERFACE structure. Future releases of the USB Driver may contain features that provide events to the Driver Client. Please refer to the following Root Hub Operation section for details on how the driver indicates device attach and detach to the client.

Root Hub Operation

A key feature of the HCD is the Root Hub Driver. The Root Hub Driver emulates hub operation in USB Driver software and provides a hub like interface to the USB Host Layer. The USB Host Layer treats the root hub like an external hub. This simplifies the implementation of USB Host Layer while supporting multiple devices through a hub. In that, the USB Host layer does not have to treat a device connected directly to the USB peripheral differently than a device connected to an external hub. The following code example shows how the USB Host Layer calls the root hub function to obtain information about the root hub.

/* This code example shows how the USB Host Layer calls the root hub functions to
 * obtain information about the root. The USB Host Layer first opens the HCD and
 * then accesses the root hub functions through the rootHubInterface member of
 * hcdInterface. rootHubInterface is of the type DRV_USB_ROOT_HUB_INTERFACE and
 * the hcdInterface is of the type of DRV_USB_HOST_INTERFACE. */

/* The code example shows how the Host Layer gets to know the root hub operation
 * speed, number of root hub ports and the maximum amount of current that the
 * root can supply. These function can be called only after HCD was opened and a
 * valid driver handle obtained. */

case USB_HOST_BUS_STATE_ENABLING:

    /* The bus is being enabled. Try opening the HCD */
    busObj->hcdHandle = busObj->hcdInterface->open(busObj->hcdIndex, DRV_IO_INTENT_EXCLUSIVE |
            DRV_IO_INTENT_NONBLOCKING | DRV_IO_INTENT_READWRITE );

    /* Validate the Open function status */
    if (DRV_HANDLE_INVALID == busObj->hcdHandle )
    {
        /* The driver may not open the first time. This is okay. We
         * should try opening it again. The state of bus is not
         * changed. */
    }
    else
    {
        /* Update the bus root hub information with the
         * details of the controller. Get the bus speed, number of
         * ports, the maximum current that the HCD can supply,
         * pointer to the root hub port functions. */

        SYS_DEBUG_PRINT(SYS_ERROR_INFO,
             "\r\nUSB Host Layer: Bus %d Root Hub Driver Opened.",hcCount);

        busObj->rootHubInfo.speed =
             busObj->hcdInterface->rootHubInterface.rootHubSpeedGet(busObj->hcdHandle);

        busObj->rootHubInfo.ports =
             busObj->hcdInterface->rootHubInterface.rootHubPortNumbersGet(busObj->hcdHandle);

        busObj->rootHubInfo.power =
             busObj->hcdInterface->rootHubInterface.rootHubMaxCurrentGet(busObj->hcdHandle);

        busObj->rootHubInfo.rootHubPortInterface =
             busObj->hcdInterface->rootHubInterface.rootHubPortInterface;

The USB Host Layer must initialize and enable the operation of the root hub. While initializing the Root Hub Driver, the Host layer will assign a unique identifier to the root hub. The root hub will return this value as the parent identifier while calling the USB_HOST_DeviceEnumerate function. The USB Host Layer must then enable the operation of the root hub driver. This will cause the root hub driver to detect device attach and detach. The following code example shows how the USB Host Layer initializes and enables the root hub driver

/* The following code example show how the USB Host Layer initializes the root
 * hub and then enables the root hub operation. The
 * rootHubDevice->deviceIdentifier is a unique identifier that allows the USB
 * Host layer to identify this root hub. It is returned by the root hub driver
 * in the USB_HOST_DeviceEnumerate() function as the parent identifier when the
 * device is connected to the root hub. */

/* The hcdHandle is the driver handle. The hcdInterface pointer is of the type
 * DRV_USB_HOST_INTERFACE and points to the HCD interface. */

busObj->hcdInterface->rootHubInterface.rootHubInitialize( busObj->hcdHandle ,
                                                          rootHubDevice->deviceIdentifier );
busObj->hcdInterface->rootHubInterface.rootHubOperationEnable( busObj->hcdHandle , true );

When a device is attached, the Root Hub Driver will implement the required settling attach settling delay and will then call the USB Host Layer’s USB_HOST_DeviceEnumerate function to enumerate the device. While calling this function, the root hub driver will provide the identifier that was provided to it in its initialize function. The USB_HOST_DeviceEnumerate function will return an identifier which uniquely identifies the attached device. The root hub driver uses this value to identify the device to the Host when the USB_HOST_DeviceDenumerate function is called on device detach. The following code example shows how the Root Hub driver calls the USB_HOST_DeviceEnumerate and the USB_HOST_DeviceDenumerate functions.

/* The following code shows how the root hub driver calls the
 * USB_HOST_DeviceEnumerate() function in the device attach interrupt. As seen
 * here, the root hub returns the identifier that the USB Host Layer assigned to
 * it the rootHubInitialize function call. The pUSBDrvObj->usbHostDeviceInfo
 * variable contains this identifier. */

if(PLIB_USB_InterruptFlagGet(usbID, USB_INT_ATTACH))
{
    /* We can treat this as a valid attach. We then clear the
     * detach flag and enable the detach interrupt. We enable
     * the Transaction interrupt */

    PLIB_USB_InterruptFlagClear(usbID, USB_INT_HOST_DETACH);
    PLIB_USB_InterruptEnable(usbID, USB_INT_HOST_DETACH);
    PLIB_USB_InterruptEnable(usbID, USB_INT_TOKEN_DONE);

    /* Ask the Host layer to enumerate this device. While calling
     * this function, the UHD of the parent device which is the
     * root hub in this case.
     * */
    pUSBDrvObj->attachedDeviceObjHandle = USB_HOST_DeviceEnumerate
                                          (pUSBDrvObj->usbHostDeviceInfo, 0);
}

/* The following code example shows how the root hub driver calls the
 * USB_HOST_DeviceDenumerate() function in the device detach interrupt. Note how
 * the attachedDeviceObjHandle that was assigned at the time of device
 * enumeration is returned to the Host Layer to let the Host know which device
 * is being detached. */

if((usbInterrupts & USB_INT_HOST_DETACH) && (enabledUSBInterrupts & USB_INT_HOST_DETACH))
{
    /* Perform other detach related handling */

    /* Ask the Host Layer to de-enumerate this device. */
    USB_HOST_DeviceDenumerate (pUSBDrvObj->attachedDeviceObjHandle);

    /* Disable the LS Direct Connect. It may have been enabled if the last
     attach was for a Low-Speed device. */
    PLIB_USB_EP0LSDirectConnectDisable(pUSBDrvObj->usbID);

    /* Continue to perform detach handling */
}
Root Hub Port Operation

The HCD Root Hub Driver exposes a set of port related functions that allow the USB Host Layer to control the port. The most commonly used functions are the function to reset the port and get the port speed. In this case, this is the speed of the attached device. The following code example shows how the USB Host Layer calls the hubPortReset, hubPortResetIsComplete and hubPortSpeedGet port functions.

/* The following code shows an example of how the Host Layer called the
 * hubPortReset function to reset the port to which the device is connected.
 * The code proceeds with the port reset if no device on the bus is in an
 * enumeration state. It will then call the hubPortReset function of the parent
 * hub of the device. The parent hub, hubInterface member of deviceObj points to
 * this driver, can be the root hub or an external hub */

if(!busObj->deviceIsEnumerating)
{
    /* Remember which device is enumerating */
    busObj->enumeratingDeviceIdentifier = deviceObj->deviceIdentifier;

    /* Grab the flag */
    busObj->deviceIsEnumerating = true;

    /* Reset the device */
    deviceObj->hubInterface->hubPortReset( deviceObj->hubHandle, deviceObj->devicePort );
}

/* The following code example shows how the Host checks if the port reset
 * operation has completed. If the reset operation has completed, the speed of
 * the attached device can be obtained. The reset settling delay can then be
 * started. */

 case USB_HOST_DEVICE_STATE_WAITING_FOR_RESET_COMPLETE:

    /* Check if the reset has completed */
    if(deviceObj->hubInterface->hubPortResetIsComplete
                 ( deviceObj->hubHandle ,deviceObj->devicePort ))
    {
        /* The reset has completed. We can also obtain the speed of the
         * device. We give a reset recovery delay to the device */

        deviceObj->speed = deviceObj->hubInterface->hubPortSpeedGet
                           (deviceObj->hubHandle, deviceObj->devicePort);

        deviceObj->deviceState = USB_HOST_DEVICE_STATE_START_RESET_SETTLING_DELAY;
    }
Opening and Closing a Pipe

The HCD client can open a pipe to the device after resetting the device. The USB Host Layer calls the hostPipeSetup function in the DRV_USB_HOST_INTERFACE structure to open a pipe. The USB Host Layer must open a pipe to communicate to a specific endpoint on a target device. While opening the pipe, the USB Host Layer must specify parameters which specify the address of the target device, the type of the transfer that the pipe must support and the speed of the pipe. If the device is connected to a hub, the address of the hub must be specified. The HCD Pipe Setup function is not interrupt-safe. It should not be called in any event handler that executes in an interrupt context. 

The Pipe Setup function returns a valid pipe handle if the pipe was opened successfully. Pipe creation may fail if the target device was disconnected or if there are insufficient resources to open the pipe. The pipe handle is then used along with the hostIRPSubmit function to transfer data between the Host and the device. The following code shows example usage of a Pipe Open function.

/* The following code example shows how the Host Layer uses the hostPipeSetup
 * function to open a control pipe to the attached device. Most of the
 * parameters that are passed to this function become known when the device is
 * attached. The pipe handle is checked for validity after the hostPipeSetup
 * function call. */

if(busObj->timerExpired)
{
    busObj->busOperationsTimerHandle = SYS_TMR_HANDLE_INVALID;
    /* Settling delay has completed. Now we can open default address
     * pipe and and get the configuration descriptor */

    SYS_DEBUG_PRINT(SYS_ERROR_INFO,
                    "\r\nUSB Host Layer: Bus %d Device Reset Complete.", busIndex);

    deviceObj->controlPipeHandle =
               deviceObj->hcdInterface->hostPipeSetup( deviceObj->hcdHandle,
               USB_HOST_DEFAULT_ADDRESS , 0 /* Endpoint */,
               deviceObj->hubAddress /* Address of the hub */,
               deviceObj->devicePort /* Address of the port */,
               USB_TRANSFER_TYPE_CONTROL, /* Type of pipe to open */
               0 /* bInterval */, 8 /* Endpoint Size */, deviceObj->speed );

    if(DRV_USB_HOST_PIPE_HANDLE_INVALID == deviceObj->controlPipeHandle)
    {
        /* We need a pipe else we cannot proceed */
        SYS_DEBUG_PRINT(SYS_ERROR_DEBUG,
        "\r\nUSB Host Layer: Bus %d Could not open control pipe. Device not supported.", busIndex);
    }
}

An open pipe consumes computational and memory resources and must therefore must be closed if it will not be used. This is especially true of pipes to a device that is detached. The Host Layer calls the hostPipeClose function in the DRV_USB_HOST_INTERFACE structure to close the pipe. The pipe to be closed is specified by the pipe handle. The Pipe Close function can be called from an event handler. It is interrupt safe. Closing a pipe will cancel all pending transfers on that pipe. The IRP callback for such canceled transfers will be called with the status USB_HOST_IRP_STATUS_ABORTED. The following code example shows an example of closing the pipe.

/* The following code example shows an example of how the Host Layer calls the
 * hostPipeClose function to close an open pipe. Pipe should be closed if it
 * will not used. An open pipe consumes memory resources. In this example, the
 * Host Layer closes the pipe if it was not able successfully submit an IRP to
 * this pipe. */

/* Submit the IRP */
if(USB_ERROR_NONE != deviceObj->hcdInterface->hostIRPSubmit
                  ( deviceObj->controlPipeHandle, & (deviceObj->controlTransferObj.controlIRP)))
{
    /* We need to be able to send the IRP. We move the device to
     * an error state. Close the pipe and send an event to the
     * application. The assigned address will be released when
     * the device in unplugged. */

    SYS_DEBUG_PRINT(SYS_ERROR_DEBUG,
        "\r\nUSB Host Layer: Bus %d Set Address IRP failed. Device not supported.", busIndex);

    /* Move the device to error state */
    deviceObj->deviceState = USB_HOST_DEVICE_STATE_ERROR;

    /* Close the pipe as we are about mark this device as unsupported. */
    deviceObj->hcdInterface->hostPipeClose(deviceObj->controlPipeHandle);
}
Transferring Data to an Attached Device

The USB Host Layer, the HCD client, needs to transfer data to the attached device to understand the device capabilities and to operate the device. The HCD uses a concept of Input Output Request Packet (IRP) to transfer data to and from the attached device. IRPs are transported over pipes which are setup by calling the USB Driver Pipe Setup function. 

A Host IRP is a USB_HOST_IRP type data structure. The IRP is created by the Host layer and submitted to the HCD for processing through the hostIRPSubmit function. At the time of submitting the IRP, the pipe over which the IRP must be transported is specified. The data request in the IRP is transported using the attributes of pipe. When an IRP is submitted to the HCD, it is owned by the HCD and cannot be modified by the Host Layer until the HCD issues an IRP callback. The HCD will issue the IRP callback when it has completed or terminated processing of the IRP. 

An IRP does not have its own transfer type. It inherits the properties of the pipe to which it is submitted. Hence an IRP becomes a control transfer IRP it was submitted to a control transfer pipe. A pipe allows multiple IRPs to be queued. This allows the Host Layer to submit IRPs to a pipe even while an IRP is being processed on the pipe. The HCD will process an IRP in the order that it was received. The following code example shows the USB_HOST_IRP data structure.

/* The following code example shows the USB_HOST_IRP structure. The Host Layer
 * uses this structure to place data transfer requests on a pipe. */

typedef struct _USB_HOST_IRP
{
    /* Points to the 8 byte setup command packet in case this is a IRP is
     * scheduled on a CONTROL pipe. Should be NULL otherwise */
    void * setup;

    /* Pointer to data buffer */
    void * data;

    /* Size of the data buffer */
    unsigned int size;

    /* Status of the IRP */
    USB_HOST_IRP_STATUS status;

    /* Request specific flags */
    USB_HOST_IRP_FLAG flags;

    /* User data */
    uintptr_t userData;

    /* Pointer to function to be called when IRP is terminated. Can be NULL, in
     * which case the function will not be called. */
    void (*callback)(struct _USB_HOST_IRP * irp);

    /****************************************
     * These members of the IRP should not be
     * modified by client
     ****************************************/
   uintptr_t privateData[7];

} USB_HOST_IRP;

The setup member of the USB_HOST_IRP structure must point to the 8 byte setup packet for control transfers. The driver will send this 8 byte data in the Setup phase of the control transfer. It can be NULL for non-control transfers. This member is only considered if the IRP is submitted to a control transfer pipe. It is ignored for non-control transfer pipes. The structure of the setup command should match that specified in the USB 2.0 specification. 

The data member of the USB_HOST_IRP structure points to a data buffer. This data buffer will contain the data that needs to be sent to the device for data stage of a OUT transfer, or it will contain the data that was received from the device during an IN transfer. Any hardware specific cache coherency and address alignment requirements must be considered while allocating this data buffer. The Driver Client should not modify or examine the contents of the IRP after the IRP has been submitted and is being processed. It can be examined after the driver has released the IRP. 

The size member of the USB_HOST_IRP structure contains the size of the transfer. for Bulk transfers, the size of the transfer can exceed the size of the transaction (which is equal to size of the endpoint reported by the device). The HCD in such a case will split up the transfer into transactions. This process does not require external intervention. For control transfers, the size of the transfer is specified in the setup packet (pointed to by the setup member of the USB_HOST_IRP structure). The driver will itself process the Setup, Data (if required) and Handshake stages of control transfer. This process again does not require external intervention. For interrupt and isochronous transfers, the size of transfer specified in the IRP cannot exceed the size of the transaction. If size is specified as 0, then the driver will send a zero length packet. The size parameter of the IRP is updated by the driver when IRP processing is completed. This will contain the size of the completed transfer. 

The status member of the IRP provides the completion status of the IRP and should be checked only when the IRP processing has completed. This is indicated by the driver calling the IRP callback function. The IRP status is a USB_HOST_IRP_STATUS type. The following code example shows the different possible values of the status member and an example of submit a control transfer IRP.

/* The following code shows an example of how the Host Layer populates
 * the IRP object and then submits it. IRP_Callback function is called when an
 * IRP has completed processing. The status of the IRP at completion can be
 * checked in the status flag. The size field of the irp will contain the amount
 * of data transferred. */

void IRP_Callback(USB_HOST_IRP * irp)
{
    /* irp is pointing to the IRP for which the callback has occurred. In most
     * cases this function will execute in an interrupt context. The application
     * should not perform any hardware access or interrupt unsafe operations in
     * this function. */

    switch(irp->status)
    {
        case USB_HOST_IRP_STATUS_ERROR_UNKNOWN:
            /* IRP was terminated due to an unknown error */
            break;

        case USB_HOST_IRP_STATUS_ABORTED:
            /* IRP was terminated by the application */
            break;

        case USB_HOST_IRP_STATUS_ERROR_BUS:
            /* IRP was terminated due to a bus error */
            break;

        case USB_HOST_IRP_STATUS_ERROR_DATA:
            /* IRP was terminated due to data error */
            break;

        case USB_HOST_IRP_STATUS_ERROR_NAK_TIMEOUT:
            /* IRP was terminated because of a NAK timeout */
            break;

        case USB_HOST_IRP_STATUS_ERROR_STALL:
            /* IRP was terminated because of a device sent a STALL */
            break;

        case USB_HOST_IRP_STATUS_COMPLETED:
            /* IRP has been completed */
            break;

        case USB_HOST_IRP_STATUS_COMPLETED_SHORT:
            /* IRP has been completed but the amount of data processed was less
             * than requested. */
            break;

        default:
            break;
    }
}

/* In the following code example the a control transfer IRP is submitted to a
 * control pipe. The setup parameter of the IRP points to the Setup command of
 * the control transfer. The direction of the data stage is specified by the
 * Setup packet. */

USB_HOST_IRP irp;
USB_ERROR result;
USB_HOST_PIPE_HANDLE controlPipe;
USB_SETUP_PACKET setup;
uint8_t controlTransferData[32];

irp.setup = setup;
irp.data = controlTransferData;
irp.size = 32;
irp.flags = USB_HOST_IRP_FLAG_NONE ;
irp.userData = &someApplicationObject;
irp.callback = IRP_Callback;

result = DRV_USBFS_HOST_IRPSubmit(controlPipeHandle, &irp);

switch(result)
{
    case USB_ERROR_NONE:
        /* The IRP was submitted successfully */
        break;

    case USB_ERROR_HOST_PIPE_INVALID:
        /* The specified pipe handle is not valid */
        break;

    case USB_ERROR_OSAL_FUNCTION:
        /* An error occurred while trying to grab mutex */
        break;

    default:
        break;
}

The flags member of the USB_HOST_IRP structure specifies flags which affect the behavior of the IRP. The USB_HOST_IRP_FLAG enumeration specifies the available option. The USB_HOST_IRP_FLAG_SEND_ZLP causes the driver to add a Zero Length Packet (ZLP) to the data stage of the transfer when the transfer size is an exact multiple of the endpoint size. The USB_HOST_IRP_WAIT_FOR_ZLP flag will cause the driver to wait for a ZLP from the device in a case where the size of data received thus far in the transfer is an exact multiple of the endpoint size. 

The callback member of the USB_HOST_IRP structure points to a function which the driver calls when the IRP processing is completed. The Driver Client must implement this function and assign the pointer to this function to the callback member of the IRP. Every IRP can have its own callback function or one common callback function could be used. The callback function will execute in an interrupt context. The Driver Client should not execute interrupt unsafe, blocking, or computationally intensive operations in the callback function. The client can call hostIRPSubmit function in the IRP callback function to submit another IRP or resubmit the same IRP. The client can check the status and size of the IRP in the callback function. 

The userData member of the USB_HOST_IRP structure can be used by the client to associate a client specific context with the Host. This context can then be used by the client, in the IRP callback function to identify the context in which the IRP was submitted. This member is particularly useful if the client wants to implement one callback function for all IRPs. 

The privateData member of the IRP is used by the driver and should not be accessed or manipulated by the Driver Client. The following code examples show usage of IRPs to transfer data between the Host and the attached device and along with the different flags.

/* The following code shows an example of submitting an IRP to send data
 * to a device. In this example we will request the driver to send a ZLP after
 * sending the last transaction. The driver will send the ZLP only if the size
 * of the transfer is a multiple of the endpoint size. This is not a control
 * transfer IRP. So the setup field of the IRP will be ignored.  */

USB_HOST_IRP irp;
USB_ERROR result;
USB_HOST_PIPE_HANDLE bulkOUTPipeHandle;
uint8_t data[128];

irp.data = data;
irp.size = 128;
irp.flags = USB_HOST_IRP_FLAG_SEND_ZLP ;
irp.userData = &someApplicationObject;
irp.callback = IRP_Callback;

result = DRV_USBFS_HOST_IPRSubmit( bulkOUTPipeHandle, &irp );


/* The following code shows an example of submitting an IRP to receive
 * data to a device. In this example we will request the driver to wait for a
 * ZLP after receiving the last transaction. The driver will wait for the ZLP
 * only if the size of the transfer is a multiple of the endpoint size. This is
 * not a control transfer IRP. So the setup field of the IRP will be ignored.
 * */

USB_HOST_IRP irp;
USB_ERROR result;
USB_HOST_PIPE_HANDLE bulkINPipeHandle;
uint8_t data[128];

irp.data = data;
irp.size = 128;
irp.flags = USB_HOST_IRP_FLAG_WAIT_FOR_ZLP ;
irp.userData = &someApplicationObject;
irp.callback = IRP_Callback;

result = DRV_USBFS_HOST_IPRSubmit( bulkINPipeHandle, &irp );