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.
1755 lines
47 KiB
1755 lines
47 KiB
/*++
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ENUM.C
|
|
|
|
Abstract:
|
|
|
|
This module contains the enumeration code needed to figure out
|
|
whether or not a device is attached to the serial port. If there
|
|
is one, it will obtain the PNP COM ID (if the device is PNP) and
|
|
parse out the relevant fields.
|
|
|
|
@@BEGIN_DDKSPLIT
|
|
|
|
Author:
|
|
|
|
Jay Senior
|
|
|
|
@@END_DDKSPLIT
|
|
|
|
Environment:
|
|
|
|
kernel mode only
|
|
|
|
Notes:
|
|
|
|
|
|
@@BEGIN_DDKSPLIT
|
|
|
|
Revision History:
|
|
Louis J. Giliberto, Jr. 22-March-1998 Cleanup
|
|
Louis J. Giliberto, Jr. 11-Jan-2000 Cleanup / fix postponed
|
|
|
|
@@END_DDKSPLIT
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
#define MAX_DEVNODE_NAME 256 // Total size of Device ID
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGESENM, SerenumValidateID)
|
|
#pragma alloc_text(PAGESENM, SerenumDoEnumProtocol)
|
|
#pragma alloc_text(PAGESENM, SerenumCheckForLegacyDevice)
|
|
#pragma alloc_text(PAGESENM, SerenumScanOtherIdForMouse)
|
|
#pragma alloc_text(PAGESENM, Serenum_ReenumerateDevices)
|
|
#pragma alloc_text(PAGESENM, Serenum_IoSyncReq)
|
|
#pragma alloc_text(PAGESENM, Serenum_IoSyncReqWithIrp)
|
|
#pragma alloc_text(PAGESENM, Serenum_IoSyncIoctlEx)
|
|
#pragma alloc_text(PAGESENM, Serenum_ReadSerialPort)
|
|
#pragma alloc_text(PAGESENM, Serenum_Wait)
|
|
#pragma alloc_text(PAGESENM, SerenumReleaseThreadReference)
|
|
|
|
//#pragma alloc_text (PAGE, Serenum_GetRegistryKeyValue)
|
|
#endif
|
|
|
|
#if !defined(__isascii)
|
|
#define __isascii(_c) ( (unsigned)(_c) < 0x80 )
|
|
#endif // !defined(__isascii)
|
|
|
|
void
|
|
SerenumScanOtherIdForMouse(IN PCHAR PBuffer, IN ULONG BufLen,
|
|
OUT PCHAR *PpMouseId)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routines a PnP packet for a mouse ID up to the first PnP delimiter
|
|
(i.e, '(').
|
|
|
|
Arguments:
|
|
|
|
PBuffer - Pointer to the buffer to scan
|
|
|
|
BufLen - Length of the buffer in bytes
|
|
|
|
PpMouseId - Pointer to the pointer to the mouse ID (this will be set
|
|
to point to the location in the buffer where the mouse ID
|
|
was found)
|
|
|
|
Return value:
|
|
|
|
void
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
*PpMouseId = PBuffer;
|
|
|
|
while (BufLen--) {
|
|
if (**PpMouseId == 'M' || **PpMouseId == 'B') {
|
|
return;
|
|
} else if (**PpMouseId == '(' || **PpMouseId == ('(' - 0x20)) {
|
|
*PpMouseId = NULL;
|
|
return;
|
|
}
|
|
(*PpMouseId)++;
|
|
}
|
|
|
|
*PpMouseId = NULL;
|
|
}
|
|
|
|
#if DBG
|
|
VOID SerenumHexDump(PUCHAR PBuf, ULONG NBytes)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Hex dump a buffer with NPerRow chars per row output
|
|
|
|
Arguments:
|
|
|
|
PBuf - Pointer to the buffer to dump
|
|
|
|
NBytes - Length of the buffer in bytes
|
|
|
|
Return value:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
const ULONG NPerRow = 20;
|
|
|
|
ULONG dmpi;
|
|
ULONG col;
|
|
UCHAR c;
|
|
ULONG LoopCount = 1;
|
|
ULONG dividend = NBytes / NPerRow;
|
|
ULONG remainder = NBytes % NPerRow;
|
|
ULONG nHexChars = NPerRow;
|
|
ULONG nSpaces = 1;
|
|
|
|
DbgPrint("SERENUM: Raw Data Packet on probe\n");
|
|
|
|
if (remainder) {
|
|
LoopCount++;
|
|
}
|
|
|
|
for (dmpi = 0; dmpi < (dividend + 1); dmpi++) {
|
|
DbgPrint("-------: ");
|
|
|
|
for (col = 0; col < nHexChars; col++) {
|
|
DbgPrint("%02x ", (unsigned char)PBuf[dmpi * NPerRow + col]);
|
|
}
|
|
|
|
for (col = 0; col < nSpaces; col++) {
|
|
DbgPrint(" ");
|
|
}
|
|
|
|
for (col = 0; col < nHexChars; col++){
|
|
c = PBuf[dmpi * NPerRow + col];
|
|
|
|
if (__isascii(c) && (c > ' ')){
|
|
DbgPrint("%c", c);
|
|
}else{
|
|
DbgPrint(".");
|
|
}
|
|
}
|
|
|
|
DbgPrint("\n");
|
|
|
|
//
|
|
// If this is the last one, then we have less that NPerRow to dump
|
|
//
|
|
|
|
if (dmpi == dividend) {
|
|
if (remainder == 0) {
|
|
//
|
|
// This was an even multiple -- we're done
|
|
//
|
|
|
|
break; // for (dmpi)
|
|
} else {
|
|
nHexChars = remainder;
|
|
nSpaces = NPerRow - nHexChars;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // DBG
|
|
|
|
NTSTATUS
|
|
SerenumDoEnumProtocol(PFDO_DEVICE_DATA PFdoData, PUCHAR *PpBuf, PUSHORT PNBytes,
|
|
PBOOLEAN PDSRMissing)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
ULONG i;
|
|
ULONG bitMask;
|
|
KEVENT event;
|
|
KTIMER timer;
|
|
NTSTATUS status;
|
|
PUCHAR pReadBuf;
|
|
USHORT nRead;
|
|
PDEVICE_OBJECT pDevStack = PFdoData->TopOfStack;
|
|
SERIAL_BAUD_RATE baudRate;
|
|
SERIAL_LINE_CONTROL lineControl;
|
|
SERIAL_HANDFLOW handflow;
|
|
|
|
#if DBG
|
|
#define PERFCNT 1
|
|
#endif
|
|
|
|
#if defined(PERFCNT)
|
|
LARGE_INTEGER perfFreq;
|
|
LARGE_INTEGER stPerfCnt, endPerfCnt;
|
|
LONG diff;
|
|
#endif
|
|
|
|
LARGE_INTEGER DefaultWait;
|
|
|
|
PAGED_CODE();
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
KeInitializeTimer(&timer);
|
|
|
|
#if defined(PERFCNT)
|
|
perfFreq.QuadPart = (LONGLONG) 0;
|
|
#endif
|
|
DefaultWait.QuadPart = (LONGLONG) -(SERENUM_DEFAULT_WAIT);
|
|
*PpBuf = NULL;
|
|
pReadBuf = NULL;
|
|
nRead = 0;
|
|
*PDSRMissing = FALSE;
|
|
|
|
LOGENTRY(LOG_ENUM, 'SDEP', PFdoData, PpBuf, PDSRMissing);
|
|
|
|
|
|
pReadBuf = ExAllocatePool(NonPagedPool, MAX_DEVNODE_NAME + sizeof(CHAR) );
|
|
*(pReadBuf + MAX_DEVNODE_NAME) = 0;
|
|
if (pReadBuf == NULL) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
LOGENTRY(LOG_ENUM, 'SDE1', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
}
|
|
|
|
//
|
|
// Set DTR
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Setting DTR...\n"));
|
|
|
|
status = Serenum_IoSyncIoctl(IOCTL_SERIAL_SET_DTR, FALSE, pDevStack, &event);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDE2', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
}
|
|
|
|
//
|
|
// Clear RTS
|
|
//
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Clearing RTS...\n"));
|
|
|
|
status = Serenum_IoSyncIoctl(IOCTL_SERIAL_CLR_RTS, FALSE, pDevStack, &event);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDE3', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
}
|
|
|
|
//
|
|
// Wait for the default timeout period
|
|
//
|
|
|
|
#if defined(PERFCNT)
|
|
stPerfCnt = KeQueryPerformanceCounter(&perfFreq);
|
|
#endif
|
|
|
|
status = Serenum_Wait(&timer, DefaultWait);
|
|
|
|
#if defined(PERFCNT)
|
|
endPerfCnt = KeQueryPerformanceCounter(NULL);
|
|
diff = (LONG)(endPerfCnt.QuadPart - stPerfCnt.QuadPart);
|
|
diff *= 1000;
|
|
diff /= (LONG)perfFreq.QuadPart;
|
|
|
|
LOGENTRY(LOG_ENUM, 'SDT0', PFdoData, diff, 0);
|
|
#endif
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_ERROR,
|
|
("Timer failed with status %x\n", status ));
|
|
LOGENTRY(LOG_ENUM, 'SDE4', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
}
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Checking DSR...\n"));
|
|
|
|
status = Serenum_IoSyncIoctlEx(IOCTL_SERIAL_GET_MODEMSTATUS, FALSE,
|
|
pDevStack, &event, NULL, 0, &bitMask,
|
|
sizeof(ULONG));
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDE5', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
}
|
|
|
|
//
|
|
// If DSR is not set, then a legacy device (like a mouse) may be attached --
|
|
// they are not required to assert DSR when they are present and ready.
|
|
//
|
|
|
|
if ((SERIAL_DSR_STATE & bitMask) == 0) {
|
|
Serenum_KdPrint (PFdoData, SER_DBG_SS_TRACE,
|
|
("No PNP device available - DSR not set.\n"));
|
|
*PDSRMissing = TRUE;
|
|
LOGENTRY(LOG_ENUM, 'SDND', PFdoData, 0, 0);
|
|
}
|
|
|
|
//
|
|
// Setup the serial port for 1200 bits/s, 7 data bits,
|
|
// no parity, one stop bit
|
|
//
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Setting baud rate to 1200..."
|
|
"\n"));
|
|
baudRate.BaudRate = 1200;
|
|
status = Serenum_IoSyncIoctlEx(IOCTL_SERIAL_SET_BAUD_RATE, FALSE, pDevStack,
|
|
&event, &baudRate, sizeof(SERIAL_BAUD_RATE),
|
|
NULL, 0);
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDE6', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
}
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Setting the line control...\n"));
|
|
|
|
lineControl.StopBits = STOP_BIT_1;
|
|
lineControl.Parity = NO_PARITY;
|
|
lineControl.WordLength = 7;
|
|
|
|
status = Serenum_IoSyncIoctlEx(IOCTL_SERIAL_SET_LINE_CONTROL, FALSE,
|
|
pDevStack, &event, &lineControl,
|
|
sizeof(SERIAL_LINE_CONTROL), NULL, 0);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDE7', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
}
|
|
|
|
|
|
//
|
|
// loop twice
|
|
// The first iteration is for reading the PNP ID string from modems
|
|
// and mice.
|
|
// The second iteration is for other devices.
|
|
//
|
|
for (i = 0; i < 2; i++) {
|
|
//
|
|
// Purge the buffers before reading
|
|
//
|
|
|
|
LOGENTRY(LOG_ENUM, 'SDEI', PFdoData, i, 0);
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Purging all buffers...\n"));
|
|
|
|
bitMask = SERIAL_PURGE_RXCLEAR;
|
|
|
|
status = Serenum_IoSyncIoctlEx(IOCTL_SERIAL_PURGE, FALSE, pDevStack,
|
|
&event, &bitMask, sizeof(ULONG), NULL, 0);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDE8', PFdoData, status, 0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Clear DTR
|
|
//
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Clearing DTR...\n"));
|
|
|
|
status = Serenum_IoSyncIoctl(IOCTL_SERIAL_CLR_DTR, FALSE, pDevStack,
|
|
&event);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDE9', PFdoData, status, 0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Clear RTS
|
|
//
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Clearing RTS...\n"));
|
|
|
|
status = Serenum_IoSyncIoctl(IOCTL_SERIAL_CLR_RTS, FALSE, pDevStack,
|
|
&event);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDEA', PFdoData, status, 0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Set a timer for 200 ms
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Waiting...\n"));
|
|
|
|
#if defined(PERFCNT)
|
|
stPerfCnt = KeQueryPerformanceCounter(&perfFreq);
|
|
#endif
|
|
|
|
status = Serenum_Wait(&timer, DefaultWait);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_ERROR,
|
|
("Timer failed with status %x\n", status ));
|
|
LOGENTRY(LOG_ENUM, 'SDEB', PFdoData, status, 0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// set DTR
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Setting DTR...\n"));
|
|
|
|
status = Serenum_IoSyncIoctl(IOCTL_SERIAL_SET_DTR, FALSE, pDevStack,
|
|
&event);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDEC', PFdoData, status, 0);
|
|
break;
|
|
}
|
|
|
|
|
|
#if defined(PERFCNT)
|
|
endPerfCnt = KeQueryPerformanceCounter(NULL);
|
|
diff = (LONG)(endPerfCnt.QuadPart - stPerfCnt.QuadPart);
|
|
diff *= 1000;
|
|
diff /= (LONG)perfFreq.QuadPart;
|
|
|
|
LOGENTRY(LOG_ENUM, 'SDT1', PFdoData, diff, 0);
|
|
#endif
|
|
|
|
//
|
|
// First iteration is for modems
|
|
// Therefore wait for 200 ms as per protocol for getting PNP string out
|
|
//
|
|
|
|
if (!i) {
|
|
status = Serenum_Wait(&timer, DefaultWait);
|
|
if (!NT_SUCCESS(status)) {
|
|
Serenum_KdPrint (PFdoData, SER_DBG_SS_ERROR,
|
|
("Timer failed with status %x\n", status ));
|
|
LOGENTRY(LOG_ENUM, 'SDED', PFdoData, status, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// set RTS
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Setting RTS...\n"));
|
|
|
|
status = Serenum_IoSyncIoctl(IOCTL_SERIAL_SET_RTS, FALSE, pDevStack,
|
|
&event);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LOGENTRY(LOG_ENUM, 'SDEF', PFdoData, status, 0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Read from the serial port
|
|
//
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Reading the serial port...\n"));
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_INFO, ("Address: %x\n", pReadBuf));
|
|
|
|
nRead = 0;
|
|
|
|
#if DBG
|
|
RtlFillMemory(pReadBuf, MAX_DEVNODE_NAME, 0xff);
|
|
#endif
|
|
|
|
//
|
|
// Flush the input buffer
|
|
//
|
|
|
|
status = Serenum_ReadSerialPort(pReadBuf, MAX_DEVNODE_NAME,
|
|
SERENUM_SERIAL_READ_TIME, &nRead,
|
|
&ioStatusBlock, PFdoData);
|
|
|
|
switch (status) {
|
|
case STATUS_TIMEOUT:
|
|
if (nRead == 0) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Timeout with no bytes read; continuing\n"));
|
|
LOGENTRY(LOG_ENUM, 'SDEG', PFdoData, status, 0);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// We timed out with data, so we use what we have
|
|
//
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
LOGENTRY(LOG_ENUM, 'SDEH', PFdoData, status, 0);
|
|
break;
|
|
|
|
case STATUS_SUCCESS:
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Read succeeded\n"));
|
|
LOGENTRY(LOG_ENUM, 'SDEJ', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
break;
|
|
|
|
default:
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Read failed with 0x%x\n",
|
|
status));
|
|
LOGENTRY(LOG_ENUM, 'SDEK', PFdoData, status, 0);
|
|
goto ProtocolDone;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If anything was read from the serial port, we're done!
|
|
//
|
|
|
|
if (nRead) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ProtocolDone:;
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
if (pReadBuf != NULL) {
|
|
ExFreePool(pReadBuf);
|
|
pReadBuf = NULL;
|
|
}
|
|
}
|
|
|
|
*PNBytes = nRead;
|
|
*PpBuf = pReadBuf;
|
|
LOGENTRY(LOG_ENUM, 'SDE0', PFdoData, status, nRead);
|
|
return status;
|
|
}
|
|
|
|
BOOLEAN
|
|
SerenumValidateID(IN PUNICODE_STRING PId)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This validates all the characters in a MULTI_SZ for a Pnp ID.
|
|
|
|
Invalid characters are:
|
|
c < 0x20 (' ')
|
|
c > 0x7F
|
|
c == 0x2C (',')
|
|
|
|
Arguments:
|
|
|
|
PId - Pointer to a multi_sz containing the IDs
|
|
|
|
Return value:
|
|
|
|
BOOLEAN -- TRUE if valid ID, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
WCHAR *cp;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Walk each string in the multisz and check for bad characters
|
|
//
|
|
|
|
cp = PId->Buffer;
|
|
|
|
if (cp == NULL) {
|
|
return TRUE;
|
|
}
|
|
|
|
do {
|
|
while (*cp) {
|
|
if ((*cp < L' ') || (*cp > L'\x7f') || (*cp == L',') ) {
|
|
return FALSE;
|
|
}
|
|
|
|
cp++;
|
|
}
|
|
|
|
cp++;
|
|
} while (*cp);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
SerenumCheckForLegacyDevice(IN PFDO_DEVICE_DATA PFdoData, IN PCHAR PIdBuf,
|
|
IN ULONG BufferLen,
|
|
IN OUT PUNICODE_STRING PHardwareIDs,
|
|
IN OUT PUNICODE_STRING PCompIDs,
|
|
IN OUT PUNICODE_STRING PDeviceIDs)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine implements legacy mouse detection
|
|
|
|
Arguments:
|
|
|
|
PFdoData - pointer to the FDO's device-specific data
|
|
PIdBuf - Buffer of data returned from device
|
|
BufferLen - length of PIdBuf in bytes
|
|
PHardwareIDs - MULTI_SZ to return hardware ID's in
|
|
PCompIDs - MULTI_SZ to return compatible ID's in
|
|
PDeviceIDs - MULTI_SZ to return device ID's in
|
|
|
|
Return value:
|
|
|
|
BOOLEAN -- TRUE if mouse detected, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
PCHAR mouseId = PIdBuf;
|
|
ULONG charCnt;
|
|
BOOLEAN rval = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
SerenumScanOtherIdForMouse(PIdBuf, BufferLen, &mouseId);
|
|
|
|
if (mouseId != NULL) {
|
|
//
|
|
// A legacy device is attached to the serial port, since DSR was
|
|
// not set when RTS was set.
|
|
// If we find a mouse from the PIdBuf, copy the appropriate
|
|
// strings into the hardwareIDs and compIDs manually.
|
|
//
|
|
if (*mouseId == 'M') {
|
|
if ((mouseId - PIdBuf) > 1 && mouseId[1] == '3') {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("*PNP0F08 mouse\n"));
|
|
Serenum_InitMultiString(PFdoData, PHardwareIDs, "*PNP0F08", NULL);
|
|
Serenum_InitMultiString(PFdoData, PCompIDs, "SERIAL_MOUSE", NULL);
|
|
//
|
|
// ADRIAO CIMEXCIMEX 04/28/1999 -
|
|
// Device ID's should be unique, at least as unique as the
|
|
// hardware ID's. This ID should really be Serenum\\PNP0F08
|
|
//
|
|
Serenum_InitMultiString(PFdoData, PDeviceIDs, "Serenum\\Mouse",
|
|
NULL);
|
|
rval = TRUE;
|
|
|
|
} else {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("*PNP0F01 mouse\n"));
|
|
Serenum_InitMultiString(PFdoData, PHardwareIDs, "*PNP0F01", NULL);
|
|
Serenum_InitMultiString(PFdoData, PCompIDs, "SERIAL_MOUSE", NULL);
|
|
//
|
|
// ADRIAO CIMEXCIMEX 04/28/1999 -
|
|
// Device ID's should be unique, at least as unique as the
|
|
// hardware ID's. This ID should really be Serenum\\PNP0F01
|
|
//
|
|
Serenum_InitMultiString(PFdoData, PDeviceIDs, "Serenum\\Mouse",
|
|
NULL);
|
|
rval = TRUE;
|
|
}
|
|
} else if (*mouseId == 'B') {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("*PNP0F09 mouse\n"));
|
|
Serenum_InitMultiString(PFdoData, PHardwareIDs, "*PNP0F09", NULL);
|
|
Serenum_InitMultiString(PFdoData, PCompIDs, "*PNP0F0F", "SERIAL_MOUSE",
|
|
NULL);
|
|
//
|
|
// ADRIAO CIMEXCIMEX 04/28/1999 -
|
|
// Device ID's should be unique, at least as unique as the
|
|
// hardware ID's. This ID should really be Serenum\\PNP0F09
|
|
//
|
|
Serenum_InitMultiString(PFdoData, PDeviceIDs, "Serenum\\BallPoint",
|
|
NULL);
|
|
rval = TRUE;
|
|
}
|
|
|
|
#if DBG
|
|
if (rval) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Buffers at 0x%x 0x%x 0x%x\n",
|
|
PHardwareIDs->Buffer, PCompIDs->Buffer,
|
|
PDeviceIDs->Buffer));
|
|
}
|
|
#endif // DBG
|
|
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
NTSTATUS
|
|
Serenum_ReenumerateDevices(IN PIRP Irp, IN PFDO_DEVICE_DATA PFdoData,
|
|
PBOOLEAN PSameDevice)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This enumerates the serenum bus which is represented by Fdo (a pointer
|
|
to the device object representing the serial bus). It creates new PDOs
|
|
for any new devices which have been discovered since the last enumeration
|
|
|
|
Arguments:
|
|
|
|
PFdoData - Pointer to the fdo's device extension
|
|
for the serial bus which needs to be enumerated
|
|
Irp - Pointer to the Irp which was sent to reenumerate.
|
|
|
|
Return value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
KEVENT event;
|
|
KTIMER timer;
|
|
|
|
UNICODE_STRING pdoUniName;
|
|
PDEVICE_OBJECT pdo = PFdoData->NewPDO;
|
|
PDEVICE_OBJECT pDevStack = PFdoData->TopOfStack;
|
|
PPDO_DEVICE_DATA pdoData;
|
|
UNICODE_STRING hardwareIDs;
|
|
UNICODE_STRING compIDs;
|
|
UNICODE_STRING deviceIDs;
|
|
UNICODE_STRING devDesc;
|
|
UNICODE_STRING serNo;
|
|
UNICODE_STRING pnpRev;
|
|
HANDLE pnpKey;
|
|
|
|
|
|
BOOLEAN DSRMissing = FALSE;
|
|
BOOLEAN legacyDeviceFound = FALSE;
|
|
|
|
USHORT nActual = 0;
|
|
ULONG i;
|
|
|
|
PCHAR pReadBuf = NULL;
|
|
WCHAR pdoName[] = SERENUM_PDO_NAME_BASE;
|
|
|
|
SERIAL_BASIC_SETTINGS basicSettings;
|
|
BOOLEAN basicSettingsDone = FALSE;
|
|
SERIAL_TIMEOUTS timeouts, newTimeouts;
|
|
|
|
BOOLEAN validIDs = TRUE;
|
|
KIRQL oldIrql;
|
|
ULONG curTry = 0;
|
|
BOOLEAN sameDevice = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// While enumeration is taking place, we can't allow a Create to come down
|
|
// from an upper driver. We use this semaphore to protect ourselves.
|
|
//
|
|
|
|
status = KeWaitForSingleObject(&PFdoData->CreateSemaphore, Executive,
|
|
KernelMode, FALSE, NULL);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
RtlInitUnicodeString(&pdoUniName, pdoName);
|
|
pdoName[((sizeof(pdoName)/sizeof(WCHAR)) - 2)] = L'0' + PFdoData->PdoIndex++;
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
KeInitializeTimer(&timer);
|
|
|
|
RtlInitUnicodeString(&hardwareIDs, NULL);
|
|
RtlInitUnicodeString(&compIDs, NULL);
|
|
RtlInitUnicodeString(&deviceIDs, NULL);
|
|
RtlInitUnicodeString(&devDesc, NULL);
|
|
RtlInitUnicodeString(&serNo, NULL);
|
|
RtlInitUnicodeString(&pnpRev, NULL);
|
|
|
|
//
|
|
// If the current PDO should be marked missing, do so.
|
|
//
|
|
if (PFdoData->PDOForcedRemove && pdo != NULL) {
|
|
Serenum_PDO_EnumMarkMissing(PFdoData, pdo->DeviceExtension);
|
|
pdo = NULL;
|
|
}
|
|
|
|
//
|
|
// Open the Serial port before sending Irps down
|
|
// Use the Irp passed to us, and grab it on the way up.
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Opening the serial port...\n"));
|
|
|
|
status = Serenum_IoSyncReqWithIrp(Irp, IRP_MJ_CREATE, &event, pDevStack);
|
|
|
|
LOGENTRY(LOG_ENUM, 'SRRO', PFdoData, status, 0);
|
|
|
|
//
|
|
// If we cannot open the stack, odd's are we have a live and started PDO on
|
|
// it. Since enumeration might interfere with running devices, we do not
|
|
// adjust our list of children if we cannot open the stack.
|
|
//
|
|
if (!NT_SUCCESS(status)) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Failed to open the serial port...\n"));
|
|
KeReleaseSemaphore(&PFdoData->CreateSemaphore, IO_NO_INCREMENT, 1, FALSE);
|
|
LOGENTRY(LOG_ENUM, 'SRR1', PFdoData, status, 0);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Set up the COM port
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Setting up port\n"));
|
|
|
|
status = Serenum_IoSyncIoctlEx(IOCTL_SERIAL_INTERNAL_BASIC_SETTINGS, TRUE,
|
|
pDevStack, &event, NULL, 0,
|
|
&basicSettings, sizeof(basicSettings));
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
basicSettingsDone = TRUE;
|
|
} else {
|
|
//
|
|
// This "serial" driver doesn't support BASIC_SETTINGS so instead
|
|
// we just set what we really need the old fashioned way
|
|
//
|
|
|
|
status = Serenum_IoSyncIoctlEx(IOCTL_SERIAL_GET_TIMEOUTS, FALSE,
|
|
pDevStack, &event,
|
|
NULL, 0, &timeouts, sizeof(timeouts));
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
//
|
|
// This should not happen because we are sending an Ioctl to Serial
|
|
// but for robustness of the code we check the return status.
|
|
//
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Failed to get the serial timeouts...\n"));
|
|
KeReleaseSemaphore(&PFdoData->CreateSemaphore, IO_NO_INCREMENT, 1, FALSE);
|
|
return status;
|
|
}
|
|
|
|
RtlZeroMemory(&newTimeouts, sizeof(newTimeouts));
|
|
|
|
Serenum_IoSyncIoctlEx(IOCTL_SERIAL_SET_TIMEOUTS, FALSE, pDevStack, &event,
|
|
&newTimeouts, sizeof(newTimeouts), NULL, 0);
|
|
}
|
|
|
|
|
|
//
|
|
// Run the serial PnP device detection protocol; give it up to 3 tries
|
|
//
|
|
|
|
while (curTry <= 2) {
|
|
if (pReadBuf) {
|
|
ExFreePool(pReadBuf);
|
|
pReadBuf = NULL;
|
|
}
|
|
|
|
status = SerenumDoEnumProtocol(PFdoData, &pReadBuf, &nActual,
|
|
&DSRMissing);
|
|
|
|
if (status == STATUS_SUCCESS) {
|
|
break;
|
|
}
|
|
|
|
curTry++;
|
|
}
|
|
|
|
//
|
|
// If DSR wasn't set any existing pdos will be eliminated
|
|
//
|
|
|
|
|
|
if (basicSettingsDone) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Restoring basic settings\n"));
|
|
|
|
Serenum_IoSyncIoctlEx(IOCTL_SERIAL_INTERNAL_RESTORE_SETTINGS, TRUE,
|
|
pDevStack, &event, &basicSettings,
|
|
sizeof(basicSettings), NULL, 0);
|
|
} else {
|
|
Serenum_IoSyncIoctlEx(IOCTL_SERIAL_SET_TIMEOUTS, FALSE, pDevStack, &event,
|
|
&timeouts, sizeof(timeouts), NULL, 0);
|
|
}
|
|
|
|
//
|
|
// Cleanup and then Close
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Cleanup on the serial port...\n"));
|
|
|
|
//
|
|
// We ignore the status -- we have to finish closing
|
|
//
|
|
|
|
(void)Serenum_IoSyncReqWithIrp(Irp, IRP_MJ_CLEANUP, &event, pDevStack);
|
|
|
|
#if DBG
|
|
if (!NT_SUCCESS(status)) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_ERROR,
|
|
("Failed to cleanup the serial port...\n"));
|
|
// don't return because we want to attempt to close!
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Close the Serial port after everything is done
|
|
//
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Closing the serial port...\n"));
|
|
|
|
//
|
|
// We ignore the status -- we have to close!!
|
|
//
|
|
|
|
Serenum_IoSyncReqWithIrp(Irp, IRP_MJ_CLOSE, &event, pDevStack);
|
|
|
|
LOGENTRY(LOG_ENUM, 'SRRC', PFdoData, 0, 0);
|
|
|
|
//
|
|
// Our status is that of the enumeration
|
|
//
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_ERROR,
|
|
("Failed to enumerate the serial port...\n"));
|
|
KeReleaseSemaphore(&PFdoData->CreateSemaphore, IO_NO_INCREMENT, 1, FALSE);
|
|
if (pReadBuf != NULL) {
|
|
ExFreePool(pReadBuf);
|
|
}
|
|
LOGENTRY(LOG_ENUM, 'SRR2', PFdoData, status, 0);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Check if anything was read, and if not, we're done
|
|
//
|
|
|
|
if (nActual == 0) {
|
|
if (pReadBuf != NULL) {
|
|
ExFreePool(pReadBuf);
|
|
pReadBuf = NULL;
|
|
}
|
|
|
|
if (pdo != NULL) {
|
|
//
|
|
// Something was there. The device must have been unplugged.
|
|
// Remove the PDO.
|
|
//
|
|
|
|
Serenum_PDO_EnumMarkMissing(PFdoData, pdo->DeviceExtension);
|
|
pdo = NULL;
|
|
}
|
|
|
|
goto ExitReenumerate;
|
|
}
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Something was read from the serial port...\n"));
|
|
|
|
|
|
|
|
#if 0
|
|
if (PFdoData->DebugLevel & SER_DBG_PNP_DUMP_PACKET) {
|
|
SerenumHexDump(pReadBuf, nActual);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Determine from the result whether the current pdo (if we have one),
|
|
// should be deleted. If it's the same device, then keep it. If it's a
|
|
// different device or if the device is a legacy device, then create a
|
|
// new pdo.
|
|
//
|
|
if (DSRMissing) {
|
|
legacyDeviceFound
|
|
= SerenumCheckForLegacyDevice(PFdoData, pReadBuf, nActual,
|
|
&hardwareIDs, &compIDs, &deviceIDs);
|
|
}
|
|
|
|
if (!legacyDeviceFound) {
|
|
//
|
|
// No legacy device was found, so parse the data we got back
|
|
// from the device.
|
|
//
|
|
|
|
status = Serenum_ParseData(PFdoData, pReadBuf, nActual, &hardwareIDs,
|
|
&compIDs, &deviceIDs, &devDesc, &serNo, &pnpRev);
|
|
|
|
//
|
|
// Last chance:
|
|
//
|
|
// 1) DSR is present
|
|
// 2) Not a PnP device
|
|
//
|
|
// There are some devices that are legacy but also assert DSR (e.g., the
|
|
// gyropoint mouse). Give it one last shot.
|
|
//
|
|
|
|
if (!DSRMissing && !NT_SUCCESS(status)) {
|
|
|
|
|
|
//
|
|
// CIMEXCIMEX Serenum_ParseData() isn't very tidy, so we
|
|
// must clean up after them
|
|
//
|
|
|
|
SerenumFreeUnicodeString(&hardwareIDs);
|
|
SerenumFreeUnicodeString(&compIDs);
|
|
SerenumFreeUnicodeString(&deviceIDs);
|
|
SerenumFreeUnicodeString(&devDesc);
|
|
SerenumFreeUnicodeString(&serNo);
|
|
SerenumFreeUnicodeString(&pnpRev);
|
|
|
|
if (SerenumCheckForLegacyDevice(PFdoData, pReadBuf, nActual,
|
|
&hardwareIDs, &compIDs, &deviceIDs)) {
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the data can't be parsed and this isn't a legacy device, then
|
|
// it is something we don't understand. We bail out at this point
|
|
//
|
|
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_ERROR,
|
|
("Failed to parse the data for the new device\n"));
|
|
|
|
//
|
|
// If there is a current PDO, remove it since we can't ID the
|
|
// attached device.
|
|
//
|
|
|
|
if (pdo) {
|
|
Serenum_PDO_EnumMarkMissing(PFdoData, pdo->DeviceExtension);
|
|
pdo = NULL;
|
|
}
|
|
|
|
SerenumFreeUnicodeString(&hardwareIDs);
|
|
SerenumFreeUnicodeString(&compIDs);
|
|
SerenumFreeUnicodeString(&deviceIDs);
|
|
SerenumFreeUnicodeString(&devDesc);
|
|
SerenumFreeUnicodeString(&serNo);
|
|
SerenumFreeUnicodeString(&pnpRev);
|
|
|
|
|
|
ExFreePool(pReadBuf);
|
|
pReadBuf = NULL;
|
|
|
|
goto ExitReenumerate;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We're now finally able to free this read buffer.
|
|
//
|
|
|
|
if (pReadBuf != NULL) {
|
|
ExFreePool(pReadBuf);
|
|
}
|
|
|
|
//
|
|
// Validate all the ID's -- if any are illegal,
|
|
// then we fail the enumeration
|
|
//
|
|
|
|
if (!SerenumValidateID(&hardwareIDs) || !SerenumValidateID(&compIDs)
|
|
|| !SerenumValidateID(&deviceIDs)) {
|
|
|
|
//
|
|
// If a PDO already exists, mark it missing and get rid
|
|
// of it since we don't know what is out there any longer
|
|
//
|
|
|
|
if (pdo) {
|
|
Serenum_PDO_EnumMarkMissing(PFdoData, pdo->DeviceExtension);
|
|
pdo = NULL;
|
|
}
|
|
|
|
SerenumFreeUnicodeString(&hardwareIDs);
|
|
SerenumFreeUnicodeString(&compIDs);
|
|
SerenumFreeUnicodeString(&deviceIDs);
|
|
SerenumFreeUnicodeString(&devDesc);
|
|
SerenumFreeUnicodeString(&serNo);
|
|
SerenumFreeUnicodeString(&pnpRev);
|
|
|
|
|
|
goto ExitReenumerate;
|
|
}
|
|
|
|
//
|
|
// Check if the current device is the same as the one that we're
|
|
// enumerating. If so, we'll just keep the current pdo.
|
|
//
|
|
if (pdo) {
|
|
pdoData = pdo->DeviceExtension;
|
|
|
|
//
|
|
// ADRIAO CIMEXCIMEX 04/28/1999 -
|
|
// We should be comparing device ID's here, but the above mentioned
|
|
// bug must be fixed first. Note that even this code is broken as it
|
|
// doesn't take into account that hardware/compID's are multiSz.
|
|
//
|
|
|
|
if (!(RtlEqualUnicodeString(&pdoData->HardwareIDs, &hardwareIDs, FALSE)
|
|
&& RtlEqualUnicodeString(&pdoData->CompIDs, &compIDs, FALSE))) {
|
|
//
|
|
// The ids are not the same, so get rid of this pdo and create a
|
|
// new one so that the PNP system will query the ids and find a
|
|
// new driver
|
|
//
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE, ("Different device."
|
|
" Removing PDO %x\n",
|
|
pdo));
|
|
Serenum_PDO_EnumMarkMissing(PFdoData, pdoData);
|
|
pdo = NULL;
|
|
} else {
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Same device. Keeping current Pdo %x\n", pdo));
|
|
sameDevice = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there isn't a pdo, then create one!
|
|
//
|
|
if (!pdo) {
|
|
//
|
|
// Allocate a pdo
|
|
//
|
|
status = IoCreateDevice(PFdoData->Self->DriverObject,
|
|
sizeof(PDO_DEVICE_DATA), &pdoUniName,
|
|
FILE_DEVICE_UNKNOWN,
|
|
FILE_AUTOGENERATED_DEVICE_NAME, FALSE, &pdo);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_ERROR,
|
|
("Create device failed\n"));
|
|
KeReleaseSemaphore(&PFdoData->CreateSemaphore, IO_NO_INCREMENT, 1,
|
|
FALSE);
|
|
return status;
|
|
}
|
|
|
|
Serenum_KdPrint(PFdoData, SER_DBG_SS_TRACE,
|
|
("Created PDO on top of filter: %x\n",pdo));
|
|
|
|
|
|
//
|
|
// Initialize the rest of the device object
|
|
//
|
|
|
|
|
|
pdoData = pdo->DeviceExtension;
|
|
|
|
//
|
|
// Copy our temp buffers over to the DevExt
|
|
//
|
|
|
|
pdoData->HardwareIDs = hardwareIDs;
|
|
pdoData->CompIDs = compIDs;
|
|
pdoData->DeviceIDs = deviceIDs;
|
|
pdoData->DevDesc = devDesc;
|
|
pdoData->SerialNo = serNo;
|
|
pdoData->PnPRev = pnpRev;
|
|
|
|
Serenum_InitPDO(pdo, PFdoData);
|
|
|
|
}
|
|
|
|
ExitReenumerate:;
|
|
|
|
KeReleaseSemaphore(&PFdoData->CreateSemaphore, IO_NO_INCREMENT, 1, FALSE);
|
|
|
|
*PSameDevice = sameDevice;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
void
|
|
Serenum_PDO_EnumMarkMissing(PFDO_DEVICE_DATA FdoData, PPDO_DEVICE_DATA PdoData)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Removes the attached pdo from the fdo's list of children.
|
|
|
|
NOTE: THIS FUNCTION CAN ONLY BE CALLED DURING AN ENUMERATION. If called
|
|
outside of enumeration, Serenum might delete it's PDO before PnP has
|
|
been told the PDO is gone.
|
|
|
|
Arguments:
|
|
FdoData - Pointer to the fdo's device extension
|
|
PdoData - Pointer to the pdo's device extension
|
|
|
|
Return value:
|
|
none
|
|
|
|
--*/
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
Serenum_KdPrint (FdoData, SER_DBG_SS_TRACE, ("Removing Pdo %x\n",
|
|
PdoData->Self));
|
|
|
|
ASSERT(PdoData->Attached);
|
|
|
|
KeAcquireSpinLock(&FdoData->EnumerationLock, &oldIrql);
|
|
|
|
PdoData->Attached = FALSE;
|
|
FdoData->NewPDO = NULL;
|
|
FdoData->NewPdoData = NULL;
|
|
FdoData->NewNumPDOs = 0;
|
|
FdoData->NewPDOForcedRemove = FALSE;
|
|
|
|
FdoData->EnumFlags |= SERENUM_ENUMFLAG_DIRTY;
|
|
|
|
KeReleaseSpinLock(&FdoData->EnumerationLock, oldIrql);
|
|
}
|
|
|
|
NTSTATUS
|
|
Serenum_IoSyncReqWithIrp(PIRP PIrp, UCHAR MajorFunction, PKEVENT PEvent,
|
|
PDEVICE_OBJECT PDevObj )
|
|
/*++
|
|
|
|
Routine Description:
|
|
Performs a synchronous IO request by waiting on the event object
|
|
passed to it. The IRP isn't deallocated after this call.
|
|
|
|
Arguments:
|
|
PIrp - The IRP to be used for this request
|
|
|
|
MajorFunction - The major function
|
|
|
|
PEvent - An event used to wait for the IRP
|
|
|
|
PDevObj - The object that we're performing the IO request upon
|
|
|
|
Return value:
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
PIO_STACK_LOCATION stack;
|
|
NTSTATUS status;
|
|
|
|
stack = IoGetNextIrpStackLocation(PIrp);
|
|
|
|
stack->MajorFunction = MajorFunction;
|
|
|
|
KeClearEvent(PEvent);
|
|
|
|
IoSetCompletionRoutine(PIrp, Serenum_EnumComplete, PEvent, TRUE,
|
|
TRUE, TRUE);
|
|
|
|
status = Serenum_IoSyncReq(PDevObj, PIrp, PEvent);
|
|
|
|
if (status == STATUS_SUCCESS) {
|
|
status = PIrp->IoStatus.Status;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
Serenum_IoSyncIoctlEx(ULONG Ioctl, BOOLEAN Internal, PDEVICE_OBJECT PDevObj,
|
|
PKEVENT PEvent, PVOID PInBuffer, ULONG InBufferLen,
|
|
PVOID POutBuffer, ULONG OutBufferLen)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Performs a synchronous IO control request by waiting on the event object
|
|
passed to it. The IRP is deallocated by the IO system when finished.
|
|
|
|
Return value:
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
PIRP pIrp;
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
|
|
KeClearEvent(PEvent);
|
|
|
|
// Allocate an IRP - No need to release
|
|
// When the next-lower driver completes this IRP, the IO Mgr releases it.
|
|
|
|
pIrp = IoBuildDeviceIoControlRequest(Ioctl, PDevObj, PInBuffer, InBufferLen,
|
|
POutBuffer, OutBufferLen, Internal,
|
|
PEvent, &IoStatusBlock);
|
|
|
|
if (pIrp == NULL) {
|
|
Serenum_KdPrint_Def (SER_DBG_SS_ERROR, ("Failed to allocate IRP\n"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
status = Serenum_IoSyncReq(PDevObj, pIrp, PEvent);
|
|
|
|
|
|
if (status == STATUS_SUCCESS) {
|
|
status = IoStatusBlock.Status;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
Serenum_IoSyncReq(PDEVICE_OBJECT PDevObj, IN PIRP PIrp, PKEVENT PEvent)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Performs a synchronous IO request by waiting on the event object
|
|
passed to it. The IRP is deallocated by the IO system when finished.
|
|
|
|
Return value:
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
|
|
status = IoCallDriver(PDevObj, PIrp);
|
|
|
|
if (status == STATUS_PENDING) {
|
|
// wait for it...
|
|
status = KeWaitForSingleObject(PEvent, Executive, KernelMode, FALSE,
|
|
NULL);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
Serenum_Wait(IN PKTIMER Timer, IN LARGE_INTEGER DueTime)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Performs a wait for the specified time.
|
|
NB: Negative time is relative to the current time. Positive time
|
|
represents an absolute time to wait until.
|
|
|
|
Return value:
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
if (KeSetTimer(Timer, DueTime, NULL)) {
|
|
Serenum_KdPrint_Def(SER_DBG_SS_INFO, ("Timer already set: %x\n", Timer));
|
|
}
|
|
|
|
return KeWaitForSingleObject(Timer, Executive, KernelMode, FALSE, NULL);
|
|
}
|
|
|
|
NTSTATUS
|
|
Serenum_EnumComplete (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
A completion routine for use when calling the lower device objects to
|
|
which our bus (FDO) is attached. It sets the event for the synchronous
|
|
calls done.
|
|
|
|
--*/
|
|
{
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
if (Irp->PendingReturned) {
|
|
IoMarkIrpPending(Irp);
|
|
}
|
|
|
|
KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, FALSE);
|
|
// No special priority
|
|
// No Wait
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED; // Keep this IRP
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
Serenum_ReadSerialPort(OUT PCHAR PReadBuffer, IN USHORT Buflen,
|
|
IN ULONG Timeout, OUT PUSHORT nActual,
|
|
OUT PIO_STATUS_BLOCK PIoStatusBlock,
|
|
IN const PFDO_DEVICE_DATA FdoData)
|
|
{
|
|
NTSTATUS status;
|
|
PIRP pIrp;
|
|
LARGE_INTEGER startingOffset;
|
|
KEVENT event;
|
|
SERIAL_TIMEOUTS timeouts;
|
|
ULONG i;
|
|
|
|
startingOffset.QuadPart = (LONGLONG) 0;
|
|
//
|
|
// Set the proper timeouts for the read
|
|
//
|
|
|
|
timeouts.ReadIntervalTimeout = MAXULONG;
|
|
timeouts.ReadTotalTimeoutMultiplier = MAXULONG;
|
|
timeouts.ReadTotalTimeoutConstant = Timeout;
|
|
timeouts.WriteTotalTimeoutMultiplier = 0;
|
|
timeouts.WriteTotalTimeoutConstant = 0;
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
|
|
status = Serenum_IoSyncIoctlEx(IOCTL_SERIAL_SET_TIMEOUTS, FALSE,
|
|
FdoData->TopOfStack, &event, &timeouts,
|
|
sizeof(timeouts), NULL, 0);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
Serenum_KdPrint(FdoData, SER_DBG_SS_TRACE, ("Read pending...\n"));
|
|
|
|
*nActual = 0;
|
|
|
|
while (*nActual < Buflen) {
|
|
KeClearEvent(&event);
|
|
|
|
pIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ, FdoData->TopOfStack,
|
|
PReadBuffer, 1, &startingOffset,
|
|
&event, PIoStatusBlock);
|
|
|
|
if (pIrp == NULL) {
|
|
Serenum_KdPrint(FdoData, SER_DBG_SS_ERROR, ("Failed to allocate IRP"
|
|
"\n"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
status = IoCallDriver(FdoData->TopOfStack, pIrp);
|
|
|
|
if (status == STATUS_PENDING) {
|
|
|
|
//
|
|
// Wait for the IRP
|
|
//
|
|
|
|
status = KeWaitForSingleObject(&event, Executive, KernelMode,
|
|
FALSE, NULL);
|
|
|
|
if (status == STATUS_SUCCESS) {
|
|
status = PIoStatusBlock->Status;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(status) || status == STATUS_TIMEOUT) {
|
|
Serenum_KdPrint (FdoData, SER_DBG_SS_ERROR,
|
|
("IO Call failed with status %x\n", status));
|
|
return status;
|
|
}
|
|
|
|
*nActual += (USHORT)PIoStatusBlock->Information;
|
|
PReadBuffer += (USHORT)PIoStatusBlock->Information;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
Serenum_GetRegistryKeyValue(IN HANDLE Handle, IN PWCHAR KeyNameString,
|
|
IN ULONG KeyNameStringLength, IN PVOID Data,
|
|
IN ULONG DataLength, OUT PULONG ActualLength)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reads a registry key value from an already opened registry key.
|
|
|
|
Arguments:
|
|
|
|
Handle Handle to the opened registry key
|
|
|
|
KeyNameString ANSI string to the desired key
|
|
|
|
KeyNameStringLength Length of the KeyNameString
|
|
|
|
Data Buffer to place the key value in
|
|
|
|
DataLength Length of the data buffer
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if all works, otherwise status of system call that
|
|
went wrong.
|
|
|
|
--*/
|
|
{
|
|
UNICODE_STRING keyName;
|
|
ULONG length;
|
|
PKEY_VALUE_FULL_INFORMATION fullInfo;
|
|
|
|
NTSTATUS ntStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
RtlInitUnicodeString (&keyName, KeyNameString);
|
|
|
|
length = sizeof(KEY_VALUE_FULL_INFORMATION) + KeyNameStringLength
|
|
+ DataLength;
|
|
fullInfo = ExAllocatePool(PagedPool, length);
|
|
|
|
if (ActualLength != NULL) {
|
|
*ActualLength = 0;
|
|
}
|
|
|
|
if (fullInfo) {
|
|
ntStatus = ZwQueryValueKey(Handle, &keyName, KeyValueFullInformation,
|
|
fullInfo, length, &length);
|
|
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
//
|
|
// If there is enough room in the data buffer, copy the output
|
|
//
|
|
|
|
if (DataLength >= fullInfo->DataLength) {
|
|
RtlCopyMemory(Data, ((PUCHAR)fullInfo) + fullInfo->DataOffset,
|
|
fullInfo->DataLength);
|
|
if (ActualLength != NULL) {
|
|
*ActualLength = fullInfo->DataLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
ExFreePool(fullInfo);
|
|
}
|
|
|
|
if (!NT_SUCCESS(ntStatus) && !NT_ERROR(ntStatus)) {
|
|
if (ntStatus == STATUS_BUFFER_OVERFLOW) {
|
|
ntStatus = STATUS_BUFFER_TOO_SMALL;
|
|
} else {
|
|
ntStatus = STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
return ntStatus;
|
|
}
|
|
|
|
|
|
VOID
|
|
SerenumWaitForEnumThreadTerminate(IN PFDO_DEVICE_DATA PFdoData)
|
|
{
|
|
KIRQL oldIrql;
|
|
PVOID pThreadObj;
|
|
|
|
//
|
|
// Take a reference under the lock so the thread can't disappear on us.
|
|
//
|
|
|
|
KeAcquireSpinLock(&PFdoData->EnumerationLock, &oldIrql);
|
|
|
|
//
|
|
// If the work item beat us, then the thread is done and we can
|
|
// delete/stop/unload. Otherwise, we have to wait. We can use
|
|
// the reference we stole to hold the object around.
|
|
//
|
|
|
|
if (PFdoData->ThreadObj != NULL) {
|
|
pThreadObj = PFdoData->ThreadObj;
|
|
PFdoData->ThreadObj = NULL;
|
|
PFdoData->EnumFlags &= ~SERENUM_ENUMFLAG_PENDING;
|
|
} else {
|
|
pThreadObj = NULL;
|
|
}
|
|
|
|
KeReleaseSpinLock(&PFdoData->EnumerationLock, oldIrql);
|
|
|
|
if (pThreadObj != NULL) {
|
|
KeWaitForSingleObject(pThreadObj, Executive, KernelMode, FALSE, NULL);
|
|
ObDereferenceObject(pThreadObj);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
SerenumEnumThreadWorkItem(IN PDEVICE_OBJECT PDevObj, IN PVOID PFdoData)
|
|
{
|
|
PFDO_DEVICE_DATA pFdoData = (PFDO_DEVICE_DATA)PFdoData;
|
|
KIRQL oldIrql;
|
|
PVOID pThreadObj;
|
|
PIO_WORKITEM pWorkItem;
|
|
|
|
UNREFERENCED_PARAMETER(PDevObj);
|
|
|
|
//
|
|
// See if the delete/stop code beat us to the thread obj.
|
|
// If not, we can derefence the thread.
|
|
//
|
|
|
|
KeAcquireSpinLock(&pFdoData->EnumerationLock, &oldIrql);
|
|
|
|
if (pFdoData->ThreadObj != NULL) {
|
|
pThreadObj = pFdoData->ThreadObj;
|
|
pFdoData->ThreadObj = NULL;
|
|
pFdoData->EnumFlags &= ~SERENUM_ENUMFLAG_PENDING;
|
|
} else {
|
|
pThreadObj = NULL;
|
|
}
|
|
|
|
pWorkItem = pFdoData->EnumWorkItem;
|
|
pFdoData->EnumWorkItem = NULL;
|
|
|
|
KeReleaseSpinLock(&pFdoData->EnumerationLock, oldIrql);
|
|
|
|
if (pThreadObj != NULL) {
|
|
ObDereferenceObject(pThreadObj);
|
|
}
|
|
|
|
IoFreeWorkItem(pWorkItem);
|
|
}
|
|
|
|
|
|
VOID
|
|
SerenumEnumThread(IN PVOID PFdoData)
|
|
{
|
|
PFDO_DEVICE_DATA pFdoData = (PFDO_DEVICE_DATA)PFdoData;
|
|
PIRP pIrp = NULL;
|
|
PIO_STACK_LOCATION pIrpSp;
|
|
NTSTATUS status;
|
|
KIRQL oldIrql;
|
|
PKTHREAD pThread;
|
|
BOOLEAN sameDevice = TRUE;
|
|
|
|
pThread = KeGetCurrentThread();
|
|
|
|
KeSetPriorityThread(pThread, HIGH_PRIORITY);
|
|
|
|
pIrp = IoAllocateIrp(pFdoData->TopOfStack->StackSize + 1, FALSE);
|
|
|
|
if (pIrp == NULL) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto SerenumEnumThreadErrOut;
|
|
}
|
|
IoSetNextIrpStackLocation(pIrp);
|
|
status = Serenum_ReenumerateDevices(pIrp, pFdoData, &sameDevice);
|
|
|
|
SerenumEnumThreadErrOut:
|
|
|
|
if (pIrp != NULL) {
|
|
IoFreeIrp(pIrp);
|
|
}
|
|
|
|
KeAcquireSpinLock(&pFdoData->EnumerationLock, &oldIrql);
|
|
|
|
if ((status == STATUS_SUCCESS) && !sameDevice) {
|
|
pFdoData->EnumFlags |= SERENUM_ENUMFLAG_DIRTY;
|
|
}
|
|
|
|
KeReleaseSpinLock(&pFdoData->EnumerationLock, oldIrql);
|
|
|
|
if ((status == STATUS_SUCCESS) && !sameDevice) {
|
|
IoInvalidateDeviceRelations(pFdoData->UnderlyingPDO, BusRelations);
|
|
}
|
|
|
|
//
|
|
// Queue a work item to release the last reference if remove/stop
|
|
// hasn't already.
|
|
//
|
|
|
|
IoQueueWorkItem(pFdoData->EnumWorkItem, SerenumEnumThreadWorkItem,
|
|
DelayedWorkQueue, pFdoData);
|
|
|
|
PsTerminateSystemThread(STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SerenumStartProtocolThread(IN PFDO_DEVICE_DATA PFdoData)
|
|
{
|
|
HANDLE hThread;
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objAttrib;
|
|
HANDLE handle;
|
|
PVOID tmpObj;
|
|
KIRQL oldIrql;
|
|
PIO_WORKITEM pWorkItem;
|
|
|
|
InitializeObjectAttributes(&objAttrib, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
|
|
pWorkItem = IoAllocateWorkItem(PFdoData->Self);
|
|
|
|
if (pWorkItem == NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
PFdoData->EnumWorkItem = pWorkItem;
|
|
|
|
status = PsCreateSystemThread(&handle, THREAD_ALL_ACCESS, NULL, NULL, NULL,
|
|
SerenumEnumThread, PFdoData);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
ASSERT(PFdoData->ThreadObj == NULL);
|
|
|
|
//
|
|
// We do this merely to get an object pointer that the remove
|
|
// code can wait on.
|
|
//
|
|
|
|
status = ObReferenceObjectByHandle(handle, THREAD_ALL_ACCESS, NULL,
|
|
KernelMode, &tmpObj, NULL);
|
|
|
|
|
|
KeAcquireSpinLock(&PFdoData->EnumerationLock, &oldIrql);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
PFdoData->ThreadObj = tmpObj;
|
|
KeReleaseSpinLock(&PFdoData->EnumerationLock, oldIrql);
|
|
} else {
|
|
//
|
|
// The thread may be done by now, so no one would need to
|
|
// synchronize with it.
|
|
//
|
|
|
|
PFdoData->ThreadObj = NULL;
|
|
PFdoData->EnumWorkItem = NULL;
|
|
KeReleaseSpinLock(&PFdoData->EnumerationLock, oldIrql);
|
|
|
|
}
|
|
|
|
//
|
|
// Close the handle so the only references possible are the ones
|
|
// for the thread itself and the one either the work item or
|
|
// remove will take care of
|
|
//
|
|
|
|
ZwClose(handle);
|
|
} else {
|
|
PFdoData->EnumWorkItem = NULL;
|
|
IoFreeWorkItem(pWorkItem);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
|