An interesting (albeit simplistic) example to consider is a loopback device. The driver processes write requests by reserving a paged pool buffer and copying user data into this temporary buffer. The buffer is retained until such time as a read request is issued. The data returned by the read request is the contents of the temporary buffer, which is then released. The example demonstrates the implementation of read and write Dispatch routines as well as user buffer access.
NTSTATUS DispatchWrite( INPDEVICE_OBJECT pDO IN PIRP pIrp) { NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION pDE; PVOID userBuffer; ULONG xferSize; // The stack location contains the user buffer info PIO_STACK_LOCATION pIrpStack; pIrpStack = IoGetCurrentIrpStackLocation( pIrp ); // The example assumes the device is using BUFFERED_IO userBuffer = pIrp->AssociatedIrp.SystemBuffer; xferSize = pIrpStack->Parameters.Write.Length; // The temporary buffer pointer is kept // in the DEVICE_EXTENSION (obtained from Device obj) pDE = (PDEVICE_EXTENSION) pDO > DeviceExtension; // If there is already a buffer, free it... if (pDE->deviceBuffer != NULL) { ExFreePool( pDE->deviceBuffer ); PDE->deviceBuffer = NULL; xferSize = 0; } pDE->deviceBuffer = ExAllocatePool(PagedPool, xferSize); if (pDE->deviceBuffer == NULL) { // buffer didn't allocate??? status = STATUS_INSUFFICIENT_RESOURCES; xferSize = 0; } else { // copy the buffer pDE->deviceBufferSize = xferSize; RtlCopyMemory( pDE->deviceBuffer, userBuffer, xferSize ); } // Now complete the IRP no device operation needed pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = xferSize; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return status; } NTSTATUS DispatchRead( IN PDEVICE_OBJECT pDO, IN PIRP pIrp ) { NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION pDE; PVOID userBuffer; ULONG xferSize; // The stack location contains the user buffer info PIO_STACK_LOCATION pIrpStack; pIrpStck = IoGetCurrentIrpStackLocation( pIrp ); userBuffer = pIrp->AssociatedIrp.SystemBuffer; xferSize = pIrpStack->Parameters.Read.Length; // The temporary buffer pointer is kept // in the DEVICE_EXTENSION (obtained from Device obj) pDE = (PDEVICE_EXTENSION) pDO > DeviceExtension; // Don't transfer more than the user's request xferSize = (xferSize < pDE->deviceBufferSize) ? xferSize : pDE->deviceBufferSize; // Now copy the temporary buffer into user space RtlCopyMemory(userBuffer, pDE->deviceBuffer, xferSize); // Free the temporary paged pool buffer ExFreePool( pDE->deviceBuffer ); pDE->deviceBuffer = NULL; pDE->deviceBufferSize = 0; // and complete the I/O request... pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = xferSize; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return status; }
This code example shows only the Dispatch routines for read and write. While they do very little, create and close Dispatch routines must be provided to make this a usable driver. A complete working version of this loopback driver, including a Win32 console test program, is included on the accompanying disk for chapter 7. The driver requires manual installation, as was demonstrated in the last chapter.