Developers' Tour of UDev

First-time and potential driver developers use this tour to learn how to create a "USB device in Windows" using UDev.

Tip:  The tour assumes you are an experienced Windows kernel mode programmer

This tour is presented in “programming order.” That is after adding a UDev header file and members to your device extension, the tour starts with the first call your client makes to the UDev library. Initialization is complex, however once set up; the USB device and transfers are initiated and managed relatively easily.

Tips:

General Preparations

  1. Add UDev header file "NcUDev.h" to your source files as appropriate.

#include <NcUDev.h>

 

  1. In your driver’s device extension structure, add entries for UDEV_DEVICE and PUDEV_EXT. The UDEV_DEVICE structure represents visible members of your unique USB device. For instance your client specifies the entire 18-byte content of member DeviceDescriptor; when the USB host requests this standard USB descriptor UDev manages the transfer. (Tips: For detailed information about USB device descriptor content, see USB 2.0 spec: 9.6.1. Several other members of UDEV_DEVICE, including other USB descriptors, must be setup by your client before enabling the USB cable connection.)

    typedef struct _DEVICE_EXTENSION

    {

         UDEV_DEVICE UDevDevice;   // Structure uniquely describing your USB device

         PUDEV_EXT pudx;           // Device extension for PLX's UDev

 

  1. In the DeviceEventHandler member of your UDEV_DEVICE structure, specify an event handler function. This is a new function added to your client for handling non-transfer USB events. As these comparatively rare events occur, your event handler examines event codes and takes appropriate action. Event handling is discussed further on in the tour.
     

Your AddDevice function

Using the following steps in your driver’s AddDevice function, PUDEV_EXT will refer to an extra extension existing within your device extension. The extra extension is used by UDev to manage device states and transfers. The address of the extra extension is passed to all but the very first UDev function, UDev_GetSizeofLibraryExtension. The content of UDev’s extension is opaque to your driver.

 

  1. In your AddDevice function, call UDev_GetSizeofLibraryExtension. This UDev function returns the number of bytes required by UDev to maintain various device and transfer states. Add this size to your driver’s device extension requirement. For proper alignment, round the size of your Device Extension up to the next 8-byte multiple. Pass the total number of bytes required as (for instance) the DeviceExtensionSize parameter in your in your call to IoCreateDevice.

    ULONG xsize = (sizeof(DEVICE_EXTENSION) + 7) & ~7;  // Round up the extension size

    ULONG udsize = UDev_GetSizeofLibraryExtension();    // Size of PLX UDev extension

   

    // Create a functional device object to represent the hardware we're managing.

    NTSTATUS NtStatus = IoCreateDevice(

        DriverObject,               // IN PDRIVER_OBJECT  DriverObject,

        xsize + udsize,             // IN ULONG  DeviceExtensionSize,

        NULL,                       // IN PUNICODE_STRING  DeviceName  OPTIONAL,

        FILE_DEVICE_UNKNOWN,        // IN DEVICE_TYPE  DeviceType,

        FILE_DEVICE_SECURE_OPEN,    // IN ULONG  DeviceCharacteristics,

        FALSE,                      // IN BOOLEAN  Exclusive,

        &fdo                        // OUT PDEVICE_OBJECT  *DeviceObject

        );

 

  1. Calculate and record the address of UDev’s device extension into your own extension. Recording the address is convenient, as it is a required parameter in all subsequent UDev calls.

 

        PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

 

        // Initialize to use PLX's UDev library

        //  - In the memory layout, UDev's private device extension follows

        //    the client driver’s device extension

        pdx -> pudx= (PUDEV_EXT)((PUCHAR)pdx+ xsize);

 

  1. Setup a UDev initialization structure and call UDev_InitializeLibraryExtension. Tip: all your calls to UDev must now include the address of UDev’s private device extension. In the example below, see “pdx->pudx”.

 

        UDEV_INIT_STRUCT udis;

        RtlZeroMemory(&udis, sizeof(udis));

        udis.Size = sizeof(udis);

        udis.DeviceObject = fdo;     // Client's functional device object

        udis.Ldo = pdx->LowerDeviceObject;

        udis.Pdo = pdo;

 

        NtStatus = UDev_InitializeLibraryExtension(pdx->pudx, &udis);

 

Your StartDevice function

  1. In your StartDevice function, call UDev_StartDevice. Tip: Copy your FDO to the client context in UDEV_DEVICE for future use, such as when your driver is notified of a USB cable connection event callback. Tip: Be sure to call UDev’s other PnP functions symmetrically, such as UDev_StopDevice and UDev_RemoveDevice.

    StartDevice(

        PDEVICE_OBJECT fdo,

        PCM_PARTIAL_RESOURCE_LIST raw,

        PCM_PARTIAL_RESOURCE_LIST translated

        )

    {

        PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

        pdx->UDevDevice->ClientContext[0] = fdo; // Tip: This is useful in the event handler!

 

        NTSTATUS NtStatus = UDev_StartDevice(pdx->pudx, translated);

 

  1. Specify your unique USB device to UDev by calling UDev_SetupDevice. Advanced tip: In practice, you might choose to dynamically setup a different device at some arbitrary time.

 

        NtStatus = UDev_SetupDevice(pdx->pudx, pdx->UDevDevice);

 

  1. Enable the USB cable connection by calling UDev_UsbEnable. Tip: In practice, you might choose to enable the USB cable connection at a different time.

        UDev_UsbEnable(pdx->pudx);

 

Device Event Handler –handle “Set Configuration” – create endpoint

From previous steps, your client specified a DeviceEventHandler entry point in your UDEV_DEVICE. Your event handler examines DeviceEvent in UDEV_DEVICE. Your handler decodes the event value and takes an appropriate course of action. Events include cable connection changes (VBUS), USB reset signaling and the arrival of setup requests. Depending on the complexity of your design, many events can be ignored. For instance, UDev can handle standard USB requests for you. (Standard USB requests are described in USB 2.0: 9.4.) Tips: Working examples of device event handlers are provided in UDev distributions. Be aware that your device event handler is called at DISPATCH_LEVEL.

 

  1. Implement a device event handler in your client. For this simple tour, the event handler only takes action on the set configuration event, UDEV_RARE_DEVICE_EVENT_SET_CONFIGURATION. This important event signals your driver that a host-side USB driver associated with your specific device is running, it accepts your device’s configuration, and it is ready to start using your device. Your event handler can use this event to create endpoints, and even initiate transfers.

 

    NTSTATUS

    DeviceEventHandler(

        PUDEV_DEVICE UDevDevice

        )

    {   // Handle rare (non-transfer) USB device events

        //  - Extract FDO that was set earlier

        PDEVICE_OBJECT fdo = (PDEVICE_OBJECT)UDevDevice->ClientContext[0];

        PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

 

        switch(UDevDevice->DeviceEvent)

        {   // Out of several possible device events, ignore all but:

        case UDEV_RARE_DEVICE_EVENT_SET_CONFIGURATION:

 

  1. Call UDev_EpCreate to prepare UDev for endpoint data transfers. Call once for each endpoint your device supports. In this tour, only one endpoint is created. Note the logical endpoint parameter is the ordinal position of an endpoint descriptor in a USB configuration, plus one. (Zero is reserved for endpoint zero.) Endpoint descriptors are specified in your USB configurations. (Refer to HS_Configuration or FS_Configuration in PUDEV_DEVICE. See USB 2.0 9.6.3 for configuration content, and 9.6.6 for endpoint descriptor content.) Tips: UDev programs the USB controller’s endpoint using endpoint descriptor information provided by your client. Anytime the USB host requests a configuration, UDev’s standard request handlers return content specified in your HS_Configuration or FS_Configuration.)

            PUDEV_ENDPOINT UDevEp;

            NTSTATUS NtStatus = UDev_EpCreate(

                pdx->pudx,                      // PUDEV_EXT pudx,

                1,                              // UCHAR LogicalEp,

                UDEV_DEFAULT_ENDPOINT_MAPPING,  // UCHAR MapToNcEp,

                &UDevEp                         // PUDEV_ENDPOINT* pEp

                );

 

Your client can initiate endpoint data transfers anytime after successful endpoint creation, including from within the event handler.

 

Endpoint data transfers

  1. Call UDev_EpTransfer to initiate an endpoint data transfer. This function always returns immediately. Your transfer completes later in the form of a completion callback or an NT event. Your client describes details of its transfer request in a UDEV_EP_TRANSFER structure. This structure includes the buffer address, buffer size, transfer flags, a logical endpoint, and zero or more completion methods.

UDev applies your completion methods when the transfer completes. Tips: Your client creates and manages memory for UDEV_EP_TRANSFER structures, but calling UDev_EpTransfer and the transfer’s completion, your client must not access members or free the structure. (Exception: UDEV_EP_TRANSFER member ClientContext can be accessed at any time.) UDev_EpTransfer also applies to Endpoint Zero transfers; however it must be called at an appropriate phase of USB’s three-phase control transfer. (See USB 2.0: 8.5.3 and UDEV_RARE_DEVICE_EVENT_DEVICE_REQUEST.) Your client can initiate and run concurrent USB transfers on multiple endpoints, but UDev cannot queue multiple transfer requests on any one endpoint.

 

  1. Transfer completion: Upon completion of an endpoint data transfer, your completion method examines the same UDEV_EP_TRANSFER structure. Minimally, your client checks completion status. It can also check the number of bytes actually transferred. If appropriate, your completion method can recycle the structure and safely call UDev_EpTransfer again.

 

Tour conclusion

This concludes the UDev API tour. You are encouraged to start building your own UDev client right away. Start with examples provided in PLX distributions. These distributions include precompiled clients drivers, their matching source files, UDev development tools and more. The Checked build UDev core drivers provide a wealth of debugging information. If you have problems, cut-and-paste debug output text to an email, add details to the text and send it to us for expert evaluation and assistance.

If you have any questions or comments, please contact us.

 

Thank you,

PLX UDev development team