íà ãëàâíóþ | âîéòè | ðåãèñòðàöèÿ | DMCA | êîíòàêòû | ñïðàâêà | donate |      

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ý Þ ß


ìîÿ ïîëêà | æàíðû | ðåêîìåíäóåì | ðåéòèíã êíèã | ðåéòèíã àâòîðîâ | âïå÷àòëåíèÿ | íîâîå | ôîðóì | ñáîðíèêè | ÷èòàëêè | àâòîðàì | äîáàâèòü



Client Design

This section briefly discusses options for both device firmware engineers and Windows device driver writers.


Opening and Closing the HidKbd Device

The DDK documentation for the HID class driver read and write handler says that the IRP file object pointer must be valid. HidKbd obtained a file object using IoGetDeviceObjectPointer when it first found a HID device. However, this file handle was closed because it stops the HID device from being removed.

When a user mode application opens a handle to a HidKbd device, the Create IRP handler receives another file object pointer. This same file object pointer is passed in subsequent Read, Write, and Close IRPs, etc.

The HidKbd Create IRP handler, HidKbdCreate, therefore, has to tell the HID class driver about this new file object pointer. It does this by passing the Create IRP to the HID class driver. This is actually very easy to do by putting this extra code in the HidKbdCreate routine.

As HidKbd does not need to process the IRP afterwards, there is no need to set a completion routine.

// Forward IRP to HID class driver device

IoSkipCurrentIrpStackLocation(Irp);

return IoCallDriver(dx->HidDevice, Irp);

The HidKbd Close IRP handler, HidKbdClose, has exactly the same lines in it. This tells the HID class driver that the file handle is being closed.

A side effect of making the HID class driver open a handle for the device is that Windows 2000 will not let the HID device be removed for the duration. This is a perfectly acceptable behavior.

Reading and Writing Data

Our HID kernel mode client is now finally ready to read and write data.

HidKbd currently only supports reading of input reports. The Read IRP expects the provided buffer to be big enough. For a keyboard-input report, the buffer must be at least nine bytes long. The first byte will be 0, with the eight bytes of the input report in the remaining bytes. HidKbd makes no attempt to analyze the data in the same way as HidKbdUser. Instead, it simply returns all the information to the user mode application.

The main Read IRP handler, HidKbdRead, eventually calls ReadHidKbdInputReport, shown in Listing 23.11. ReadHidKbdInputReport is passed the precious file object pointer and a pointer to the buffer. It returns a count of the number of bytes transferred.

ReadHidKbdInputReport looks similar to the CallUSBDI and CallHidIoctl routines described before. This time HidKbd must issue a read request to the HID class driver, so it uses IoBuildSynchronousFsdRequest kernel call to build a suitable Read IRP. An event can be used to wait synchronously for the IRP to be completed, so ReadHidKbdInputReport must be called at PASSIVE_LEVEL IRQL.

By default, IoBuildSynchronousFsdRequest does not insert a file object pointer into the IRP. Therefore, HidKbd must do this job by hand. It calls IoGetNextIrpStackLocation to get the stack location that will be seen by the next driver, the HID class driver. ReadHidKbdInputReport then simply stores the PFILE_OBJECT in the stack FileObject field.

Finally, HidKbd runs IoCallDriver to call the HID class driver. If the IRP is still pending when this call returns, ReadHidKbdInputReport waits for the event to become signalled when the IRP does complete.

I have left out one small part of the story. The DDK says that HID class drivers use Direct I/O for their input and output buffers, not Buffered I/O. Luckily, IoBuildSynchronousFsdRequest sorts this out for us. It checks if the called driver uses Direct I/O. If it does,it allocates the required MDL for the passed input or output buffer (and deallocates it on completion).


Listing 23.11 ReadHidKbdlnputReport routine

NTSTATUS ReadHidKbdInputReport(PFILE_OBJECT FileObject, PVOID Buffer, ULONG& BytesTxd) {

 PHIDKBD_DEVICE_EXTENSION dx = (PHIDKBD_DEVICE_EXTENSION)HidKbdDo->DeviceExtension;

 BytesTxd = 0;

 if (HidKbdDo==NULL) return STATUS_NO_MEDIA_IN_DEVICE;

 IO_STATUS_BLOCK IoStatus;

 IoStatus.Information = 0;

 KEVENT event;

 LARGE_INTEGER FilePointer;

 FilePointer.QuadPart = 0i64;


 // Initialise IRP completion event

 KeInitializeEvent(&event, NotificationEvent, FALSE);

 PIRP Irp = IoBuildSynchronousFsdRequest(IRP_MJ_READ, dx->HidDevice,

  Buffer, dx->HidInputReportLen, &FilePointer, &event, &IoStatus);

 if (Irp==NULL) return STATUS_INSUFFICIENT_RESOURCES;


 // Store file object pointer

 PIO_STACK_LOCATI0N IrpStack = IoGetNextIrpStackLocation(Irp);

 IrpStack->FileObject = FileObject;


 // Call the driver and wait for completion if necessary

 NTSTATUS status = IoCallDriver(dx->HidDevice, Irp);

 if (status == STATUS_PENDING) {

  KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, NULL);

  status = IoStatus.Status;

 }


 // return IRP completion status

 DebugPrint("ReadHidKbdInputReport: status %x", status);

 BytesTxd = IoStatus.Information;

 return status;

}

Permanently Allocated IRP

A kernel mode HID client is likely to be reading many input reports. Rather than building up a suitable IRP for each call, it is more efficient to have one at the ready all the time. However, this approach is a bit more complicated to set up. The HidKbd driver has this alternative code commented out.

When a HidKbd device is created, it must allocate the IRP that will be reused in all subsequent read and write requests. The SetupHidIrp routine, shown in Listing 23.12, calls IoAllocateIrp to obtain a suitable IRP pointer from the I/O Manager. As IRPs have a variable number of stack locations, SetupHidIrp must pass the desired stack size. The second parameter to IoAllocateIrp should be FALSE for intermediate drivers.

It also makes sense to preallocate a buffer for input and output reports. SetupHidIrp works out the size of buffer needed and allocates it from the nonpaged pool. The final preparatory step is to allocate an MDL for this buffer. Remember that the HID class driver uses Direct I/O and so needs an MDL passed in Read and Write IRPs. The call to IoAllocateMdl makes a suitable MDL out of the buffer pointer.


Listing 23.12 SetupHidIrp routine

void SetupHidIrp(IN PHIDKBD_DEVICE_EXTENSION dx, IN CCHAR StackSize) {

 // Work out maximum size of input and output reports

 dx->HidMaxReportLen = dx->HidInputReportLen;

 if (dx->HidOutputReportLen > dx->HidMaxReportLen) dx->HidMaxReportLen = dx->HidOutputReportLen;

 DebugPrint("Setting up HidIrp etc %d", dx->HidMaxReportLen);

 if( dx->HidMaxReportLen==0) return;

 dx->HidReport = ExAllocatePool(NonPagedPool, dx->HidMaxReportLen);

 if (dx->HidReport==NULL) return;

 dx->HidIrp = IoAlIocateIrp(StackSize, FALSE);

 if (dx->HidIrp==NULL) return;

 dx->HidReportMdl = IoAllocateMdl(dx->HidReport, dx->HidMaxReportLen, FALSE, FALSE, NULL);

 if (dx->HidReportMdl==NULL) {

  IoFreeIrp(dx->HidIrp);

  dx->HidIrp = NULL;

 }

}

When the HidKbd device is removed, the IRP, the buffer memory, and the MDL must be freed. Listing 23.13 shows how the RemoveHidIrp routine does this job using the IoFreeMdl, IoFreeIrp, and ExFreePool routines.


Listing 23.13 RemoveHidIrp routine

void RemoveHidIrp(IN PHIDKBD_DEVICE_EXTENSION dx) (

 DebugPrintMsg("Removing HidIrp etc");

 if (dx->HidReportMdl!=NULL) {

  IoFreeMdl(dx->HidReportMdl);

  dx->HidReportMdl = NULL;

 }

 if (dx->HidIrp!=NULL) {

  IoFreeIrp(dx->HidIrp);

  dx->HidIrp = NULL;

 }

 if (dx->HidReport!=NULL) {

  ExFreePool(dx->HidReport);

  dx->HidReport = NULL;

 }

}

I can now discuss how to use this preallocated IRP. Listing 23.14 shows the replacement ReadHidKbdInputReport routine. This time, it cannot use IoBuildSynchronousFsdRequest, so the IRP and its stack must be built by hand.

The IoInitializeIrp call is used to initialize the IRP. IoInitializeIrp incorrectly clears the IRP AllocationFlags field, so this must be preserved. In W2000, IoReuseIrp correctly reinitialises the IRP. ReadHidKbdInputReport then stores the MDL for the buffer in the IRP MdlAddress field. As before, it calls IoGetNextIrpStackLocation to get the next stack location. ReadHidKbdInputReport must set up all the stack parameters carefully: the MajorFunction, the Parameters.Read fields, and the FileObject.

Finally, ReadHidKbdInputReport needs to set a completion routine so that it knows when the IRP has completed. It passes an event to the completion routine. The completion routine sets the event into the signalled state when it is run). ReadHidKbdInputReport waits until the event is set (i.e., when the IRP has been completed by the lower driver. Assuming that the HID driver has returned data, the final job is to copy the data into the user's buffer, using RtlCopyMemory.

The ReadComplete completion routine returns STATUS_MORE_PROCESSING_REQUIRED. This stops the I/O Manager from deleting the IRP. The IRP will be reused so it must not be deleted.


Listing 23.14 New ReadHidKbdInputReport routine

NTSTATUS ReadHidKbdInputReport(PHIDKBD_DEVICE_EXTENSION dx, PFILE_OBJECT FileObject, PVOID Buffer, ULONG& BytesTxd) {

 BytesTxd = 0;

 if (HidKbdDo==NULL || dx->HidIrp==NULL || dx->HidReport==NULL) {

  DebugPrintMsg("No HidIrp");

  return STATUS_INSUFFICIENT_RESOURCES;

 }

 RtlZeroMemory(dx->HidReport, dx->HidMaxReportLen);


 // Initialise IRP completion event

 KEVENT event;

 KeInitializeEvent(&event, NotificationEvent, FALSE);


 // Initialise IRP

 UCHAR AllocationFlags = dx->HidIrp->AllocationFlags;

 IoInitializeIrp(dx->HidIrp, IoSizeOfIrp(HidKbdDo->StackSize), HidKbdDo->StackSize);

 dx->HidIrp->AllocationFlags = AllocationFlags;

 dx->HidIrp->MdlAddress = dx->HidReportMdl;

 PIO_STACK_LOCATION IrpStack = IoGetNextIrpStackLocation(dx->HidIrp);

 IrpStack->MajorFunction = IRP_MJ_READ;

 IrpStack->Parameters.Read.Key = 0;

 IrpStack->Parameters.Read.Length = dx->HidInputReportLen;

 IrpStack->Parameters.Read.ByteOffset.QuadPart = 0;

 IrpStack->FileObject = FileObject;

 IoSetCompletionRoutine(dx->HidIrp, (PIO_COMPLETION_ROUTINE)ReadComplete, &event, TRUE, TRUE, TRUE);

 NTSTATUS status = IoCallDriver(dx->HidDevice, dx->HidIrp);

 if (status == STATUS_PENDING) {

  KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, NULL);

  status = dx->HidIrp->IoStatus.Status;

 }


 // return IRP completion status

 DebugPrint("ReadHidKbdInputReport: status %x", status);

 BytesTxd = dx->HidIrp->IoStatus.Information;

 if (BytesTxd>0) RtlCopyMemory(Buffer, dx->HidReport, BytesTxd);

 return status;

}


NTSTATUS ReadComplete(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN PKEVENT Event) {

 KeSetEvent(Event, 0, FALSE);

 return STATUS_MORE_PROCESSING_REQUIRED;

}

Even Better …

Two problems exist with the permanently allocated IRP solution I have just presented. The first is that the driver will not cope with two "simultaneous" read requests as it uses the same buffer in each call. A quick fix to this problem would be to allow only one read request at a time. The next best solution is dropping the shared buffer; an MDL must then be allocated for the user buffer in each read request.

In fact, the best solution is not to use a permanently allocated IRP, but to reuse the Read IRP. If the HidKbd device uses Direct I/O, the operating system will even do the MDL allocation. In this version, the ReadHidKbdInputReport routine only needs to set up the next IRP stack location appropriately. In fact, calling IoCopyCurrentIrpStackLocationToNext will probably do this job just fine.

The second problem with both the earlier techniques of calling the HID class driver is that they can be inefficient. In both the earlier cases, the call to KeWaitForSingleObject forces the current thread to block waiting for the event to become signalled. As HidKbd may operate in the context of a user thread, this may stop any other overlapped operations from running.[60] The solution to this problem is to modify the completion routine. If the completion routine completes the original Read IRP, there is no need for ReadHidKbdInputReport to wait for the IRP completion event.

This technique should be used wherever possible. The HidKbd Create and Close IRP use this technique as they pass their IRPs to the HID class driver, which completes them in due course. However, it is probably still worth using events in the CallHidIoctl routine for two reasons. The first is that HidKbd needs to know the IRP results. Secondly, my guess is that the HID class driver will be able to complete these IOCTLs straightaway, as it should already have the information at hand.

The CallUSBDI routine in the UsbKbd driver is a candidate for this technique, as it is more than likely that the USB class drivers will take some time to process a USBDI request. However, it is usually the case that the USBDI call results are needed. Processing the results in a completion routine is just about possible. However, this will probably lead to code that is very complicated. In the end, it is probably simplest to leave the UsbKbd code as it is.


New Features | Writing Windows WDM Device Drivers | Endpoint Type Selection