990 lines
25 KiB
C++
990 lines
25 KiB
C++
|
/*++
|
||
|
|
||
|
Copyright (c) 2002 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
usbsamp.cpp
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
This module implements a sample USB class driver.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#define _NTOS_
|
||
|
#include <ntddk.h>
|
||
|
#include <xtl.h>
|
||
|
#include <usb.h>
|
||
|
#include "hiddefs.h"
|
||
|
#include "usbsamp.h"
|
||
|
|
||
|
//
|
||
|
// Declare the structure used by the USB enumeration code to internally track
|
||
|
// devices of this type.
|
||
|
//
|
||
|
|
||
|
DECLARE_XPP_TYPE(XDEVICE_TYPE_SAMPLE)
|
||
|
|
||
|
//
|
||
|
// Create the table of device types that this class driver supports.
|
||
|
//
|
||
|
|
||
|
USB_DEVICE_TYPE_TABLE_BEGIN(Sample)
|
||
|
USB_DEVICE_TYPE_TABLE_ENTRY(XDEVICE_TYPE_SAMPLE)
|
||
|
USB_DEVICE_TYPE_TABLE_END()
|
||
|
|
||
|
//
|
||
|
// Create the structure that binds the USB class, subclass, and protocol codes
|
||
|
// to this class driver.
|
||
|
//
|
||
|
|
||
|
USB_CLASS_DRIVER_DECLARATION(Sample, USB_DEVICE_CLASS_HUMAN_INTERFACE, 0xFF, 0xFF)
|
||
|
|
||
|
//
|
||
|
// Register the class driver with the USB core driver by placing a pointer to
|
||
|
// the above structure in the .XPP$Class section.
|
||
|
//
|
||
|
|
||
|
#pragma data_seg(".XPP$ClassSample")
|
||
|
USB_CLASS_DECLARATION_POINTER(Sample)
|
||
|
#pragma data_seg(".XPP$Data")
|
||
|
|
||
|
//
|
||
|
// During device enumeration, an URB may be required. Because device
|
||
|
// enumeration is serialized, only one URB is required. Allocate this as a
|
||
|
// global.
|
||
|
//
|
||
|
|
||
|
URB SampleEnumerationUrb;
|
||
|
|
||
|
//
|
||
|
// Stores all of the per-port instance data related to a device. Allocate the
|
||
|
// maximum number of devices as a global.
|
||
|
//
|
||
|
|
||
|
typedef struct _SAMPLE_DEVICE_STATE {
|
||
|
IUsbDevice *Device;
|
||
|
BOOLEAN DeviceAttached : 1;
|
||
|
BOOLEAN DeviceReady : 1;
|
||
|
BOOLEAN DeviceOpened : 1;
|
||
|
BOOLEAN DeviceRemoved : 1;
|
||
|
BOOLEAN DefaultEndpointOpened : 1;
|
||
|
BOOLEAN ClosingEndpoints : 1;
|
||
|
BOOLEAN RemoveDevicePending : 1;
|
||
|
BOOLEAN CloseDevicePending : 1;
|
||
|
UCHAR InterruptEndpointAddress;
|
||
|
UCHAR InterfaceNumber;
|
||
|
PVOID InterruptEndpointHandle;
|
||
|
URB CloseEndpointUrb;
|
||
|
KEVENT CloseEndpointEvent;
|
||
|
CRITICAL_SECTION CriticalSection;
|
||
|
} SAMPLE_DEVICE_STATE, *PSAMPLE_DEVICE_STATE;
|
||
|
|
||
|
SAMPLE_DEVICE_STATE SampleDeviceState[XGetPortCount()];
|
||
|
|
||
|
//
|
||
|
// Local support.
|
||
|
//
|
||
|
|
||
|
VOID
|
||
|
SampleSetProtocolComplete(
|
||
|
PURB Urb,
|
||
|
PVOID Context
|
||
|
);
|
||
|
|
||
|
VOID
|
||
|
SampleCloseEndpointsAsync(
|
||
|
PSAMPLE_DEVICE_STATE DeviceState
|
||
|
);
|
||
|
|
||
|
VOID
|
||
|
SampleInit(
|
||
|
IUsbInit *UsbInit
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked by the core USB driver to initialize the class
|
||
|
driver. This routine can allocate resources for the expected number of
|
||
|
devices (either statically known or dynamically determined from the values
|
||
|
from XInitDevices) and register resource requirements with the core USB
|
||
|
driver.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
UsbInit - Specifies a virtual table of functions that can be used to control
|
||
|
the behavior of this class driver.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
DWORD dwPort;
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
|
||
|
DbgPrint("SAMPLE: SampleInit called.\n");
|
||
|
|
||
|
for (dwPort = 0; dwPort < XGetPortCount(); dwPort++) {
|
||
|
|
||
|
DeviceState = &SampleDeviceState[dwPort];
|
||
|
|
||
|
//
|
||
|
// Initialize the event used to synchronize the closing of endpoints.
|
||
|
//
|
||
|
|
||
|
KeInitializeEvent(&DeviceState->CloseEndpointEvent,
|
||
|
NotificationEvent, FALSE);
|
||
|
|
||
|
//
|
||
|
// Initialize the critical section used to synchronize passive level API
|
||
|
// code.
|
||
|
//
|
||
|
|
||
|
InitializeCriticalSection(&DeviceState->CriticalSection);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleAddDevice(
|
||
|
IUsbDevice *Device
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked by the core USB driver during enumeration time when
|
||
|
a device has been added that is supported by this class driver.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Device - Specifies a virtual table of functions that can be used to control
|
||
|
the behavior of the device.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
DWORD dwPort;
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
const USB_DEVICE_DESCRIPTOR8 *DeviceDescriptor;
|
||
|
const USB_CONFIGURATION_DESCRIPTOR *ConfigurationDescriptor;
|
||
|
const USB_INTERFACE_DESCRIPTOR *InterfaceDescriptor;
|
||
|
const USB_ENDPOINT_DESCRIPTOR *EndpointDescriptor;
|
||
|
|
||
|
DbgPrint("SAMPLE: SampleAddDevice called.\n");
|
||
|
|
||
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
||
|
|
||
|
//
|
||
|
// Verify that the device is attached to a legal port number. Note that if
|
||
|
// the device were plugged into the bottom slot of a hub, then the port
|
||
|
// number will exceed XGetPortCount() and we'll ignore the device.
|
||
|
//
|
||
|
|
||
|
dwPort = Device->GetPort();
|
||
|
|
||
|
if (dwPort >= XGetPortCount()) {
|
||
|
DbgPrint("SAMPLE: Illegal port number.\n");
|
||
|
Device->AddComplete(USBD_STATUS_UNSUPPORTED_DEVICE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DeviceState = &SampleDeviceState[dwPort];
|
||
|
|
||
|
//
|
||
|
// Verify that we haven't already seen a device attached.
|
||
|
//
|
||
|
|
||
|
if (DeviceState->DeviceAttached) {
|
||
|
DbgPrint("SAMPLE: Device already attached.\n");
|
||
|
Device->AddComplete(USBD_STATUS_UNSUPPORTED_DEVICE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ASSERT(!DeviceState->DeviceReady);
|
||
|
|
||
|
//
|
||
|
// Dump the contents of the device descriptor.
|
||
|
//
|
||
|
|
||
|
DeviceDescriptor = Device->GetDeviceDescriptor();
|
||
|
|
||
|
DbgPrint("Device Descriptor\n");
|
||
|
DbgPrint("\tbLength=%02x\n", DeviceDescriptor->bLength);
|
||
|
DbgPrint("\tbDescriptorType=%02x\n", DeviceDescriptor->bDescriptorType);
|
||
|
DbgPrint("\tbcdUSB=%04x\n", DeviceDescriptor->bcdUSB);
|
||
|
DbgPrint("\tbDeviceClass=%02x\n", DeviceDescriptor->bDeviceClass);
|
||
|
DbgPrint("\tbDeviceSubClass=%02x\n", DeviceDescriptor->bDeviceSubClass);
|
||
|
DbgPrint("\tbDeviceProtocol=%02x\n", DeviceDescriptor->bDeviceProtocol);
|
||
|
DbgPrint("\tbMaxPacketSize0=%02x\n", DeviceDescriptor->bMaxPacketSize0);
|
||
|
DbgPrint("\n");
|
||
|
|
||
|
//
|
||
|
// Dump the contents of the configuration descriptor.
|
||
|
//
|
||
|
|
||
|
ConfigurationDescriptor = Device->GetConfigurationDescriptor();
|
||
|
|
||
|
DbgPrint("Configuration Descriptor\n");
|
||
|
DbgPrint("\tbLength=%02x\n", ConfigurationDescriptor->bLength);
|
||
|
DbgPrint("\tbDescriptorType=%02x\n", ConfigurationDescriptor->bDescriptorType);
|
||
|
DbgPrint("\twTotalLength=%04x\n", ConfigurationDescriptor->wTotalLength);
|
||
|
DbgPrint("\tbNumInterfaces=%02x\n", ConfigurationDescriptor->bNumInterfaces);
|
||
|
DbgPrint("\tbConfigurationValue=%02x\n", ConfigurationDescriptor->bConfigurationValue);
|
||
|
DbgPrint("\tiConfiguration=%02x\n", ConfigurationDescriptor->iConfiguration);
|
||
|
DbgPrint("\tbmAttributes=%02x\n", ConfigurationDescriptor->bmAttributes);
|
||
|
DbgPrint("\tMaxPower=%02x\n", ConfigurationDescriptor->MaxPower);
|
||
|
DbgPrint("\n");
|
||
|
|
||
|
//
|
||
|
// Dump the contents of the interface descriptor.
|
||
|
//
|
||
|
|
||
|
InterfaceDescriptor = Device->GetInterfaceDescriptor();
|
||
|
|
||
|
DbgPrint("Interface Descriptor\n");
|
||
|
DbgPrint("\tbLength=%02x\n", InterfaceDescriptor->bLength);
|
||
|
DbgPrint("\tbDescriptorType=%02x\n", InterfaceDescriptor->bDescriptorType);
|
||
|
DbgPrint("\tbInterfaceNumber=%02x\n", InterfaceDescriptor->bInterfaceNumber);
|
||
|
DbgPrint("\tbAlternateSetting=%02x\n", InterfaceDescriptor->bAlternateSetting);
|
||
|
DbgPrint("\tbNumEndpoints=%02x\n", InterfaceDescriptor->bNumEndpoints);
|
||
|
DbgPrint("\tbInterfaceClass=%02x\n", InterfaceDescriptor->bInterfaceClass);
|
||
|
DbgPrint("\tbInterfaceSubClass=%02x\n", InterfaceDescriptor->bInterfaceSubClass);
|
||
|
DbgPrint("\tbInterfaceProtocol=%02x\n", InterfaceDescriptor->bInterfaceProtocol);
|
||
|
DbgPrint("\tiInterface=%02x\n", InterfaceDescriptor->iInterface);
|
||
|
DbgPrint("\n");
|
||
|
|
||
|
//
|
||
|
// Dump the contents of the endpoint descriptor.
|
||
|
//
|
||
|
|
||
|
EndpointDescriptor = Device->GetEndpointDescriptor(USB_ENDPOINT_TYPE_INTERRUPT,
|
||
|
TRUE, 0);
|
||
|
|
||
|
if (EndpointDescriptor == NULL) {
|
||
|
Device->AddComplete(USBD_STATUS_UNSUPPORTED_DEVICE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DbgPrint("Endpoint Descriptor\n");
|
||
|
DbgPrint("\tbLength=%02x\n", EndpointDescriptor->bLength);
|
||
|
DbgPrint("\tbDescriptorType=%02x\n", EndpointDescriptor->bDescriptorType);
|
||
|
DbgPrint("\tbEndpointAddress=%02x\n", EndpointDescriptor->bEndpointAddress);
|
||
|
DbgPrint("\tbmAttributes=%02x\n", EndpointDescriptor->bmAttributes);
|
||
|
DbgPrint("\twMaxPacketSize=%02x\n", EndpointDescriptor->wMaxPacketSize);
|
||
|
DbgPrint("\tbInterval=%02x\n", EndpointDescriptor->bInterval);
|
||
|
DbgPrint("\n");
|
||
|
|
||
|
//
|
||
|
// Verify that the device supports the mouse boot protocol.
|
||
|
//
|
||
|
|
||
|
if ((InterfaceDescriptor->bInterfaceSubClass != HID_SUBCLASS_BOOT) ||
|
||
|
(InterfaceDescriptor->bInterfaceProtocol != HID_PROTOCOL_MOUSE)) {
|
||
|
DbgPrint("SAMPLE: Device is not a mouse.\n");
|
||
|
Device->AddComplete(USBD_STATUS_UNSUPPORTED_DEVICE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remember that a device is physically attached.
|
||
|
//
|
||
|
|
||
|
DeviceState->DeviceAttached = TRUE;
|
||
|
|
||
|
//
|
||
|
// Store information about the device in our globals.
|
||
|
//
|
||
|
|
||
|
DeviceState->Device = Device;
|
||
|
DeviceState->InterfaceNumber = Device->GetInterfaceNumber();
|
||
|
DeviceState->InterruptEndpointAddress =
|
||
|
EndpointDescriptor->bEndpointAddress;
|
||
|
|
||
|
//
|
||
|
// The USB enumeration code has the default endpoint open for a class driver
|
||
|
// to use until the AddComplete routine is invoked. After AddComplete has
|
||
|
// been called, then the class driver must open the default endpoint itself.
|
||
|
//
|
||
|
// For a mouse device, we need to switch to the boot protocol, so do that
|
||
|
// now while the default endpoint is open.
|
||
|
//
|
||
|
|
||
|
USB_BUILD_CONTROL_TRANSFER(&SampleEnumerationUrb.ControlTransfer,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
0,
|
||
|
0,
|
||
|
SampleSetProtocolComplete,
|
||
|
DeviceState,
|
||
|
TRUE,
|
||
|
USB_HOST_TO_DEVICE | USB_CLASS_COMMAND | USB_COMMAND_TO_INTERFACE,
|
||
|
HID_REQUEST_SET_PROTOCOL,
|
||
|
HID_SET_PROTOCOL_BOOT,
|
||
|
DeviceState->InterfaceNumber,
|
||
|
0);
|
||
|
|
||
|
Device->SubmitRequest(&SampleEnumerationUrb);
|
||
|
|
||
|
//
|
||
|
// The USB enumeration code is blocked until we call AddComplete on our
|
||
|
// device, but other processing will continue while the above request is in
|
||
|
// progress.
|
||
|
//
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleSetProtocolComplete(
|
||
|
PURB Urb,
|
||
|
PVOID Context
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked after the URB has completed to set the protocol of
|
||
|
the HID device.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Urb - Specifies the pointer to the URB that has completed.
|
||
|
|
||
|
Context - Specifies the context supplied to URB_BUILD_CONTROL_TRANSFER.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
|
||
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
||
|
|
||
|
DeviceState = (PSAMPLE_DEVICE_STATE)Context;
|
||
|
|
||
|
//
|
||
|
// Check if an error occurred while setting the protocol. If so, fail to
|
||
|
// attach the device and forward the error to the USB enumeration code.
|
||
|
//
|
||
|
|
||
|
if (USBD_ERROR(Urb->Header.Status)) {
|
||
|
DeviceState->DeviceAttached = FALSE;
|
||
|
DeviceState->Device->AddComplete(USBD_STATUS_UNSUPPORTED_DEVICE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remember that the device is now ready to be used.
|
||
|
//
|
||
|
|
||
|
DeviceState->DeviceReady = TRUE;
|
||
|
|
||
|
//
|
||
|
// Notify the USB enumeration code that we have successfully added the
|
||
|
// device.
|
||
|
//
|
||
|
// Note that after this call has completed, the default endpoint is no
|
||
|
// longer open and we must manually open it if required.
|
||
|
//
|
||
|
|
||
|
DeviceState->Device->AddComplete(USBD_STATUS_SUCCESS);
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleRemoveDevice(
|
||
|
IUsbDevice *Device
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked by the core USB driver during enumeration time when
|
||
|
a device has been removed that had successfully been added before.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Device - Specifies a virtual table of functions that can be used to control
|
||
|
the behavior of the device.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
DWORD dwPort;
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
|
||
|
DbgPrint("SAMPLE: SampleRemoveDevice called.\n");
|
||
|
|
||
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
||
|
|
||
|
dwPort = Device->GetPort();
|
||
|
|
||
|
ASSERT(dwPort < XGetPortCount());
|
||
|
|
||
|
DeviceState = &SampleDeviceState[dwPort];
|
||
|
|
||
|
ASSERT(DeviceState->DeviceAttached);
|
||
|
ASSERT(DeviceState->Device == Device);
|
||
|
|
||
|
//
|
||
|
// Remember that we're in the middle of a device removal.
|
||
|
//
|
||
|
|
||
|
DeviceState->RemoveDevicePending = TRUE;
|
||
|
|
||
|
//
|
||
|
// Close all of the open endpoints. When this operation completes, the
|
||
|
// remove device process will be completed.
|
||
|
//
|
||
|
|
||
|
SampleCloseEndpointsAsync(DeviceState);
|
||
|
}
|
||
|
|
||
|
USBD_STATUS
|
||
|
SampleOpenEndpoints(
|
||
|
PSAMPLE_DEVICE_STATE DeviceState
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine opens all of the endpoints for the device.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
DeviceState - Specifies the device whose endpoints are to be opened.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
USBD status code.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
URB OpenUrb;
|
||
|
USBD_STATUS UsbdStatus;
|
||
|
|
||
|
//
|
||
|
// Open the default endpoint.
|
||
|
//
|
||
|
|
||
|
USB_BUILD_OPEN_DEFAULT_ENDPOINT(&OpenUrb.OpenEndpoint);
|
||
|
|
||
|
UsbdStatus = DeviceState->Device->SubmitRequest(&OpenUrb);
|
||
|
|
||
|
if (USBD_SUCCESS(UsbdStatus)) {
|
||
|
|
||
|
DeviceState->DefaultEndpointOpened = TRUE;
|
||
|
|
||
|
//
|
||
|
// Open the "interrupt in" endpoint.
|
||
|
//
|
||
|
|
||
|
USB_BUILD_OPEN_ENDPOINT(&OpenUrb.OpenEndpoint,
|
||
|
DeviceState->InterruptEndpointAddress,
|
||
|
USB_ENDPOINT_TYPE_INTERRUPT,
|
||
|
sizeof(HID_BOOT_MOUSE_REPORT),
|
||
|
10);
|
||
|
|
||
|
UsbdStatus = DeviceState->Device->SubmitRequest(&OpenUrb);
|
||
|
|
||
|
if (USBD_SUCCESS(UsbdStatus)) {
|
||
|
DeviceState->InterruptEndpointHandle =
|
||
|
OpenUrb.OpenEndpoint.EndpointHandle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return UsbdStatus;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleCloseEndpointsComplete(
|
||
|
PURB Urb,
|
||
|
PVOID Context
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked after the URB has completed to close an endpoint.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Urb - Specifies the pointer to the URB that has completed.
|
||
|
|
||
|
Context - Specifies the context supplied to URB_BUILD_CONTROL_TRANSFER.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
|
||
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
||
|
|
||
|
DeviceState = (PSAMPLE_DEVICE_STATE)Context;
|
||
|
|
||
|
if (DeviceState->DefaultEndpointOpened) {
|
||
|
|
||
|
//
|
||
|
// Close the default endpoint.
|
||
|
//
|
||
|
|
||
|
USB_BUILD_CLOSE_DEFAULT_ENDPOINT(&Urb->CloseEndpoint,
|
||
|
SampleCloseEndpointsComplete,
|
||
|
DeviceState);
|
||
|
|
||
|
DeviceState->DefaultEndpointOpened = FALSE;
|
||
|
|
||
|
} else if (DeviceState->InterruptEndpointHandle != NULL) {
|
||
|
|
||
|
//
|
||
|
// Close the "interrupt in" endpoint.
|
||
|
//
|
||
|
|
||
|
USB_BUILD_CLOSE_ENDPOINT(&Urb->CloseEndpoint,
|
||
|
DeviceState->InterruptEndpointHandle,
|
||
|
SampleCloseEndpointsComplete,
|
||
|
DeviceState);
|
||
|
|
||
|
DeviceState->InterruptEndpointHandle = NULL;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// All endpoints are closed. Signal the close endpoint event in case a
|
||
|
// thread is waiting for the close endpoint to complete.
|
||
|
//
|
||
|
|
||
|
KeSetEvent(&DeviceState->CloseEndpointEvent, IO_NO_INCREMENT,
|
||
|
FALSE);
|
||
|
DeviceState->ClosingEndpoints = FALSE;
|
||
|
|
||
|
//
|
||
|
// If the device has been removed, then complete the device removal
|
||
|
// process by notifying the USB enumeration code.
|
||
|
//
|
||
|
|
||
|
if (DeviceState->RemoveDevicePending) {
|
||
|
|
||
|
DeviceState->RemoveDevicePending = FALSE;
|
||
|
DeviceState->DeviceAttached = FALSE;
|
||
|
DeviceState->DeviceReady = FALSE;
|
||
|
DeviceState->DeviceRemoved = TRUE;
|
||
|
|
||
|
DeviceState->Device->RemoveComplete();
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// There's at least one endpoint still open. If an endpoint is open, then
|
||
|
// we must still think the device is logically attached and connected to
|
||
|
// this device extension.
|
||
|
//
|
||
|
|
||
|
ASSERT(DeviceState->DeviceAttached);
|
||
|
|
||
|
//
|
||
|
// Submit the close request and wait for the USB driver to close the
|
||
|
// endpoint.
|
||
|
//
|
||
|
|
||
|
DeviceState->Device->SubmitRequest(Urb);
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleCloseEndpointsAsync(
|
||
|
PSAMPLE_DEVICE_STATE DeviceState
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine asynchronously closes all of the open endpoints.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
DeviceState - Specifies the device whose endpoints are to be closed.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
||
|
|
||
|
//
|
||
|
// If we haven't already started closing endpoints, then kick off the
|
||
|
// process.
|
||
|
//
|
||
|
|
||
|
if (!DeviceState->ClosingEndpoints) {
|
||
|
|
||
|
//
|
||
|
// Clear the close endpoint event used for synchronous close operations.
|
||
|
//
|
||
|
|
||
|
KeClearEvent(&DeviceState->CloseEndpointEvent);
|
||
|
DeviceState->ClosingEndpoints = TRUE;
|
||
|
|
||
|
//
|
||
|
// Enter the close endpoints state machine.
|
||
|
//
|
||
|
|
||
|
SampleCloseEndpointsComplete(&DeviceState->CloseEndpointUrb,
|
||
|
DeviceState);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleCloseEndpoints(
|
||
|
PSAMPLE_DEVICE_STATE DeviceState
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine synchronously closes all of the open endpoints.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
DeviceState - Specifies the device whose endpoints are to be opened.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
KIRQL OldIrql;
|
||
|
|
||
|
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
||
|
|
||
|
//
|
||
|
// Asynchronously close all of the open endpoints. When all of the
|
||
|
// endpoints are closed, the close endpoint event is signaled.
|
||
|
//
|
||
|
|
||
|
OldIrql = KeRaiseIrqlToDpcLevel();
|
||
|
|
||
|
SampleCloseEndpointsAsync(DeviceState);
|
||
|
|
||
|
KeLowerIrql(OldIrql);
|
||
|
|
||
|
//
|
||
|
// Block until the endpoints have been closed.
|
||
|
//
|
||
|
|
||
|
KeWaitForSingleObject(&DeviceState->CloseEndpointEvent, Executive,
|
||
|
KernelMode, FALSE, NULL);
|
||
|
}
|
||
|
|
||
|
DWORD
|
||
|
SampleOpenDevice(
|
||
|
DWORD dwPort
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked by application code to open a device that has been
|
||
|
successfully enumerated (in particular, XGetDevices reports that the device
|
||
|
is present).
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
dwPort - Specifies the port number of the device to be opened.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Status of operation.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
KIRQL OldIrql;
|
||
|
USBD_STATUS UsbdStatus;
|
||
|
|
||
|
ASSERT(dwPort < XGetPortCount());
|
||
|
|
||
|
DeviceState = &SampleDeviceState[dwPort];
|
||
|
|
||
|
//
|
||
|
// Synchronize passive level access to this device's state.
|
||
|
//
|
||
|
|
||
|
EnterCriticalSection(&DeviceState->CriticalSection);
|
||
|
|
||
|
//
|
||
|
// If the device has already been opened, then bail out now.
|
||
|
|
||
|
if (DeviceState->DeviceOpened) {
|
||
|
LeaveCriticalSection(&DeviceState->CriticalSection);
|
||
|
return ERROR_SHARING_VIOLATION;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Synchronize access to our globals with the AddDevice and RemoveDevice
|
||
|
// entrypoints by raising the IRQL to DPC level. While running at this
|
||
|
// raised IRQL, thread preemption is disabled. Only interupt service
|
||
|
// routines are allowed to run.
|
||
|
//
|
||
|
|
||
|
OldIrql = KeRaiseIrqlToDpcLevel();
|
||
|
|
||
|
//
|
||
|
// Reset the flag that indicates that the device has been removed while the
|
||
|
// device was open or was in the process of opening.
|
||
|
//
|
||
|
|
||
|
DeviceState->DeviceRemoved = FALSE;
|
||
|
|
||
|
//
|
||
|
// If the device is not ready for use, then bail out now.
|
||
|
//
|
||
|
|
||
|
if (!DeviceState->DeviceReady) {
|
||
|
KeLowerIrql(OldIrql);
|
||
|
LeaveCriticalSection(&DeviceState->CriticalSection);
|
||
|
return ERROR_DEVICE_NOT_CONNECTED;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Open the device's endpoints. Note that if this routine fails, some of
|
||
|
// the endpoints may have been opened, so we need to close these endpoints.
|
||
|
//
|
||
|
|
||
|
UsbdStatus = SampleOpenEndpoints(DeviceState);
|
||
|
|
||
|
if (!USBD_SUCCESS(UsbdStatus)) {
|
||
|
KeLowerIrql(OldIrql);
|
||
|
SampleCloseEndpoints(DeviceState);
|
||
|
LeaveCriticalSection(&DeviceState->CriticalSection);
|
||
|
return IUsbDevice::Win32FromUsbdStatus(UsbdStatus);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remember that the device is now open.
|
||
|
//
|
||
|
|
||
|
DeviceState->DeviceOpened = TRUE;
|
||
|
|
||
|
KeLowerIrql(OldIrql);
|
||
|
|
||
|
LeaveCriticalSection(&DeviceState->CriticalSection);
|
||
|
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleSignalEventComplete(
|
||
|
PURB Urb,
|
||
|
PVOID Context
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked after a generic URB has completed.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Urb - Specifies the pointer to the URB that has completed.
|
||
|
|
||
|
Context - Specifies the context supplied to the URB builder macro.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
//
|
||
|
// Wake up the thread waiting for the URB to complete.
|
||
|
//
|
||
|
|
||
|
KeSetEvent((PKEVENT)Context, EVENT_INCREMENT, FALSE);
|
||
|
}
|
||
|
|
||
|
DWORD
|
||
|
SampleReadButtons(
|
||
|
DWORD dwPort,
|
||
|
LPBYTE lpbButtons
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked by application code to read the button state.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
dwPort - Specifies the port number of the device to be read from.
|
||
|
|
||
|
lpbButtons - Specifies the location to receive the button state.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Status of operation.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
KIRQL OldIrql;
|
||
|
KEVENT Event;
|
||
|
URB Urb;
|
||
|
HID_BOOT_MOUSE_REPORT BootMouseReport;
|
||
|
|
||
|
ASSERT(dwPort < XGetPortCount());
|
||
|
|
||
|
DeviceState = &SampleDeviceState[dwPort];
|
||
|
|
||
|
OldIrql = KeRaiseIrqlToDpcLevel();
|
||
|
|
||
|
//
|
||
|
// If the device hasn't been opened, if the device is in the process of
|
||
|
// being removed, or if the device has already been removed, then fail the
|
||
|
// request.
|
||
|
//
|
||
|
|
||
|
if (!DeviceState->DeviceOpened || DeviceState->RemoveDevicePending ||
|
||
|
DeviceState->DeviceRemoved) {
|
||
|
KeLowerIrql(OldIrql);
|
||
|
return ERROR_DEVICE_NOT_CONNECTED;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize the event used to wait for the below URB to complete.
|
||
|
//
|
||
|
|
||
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
||
|
|
||
|
//
|
||
|
// Build an URB to do a control transfer from the default endpoint. Read
|
||
|
// the input report from the mouse interface.
|
||
|
//
|
||
|
|
||
|
USB_BUILD_CONTROL_TRANSFER(&Urb.ControlTransfer,
|
||
|
NULL,
|
||
|
&BootMouseReport,
|
||
|
sizeof(HID_BOOT_MOUSE_REPORT),
|
||
|
USB_TRANSFER_DIRECTION_IN,
|
||
|
SampleSignalEventComplete,
|
||
|
&Event,
|
||
|
TRUE,
|
||
|
USB_DEVICE_TO_HOST | USB_CLASS_COMMAND | USB_COMMAND_TO_INTERFACE,
|
||
|
HID_REQUEST_GET_REPORT,
|
||
|
MAKEWORD(0, 1),
|
||
|
DeviceState->InterfaceNumber,
|
||
|
sizeof(HID_BOOT_MOUSE_REPORT));
|
||
|
|
||
|
DeviceState->Device->SubmitRequest(&Urb);
|
||
|
|
||
|
//
|
||
|
// Wait for the URB to complete. This must be done at lowered IRQL.
|
||
|
//
|
||
|
|
||
|
KeLowerIrql(OldIrql);
|
||
|
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
|
||
|
|
||
|
//
|
||
|
// Check the URB for any errors.
|
||
|
//
|
||
|
|
||
|
if (USBD_ERROR(Urb.Header.Status)) {
|
||
|
return IUsbDevice::Win32FromUsbdStatus(Urb.Header.Status);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The input report was successfullly read from the mouse, so return the
|
||
|
// button state to the caller.
|
||
|
//
|
||
|
|
||
|
*lpbButtons = BootMouseReport.Buttons;
|
||
|
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SampleCloseDevice(
|
||
|
DWORD dwPort
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is invoked by application code to close a device that had been
|
||
|
successfully opened.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
dwPort - Specifies the port number of the device to be closed.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
PSAMPLE_DEVICE_STATE DeviceState;
|
||
|
KIRQL OldIrql;
|
||
|
|
||
|
ASSERT(dwPort < XGetPortCount());
|
||
|
|
||
|
DeviceState = &SampleDeviceState[dwPort];
|
||
|
|
||
|
//
|
||
|
// Synchronize passive level access to this device's state.
|
||
|
//
|
||
|
|
||
|
EnterCriticalSection(&DeviceState->CriticalSection);
|
||
|
|
||
|
//
|
||
|
// If the device hasn't been opened, then bail out now.
|
||
|
//
|
||
|
|
||
|
if (!DeviceState->DeviceOpened) {
|
||
|
LeaveCriticalSection(&DeviceState->CriticalSection);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Close the device's endpoints.
|
||
|
//
|
||
|
|
||
|
SampleCloseEndpoints(DeviceState);
|
||
|
|
||
|
//
|
||
|
// Synchronize access to our globals with the AddDevice and RemoveDevice
|
||
|
// entrypoints by raising the IRQL to DPC level. While running at this
|
||
|
// raised IRQL, thread preemption is disabled. Only interupt service
|
||
|
// routines are allowed to run.
|
||
|
//
|
||
|
|
||
|
OldIrql = KeRaiseIrqlToDpcLevel();
|
||
|
|
||
|
//
|
||
|
// Remember that the device is now closed.
|
||
|
//
|
||
|
|
||
|
DeviceState->DeviceOpened = FALSE;
|
||
|
|
||
|
KeLowerIrql(OldIrql);
|
||
|
|
||
|
LeaveCriticalSection(&DeviceState->CriticalSection);
|
||
|
}
|