It turns out that the 8-byte keyboard report is all zeroes if no keys are pressed. The UsbKbd Read IRP handler ignores any keyboard reports that are all zero. A real driver would return read data when any key is pressed or released. A higher-level driver would have to implement an auto-repeat feature.

I can finally describe the Interrupt transfer routine, UsbDoInterruptTransfer, shown in Listing 21.6. This takes a buffer as input that must be at least 8 bytes long. UsbDoInterruptTransfer first checks that the USB pipe handle is available and that the input buffer is suitable.

In the same way as usual, memory is allocated for the URB, a _URB_BULK_OR_INTERRUPT_TRANSFER structure. UsbDoInterruptTransfer then loops until a non-zero keyboard report is returned, or until a time-out or another error occurs. UsbBuildInterruptOrBulkTransferRequest is used to build the URB each time[54]. UsbDoInterruptTransfer calls the USB class drivers in the usual way.

The time-out is checked using the KeQueryTickCount call[55]. This returns a ULONG count of 'timer interrupts' since the operating system started. The timer interrupt interval is not the same in Windows 98 and Windows 2000. Use KeQueryTimeIncrement to find out this interval in units of 100ns. In both cases, there are just over 100 timer ticks per second.

UsbDoInterruptTransfer remembers the tick count before the first keyboard Interrupt request is generated. After each Interrupt request completes, it checks the tick count again, and gives up after the specified time-out.

UsbKbd does not handle IRP cancelling or the Cleanup routine. UsbKbd relies on the time-out to complete Read IRPs. If you do implement IRP cancelling, then you will need to use the Abort Pipe URB request to cancel all requests on the specified port.

Listing 21.6 Doing interrupt transfers

NTSTATUS UsbDoInterruptTransfer(IN PUSBKBD_DEVICE_EXTENSION dx, IN PVOID UserBuffer, ULONG& UserBufferSize) {

 // Check we're selected

 if (dx->UsbPipeHandle==NULL) return STATUS_INVALID_HANDLE;

 // Check input parameters

 ULONG InputBufferSize = UserBufferSize;

 UserBufferSize = 0;

 if (UserBuffer==NULL || InputBufferSize<8) return STATUS_INVALID_PARAMETER;

 // Keyboard input reports are always 8 bytes

 long NTSTATUS status = STATUS_SUCCESS;

 ULONG OutputBufferSize = 8;

 // Allocate memory for URB

 USHORT UrbSize = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER);

 PURB urb = (PURB)ExAllocatePool(NonPagedPool, UrbSize);

 if (urb==NULL) {

  DebugPrintMsg('No URB memory');

  return STATUS_INSUFFICIENT_RESOURCES;

 }

 // Remember when we started

 // Get start tick count and length of tick in 100ns units

 LARGE_INTEGER StartTickCount;

 KeQueryTickCount(&StartTickCount);

 ULONG UnitsOf100ns = KeQueryTimeIncrement();

 // Loop until non-zero report read, error, bad length, or timed out

 while(true) {

  // Build Do Bulk or Interrupt transfer request

  UsbBuildInterruptOrBulkTransferRequest(urb, UrbSize, dx->UsbPipeHandle, UserBuffer, NULL, OutputBufferSize, USBD_TRANSFER_DIRECTION_IN, NULL);

  // Call the USB driver

  status = CallUSBDI(dx, urb);

  // Check statuses

  if (!NT_SUCCESS(status) || !USBD_SUCCESS( urb->UrbHeader.Status)) {

   DebugPrint('status %x URB status %x', status, urb->UrbHeader.Status);

   status = STATUS_UNSUCCESSFUL;

   break;

  }

  // Give up if count of bytes transferred was not 8

  if (urb->UrbBulkOrInterruptTransfer.TransferBufferLength!= OutputBufferSize) break;

  // If data non-zero then exit as we have a keypress

  __int64* pData = (__int64 *)UserBuffer;

  if (*pData!=0i64) break;

  // Check for time-out

  LARGE_INTEGER TickCountNow;

  KeQueryTickCount(&TickCountNow);

  ULONG ticks = (ULONGKTickCountNow.QuadPart – StartTickCount.QuadPart);

  if (ticks*UnitsOf100ns/10000000 >= dx->UsbTimeout) {

   DebugPrint('Time-out %d 100ns', ticks*UnitsOf100ns);

   status = STATUS_NO_MEDIA_IN_DEVICE;

   break;

  }

 }

 UserBufferSize = urb->UrbBulkOrInterruptTransfer.TransferBufferLength;

 if (NT_SUCCESS(status)) {

  PUCHAR bd = (PUCHAR)UserBuffer;

  DebugPrint('Transfer data %2x %2x %2x %2x %2x %2x %2x %2x', bd[0], bd[1], bd[2], bd[3], bd[4], bd[5], bd[6], bd[7]);

 }

 ExFreePool(urb);

 return status;

}

Control Transfers

The UsbKbd driver lets its controlling application modify the state of the keyboard LEDs. This lets me illustrate how to do Control transfers on the default pipe to endpoint zero. The Write IRP handler sends the first byte of the write buffer to the keyboard as a HID 'output report'. Again, the next chapter fully defines the format of this report and the SET_REPORT command. The lower three bits of the report correspond to the NumLock, CapsLock, and ScrollLock keys.

The Write IRP handler calls UsbSendOutputReport to send the output report. As per usual, it allocates a suitable URB. The UsbBuildVendorRequest helper function is used to fill this URB. Control transfers to the default pipe can take many shapes and forms. First, there are 'class' and 'vendor' types. For each type, you can specify whether the destination is the 'device', 'interface', 'endpoint', or 'other'.

The HID Specification tells us to send a 'class' control transfer request to the 'interface'. The call to UsbBuildVendorRequest, therefore, uses the URB_FUNCTION_CLASS_INTERFACE URB function code. The TransferFlags parameter specifies the direction of the data transfer; set the USBD_TRANSFER_DIRECTION_IN flag for input transfers. The Request, Value, and Index parameters are set as instructed by the HID Specification. Finally, the output data buffer is given.

The URB is then sent off to the class drivers. Apart from noting the URB status and freeing the URB memory, there is nothing else to do.

The UsbGetIdleRate routine in Usb.cpp shows how to input data using a control transfer over the default pipe. This issues a GET_IDLE request. The current keyboard idle rate is printed in a DebugPrint trace statement.

Listing 21.7 Doing an output control transfer

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату
×