Click here to Skip to main content
15,886,258 members
Articles / Programming Languages / C

Developing a WDF USB Kernel Mode Driver for the OSR USB FX2

Rate me:
Please Sign up or sign in to vote.
4.94/5 (59 votes)
30 Mar 2006MIT35 min read 322.3K   10.5K   180  
This article describes the process of developing a USB Kernel mode device driver using the WDF Kernel Mode Driver Foundation.
#include "ProtoTypes.h"
#include "public.h"

#pragma alloc_text(PAGE, EvtDevicePrepareHardware)
#pragma alloc_text(PAGE, ConfigureUsbInterface)
#pragma alloc_text(PAGE, ConfigureUsbPipes)
#pragma alloc_text(PAGE, EvtDeviceAdd)
#pragma alloc_text(PAGE, CreateQueues)

/*............................................................................*/
/* the callback function that will be called whenever a new device is added.  */
/* this is for PNP devices.                                                   */
/*............................................................................*/
NTSTATUS EvtDeviceAdd(
    IN WDFDRIVER  Driver,
    IN PWDFDEVICE_INIT  DeviceInit
    )
{
  NTSTATUS status;
  WDFDEVICE device;
  PDEVICE_CONTEXT devCtx = NULL;
  WDF_OBJECT_ATTRIBUTES attributes;
  WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
  WDF_DEVICE_PNP_CAPABILITIES pnpCapabilities;

  UNREFERENCED_PARAMETER(Driver);

  /*set the callback functions that will be executed on PNP and Power events*/
  WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
  pnpPowerCallbacks.EvtDevicePrepareHardware = EvtDevicePrepareHardware;
  pnpPowerCallbacks.EvtDeviceD0Entry = EvtDeviceD0Entry;
  pnpPowerCallbacks.EvtDeviceD0Exit = EvtDeviceD0Exit;
  WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

  WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoBuffered);

  /*initialize storage for the device context*/
  WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DEVICE_CONTEXT);

  /*create a device instance.*/
  status = WdfDeviceCreate(&DeviceInit, &attributes, &device);  
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfDeviceCreate failed with status 0x%08x\n", status));
    return status;
  }

  /*set the PNP capabilities of our device. we don't want an annoying
    popup if the device is pulled out of the USB slot.*/
  WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCapabilities);
  pnpCapabilities.Removable = WdfTrue;
  pnpCapabilities.SurpriseRemovalOK = WdfTrue;
  WdfDeviceSetPnpCapabilities(device, &pnpCapabilities);
  
  devCtx = GetDeviceContext(device);

  /*create a WDF memory object for the memory that is occupied by the
    WdfMemLEDArrayState variable in the device context.
    this way we have the value itself handy for debugging purposes, and
    we have a WDF memory handle that can be used for passing to the low
    level USB functions.
    this alleviates the need to getting the buffer at run time.*/
  status = WdfMemoryCreatePreallocated(WDF_NO_OBJECT_ATTRIBUTES,
                           &devCtx->D0LEDArrayState,
                           sizeof(devCtx->D0LEDArrayState),
                           &devCtx->WdfMemLEDArrayState);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfMemoryCreatePreallocated failed with status 0x%08x\n", status));
    return status;
  }

  status = CreateQueues(device, devCtx);
  if(!NT_SUCCESS(status))
    return status;

  status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_FX2, NULL);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfDeviceCreateDeviceInterface failed with status 0x%08x\n", status));
    return status;
  }

  return status;
}

/*............................................................................*/
/* create the different IO queues that will be used by for sending different  */
/* types of requests to our driver.                                           */
/*............................................................................*/
NTSTATUS CreateQueues(WDFDEVICE Device, PDEVICE_CONTEXT Context)
{
  NTSTATUS status = STATUS_SUCCESS;

  WDF_IO_QUEUE_CONFIG ioQConfig;

  /*create the default IO queue. this one will be used for ioctl request entry.
    this queue is parallel, so as to prevent unnecessary serialization for
    IO requests that can be handled in parallel.*/
  WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQConfig,
                                          WdfIoQueueDispatchParallel);
  ioQConfig.EvtIoDeviceControl = EvtDeviceIoControlEntry;
  status = WdfIoQueueCreate(Device,
                            &ioQConfig,
                            WDF_NO_OBJECT_ATTRIBUTES,
                            &Context->IoControlEntryQueue);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfIoQueueCreate failed with status 0x%08x\n", status));
    return status;
  }

  /*create the IO queue for serialize IO requests. This queue will be filled by
    the IO control entry handler with the requests that have to be serialized
    for execution.*/
  WDF_IO_QUEUE_CONFIG_INIT(&ioQConfig,
                           WdfIoQueueDispatchSequential);
  ioQConfig.EvtIoDeviceControl = EvtDeviceIoControlSerial;
  status = WdfIoQueueCreate(Device,
                            &ioQConfig,
                            WDF_NO_OBJECT_ATTRIBUTES,
                            &Context->IoControlSerialQueue);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfIoQueueCreate failed with status 0x%08x\n", status));
    return status;
  }

  /*create the IO queue for write requests*/
  WDF_IO_QUEUE_CONFIG_INIT(&ioQConfig,
                           WdfIoQueueDispatchSequential);
  ioQConfig.EvtIoWrite = EvtDeviceIoWrite;
  status = WdfIoQueueCreate(Device,
                            &ioQConfig,
                            WDF_NO_OBJECT_ATTRIBUTES,
                            &Context->IoWriteQueue);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfIoQueueCreate failed with status 0x%08x\n", status));
    return status;
  }

  status  = WdfDeviceConfigureRequestDispatching(Device,
                                                Context->IoWriteQueue,
                                                WdfRequestTypeWrite);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfDeviceConfigureRequestDispatching failed with status 0x%08x\n",
      status));
    return status;
  }

  /*create the IO queue for read requests*/
  WDF_IO_QUEUE_CONFIG_INIT(&ioQConfig,
                           WdfIoQueueDispatchSequential);
  ioQConfig.EvtIoRead = EvtDeviceIoRead;
  status = WdfIoQueueCreate(Device,
                            &ioQConfig,
                            WDF_NO_OBJECT_ATTRIBUTES,
                            &Context->IoReadQueue);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfIoQueueCreate failed with status 0x%08x\n", status));
    return status;
  }

  status  = WdfDeviceConfigureRequestDispatching(Device,
                                                Context->IoReadQueue,
                                                WdfRequestTypeRead);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfDeviceConfigureRequestDispatching failed with status 0x%08x\n",
      status));
    return status;
  }

  /*create a manual queue for storing the IOCTL_WDF_USB_GET_SWITCHSTATE_CHANGE
	  IO control requests. If a file handle associated with one or more requests
    in the queue is closed, the requests themselves are automatically removed
    from the queue by the framework and cancelled.*/
  WDF_IO_QUEUE_CONFIG_INIT(&ioQConfig,
                           WdfIoQueueDispatchManual);
  status = WdfIoQueueCreate(Device,
                            &ioQConfig,
                            WDF_NO_OBJECT_ATTRIBUTES,
                            &Context->SwitchChangeRequestQueue);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfIoQueueCreate for manual queue failed with status 0x%08x\n", status));
    return status;
  }

  return status;
}

/*............................................................................*/
/* call-back function that will be called by the pnp/power manager after the  */
/* Plug and Play manager has assigned hardware resources to the device and    */
/* after the device has entered its uninitialized D0 state. The framework     */
/* calls the driver's EvtDevicePrepareHardware callback function before       */
/* calling the driver's EvtDeviceD0Entry callback function.                   */
/*............................................................................*/
NTSTATUS EvtDevicePrepareHardware(
    IN WDFDEVICE    Device,
    IN WDFCMRESLIST ResourceList,
    IN WDFCMRESLIST ResourceListTranslated
    )
{
  NTSTATUS status;
  PDEVICE_CONTEXT devCtx = NULL; 
  WDF_USB_CONTINUOUS_READER_CONFIG interruptConfig;

  UNREFERENCED_PARAMETER(ResourceList);
  UNREFERENCED_PARAMETER(ResourceListTranslated);

  devCtx = GetDeviceContext(Device);

  status = ConfigureUsbInterface(Device, devCtx);
  if(!NT_SUCCESS(status))
    return status;

  status = ConfigureUsbPipes(devCtx);
  if(!NT_SUCCESS(status))
    return status;

  status = InitPowerManagement(Device, devCtx);
  if(!NT_SUCCESS(status))
    return status;

  /*set up the interrupt endpoint with a continuous read operation. that
    way we are guaranteed that no interrupt data is lost.*/
  WDF_USB_CONTINUOUS_READER_CONFIG_INIT(&interruptConfig,
                                        EvtUsbDeviceInterrupt,
                                        devCtx,
                                        sizeof(BYTE));
  status = WdfUsbTargetPipeConfigContinuousReader(
                                        devCtx->UsbInterruptPipe,
                                        &interruptConfig);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfUsbTargetPipeConfigContinuousReader failed with status 0x%08x\n", status));
    return status;
  }

  return status;
}

/*............................................................................*/
/* configure the USB interface of our device                                  */
/*............................................................................*/
NTSTATUS ConfigureUsbInterface(WDFDEVICE Device, PDEVICE_CONTEXT DeviceContext)
{
  NTSTATUS status = STATUS_SUCCESS;
  WDF_USB_DEVICE_SELECT_CONFIG_PARAMS usbConfig;

  status = WdfUsbTargetDeviceCreate(Device,
                                    WDF_NO_OBJECT_ATTRIBUTES,
                                    &DeviceContext->UsbDevice);

  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfUsbTargetDeviceCreate failed with status 0x%08x\n", status));
    return status;
  }

  /*initialize the parameters struct so that the device can initialize
    and use a single specified interface.
    this only works if the device has just 1 interface.*/
  WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE(&usbConfig);

  status = WdfUsbTargetDeviceSelectConfig(DeviceContext->UsbDevice,
                                          WDF_NO_OBJECT_ATTRIBUTES,
                                          &usbConfig);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfUsbTargetDeviceSelectConfig failed with status 0x%08x\n", status));
    return status;
  }

  /*put the USB interface in our device context so that we can use it in
    future calls to our driver.*/
  DeviceContext->UsbInterface =
    usbConfig.Types.SingleInterface.ConfiguredUsbInterface;

  return status;
}

/*............................................................................*/
/* configure the different IO pipes that are available on our device          */
/*............................................................................*/
NTSTATUS ConfigureUsbPipes(PDEVICE_CONTEXT DeviceContext)
{
  NTSTATUS status = STATUS_SUCCESS;
  BYTE index = 0;
  WDF_USB_PIPE_INFORMATION pipeConfig;
  WDFUSBPIPE pipe = NULL;

  DeviceContext->UsbInterruptPipe = NULL;
  DeviceContext->UsbBulkInPipe = NULL;
  DeviceContext->UsbBulkOutPipe = NULL;
  WDF_USB_PIPE_INFORMATION_INIT(&pipeConfig);
  do
  {
    pipe = WdfUsbInterfaceGetConfiguredPipe(DeviceContext->UsbInterface,
                                          index,
                                          &pipeConfig);
    if(NULL == pipe)
      break;

    /*none of our data transfers will have a guarantee that the requested
      data size is a multiple of the packet size.*/
    WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(pipe);

    if(WdfUsbPipeTypeInterrupt == pipeConfig.PipeType)
    { 
      DeviceContext->UsbInterruptPipe = pipe;
    }
    else if(WdfUsbPipeTypeBulk == pipeConfig.PipeType)
    {
      if(TRUE == WdfUsbTargetPipeIsInEndpoint(pipe))
      {
        DeviceContext->UsbBulkInPipe = pipe;
      }
      else if(TRUE == WdfUsbTargetPipeIsOutEndpoint(pipe))
      {
        DeviceContext->UsbBulkOutPipe = pipe;
      }
    }
	index++;
  } while(NULL != pipe);

  if((NULL == DeviceContext->UsbInterruptPipe) ||
	  (NULL == DeviceContext->UsbBulkInPipe) ||
	  (NULL == DeviceContext->UsbBulkOutPipe))
  {
    KdPrint((__DRIVER_NAME
      "Not all expected USB pipes were found.\n"));
    return STATUS_INVALID_PARAMETER;
  }
  
  return status;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Belgium Belgium
I am a former professional software developer (now a system admin) with an interest in everything that is about making hardware work. In the course of my work, I have programmed device drivers and services on Windows and linux.

I have written firmware for embedded devices in C and assembly language, and have designed and implemented real-time applications for testing of satellite payload equipment.

Generally, finding out how to interface hardware with software is my hobby and job.

Comments and Discussions