Handling Interfaces

This tutorial explains how the FDI can handle interfaces.

Introduction

The FDI receives tokens to the interfaces associated with the USB function from the FDC. This guide explains how the FDI can use these tokens to create and manage interfaces.

Basic steps

This guide covers the following topics:

  • Opening and closing a handle to the interface

  • Suspending and resuming, issuing and receiving endpoint zero requests

  • Suspending and resuming

  • Inspecting descriptors

  • Interface settings

  • Creating transfer descriptors

Opening and closing a handle to the interface

  • Use the RUsbInterface::Open function to open a handle to a USB interface as in the following example.

    //Receive token from the FDC
    RUsbInterface interface;
    TInt err = interface.Open(token);
    if(err != KErrNone)
        {
        return err;
        }

    The interface descriptor and its children are now available. See information on inspecting descriptors.

    Note:

    The part of the FDI that uses RUsbInterface needs the CommDD capability. If the part of the FDI that uses RUsbInterface is a DLL that is linked to by, or that plugs into, another process, that process also needs the CommDD capability.

  • Once the RUsbInterface instance is open, you can use the handle to control the device. In the following example, the interface is suspended.

    TRequestStatus status;
    interface.PermitSuspendAndWaitForResume(status);
    User::WaitForRequest(status);
  • When a RUsbInterface instance is no longer needed, you must close it. Use the RUsbInterface::Close function to do this. RUsbInterface::Close closes pipe handles related to this interface and deletes descriptor data made available when RUsbInterface::Open was called.

    interface.Close();

Suspending and resuming, issuing and receiving endpoint zero requests

You can use the following utility functions to perform standard endpoint 0 requests and control functions for suspend and resume.

void PermitSuspendAndWaitForResume(TRequestStatus& aResumeSignal);
void CancelPermitSuspend();
TInt PermitRemoteWakeup(TBool aPermitted);
TInt GetStringDescriptor(TDes8& aStringDescriptor, TUint8 aIndex, TUint16 aLangId);
TInt GetInterfaceDescriptor(TUsbInterfaceDescriptor& aDescriptor);
TInt GetAlternateInterfaceDescriptor(TInt aAlternateInterface, 
  TUsbInterfaceDescriptor& aDescriptor);
TInt GetEndpointDescriptor(TInt aAlternateInterface, TInt aEndpoint, 
  TUsbEndpointDescriptor& aDescriptor);
TInt GetAlternateInterfaceCount();
TInt EnumerateEndpointsOnInterface(TInt aAlternateInterface);
TInt SelectAlternateInterface(TInt aAlternateInterface);

These standard requests are available to an open RUsbInterface instance.

The RUsbInterface instance places no restrictions on device level requests. For example, RUsbInterface instance allows a client to issue and receive any endpoint 0 requests. (A RUsbInterface user does not know whether there are other function drivers operating on the same device.) Therefore it is best to use the utility functions listed above. Because FDIs need to be careful with device level requests, they require the CommDD capability.

The following is an example of a device level request being made:

TRequestStatus status;
TUsbTransferRequestDetails details;

details.iRequestType    = 0x00;    // Standard request, host-to-device, device
details.iRequest        = 0x07;    // SET_DESCRIPTOR
details.iValue        = 0x0304;    // String descriptor in index 4
details.iIndex        = 0x0409;    // USB English Language ID
details.iFlags        = 0x04;    // Short transfer OK

_LIT(KNewDeviceName, “My Symbian platform Device”);
TPtrC8 newDeviceName(KNewDeviceName());
TPtr8 nullPtr(NULL, 0);

interface.Ep0Transfer(details, newDeviceName, nullPtr, status);

//May want to encapsulate this wait in an active object
User::WaitForRequest(status);
if(status.Int() != KErrNone)
    {
    return status.Int();
    }

Use device level requests sparingly in Function Drivers. Most requests are anticipated to be interface or endpoint directed.

Suspending and resuming

You must use the following utility functions provided by USBDI on the RUsbInterface object to suspend a device:

  • PermitSuspendAndWaitForResume

  • CancelPermitSuspend

  • PermitRemoteWakeup.

With these utility functions, USBDI only suspends the device when all active interfaces have requested the device be suspended. If a single interface requires the device, the device is resumed.

To suspend a device, you make a request as in the following example:

TRequestStatus status;
interface.PermitSuspendAndWaitForResume(status);
//May want to encapsulate this wait in an active object
User::WaitForRequest(status);

The TRequestStatus provided as part of the function call will be completed either when the device is resumed (with KErrNone), or when the function driver wishes to resume the device (KErrCancel), which they can do using CancelPermitSuspend().

To revoke permission for suspending the device, you use the following:

interface.CancelPermitSuspend();

The reason for this asynchronous notification associated with PermitSuspendAndWaitForResume() is that when another function cancels its permission-to-suspend for another interface on the same device, the device will be resumed, as part of this all (active) interface requests for suspension are completed. Therefore the notification can be used to re-permit the device to be suspended or as a trigger to check the device for new data (may be appropriate for a remote wakeup-capable device.

Some devices support remote-wakeup, which must be enabled by the host through a SET_FEATURE request. The RUsbInterface::PermitRemoteWakeup utility function simplifies the procedure for function drivers. You enable remote wakeup as follows:

interface.PermitRemoteWakeup(ETrue);

A device initiated remote-wakeup will appear to users of USBDI just as if another function driver had cancelled suspending permission on one of its interfaces.

Inspecting descriptors

The software entity in which descriptors are inspected may be implementation dependent both on the device and the function driver. You may decide to do all descriptor processing in the FDC, where there is access to the configuration and device descriptors for the device. The Function Driver Implementation may need to inspect the descriptors associated with the interface.

An opened RUsbInterface object has a copy of the whole set of associated descriptors (endpoints, class-specific functional descriptors etc.) available to it. You can use the following utility functions provided by USBDI on the RUsbInterface object to access the descriptors associated with the interface in preparation for being inspected by the user:

  • GetInterfaceDescriptor

  • GetAlternateInterfaceDescriptor

  • GetEndpointDescriptor

  • EnumerateEndpointsOnInterface

  • GetStringDescriptor

Function Drivers take ownership of String descriptors. They need to parse these descriptors.

Important: The descriptor tree that is retrieved is owned internally by the RUsbInterface instance. A function driver must not attempt to delete or clean up any member of the descriptor tree.

GetStringDescriptor

There are two stages to extracting string descriptors:

  • Find a suitable language code

  • Get the actual string descriptor you want in the language you find supported.

In the example below, the function driver owns both the stringBuf that holds the descriptor data and the TUsbStringDescriptor that it has a pointer to.

1. Create a TBuf to retrieve the descriptor and use the GetStringDescriptor function to retrieve it.

TBuf<255> stringBuf;
//The second parameter (0) is a string descriptor 0, containing language codes 
TInt err = interface.GetStringDescriptor(stringBuf, 0, 0); // Language IDs
if(err != KErrNone)
    {
    return err;
    }

2. Create a pointer to a TUsbGenericDescriptor object.

TUsbGenericDescriptor* desc = NULL;

3. Parse the descriptor to save it into the TUsbGenericDescriptor object.

err = UsbDescriptorParser::Parse(stringBuf, desc);
if(err != KErrNone)
    {
    return err;
    }

4. Create a pointer to a TUsbStringDescriptor and cast the TUsbGenericDescriptor object to it. Clean up the TUsbStringDescriptor object.

TUsbStringDescriptor* stringDesc = TUsbStringDescriptor::Cast(desc);
if(!stringDesc)
    {
    desc->DestroyTree();
    delete desc;
    return KErrCorrupt;
    }
desc = NULL;

5. Step through the stored descriptors to find a particular descriptor, in this case US Language ID.

const TUint16 usLangId = 0x0409; // US English
TInt langId = KErrNone;
TBool usEngSupported = EFalse;
For(TInt i=0; ; ++i)
    {
    langId = stringDesc->GetLangId(i);
    if(langId == KErrNotFound)
        {
        break;
        }
    else if(langId == usLangId)
        {
        usEngSupported = ETrue;
        break;
        }
    }
if(!usEngSupported)
    {
    return KErrNotFound;
    }

Clean up the TUsbStringDescriptor object.

stringDesc->DestroyTree();
delete stringDesc;
stringDesc = NULL;
stringBuf.Zero();

Finally, get the actual string descriptor you want in the supported language.

const TUint8 index = 3; // Arbitrary index
err = interface.GetStringDescriptor(stringBuf, index, usLangId); // Actual String
if(err != KErrNone)
    {
    return err;
    }
err = UsbDescriptorParser::Parse(stringBuf, desc);
if(err != KErrNone)
    {
    return err;
    }
stringDesc = TUsbStringDescriptor::Cast(desc);
if(!stringDesc)
    {
    desc->DestroyTree();
    delete desc;
    return KErrCorrupt;
    }
TBuf<255> stringData;
stringDesc->StringData(stringData);
Output(stringData);

GetInterfaceDescriptor

The GetInterfaceDescriptor(), GetAlternateInterfaceDescriptor(), GetEndpointDescriptor() and EnumerateEndpointsOnInterface() functions provide a simple reference to an already parsed tree of descriptors. The Function Driver walks this tree of descriptors to determine the information it needs. The descriptors form a tree with a set of three pointers; iFirstChild, iNextPeer, iParent. The tree is descended by following the iFirstChild pointer, and a tier of descriptors traversed with the iNextPeer pointer. Reversing up the tree (required for iterative parsing) is done by following the iParent pointer. With these pointers, full tree traversal is possible, including breadth and depth first searching. Below is an example depth first search of an interface descriptor tree using GetInterfaceDescriptor().

TUsbInterfaceDescriptor intDesc;
TInt err = GetInterfaceDescriptor(intDesc);
if(err != KErrNone)
    {
    return err;
    }

TUsbGenericDescriptor* descriptor = intDesc;
while(descriptor)
    {
    Check(descriptor);
    if(descriptor.iFirstChild)
        {
        descriptor = descriptor->iFirstChild;
        }
    else if(descriptor.iNextPeer)
        {
        descriptor = descriptor->iNextPeer;
        }
    else
        {
        descriptor = descriptor->iParent->iNextPeer;
        }
    }

USBDI provides parsing to help with common cases. These include extracting particular alternative setting descriptors for an interface and retrieving specific endpoint descriptors. There are functions for simply determining the number of alternative settings and endpoints also.

Interface settings

The RUsbInterface::SelectAlternateInterface function allows the user to specify the alternate interface setting that the interface should be set to. The number of settings available on a USB interface can be determined by RUsbInterface::GetAlternateInterfaceCount. It is important that any pipes open on a USB interface are closed before the alternate setting is changed. USBDI will panic a client attempting to change settings while any pipes are open.

The example below illustrates how to set an alternate interface setting, based on the “index” of the setting in the configuration bundle. Note that different alternate interface settings are modelled as peers in the descriptor tree.

Note: It will usually be easier for a FDI writer to just parse the descriptor directly. This is just to demonstrate the APIs.

//interface is an opened RUsbInterface object
TInt settingCount = interface.GetAlternateInterfaceCount();
if(    settingCount < 0 ||
//aInterfaceSettingIndex represents the new setting to change to 
aInterfaceSettingIndex < 0 || 
aInterfaceSettingIndex >= settingCount )
{
return KErrBadArgument;
}

TUsbInterfaceDescriptor headDesc;
TInt err = interface.GetInterfaceDescriptor(headDesc);
if(err != KErrNone)
    {
    return err;
    }

TInt count = 0;

TUsbGenericDescriptor* descriptor = & headDesc;
TUsbInterfaceDescriptor*  intDesc = NULL;
while (count != settingCount && descriptor)
    {
    TUsbInterfaceAssociationDescriptor* iad;
    if (intDesc = TUsbInterfaceDescriptor::Cast(descriptor), intDesc)
        {
        ++count;
        }
    descriptor = descriptor->iNextPeer;
    }
__ASSERT_ALWAYS(settingCount == count, User::Panic(_L(“FDI”), EDifferentInCounts);

TUint8 settingNumber = intDesc->AlternateSetting();

err = interface.SelectAlternateInterface(settingNumber);
if(err != KErrNone)
    {
    return err;
    }

Creating transfer descriptors

Transfer descriptors are used to transfer data to and from a USB device. They provide an API to the data buffers where the data to be sent is stored. There is a transfer descriptor for each type of transfer possible: RUsbBulkTransferDescriptor, RUsbIsocTransferDescriptor and RUsbIntrTransferDescriptor.

Note: Control transfers can only be conducted on endpoint zero.

  1. Create transfer descriptors.

    TInt intrTransMaxSize = 0x40;
    RUsbIntrTransferDescriptor intrTrans(intrTransMaxSize);
    
    TInt isocMaxNumPackets = 0x10;
    TInt isocMaxPacketSize = 0x20;
    RUsbIsocTransferDescriptor isocTrans(isocMaxPacketSize, isocMaxNumPackets);
  2. Register transfer descriptors with the interface they are to be used with.

    TInt err = interface.RegisterTransferDescriptor(intrTrans);
    if(err != KErrNone)
        {
        return err;
        }
    
    err = interface.RegisterTransferDescriptor(isocTrans);
    if(err != KErrNone)
        {
    return err;
        }
  3. Call InitialiseTransferDescriptors().

    err = interface.InitialiseTransferDescriptors();
    if(err != KErrNone)
        {
        return err;
        }

Once initialised, the transfer descriptors registered to the interface are fixed. Additional registration attempts result in a panic. In practical terms this means a function driver should inspect all of the alternate settings and take them all into account when registering the transfer descriptors. If a mistake is made while registering the transfer descriptors, the registration can be reset with RUsbInterface::ResetTransferDescriptors. However once initialised, calling this function will result in a panic.

interface.ResetTransferDescriptors();

Related information