Click here to Skip to main content
15,891,253 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 323K   10.5K   180  
This article describes the process of developing a USB Kernel mode device driver using the WDF Kernel Mode Driver Foundation.
#include <initguid.h>
#include "ProtoTypes.h"
#include "public.h"

#pragma alloc_text(PAGE, EvtDeviceIoRead)
#pragma alloc_text(PAGE, EvtDeviceIoWrite)
#pragma alloc_text(PAGE, EvtDeviceIoControlEntry)
#pragma alloc_text(PAGE, EvtDeviceIoControlSerial)
#pragma alloc_text(PAGE, EvtIoReadComplete)
#pragma alloc_text(PAGE, EvtIoWriteComplete)
#pragma alloc_text(PAGE, IoCtlGetSwitchPack)
#pragma alloc_text(PAGE, IoCtlGetSwitchPackChange)
#pragma alloc_text(PAGE, IoCtlSetLightBar)
#pragma alloc_text(PAGE, IoCtlGetLightBar)
#pragma alloc_text(PAGE, llSetLightBar)
#pragma alloc_text(PAGE, llGetLightBar)

/*............................................................................*/
/* callback function for handling USB interrupts.                             */
/* the switch pack state has to be bit order reversed, and bit value inverted */
/* because the physical representation of the data is not the same as the     */
/* logical representation.                                                    */
/* this function is executed at IRQL = DISPATCH                               */
/* If there is a IO control request queued for a change notification, it is   */
/* completed with the interrupt data. multiple requests can be queued, but    */
/* only the first one is completed.                                           */
/*............................................................................*/
VOID
EvtUsbDeviceInterrupt(
    WDFUSBPIPE  Pipe,
    WDFMEMORY  Buffer,
    size_t  NumBytesTransferred,
    WDFCONTEXT  Context
    )
{
  NTSTATUS status;
  BYTE temp;
  size_t size;
  PDEVICE_CONTEXT devCtx = Context;
  WDFREQUEST Request = NULL;
  BYTE *packState = WdfMemoryGetBuffer(Buffer, &size);

  UNREFERENCED_PARAMETER(Pipe);

  ASSERT(size == sizeof(BYTE));
  ASSERT(NumBytesTransferred == size);
  ASSERT(packState != NULL);
  
  temp = *packState;
  
  temp = (temp & 0x01) << 7 | 
         (temp & 0x02) << 5 | 
         (temp & 0x04) << 3 | 
         (temp & 0x08) << 1 | 
         (temp & 0x10) >> 1 | 
         (temp & 0x20) >> 3 | 
         (temp & 0x40) >> 5 | 
         (temp & 0x80) >> 7;

  KdPrint((__DRIVER_NAME "Converted switch pack from 0x%02x to 0x%02x\n",
    (ULONG)*packState, (ULONG)temp));

  devCtx->ActSwitchPack = ~temp;

  /*is there an io control queued? if so then complete the first one*/
  status = WdfIoQueueRetrieveNextRequest(devCtx->SwitchChangeRequestQueue,
                                         &Request);
  if(NT_SUCCESS(status))
  {
    BYTE* outBuffer;
    status = WdfRequestRetrieveOutputBuffer(Request,
                                            sizeof(BYTE),
                                            &outBuffer,
                                            NULL);

    if(NT_SUCCESS(status))
    {
      /*do not use the value in the device context, since that may already have
        changed because of a second interrupt while this one was handled.*/
      *outBuffer = temp;
      WdfRequestCompleteWithInformation(Request, status, sizeof(BYTE));
    }
    else
      WdfRequestComplete(Request, status);
    
    KdPrint((__DRIVER_NAME "Completed async pending IOCTL.\n"));
  }
}

/*............................................................................*/
/* callback function for handling write requests.                             */
/*............................................................................*/
VOID
EvtDeviceIoWrite(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  Length
    )
{
  NTSTATUS status = STATUS_SUCCESS;
  PDEVICE_CONTEXT devCtx = NULL;
  WDFMEMORY requestMem;

  devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));
  
  UNREFERENCED_PARAMETER(Length);

  KdPrint((__DRIVER_NAME "Received a write request of %d bytes\n", Length));

  status = WdfRequestRetrieveInputMemory(Request, &requestMem);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfRequestRetrieveInputMemory failed with status 0x%08x\n", status));
    WdfRequestComplete(Request, status);
    return;
  }

  status = WdfUsbTargetPipeFormatRequestForWrite(
                                      devCtx->UsbBulkOutPipe,
                                      Request,
                                      requestMem,
                                      NULL);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfUsbTargetPipeFormatRequestForWrite failed with status 0x%08x\n", status));
    WdfRequestComplete(Request, status);
    return;
  }
  WdfRequestSetCompletionRoutine(Request,
                                 EvtIoWriteComplete,
                                 devCtx->UsbBulkOutPipe);
  if(FALSE == WdfRequestSend(Request,
                            WdfUsbTargetPipeGetIoTarget(devCtx->UsbBulkOutPipe),
                            NULL))
  {
    KdPrint((__DRIVER_NAME "WdfRequestSend failed with status 0x%08x\n", status));
    status = WdfRequestGetStatus(Request);
  }
  else
    return;

  WdfRequestComplete(Request, status);
}

/*............................................................................*/
/* callback function for handling read requests                               */
/*............................................................................*/
VOID
EvtDeviceIoRead(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  Length
    )
{
  NTSTATUS status = STATUS_SUCCESS;
  PDEVICE_CONTEXT devCtx = NULL;
  WDFMEMORY requestMem;

  devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));

  UNREFERENCED_PARAMETER(Length);
  KdPrint((__DRIVER_NAME "Received a read request of %d bytes\n", Length));

  status = WdfRequestRetrieveOutputMemory(Request, &requestMem);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfRequestRetrieveOutputMemory failed with status 0x%08x\n", status));
    WdfRequestComplete(Request, status);
    return;
  }

  status = WdfUsbTargetPipeFormatRequestForRead(
                                      devCtx->UsbBulkInPipe,
                                      Request,
                                      requestMem,
                                      NULL);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfUsbTargetPipeFormatRequestForRead failed with status 0x%08x\n", status));
    WdfRequestComplete(Request, status);
    return;
  }
  WdfRequestSetCompletionRoutine(Request,
                                 EvtIoReadComplete,
                                 devCtx->UsbBulkInPipe);
  if(FALSE == WdfRequestSend(Request,
                            WdfUsbTargetPipeGetIoTarget(devCtx->UsbBulkInPipe),
                            NULL))
  {
    KdPrint((__DRIVER_NAME "WdfRequestSend failed with status 0x%08x\n", status));
    status = WdfRequestGetStatus(Request);
  }
  else
    return;

  WdfRequestComplete(Request, status);

}

/*............................................................................*/
/* entry point for all device IO control operations. here the driver will     */
/* determine what has to happen with the request. change requests get put on  */
/* the manual queue. all other requests get passed to the serialized queue.   */
/*............................................................................*/
VOID
EvtDeviceIoControlEntry(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  OutputBufferLength,
    IN size_t  InputBufferLength,
    IN ULONG  IoControlCode
    )
{
  switch(IoControlCode)
  {
  case IOCTL_WDF_USB_GET_SWITCHSTATE:
    IoCtlGetSwitchPack(Queue, Request, OutputBufferLength, InputBufferLength);
    break;
  case IOCTL_WDF_USB_GET_SWITCHSTATE_CHANGE:
    IoCtlGetSwitchPackChange(Queue, Request, OutputBufferLength, InputBufferLength);
    break;
  default:
    {
      PDEVICE_CONTEXT devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));
      WdfRequestForwardToIoQueue(Request, devCtx->IoControlSerialQueue);
    }
    break;
  }
}

/*............................................................................*/
/* callback function for handling serialized device IO control operations.    */
/*............................................................................*/
VOID
EvtDeviceIoControlSerial(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  OutputBufferLength,
    IN size_t  InputBufferLength,
    IN ULONG  IoControlCode
    )
{
  switch(IoControlCode)
  {
  case IOCTL_WDF_USB_SET_LIGHTBAR:
    IoCtlSetLightBar(Queue, Request, OutputBufferLength, InputBufferLength);
    break;
  case IOCTL_WDF_USB_GET_LIGHTBAR:
    IoCtlGetLightBar(Queue, Request, OutputBufferLength, InputBufferLength);
    break;
  default:
    WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
    break;
  }
}

/*............................................................................*/
/* callback function for signalling the completion of a read request.         */
/*............................................................................*/
VOID
EvtIoReadComplete(
    IN WDFREQUEST  Request,
    IN WDFIOTARGET  Target,
    IN PWDF_REQUEST_COMPLETION_PARAMS  Params,
    IN WDFCONTEXT  Context)
{
  PWDF_USB_REQUEST_COMPLETION_PARAMS usbCompletionParams;

  UNREFERENCED_PARAMETER(Context);
  UNREFERENCED_PARAMETER(Target);

  usbCompletionParams = Params->Parameters.Usb.Completion;

  if(NT_SUCCESS(Params->IoStatus.Status))
  {
    KdPrint((__DRIVER_NAME "Completed the read request with %d bytes\n",
          usbCompletionParams->Parameters.PipeRead.Length));
  }
  else
  {
    KdPrint((__DRIVER_NAME "Failed the read request with status 0x%08x\n",
          Params->IoStatus.Status));
  }
  WdfRequestCompleteWithInformation(Request,
                                    Params->IoStatus.Status,
                                    usbCompletionParams->Parameters.PipeRead.Length);
}

/*............................................................................*/
/* callback function for signalling the completion of a write request.        */
/*............................................................................*/
VOID
EvtIoWriteComplete(
    IN WDFREQUEST  Request,
    IN WDFIOTARGET  Target,
    IN PWDF_REQUEST_COMPLETION_PARAMS  Params,
    IN WDFCONTEXT  Context)
{
  PWDF_USB_REQUEST_COMPLETION_PARAMS usbCompletionParams;

  UNREFERENCED_PARAMETER(Context);
  UNREFERENCED_PARAMETER(Target);

  usbCompletionParams = Params->Parameters.Usb.Completion;

  if(NT_SUCCESS(Params->IoStatus.Status))
  {
    KdPrint((__DRIVER_NAME "Completed the write request with %d bytes\n",
          usbCompletionParams->Parameters.PipeWrite.Length));
  }
  else
  {
    KdPrint((__DRIVER_NAME "Failed the read request with status 0x%08x\n",
          Params->IoStatus.Status));
  }
  WdfRequestCompleteWithInformation(Request,
                                    Params->IoStatus.Status,
                                    usbCompletionParams->Parameters.PipeWrite.Length);
}

/*............................................................................*/
/* return the current value of the switch pack state.                         */
/*............................................................................*/
VOID
IoCtlGetSwitchPack(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  OutputBufferLength,
    IN size_t  InputBufferLength)
{
  NTSTATUS status = STATUS_SUCCESS;
  BYTE *outChar = NULL;
  size_t length = 0;
  PDEVICE_CONTEXT devCtx = NULL;

  UNREFERENCED_PARAMETER(InputBufferLength);

  if(OutputBufferLength < sizeof(BYTE))
  {
    KdPrint((__DRIVER_NAME
        "IOCTL_WDF_USB_GET_SWITCHSTATE OutputBufferLength < sizeof(BYTE)\n"));
    WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
    return;
  }

  status = WdfRequestRetrieveOutputBuffer(Request,
                                          sizeof(BYTE),
                                          &outChar,
                                          &length);

  if(NT_SUCCESS(status))
  {
    ASSERT(length >= sizeof(BYTE));
    devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));
    *outChar = devCtx->ActSwitchPack;
  }

  WdfRequestCompleteWithInformation(Request, status, sizeof(BYTE));
}

/*............................................................................*/
/* Get a the value of the switch pack, but only when a new value is received. */
/* this means that the IO control request is queued in the manual queue until */
/* it can be completed by the USB interrupt callbcack function.               */
/*............................................................................*/
VOID
IoCtlGetSwitchPackChange(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  OutputBufferLength,
    IN size_t  InputBufferLength)
{
  NTSTATUS status = STATUS_SUCCESS;
  PDEVICE_CONTEXT devCtx = NULL;

  UNREFERENCED_PARAMETER(InputBufferLength);
  UNREFERENCED_PARAMETER(OutputBufferLength);
  UNREFERENCED_PARAMETER(Queue);

  devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));

  /*If the request is succesfull the request ownership is also transferred
    back to the framework.*/
  status = WdfRequestForwardToIoQueue(Request,
                                      devCtx->SwitchChangeRequestQueue);
  /*if the request cannot be forwarded it has to be completed with
    the appropriate status code because the driver still owns the request.*/
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
        "WdfRequestForwardToIoQueue failed with code 0x%08x.\n", status));
    WdfRequestComplete(Request, status);
  }
}

/*............................................................................*/
/* set the state of the LEDs in the LED array.                                */
/*............................................................................*/
VOID
IoCtlSetLightBar(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  OutputBufferLength,
    IN size_t  InputBufferLength)
{
  NTSTATUS status = STATUS_SUCCESS;
  PDEVICE_CONTEXT devCtx = NULL;
  WDFMEMORY memHandle = NULL;

  UNREFERENCED_PARAMETER(OutputBufferLength);

  if(InputBufferLength < sizeof(BYTE))
  {
    KdPrint((__DRIVER_NAME
        "IOCTL_WDF_USB_SET_LIGHTBAR InputBufferLength < sizeof(BYTE)\n"));
    WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
    return;
  }

  devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));
  
  status = WdfRequestRetrieveInputMemory(Request, &memHandle);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "Could not retrieve the lightbar memory handle\n"));
    WdfRequestComplete(Request, status);
    return;
  }

  status = llSetLightBar(devCtx, memHandle);

  WdfRequestCompleteWithInformation(Request, status, sizeof(BYTE));
}

/*............................................................................*/
/* get the state of the LEDs in the LED array.                                */
/*............................................................................*/
VOID
IoCtlGetLightBar(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  OutputBufferLength,
    IN size_t  InputBufferLength)
{
  NTSTATUS status = STATUS_SUCCESS;
  PDEVICE_CONTEXT devCtx = NULL;
  WDFMEMORY memHandle = NULL;
    
  KdPrint((__DRIVER_NAME "entering IoCtlGetLightBar\n"));
  UNREFERENCED_PARAMETER(InputBufferLength);

  if(OutputBufferLength < sizeof(BYTE))
  {
    KdPrint((__DRIVER_NAME
        "IOCTL_WDF_USB_SET_LIGHTBAR OutputBufferLength < sizeof(BYTE)\n"));
    WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
    return;
  }

  devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));

  status = WdfRequestRetrieveOutputMemory(Request, &memHandle);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "Could not retrieve the lightbar memory handle\n"));
    WdfRequestComplete(Request, status);
    return;
  }

  status = llGetLightBar(devCtx, memHandle);

  WdfRequestCompleteWithInformation(Request, status, sizeof(BYTE));
}

/*............................................................................*/
/* low level USB function that sets the state of the LED array.               */
/*............................................................................*/
NTSTATUS
llSetLightBar(
    IN PDEVICE_CONTEXT Context,
    IN WDFMEMORY State
    )
{
  NTSTATUS status = STATUS_SUCCESS;
  WDF_USB_CONTROL_SETUP_PACKET controlPacket;
  BYTE physVal = 0;
  WDF_MEMORY_DESCRIPTOR memDescriptor;
  BYTE *inChar = NULL;
  size_t length = 0;

  inChar = WdfMemoryGetBuffer(State, &length);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "Could not retrieve the lightbar memory pointer\n"));
    return status;
  }

  ASSERT(length >= sizeof(BYTE));

  /*translate the supplied logical value to a value that represents the
    values of the LEDs in the pyhsical light array. we copy the value
    back into the memory buffer because we will reuse it for the vendor
    command*/
  physVal = ((*inChar & 0x07) << 5) | ((*inChar & 0xF8) >> 3);
  KdPrint((__DRIVER_NAME "Original value = 0x%x, new value = 0x%x\n",
    *inChar, physVal));
  *inChar = physVal;

  WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&memDescriptor, State, NULL);

  WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR(
                            &controlPacket,
                            BmRequestHostToDevice,
                            BmRequestToDevice,
                            VC_SET_LIGHT_BAR,
                            0,
                            0);

  status = WdfUsbTargetDeviceSendControlTransferSynchronously(
                            Context->UsbDevice,
                            NULL,
                            NULL,
                            &controlPacket,
                            &memDescriptor,
                            NULL);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfUsbTargetDeviceSendControlTransferSynchronouslyfailed with status 0x%08x\n",
      status));
    return status;
  }

  return status;
}

/*............................................................................*/
/* low level USB function that retrieves the state of the LED array.          */
/*............................................................................*/
NTSTATUS
llGetLightBar(
    IN PDEVICE_CONTEXT Context,
    IN WDFMEMORY State
    )
{
  NTSTATUS status = STATUS_SUCCESS;
  WDF_USB_CONTROL_SETUP_PACKET controlPacket;
  WDF_MEMORY_DESCRIPTOR memDescriptor;
  BYTE logicalVal = 0;
  BYTE *inChar = NULL;
  size_t length = 0;

  KdPrint((__DRIVER_NAME "entering llGetLightBar\n"));

  inChar = WdfMemoryGetBuffer(State, &length);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "Could not retrieve the lightbar memory pointer\n"));
    return status;
  }
  
  ASSERT(length >= sizeof(BYTE));
  ASSERT(NULL != inChar);

  /*initialize the descriptor that will be passed to the USB driver*/
  WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&memDescriptor, State, NULL);

  WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR(
                            &controlPacket,
                            BmRequestDeviceToHost,
                            BmRequestToDevice,
                            VC_GET_LIGHT_BAR,
                            0,
                            0);

  status = WdfUsbTargetDeviceSendControlTransferSynchronously(
                            Context->UsbDevice,
                            NULL,
                            NULL,
                            &controlPacket,
                            &memDescriptor,
                            NULL);
  if(!NT_SUCCESS(status))
  {
    KdPrint((__DRIVER_NAME
      "WdfUsbTargetDeviceSendControlTransferSynchronouslyfailed with status 0x%08x\n",
      status));
    return status;
  }

  /*translate the supplied physical value to a value that represents the
    values of the LEDs in the logical light array.*/
  logicalVal = ((*inChar & 0x1F) << 3) | ((*inChar & 0xE0) >> 5);
  KdPrint((__DRIVER_NAME "Original value = 0x%x, new value = 0x%x\n",
    *inChar, logicalVal));
  *inChar = logicalVal;
  
  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