Article 12527 of comp.os.ms-windows.programmer.nt.kernel-mode:
Connie, I repost this every now and then to let people read it who have not
seen it before.

-Paul

Here is an explanation of buffers and DeviceIoControl.

First, here are the parameters,

BOOL DeviceIoControl(
    HANDLE hDevice,	// handle to device of interest
    DWORD dwIoControlCode,	// control code of operation to perform
    LPVOID lpInBuffer,	// pointer to buffer to supply input data
    DWORD nInBufferSize,	// size of input buffer
    LPVOID lpOutBuffer,	// pointer to buffer to receive output data
    DWORD nOutBufferSize,	// size of output buffer
    LPDWORD lpBytesReturned,	// pointer to variable to receive output byte
count
    LPOVERLAPPED lpOverlapped 	// pointer to overlapped structure for
asynchronous operation
   );	
 
METHOD_BUFFERED

user-mode perspective

lpInBuffer - optional, contains data that is written to the driver
lpOutBuffer - optional, contains data that is read from the driver after
the call has completed

lpInBuffer and lpOutBuffer can be two buffers or a single shared buffer. 
If a shared buffer, lpInBuffer is overwritten by lpOutBuffer.


I/O Manager perspective

examines nInBufferSize and nOutBufferSize.  Allocates memory from non-paged
pool and puts the address of this pool in Irp->AssociatedIrp.SystemBuffer. 
The size of this buffer is equal to the size of the larger of the two
bufferes.  This buffer is accessible at any IRQL.

copies nInBufferSize to irpSp->Parameters.DeviceIoControl.InputBufferLength
copies nOutBufferSize to
irpSp->Parameters.DeviceIoControl.OutputBufferLength
copies contents of lpInBuffer to SystemBuffer allocated above
calls your driver



Device Driver perspective

you have one buffer, Irp->AssociatedIrp.SystemBuffer.  You read input data
from this buffer and you write output data to the same buffer, overwriting
the input data.

Before calling IoCompleteRequest, you must
- set IoStatus.Status to an approriate NtStatus
- if IoStatus.Status == STATUS_SUCCESS
	set IoStatus.Information to the 
	number of bytes you want copied 
	from the SystemBuffer back into
	lpOutBuffer.


I/O Manager Completion Routine perspective

looks at IoStatus block, if IoStatus.Status = STATUS_SUCCESS, copies the
number of bytes specified by IoStatus.Information from
Irp->AssociatedIrp.SystemBuffer into lpOutBuffer
completes the request






METHOD_IN_DIRECT

user-mode perspective

lpInBuffer - optional, contains data that is written to the driver.  This
buffer is used in the exact same fashion as METHOD_BUFFERED.  To avoid
confusion, mentally rename this buffer to lpControlBuffer.  This is
typically a small, optional buffer that might contain a control structure
with useful information for the device driver.  This buffer is smal and is
double buffered.

lpOutBuffer - NOT OPTIONAL, This LARGE buffer contains data that is read by
the driver.  To avoid confusion, mentally rename this buffer to
lpDataTransferBuffer.  This is physically the same buffer that the device
driver will read from.  There is no double buffering.  Technically, this
buffer is still optional, but since you are using this buffering method,
what would be the point???

I/O Manager perspective

If lpInBuffer exists, allocates memory from non-paged pool and puts the
address of this pool in Irp->AssociatedIrp.SystemBuffer.  This buffer is
accessible at any IRQL.  

copies nInBufferSize to irpSp->Parameters.DeviceIoControl.InputBufferLength
copies nOutBufferSize to
irpSp->Parameters.DeviceIoControl.OutputBufferLength
copies contents of lpInBuffer to SystemBuffer allocated above
So far this is completely identical to METHOD_BUFFERED.  Most likely
lpInBuffer (mentally renamed to lpControlBuffer) is very small in size.

For lpOutBuffer (mentally renamed to lpDataTransferBuffer), an MDL is
allocated.  lpOutBuffer is probed and locked into memory.  Then, the user
buffer virtual addresses are checked to be sure they are readable in the
caller's access mode.

The MDL is address is stored in Irp->MdlAddress.
Your driver is called.


Device Driver perspective

The device driver can read the copy of lpOutBuffer via
Irp->AssociatedIrp.SystemBuffer.  Anything written by the device driver to
this buffer is lost.  The I/O Manager does not copy any data back to the
user-mode buffers as it did in the completion routine for METHOD_BUFFERED. 
Art Baker's book is wrong in this respect (page 168, "data going from the
driver back to the caller is passed through an intermediate system-space
buffer" and page 177, "When the IOCTL IRP is completed, the contents of the
system buffer will be copied back into the callers original output buffer".

The device driver accesses the Win32 buffer directly via Irp->MdlAddress. 
The driver uses whatever Mdl API's to read the buffer.  Usually, this
buffer is to be written to some mass storage media or some similar
operation.  Since this is a large data transfer, assume a completion
routine is required.

mark the Irp pending
queue it
return status pending




Device Driver Completion Routine perspective

standard completion routine operations
set IoStatus.Status to an approriate NtStatus
IoStatus.Information is not needed
completete the request 




I/O Manager Completion Routine perspective

standard I/O Manager completion routine operations
unmap the pages
deallocate the Mdl
complete the request





METHOD_OUT_DIRECT

user-mode perspective

lpInBuffer - optional, contains data that is written to the driver.  This
buffer is used in the exact same fashion as METHOD_BUFFERED.  To avoid
confusion, mentally rename this buffer to lpControlBuffer.  This is
typically a small, optional buffer that might contain a control structure
with useful information for the device driver.  This buffer is smal and is
double buffered.

lpOutBuffer - NOT OPTIONAL, This LARGE buffer contains data that is written
by the driver and read by the wer-mode application when the request is
completed.  To avoid confusion, mentally rename this buffer to
lpDataTransferBuffer.  This is physically the same buffer that the device
driver will write to.  There is no double buffering.  Technically, this
buffer is still optional, but since you are using this buffering method,
what would be the point???

I/O Manager perspective

If lpInBuffer exists, allocates memory from non-paged pool and puts the
address of this pool in Irp->AssociatedIrp.SystemBuffer.  This buffer is
accessible at any IRQL.  

copies nInBufferSize to irpSp->Parameters.DeviceIoControl.InputBufferLength
copies nOutBufferSize to
irpSp->Parameters.DeviceIoControl.OutputBufferLength
copies contents of lpInBuffer to SystemBuffer allocated above
So far this is completely identical to METHOD_BUFFERED.  Most likely
lpInBuffer (mentally renamed to lpControlBuffer) is very small in size.

For lpOutBuffer (mentally renamed to lpDataTransferBuffer), an MDL is
allocated.  lpOutBuffer is probed and locked into memory.  Then the user
buffer's addresses are checked to make sure the caller could write to them
in the caller's access mode.

The MDL is address is stored in Irp->MdlAddress.
Your driver is called.


Device Driver perspective

The device driver can read the copy of lpOutBuffer via
Irp->AssociatedIrp.SystemBuffer.  Anything written by the device driver to
this buffer is lost.

The device driver accesses the Win32 buffer directly via Irp->MdlAddress. 
The driver uses whatever Mdl API's to write data to the buffer.  Usually,
this buffer is to be read from some mass storage media or some similar
operation.  Since this is a large data transfer, assume a completion
routine is required.

mark the Irp pending
queue it
return status pending




Device Driver Completion Routine perspective

standard completion routine operations
set IoStatus.Status to an approriate NtStatus
IoStatus.Information is not needed
completete the request 




I/O Manager Completion Routine perspective

standard I/O Manager completion routine operations
unmap the pages
deallocate the Mdl
complete the request




METHOD_NEITHER

I/O Manager perspective

Irp->UserBuffer = lpOutputBuffer;
IrpSp->Parameters.DeviceIoControl.Type3InputBuffer = lpInputBuffer;

No comments here.  Don't use METHOD_DIRECT unless you know what you are
doing.  Simple rule.

If your IOCtl involves no data transfer buffers, then METHOD_NEITHER is the
fastest path through the I/O Manager that involves an Irp.




Final Comment

Don't touch Irp->UserBuffer.  This is a bookmark for the I/O Manager.  Two
major problems can occur.  1 - page fault at high IRQL, or 2 - you write
something to Irp->UserBuffer and the I/O Manager overwrites you in its
completion routine.  File systems access Irp->UserBuffer, but FSD writers
know all of the above and know when it is safe to touch Irp->UserBuffer.









In article <339706A7.43BE@proaxis.com>, Chet Douglas <cdouglas@proaxis.com>
 writes:
> Can someone tell me if I have the following correct??  

Not even close, sorry.

> This chart is trying to show how the input and output pointers in the 
> DeviceIOControl call from ring 3 are translated into a system supplied 
> IRP in ring 0.  
> 
> ADDR XLATED refers to whether the system supplied IRP has translated the 
> buffer into kernel mode address space.

This NEVER happens.  What does happen is that a system-space buffer 
may be allocated and the user buffer *copied* to or from the process 
buffer. 

> PROBED/LOCKED refers to whether the system has probed and locked the 
> pages into physical memory for the system supplied IRP.
> 
> DATA DIR refers to the direction in which data can flow.  IN means the 
> data will be going into the buffer.  OUT means the data will be going 
> out of the buffer.  BOTH means either direction will work. 

This is the backwards of DeviceIoControl's interpretation of "in" and 
"out", eg in the terms InBuffer and OutBuffer.  Let's change it to 
"memory access", read vs. write - memory is read by driver and/or DMA 
device, or memory is written by driver and/or DMA device. 

Another key point is that for DeviceIoControl, the device object flags 
DO_BUFFERED_IO and DO_DIRECT_IO have no, repeat, no significance 
whatsoever.  Those flags only apply to WriteFile and ReadFile calls. 

-----------------------------------------------------------------------
DEVIOCTL	DRIVER				DATA	PROBED/	MEMORY
RING 3		RING 0				COPIED	LOCKED	ACCESS	
===============	=============================== =======	=======	======
METHOD_BUFFERED			
pInBuffer	pIRP->AssociatedIrp.SystemBuffer   Y	N	read
pOutBuffer	pIRP->AssociatedIrp.SystemBuffer   Y    N	write

METHOD_IN_DIRECT
pInBuffer	pIRP->AssociatedIrp.SystemBuffer   Y	N	read
pOutBuffer	pIRP->MDLAddress		   N	Y	read

METHOD_OUT_DIRECT
pInBuffer	pIRP->AssociatedIrp.SystemBuffer   Y	N	read
pOutBuffer	pIRP->MDLAddress		   N	Y	write

METHOD_NEITHER	
pInBuffer	pIRPStack->Params.DevIoctl.Type3InputBuffer
						   N    N	any (unsafe)
pOutBuffer	pIRP->UserBuffer		   N 	N	any (unsafe)

In this last case the "raw" user-space addresses and sizes are simply
placed in the indicated fields in the IRP.  I do not believe that NT 
has even probed them for safe access. While in requesting thread context 
the driver can reference these buffers in either "direction" but must 
do so in a try/except block, as the app could undefine these buffers 
at any time.  It could also do IoAllocateMdl / MmProbeAndLockPages on 
them; this latter call would also have to be done in a try/except 
block. 

	--- Jamie Hanrahan, Kernel Mode Systems, San Diego CA
        Internet: jeh@cmkrnl.com (JH645)  CompuServe: 74140,2055  
drivers, internals, networks, applications, and training for VMS and
Windows NT
NT driver FAQ, links, and other information:  http://www.cmkrnl.com/

If you post a reply in news, please don't e-mail it too.  


Conny Ljungqvist <clj@swipnet.se> wrote in article
<345F74C7.2056@swipnet.se>...
> Hi,
> 
> I want to send a large data buffer to a device driver, modify it and
> then return it to calling application as efficient as possible.
> 
> If METHOD_IN_DIRECT is used, data to device driver is not copied from
> app. and can be read  from by device driver but can't I also write in
> the incoming buffer?
> If not, howcome?
> 
> If METHOD_OUT_DIRECT is used, data from device driver is not copied to
> app. and can be written to by device driver but can't I also read from
> the outgoing buffer?
> If not, howcome?
> 
> I guess I can use METHOD_NEITHER but it seems one has to know an awful
> lot about how NT works internally. Is there any sample around solving
> this problem using METHOD_NEITHER?
> 
> Thanks, Conny Ljungqvist
>