OSRLogo
OSRLogoOSRLogoOSRLogo x Seminar Ad
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Thu, 14 Mar 2019     118020 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
Downloads
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

UMDF 102 - Overview of a UMDF Driver

 

This article will provide an overview of what it takes to implement a UMDF driver. It assumes familiarity with the UMDF architecture, as described in UMDF 101 Understanding the User Mode Driver Framework.  Please keep in mind that the UMDF architecture is still changing. Therefore, the information presented may not reflect how the released version looks. With that in mind, let's start at the beginning.

It's a DLL

A UMDF driver is a Dynamic Link Library (DLL) and a COM object wrapped into one. Since it's a DLL, it should support DllMain, which is a well-known entry point for a DLL. Figure 1 contains the code for DllMain.

BOOL
WINAPI DllMain(
        HINSTANCE ModuleHandle,
        DWORD Reason,
        PVOID /* Reserved */)
{
    if (DLL_PROCESS_ATTACH == Reason) {
        //
       // Initialize tracing.
       //
 
        WPP_INIT_TRACING(MYDRIVER_TRACING_ID);
 
       //
       // Save the DLL handle for use when
       // registering & unregistering the server.
       //
       g_ModuleHandle = ModuleHandle;
    }
    else if (DLL_PROCESS_DETACH == Reason) {
        //
        // Cleanup tracing.
        //
 
        WPP_CLEANUP();
    }
     return TRUE;
}

  Figure 1 - DllMain

As you can see, DLLMain is pretty simple. Now we just need to look at the reason code passed to us, and then decide what processing needs to be done. If a process is being attached to this DLL for the first time, we initialize WPP tracing for the driver. We also save the ModuleHandle passed in to us for later use when registering with COM. If this DLL is being detached from, then all we do is end WPP tracing by calling WPP_CLEANUP().

 

Besides being a DLL, the UMDF driver is also a COM-based object. This DLL has to provide additional routines that get the COM-based object registered. In addition, the DLL provides routines that COM will use to get the IWDFDriver-based object created. In order to provide these routines, the following entry points must be implemented:

  • DllCanUnloadNow
  • DllGetClassObject
  • DllRegisterServer
  • DllUnregisterServer

Most of these routines are complicated, especially if you're not familiar with the basics of COM. I could kill a lot of trees by explaining these routines, but I'll refrain. Why? Well 99.9% of the code to do this registration is boilerplate that can be copied from one project to another. The one exception is the DllGetClassObject routine, which is responsible for creating an instance of the IWDFDriver-based driver by doing the necessary COM stuff. In the sample UMDF projects contained in the UMDF kit I used, each project contained dllsup.cpp and comsup.cpp that did all the work. Since we're more concerned about our driver than the COM related matters, we're going to skip a discussion of the COM material and move right into the guts of the driver.

 

The Driver

The driver we're going to talk about in this article is a sample written for the OSR USB-FX2 Learning Kit (See OSR's USB-FX2 Learning Kit -- The Way Writing Drivers Was Meant To Be). This driver is part of the UMDF version distributed in the WDF Pre-Release, handed out at the 2005 Windows Driver DevCon. Again, UMDF is still undergoing development, so its architecture and the sample driver we're going to talk about may be quite different in the initial release. Please keep this in mind when reading this article.

 

Creating an Instance of IWDFDriver Derived Class

Since we're creating a driver, the driver "object" has to first get created somehow. If this was a kernel mode driver, the I/O Manager would create a Driver Object, and then load the driver. In the case of a UMDF driver, the DLL gets loaded and eventually a routine in comsup.cpp called CClassFactory::CreateInstance would be called. This routine is shown in Figure 2.

HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance(
    __in_opt IUnknown * /* OuterObject */,
    __in REFIID InterfaceId,
    __out PVOID *Object
    ){
    HRESULT hr;

    PCMyDriver driver;

    hr = CMyDriver::CreateInstance(&driver);

    if (S_OK == hr)
    {
        hr = driver->QueryInterface(InterfaceId, Object);
        driver->Release();
    }

    return hr;
}

 Figure 2 - CClassFactory::CreateInstance

CClassFactory::CreateInstance is responsible for creating an instance of the COM object that supports IWDFDriver interfaces, which in our case is part of the CMyDriverClass shown in Figure 3. It does this by calling the CMyDriver::CreateInstance routine, which is a static function in the CMyDriver class.

class CMyDriver : public CUnknown, public IDriverEntry
{
// Private methods.
private:
    // Returns a referenced pointer to the IDriverEntry interface.
    IDriverEntry * QueryIDriverEntry(VOID){
        AddRef();
        return static_cast<IDriverEntry*>(this);
    }

    HRESULT Initialize(VOID);
//
// Public methods
//
public:
    // The factory method used to create an instance of this driver.
    static HRESULT CreateInstance(__out PCMyDriver *Driver);

//
// COM methods
//                                                           
public:

    //
    // IDriverEntry methods
    //

    virtual HRESULT STDMETHODCALLTYPE
    OnInitialize(__in IWDFDriver *FxWdfDriver) {
        return S_OK;
    }

    virtual HRESULTSTDMETHODCALLTYPE
    OnDeviceAdd(__in IWDFDriver *FxWdfDriver,
        __in IWDFDeviceInitialize *FxDeviceInit);

    virtual VOID STDMETHODCALLTYPE
    OnDeinitialize(__in IWDFDriver *FxWdfDriver) {
        return;
    }

    // IUnknown methods.
    // We have to implement basic ones here that redirect to the
    // base class because of the multiple inheritance.
    //
virtual ULONG STDMETHODCALLTYPE AddRef(VOID){
        return __super::AddRef();
    }

    virtual ULONG STDMETHODCALLTYPE Release(VOID){
        return __super::Release();
    }

    virtual HRESULTSTDMETHODCALLTYPE QueryInterface(
        __in REFIID InterfaceId,
        __out PVOID *Object);
};

  Figure 3 - CMyDriver Class Definition

The CMyDriver::CreateInstance shown in Figure 4 creates an instance of the CMyDriver class and does any initialization of this object's data members that may be required. Once the instance is created, the CClassFactory::CreateInstance routine in Figure 2 calls the QueryInterface method of the newly created instance of CMyDriver.

HRESULT
CMyDriver::CreateInstance(__out PCMyDriver *Driver)
{
    PCMyDriver driver;
    HRESULT hr;

    //
    // Allocate the callback object.
    //
    driver = new CMyDriver();

    // Do driver object initialization

..........Rest of the code intentionally omitted.....

Figure 4 - CMyDriver::CreateInstance

Why? Well, since a UDMF driver has to support an IWDFDriver-based class supporting the IDriverEntry interface, the call to QueryInterface is used to see if the newly created object supports that interface. Since our object does support that interface, we return a pointer to the objects IDriverEntry interface. Remember that by supporting an interface, we mean we support the callbacks for that interface. In the case of IDriverEntry, this means we support the OnInitialize, OnDeviceAdd, and OnDeinitialize callbacks.

 

The QueryInterface routine in Figure 5 shows how this routine is implemented. (The QueryIDriverEntry routine is in Figure 3.)

HRESULT
CMyDriver::QueryInterface(
    __in REFIID InterfaceId,
    __out PVOID *Interface
    ){
    if (IsEqualIID(InterfaceId, __uuidof(IDriverEntry)))
    {
        *Interface = QueryIDriverEntry();
        return S_OK;
    }
    else
    {
        return CUnknown::QueryInterface(InterfaceId, Interface);
    }
}

 Figure 5 - CMyDriver::QueryInterface

Whew! We have gone through a lot just to get to the point where our IWDFDriver-based object is created and ready to be used by UMDF. These steps really aren't as complicated as they sound since most of the work is done for you.

 

Creating Instance of a IWDFDevice Derived Class

Now that our IWDFDriver object instance is created, we're at the point where the driver can handle the addition of devices. In a KMDF driver this occurs when the EvtDriverDeviceAdd routine is called. In the case of UMDF, however, this occurs when the CMyDriver::OnDeviceAdd callback, shown in Figure 6, is called. As with a KMDF driver, the goal of OnDeviceAdd is to create an instance of an IWDFDevice-based class and perform any initialization that might be necessary for that class.

HHRESULT CMyDriver::OnDeviceAdd(
    __in IWDFDriver *FxWdfDriver,
    __in IWDFDeviceInitialize *FxDeviceInit)
{

    HRESULT hr;
    PCMyDevice device = NULL;

    //
    // TODO: Do any per-device initialization (reading settings from the
    //       registry for example) necessary before creating your
    //       device callback object. Otherwise you can leave such
    //       initialization to the initialization of the device event handler.
    // Create a new instance of our device callback object
    //
    hr = CMyDevice::CreateInstance(FxWdfDriver, FxDeviceInit, &device);

    //
    // TODO: Change any per-device settings the object exposes before
    //       calling Configure to let it complete its initialization.
    //
    // If that succeeded, then call the device's construct method. This
    // allows the device to create any queues or other structures it
    // needs now that the corresponding fx device object has been created.
    //
    if (S_OK == hr) {
        hr = device->Configure();
    }

.................Code intentionally omitted.

 Figure 6 - CMyDriver::OnDeviceAdd

The creation of an IWDFDevice instance is similar to what was done in the CMyDriver::CreateInstance, so we don't need to show that implementation. However, it is important to show what you might do for device initialization. As in KMDF, you have the option of setting attributes for the device. These attributes could be:

  • Exclusivity - either exclusive or not
  • Filtering - either filtering or not
  • IoType - Buffered, DirectIo, or Undefined
  • Locking - None, WdfDeviceLevel, or WdfObjectLevel
  • Power Policy Ownership - either power policy owner or not

Figure 7 shows the CMyDevice class definition that will be discussed shortly.  Figure 8 shows the initialization of the CMyDevice class by the CMyDevice::Initialize routine. The routine begins by setting the locking constraints used by this device to WdfDeviceLevel via the call to SetLockingConstraint. WdfDeviceLevel indicates all callbacks into the driver are associated with this device are serialized. This serialization includes all callbacks associated with I/O queues. Finally, we create a new UMDF IWDFDevice object and assign the new callback object to handle any device level events that occur.

class CMyDevice : public CUnknown, public IDevicePnpHardware
{
 
//
// Private data members.
//
private:
 
    IWDFDevice *m_FxDevice;
 
.....Part of class definition intentionally omitted..
 
};

 Figure 7 - CMyDevice Class Definition

HRESULT
CMyDevice::Initialize(__in IWDFDriver         * FxDriver,__in IWDFDeviceInitialize * FxDeviceInit)
{
   IWDFDevice *fxDevice;
   DWORD status;
    HRESULT hr = S_OK;
 
    //
    // Configure things like the locking model before we go to create the
    // partner device.
    //
    FxDeviceInit->SetLockingConstraint(WdfDeviceLevel);
 
    //
    // TODO: If you're writing a filter driver, then indicate that here.
    // FxDeviceInit->SetFilter();
    //
       
    //
    // TODO: Any per-device initialization that must be done before
    //       creating the partner object.
    //
    ...................Other initialization code Omitted here.............
    //
    // QueryIUnknown references the IUnknown interface that it returns.
    // This is the same as referencing the device. We pass that to
    // CreateDevice, which takes its own reference if everything works.
    //
    if (S_OK == hr) {
        IUnknown *unknown = this->QueryIUnknown();
        hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
        unknown->Release();
    }

    // If that succeeded, then set the FxDevice member variable.
    //
    if (S_OK == hr){
        m_FxDevice = fxDevice;
 
        //
        // Drop the reference we got from CreateDevice. Since this object
        // is partnered with the framework object, they have the same
        // lifespan - there is no need for an additional reference.
        //
        fxDevice->Release();
    }
 
    return hr;
}

Figure 8 - CMyDevice::Initialize

With the IWDFDevice-based object constructed, it's time to create any queues or other structures the driver needs. In this sample driver this work is done by the CMyDevice::Configure routine that creates an IWDFQueue object in much the same way the IWDFDevice object was created. In addition, a device interface for this device will be created by calling IWDFDevice::SetDeviceInterface, which makes the device's services visible to user mode.

 

But we're getting ahead of ourselves. Having queues are nice, but how do you talk to the hardware? That's next.

 

Connecting to Hardware via IDevicePnHardware::OnPrepareHardware

The CMyDevice class derives interfaces from both CUnknown and IDevicePnPHardware. Since the USB OSR FX2 device is a PnP device and we're creating a driver for that piece of hardware, the driver needs to be notified of the resources that will be used to communicate with the USB device. As mentioned in the UMDF Architecture article, communication with the device is done via the WinUSB driver. Figure 9  shows how this is accomplished. Note that we have intentionally shortened the routine in Figure 9 for clarity. OnPrepareHardware gets the device name for the device by calling IWDFDevice::GetDeviceName, which retrieves the name of the underlying kernel mode device. We do a Win32 CreateFile to open it up, and then call WinUSB_Initialize to make a connection to WinUSB. Once that is done successfully, we can call all the other WinUSB APIs in order to interact with the device. It's pretty slick, if I do say so.

HRESULT CMyDevice::OnPrepareHardware(__in IWDFDevice * /* FxDevice */)
{  
     PWSTR deviceName = NULL;
    DWORD deviceNameCch = 0;
 
    DWORD status;
    HRESULT hr;
 
..... A lot of code in this routine was intentionally omitted............
 
    //
    // Get the actual name
    //
    hr = m_FxDevice->GetDeviceName(deviceName, &deviceNameCch);
 
    // Open the device and get the handle
 
    m_Handle = CreateFile(deviceName,
                          (GENERIC_READ | GENERIC_WRITE),
                          0,
                          NULL,
                          OPEN_EXISTING,
                          FILE_FLAG_OVERLAPPED,
                          NULL);
 
    //
    // Attempt to initialize WinUSB.
    //
 
    WinUsb_Initialize(m_Handle, &m_UsbHandle))
 
    //
    // Set this as the context for the device object.
    //
 
    hr = DiscoverBulkEndpoints();
}

 Figure 9 - CMyDevice::OnPrepareHardware

Handling I/O

As in KMDF, our driver will have one or more queues from which it will receive I/O requests from the user. The IWDFQueue derived class for this driver will need to implement callbacks for interfaces such as IQueueCallbackRead and IQueueCallbackWrite. These routines will process the received IWDFIoRequest and will perform the corresponding I/O to the USB device via the WinUSB API.

There You Have It

Well, in this relatively short article we've attempted to demonstrate some of the basics of a UDMF driver. We're sure you are eager for more information, but that will have to wait until UDMF becomes more cast in stone.

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

"Will umdf driver will work as filter driver"
Which means, Can a umdf driver can talk directly to serial port driver and send AT commands and parse the AT command responses? So this kind of driver can we call it as umdf filter driver?

Rating:
13-Sep-12, david preetham


"WinUSB.sys missing (RTM WDK on WinXP SP2)"
The example is a great starting point - many thanks! As it is even included in the WDK RTM version, to build it is a breeze.

When I tried to use the Step1 sample driver (after trying it with an adapted version I got back to the plain starting point) on my WinXP SP2 development machine, however, installer complains "Cannot initialize hardware".

In the logs I can see the WDF update runs OK, but then it looks for WinUSB.sys, does not find it, and then the registration/initialisation of my USB driver fails.

Because I have seen comments of other perople about using UMDF on XP (not working upgrade from 5744 to 6000 version), I assume WinUSB.sys was installed with an earlier version of the WDK, and I got the bullet only because I installed the RTM version on a "fresh" development box.

Any idea where to find WinUSB.sys?

Thanks -Hagen

Rating:
15-Nov-06, Hagen Patzke


Post Your Comments.
Print this article.
Email this article.
bottom nav links