Registering Descriptor Parsers

This tutorial explains how to write a descriptor parsers for cases not handled by the framework parser.

Introduction

Ideally the FDC does the parsing and collects the data that is relevant to the FDI in a data structure that the FDI and the FDC share. However, if you design your FDC to pass raw USB descriptors to the FDI as binary data or if you wish the parser to be able to parse custom or class-specific descriptors, you may need to write your own descriptor parser. This tutorial explains how to write a descriptor parser.

Basic steps

The high level steps to registering a descriptor parser are shown here:

  1. Creating a custom descriptor class

  2. Writing a parsing routine

  3. Registering custom parsing

After doing the above, when the FDI receives descriptor information in binary format, it can use USBDescriptorParser::Parse() to reconstruct it into a USB descriptor.

Creating a custom descriptor class

A custom USB descriptor class must derive from TUsbGenericDescriptor. The TUsbGenericDescriptor class is made up of the following virtual functions that you must implement.

virtual TBool IsParent(TUsbGenericDescriptor& aPotentialParent);
virtual TBool IsPeer(TUsbGenericDescriptor& aPotentialPeer);
virtual TBool IsChild(TUsbGenericDescriptor& aPotentialChild);

These functions need to check how the USB descriptor is bound into the tree formed by the bundle provided to the parser. For example the IsParent() returns ETrue to an IsParent call if the custom USB descriptor is the parent of the given descriptor, otherwise it returns EFalse.

TBool TUsbCustomDescriptor::IsParent(TUsbGenericDescriptor& aPotentialParent)
{
switch(aPotentialParent.ibDescriptorType)
  {
  case EInterface:
     return ETrue;
  default:
     return EFalse;
        }
}

TBool TUsbCustomDescriptor::IsPeer(TUsbGenericDescriptor& aPotentialPeer)
{
switch(aPotentialPeer.ibDescriptorType)
  {
  case EEndpoint:
     return ETrue;
  default:
     return EFalse;
  }
}

This example is for a descriptor at the same level as an endpoint descriptor.

Writing a parsing routine

In the following example, TUsbCustomDescriptor is a custom class derived from TUsbGenericDescriptor. TUsbCustomDescriptor::ParseL() needs to do the following:

  1. Check whether the binary data received is a USB descriptor the function can parse.

  2. If it is a USB descriptor of the type that TUsbCustomDescriptor represents, create a new instance of the TUsbCustomDescriptor class you derived from TUsbGenericDescriptor.

    The TUsbGenericDescriptor class has utility methods to access the fields of the USB descriptor.

  3. Set the standard fields and set the iBlob member of the USB descriptor class it has created to point to the data region that the custom USB descriptor class represents.

  4. Update the TPtrC8 parameter to point to the remaining data that trails the parsed USB descriptor and return the USB descriptor class.

/*static*/ TUsbCustomDescriptor* TUsbCustomDescriptor::ParseL
 (TPtrC8& aUsbDes, TUsbGenericDescriptor* aPreviousDesc)
 {
 TUsbCustomDescriptor* custDes = NULL;
   //KMinCustomDesDecisionLength – minimum bytes required to 
   //recognise descriptor
   //KbDescriptorTypeOffset – offset of bDescriptorType field = 1
   //KCustomDescriptorType - descriptor type,
   //e.g. CS_INTERFACE = 24h or CS_ENDPOINT = 25h
   //KbLengthOffset – offset to size of descriptor = 0
   //KSizeInOctetstotal - number of bytes required in descriptor,
   //e.g. device descriptor would need 18
   const TInt KMinCustomDesDecisionLength = 2;
   if(    aUsbDes.Length() >= KMinCustomDesDecisionLength && 
      aUsbDes[KbDescriptorTypeOffset] == KCustomDescriptorType && 
      aUsbDes[KbLengthOffset] == TUsbCustomDescriptor::KSizeInOctets)
      {
      // Robustness check - check that we have enough data.
      if(aUsbDes.Length() < TUsbCustomDescriptor::KSizeInOctets)
      {
        User::Leave(KErrCorrupt);
      }
      // Looks okay to be a custom descriptor
      custDes = new(ELeave) TUsbCustomDescriptor;
      // Set the standard fields
      custDes->ibLength = TUsbCustomDescriptor::KSizeInOctets;
      custDes->ibDescriptorType = KCustomDescriptorType;
      // Set the blob appropriately
      custDes->iBlob.Set(aUsbDes.Left
         (TUsbCustomDescriptor::KSizeInOctets));
      // Null the pointers
      custDes->iFirstChild = NULL;
      custDes->iNextPeer = NULL;
      custDes->iParent = NULL;
      custDes->iRecognisedAndParsed = ERecognised;
  // Update the data-left-to-parse Symbian descriptor
        aUsbDes.Set(aUsbDes.Mid(TUsbCustomDescriptor::KSizeInOctets));
        }
return custDes;
    }

The following are the different exit conditions:

  • Return a valid pointer to a TUsbGenericDescriptor instance

  • Return a NULL pointer

  • Leave with an error code.

The first case is the success case, the second the case where the routine does not identify the USB descriptor type it parses, the third case is where the parser recognises the USB descriptor, but it is corrupted in some way. There is a subtle difference between the second and third cases, but it is an important one. Leaving will abandon processing of the USB descriptor data, and one will be returned a USB descriptor tree that is incomplete. Returning NULL will cause the framework to go on to the next parser and continue to parse the descriptor tree.

Registering custom parsing

Once you have created the parsing routine and custom USB descriptor class, you are ready to enable the custom parsing. Register the custom parser in each thread you want to parse USB descriptor data using the following call.

UsbDescriptorParser::RegisterCustomParserL(TUsbCustomDescriptor::ParseL);

Use the UnregisterCustomParser() function to remove the custom parser from the framework.

UsbDescriptorParser::UnregisterCustomParser(TUsbCustomDescriptor::ParseL);

Related information