OSRLogo
OSRLogoOSRLogoOSRLogo x Subscribe to The NT Insider
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

KMDF Filter Driver: 30-Minutes -- Installation: Ah...Somewhat Longer


 Click Here to Download: Code Associated With This Article Zip Archive, 29 KB

 

If you're like me, first of all - I'm sorry. Second of all, you must frequently find yourself needing to write a quick, throwaway filter for some incredibly annoying purpose. For example, we recently had an issue where reads to certain regions on the disk appeared to be causing us a great deal of pain. To isolate the offending code path, I gutted all of the WMI crap out of DiskPerf (for what felt like the 500th time) and added a DbgBreakPoint when an IRP that matched my parameters came in. This worked great and all, but after giving it a closer look my confidence in the DiskPerf code started to wane (any code that still does *nextIrpStack = *currentIrpStack doesn't belong anywhere but my recycle bin). Even though I'm only using this filter as a one-off, I still want it to be correct. So, I decided that there had to be a better way.

Before we start, I'm assuming you already have a vague idea as to how to write a KMDF driver. If you have no clue what a WDFDEVICE_INIT structure is or what the hell an I/O event callback is, then you should check out some of our previous KMDF articles such as A Soft Life - Implementing Software-Only Drivers Using KMDF.

Writing a KMDF Filter: It's Easy, Really!

I decided that instead of going the route of writing a generic filter driver framework in WDM, I'd stay on the bleeding edge and try my hand at a KMDF filter. As it turns out, writing a filter in KMDF is a fairly pleasant experience. By the time I had the basic code in place that spit requests out in the debugger, I felt like I had barely done anything. So much so that I decided to throw in some code to send the I/O request information back to user mode. A few hundred lines later, all that was left to do was install this chicken and see if it would fly. But, I'm getting a bit ahead of myself...

The Basics

The heart of a KMDF filter driver is its call to WdfFdoInitSetFilter during EvtDeviceAdd. This indicates to the framework that your device is indeed a filter and results in all sorts of great things being done for you. Two examples of these great things being:

  1. The automatic passing of any requests that your driver does not handle to the next driver.
  2. Your underlying WDM device object's DeviceType, Characteristics, and Flags fields being properly set based on the device to which you're attached.

The first feature listed above is important to stress since it is a major distinction between a WDM filter and a KMDF filter. In the WDM world, the default behavior provided for unhandled requests is a kernel routine that completes the request with STATUS_INVALID_DEVICE_REQUEST. This makes writing a WDM filter from scratch time consuming because the first thing you need to do is write the boilerplate code for the "correct default" behavior (trivial for most requests, but not necessarily for PnP). However, by default KMDF provides all of the correct default behavior. This means that instead of wasting your time writing code to deal with requests that you have no interest in, you can get right to the things that are important to you. It is important to note that this feature doesn't apply just to the I/O callbacks, but also to all callbacks available in KMDF.

 

That being said, see Figures 1 (DriverEntry) & 2 (EvtDeviceAdd) for a filter that isn't interested in any requests whatsoever.

NTSTATUS
DriverEntry(
    PDRIVER_OBJECT DriverObject,
    PUNICODE_STRING RegistryPath
    ) {

    WDF_DRIVER_CONFIG config;
    NTSTATUS          status;

    WDF_DRIVER_CONFIG_INIT(&config,
                           WdfFltrEvtDeviceAdd);

    //
    // Create the framework driver object...
    //
    status = WdfDriverCreate(DriverObject,
                             RegistryPath,
                             WDF_NO_OBJECT_ATTRIBUTES,
                             &config,
                             NULL);
    if (!NT_SUCCESS(status)) {
        WdfFltrTrace(("WdfDriverCreate failed - 0x%x\n", status));
        return status;
    }

    return STATUS_SUCCESS;

}

Figure 1 - DriverEntry

 

NTSTATUS
WdfFltrEvtDeviceAdd(
    IN WDFDRIVER Driver,
    IN PWDFDEVICE_INIT  DeviceInit
    ) {

    NTSTATUS                     status;
    WDF_OBJECT_ATTRIBUTES        wdfObjectAttr;
    WDFDEVICE                    wdfDevice;
    PDEVICE_CONTEXT              devCont;

    //
    // We're a filter, so mark our device init structure
    //  as such. This will do all sorts of lovely things,
    //  such as pass requests that we don't care about
    //  to the FDO unharmed...
    //
    WdfFdoInitSetFilter(DeviceInit);

    //
    // We don't know what our device type is going to be yet
    //  (we're an agnostic filter, remember?) So mark it as
    //  UNKNOWN. When we call WdfDeviceCreate the framework
    //  will do all the legwork for us to determine what the
    //  appropriate type is.
    //
    WdfDeviceInitSetDeviceType(DeviceInit,
                               FILE_DEVICE_UNKNOWN);

    //
    // Setup our device attributes to have our context type
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfObjectAttr,
                                            DEVICE_CONTEXT);

    //
    // And create our WDF device. This does a multitude of things,
    //  including:
    //
    //  1) Create a WDM device object
    //  2) Attach the device object to the filtered device object
    //  3) Propagate all the flags and characteristics of the
    //     target device to our filter device. So, for example, if
    //     the target device is setup for direct I/O our filter
    //     device will also be setup for direct I/O
    //
    status = WdfDeviceCreate(&DeviceInit,
                             &wdfObjectAttr,
                             &wdfDevice);

    if (!NT_SUCCESS(status)) {
        WdfFltrTrace(("WdfDeviceCreate failed - 0x%x\n", status));
        return status;
    }

    //
    // Get our filter context
    //
    devCont = WdfFltrGetDeviceContext(wdfDevice);

    //
    // Initialize our context...
    //
    devCont->MagicNumber = DEVICE_CONTEXT_MAGIC;

    //
    // Success!
    //
    return STATUS_SUCCESS;

}

Figure 2 - EvtDeviceAdd

 

It doesn't make for the world's most useful filter, but it illustrates the point of how little boilerplate code needs to be written. It's definitely a great thing when you can write a filter driver skeleton with a full PnP state machine in 99 lines of code. 

With the core of the filter done, it's time to add some I/O event callbacks. This requires a few extra steps than the code included above, but nothing that should be too shocking for those already familiar with the framework. This code includes the EvtDeviceAdd section of the previous code and additional code, included in bold, that is necessary to process write requests. See Figure 3.

NTSTATUS
WdfFltrEvtDeviceAdd(
    IN WDFDRIVER Driver,
    IN PWDFDEVICE_INIT  DeviceInit
    ) {

    NTSTATUS                     status;
    WDF_OBJECT_ATTRIBUTES        wdfObjectAttr;
    WDFDEVICE                    wdfDevice;
    PDEVICE_CONTEXT              devCont;
    WDF_IO_QUEUE_CONFIG          ioQueueConfig;

    //
    // We're a filter, so mark our device init structure
    //  as such. This will do all sorts of lovely things,
    //  such as pass requests that we don't care about
    //  to the FDO unharmed...
    //
    WdfFdoInitSetFilter(DeviceInit);

    //
    // We don't know what our device type is going to be yet
    //  (we're an agnostic filter, remember?) So mark it as
    //  UNKNOWN. When we call WdfDeviceCreate the framework
    //  will do all the legwork for us to determine what the
    //  appropriate type is.
    //
    WdfDeviceInitSetDeviceType(DeviceInit,
                               FILE_DEVICE_UNKNOWN);

    //
    // Setup our device attributes to have our context type
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfObjectAttr,
                                            DEVICE_CONTEXT);

    //
    // And create our WDF device. This does a multitude of things,
    //  including:
    //
    //  1) Create a WDM device object
    //  2) Attach the device object to the filtered device object
    //  3) Propagate all the flags and characteristics of the
    //     target device to our filter device. So, for example, if
    //     the target device is setup for direct I/O our filter
    //     device will also be setup for direct I/O
    //
    status = WdfDeviceCreate(&DeviceInit,
                             &wdfObjectAttr,
                             &wdfDevice);

    if (!NT_SUCCESS(status)) {
        WdfFltrTrace(("WdfDeviceCreate failed - 0x%x\n", status));
        return status;
    }

    //
    // Get our filter context
    //
    devCont = WdfFltrGetDeviceContext(wdfDevice);

    //
    // Initialize our context...
    devCont->MagicNumber = DEVICE_CONTEXT_MAGIC;

    // Figure out where we'll be sending all our requests
    //  once we're done with them
    //
    devCont->TargetToSendRequestsTo = WdfDeviceGetIoTarget(wdfDevice);

    //
    // Now that this step is completed, we can create our default queue.
    //  This queue will allow us to pick off any I/O requests
    //  that we may be interested in before they are forwarded
    //  to the target device.
    //
    // Unless you have a compelling reason to do otherwise, you should
    //  create a parallel queue so the framework won?t add any
    //  synchronization to your callbacks. This, along with the fact
    //  that by default a filter device?s queue are set to NOT be
    //  power managed, allows the filtered device to decide what sort
    //  of queuing and serialization should be applied to its queues
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig,
                                           WdfIoQueueDispatchParallel);

    //
    // We just want writes for now...
    //
    ioQueueConfig.EvtIoWrite = WdfFltrWrite;

    //
    // Create the queue...
    //
    status = WdfIoQueueCreate(wdfDevice,
                              &ioQueueConfig,
                              WDF_NO_OBJECT_ATTRIBUTES,
                              NULL);

    if (!NT_SUCCESS(status)) {
         //
         // No need to delete the WDFDEVICE we created, the framework
         //  will clean that up for us.
        //
        WdfFltrTrace(("WdfIoQueueCreate failed - 0x%x\n", status));
        return status;
    }

    //
    // Success!
    //
    return STATUS_SUCCESS;

}

Figure 3 - Processing Write Requests

 

The only piece to note is the setting of the TargetToSendRequestsTo field of the device context. For many, but not all, of the various callbacks that you register for, the framework makes you responsible for either completing the request in your filter or passing the request to the next device. In order to forward requests that you are done with, you need a WDFIOTARGET. You can retrieve the I/O target of the device you are attached to by calling WdfDeviceGetIoTarget on your filter's WDFDEVICE.

Of course, knowing which callbacks auto-forward the request to the next device, and which do not isn't exactly obvious or consistent (e.g., create requests are not forwarded but closes are). A good rule of thumb appears to be, "If the callback is passed a WDFREQUEST as a parameter, you must either complete it or pass it along."

Implementing an I/O Callback

Once the EvtDeviceAddcallback is written, it's just a matter of implementing the four I/O callbacks. Because the differences between the callbacks are negligible, I will only show the code for the write callback (Figure 4). The rest of the callbacks can be found in their entirety in the source code supplied with this article (top of page).

VOID
WdfFltrWrite(

    IN WDFQUEUE Queue,
    IN WDFREQUEST Request,
    IN size_t Length
    ) {

    PUCHAR                   dataBuffer = NULL;
    PIRP                     wdmWriteIrp;
    WDF_REQUEST_SEND_OPTIONS options;
    PDEVICE_CONTEXT          devCont;
    NTSTATUS                 status;

    //
    // Get our device context back so that we can forward
    //  the request to the appropriate location when we're
    //  done.
    //
    devCont = WdfFltrGetDeviceContext(WdfIoQueueGetDevice(Queue));

    //
    // Retrieve some parameters of the request to print
    //  to the debugger
    //
    if (Length != 0) {

        //
        // This is a non-zero length transfer, retrieve
        //  the data buffer.
        //

        status = WdfRequestRetrieveInputBuffer(Request,
                                               Length,
                                               (PVOID *)&dataBuffer,
                                               NULL);

        if (!NT_SUCCESS(status)) {
            //
            // Not good. We'll still pass the request down
            //  and let the next device decide what to do with
            //  this.
            //
            WdfFltrTrace(("RetrieveInputBuffer failed - 0x%x\n",

                         status));
        }

    }

    //
    // Get the WDM IRP
    //
    wdmWriteIrp = WdfRequestWdmGetIrp(Request);

#if DBG
    DbgPrint("WdfFltrWrite: IRP-0x%p; Buffer-0x%p; Length-0x%x\n",
        wdmWriteIrp, dataBuffer, Length);
#endif

    //
    // We've done everything that we wanted to do with this
    //  request. Pass it along.
    //
    WDF_REQUEST_SEND_OPTIONS_INIT(
                        &options,
                        WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET);

    if (!WdfRequestSend(Request,
                        devCont->TargetToSendRequestsTo,
                        &options)) {

        //
        // Yikes! We can't forward it, so complete it
        //
        status = WdfRequestGetStatus(Request);
        WdfFltrTrace(("WdfRequestSend failed - 0x%x\n", status));
        WdfRequestComplete(Request, status);
    }
    return;
}

Figure 4 - I/O Callbacks-Write

 

It's hard to complain too much about this, so I'll only complain a little. I really would have preferred a more intuitive way to indicate to the framework that I was done with the request. All they've managed to achieve in these I/O event callbacks is exchange the IoSkipCurrentIrpStack Location/IoCallDriver sequence for an extra 16 byte variable on the stack, a macro with a 41 character constant as a parameter, and three function calls. But, I digress...

What about the non-I/O Callbacks?

There are several other callbacks that a KMDF device can register for including, but not limited to, those listed in Table 1.

File Object

 

EvtDeviceFileCreate

EvtFileCleanup

EvtFileClose

PnP/Power

Callbacks

EvtDeviceD0Entry

EvtDeviceD0Exit

EvtDeviceQueryStop

EvtDeviceQueryRemove

EvtDeviceSurpriseRemoval

Table 1 - Non I/O Callbacks

 

Registering for any of these callbacks is done exactly as it would be in a KMDF functional driver. However, a word of warning comes with the EvtDeviceFileCreate callback, which receives a WDFREQUEST as a parameter that must be manually forwarded to the next device or completed. The rest of the callbacks listed above require no special processing.

A Couple of "Advanced" Features

After I bolted through writing the basic framework to print-out, read, write, and device control requests, I realized that two key features were missing. The first missing feature was printing the completion status of the requests.

Setting Up a Completion Routine

Setting a completion routine in a request is only slightly more annoying than doing a "send and forget" operation. The WDF_REQUEST_SEND_OPTIONS structure is no longer required, but two additional function calls (Figure 5) are:

WdfRequestFormatRequestUsingCurrentType
WdfRequestSetCompletionRoutine

(Anyone else starting to think that maybe we should up the acceptable norm from 80 characters to 160?)

 //
 // Setup the request for the next driver
 //
 WdfRequestFormatRequestUsingCurrentType(Request);
 
 //
 // Set the completion routine...
 //
 WdfRequestSetCompletionRoutine(Request,
                                CompletionRoutine,
                                CompletionContext);
 //
 // And send it!
 //
 if (!WdfRequestSend(Request,
                     devCont->TargetToSendRequestsTo,
                     NULL)) {
 
     status = WdfRequestGetStatus(Request);
     WdfRequestComplete(Request, status);
 }

Figure 5 - WdfRequestFormatRequestUsingCurrentType
                    And WdfRequestSetCompletionRoutine

 

In terms of processing the completion routine, things are fairly standard with one minor "gotcha" that shouldn't be new to anyone who has been using the framework for a while. If you look at the prototype for a KMDF completion routine, you'll notice it has a VOID return value (See Figure 6).

typedef VOID
  (*PFN_WDF_REQUEST_COMPLETION_ROUTINE)(
    IN WDFREQUEST  Request,
    IN WDFIOTARGET  Target,
    IN PWDF_REQUEST_COMPLETION_PARAMS  Params,
    IN WDFCONTEXT  Context
    );

Figure 6 - Prototype Completion Routine

 

Therefore, there is no way to indicate to the framework that either: a) you are done with the request; or b) you wish to reclaim it for further processing by using a return value as you would in WDM. In KMDF you indicate that you are done with the request by calling WdfRequestComplete from within the completion routine (See Figure 7).

VOID
WdfFltrReadComplete(
    IN WDFREQUEST Request,
    IN WDFIOTARGET Target,
    IN PWDF_REQUEST_COMPLETION_PARAMS Params,
    IN WDFCONTEXT Context
    ) {

 

Figure 7 - WdfRequestComplete

 

If you instead wanted to reclaim the KMDF request, you would simply store the request away for later processing and return from the completion callback without calling WdfRequestComplete.

Up to this point the filter I created was fine. But what if I wanted to see the actual WDM PNP or POWER IRPs that were sent to the device I was filtering?

Setting Up Callbacks for WDM

While your KMDF driver sees the abstraction of WDM requests, such as the transition from S0 to Sx, it normally doesn't see the IRPs because KMDF processes these requests internally. Fortunately, KMDF provides an escape mechanism that allows you to catch these IRPs before KMDF deals with them.

In order to do this you need to make a modification to your DeviceInit structure before you create your filter device in your EvtDeviceAdd callback. This involves making a call to WdfDeviceInitAssignWdmIrpPreprocessCallback and specifying the major and minor function codes that you're interested in (or none if you're interested in them all). In my case I decided to setup a callback for IRP_MJ_PNP requests (See Figure 8).

status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
                                DeviceInit,
                                WdfFltrWdmPnp,
                                IRP_MJ_PNP,
                                NULL,
                                0);

...
status = WdfDeviceCreate(&DeviceInit,
                             &wdfObjectAttr,
                             &wdfDevice);

Figure 8 - Setting Up Callbacks for WDM (IRP_MJ_PNP)

 

At this point you need to implement a callback whose job in life is to setup the next stack location and pass the request back to the framework using the WdfDeviceWdmDispatch PreprocessedIrp DDI. This results in the callback looking something like Figure 9.

NTSTATUS
WdfFltrWdmPnp(
    WDFDEVICE Device,
    PIRP Irp
    ) {

    IoSkipCurrentIrpStackLocation(Irp);

    //
    // Give the request back to the framework
    //
    return WdfDeviceWdmDispatchPreprocessedIrp(Device,
                                               Irp);
}

Figure 9 - Setting Up The Next Stack Location

 

And Then There Was Installation...

Once your nifty new filter is written, it's time to install it. This was easily the most frustrating part of writing the filter because it took quite a bit of time to get right (hence the subtitle to this article).

I typically install my class filters using a simple INF file (based on diskperf.inf) and Right Click->Install. As everyone who has tried installing a real driver this way knows, very little of the INF is actually processed. However, because all that is required for a class filter is a Services key and an {Upper|Lower}Filters value in the appropriate location, it actually works quite well. Alas, this tried and true method will no longer work since a KMDF class filter is a different beast.

The Coinstaller

The main problem is the WDF coinstaller. Unless the coinstaller is called both before and after the filter service is created, your driver has absolutely zero chance of working properly. Unfortunately, currently there is no way to coerce the install process for non-hardware based INFs to call into the coinstaller. Therefore, installation of a KMDF based class filter requires a separate installation program with a touch of Secret Monkey Voodoo and a dash of Magical Incantation to make it work.

WdfPreDeviceInstall/WdfPostDeviceInstall

The trick to making this work lies in two functions exported by WdfCoInstaller01000.dll: WdfPreDeviceInstall and WdfPostDeviceInstall. By combining these function calls with the (well-worn) InstDrv sample from the NT4 DDK, you have a recipe for eventual success. Instead of going into a long diatribe about how to put all of this sauce together, I'll refer you to the "nonpnp" sample in the WDF distribution or the "wdffltrinstall" application provided with the source for the filter described in this article is supplied (top of page). All I will do here is lay out some guidelines that will hopefully keep you from wasting as much time as I did.

1.       Make sure that the service start type used when calling CreateService absolutely matches what you need. Installing a floppy filter? Demand start should be OK. Installing a disk filter? Make sure you're set to boot start. The problem is that the coinstaller installs the KMDF library driver to match your driver. Deciding that you didn't want to be demand start and flipping the bits in the registry to make yourself boot start doesn't work as well as expected (which is to say, "didn't work at all for me").

2.       If your driver is having trouble loading, set a breakpoint in the KMDF DriverEntry routine ('bu drivername!FxDriverEntry') and set WDFLDR!WdfLdrDiags to TRUE ('eb WDFLDR!WdfLdrDiags 1'). This will print out some diagnostics that might be helpful.

3.       The KMDF coinstaller logs to %SystemRoot%\setupact.log, not %SystemRoot%\setupapi.log.

4.       Enable SR and create a restore point. Trust me, you'll probably need it. :-)

Full Sample Available Online

The only piece left is the creation of a control device object and interfacing to a user mode application. The code is straightforward and not particularly interesting, so I'll leave an analysis/explanation of it as an exercise for the reader. Again, all associated code can be downloaded from the link at the top of this page.

Happy filtering!

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

"Not filtering read and writes"
Hello,

I have built this sample for Win 7 x86 checked version.

When I am running the IOMeter, my windbg is not breaking in read and write, but did break in devicecontrol, internaldevicecointrol and pnp events.

I need to filter read and write IO requests.

My environment: 1. Virtualbox VM and virtualkd integration with Windbg 2. Added the wdffltr as the lower disk class driver in the registry 3. Win 7 x86 OS in the VM. 4. WDF Coinstaller is 1.10.

What I am missing?

Thank you,

Rating:
17-Dec-12, Rajib Ghosal


"RE: Windows 7"
The installer is looking for an ancient version of the KMDF co-installer. You probably need to change the filter installation utility to use the 1.9 installer and change the version number to 1.9.

09-Feb-11, Scott Noone


"Windows 7"
Seems It does not work on Windows 7. Any updated information on why it fails in Windows 7.

Rating:
09-Feb-11, mahantesh g


"Device Type Issue (?)"
I am a newbie to device driver writing and have been studying this filter driver to educate myself. One thing that puzzles me is that in the header file (wdffltr_ioctl.h)for this filter, the device type is specified as 0x0F59 in the following statements:

----------------- // // The following value is arbitrarily chosen from the space defined by Microsoft // as being "for non-Microsoft use" // #define FILE_DEVICE_WDFFTLR 0x0F59 //

-----------------

However, when I read the Microsoft description of "Device Type," it states:

-----------------

DeviceType Identifies the device type. This value must match the value that is set in the DeviceType member of the driver's DEVICE_OBJECT structure. (See Specifying Device Types [ http://msdn.microsoft.com/en-us/library/ms794701.aspx ] ). Values of less than 0x8000 are reserved for Microsoft. Values of 0x8000 and higher can be used by vendors. Note that the vendor-assigned values set the Common bit.

-----------------

The constant FILE_DEVICE_WDFFTLR is used later in several macros similar to the one listed next to set Device Control Codes for use in the filter.

-----------------

#define IOCTL_WDFFLT_GET_BUFFER CTL_CODE(FILE_DEVICE_WDFFTLR, 2049, METHOD_OUT_DIRECT, FILE_READ_ACCESS) -----------------

where FILE_DEVICE_WDFFTLR is the Device Type used by CTL_CODE.

As I understand it, the Device Type for Non-Microsoft use should be greater than x8000. Am I wrong or is this an incorrect setting of the Device Type? Should this value be, in fact, larger than x8000 as this driver is not for a Microsoft defined device? Or, perhaps a value larger than x8000 might conflict with a vendor driver? I'm just not sure of the requirement here.

Any help on this would be appreciated.

Thanks,

Wayne

Rating:
27-Dec-08, Wayne Torrey


"Debug Hint"
I found the setting eb WDFLDR!WdfLdrDiags 1 helpful. However if you don't want to set that everytime you start a debug session, create a key in System\CurrentControlSet\Control\Wdf\Kmdf called Diagnostics and then add the value DbgPrintOn as DWORD and set it to 1.

Rating:
10-Oct-06, Gabe Black


"Automated install using the coinstaller for a software only driver"
It would be helpful if a brief note or pointer were added explaining the configuration info required in the CreateService or the StartService routine in order to make the Software only drivers work. Currently after successful calls to "WdfPreDeviceInstall", createService and "WdfPostDeviceInstall", when 'StartService' is called an error with system error code '1058' is returned. This error corresponds to "The service cannot be started, either because it is disabled or because it has no enabled devices associated with it". Because this is a software only driver, I do not expect there to be any enabled devices associated to it. In the 'inf' equivalent of installing driver we would instructed the inf to install the device at the 'root' and hence avoided the issue.

Rating:
14-Aug-06, Ramkumar Sankaranarayanan


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