/*++ 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; }