Considering Function Driver Design Approaches

This section highlights considerations for passing descriptors between the FDC and FDI and for designing a simple and a more complex Function Driver.

Passing descriptors to the FDI

The FDC receives USB descriptors (and the interface) from the USBDI and passes them to the FDI.

In cases where the FDI requires a subset of the data that the FDC receives, the FDC can extract the minimum necessary information from the USB descriptor bundles and store it in a class or struct. It can then use an IPC mechanism to pass the stored information.

Note: As with many other classes, the USB descriptors cannot be packaged using TPckg classes for passing through IPC because they contain pointers.

In cases where the FDI requires most of the information from the USB descriptors, it may be more efficient to send the complete binary data of the descriptor.

All USB descriptor base classes derive from the TUsbGenericDescriptor base class. The TUsbGenericDescriptor base class has a data member TPtrC8 iBlob. iBlob has the complete binary data, including headers, of the descriptor it represents as read from the bus. iBlob is public, so you can pass it directly to the FDI using an IPC mechanism. The FDI can use it directly or reconstruct it into a USB descriptor using the UsbDescriptorParser::Parse() API.

Warning: Passing a partial configuration descriptor to the FDI may cause a panic. To avoid this, you should validate the length of the iBlob by comparing it to the value returned by the TUsbConfigurationDescriptor::TotalLength() API.

Designing a simple Function Driver

We will consider designing a simple Function Driver (FD) which supports only one device at a time, and a Class with single-interface Functions.

When Mfi1NewFunction() is called, the Function Driver Controller (FDC) claims the first interface in the array using TokenForInterface(). The token returned is stored in a data member. The Function is single interface so no array, dynamic or otherwise, is needed to hold the required token.

Next, the FDC checks a private flag to see if the FD already has a Function. If it does, the FDC returns KErrInUse. This turns into a ‘driver loading’ error notification to USBMAN. The token in the data member is never used and no interface handles are opened. Because the FDC returned an error from Mfi1NewFunction(), the FDF will not notify the FDC of the detachment of the device.

Note: Because only one device is supported at a time the actual device ID doesn’t need to be remembered (and certainly not in a dynamic array).

If the FD does not already have a Function, the FDC passes the token to a bespoke server in the Function Driver Implementation (FDI) using a simple synchronous API on that server. The server receives the token, tries to open an RUsbInterface handle using it (this may fail due to out of memory), and completes the FDC’s request with an error.

The FDC receives the error and returns it from Mfi1NewFunction(). If it was KErrNone, the FDI has an RUsbInterface handle open and the driver was loaded successfully. If it was other than KErrNone, the FDI failed to open the RUsbInterface handle. The FDF will not notify the FDC of the removal of the device. It is anticipated that the FDI will use its RUsbInterface handle to get the interface, endpoint and other descriptors from the device.

It will use these to implement the Class-specific functionality.

If the FDC is going to need any of the device’s string descriptors, it should use the string descriptor APIs to obtain them during Mfi1NewFunction().

Assuming the approach of having a bespoke server in the FDI is taken, it is up to the FD when the FDC opens the RSessionBase -derived handle on the FDI server. This may be done when the FDC is instantiated or when Mfi1NewFunction() is called. FDCs are only instantiated just before use (they are not instantiated on FDF startup) and are destroyed when the last relevant device is detached. So for ease of implementation it is a good idea to open the handle at construction time. This will ease error handling when Mfi1NewFunction() is called (especially in FDs which support more than one simultaneous Function).

When the device is removed, Mfi1DeviceDetached() is called only if Mfi1NewFunction() returned KErrNone. The FDC may assume that the device being detached is the single device from which it has taken control of the interface (assuming the FDC really has taken control of interfaces from only one device). The FDC calls a simple synchronous API on the server in the FDI and the FDI closes the RUsbInterface handle. Such a synchronous API cannot fail unless the corresponding server has terminated, in which case the RUsbInterface handle will have been closed anyway. Even if the RUsbInterface handle is still open, it will be useless as USBDI will have invalidated its tokens and the handle will have become inoperative.

Designing a more complex Function Driver

We will also consider a more complex FD which supports more than one device at a time, and a Class with multiple-interface Functions.

The FD supports more than one device at a time, and it is required by the FDF API that a FD close its interface handles when the relevant device is detached. Therefore the FD may have to store a dynamic (growing and shrinking) mapping between device IDs and sets of interface handles. The dynamic nature of such a collection will clearly be failable.

If the Class defines Functions which contain a fixed number of interfaces (for instance ACM, which always has precisely two interfaces), this simplifies matters. Fixed-size arrays can be used to store and transfer the interface tokens. If the Functions vary in the number of interfaces, design and implementation becomes more complex. At the design level, there will be error handling trying to store the tokens derived from repeated calls to TokenForInterface(). At the implementation level, there will be passing a variable-size collection of tokens across an IPC boundary to the bespoke server in the FDI.

When Mfi1DeviceDetached() is called, the FD must check the ID of the detached device to close the correct set of interface handles. The mapping between device IDs and sets of interface handles may be kept in the FDC or the FDI. All interface handles opened from the detaching device must be closed. The FD must also use the correct device ID in calls to the string descriptor APIs.