You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2711 lines
94 KiB
2711 lines
94 KiB
/****************************** Module Header ******************************\
|
|
* Module Name: pnp.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* This module tracks device interface changes so we can keep track of know how many mice and
|
|
* keyboards and mouse
|
|
* and mouse reports.
|
|
*
|
|
* History:
|
|
* 97-10-16 IanJa Interpreted from a dream that Ken Ray had.
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
BOOL gbFirstConnectionDone;
|
|
|
|
DEVICE_TEMPLATE aDeviceTemplate[DEVICE_TYPE_MAX + 1] = {
|
|
// DEVICE_TYPE_MOUSE
|
|
{
|
|
sizeof(GENERIC_DEVICE_INFO)+sizeof(MOUSE_DEVICE_INFO), // cbDeviceInfo
|
|
&GUID_CLASS_MOUSE, // pClassGUID
|
|
PMAP_MOUCLASS_PARAMS, // uiRegistrySection
|
|
L"mouclass", // pwszClassName
|
|
DD_MOUSE_DEVICE_NAME_U L"0", // pwszDefDevName
|
|
DD_MOUSE_DEVICE_NAME_U L"Legacy0", // pwszLegacyDevName
|
|
IOCTL_MOUSE_QUERY_ATTRIBUTES, // IOCTL_Attr
|
|
FIELD_OFFSET(DEVICEINFO, mouse.Attr), // offAttr
|
|
sizeof((PDEVICEINFO)NULL)->mouse.Attr, // cbAttr
|
|
FIELD_OFFSET(DEVICEINFO, mouse.Data), // offData
|
|
sizeof((PDEVICEINFO)NULL)->mouse.Data, // cbData
|
|
ProcessMouseInput, // Reader routine
|
|
NULL // pkeHidChange
|
|
},
|
|
// DEVICE_TYPE_KEYBOARD
|
|
{
|
|
sizeof(GENERIC_DEVICE_INFO)+sizeof(KEYBOARD_DEVICE_INFO), // cbDeviceInfo
|
|
&GUID_CLASS_KEYBOARD, // pClassGUID
|
|
PMAP_KBDCLASS_PARAMS, // uiRegistrySection
|
|
L"kbdclass", // pwszClassName
|
|
DD_KEYBOARD_DEVICE_NAME_U L"0", // pwszDefDevName
|
|
DD_KEYBOARD_DEVICE_NAME_U L"Legacy0", // pwszLegacyDevName
|
|
IOCTL_KEYBOARD_QUERY_ATTRIBUTES, // IOCTL_Attr
|
|
FIELD_OFFSET(DEVICEINFO, keyboard.Attr), // offAttr
|
|
sizeof((PDEVICEINFO)NULL)->keyboard.Attr, // cbAttr
|
|
FIELD_OFFSET(DEVICEINFO, keyboard.Data), // offData
|
|
sizeof((PDEVICEINFO)NULL)->keyboard.Data, // cbData
|
|
ProcessKeyboardInput, // Reader routine
|
|
NULL // pkeHidChange
|
|
},
|
|
#ifdef GENERIC_INPUT
|
|
// DEVICE_TYPE_HID
|
|
{
|
|
sizeof(GENERIC_DEVICE_INFO)+sizeof(HID_DEVICE_INFO), // cbDeviceInfo
|
|
&GUID_CLASS_INPUT, // pClassGUID
|
|
0, // uiRegistrySection. LATER: add real one
|
|
L"hid", // pwszClassName
|
|
L"", // pwszDefDevName
|
|
L"", // pwszLegacyDevName
|
|
0, // IOCTL_ATTR
|
|
0, // offAttr
|
|
0, // cbAttr
|
|
0, // offData
|
|
0, // cbData
|
|
ProcessHidInput, // Reader routine
|
|
NULL, // pkeHidChange,
|
|
DT_HID, // dwFlags
|
|
},
|
|
#endif
|
|
// Add new input device type template here
|
|
};
|
|
|
|
//
|
|
// We need to remember device class notification entries since we need
|
|
// them to unregister the device class notification when we disconnect
|
|
// from the console.
|
|
//
|
|
|
|
PVOID aDeviceClassNotificationEntry[DEVICE_TYPE_MAX + 1];
|
|
|
|
#ifdef DIAGNOSE_IO
|
|
NTSTATUS gKbdIoctlLEDSStatus = -1; // last IOCTL_KEYBOARD_QUERY_INDICATORS
|
|
#endif
|
|
|
|
typedef struct _CDROM_NOTIFY {
|
|
LIST_ENTRY Entry;
|
|
ULONG Size;
|
|
PVOID RegistrationHandle;
|
|
ULONG Event;
|
|
// Must be last field
|
|
MOUNTMGR_DRIVE_LETTER_TARGET DeviceName;
|
|
} CDROM_NOTIFY, *PCDROM_NOTIFY;
|
|
|
|
PVOID gCDROMClassRegistrationEntry;
|
|
LIST_ENTRY gCDROMNotifyList;
|
|
LIST_ENTRY gMediaChangeList;
|
|
PFAST_MUTEX gMediaChangeMutex;
|
|
HANDLE gpEventMediaChange;
|
|
|
|
#define EVENT_CDROM_MEDIA_ARRIVAL 1
|
|
#define EVENT_CDROM_MEDIA_REMOVAL 2
|
|
|
|
/***************************************************************************\
|
|
* Win32kPnPDriverEntry
|
|
*
|
|
* This is the callback function when we call IoCreateDriver to create a
|
|
* PnP Driver Object. In this function, we need to remember the DriverObject.
|
|
*
|
|
* Parameters:
|
|
* DriverObject - Pointer to the driver object created by the system.
|
|
* RegistryPath - is NULL.
|
|
*
|
|
* Return Value: STATUS_SUCCESS
|
|
*
|
|
* History:
|
|
* 10-20-97 IanJa Taken from ntos\io\pnpinit.c
|
|
\***************************************************************************/
|
|
NTSTATUS Win32kPnPDriverEntry(
|
|
IN PDRIVER_OBJECT DriverObject,
|
|
IN PUNICODE_STRING pustrRegistryPath)
|
|
{
|
|
UNREFERENCED_PARAMETER(pustrRegistryPath);
|
|
|
|
TAGMSG1(DBGTAG_PNP,
|
|
"Win32kPnPDriverEntry(DriverObject 0x%p)",
|
|
DriverObject);
|
|
|
|
//
|
|
// Squirrel away the pointer to our driver object.
|
|
//
|
|
gpWin32kDriverObject = DriverObject;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* Initialize the global event used in notifying CSR that media has changed.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
NTSTATUS InitializeMediaChange(
|
|
HANDLE hMediaRequestEvent)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
if (!IsRemoteConnection()) {
|
|
InitializeListHead(&gCDROMNotifyList);
|
|
InitializeListHead(&gMediaChangeList);
|
|
|
|
Status = ObReferenceObjectByHandle(hMediaRequestEvent,
|
|
EVENT_ALL_ACCESS,
|
|
*ExEventObjectType,
|
|
KernelMode,
|
|
&gpEventMediaChange,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
gMediaChangeMutex = UserAllocPoolNonPagedNS(sizeof(FAST_MUTEX), TAG_PNP);
|
|
if (gMediaChangeMutex) {
|
|
ExInitializeFastMutex(gMediaChangeMutex);
|
|
} else {
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
} else {
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
VOID
|
|
CleanupMediaChange(
|
|
VOID)
|
|
{
|
|
if (gMediaChangeMutex) {
|
|
UserFreePool(gMediaChangeMutex);
|
|
gMediaChangeMutex = 0;
|
|
}
|
|
}
|
|
|
|
__inline VOID EnterMediaCrit(
|
|
VOID)
|
|
{
|
|
KeEnterCriticalRegion();
|
|
ExAcquireFastMutexUnsafe(gMediaChangeMutex);
|
|
}
|
|
|
|
__inline VOID LeaveMediaCrit(
|
|
VOID)
|
|
{
|
|
ExReleaseFastMutexUnsafe(gMediaChangeMutex);
|
|
KeLeaveCriticalRegion();
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
* Routines to support CDROM driver letters.
|
|
*
|
|
* Execution Context:
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
ULONG xxxGetDeviceChangeInfo(
|
|
VOID)
|
|
{
|
|
UNICODE_STRING name;
|
|
PFILE_OBJECT FileObject;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
KEVENT event;
|
|
PIRP irp;
|
|
MOUNTMGR_DRIVE_LETTER_INFORMATION output;
|
|
IO_STATUS_BLOCK ioStatus;
|
|
NTSTATUS status;
|
|
PCDROM_NOTIFY pContext;
|
|
PTHREADINFO ptiCurrent;
|
|
TL tlContext;
|
|
TL tlFileObject;
|
|
|
|
ULONG retval = 0;
|
|
|
|
if (!(ISCSRSS())) {
|
|
return 0;
|
|
}
|
|
|
|
EnterMediaCrit();
|
|
if (!IsListEmpty(&gMediaChangeList)) {
|
|
pContext = (PCDROM_NOTIFY)RemoveTailList(&gMediaChangeList);
|
|
} else {
|
|
pContext = NULL;
|
|
}
|
|
LeaveMediaCrit();
|
|
|
|
if (pContext == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
ThreadLockPool(ptiCurrent, pContext, &tlContext);
|
|
|
|
RtlInitUnicodeString(&name, MOUNTMGR_DEVICE_NAME);
|
|
status = IoGetDeviceObjectPointer(&name,
|
|
FILE_READ_ATTRIBUTES,
|
|
&FileObject,
|
|
&DeviceObject);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
PushW32ThreadLock(FileObject, &tlFileObject, UserDereferenceObject);
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER,
|
|
DeviceObject,
|
|
&pContext->DeviceName,
|
|
sizeof(MOUNTMGR_DRIVE_LETTER_TARGET) +
|
|
pContext->DeviceName.DeviceNameLength,
|
|
&output,
|
|
sizeof(output),
|
|
FALSE,
|
|
&event,
|
|
&ioStatus);
|
|
if (irp) {
|
|
/*
|
|
* This IoCallDriver may block nearly for good --- the device
|
|
* may be in the D3 state and IoCallDriver could take way
|
|
* too long, waiting for its powering up.
|
|
* They may not even return STATUS_PENDING. We'd better
|
|
* leave the critsec here.
|
|
*/
|
|
LeaveCrit();
|
|
|
|
status = IoCallDriver(DeviceObject, irp);
|
|
if (status == STATUS_PENDING) {
|
|
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
status = ioStatus.Status;
|
|
}
|
|
|
|
EnterCrit();
|
|
|
|
if ((status == STATUS_SUCCESS) && (output.CurrentDriveLetter)) {
|
|
UserAssert((output.CurrentDriveLetter - 'A') < 30);
|
|
retval = 1 << (output.CurrentDriveLetter - 'A');
|
|
|
|
if (pContext->Event & EVENT_CDROM_MEDIA_ARRIVAL) {
|
|
retval |= HMCE_ARRIVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
PopAndFreeW32ThreadLock(&tlFileObject);
|
|
}
|
|
|
|
//
|
|
// Allways free the request
|
|
//
|
|
ThreadUnlockAndFreePool(ptiCurrent, &tlContext);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* Handle device notifications such as MediaChanged
|
|
*
|
|
* Execution Context:
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
NTSTATUS DeviceCDROMNotify(
|
|
IN PTARGET_DEVICE_CUSTOM_NOTIFICATION Notification,
|
|
IN PCDROM_NOTIFY pContext)
|
|
{
|
|
PCDROM_NOTIFY pNew;
|
|
|
|
CheckCritOut();
|
|
|
|
if (IsRemoteConnection()) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
UserAssert(pContext);
|
|
|
|
if (IsEqualGUID(&Notification->Event, &GUID_IO_MEDIA_ARRIVAL)) {
|
|
pContext->Event = EVENT_CDROM_MEDIA_ARRIVAL;
|
|
} else if (IsEqualGUID(&Notification->Event, &GUID_IO_MEDIA_REMOVAL)) {
|
|
pContext->Event = EVENT_CDROM_MEDIA_REMOVAL;
|
|
} else if (IsEqualGUID(&Notification->Event, &GUID_TARGET_DEVICE_REMOVE_COMPLETE)) {
|
|
EnterMediaCrit();
|
|
if (!gCDROMClassRegistrationEntry) {
|
|
// This is being cleaned up by xxxUnregisterDeviceNotifications
|
|
LeaveMediaCrit();
|
|
return STATUS_SUCCESS;
|
|
}
|
|
RemoveEntryList(&pContext->Entry);
|
|
LeaveMediaCrit();
|
|
IoUnregisterPlugPlayNotification(pContext->RegistrationHandle);
|
|
UserFreePool(pContext);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#ifdef AUTORUN_CURSOR
|
|
else if (IsEqualGUID(&Notification->Event, &GUID_IO_DEVICE_BECOMING_READY)) {
|
|
PDEVICE_EVENT_BECOMING_READY pdebr = (DEVICE_EVENT_BECOMING_READY*)Notification->CustomDataBuffer;
|
|
ShowAutorunCursor(pdebr->Estimated100msToReady * 10);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
else {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Process the arrival or removal.
|
|
//
|
|
// We must queue this otherwise we end up bugchecking on Terminal Server
|
|
// This is due to opening a handle from within the system process which
|
|
// requires us to do an attach process.
|
|
//
|
|
|
|
pNew = UserAllocPoolNonPaged(pContext->Size, TAG_PNP);
|
|
if (pNew) {
|
|
RtlCopyMemory(pNew, pContext, pContext->Size);
|
|
|
|
EnterMediaCrit();
|
|
InsertHeadList(&gMediaChangeList, &pNew->Entry);
|
|
LeaveMediaCrit();
|
|
|
|
KeSetEvent(gpEventMediaChange, EVENT_INCREMENT, FALSE);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
* DeviceClassCDROMNotify
|
|
*
|
|
* This gets called when CDROM appears or disappears
|
|
*
|
|
\***************************************************************************/
|
|
NTSTATUS
|
|
DeviceClassCDROMNotify (
|
|
IN PDEVICE_INTERFACE_CHANGE_NOTIFICATION classChange,
|
|
IN PVOID Unused
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PFILE_OBJECT FileObject;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
PCDROM_NOTIFY pContext;
|
|
ULONG Size;
|
|
|
|
UNREFERENCED_PARAMETER(Unused);
|
|
|
|
CheckCritOut();
|
|
|
|
/*
|
|
* Sanity check the DeviceType, and that it matches the InterfaceClassGuid
|
|
*/
|
|
UserAssert(IsEqualGUID(&classChange->InterfaceClassGuid, &CdRomClassGuid));
|
|
|
|
if (IsEqualGUID(&classChange->Event, &GUID_DEVICE_INTERFACE_ARRIVAL)) {
|
|
|
|
Status = IoGetDeviceObjectPointer(classChange->SymbolicLinkName,
|
|
FILE_READ_ATTRIBUTES,
|
|
&FileObject,
|
|
&DeviceObject);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
Size = sizeof(CDROM_NOTIFY) + classChange->SymbolicLinkName->Length;
|
|
|
|
pContext = (PCDROM_NOTIFY) UserAllocPool(Size, TAG_PNP);
|
|
|
|
//
|
|
// Register For MediaChangeNotifications on all the CDROMs.
|
|
//
|
|
|
|
if (pContext) {
|
|
|
|
pContext->Size = Size;
|
|
pContext->DeviceName.DeviceNameLength = classChange->SymbolicLinkName->Length;
|
|
RtlCopyMemory(pContext->DeviceName.DeviceName,
|
|
classChange->SymbolicLinkName->Buffer,
|
|
pContext->DeviceName.DeviceNameLength);
|
|
|
|
if (NT_SUCCESS(IoRegisterPlugPlayNotification (
|
|
EventCategoryTargetDeviceChange,
|
|
0,
|
|
FileObject,
|
|
gpWin32kDriverObject,
|
|
DeviceCDROMNotify,
|
|
pContext,
|
|
&(pContext->RegistrationHandle)))) {
|
|
EnterMediaCrit();
|
|
InsertHeadList(&gCDROMNotifyList, &pContext->Entry);
|
|
LeaveMediaCrit();
|
|
} else {
|
|
RIPMSG2(RIP_WARNING,
|
|
"Failed to register CDROM Device Notification '%.*ws'.",
|
|
pContext->DeviceName.DeviceNameLength,
|
|
pContext->DeviceName.DeviceName);
|
|
UserFreePool(pContext);
|
|
}
|
|
} else {
|
|
RIPMSG2(RIP_WARNING,
|
|
"Failed to allocate pool block for CDROM '%.*ws'.",
|
|
pContext->DeviceName.DeviceNameLength,
|
|
pContext->DeviceName.DeviceName);
|
|
}
|
|
|
|
ObDereferenceObject(FileObject);
|
|
}
|
|
} else if (IsEqualGUID(&classChange->Event, &GUID_DEVICE_INTERFACE_REMOVAL)) {
|
|
|
|
//
|
|
// Do nothing - we already remove the registration.
|
|
//
|
|
|
|
} else {
|
|
RIPMSG0(RIP_ERROR, "Unrecognized Event GUID");
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
|
|
PPNP_NOTIFICATION_RECORD gpPnpNotificationRecord;
|
|
DWORD gdwPnpNotificationRecSize = 256;
|
|
|
|
UINT giPnpSeq;
|
|
BOOL gfRecordPnpNotification = TRUE;
|
|
|
|
VOID CleanupPnpNotificationRecord(
|
|
VOID)
|
|
{
|
|
CheckDeviceInfoListCritIn();
|
|
|
|
gfRecordPnpNotification = FALSE;
|
|
if (gpPnpNotificationRecord) {
|
|
UserFreePool(gpPnpNotificationRecord);
|
|
gpPnpNotificationRecord = NULL;
|
|
}
|
|
}
|
|
|
|
VOID RecordPnpNotification(
|
|
PNP_NOTIFICATION_TYPE type,
|
|
PDEVICEINFO pDeviceInfo,
|
|
ULONG_PTR NotificationCode)
|
|
{
|
|
UINT iIndex;
|
|
UINT i = 0;
|
|
PUNICODE_STRING pName = NULL;
|
|
HANDLE hDeviceInfo = NULL;
|
|
|
|
CheckDeviceInfoListCritIn();
|
|
UserAssert(gfRecordPnpNotification);
|
|
|
|
if (gpPnpNotificationRecord == NULL) {
|
|
gpPnpNotificationRecord = UserAllocPoolZInit(sizeof *gpPnpNotificationRecord * gdwPnpNotificationRecSize, TAG_PNP);
|
|
}
|
|
if (gpPnpNotificationRecord == NULL) {
|
|
return;
|
|
}
|
|
|
|
iIndex = giPnpSeq % gdwPnpNotificationRecSize;
|
|
|
|
gpPnpNotificationRecord[iIndex].pKThread = PsGetCurrentThread();
|
|
gpPnpNotificationRecord[iIndex].iSeq = ++giPnpSeq; // the first record is numbered as 1.
|
|
gpPnpNotificationRecord[iIndex].type = type;
|
|
/*
|
|
* If there is a pathname, copy it here.
|
|
*/
|
|
switch (type) {
|
|
case PNP_NTF_CLASSNOTIFY:
|
|
/*
|
|
* pDeviceInfo is actually a pUnicodeString.
|
|
*/
|
|
pName = (PUNICODE_STRING)pDeviceInfo;
|
|
pDeviceInfo = NULL;
|
|
break;
|
|
case PNP_NTF_DEVICENOTIFY_UNLISTED:
|
|
/*
|
|
* pDeviceInfo is invalid, cannot be looked up.
|
|
*/
|
|
UserAssert(pName == NULL);
|
|
break;
|
|
default:
|
|
if (pDeviceInfo) {
|
|
pName = &pDeviceInfo->ustrName;
|
|
hDeviceInfo = PtoHq(pDeviceInfo);
|
|
}
|
|
break;
|
|
}
|
|
UserAssert(i == 0);
|
|
if (pName) {
|
|
for ( ; i < ARRAY_SIZE(gpPnpNotificationRecord[iIndex].szPathName) - 1 && i < (UINT)pName->Length / sizeof(WCHAR); ++i) {
|
|
gpPnpNotificationRecord[iIndex].szPathName[i] = (UCHAR)pName->Buffer[i];
|
|
}
|
|
}
|
|
gpPnpNotificationRecord[iIndex].szPathName[i] = 0;
|
|
|
|
/*
|
|
* Store the rest of information
|
|
*/
|
|
gpPnpNotificationRecord[iIndex].pDeviceInfo = pDeviceInfo;
|
|
gpPnpNotificationRecord[iIndex].hDeviceInfo = hDeviceInfo;
|
|
gpPnpNotificationRecord[iIndex].NotificationCode = NotificationCode;
|
|
|
|
/*
|
|
* Store the stack trace.
|
|
*/
|
|
RtlWalkFrameChain(gpPnpNotificationRecord[iIndex].trace,
|
|
ARRAY_SIZE(gpPnpNotificationRecord[iIndex].trace),
|
|
0);
|
|
}
|
|
|
|
#endif // TRACK_PNP_NOTIFICATION
|
|
|
|
|
|
/***************************************************************************\
|
|
* CreateDeviceInfo
|
|
*
|
|
* This creates an instance of an input device for USER. To do this it:
|
|
* - Allocates a DEVICEINFO struct
|
|
* - Adds it to USER's list of input devices
|
|
* - Initializes some of the fields
|
|
* - Signals the input servicing thread to open and read the new device.
|
|
*
|
|
* Type - the device type (DEVICE_TYPE_MOUSE, DEVICE_TYPE_KEYBOARD)
|
|
* Name - the device name.
|
|
* When trying to open a HYDRA client's mouse, Name is NULL.
|
|
* bFlags - some initial flags to set (eg: GDIF_NOTPNP)
|
|
*
|
|
* THIS FUNCTION IS CALLED IN THE CONTEXT OF THE KERNEL PROCESS
|
|
* so we mustn't open the mouse here, else the handle we get will not belong
|
|
* to the Win32k process.
|
|
*
|
|
* History:
|
|
* 11-26-90 DavidPe Created.
|
|
* 01-07-98 IanJa Plug & Play
|
|
\***************************************************************************/
|
|
|
|
PDEVICEINFO CreateDeviceInfo(DWORD DeviceType, PUNICODE_STRING pustrName, BYTE bFlags)
|
|
{
|
|
PDEVICEINFO pDeviceInfo = NULL;
|
|
|
|
CheckCritIn();
|
|
BEGINATOMICCHECK();
|
|
|
|
UserAssert(pustrName != NULL);
|
|
|
|
TAGMSGF4(DBGTAG_PNP, "CreateDeviceInfo(%d, %.*ws, %x)", DeviceType, pustrName->Length / sizeof(WCHAR), pustrName->Buffer, bFlags);
|
|
|
|
if (DeviceType > DEVICE_TYPE_MAX) {
|
|
RIPMSGF1(RIP_ERROR, "Unknown DeviceType %lx", DeviceType);
|
|
}
|
|
|
|
#if defined(PRERELEASE) && defined(CHECK_DEVICE_DUPLICATE)
|
|
{
|
|
PDEVICEINFO pdi;
|
|
|
|
CheckCritIn();
|
|
EnterDeviceInfoListCrit();
|
|
for (pdi = gpDeviceInfoList; pdi; pdi = pdi->pNext) {
|
|
if (wcsncmp(pustrName->Buffer, pdi->ustrName.Buffer, pdi->ustrName.Length / sizeof(WCHAR)) == 0) {
|
|
TAGMSGF1(DBGTAG_PNP, "the new device is already in the list! %p", pdi);
|
|
break;
|
|
}
|
|
}
|
|
LeaveDeviceInfoListCrit();
|
|
}
|
|
#endif
|
|
|
|
#ifdef GENERIC_INPUT
|
|
pDeviceInfo = (PDEVICEINFO)HMAllocObject(NULL, NULL, (BYTE)TYPE_DEVICEINFO, (DWORD)aDeviceTemplate[DeviceType].cbDeviceInfo);
|
|
#else
|
|
pDeviceInfo = UserAllocPoolZInit(aDeviceTemplate[DeviceType].cbDeviceInfo, TAG_PNP);
|
|
#endif
|
|
|
|
if (pDeviceInfo == NULL) {
|
|
RIPMSGF0(RIP_WARNING, "out of memory allocating DEVICEINFO");
|
|
EXITATOMICCHECK();
|
|
return NULL;
|
|
}
|
|
|
|
if (pustrName->Buffer != NULL) {
|
|
pDeviceInfo->ustrName.Buffer = UserAllocPool(pustrName->Length, TAG_PNP);
|
|
|
|
if (pDeviceInfo->ustrName.Buffer == NULL) {
|
|
RIPMSGF2(RIP_WARNING, "Can't duplicate string %.*ws",
|
|
pustrName->Length / sizeof(WCHAR),
|
|
pustrName->Buffer);
|
|
goto CreateFailed;
|
|
}
|
|
|
|
pDeviceInfo->ustrName.MaximumLength = pustrName->Length;
|
|
RtlCopyUnicodeString(&pDeviceInfo->ustrName, pustrName);
|
|
}
|
|
|
|
pDeviceInfo->type = (BYTE)DeviceType;
|
|
pDeviceInfo->bFlags |= bFlags;
|
|
|
|
/*
|
|
* Create this device's HidChangeCompletion event. When the RIT completes
|
|
* a synchronous ProcessDeviceChanges() it signals the HidChangeCompletion
|
|
* event to wake the requesting RequestDeviceChange() which is blocking on
|
|
* the event.
|
|
* Each device has it's own HidChangeCompletion event,
|
|
* since multiple PnP notification may arrive for several different
|
|
* devices simultaneously. (see #331320 IanJa)
|
|
*/
|
|
pDeviceInfo->pkeHidChangeCompleted = CreateKernelEvent(SynchronizationEvent, FALSE);
|
|
if (pDeviceInfo->pkeHidChangeCompleted == NULL) {
|
|
RIPMSGF0(RIP_WARNING,
|
|
"failed to create pkeHidChangeCompleted");
|
|
goto CreateFailed;
|
|
}
|
|
|
|
EnterDeviceInfoListCrit();
|
|
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
/*
|
|
* Placing tracking code here may miss the failure cases above,
|
|
* but they're pretty exceptional cases that can be safely ignored.
|
|
*/
|
|
if (gfRecordPnpNotification) {
|
|
RecordPnpNotification(PNP_NTF_CREATEDEVICEINFO, pDeviceInfo, DeviceType);
|
|
}
|
|
#endif
|
|
|
|
#ifdef GENERIC_INPUT
|
|
|
|
if (aDeviceTemplate[DeviceType].dwFlags & DT_HID) {
|
|
/*
|
|
* Create HID specific information.
|
|
*/
|
|
pDeviceInfo->hid.pHidDesc = HidCreateDeviceInfo(pDeviceInfo);
|
|
|
|
if (pDeviceInfo->hid.pHidDesc == NULL) {
|
|
/*
|
|
* Something wrong happened and we failed to
|
|
* create the device information.
|
|
* Or the device is not our target.
|
|
* Should bail out anyway.
|
|
*/
|
|
TAGMSGF0(DBGTAG_PNP, "HidCreateDeviceInfo bailed out.");
|
|
LeaveDeviceInfoListCrit();
|
|
goto CreateFailed;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Link it in
|
|
*/
|
|
pDeviceInfo->pNext = gpDeviceInfoList;
|
|
gpDeviceInfoList = pDeviceInfo;
|
|
|
|
/*
|
|
* Tell the RIT there is a new device so that it can open it and start
|
|
* reading from it. This is non-blocking (no GDIAF_PNPWAITING bit set)
|
|
*/
|
|
RequestDeviceChange(pDeviceInfo, GDIAF_ARRIVED, TRUE);
|
|
LeaveDeviceInfoListCrit();
|
|
|
|
EXITATOMICCHECK();
|
|
return pDeviceInfo;
|
|
|
|
CreateFailed:
|
|
if (pDeviceInfo) {
|
|
if (pDeviceInfo->ustrName.Buffer) {
|
|
UserFreePool(pDeviceInfo->ustrName.Buffer);
|
|
}
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->hid.pHidDesc) {
|
|
FreeHidDesc(pDeviceInfo->hid.pHidDesc);
|
|
#if DBG
|
|
pDeviceInfo->hid.pHidDesc = NULL;
|
|
#endif
|
|
}
|
|
if (pDeviceInfo->pkeHidChangeCompleted) {
|
|
FreeKernelEvent(&pDeviceInfo->pkeHidChangeCompleted);
|
|
}
|
|
HMFreeObject(pDeviceInfo);
|
|
#else
|
|
UserFreePool(pDeviceInfo);
|
|
#endif
|
|
}
|
|
|
|
ENDATOMICCHECK();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* DeviceClassNotify
|
|
*
|
|
* This gets called when an input device is attached or detached.
|
|
* If this happens during initialization (for mice already connected) we
|
|
* come here by in the context of the RIT. If hot-(un)plugging a mouse,
|
|
* then we are called on a thread from the Kernel process.
|
|
*
|
|
* History:
|
|
* 10-20-97 IanJa Taken from some old code of KenRay's
|
|
\***************************************************************************/
|
|
NTSTATUS
|
|
DeviceClassNotify (
|
|
IN PDEVICE_INTERFACE_CHANGE_NOTIFICATION classChange,
|
|
IN PVOID DeviceType // (context)
|
|
)
|
|
{
|
|
DWORD dwDeviceType;
|
|
|
|
CheckCritOut();
|
|
dwDeviceType = PtrToUlong( DeviceType );
|
|
TAGMSG2(DBGTAG_PNP, "enter DeviceClassNotify(%lx, %lx)", classChange, dwDeviceType);
|
|
|
|
/*
|
|
* Sanity check the DeviceType, and that it matches the InterfaceClassGuid
|
|
*/
|
|
UserAssert(dwDeviceType <= DEVICE_TYPE_MAX);
|
|
UserAssert(IsEqualGUID(&classChange->InterfaceClassGuid, aDeviceTemplate[dwDeviceType].pClassGUID));
|
|
|
|
if (IsRemoteConnection()) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
TAGMSG3(DBGTAG_PNP | RIP_THERESMORE, " Event GUID %lx, %x, %x",
|
|
classChange->Event.Data1,
|
|
classChange->Event.Data2,
|
|
classChange->Event.Data3);
|
|
TAGMSG8(DBGTAG_PNP | RIP_THERESMORE, " %2x%2x%2x%2x%2x%2x%2x%2x",
|
|
classChange->Event.Data4[0], classChange->Event.Data4[1],
|
|
classChange->Event.Data4[2], classChange->Event.Data4[3],
|
|
classChange->Event.Data4[4], classChange->Event.Data4[5],
|
|
classChange->Event.Data4[6], classChange->Event.Data4[7]);
|
|
TAGMSG4(DBGTAG_PNP | RIP_THERESMORE, " InterfaceClassGuid %lx, %lx, %lx, %lx",
|
|
((DWORD *)&(classChange->InterfaceClassGuid))[0],
|
|
((DWORD *)&(classChange->InterfaceClassGuid))[1],
|
|
((DWORD *)&(classChange->InterfaceClassGuid))[2],
|
|
((DWORD *)&(classChange->InterfaceClassGuid))[3]);
|
|
TAGMSG1(DBGTAG_PNP | RIP_THERESMORE, " SymbolicLinkName %ws", classChange->SymbolicLinkName->Buffer);
|
|
|
|
if (IsEqualGUID(&classChange->Event, &GUID_DEVICE_INTERFACE_ARRIVAL)) {
|
|
|
|
// A new hid device class association has arrived
|
|
EnterCrit();
|
|
TRACE_INIT(("DeviceClassNotify - SymbolicLinkName : %ws \n", classChange->SymbolicLinkName->Buffer));
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
if (gfRecordPnpNotification) {
|
|
CheckDeviceInfoListCritOut();
|
|
EnterDeviceInfoListCrit();
|
|
RecordPnpNotification(PNP_NTF_CLASSNOTIFY, (PVOID)classChange->SymbolicLinkName, (ULONG_PTR)DeviceType);
|
|
LeaveDeviceInfoListCrit();
|
|
}
|
|
#endif
|
|
CreateDeviceInfo(dwDeviceType, classChange->SymbolicLinkName, 0);
|
|
LeaveCrit();
|
|
TAGMSG0(DBGTAG_PNP, "=== CREATED ===");
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/****************************************************************************\
|
|
* If a device class "all-for-one" setting (ConnectMultiplePorts) is on,
|
|
* then we just open the device the old (non-PnP) way and return TRUE. (As a
|
|
* safety feature we also do this if gpWin32kDriverObject is NULL, because this
|
|
* driver object is needed to register for PnP device class notifications)
|
|
* Otherwise, return FALSE so we can continue and register for Arrival/Departure
|
|
* notifications.
|
|
*
|
|
* This code was originally intended to be temporary until ConnectMultiplePorts
|
|
* was finally turned off.
|
|
* But now I think we have to keep it for backward compatibility with
|
|
* drivers that filter Pointer/KeyboardClass0 and/or those that replace
|
|
* Pointer/KeyboardClass0 by putting a different name in the registry under
|
|
* System\CurrentControlSet\Services\RIT\mouclass (or kbbclass)
|
|
\****************************************************************************/
|
|
BOOL
|
|
OpenMultiplePortDevice(DWORD DeviceType)
|
|
{
|
|
WCHAR awchDeviceName[MAX_PATH];
|
|
UNICODE_STRING DeviceName;
|
|
PDEVICE_TEMPLATE pDevTpl;
|
|
PDEVICEINFO pDeviceInfo;
|
|
PWCHAR pwchNameIndex;
|
|
|
|
UINT uiConnectMultiplePorts = 0;
|
|
|
|
CheckCritIn();
|
|
|
|
if (DeviceType <= DEVICE_TYPE_MAX) {
|
|
pDevTpl = &aDeviceTemplate[DeviceType];
|
|
} else {
|
|
RIPMSG1(RIP_ERROR, "OpenMultiplePortDevice(%d) - unknown type", DeviceType);
|
|
return FALSE;
|
|
}
|
|
|
|
if (IsRemoteConnection()) {
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef GENERIC_INPUT
|
|
if (pDevTpl->dwFlags & DT_HID) {
|
|
/*
|
|
* HID devices don't need multiple port
|
|
*/
|
|
return FALSE;
|
|
}
|
|
#endif // GENERIC_INPUT
|
|
|
|
/*
|
|
* Note that we don't need to FastOpenUserProfileMapping() here since
|
|
* uiRegistrySection (PMAP_MOUCLASS_PARAMS/PMAP_KBDCLASS_PARAMS) is a
|
|
* machine setiing, not a user setting.
|
|
*/
|
|
FastGetProfileDwordW(NULL,
|
|
pDevTpl->uiRegistrySection, L"ConnectMultiplePorts", 0, &uiConnectMultiplePorts, 0);
|
|
|
|
/*
|
|
* Open the device for read access.
|
|
*/
|
|
if (uiConnectMultiplePorts || (gpWin32kDriverObject == NULL)) {
|
|
/*
|
|
* Find out if there is a name substitution in the registry.
|
|
* Note that we don't need to FastOpenUserProfileMapping() here since
|
|
* PMAP_INPUT is a machine setting, not a user setting.
|
|
*/
|
|
FastGetProfileStringW(NULL,
|
|
PMAP_INPUT,
|
|
pDevTpl->pwszClassName,
|
|
pDevTpl->pwszDefDevName, // if no substitution, use this default
|
|
awchDeviceName,
|
|
sizeof(awchDeviceName)/sizeof(WCHAR),
|
|
0);
|
|
|
|
RtlInitUnicodeString(&DeviceName, awchDeviceName);
|
|
|
|
pDeviceInfo = CreateDeviceInfo(DeviceType, &DeviceName, GDIF_NOTPNP);
|
|
if (pDeviceInfo) {
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
DeviceName.Length = 0;
|
|
DeviceName.MaximumLength = sizeof(awchDeviceName);
|
|
DeviceName.Buffer = awchDeviceName;
|
|
|
|
RtlAppendUnicodeToString(&DeviceName, pDevTpl->pwszLegacyDevName);
|
|
pwchNameIndex = &DeviceName.Buffer[(DeviceName.Length / sizeof(WCHAR)) - 1];
|
|
for (*pwchNameIndex = L'0'; *pwchNameIndex <= L'9'; (*pwchNameIndex)++) {
|
|
CreateDeviceInfo(DeviceType, &DeviceName, GDIF_NOTPNP);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* RegisterCDROMNotify
|
|
*
|
|
* History:
|
|
* 08-21-00 VTan Created
|
|
\***************************************************************************/
|
|
VOID RegisterCDROMNotify(
|
|
VOID)
|
|
{
|
|
UserAssert(!IsRemoteConnection());
|
|
UserAssert(gpWin32kDriverObject != NULL);
|
|
|
|
if (gpWin32kDriverObject != NULL) {
|
|
IoRegisterPlugPlayNotification (
|
|
EventCategoryDeviceInterfaceChange,
|
|
PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
|
|
(PVOID) &CdRomClassGuid,
|
|
gpWin32kDriverObject,
|
|
(PDRIVER_NOTIFICATION_CALLBACK_ROUTINE)DeviceClassCDROMNotify,
|
|
NULL,
|
|
&gCDROMClassRegistrationEntry);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* RegisterForDeviceClassNotifications
|
|
*
|
|
* Get ready to receive notifications that a mouse or keyboard is plugged in
|
|
* or removed, then request notifications by registering for them.
|
|
*
|
|
* History:
|
|
* 10-20-97 IanJa Taken from ntos\io\pnpinit.c
|
|
\***************************************************************************/
|
|
NTSTATUS
|
|
xxxRegisterForDeviceClassNotifications(
|
|
VOID)
|
|
{
|
|
IO_NOTIFICATION_EVENT_CATEGORY eventCategory;
|
|
ULONG eventFlags;
|
|
NTSTATUS Status;
|
|
UNICODE_STRING ustrDriverName;
|
|
DWORD DeviceType;
|
|
|
|
CheckCritIn();
|
|
|
|
TAGMSG0(DBGTAG_PNP, "enter xxxRegisterForDeviceClassNotifications()");
|
|
|
|
/*
|
|
* Remote hydra session indicates CreateDeviceInfo in xxxRemoteReconnect.
|
|
*/
|
|
UserAssert(!IsRemoteConnection());
|
|
|
|
if (!gbFirstConnectionDone) {
|
|
if (!gbRemoteSession) {
|
|
// Session 0
|
|
/*
|
|
* This must be done before devices are registered for device
|
|
* notifications which will occur as a result of CreateDeviceInfo.
|
|
*/
|
|
RtlInitUnicodeString(&ustrDriverName, L"\\Driver\\Win32k");
|
|
Status = IoCreateDriver(&ustrDriverName, Win32kPnPDriverEntry);
|
|
|
|
TAGMSG1(DBGTAG_PNP | RIP_THERESMORE, "IoCreateDriver returned status = %lx", Status);
|
|
TAGMSG1(DBGTAG_PNP, "gpWin32kDriverObject = %lx", gpWin32kDriverObject);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSG1(RIP_ERROR, "IoCreateDriver failed, status %lx", Status);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
UserAssert(gpWin32kDriverObject);
|
|
} else {
|
|
UserAssert(gpWin32kDriverObject == NULL);
|
|
/*
|
|
* Non-Zero session attached to the console
|
|
*/
|
|
|
|
RtlInitUnicodeString(&ustrDriverName, L"\\Driver\\Win32k");
|
|
|
|
//
|
|
// Attempt to open the driver object
|
|
//
|
|
Status = ObReferenceObjectByName(&ustrDriverName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
0,
|
|
*IoDriverObjectType,
|
|
KernelMode,
|
|
NULL,
|
|
&gpWin32kDriverObject);
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSG1(RIP_ERROR, "ObReferenceObjectByName failed, status %lx", Status);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
UserAssert(gpWin32kDriverObject);
|
|
}
|
|
}
|
|
|
|
//
|
|
// We are only interested in DeviceClasses changing.
|
|
//
|
|
eventCategory = EventCategoryDeviceInterfaceChange;
|
|
|
|
//
|
|
// We want to be notified for all devices that are in the system.
|
|
// those that are know now, and those that will arive later.
|
|
// This allows us to have one code path for adding devices, and eliminates
|
|
// the nasty race condition. If we were only interested in the devices
|
|
// that exist at this one moment in time, and not future devices, we
|
|
// would call IoGetDeviceClassAssociations.
|
|
//
|
|
eventFlags = PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES;
|
|
|
|
|
|
/*
|
|
* For all input device types:
|
|
* If they are Multiple Port Devices (ie: not PnP) just open them
|
|
* Else Register them for PnP notifications (they will be opened when the
|
|
* arrival notification arrives.
|
|
* If devices are already attached, we will received immediate notification
|
|
* during the call to IoRegisterPlugPlayNotification, so we must LeaveCrit
|
|
* because the callback routine DeviceClassNotify expects it.
|
|
*/
|
|
for (DeviceType = 0; DeviceType <= DEVICE_TYPE_MAX; DeviceType++) {
|
|
if (!OpenMultiplePortDevice(DeviceType) && (gpWin32kDriverObject != NULL)) {
|
|
/*
|
|
* Make the registration.
|
|
*/
|
|
|
|
TAGMSG1(DBGTAG_PNP, "Registering device type %d", DeviceType);
|
|
|
|
LeaveCrit(); // for DeviceClassNotify
|
|
Status = IoRegisterPlugPlayNotification (
|
|
eventCategory,
|
|
eventFlags,
|
|
(PVOID)aDeviceTemplate[DeviceType].pClassGUID,
|
|
gpWin32kDriverObject,
|
|
(PDRIVER_NOTIFICATION_CALLBACK_ROUTINE)DeviceClassNotify,
|
|
LongToPtr( DeviceType ),
|
|
&aDeviceClassNotificationEntry[DeviceType]);
|
|
|
|
EnterCrit();
|
|
|
|
|
|
TAGMSG1(DBGTAG_PNP, "Registration returned status %lx", Status);
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSG2(RIP_ERROR, "IoRegisterPlugPlayNotification(%d) failed, status %lx",
|
|
DeviceType, Status);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now Register for CD_ROM notifications
|
|
LeaveCrit(); // for DeviceClassNotify
|
|
|
|
if (!gbFirstConnectionDone && gpWin32kDriverObject != NULL) {
|
|
if (!IsRemoteConnection()) {
|
|
RegisterCDROMNotify();
|
|
}
|
|
|
|
gbFirstConnectionDone = TRUE;
|
|
}
|
|
EnterCrit();
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UnregisterDeviceClassNotifications
|
|
*
|
|
* Remove device class notification registrations.
|
|
*
|
|
* History:
|
|
* 02-28-00 Earhart Created
|
|
\***************************************************************************/
|
|
VOID
|
|
xxxUnregisterDeviceClassNotifications(
|
|
VOID)
|
|
{
|
|
// Our input devices will automatically unregister themselves; we
|
|
// need to clean up cdrom, though.
|
|
PLIST_ENTRY pNext;
|
|
PCDROM_NOTIFY pContext;
|
|
PVOID RegistrationEntry;
|
|
|
|
EnterMediaCrit();
|
|
|
|
if (gCDROMClassRegistrationEntry) {
|
|
RegistrationEntry = gCDROMClassRegistrationEntry;
|
|
gCDROMClassRegistrationEntry = NULL;
|
|
LeaveMediaCrit();
|
|
IoUnregisterPlugPlayNotification(RegistrationEntry);
|
|
EnterMediaCrit();
|
|
}
|
|
|
|
while (TRUE) {
|
|
pNext = RemoveHeadList(&gCDROMNotifyList);
|
|
if (!pNext || pNext == &gCDROMNotifyList) {
|
|
break;
|
|
}
|
|
pContext = CONTAINING_RECORD(pNext, CDROM_NOTIFY, Entry);
|
|
LeaveMediaCrit(); /* in case there's a notification pending */
|
|
IoUnregisterPlugPlayNotification(pContext->RegistrationHandle);
|
|
UserFreePool(pContext);
|
|
EnterMediaCrit();
|
|
}
|
|
|
|
LeaveMediaCrit();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetKbdExId
|
|
*
|
|
* Get extended keyboard id with WMI
|
|
*
|
|
* History:
|
|
* 01-02-01 Hiroyama Created
|
|
\***************************************************************************/
|
|
NTSTATUS GetKbdExId(
|
|
HANDLE hDevice,
|
|
PKEYBOARD_ID_EX pIdEx)
|
|
{
|
|
PWNODE_SINGLE_INSTANCE pNode;
|
|
ULONG size;
|
|
PVOID p = NULL;
|
|
NTSTATUS status;
|
|
UNICODE_STRING str;
|
|
|
|
status = IoWMIOpenBlock((LPGUID)&MSKeyboard_ExtendedID_GUID, WMIGUID_QUERY, &p);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
status = IoWMIHandleToInstanceName(p, hDevice, &str);
|
|
TAGMSG2(DBGTAG_PNP, "GetKbdExId: DevName='%.*ws'",
|
|
str.Length / sizeof(WCHAR),
|
|
str.Buffer);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
// Get the size
|
|
size = 0;
|
|
IoWMIQuerySingleInstance(p, &str, &size, NULL);
|
|
|
|
size += sizeof *pIdEx;
|
|
pNode = UserAllocPoolNonPaged(size, TAG_KBDEXID);
|
|
|
|
if (pNode) {
|
|
status = IoWMIQuerySingleInstance(p, &str, &size, pNode);
|
|
if (NT_SUCCESS(status)) {
|
|
*pIdEx = *(PKEYBOARD_ID_EX)(((PUCHAR)pNode) + pNode->DataBlockOffset);
|
|
}
|
|
|
|
UserFreePool(pNode);
|
|
}
|
|
|
|
RtlFreeUnicodeString(&str);
|
|
}
|
|
|
|
ObDereferenceObject(p);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* QueryDeviceInfo
|
|
*
|
|
* Query the device information. This function is an async function,
|
|
* so be sure any buffers it uses aren't allocated on the stack!
|
|
*
|
|
* If this is an asynchronous IOCTL, perhaps we should be waiting on
|
|
* the file handle or on an event for it to succeed?
|
|
*
|
|
* This function must called by the RIT, not directly by PnP notification
|
|
* (else the handle we issue the IOCTL on will be invalid)
|
|
*
|
|
* History:
|
|
* 01-20-99 IanJa Created.
|
|
\***************************************************************************/
|
|
NTSTATUS
|
|
QueryDeviceInfo(
|
|
PDEVICEINFO pDeviceInfo)
|
|
{
|
|
NTSTATUS Status;
|
|
PDEVICE_TEMPLATE pDevTpl = &aDeviceTemplate[pDeviceInfo->type];
|
|
KEYBOARD_ID_EX IdEx;
|
|
|
|
#ifdef GENERIC_INPUT
|
|
UserAssert(pDeviceInfo->type != DEVICE_TYPE_HID);
|
|
#endif
|
|
|
|
#ifdef DIAGNOSE_IO
|
|
pDeviceInfo->AttrStatus =
|
|
#endif
|
|
Status = ZwDeviceIoControlFile(pDeviceInfo->handle, NULL, NULL, NULL,
|
|
&pDeviceInfo->iosb,
|
|
pDevTpl->IOCTL_Attr,
|
|
NULL, 0,
|
|
(PVOID)((PBYTE)pDeviceInfo + pDevTpl->offAttr),
|
|
pDevTpl->cbAttr);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSG2(RIP_WARNING, "QueryDeviceInfo(%p): IOCTL failed - Status %lx",
|
|
pDeviceInfo, Status);
|
|
}
|
|
TAGMSG1(DBGTAG_PNP, "IOCTL_*_QUERY_ATTRIBUTES returns Status %lx", Status);
|
|
|
|
if (pDeviceInfo->type == DEVICE_TYPE_KEYBOARD) {
|
|
if (NT_SUCCESS(GetKbdExId(pDeviceInfo->handle, &IdEx))) {
|
|
TAGMSG4(DBGTAG_PNP, "QueryDeviceInfo: kbd (%x,%x) ExId:(%x,%x)",
|
|
pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Type, pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Subtype,
|
|
IdEx.Type, IdEx.Subtype);
|
|
pDeviceInfo->keyboard.IdEx = IdEx;
|
|
} else {
|
|
// What can we do?
|
|
pDeviceInfo->keyboard.IdEx.Type = pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Type;
|
|
pDeviceInfo->keyboard.IdEx.Subtype = pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Subtype;
|
|
TAGMSG3(DBGTAG_PNP, "QueryDeviceInfo: failed to get ExId for pDevice=%p, fallback to (%x,%x)",
|
|
pDeviceInfo, pDeviceInfo->keyboard.IdEx.Type, pDeviceInfo->keyboard.IdEx.Subtype);
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* OpenDevice
|
|
*
|
|
* This function opens an input device for USER, mouse or keyboard.
|
|
*
|
|
*
|
|
* Return value
|
|
* BOOL did the operation succeed?
|
|
*
|
|
* When trying to open a HYDRA client's mouse (or kbd?), pDeviceInfo->ustrName
|
|
* is NULL.
|
|
*
|
|
* This function must called by the RIT, not directly by PnP
|
|
* notification (that way the handle we are about to create will be in the right
|
|
* our process)
|
|
*
|
|
* History:
|
|
* 11-26-90 DavidPe Created.
|
|
* 01-07-98 IanJa Plug & Play
|
|
* 04-17-98 IanJa Only open mice in RIT context.
|
|
\***************************************************************************/
|
|
BOOL OpenDevice(
|
|
PDEVICEINFO pDeviceInfo)
|
|
{
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
NTSTATUS Status;
|
|
ULONG ulAccessMode = FILE_READ_DATA | SYNCHRONIZE;
|
|
ULONG ulShareMode = FILE_SHARE_WRITE;
|
|
UINT i;
|
|
|
|
CheckCritIn();
|
|
UserAssert((PtiCurrentShared() == gptiRit) || (PtiCurrentShared() == gTermIO.ptiDesktop));
|
|
|
|
TAGMSG4(DBGTAG_PNP, "OpenDevice(): Opening type %d (%lx %.*ws)",
|
|
pDeviceInfo->type, pDeviceInfo->handle, pDeviceInfo->ustrName.Length / sizeof(WCHAR), pDeviceInfo->ustrName.Buffer);
|
|
|
|
#ifdef DIAGNOSE_IO
|
|
pDeviceInfo->OpenerProcess = PsGetCurrentProcessId();
|
|
#endif
|
|
|
|
if (IsRemoteConnection()) {
|
|
|
|
TRACE_INIT(("OpenDevice - Remote mode\n"));
|
|
|
|
/*
|
|
* For other than the console, the mouse handle is
|
|
* set before createwinstation.
|
|
*/
|
|
|
|
pDeviceInfo->bFlags |= GDIF_NOTPNP;
|
|
|
|
switch (pDeviceInfo->type) {
|
|
case DEVICE_TYPE_MOUSE:
|
|
pDeviceInfo->handle = ghRemoteMouseChannel;
|
|
if (ghRemoteMouseChannel == NULL) {
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case DEVICE_TYPE_KEYBOARD:
|
|
pDeviceInfo->handle = ghRemoteKeyboardChannel;
|
|
if (ghRemoteKeyboardChannel == NULL) {
|
|
return FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
RIPMSG2(RIP_ERROR, "Unknown device type %d DeviceInfo %#p",
|
|
pDeviceInfo->type, pDeviceInfo);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
InitializeObjectAttributes(&ObjectAttributes, &(pDeviceInfo->ustrName), 0, NULL, NULL);
|
|
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
|
|
ulAccessMode |= FILE_WRITE_DATA;
|
|
ulShareMode |= FILE_SHARE_READ;
|
|
}
|
|
#endif
|
|
|
|
// USB devices are slow, so they may not have been closed before we
|
|
// open again here so let us delay execution for some time and try
|
|
// to open them again. We delay 1/10th of a second for a max of 30
|
|
// times, making a total wait time of 3 seconds.
|
|
//
|
|
// If we fast user switch too fast, the serial port may be in the
|
|
// process of closing where it stalls execution. This is a rare
|
|
// case where we may open the serial port while it is stalling
|
|
// and get back STATUS_ACCESS_DENIED and lose the user's device.
|
|
// In this case, we should retry the open and it should succeed
|
|
// once the serial port has closed.
|
|
|
|
for (i = 0; i < MAX_RETRIES_TO_OPEN; i++) {
|
|
#ifdef DIAGNOSE_IO
|
|
pDeviceInfo->OpenStatus =
|
|
#endif
|
|
Status = ZwCreateFile(&pDeviceInfo->handle, ulAccessMode,
|
|
&ObjectAttributes, &pDeviceInfo->iosb, NULL, 0, ulShareMode, FILE_OPEN_IF, 0, NULL, 0);
|
|
|
|
if ((STATUS_SHARING_VIOLATION == Status) ||
|
|
(Status == STATUS_ACCESS_DENIED)) {
|
|
// Sleep for 1/10th of a second
|
|
UserSleep(100);
|
|
} else {
|
|
// Device opened successfully or some other error occured
|
|
break;
|
|
}
|
|
}
|
|
|
|
TAGMSG2(DBGTAG_PNP, "ZwCreateFile returns handle %lx, Status %lx",
|
|
pDeviceInfo->handle, Status);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
if ((pDeviceInfo->bFlags & GDIF_NOTPNP) == 0) {
|
|
/*
|
|
* Don't warn about PS/2 mice: the PointerClassLegacy0 -9 and
|
|
* KeyboardClassLegacy0 - 9 will usually fail to be created
|
|
*/
|
|
RIPMSG1(RIP_WARNING, "OpenDevice: ZwCreateFile failed with Status %lx", Status);
|
|
}
|
|
TRACE_INIT(("OpenDevice: ZwCreateFile failed with Status %lx", Status));
|
|
/*
|
|
* Don't FreeDeviceInfo here because that alters gpDeviceInfoList
|
|
* which our caller, ProcessDeviceChanges, is traversing.
|
|
* Instead, let ProcessDeviceChanges do it.
|
|
*/
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
#ifdef GENERIC_INPUT
|
|
/*
|
|
* All the HID Information has been already acquired through
|
|
* HidCreateDeviceInfo. Let's skip HID deviceinfo here.
|
|
*/
|
|
if (pDeviceInfo->type != DEVICE_TYPE_HID) {
|
|
#endif
|
|
Status = QueryDeviceInfo(pDeviceInfo);
|
|
#ifdef GENERIC_INPUT
|
|
}
|
|
#endif
|
|
|
|
return NT_SUCCESS(Status);
|
|
}
|
|
|
|
VOID CloseDevice(
|
|
PDEVICEINFO pDeviceInfo)
|
|
{
|
|
NTSTATUS Status;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
CheckCritIn();
|
|
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
if (gfRecordPnpNotification) {
|
|
CheckDeviceInfoListCritIn();
|
|
RecordPnpNotification(PNP_NTF_CLOSEDEVICE, pDeviceInfo, pDeviceInfo->usActions);
|
|
}
|
|
#endif // TRACK_PNP_NOTIFICATION
|
|
|
|
TAGMSG5(DBGTAG_PNP, "CloseDevice(%p): closing type %d (%lx %.*ws)",
|
|
pDeviceInfo,
|
|
pDeviceInfo->type, pDeviceInfo->handle,
|
|
pDeviceInfo->ustrName.Length / sizeof(WCHAR), pDeviceInfo->ustrName.Buffer);
|
|
|
|
if (pDeviceInfo->handle) {
|
|
UserAssert(pDeviceInfo->OpenerProcess == PsGetCurrentProcessId());
|
|
|
|
ZwCancelIoFile(pDeviceInfo->handle, &IoStatusBlock);
|
|
UserAssertMsg2(NT_SUCCESS(IoStatusBlock.Status), "NtCancelIoFile handle %x failed status %#x",
|
|
pDeviceInfo->handle, IoStatusBlock.Status);
|
|
|
|
if (pDeviceInfo->handle == ghRemoteMouseChannel) {
|
|
UserAssert(pDeviceInfo->type == DEVICE_TYPE_MOUSE);
|
|
pDeviceInfo->handle = 0;
|
|
return;
|
|
}
|
|
|
|
if (pDeviceInfo->handle == ghRemoteKeyboardChannel) {
|
|
UserAssert(pDeviceInfo->type == DEVICE_TYPE_KEYBOARD);
|
|
pDeviceInfo->handle = 0;
|
|
return;
|
|
}
|
|
|
|
Status = ZwClose(pDeviceInfo->handle);
|
|
UserAssertMsg2(NT_SUCCESS(Status), "ZwClose handle %x failed status %#x",
|
|
pDeviceInfo->handle, Status);
|
|
pDeviceInfo->handle = 0;
|
|
} else {
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
|
|
/*
|
|
* HID devices may be closed regardless the error conditions.
|
|
*/
|
|
TAGMSG2(DBGTAG_PNP, "CloseDevice: hid: pDeviceInfo->iosb.Status=%x, ReadStatus=%x",
|
|
pDeviceInfo->iosb.Status, pDeviceInfo->ReadStatus);
|
|
} else {
|
|
#endif
|
|
/*
|
|
* Assert the IO was cancelled or we tried to read the device
|
|
* after the first close (which set the handle to 0 - an invalid handle)
|
|
*/
|
|
UserAssert((pDeviceInfo->iosb.Status == STATUS_CANCELLED) ||
|
|
(pDeviceInfo->ReadStatus == STATUS_INVALID_HANDLE));
|
|
|
|
#ifdef GENERIC_INPUT
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************\
|
|
* RegisterForDeviceChangeNotifications()
|
|
*
|
|
* Device Notifications such as QueryRemove, RemoveCancelled, RemoveComplete
|
|
* tell us what is going on with the mouse.
|
|
* To register for device notifications:
|
|
* (1) Obtain a pointer to the device object (pFileObject)
|
|
* (2) Register for target device change notifications, saving the
|
|
* notification handle (which we will need in order to deregister)
|
|
*
|
|
* It doesn't matter too much if this fails: we just won't be able to eject the
|
|
* hardware via the UI very successfully. (We can still just yank it though).
|
|
* This will also fail if the ConnectMultiplePorts was set for this device.
|
|
*
|
|
* 1998-10-05 IanJa Created
|
|
\*****************************************************************************/
|
|
BOOL RegisterForDeviceChangeNotifications(
|
|
PDEVICEINFO pDeviceInfo)
|
|
{
|
|
PFILE_OBJECT pFileObject;
|
|
NTSTATUS Status;
|
|
|
|
/*
|
|
* In or Out of User critical section:
|
|
* In when called from RIT ProcessDeviceChanges();
|
|
* Out when called from the DeviceNotify callback
|
|
*/
|
|
|
|
if (IsRemoteConnection()) {
|
|
TRACE_INIT(("RegisterForDeviceChangeNotifications called for remote session\n"));
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
CheckCritIn();
|
|
UserAssert((PtiCurrentShared() == gptiRit) || (PtiCurrentShared() == gTermIO.ptiDesktop));
|
|
UserAssert(pDeviceInfo->handle);
|
|
UserAssert(pDeviceInfo->OpenerProcess == PsGetCurrentProcessId());
|
|
|
|
if (pDeviceInfo->bFlags & GDIF_NOTPNP) {
|
|
return TRUE;
|
|
}
|
|
Status = ObReferenceObjectByHandle(pDeviceInfo->handle,
|
|
0,
|
|
NULL,
|
|
KernelMode,
|
|
(PVOID)&pFileObject,
|
|
NULL);
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = IoRegisterPlugPlayNotification (
|
|
EventCategoryTargetDeviceChange, // EventCategory
|
|
0, // EventCategoryFlags
|
|
(PVOID)pFileObject, // EventCategoryData
|
|
gpWin32kDriverObject, // DriverObject
|
|
// (PDRIVER_NOTIFICATION_CALLBACK_ROUTINE)
|
|
DeviceNotify,
|
|
(PVOID)pDeviceInfo, // Context
|
|
&pDeviceInfo->NotificationEntry);
|
|
ObDereferenceObject(pFileObject);
|
|
if (!NT_SUCCESS(Status)) {
|
|
// This is only OK if ConnectMultiplePorts is on (ie: not a PnP device)
|
|
// For the record, the old RIPMSG referred NTBUG #333453.
|
|
RIPMSG3(RIP_ERROR,
|
|
"IoRegisterPlugPlayNotification failed on device %.*ws, status %lx",
|
|
pDeviceInfo->ustrName.Length / sizeof(WCHAR),
|
|
pDeviceInfo->ustrName.Buffer, Status);
|
|
}
|
|
} else {
|
|
// non-catastrophic error (won't be able to remove device)
|
|
RIPMSG2(RIP_ERROR, "Can't get pFileObject from handle %lx, status %lx",
|
|
pDeviceInfo->handle, Status);
|
|
}
|
|
|
|
return NT_SUCCESS(Status);
|
|
}
|
|
|
|
|
|
BOOL UnregisterForDeviceChangeNotifications(PDEVICEINFO pDeviceInfo)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
if (gfRecordPnpNotification) {
|
|
CheckDeviceInfoListCritIn();
|
|
RecordPnpNotification(PNP_NTF_UNREGISTER_NOTIFICATION, pDeviceInfo, pDeviceInfo->usActions);
|
|
}
|
|
#endif
|
|
|
|
CheckCritIn();
|
|
UserAssert((PtiCurrentShared() == gptiRit) || (PtiCurrentShared() == gTermIO.ptiDesktop));
|
|
UserAssert(pDeviceInfo->OpenerProcess == PsGetCurrentProcessId());
|
|
|
|
if (pDeviceInfo->NotificationEntry == NULL) {
|
|
/*
|
|
* This happens for non-PnP devices or if the earlier
|
|
* IoRegisterPlugPlayNotification() failed. Return now since
|
|
* IoUnregisterPlugPlayNotification(NULL) will bluescreen.
|
|
* And other case is also when we detach remote devices (which are
|
|
* not PnP) when reconnecting locally.
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
// non-PnP devices should not have any NotificationEntry:
|
|
UserAssert((pDeviceInfo->bFlags & GDIF_NOTPNP) == 0);
|
|
|
|
TAGMSG4(DBGTAG_PNP, "UnregisterForDeviceChangeNotifications(): type %d (%lx %.*ws)",
|
|
pDeviceInfo->type, pDeviceInfo, pDeviceInfo->ustrName.Length / sizeof(WCHAR), pDeviceInfo->ustrName.Buffer);
|
|
Status = IoUnregisterPlugPlayNotification(pDeviceInfo->NotificationEntry);
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSG2(RIP_ERROR,
|
|
"IoUnregisterPlugPlayNotification failed Status = %lx, DEVICEINFO %lx",
|
|
Status, pDeviceInfo);
|
|
return FALSE;
|
|
}
|
|
pDeviceInfo->NotificationEntry = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* Handle device notifications such as QueryRemove, CancelRemove etc.
|
|
*
|
|
* Execution Context:
|
|
* when yanked: a non-WIN32 thread.
|
|
* via UI: ??? (won't see this except from laptop being undocked?)
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
__inline USHORT GetPnpActionFromGuid(
|
|
GUID *pEvent)
|
|
{
|
|
USHORT usAction = 0;
|
|
|
|
if (IsEqualGUID(pEvent, &GUID_TARGET_DEVICE_QUERY_REMOVE)) {
|
|
TAGMSG0(DBGTAG_PNP | RIP_NONAME, "QueryRemove");
|
|
usAction = GDIAF_QUERYREMOVE;
|
|
|
|
} else if (IsEqualGUID(pEvent, &GUID_TARGET_DEVICE_REMOVE_CANCELLED)) {
|
|
TAGMSG0(DBGTAG_PNP | RIP_NONAME, "RemoveCancelled");
|
|
usAction = GDIAF_REMOVECANCELLED;
|
|
|
|
} else if (IsEqualGUID(pEvent, &GUID_TARGET_DEVICE_REMOVE_COMPLETE)) {
|
|
TAGMSG1(DBGTAG_PNP | RIP_NONAME, "RemoveComplete (process %#x)", PsGetCurrentProcessId());
|
|
usAction = GDIAF_DEPARTED;
|
|
|
|
} else {
|
|
TAGMSG4(DBGTAG_PNP | RIP_NONAME, "GUID Unknown: %lx:%lx:%lx:%x...",
|
|
pEvent->Data1, pEvent->Data2,
|
|
pEvent->Data3, pEvent->Data4[0]);
|
|
}
|
|
return usAction;
|
|
}
|
|
|
|
|
|
NTSTATUS DeviceNotify(
|
|
IN PPLUGPLAY_NOTIFY_HDR pNotification,
|
|
IN PDEVICEINFO pDeviceInfo) // should the context be a kernel address?
|
|
{
|
|
USHORT usAction;
|
|
PDEVICEINFO pDeviceInfoTmp;
|
|
|
|
CheckCritOut();
|
|
CheckDeviceInfoListCritOut();
|
|
|
|
/*
|
|
* Check the validity of pDeviceInfo.
|
|
*/
|
|
EnterDeviceInfoListCrit();
|
|
for (pDeviceInfoTmp = gpDeviceInfoList; pDeviceInfoTmp; pDeviceInfoTmp = pDeviceInfoTmp->pNext) {
|
|
if (pDeviceInfoTmp == pDeviceInfo) {
|
|
break;
|
|
}
|
|
}
|
|
if (pDeviceInfoTmp == NULL) {
|
|
/*
|
|
* This is an unknown device, most likely the one already freed.
|
|
*/
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
if (gfRecordPnpNotification) {
|
|
RecordPnpNotification(PNP_NTF_DEVICENOTIFY_UNLISTED, pDeviceInfo, GetPnpActionFromGuid(&pNotification->Event));
|
|
}
|
|
#endif
|
|
|
|
RIPMSG1(RIP_ERROR, "win32k!DeviceNotify: Notification for unlisted DEVICEINFO %p, contact ntuserdt!", pDeviceInfo);
|
|
|
|
LeaveDeviceInfoListCrit();
|
|
/*
|
|
* Not to prevent device removal etc.,
|
|
* return success here.
|
|
*/
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
if (gfRecordPnpNotification) {
|
|
RecordPnpNotification(PNP_NTF_DEVICENOTIFY, pDeviceInfo, GetPnpActionFromGuid(&pNotification->Event));
|
|
}
|
|
#endif
|
|
LeaveDeviceInfoListCrit();
|
|
|
|
if (IsRemoteConnection()) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
TAGMSG1(DBGTAG_PNP | RIP_THERESMORE, "DeviceNotify >>> %lx", pDeviceInfo);
|
|
|
|
UserAssert(pDeviceInfo->OpenerProcess != PsGetCurrentProcessId());
|
|
UserAssert(pDeviceInfo->usActions == 0);
|
|
|
|
usAction = GetPnpActionFromGuid(&pNotification->Event);
|
|
if (usAction == 0) {
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/*
|
|
* Signal the RIT to ProcessDeviceChanges()
|
|
* Wait for completion according to the GDIAF_PNPWAITING bit
|
|
*/
|
|
CheckCritOut();
|
|
CheckDeviceInfoListCritOut();
|
|
|
|
/*
|
|
* There is small window where we can get a PnP notification for a device that
|
|
* we just have unregister unregistered a notification for and that we are deleting
|
|
* so for PnP notification we need to check the device is valid (still in the list
|
|
* and not being deleted.
|
|
*/
|
|
EnterDeviceInfoListCrit();
|
|
pDeviceInfoTmp = gpDeviceInfoList;
|
|
while (pDeviceInfoTmp) {
|
|
if (pDeviceInfoTmp == pDeviceInfo ) {
|
|
if (!(pDeviceInfo->usActions & (GDIAF_FREEME | GDIAF_DEPARTED))) {
|
|
KeResetEvent(gpEventPnPWainting);
|
|
gbPnPWaiting = TRUE;
|
|
RequestDeviceChange(pDeviceInfo, (USHORT)(usAction | GDIAF_PNPWAITING), TRUE);
|
|
gbPnPWaiting = FALSE;
|
|
KeSetEvent(gpEventPnPWainting, EVENT_INCREMENT, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
pDeviceInfoTmp = pDeviceInfoTmp->pNext;
|
|
}
|
|
LeaveDeviceInfoListCrit();
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* StartDeviceRead
|
|
*
|
|
* This function makes an asynchronous read request to the input device driver,
|
|
* unless the device has been marked for destruction (GDIAF_FREEME)
|
|
*
|
|
* Returns:
|
|
* The next DeviceInfo on the list if this device was freed: If the caller
|
|
* was not already in the DeviceInfoList critical section, the this must be
|
|
* ignored as it is not safe.
|
|
* NULL if the read succeeded.
|
|
*
|
|
* History:
|
|
* 11-26-90 DavidPe Created.
|
|
* 10-20-98 IanJa Generalized for PnP input devices
|
|
\***************************************************************************/
|
|
PDEVICEINFO StartDeviceRead(
|
|
PDEVICEINFO pDeviceInfo)
|
|
{
|
|
PDEVICE_TEMPLATE pDevTpl;
|
|
#ifdef GENERIC_INPUT
|
|
PVOID pBuffer;
|
|
ULONG ulLengthToRead;
|
|
#endif
|
|
|
|
#if !defined(GENERIC_INPUT)
|
|
pDeviceInfo->bFlags |= GDIF_READING;
|
|
#endif
|
|
|
|
/*
|
|
* If this device need to be freed, abandon
|
|
* reading now and request the free.
|
|
*/
|
|
if (pDeviceInfo->usActions & GDIAF_FREEME) {
|
|
#ifdef GENERIC_INPUT
|
|
BEGIN_REENTERCRIT() {
|
|
#if DBG
|
|
if (fAlreadyHadCrit) {
|
|
CheckDeviceInfoListCritIn();
|
|
}
|
|
#endif
|
|
#endif
|
|
BEGIN_REENTER_DEVICEINFOLISTCRIT() {
|
|
pDeviceInfo->bFlags &= ~GDIF_READING;
|
|
pDeviceInfo = FreeDeviceInfo(pDeviceInfo);
|
|
} END_REENTER_DEVICEINFOLISTCRIT();
|
|
#ifdef GENERIC_INPUT
|
|
} END_REENTERCRIT();
|
|
#endif
|
|
return pDeviceInfo;
|
|
}
|
|
|
|
if (gbExitInProgress || gbStopReadInput) {
|
|
// Let's not post any more reads when we're trying to exit, eh?
|
|
pDeviceInfo->bFlags &= ~GDIF_READING;
|
|
pDeviceInfo->iosb.Status = STATUS_UNSUCCESSFUL;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Initialize in case read fails
|
|
*/
|
|
pDeviceInfo->iosb.Status = STATUS_UNSUCCESSFUL; // catch concurrent writes?
|
|
pDeviceInfo->iosb.Information = 0;
|
|
|
|
pDevTpl = &aDeviceTemplate[pDeviceInfo->type];
|
|
|
|
UserAssert(pDeviceInfo->OpenerProcess == PsGetCurrentProcessId());
|
|
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
|
|
UserAssert(pDeviceInfo->hid.pTLCInfo);
|
|
if (pDeviceInfo->handle == NULL) {
|
|
/*
|
|
* Currently this device is not requested by anyone.
|
|
*/
|
|
TAGMSG1(DBGTAG_PNP, "StartDeviceRead: pDevInfo=%p has been closed on demand.", pDeviceInfo);
|
|
BEGIN_REENTER_DEVICEINFOLISTCRIT()
|
|
if (pDeviceInfo->handle == NULL) {
|
|
if (pDeviceInfo->bFlags & GDIF_READING) {
|
|
pDeviceInfo->bFlags &= ~GDIF_READING;
|
|
TAGMSG1(DBGTAG_PNP, "StartDeviceRead: pDevInfo=%p, bFlags has been reset.", pDeviceInfo);
|
|
}
|
|
}
|
|
END_REENTER_DEVICEINFOLISTCRIT();
|
|
return NULL;
|
|
}
|
|
|
|
pBuffer = pDeviceInfo->hid.pHidDesc->pInputBuffer;
|
|
ulLengthToRead = pDeviceInfo->hid.pHidDesc->hidpCaps.InputReportByteLength * MAXIMUM_ITEMS_READ;
|
|
} else {
|
|
pBuffer = (PBYTE)pDeviceInfo + pDevTpl->offData;
|
|
ulLengthToRead = pDevTpl->cbData;
|
|
}
|
|
#endif
|
|
|
|
if (pDeviceInfo->handle == NULL) {
|
|
BEGIN_REENTER_DEVICEINFOLISTCRIT() {
|
|
/*
|
|
* Make sure the handle is truely NULL.
|
|
* If this is the case, perhaps this is called from APC
|
|
* that happened at bad timing, like in the middle of
|
|
* device removal query, when ProcessDeviceChanges completed
|
|
* but RequestDeviceChange is not awaken for the complete event.
|
|
* The code can olnly simply bail out once in the situation.
|
|
*/
|
|
if (pDeviceInfo->handle == NULL) {
|
|
pDeviceInfo->bFlags &= ~GDIF_READING;
|
|
pDeviceInfo->ReadStatus = STATUS_INVALID_HANDLE;
|
|
}
|
|
} END_REENTER_DEVICEINFOLISTCRIT();
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef GENERIC_INPUT
|
|
pDeviceInfo->bFlags |= GDIF_READING;
|
|
#endif
|
|
|
|
LOGTIME(pDeviceInfo->timeStartRead);
|
|
|
|
#ifdef DIAGNOSE_IO
|
|
pDeviceInfo->nReadsOutstanding++;
|
|
#endif
|
|
|
|
UserAssert(pDeviceInfo->handle);
|
|
|
|
/*
|
|
* Avoid to start reading NULL device handle.
|
|
* This happen when the DeviceNotify receives QUERY_REMOVE
|
|
* and the RIT finishes processing it, but RequestDeviceChange
|
|
* has not finished its wait.
|
|
*/
|
|
#ifdef GENERIC_INPUT
|
|
pDeviceInfo->ReadStatus = ZwReadFile(
|
|
pDeviceInfo->handle,
|
|
NULL, // hReadEvent
|
|
InputApc, // InputApc()
|
|
pDeviceInfo, // ApcContext
|
|
&pDeviceInfo->iosb,
|
|
pBuffer,
|
|
ulLengthToRead,
|
|
PZERO(LARGE_INTEGER), NULL);
|
|
#else
|
|
|
|
pDeviceInfo->ReadStatus = ZwReadFile(
|
|
pDeviceInfo->handle,
|
|
NULL, // hReadEvent
|
|
InputApc, // InputApc()
|
|
pDeviceInfo, // ApcContext
|
|
&pDeviceInfo->iosb,
|
|
(PVOID)((PBYTE)pDeviceInfo + pDevTpl->offData),
|
|
pDevTpl->cbData,
|
|
PZERO(LARGE_INTEGER), NULL);
|
|
#endif
|
|
|
|
LOGTIME(pDeviceInfo->timeEndRead);
|
|
|
|
#if DBG
|
|
if (pDeviceInfo->bFlags & GDIF_DBGREAD) {
|
|
TAGMSG2(DBGTAG_PNP, "ZwReadFile of Device handle %lx returned status %lx",
|
|
pDeviceInfo->handle, pDeviceInfo->ReadStatus);
|
|
}
|
|
#endif
|
|
|
|
if (!NT_SUCCESS(pDeviceInfo->ReadStatus)) {
|
|
BEGIN_REENTER_DEVICEINFOLISTCRIT() {
|
|
/*
|
|
* If insufficient resources, retry the read the next time the RIT
|
|
* wakes up for the ID_TIMER event by incrementing gnRetryReadInput
|
|
* (Cheaper than setting our own timer),
|
|
* Else just abandon reading.
|
|
*/
|
|
if (pDeviceInfo->ReadStatus == STATUS_INSUFFICIENT_RESOURCES) {
|
|
if (pDeviceInfo->nRetryRead++ < MAXIMUM_READ_RETRIES) {
|
|
pDeviceInfo->usActions |= GDIAF_RETRYREAD;
|
|
gnRetryReadInput++;
|
|
}
|
|
} else {
|
|
pDeviceInfo->bFlags &= ~GDIF_READING;
|
|
}
|
|
|
|
#ifdef DIAGNOSE_IO
|
|
pDeviceInfo->nReadsOutstanding--;
|
|
#endif
|
|
} END_REENTER_DEVICEINFOLISTCRIT();
|
|
} else {
|
|
pDeviceInfo->nRetryRead = 0;
|
|
}
|
|
|
|
if (!gbRemoteSession && !NT_SUCCESS(pDeviceInfo->ReadStatus))
|
|
RIPMSG2(RIP_WARNING, "StartDeviceRead %#p failed Status %#x",
|
|
pDeviceInfo, pDeviceInfo->ReadStatus);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef GENERIC_INPUT
|
|
/***************************************************************************\
|
|
* StopDeviceRead
|
|
*
|
|
* History:
|
|
* XX-XX-00 Hiroyama created
|
|
\***************************************************************************/
|
|
PDEVICEINFO StopDeviceRead(
|
|
PDEVICEINFO pDeviceInfo)
|
|
{
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
|
|
TAGMSG1(DBGTAG_PNP, "StopDeviceRead(%p)", pDeviceInfo);
|
|
|
|
CheckCritIn();
|
|
CheckDeviceInfoListCritIn();
|
|
|
|
UserAssert(pDeviceInfo->type == DEVICE_TYPE_HID);
|
|
UserAssert(pDeviceInfo->handle);
|
|
UserAssert(pDeviceInfo->OpenerProcess == PsGetCurrentProcessId());
|
|
|
|
/*
|
|
* Stop reading this HID device.
|
|
*/
|
|
pDeviceInfo->bFlags &= ~GDIF_READING;
|
|
|
|
ZwCancelIoFile(pDeviceInfo->handle, &IoStatusBlock);
|
|
UserAssertMsg2(NT_SUCCESS(IoStatusBlock.Status), "NtCancelIoFile handle %x failed status %#x",
|
|
pDeviceInfo->handle, IoStatusBlock.Status);
|
|
|
|
CloseDevice(pDeviceInfo);
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/***************************************************************************\
|
|
* IsKnownKeyboardType
|
|
*
|
|
* Checks if the given type/subtype is the known IDs
|
|
* History:
|
|
* XX-XX-00 Hiroyama created
|
|
\***************************************************************************/
|
|
__inline BOOL IsKnownKeyboardType(
|
|
DWORD dwType,
|
|
DWORD dwSubType)
|
|
{
|
|
switch (dwType) {
|
|
case 4: // Generic
|
|
if ((BYTE)dwSubType == 0xff) {
|
|
/*
|
|
* Bogus subtype, most likely invalid Hydra device.
|
|
*/
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
case 7: // Japanese
|
|
case 8: // Korean
|
|
return TRUE;
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* IsPS2Keyboard
|
|
*
|
|
* return TRUE for the PS/2 device name
|
|
* XX-XX-00 Hiroyama created
|
|
\***************************************************************************/
|
|
__inline BOOL IsPS2Keyboard(
|
|
LPWSTR pwszDevice)
|
|
{
|
|
static const WCHAR wszPS2Header[] = L"\\??\\Root#*";
|
|
static const WCHAR wszPS2HeaderACPI[] = L"\\??\\ACPI#*";
|
|
|
|
return wcsncmp(pwszDevice, wszPS2Header, ARRAY_SIZE(wszPS2Header) - 1) == 0 ||
|
|
wcsncmp(pwszDevice, wszPS2HeaderACPI, ARRAY_SIZE(wszPS2HeaderACPI) - 1) == 0;
|
|
}
|
|
|
|
__inline BOOL IsRDPKeyboard(
|
|
LPWSTR pwszDevice)
|
|
{
|
|
static const WCHAR wszRDPHeader[] = L"\\??\\Root#RDP";
|
|
|
|
return wcsncmp(pwszDevice, wszRDPHeader, ARRAY_SIZE(wszRDPHeader) - 1) == 0;
|
|
}
|
|
|
|
VOID ProcessDeviceChanges(
|
|
DWORD DeviceType)
|
|
{
|
|
PDEVICEINFO pDeviceInfo;
|
|
USHORT usOriginalActions;
|
|
#if DBG
|
|
volatile int nChanges = 0;
|
|
ULONG timeStartReadPrev;
|
|
#endif
|
|
|
|
/*
|
|
* Reset summary information for all Mice and Keyboards
|
|
*/
|
|
DWORD nMice = 0;
|
|
DWORD nWheels = 0;
|
|
DWORD nMaxButtons = 0;
|
|
int nKeyboards = 0;
|
|
BOOLEAN fKeyboardIdSet = FALSE;
|
|
#ifdef GENERIC_INPUT
|
|
int nHid = 0;
|
|
#endif
|
|
|
|
CheckCritIn();
|
|
BEGINATOMICCHECK();
|
|
UserAssert((PtiCurrentShared() == gptiRit) || (PtiCurrentShared() == gTermIO.ptiDesktop));
|
|
|
|
EnterDeviceInfoListCrit();
|
|
BEGINATOMICDEVICEINFOLISTCHECK();
|
|
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
if (gfRecordPnpNotification) {
|
|
RecordPnpNotification(PNP_NTF_PROCESSDEVICECHANGES, NULL, DeviceType);
|
|
}
|
|
#endif
|
|
|
|
if (DeviceType == DEVICE_TYPE_KEYBOARD) {
|
|
/*
|
|
* Set the fallback value.
|
|
*/
|
|
gKeyboardInfo = gKeyboardDefaultInfo;
|
|
}
|
|
|
|
/*
|
|
* Look for devices to Create (those which have newly arrived)
|
|
* and for devices to Terminate (these which have just departed)
|
|
* and for device change notifications.
|
|
* Make sure the actions are processed in the right order in case we
|
|
* are being asked for more than one action per device: for example,
|
|
* we sometimes get QueryRemove followed quickly by RemoveCancelled
|
|
* and both actions arrive here together: we should do them in the
|
|
* correct order.
|
|
*/
|
|
pDeviceInfo = gpDeviceInfoList;
|
|
while (pDeviceInfo) {
|
|
if (pDeviceInfo->type != DeviceType) {
|
|
pDeviceInfo = pDeviceInfo->pNext;
|
|
continue;
|
|
}
|
|
|
|
usOriginalActions = pDeviceInfo->usActions;
|
|
UserAssert((usOriginalActions == 0) || (usOriginalActions & ~GDIAF_PNPWAITING));
|
|
|
|
/*
|
|
* Refresh Mouse:
|
|
* We read a MOUSE_ATTRIBUTES_CHANGED flag when a PS/2 mouse
|
|
* is plugged back in. Find out the attributes of the device.
|
|
*/
|
|
if (pDeviceInfo->usActions & GDIAF_REFRESH_MOUSE) {
|
|
pDeviceInfo->usActions &= ~GDIAF_REFRESH_MOUSE;
|
|
|
|
UserAssert(pDeviceInfo->type == DEVICE_TYPE_MOUSE);
|
|
#if DBG
|
|
nChanges++;
|
|
#endif
|
|
TAGMSG1(DBGTAG_PNP, "QueryDeviceInfo: %lx", pDeviceInfo);
|
|
QueryDeviceInfo(pDeviceInfo);
|
|
}
|
|
|
|
/*
|
|
* QueryRemove:
|
|
* Close the file object, but retain the DEVICEINFO struct and the
|
|
* registration in case we later get a RemoveCancelled.
|
|
*/
|
|
if (pDeviceInfo->usActions & GDIAF_QUERYREMOVE) {
|
|
pDeviceInfo->usActions &= ~GDIAF_QUERYREMOVE;
|
|
#if DBG
|
|
nChanges++;
|
|
#endif
|
|
TAGMSG1(DBGTAG_PNP, "QueryRemove: %lx", pDeviceInfo);
|
|
CloseDevice(pDeviceInfo);
|
|
}
|
|
|
|
/*
|
|
* New device arrived or RemoveCancelled:
|
|
* If new device, Open it, register for notifications and start reading
|
|
* If RemoveCancelled, unregister the old notfications first
|
|
*/
|
|
if (pDeviceInfo->usActions & (GDIAF_ARRIVED | GDIAF_REMOVECANCELLED)) {
|
|
// Reopen the file object, (this is a new file object, of course),
|
|
// Unregister for the old file, register with this new one.
|
|
if (pDeviceInfo->usActions & GDIAF_REMOVECANCELLED) {
|
|
pDeviceInfo->usActions &= ~GDIAF_REMOVECANCELLED;
|
|
#if DBG
|
|
nChanges++;
|
|
#endif
|
|
TAGMSG1(DBGTAG_PNP, "RemoveCancelled: %lx", pDeviceInfo);
|
|
UnregisterForDeviceChangeNotifications(pDeviceInfo);
|
|
}
|
|
|
|
#if DBG
|
|
if (pDeviceInfo->usActions & GDIAF_ARRIVED) {
|
|
nChanges++;
|
|
}
|
|
#endif
|
|
|
|
|
|
pDeviceInfo->usActions &= ~GDIAF_ARRIVED;
|
|
if (OpenDevice(pDeviceInfo)) {
|
|
PDEVICEINFO pDeviceInfoNext;
|
|
|
|
if (!IsRemoteConnection()) {
|
|
RegisterForDeviceChangeNotifications(pDeviceInfo);
|
|
}
|
|
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
|
|
/*
|
|
* If this device is not requested, close the device now.
|
|
*/
|
|
UserAssert(pDeviceInfo->handle);
|
|
UserAssert(pDeviceInfo->hid.pTLCInfo);
|
|
|
|
if (pDeviceInfo->handle && !HidTLCActive(pDeviceInfo->hid.pTLCInfo)) {
|
|
StopDeviceRead(pDeviceInfo); // also closes the handle
|
|
}
|
|
}
|
|
if (!((IsRemoteConnection()) && (pDeviceInfo->usActions & GDIAF_RECONNECT)) && pDeviceInfo->handle) {
|
|
pDeviceInfoNext = StartDeviceRead(pDeviceInfo);
|
|
if (pDeviceInfoNext) {
|
|
/*
|
|
* pDeviceInfo was freed, move onto the next
|
|
*/
|
|
pDeviceInfo = pDeviceInfoNext;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
if (!((IsRemoteConnection()) && (pDeviceInfo->usActions & GDIAF_RECONNECT))) {
|
|
|
|
pDeviceInfoNext = StartDeviceRead(pDeviceInfo);
|
|
if (pDeviceInfoNext) {
|
|
/*
|
|
* pDeviceInfo wasa freed, move onto the next
|
|
*/
|
|
pDeviceInfo = pDeviceInfoNext;
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
pDeviceInfo->usActions &= ~GDIAF_RECONNECT;
|
|
|
|
} else {
|
|
/*
|
|
* If the Open failed, we free the device here, and move on to
|
|
* the next device.
|
|
* Assert to catch re-open failure upon RemoveCancelled.
|
|
*/
|
|
#if DBG
|
|
if ((usOriginalActions & GDIAF_ARRIVED) == 0) {
|
|
RIPMSG2(RIP_WARNING, "Re-Open %#p failed status %x during RemoveCancelled",
|
|
pDeviceInfo, pDeviceInfo->OpenStatus);
|
|
}
|
|
#endif
|
|
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->type == DEVICE_TYPE_HID) {
|
|
/*
|
|
* Some other applications may open this device
|
|
* exclusively. We may succeed to open it later on, so
|
|
* keep this deviceinfo around until it's physically
|
|
* detached.
|
|
*/
|
|
RIPMSG1(RIP_WARNING, "ProcessDeviceChanges: failed to open the device %p",
|
|
pDeviceInfo);
|
|
} else {
|
|
#endif
|
|
pDeviceInfo = FreeDeviceInfo(pDeviceInfo);
|
|
continue;
|
|
#ifdef GENERIC_INPUT
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* RemoveComplete:
|
|
* Close the file object, if you have not already done so, Unregister.
|
|
* FreeDeviceInfo here (which will actually request a free from the
|
|
* reader or the PnP requestor thread), and move on to the next device.
|
|
*/
|
|
if (pDeviceInfo->usActions & GDIAF_DEPARTED) {
|
|
pDeviceInfo->usActions &= ~GDIAF_DEPARTED;
|
|
#if DBG
|
|
nChanges++;
|
|
#endif
|
|
TAGMSG1(DBGTAG_PNP, "RemoveComplete: %lx (process %#x)", pDeviceInfo);
|
|
CloseDevice(pDeviceInfo);
|
|
UnregisterForDeviceChangeNotifications(pDeviceInfo);
|
|
pDeviceInfo = FreeDeviceInfo(pDeviceInfo);
|
|
continue;
|
|
}
|
|
|
|
if (pDeviceInfo->usActions & GDIAF_IME_STATUS) {
|
|
pDeviceInfo->usActions &= ~GDIAF_IME_STATUS;
|
|
#if DBG
|
|
nChanges++;
|
|
#endif
|
|
if ((pDeviceInfo->type == DEVICE_TYPE_KEYBOARD) && (pDeviceInfo->handle)) {
|
|
if (FUJITSU_KBD_CONSOLE(pDeviceInfo->keyboard.Attr.KeyboardIdentifier) ||
|
|
(gbRemoteSession &&
|
|
FUJITSU_KBD_REMOTE(gRemoteClientKeyboardType))
|
|
) {
|
|
/*
|
|
* Fill up the KEYBOARD_IME_STATUS structure.
|
|
*/
|
|
ZwDeviceIoControlFile(pDeviceInfo->handle, NULL, NULL, NULL,
|
|
&giosbKbdControl, IOCTL_KEYBOARD_SET_IME_STATUS,
|
|
(PVOID)&gKbdImeStatus, sizeof(gKbdImeStatus), NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pDeviceInfo->usActions & GDIAF_RETRYREAD) {
|
|
PDEVICEINFO pDeviceInfoNext;
|
|
pDeviceInfo->usActions &= ~GDIAF_RETRYREAD;
|
|
UserAssert(pDeviceInfo->ReadStatus == STATUS_INSUFFICIENT_RESOURCES);
|
|
#if DBG
|
|
timeStartReadPrev = pDeviceInfo->timeStartRead;
|
|
#endif
|
|
TAGMSG2(DBGTAG_PNP, "Retry Read %#p after %lx ticks",
|
|
pDeviceInfo, pDeviceInfo->timeStartRead - timeStartReadPrev);
|
|
pDeviceInfoNext = StartDeviceRead(pDeviceInfo);
|
|
if (pDeviceInfoNext) {
|
|
/*
|
|
* pDeviceInfo wasa freed, move onto the next
|
|
*/
|
|
pDeviceInfo = pDeviceInfoNext;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->usActions & GDIAF_STARTREAD) {
|
|
|
|
pDeviceInfo->usActions &= ~GDIAF_STARTREAD;
|
|
#if DBG
|
|
timeStartReadPrev = pDeviceInfo->timeStartRead;
|
|
#endif
|
|
TAGMSG1(DBGTAG_PNP, "Start Read %#p", pDeviceInfo);
|
|
UserAssert(pDeviceInfo->handle == NULL);
|
|
UserAssert(pDeviceInfo->type == DEVICE_TYPE_HID);
|
|
UserAssert(HidTLCActive(pDeviceInfo->hid.pTLCInfo)); // a bit over active assert?
|
|
|
|
if (!OpenDevice(pDeviceInfo)) {
|
|
/*
|
|
* Failed to open, perhaps some other applications
|
|
* has opened this device exclusively.
|
|
* We can't do nothing more than ignoring the failure.
|
|
* Let's get going.
|
|
*/
|
|
RIPMSG1(RIP_WARNING, "ProcessDeviceChanges: STARTREAD failed to reopen the device %p",
|
|
pDeviceInfo);
|
|
} else {
|
|
PDEVICEINFO pDeviceInfoNext;
|
|
|
|
pDeviceInfoNext = StartDeviceRead(pDeviceInfo);
|
|
if (pDeviceInfoNext) {
|
|
/*
|
|
* pDeviceInfo was freed, move onto the next
|
|
*/
|
|
pDeviceInfo = pDeviceInfoNext;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pDeviceInfo->usActions & GDIAF_STOPREAD) {
|
|
|
|
pDeviceInfo->usActions &= ~GDIAF_STOPREAD;
|
|
UserAssert(pDeviceInfo->type == DEVICE_TYPE_HID);
|
|
if (pDeviceInfo->handle) {
|
|
PDEVICEINFO pDeviceInfoNext;
|
|
|
|
/*
|
|
* StopDeviceRead cancels pending I/O,
|
|
* and closes the device handle,
|
|
* but basically the deviceinfo itself keeps
|
|
* alive.
|
|
*/
|
|
pDeviceInfoNext = StopDeviceRead(pDeviceInfo);
|
|
if (pDeviceInfoNext) {
|
|
/*
|
|
* pDeviceInfo was freed, move onto the next
|
|
*/
|
|
pDeviceInfo = pDeviceInfoNext;
|
|
}
|
|
} else {
|
|
RIPMSG1(RIP_WARNING, "ProcessDeviceChanges: STOPREAD, but handle is already NULL for %p",
|
|
pDeviceInfo);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Gather summary information on open devices
|
|
*/
|
|
if (pDeviceInfo->handle) {
|
|
switch (pDeviceInfo->type) {
|
|
case DEVICE_TYPE_MOUSE:
|
|
UserAssert(PtiCurrentShared() == gTermIO.ptiDesktop);
|
|
if (pDeviceInfo->usActions & GDIAF_REFRESH_MOUSE) {
|
|
pDeviceInfo->usActions &= ~GDIAF_REFRESH_MOUSE;
|
|
#if DBG
|
|
nChanges++;
|
|
#endif
|
|
}
|
|
nMice++;
|
|
nMaxButtons = max(nMaxButtons, pDeviceInfo->mouse.Attr.NumberOfButtons);
|
|
switch(pDeviceInfo->mouse.Attr.MouseIdentifier) {
|
|
case WHEELMOUSE_I8042_HARDWARE:
|
|
case WHEELMOUSE_SERIAL_HARDWARE:
|
|
case WHEELMOUSE_HID_HARDWARE:
|
|
nWheels++;
|
|
}
|
|
break;
|
|
|
|
case DEVICE_TYPE_KEYBOARD:
|
|
UserAssert(PtiCurrentShared() == gptiRit);
|
|
// LEDStatus held in win32k.sys and later force the new keyboard
|
|
// to be set accordingly.
|
|
if (pDeviceInfo->ustrName.Buffer == NULL) {
|
|
/*
|
|
* This most likely is a bogus Hydra device.
|
|
*/
|
|
RIPMSG1(RIP_WARNING, "ProcessDeviceChanges: KBD pDevInfo=%p has no name!", pDeviceInfo);
|
|
if (!fKeyboardIdSet) {
|
|
/*
|
|
* If keyboard id/attr is not set, try to get it from this device
|
|
* anyway. If there are legit PS/2 devices after this, we'll get
|
|
* a chance to re-aquire more meaningful id/attr.
|
|
*/
|
|
goto get_attr_anyway;
|
|
}
|
|
} else {
|
|
NTSTATUS Status;
|
|
|
|
if ((!fKeyboardIdSet || IsPS2Keyboard(pDeviceInfo->ustrName.Buffer)) &&
|
|
!IsRDPKeyboard(pDeviceInfo->ustrName.Buffer)) {
|
|
get_attr_anyway:
|
|
|
|
#if 0
|
|
/*
|
|
* LATER: when other GI stuff in ntinput.c goes in,
|
|
* move this boot-time LED and type/subtype initialization to
|
|
* ntinput.c where the RIT is initialized.
|
|
*/
|
|
#ifdef DIAGNOSE_IO
|
|
gKbdIoctlLEDSStatus =
|
|
#endif
|
|
Status = ZwDeviceIoControlFile(pDeviceInfo->handle, NULL, NULL, NULL,
|
|
&giosbKbdControl, IOCTL_KEYBOARD_QUERY_INDICATORS,
|
|
NULL, 0,
|
|
(PVOID)&gklpBootTime, sizeof(gklpBootTime));
|
|
UserAssertMsg2(NT_SUCCESS(Status),
|
|
"IOCTL_KEYBOARD_QUERY_INDICATORS failed: DeviceInfo %#x, Status %#x",
|
|
pDeviceInfo, Status);
|
|
|
|
TAGMSG1(DBGTAG_PNP, "ProcessDeviceChanges: led flag is %x", gklpBootTime.LedFlags);
|
|
#else
|
|
UNREFERENCED_PARAMETER(Status);
|
|
#endif // 0
|
|
|
|
if (IsKnownKeyboardType(pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Type,
|
|
pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Subtype)) {
|
|
USHORT NumberOfFunctionKeysSave = gKeyboardInfo.NumberOfFunctionKeys;
|
|
|
|
gKeyboardInfo = pDeviceInfo->keyboard.Attr;
|
|
/*
|
|
* Store the maximum number of function keys into gKeyboardInfo.
|
|
*/
|
|
if (NumberOfFunctionKeysSave > gKeyboardInfo.NumberOfFunctionKeys) {
|
|
gKeyboardInfo.NumberOfFunctionKeys = NumberOfFunctionKeysSave;
|
|
}
|
|
} else {
|
|
RIPMSG3(RIP_WARNING, "ProcessDeviceChanges: kbd pDevInfo %p has bogus type/subtype=%x/%x",
|
|
pDeviceInfo,
|
|
pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Type,
|
|
pDeviceInfo->keyboard.Attr.KeyboardIdentifier.Subtype);
|
|
}
|
|
|
|
if (pDeviceInfo->ustrName.Buffer) {
|
|
/*
|
|
* If this is a legit device, remember it so that we won't
|
|
* try to get other non PS/2 keyboard id/attr.
|
|
*/
|
|
fKeyboardIdSet = TRUE;
|
|
}
|
|
}
|
|
}
|
|
nKeyboards++;
|
|
break;
|
|
|
|
#ifdef GENERIC_INPUT
|
|
case DEVICE_TYPE_HID:
|
|
++nHid;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
// Add code for a new type of input device here
|
|
RIPMSG2(RIP_ERROR, "pDeviceInfo %#p has strange type %d",
|
|
pDeviceInfo, pDeviceInfo->type);
|
|
break;
|
|
}
|
|
}
|
|
#ifdef GENERIC_INPUT
|
|
else if (pDeviceInfo->type == DEVICE_TYPE_HID) {
|
|
++nHid;
|
|
TAGMSG1(DBGTAG_PNP, "ProcessDeviceChanges: HID DeviceInfo %p", pDeviceInfo);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Notify the PnP thread that a change has been completed
|
|
*/
|
|
if (usOriginalActions & GDIAF_PNPWAITING) {
|
|
KeSetEvent(pDeviceInfo->pkeHidChangeCompleted, EVENT_INCREMENT, FALSE);
|
|
}
|
|
|
|
pDeviceInfo = pDeviceInfo->pNext;
|
|
}
|
|
|
|
ENDATOMICDEVICEINFOLISTCHECK();
|
|
LeaveDeviceInfoListCrit();
|
|
|
|
|
|
switch (DeviceType) {
|
|
case DEVICE_TYPE_MOUSE:
|
|
/*
|
|
* Apply summary information for Mice
|
|
*/
|
|
if (nMice) {
|
|
if (gnMice == 0) {
|
|
/*
|
|
* We had no mouse before but we have one now: add a cursor
|
|
*/
|
|
SET_GTERMF(GTERMF_MOUSE);
|
|
SYSMET(MOUSEPRESENT) = TRUE;
|
|
SetGlobalCursorLevel(0);
|
|
UserAssert(PpiFromProcess(gpepCSRSS)->ptiList->iCursorLevel == 0);
|
|
UserAssert(PpiFromProcess(gpepCSRSS)->ptiList->pq->iCursorLevel == 0);
|
|
GreMovePointer(gpDispInfo->hDev, gpsi->ptCursor.x, gpsi->ptCursor.y,
|
|
MP_PROCEDURAL);
|
|
}
|
|
} else {
|
|
if (gnMice != 0) {
|
|
/*
|
|
* We had a mouse before but we don't now: remove the cursor
|
|
*/
|
|
CLEAR_GTERMF(GTERMF_MOUSE);
|
|
SYSMET(MOUSEPRESENT) = FALSE;
|
|
SetGlobalCursorLevel(-1);
|
|
/*
|
|
* Don't leave mouse buttons stuck down, clear the global button
|
|
* state here, otherwise weird stuff might happen.
|
|
* Also do this in Alt-Tab processing and zzzCancelJournalling.
|
|
*/
|
|
#if DBG
|
|
if (gwMouseOwnerButton)
|
|
RIPMSG1(RIP_WARNING,
|
|
"gwMouseOwnerButton=%x, being cleared forcibly\n",
|
|
gwMouseOwnerButton);
|
|
#endif
|
|
gwMouseOwnerButton = 0;
|
|
}
|
|
}
|
|
/*
|
|
* Mouse button count represents the number of buttons on the mouse with
|
|
* the most buttons.
|
|
*/
|
|
SYSMET(CMOUSEBUTTONS) = nMaxButtons;
|
|
SYSMET(MOUSEWHEELPRESENT) = (nWheels > 0);
|
|
gnMice = nMice;
|
|
break;
|
|
|
|
case DEVICE_TYPE_KEYBOARD:
|
|
/*
|
|
* Apply summary information for Keyboards
|
|
*/
|
|
|
|
if (nKeyboards > gnKeyboards) {
|
|
/*
|
|
* We have more keyboards, let set their LEDs properly
|
|
*/
|
|
UpdateKeyLights(FALSE);
|
|
/*
|
|
* The new keyboard arrived. Tell the RIT to set
|
|
* the repeat rate.
|
|
*/
|
|
RequestKeyboardRateUpdate();
|
|
}
|
|
if ((nKeyboards != 0) && (gnKeyboards == 0)) {
|
|
/*
|
|
* We had no keyboard but we have one now: set the system hotkeys.
|
|
*/
|
|
SetDebugHotKeys();
|
|
}
|
|
gnKeyboards = nKeyboards;
|
|
break;
|
|
|
|
#ifdef GENERIC_INPUT
|
|
case DEVICE_TYPE_HID:
|
|
gnHid = nHid;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ENDATOMICCHECK();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* RequestDeviceChange()
|
|
*
|
|
* Flag the Device for the specified actions, then set its pkeHidChange to
|
|
* trigger the RIT to perform the actions.
|
|
* The current thread may not be able to do this if it is a PnP notification
|
|
* from another process.
|
|
*
|
|
* History:
|
|
* 01-20-99 IanJa Created.
|
|
\***************************************************************************/
|
|
VOID RequestDeviceChange(
|
|
PDEVICEINFO pDeviceInfo,
|
|
USHORT usAction,
|
|
BOOL fInDeviceInfoListCrit)
|
|
{
|
|
PDEVICE_TEMPLATE pDevTpl = &aDeviceTemplate[pDeviceInfo->type];
|
|
UserAssert(pDevTpl->pkeHidChange != NULL);
|
|
UserAssert((usAction & GDIAF_FREEME) == 0);
|
|
UserAssert((pDeviceInfo->usActions & GDIAF_PNPWAITING) == 0);
|
|
|
|
#if DBG
|
|
if (pDeviceInfo->usActions != 0) {
|
|
TAGMSG3(DBGTAG_PNP, "RequestDeviceChange(%#p, %x), but action %x pending",
|
|
pDeviceInfo, usAction, pDeviceInfo->usActions);
|
|
}
|
|
|
|
/*
|
|
* We can't ask for synchronized actions to be performed on the Device List
|
|
* if we are holding the Device List lock or the User Critical Section:
|
|
* ProcessDeviceChanges() requires both of these itself.
|
|
*/
|
|
//if (usAction & GDIAF_PNPWAITING) {
|
|
// CheckDeviceInfoListCritOut();
|
|
// CheckCritOut();
|
|
//}
|
|
#endif
|
|
|
|
TAGMSG2(DBGTAG_PNP, "RequestDeviceChange(%p, %x)", pDeviceInfo, usAction);
|
|
|
|
/*
|
|
* Grab the DeviceInfoList critical section if we don't already have it
|
|
*/
|
|
UserAssert(!fInDeviceInfoListCrit == !ExIsResourceAcquiredExclusiveLite(gpresDeviceInfoList));
|
|
|
|
#ifdef TRACK_PNP_NOTIFICATION
|
|
if (gfRecordPnpNotification) {
|
|
if (!fInDeviceInfoListCrit) {
|
|
EnterDeviceInfoListCrit();
|
|
}
|
|
RecordPnpNotification(PNP_NTF_REQUESTDEVICECHANGE, pDeviceInfo, usAction);
|
|
if (!fInDeviceInfoListCrit) {
|
|
LeaveDeviceInfoListCrit();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef GENERIC_INPUT
|
|
if (!fInDeviceInfoListCrit) {
|
|
EnterDeviceInfoListCrit();
|
|
}
|
|
CheckDeviceInfoListCritIn();
|
|
pDeviceInfo->usActions |= usAction;
|
|
if ((pDeviceInfo->usActions & (GDIAF_STARTREAD | GDIAF_STOPREAD)) == (GDIAF_STARTREAD | GDIAF_STOPREAD)) {
|
|
pDeviceInfo->usActions &= ~(GDIAF_STARTREAD | GDIAF_STOPREAD);
|
|
}
|
|
if (!fInDeviceInfoListCrit) {
|
|
LeaveDeviceInfoListCrit();
|
|
}
|
|
|
|
#else
|
|
|
|
if (fInDeviceInfoListCrit) {
|
|
CheckDeviceInfoListCritIn();
|
|
pDeviceInfo->usActions |= usAction;
|
|
} else {
|
|
EnterDeviceInfoListCrit();
|
|
pDeviceInfo->usActions |= usAction;
|
|
LeaveDeviceInfoListCrit();
|
|
}
|
|
|
|
#endif
|
|
|
|
if (usAction & GDIAF_PNPWAITING) {
|
|
|
|
CheckDeviceInfoListCritIn();
|
|
KeSetEvent(pDevTpl->pkeHidChange, EVENT_INCREMENT, FALSE);
|
|
LeaveDeviceInfoListCrit();
|
|
KeWaitForSingleObject(pDeviceInfo->pkeHidChangeCompleted, WrUserRequest, KernelMode, FALSE, NULL);
|
|
|
|
|
|
#ifdef GENERIC_INPUT
|
|
BESURE_IN_USERCRIT(pDeviceInfo->usActions & GDIAF_FREEME);
|
|
#endif
|
|
EnterDeviceInfoListCrit();
|
|
/*
|
|
* Assert that nothing else cleared GDIAF_PNPWAITING - only do it here.
|
|
* Check that the action we were waiting for actually occurred.
|
|
*/
|
|
UserAssert(pDeviceInfo->usActions & GDIAF_PNPWAITING);
|
|
pDeviceInfo->usActions &= ~GDIAF_PNPWAITING;
|
|
UserAssert((pDeviceInfo->usActions & usAction) == 0);
|
|
if (pDeviceInfo->usActions & GDIAF_FREEME) {
|
|
FreeDeviceInfo(pDeviceInfo);
|
|
}
|
|
#ifdef GENERIC_INPUT
|
|
LeaveDeviceInfoListCrit();
|
|
END_IN_USERCRIT();
|
|
EnterDeviceInfoListCrit();
|
|
#endif
|
|
} else {
|
|
KeSetEvent(pDevTpl->pkeHidChange, EVENT_INCREMENT, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
* RemoveInputDevices()
|
|
*
|
|
* Used to detach input devices from a session. When disconnecting from a
|
|
* session that owns the local input devices we need to release them so that
|
|
* the new session that will take ownership of local console can use them
|
|
*
|
|
\***************************************************************************/
|
|
VOID RemoveInputDevices(
|
|
VOID)
|
|
{
|
|
PDEVICEINFO pDeviceInfo;
|
|
ULONG DeviceType;
|
|
NTSTATUS Status;
|
|
|
|
|
|
/*
|
|
* First Thing is to remove device class notification.
|
|
*/
|
|
for (DeviceType = 0; DeviceType <= DEVICE_TYPE_MAX; DeviceType++) {
|
|
if (aDeviceClassNotificationEntry[DeviceType] != NULL) {
|
|
IoUnregisterPlugPlayNotification(aDeviceClassNotificationEntry[DeviceType]);
|
|
aDeviceClassNotificationEntry[DeviceType] = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Then walk the device liste detaching mice and keyboads.
|
|
*/
|
|
|
|
EnterDeviceInfoListCrit();
|
|
PNP_SAFE_DEVICECRIT_IN();
|
|
pDeviceInfo = gpDeviceInfoList;
|
|
while (pDeviceInfo) {
|
|
#ifdef GENERIC_INPUT
|
|
if (pDeviceInfo->usActions & (GDIAF_DEPARTED | GDIAF_FREEME)) {
|
|
pDeviceInfo = pDeviceInfo->pNext;
|
|
continue;
|
|
}
|
|
#else
|
|
if ((pDeviceInfo->type != DEVICE_TYPE_KEYBOARD && pDeviceInfo->type != DEVICE_TYPE_MOUSE) ||
|
|
(pDeviceInfo->usActions & GDIAF_DEPARTED) ||
|
|
(pDeviceInfo->usActions & GDIAF_FREEME) ) {
|
|
pDeviceInfo = pDeviceInfo->pNext;
|
|
continue;
|
|
}
|
|
#endif
|
|
#ifdef PRERELEASE
|
|
pDeviceInfo->fForcedDetach = TRUE;
|
|
#endif
|
|
RequestDeviceChange(pDeviceInfo, GDIAF_DEPARTED, TRUE);
|
|
pDeviceInfo = gpDeviceInfoList;
|
|
}
|
|
LeaveDeviceInfoListCrit();
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* AttachInputDevices
|
|
*
|
|
* Used to Attach input devices to a session.
|
|
*
|
|
\***************************************************************************/
|
|
BOOL AttachInputDevices(
|
|
BOOL bLocalDevices)
|
|
{
|
|
UNICODE_STRING ustrName;
|
|
BOOL fSuccess = TRUE;
|
|
|
|
if (!bLocalDevices) {
|
|
RtlInitUnicodeString(&ustrName, NULL);
|
|
fSuccess &= !!CreateDeviceInfo(DEVICE_TYPE_MOUSE, &ustrName, 0);
|
|
fSuccess &= !!CreateDeviceInfo(DEVICE_TYPE_KEYBOARD, &ustrName, 0);
|
|
|
|
|
|
if (!fSuccess) {
|
|
RIPMSG0(RIP_WARNING, "AttachInputDevices Failed the creation of input devices");
|
|
}
|
|
} else {
|
|
/*
|
|
* For local devices just register Device class notification and let
|
|
* PnP call us back.
|
|
*/
|
|
xxxRegisterForDeviceClassNotifications();
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|