USB Libraries Help > USB Common Driver Interface > Common Interface > USB Driver Device Mode Operation > Transferring Data to the Host
MPLAB Harmony USB Stack
Transferring Data to the Host

The USB Device Layer, the USBCD client, needs to transfer data to the Host in response to enumeration requests for general operation on the device. The USB uses a concept of Input Output Request Packet (IRP) to transfer data to and from the Host. IRPs are transported over endpoints which are enabled by calling the USBCD Endpoint Enable function. 

A Device IRP is a USB_DEVICE_IRP type data structure. The IRP is created by the Device Layer and submitted to the USBCD for processing through the deviceIRPSubmit function. At the time of submitting the IRP, the endpoint over which the IRP must be transported is specified. The data request in the IRP is transported using the attributes of the endpoint. When an IRP is submitted to the USBCD, it is owned by the USBCD and cannot be modified by the Device Layer until the USBCD issues an IRP callback. The USBCD 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 endpoint to which it is submitted. Hence an IRP becomes a control transfer IRP it was submitted to a control endpoint. An endpoint allows multiple IRPs to be queued. This allows the Device Layer to submit IRPs to an endpoint even while an IRP is being processed on the endpoint. The USBCD will process an IRP in the order that it was received. The following code example shows the USB_DEVICE_IRP data structure:

/* This code example shows the USB_DEVICE_IPR structure. The Device Layer
 * uses such a structure to transfer data through the driver. A structure of
 * this type is allocated by the Device Layer and the other function drivers and
 * passed to the deviceIRPSubmit function. */

typedef struct _USB_DEVICE_IRP
{
    /* Pointer to the data buffer */
    void * data;

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

    /* Status of the IRP */
    USB_DEVICE_IRP_STATUS status;

    /* IRP Callback. If this is NULL, then there is no callback generated */
    void (*callback)(struct _USB_DEVICE_IRP * irp);

    /* Request specific flags */
    USB_DEVICE_IRP_FLAG flags;

    /* User data */
    uintptr_t userData;

    /***********************************
     * The following members should not
     * be modified by the client
     ***********************************/
    uint32_t privateData[3];

} USB_DEVICE_IRP;

The data member of the USB_DEVICE_IRP structure points to a data buffer. This data buffer will contain the data that needs to be sent to the Host for the data stage of an IN transfer. For an OUT transfer, it will contain the data that was received from the Host. 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_DEVICE_IRP structure specifies the size of the data buffer. The transfer will end when the device has sent or received size number of bytes. While sending data to the Host, the IRP size can exceed the size of the transaction (which is equal to the size of the endpoint). The USBCD in such a case will split up the transfer into transactions. This process does not require external intervention. The driver uses receive and transmit IRPs to process control transfers. When the driver receives a Setup packet, the IRP completion status would be USB_DEVICE_IRP_STATUS. The Driver Client should then use additional receive and transmit IRPs to complete the control transfer. 

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 or expect 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_DEVICE_IRP_STATUS type. The following code example shows the different possible values of the status member and example usage of IRPs to transfer data between the device and the Host.

/* The followoing code shows example usage of the device IRP. The submit status
 * of the IRP is available when IRP submit function returns. The completion
 * status of the IRP is available when the IRP has terminated and the IRP
 * callback function is invoked. The IRP callback
 * function shown in this example shows the possible complete status of the IRP.
 * The end application may or may not handle all the cases. Multiple IRPs can be
 * queued on an endpoint. */

void IRP_Callback(USB_DEVICE_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_DEVICE_IRP_STATUS_TERMINATED_BY_HOST:
            /* The IRP was aborted because the Host cleared the stall on the
             * endpoint */
            break;

        case USB_DEVICE_IRP_STATUS_ABORTED_ENDPOINT_HALT:
            /* IRP was aborted because the endpoint halted */
            break;


        case USB_DEVICE_IRP_STATUS_ABORTED:
            /* USB Device IRP was aborted by the function driver */
            break;

        case USB_DEVICE_IRP_STATUS_ERROR:
            /* An error occurred on the bus when the IRP was being processed */
            break;

        case USB_DEVICE_IRP_STATUS_COMPLETED:
            /* The IRP was completed */
            break;

        case USB_DEVICE_IRP_STATUS_COMPLETED_SHORT:
            /* The IRP was completed but the amount of data received was less
             * than the requested size */
            break;

        default:
            break;

    }
}

/* In the following example, the IRP is submitted to Endpoint 0x84. This is
 * interpreted as an IN direction endpoint (MSB of 0x84 is 1) and Endpoint 4.
 * The data contained in source will be sent to the USB Host. Assuming
 * the endpoint size is 64, the 130 bytes of data in this case will be sent to
 * the Host in three transaction of 64, 64 and 2 bytes. A transaction completes
 * when the Host polls (sends an IN token) the device.  The callback function
 * will then called indicating the completion status of the IRP. The application
 * should not modify the privateData field of the IRP. If the IRP was submitted
 * successfully, the buffer will be owned by the driver until the IRP callback
 * function has been called. Because the size of the transfer is not a multiple
 * of the endpoint size, the IRP flag must be set
 * USB_DEVICE_IRP_FLAG_DATA_COMPLETE. This directs the driver to not perform any
 * explicit signaling to the Host to indicate end of transfer. The last packet
 * in this case is a short packet and this signals the end of the transfer. */

USB_DEVICE_IRP irp;
USB_ERROR result;
uint8_t source[130];

irp.data = source;
irp.size = 130;
irp.called = IRP_Callback;
flags = USB_DEVICE_IRP_FLAG_DATA_COMPLETE;
userData = &someApplicationObject;

result = DRV_USBFS_DEVICE_IRPSubmit(driverHandle, 0x84, &irp);

switch(result)
{
    case USB_ERROR_PARAMETER_INVALID:
        /* This can happen if the driverHandle is invalid */
        break;

    case USB_ERROR_DEVICE_IRP_IN_USE:
        /* This can happen if the IRP is being resubmitted while it is still in
         * process (it was submitted before but processing has not completed */
        break;

    case USB_ERROR_DEVICE_ENDPOINT_INVALID;
        /* The endpoint to which this IRP is being submitted is not provisioned
         * in the system. This is controller by DRV_USBFS_ENDPOINTS_NUMBER
         * configuration parameter. */
        break;

    case USB_ERROR_ENDPOINT_NOT_CONFIGURED:
        /* The endpoint to which this IRP is being submitted is not enabled. It
         * must be enabled by calling the DRV_USBFS_DEVICE_EndpointEnable()
         * function. */
        break;

    case USB_ERROR_PARAMETER_INVALID:
        /* The USB_DEVICE_IRP_FLAG_DATA_PENDING flag was specified but the
         * transfer size is not a multiple of the endpoint size. If the IRP was
         * submitted to a receive endpoint, this error can occur if the size is
         * not a multiple of the endpoint size. */
        break;

    case USB_ERROR_OSAL_FUNCTION:
        /* An error occurred while trying to grab a mutex. This is applicable
         * when the driver is running with a RTOS. */
        break;

    case USB_ERROR_NONE:
        /* The IRP was submitted successfully. */
        break;

    default:
        break;
}

/* The following code example shows how an IRP is submitted to an OUT endpoint.
 * In this case data will be pointing to a buffer where the received data will
 * be stored. Note that the size of the IRP should be a multiple of the endpoint
 * size. The flags parameter is ignored in the data receive case. The IRP
 * terminates when the specified size of bytes has been received (the Host sends
 * OUT packets) or when a short packet has been received. */

USB_DEVICE_IRP irp;
USB_ERROR result;
uint8_t destination[128];

irp.data = destination;
irp.size = 128;
irp.called = IRP_Callback;
userData = &someApplicationObject;

result = DRV_USBFS_DEVICE_IRPSubmit(driverHandle, 0x04, &irp);

For IRPs submitted to an Interrupt or Isochronous endpoints, the driver will always send either the less than or equal to the maximum endpoint packet size worth of bytes in a transaction. The application could either submit an IRP per Interrupt/Isochronous polling interval or it could submit one IRP for multiple polling intervals. 

The flags member of the USB_DEVICE_IRP structure specifies flags which affect the behavior of the IRP. The USB_DEVICE_IRP_FLAG enumeration specifies the available option. The USB_DEVICE_IRP_FLAG_DATA_COMPLETE causes the driver to add a Zero Length Packet (ZLP) to the data stage of the IN transfer when the transfer size is an exact multiple of the endpoint size. If the transfer size is not a multiple of the endpoint size, no ZLP will be sent. The USB_DEVICE_IRP_FLAG_PENDING flag will cause the driver to not send a ZLP in a case where the size of the IN transfer is an exact multiple of the endpoint size. The following code example demonstrates this.

/* In the following code example, the IRP is submitted to an IN endpoint whose size
 * is 64. The transfer size is 128, which is an exact multiple of the endpoint
 * size. The flag is set to USB_DEVICE_IRP_FLAG_DATA_COMPLETE. The driver
 * will send two transactions of 64 bytes each and will then automatically send a
 * Zero Length Packet (ZLP), thus completing the transfer. The IRP callback will
 * be invoked when the ZLP transaction has completed. */

USB_DEVICE_IRP irp;
USB_ERROR result;
uint8_t source[128];

irp.data = source;
irp.size = 128;
irp.called = IRP_Callback;
flags = USB_DEVICE_IRP_FLAG_DATA_COMPLETE;
userData = &someApplicationObject;

result = DRV_USBFS_DEVICE_IRPSubmit(driverHandle, 0x84, &irp);

/* In the following code example, the IRP is submitted to an IN endpoint whose size
 * is 64. The transfer size is 128, which is an exact multiple of the endpoint
 * size. The flag is set to to USB_DEVICE_IRP_FLAG_DATA_PENDING. The driver will
 * send two transactions of 64 bytes each but will not send a ZLP. The USB Host
 * can then consider that there is more data pending in the transfer. The IRP
 * callback will be invoked when the two transactions have completed. */

USB_DEVICE_IRP irp;
USB_ERROR result;
uint8_t source[128];

irp.data = source;
irp.size = 128;
irp.called = IRP_Callback;
flags = USB_DEVICE_IRP_FLAG_DATA_COMPLETE;
userData = &someApplicationObject;

result = DRV_USBFS_DEVICE_IRPSubmit(driverHandle, 0x84, &irp);

The callback member of the USB_DEVICE_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 deviceIRPSubmit 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_DEVICE_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.