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

Exchange data between device drivers and user applications

Rate me:
Please Sign up or sign in to vote.
4.61/5 (14 votes)
5 Nov 20045 min read 140.5K   2.7K   66   33
Describes how to exchange data between a device driver and a user mode application.

Introduction

Although this issue was raised many-many times, I found no good and concise explanation of the basic techniques one has to use to communicate between a user mode application and a kernel mode driver. Most of the first tries are about sharing events, and use the SetEvent and the WaitForSingleObject functions to implement notification. While it may serve the purpose in certain cases, this technique is slow and not a good way of doing it.

In this article, I'll describe the way my applications and drivers communicate. This is not the only technique you can use, but I found it convenient and considerably easy to implement.

Sample Projects

Please note that the sample application isn't complete, because I cannot simulate a hardware IRQ. Nevertheless, it has all the code you need to understand what's going on. The user mode application is also un-tested, and uses IOCP ports which makes it a bit more complex.

Assumptions

Here are the assumptions I make about the problem:

  1. You have a hardware that generates interrupts.
  2. When an IRQ fires, you want to read some data from the hardware.
  3. You want to pass this data back to an application.

Note that this general description fits to many problems, such as reading a file from the hard-drive, or collecting data from a custom data acquisition hardware.

Implementation of the user mode application

As I said earlier, my assumption is that the user mode application is waiting for the driver to generate some data. Most probably, you'll want to have a thread that has a loop in it, which will read the data from the driver and do something with it. So, the first step obviously is to open the driver.

1. Open the device

I won't go into too much details here. You have to use the CreateFile function to open the device. Pass in the OPEN_EXISTING flag for the dwCreationDisposition, and the FILE_FLAG_OVERLAPPED for the dwFlagsAndAttributes. The latter one is not necessary, but since your driver is most probably 100% asynchron-ready (is it?), you'll communicate asynchronously with it, anyway. One more note on opening the device: you must call the IoCreateSymbolicLink function in your driver when you create the device object in order to be able to open the device with the CreateFile function.

2. Read that data!

Once you have the device open, you can use the ReadFile function to read data from the device driver. Since we opened the device with the overlapped flag, you have to use an OVERLAPPED structure here. When you call ReadFile, it will immediately return with 0, and GetLastError will return ERROR_IO_PENDING. Now, call one of the wait functions (i.e., WaitForSingleObject, GetQueuedCompletionStatus, etc.) to wait for the data to arrive.

When the wait function returns (with success), the buffer you passed in to ReadFile contains the data you were so keen on getting!

Implementation of the kernel mode driver

Since we want to read data from the driver, you'll need a dispatch read function. You can specify it in your DriverEntry routine by setting the DriverObject->MajorFunction[IRP_MJ_READ] field. This function has the following signature:

NTSTATUS DispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

The next step is to set up some sort of queue that will store the read requests. For this, I use the LIST_ENTRY structure and initialize it with the InitializeListHead function. You can do it in the DriverEntry routine. Don't forget that this list must be in non-pageable memory! The best thing is to put it in your device extension structure. As you probably know, queues are a real pain when you store IRPs in it and want to be sure that the cancellation of these IRPs are correctly done. God bless Microsoft, for it provided (in newer DDKs) the CSQ routines! They will do most of the hard work. (See DDK for a complete sample.)

So, the next step is to initialize our queue 'cancel-safely'. This is done by calling the IoCsqInitialize function.

After this, we implement the dispatch read function so that it puts the incoming IRP into our queue (just for clarity: 1 ReadFile in user mode = 1 IRP in the dispatch read function). This is done by calling the IoCsqInsertIrp function. Now that we have the IRP queued, we simply return STATUS_PENDING, telling the IO system that the operation is registered and will be served. Note that when we return from the dispatch routine, the ReadFile function in the user mode code will return also, and - as expected - GetLastError returns ERROR_STATUS_PENDING.

Now, when an IRQ arrives, we have to read the data from the device. Do it like this:

  1. Disable the IRQ in the hardware, so we can safely access the hardware memory or ports.
  2. Queue the DPC routine by calling KeInsertQueueDpc. It is of utmost importance that you don't do anything in your IRQ routine except for these two steps! (Of course, you can do whatever you want, but then your Windows will be deadly slow.)
  3. In your DPC routine, call the IoCsqRemoveNextIrp function to de-queue an IRP that the dispatch read function queued. If it returns NULL, the queue is empty.
  4. Get access to the user buffer by doing something like this (Irp is the IRP we've just de-queued):
    PUCHAR UserBuffer = (PUCHAR)MmGetSystemAddressForMdl(Irp->MdlAddress);
  5. Read data from your hardware and fill the UserBuffer.
  6. Make sure that the buffers are all flushed:
    KeFlushIoBuffers(Irp->MdlAddress, TRUE, FALSE);
  7. Call the IoCompleteRequest and pass in Irp to complete it.
  8. Re-enable the IRQ in the hardware.

When step 7 completes, the wait function in your user mode application returns and the buffer is full of data!

Conclusions

As you can see, it's not so difficult to implement data exchange once you know the techniques. In a real application, there are additional steps you have to make, such as checking the user buffer's size and accessibility. Also, it is usually unnecessary to complete an IRP each time you have an IRQ. You could as well just fill the buffer until it's full, and complete it when there is no space left in the buffer. The size of the buffer is in the current stack location of the IRP:

PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

ULONG BufferSize = IrpStack->Parameters.Read.Length;

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) AnyAspect Inc.
Hungary Hungary
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHi Tamás Friend... could you please help me? Pin
luixilva10-Oct-07 12:48
luixilva10-Oct-07 12:48 
GeneralKeInitializeDpc is missing ??!! Pin
bubu10116-Aug-07 22:30
bubu10116-Aug-07 22:30 
there is no reference in the code for KeInitializeDpc. the driver crashes as of result...
Questionhelp for device driver vcp Pin
neelamw18-Apr-07 4:23
neelamw18-Apr-07 4:23 
GeneralNull read question Pin
tbnz12-Apr-07 0:36
tbnz12-Apr-07 0:36 
GeneralDriver couldn't be opened. Error=2 Pin
joshbond7-Nov-06 23:29
joshbond7-Nov-06 23:29 
GeneralRe: Driver couldn't be opened. Error=2 Pin
Tamas Karoly17-Nov-06 0:24
Tamas Karoly17-Nov-06 0:24 
QuestionHelp Needed Pin
wahiajay21-Sep-06 21:37
wahiajay21-Sep-06 21:37 
AnswerRe: Help Needed Pin
Tamas Karoly17-Nov-06 0:32
Tamas Karoly17-Nov-06 0:32 
GeneralHelp Pin
nhthiemsvt23-Aug-06 21:34
nhthiemsvt23-Aug-06 21:34 
GeneralRe: Help Pin
Tamas Karoly17-Nov-06 0:37
Tamas Karoly17-Nov-06 0:37 
Questionprinter Pin
ahme oguz mermerkaya4-Apr-06 22:13
ahme oguz mermerkaya4-Apr-06 22:13 
AnswerRe: printer Pin
Tamas Karoly4-Apr-06 22:56
Tamas Karoly4-Apr-06 22:56 
GeneralRe: printer Pin
ahme oguz mermerkaya5-Apr-06 5:06
ahme oguz mermerkaya5-Apr-06 5:06 
GeneralUNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS Pin
ekzot6-Sep-05 4:51
ekzot6-Sep-05 4:51 
GeneralRe: UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS Pin
Tamas Karoly3-Dec-05 10:59
Tamas Karoly3-Dec-05 10:59 
GeneralIRQ number Pin
Member 6216712-Aug-05 12:23
Member 6216712-Aug-05 12:23 
GeneralRe: IRQ number Pin
Tamas Karoly15-Aug-05 3:23
Tamas Karoly15-Aug-05 3:23 
GeneralRe: IRQ number Pin
Member 6216715-Aug-05 4:06
Member 6216715-Aug-05 4:06 
GeneralRe: IRQ number Pin
Tamas Karoly15-Aug-05 5:16
Tamas Karoly15-Aug-05 5:16 
QuestionHow to build drive Pin
Georgi Petrov27-Dec-04 23:45
Georgi Petrov27-Dec-04 23:45 
AnswerRe: How to build drive Pin
Anonymous28-Dec-04 1:27
Anonymous28-Dec-04 1:27 
GeneralReadFileEx Pin
waleri13-Dec-04 9:19
waleri13-Dec-04 9:19 
GeneralRe: ReadFileEx Pin
Tamas Karoly13-Dec-04 9:42
Tamas Karoly13-Dec-04 9:42 
GeneralRe: ReadFileEx Pin
waleri13-Dec-04 19:29
waleri13-Dec-04 19:29 
GeneralRe: ReadFileEx Pin
Tamas Karoly13-Dec-04 21:57
Tamas Karoly13-Dec-04 21:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.