views:

916

answers:

3

I am quite new to driver development and trying to write a simple filter driver that will enable or disable a keyboard or mouse device. If I can make it work, I want to use it to disable the touchpad on my laptop when a mouse is plugged in. I realize there is probably software out there that does this already, but I am really interested in device drivers and want to learn how to do this myself.

I am using the kbfiltr and moufiltr examples that ship with the WDK, installed as upper filter drivers. The kbfiltr example creates a pdo which can be enumerated and connected to by a usermode program. This allows me to send IOCTLs to the PDO that are handled by KbFilter_EvtIoDeviceControlForRawPdo. However, when I try and do anything at all related to the filter driver, like call into KbFilter_EvtIoInternalDeviceControl so I can do something like

VOID
KbFilter_EvtIoInternalDeviceControl(
    IN WDFQUEUE      Queue,
    IN WDFREQUEST    Request,
    IN size_t        OutputBufferLength,
    IN size_t        InputBufferLength,
    IN ULONG         IoControlCode
    )
    ...
    hDevice = WdfIoQueueGetDevice(Queue);
    devExt = FilterGetData(hDevice);

    switch (IoControlCode) {      
    ...
      case IOCTL_INTERNAL_KEYBOARD_DISCONNECT:
       //
       // Clear the connection parameters in the device extension.
       //
       devExt->UpperConnectData.ClassService = NULL;
       break;
    ...
    }

I get a BSOD. It is not the above code, in the vanilla example the set to null is commented out, just calling into Kbfilter causes the BSOD. I have tried to set the device extension directly in the PDO but this also causes a BSOD, presumably because it is the PDO devExt, not kbfiltr's?

(related: what is a good way of getting the stack trace from a BSOD? I am using Virtual PC as my test environment and an unchecked build of XPSP3)

I can't send an IOCTL_INTERNAL_KEYBOARD_DISCONNECT directly to the driver stack (I understand that input devices accept only one connection at a time?) hence the need for the raw PDO. I really only need to send two IOCTLs (to enable and disable) and I figured I would just use the keyboard disconnect and connect since these were already defined.

If I am wrong about any of these assumptions, please let me know, I know I really am a noob at this but I haven't found a lot of documentation about this kind of communication via a PDO.

+4  A: 

First of all: You can do what you want to do (disable the touchpad on my laptop when a mouse is plugged in) in the user-mode. It will be much simpler and safer. Look at Using Device Installation Functions and WM_DEVICECHANGE

To debug problems in your code: Get a memory dump from BSOD or setup a kernel debugger connection (using a COM-port on your virtual PC redirected to a pipe). See Debugging Tools for Windows

Have fun!

Sergius
Thanks Sergius, do you have any more info on the SetupDi function I will need to call to enable or disable the touchpad (or even just stop its interface from sending messages)?
Dale Halliwell
Look at the devcon.exe sources from WINDDK (WINDDK\6000\tools\devcon\i386\devcon.exe, WINDDK\6000\src\setup\devcon\). It can enable/disable devices and do a lot of other things using SetupAPI.
Sergius
+9  A: 

Ok, I have finally solved this and my driver is working.

Implementation of a KMDF filter driver:

Thanks to Sergius who suggested the COM-port approach because this helped me set up WinDbg. This awesome blog post explains how to get it set up quickly, basically you let VPC set up a com port as a named pipe, enable kernel debug mode on the virtualized OS, and connect to it while it is booting. Then you can get all the DbgPrint messages when the driver is loading and do a lot more, but just the trace messages during the startup process were a huge help for me.

I think my main problem was trying to reuse an internal IOCTL in KbFiltr. This was just a bad design idea on my part because I didn't understand the difference between internal IOCTL and other IOCTLs - Internal IOCTLS such as IOCTL_INTERNAL_KEYBOARD_DISCONNECT have restricted access conditions and should only be sent by other drivers or the kernel. Also this KB article "How to send IOCTL to filter driver" is an example using the same control device structure, but it is WDM.

Anyway, after fighting with the KbFiltr example all weekend, I finally gave up and started over using the WDF Toaster/filtr example. This is a more barebones KMDF filter driver and I had to fill in a lot of blanks using KbFiltr and MouFiltr. The Toaster filter driver operation is similar to KbFiltr but it creates a control device instead of a PDO. It also sets a dos device name for the control device so you can communicate with it from usermode without having to Pinvoke to do that step. The control device allows you to control all devices which have your filter driver loaded just by iterating over the collection. A waitlock is used to synchronize access to the collection.

I also was able to just modify the INF file (to use Mouse class instead of Toaster class) and apply it straight out of the box on my test machine with no modification to the driver code! It is so much easier to start with something that is working. This page gives a comprehensive list of things you should change to adapt the samples.

Dale Halliwell
My first, and probably last, +1 comment! Saved me hours...thanks a bunch for taking the time to format your answer...perfect!
Morten Bergfall
A: 

@Dale Halliwell

I'm wondering if you can share your keyboard filter driver code. I'm trying to enable/disable a USB keyboard (from the many I have connected to my PC for special purposes) but I've been having a hard time figuring out how to do that. Trying with filter driver's development but it's moving slowly. Maybe you can share your sources to see how have you managed to do things.

I would really appreciate it.

Thank you,

Andy

AndyOlivares
Sure Andy, that's no problem, I got this working and used a windows service with WMI to turn the touchpad on or off if an external mouse was plugged in. How would you like me to send to you?
Dale Halliwell