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,
In the same way as usual, memory is allocated for the URB, a _URB_BULK_OR_INTERRUPT_TRANSFER structure.
The time-out is checked using the
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;
}
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
The HID Specification tells us to send a 'class' control transfer request to the 'interface'. The call to
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
Listing 21.7 Doing an output control transfer