/*++ Copyright (c) 1999-2000 Microsoft Corporation Module Name : common.c Abstract: Common code for the Windows CE USB Serial Host and Filter drivers Author: Jeff Midkiff (jeffmi) 08-24-99 --*/ #include #include "wceusbsh.h" #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGEWCE0, QueryRegistryParameters) #pragma alloc_text(PAGEWCE0, CreateDevObjAndSymLink) #pragma alloc_text(PAGEWCE0, DeleteDevObjAndSymLink) #pragma alloc_text(PAGEWCE0, IsWin9x) #pragma alloc_text(PAGEWCE1, LogError) #endif NTSTATUS QueryRegistryParameters( IN PUNICODE_STRING RegistryPath ) /*++ This routine queryies the Registry for our Parameters key. We are given the RegistryPath to our driver during DriverEntry, but don't yet have an extension, so we store the values in globals until we get our device extension. The values are setup from our INF. On WinNT this is under HKLM\SYSTEM\ControlSet\Services\wceusbsh\Parameters On Win98 this is under HKLM\System\CurrentControlSet\Services\Class\WCESUSB\000* Returns - nothing; use defaults --*/ { #define NUM_REG_ENTRIES 6 RTL_QUERY_REGISTRY_TABLE rtlQueryRegTbl[ NUM_REG_ENTRIES + 1 ]; ULONG sizeOfUl = sizeof( ULONG ); ULONG ulAlternateSetting = DEFAULT_ALTERNATE_SETTING; LONG lIntTimout = DEFAULT_INT_PIPE_TIMEOUT; ULONG ulMaxPipeErrors = DEFAULT_MAX_PIPE_DEVICE_ERRORS; ULONG ulDebugLevel = DBG_OFF; ULONG ulExposeComPort = FALSE; NTSTATUS status = STATUS_SUCCESS; PAGED_CODE(); ASSERT( RegistryPath != NULL ); RtlZeroMemory( rtlQueryRegTbl, sizeof(rtlQueryRegTbl) ); // // Setup the query table // Note: the 1st table entry is the \Parameters subkey, // and the last table entry is NULL // rtlQueryRegTbl[0].QueryRoutine = NULL; rtlQueryRegTbl[0].Flags = RTL_QUERY_REGISTRY_SUBKEY; rtlQueryRegTbl[0].Name = L"Parameters"; rtlQueryRegTbl[0].EntryContext = NULL; rtlQueryRegTbl[0].DefaultType = (ULONG_PTR)NULL; rtlQueryRegTbl[0].DefaultData = NULL; rtlQueryRegTbl[0].DefaultLength = (ULONG_PTR)NULL; rtlQueryRegTbl[1].QueryRoutine = NULL; rtlQueryRegTbl[1].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[1].Name = L"DebugLevel"; rtlQueryRegTbl[1].EntryContext = &DebugLevel; rtlQueryRegTbl[1].DefaultType = REG_DWORD; rtlQueryRegTbl[1].DefaultData = &ulDebugLevel; rtlQueryRegTbl[1].DefaultLength = sizeOfUl; rtlQueryRegTbl[2].QueryRoutine = NULL; rtlQueryRegTbl[2].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[2].Name = L"AlternateSetting"; rtlQueryRegTbl[2].EntryContext = &g_ulAlternateSetting; rtlQueryRegTbl[2].DefaultType = REG_DWORD; rtlQueryRegTbl[2].DefaultData = &ulAlternateSetting; rtlQueryRegTbl[2].DefaultLength = sizeOfUl; rtlQueryRegTbl[3].QueryRoutine = NULL; rtlQueryRegTbl[3].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[3].Name = L"InterruptTimeout"; rtlQueryRegTbl[3].EntryContext = &g_lIntTimout; rtlQueryRegTbl[3].DefaultType = REG_DWORD; rtlQueryRegTbl[3].DefaultData = &lIntTimout; rtlQueryRegTbl[3].DefaultLength = sizeOfUl; rtlQueryRegTbl[4].QueryRoutine = NULL; rtlQueryRegTbl[4].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[4].Name = L"MaxPipeErrors"; rtlQueryRegTbl[4].EntryContext = &g_ulMaxPipeErrors; rtlQueryRegTbl[4].DefaultType = REG_DWORD; rtlQueryRegTbl[4].DefaultData = &ulMaxPipeErrors; rtlQueryRegTbl[4].DefaultLength = sizeOfUl; rtlQueryRegTbl[5].QueryRoutine = NULL; rtlQueryRegTbl[5].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[5].Name = L"ExposeComPort"; rtlQueryRegTbl[5].EntryContext = &g_ExposeComPort; rtlQueryRegTbl[5].DefaultType = REG_DWORD; rtlQueryRegTbl[5].DefaultData = &ulExposeComPort; rtlQueryRegTbl[5].DefaultLength = sizeOfUl; // // query the Registry // status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, // RelativeTo RegistryPath->Buffer, // Path rtlQueryRegTbl, // QueryTable NULL, // Context NULL ); // Environment if ( !NT_SUCCESS( status ) ) { // // if registry query failed then use defaults // DbgDump( DBG_INIT, ("RtlQueryRegistryValues error: 0x%x\n", status) ); g_ulAlternateSetting = ulAlternateSetting; g_lIntTimout = lIntTimout; g_ulMaxPipeErrors = ulMaxPipeErrors; DebugLevel = DBG_OFF; } DbgDump( DBG_INIT, ("DebugLevel = 0x%x\n", DebugLevel)); DbgDump( DBG_INIT, ("AlternateSetting = %d\n", g_ulAlternateSetting)); DbgDump( DBG_INIT, ("MaxPipeErrors = %d\n", g_ulMaxPipeErrors)); DbgDump( DBG_INIT, ("INT Timeout = %d\n", g_lIntTimout)); return status; } VOID ReleaseSlot( IN LONG Slot ) { LONG lNumDevices = InterlockedDecrement(&g_NumDevices); UNREFERENCED_PARAMETER( Slot ); ASSERT( lNumDevices >= 0); return; } NTSTATUS AcquireSlot( OUT PULONG PSlot ) { NTSTATUS status = STATUS_SUCCESS; *PSlot = InterlockedIncrement(&g_NumDevices); if (*PSlot == (ULONG)0) { status = STATUS_INVALID_DEVICE_REQUEST; } return status; } NTSTATUS CreateDevObjAndSymLink( IN PDRIVER_OBJECT PDrvObj, IN PDEVICE_OBJECT PPDO, IN PDEVICE_OBJECT *PpDevObj, IN PCHAR PDevName ) /*++ Routine Description: Creates a named device object and symbolic link for the next available device instance. Saves both the \\Device\\PDevName%n and \\DosDevices\\PDevName%n in the device extension. Also registers our device interface with PnP system. Arguments: PDrvObj - Pointer to our driver object PPDO - Pointer to the PDO for the stack to which we should add ourselves PDevName - device name to use Return Value: NTSTATUS --*/ { PDEVICE_EXTENSION pDevExt = NULL; NTSTATUS status; ULONG deviceInstance; ULONG bufferLen; BOOLEAN gotSlot = FALSE; ANSI_STRING asDevName; ANSI_STRING asDosDevName; UNICODE_STRING usDeviceName = {0}; // seen only in kernel-mode namespace UNICODE_STRING usDosDevName = {0}; // seen in user-mode namespace CHAR dosDeviceNameBuffer[DOS_NAME_MAX]; CHAR deviceNameBuffer[DOS_NAME_MAX]; DbgDump(DBG_INIT, (">CreateDevObjAndSymLink\n")); PAGED_CODE(); ASSERT( PPDO ); // // init the callers device obj // *PpDevObj = NULL; // // Get the next device instance number // status = AcquireSlot(&deviceInstance); if (status != STATUS_SUCCESS) { DbgDump(DBG_ERR, ("AcquireSlot error: 0x%x\n", status)); goto CreateDeviceObjectError; } else { gotSlot = TRUE; } // // concat device name & instance number // ASSERT( *PDevName != (CHAR)NULL); sprintf(dosDeviceNameBuffer, "%s%s%03d", "\\DosDevices\\", PDevName, deviceInstance); sprintf(deviceNameBuffer, "%s%s%03d", "\\Device\\", PDevName, deviceInstance); // convert names to ANSI string RtlInitAnsiString(&asDevName, deviceNameBuffer); RtlInitAnsiString(&asDosDevName, dosDeviceNameBuffer); usDeviceName.Length = 0; usDeviceName.Buffer = NULL; usDosDevName.Length = 0; usDosDevName.Buffer = NULL; // // convert names to UNICODE // status = RtlAnsiStringToUnicodeString(&usDeviceName, &asDevName, TRUE); if (status != STATUS_SUCCESS) { DbgDump(DBG_ERR, ("RtlAnsiStringToUnicodeString error: 0x%x\n", status)); goto CreateDeviceObjectError; } status = RtlAnsiStringToUnicodeString(&usDosDevName, &asDosDevName, TRUE); if (status != STATUS_SUCCESS) { DbgDump(DBG_ERR, ("RtlAnsiStringToUnicodeString error: 0x%x\n", status)); goto CreateDeviceObjectError; } // // create the named devive object // Note: we may want to change this to a non-exclusive later // so xena to come in without the filter. // status = IoCreateDevice( PDrvObj, sizeof(DEVICE_EXTENSION), &usDeviceName, FILE_DEVICE_SERIAL_PORT, 0, TRUE, // Note: SerialPorts are exclusive PpDevObj); if (status != STATUS_SUCCESS) { DbgDump(DBG_ERR, ("IoCreateDevice error: 0x%x\n", status)); TEST_TRAP(); goto CreateDeviceObjectError; } // // get pointer to device extension // pDevExt = (PDEVICE_EXTENSION) (*PpDevObj)->DeviceExtension; RtlZeroMemory(pDevExt, sizeof(DEVICE_EXTENSION)); // (redundant) // // init SERIAL_PORT_INTERFACE // pDevExt->SerialPort.Type = WCE_SERIAL_PORT_TYPE; // // create symbolic link // status = IoCreateUnprotectedSymbolicLink(&usDosDevName, &usDeviceName); if (status != STATUS_SUCCESS) { DbgDump(DBG_ERR, ("IoCreateUnprotectedSymbolicLink error: 0x%x\n", status)); goto CreateDeviceObjectError; } DbgDump(DBG_INIT, ("SymbolicLink: %ws\n", usDosDevName.Buffer)); // // Make the device visible via a device association as well. // The reference string is the eight digit device index // status = IoRegisterDeviceInterface( PPDO, (LPGUID)&GUID_WCE_SERIAL_USB, NULL, &pDevExt->DeviceClassSymbolicName ); if (status != STATUS_SUCCESS) { DbgDump(DBG_ERR, ("IoRegisterDeviceInterface error: 0x%x\n", status)); pDevExt->DeviceClassSymbolicName.Buffer = NULL; goto CreateDeviceObjectError; } DbgDump(DBG_INIT, ("DeviceClassSymbolicName: %ws\n", pDevExt->DeviceClassSymbolicName.Buffer)); // // save the Dos Device link name in our extension // strcpy(pDevExt->DosDeviceName, dosDeviceNameBuffer); pDevExt->SymbolicLink = TRUE; // // save (kernel) device name in extension // bufferLen = RtlAnsiStringToUnicodeSize(&asDevName); pDevExt->DeviceName.Length = 0; pDevExt->DeviceName.MaximumLength = (USHORT)bufferLen; pDevExt->DeviceName.Buffer = ExAllocatePool(PagedPool, bufferLen); if (pDevExt->DeviceName.Buffer == NULL) { // // Skip out. We have worse problems than missing // the name if we have no memory at this point. // status = STATUS_INSUFFICIENT_RESOURCES; DbgDump(DBG_ERR, ("CreateDevObjAndSymLink ERROR: 0x%x\n", status)); goto CreateDeviceObjectError; } RtlAnsiStringToUnicodeString(&pDevExt->DeviceName, &asDevName, FALSE); // save 1's based device instance number pDevExt->SerialPort.Com.Instance = deviceInstance; CreateDeviceObjectError:; // // free Unicode strings // RtlFreeUnicodeString(&usDeviceName); RtlFreeUnicodeString(&usDosDevName); // // Delete the devobj if there was an error // if (status != STATUS_SUCCESS) { if ( *PpDevObj ) { DeleteDevObjAndSymLink( *PpDevObj ); *PpDevObj = NULL; } if (gotSlot) { ReleaseSlot(deviceInstance); } } DbgDump(DBG_INIT, ("DeleteDevObjAndSymLink\n")); PAGED_CODE(); ASSERT( PDevObj ); pDevExt = (PDEVICE_EXTENSION) PDevObj->DeviceExtension; ASSERT( pDevExt ); // get rid of the symbolic link if ( pDevExt->SymbolicLink ) { RtlInitAnsiString( &asDevLink, pDevExt->DosDeviceName ); NtStatus = RtlAnsiStringToUnicodeString( &usDevLink, &asDevLink, TRUE); ASSERT(STATUS_SUCCESS == NtStatus); NtStatus = IoDeleteSymbolicLink(&usDevLink); } if (pDevExt->DeviceClassSymbolicName.Buffer) { NtStatus = IoSetDeviceInterfaceState(&pDevExt->DeviceClassSymbolicName, FALSE); if (NT_SUCCESS(NtStatus)) { DbgDump(DBG_WRN, ("IoSetDeviceInterfaceState.3: OFF\n")); } ExFreePool( pDevExt->DeviceClassSymbolicName.Buffer ); pDevExt->DeviceClassSymbolicName.Buffer = NULL; } if (pDevExt->DeviceName.Buffer != NULL) { ExFreePool(pDevExt->DeviceName.Buffer); RtlInitUnicodeString(&pDevExt->DeviceName, NULL); } // // Wait to do this untill here as this triggers the unload routine // at which point everything better have been deallocated // IoDeleteDevice( PDevObj ); DbgDump(DBG_INIT, ("DeviceExtension; NTSTATUS status = STATUS_INVALID_PARAMETER; PWCE_WORK_ITEM pWorkItem; KIRQL irql; DbgDump(DBG_WORK_ITEMS, (">QueueWorkItem\n" )); // // N.B: you need to ensure your driver does not queue anything when it is stopped. // KeAcquireSpinLock(&pDevExt->ControlLock, &irql); if ( !CanAcceptIoRequests(PDevObj, FALSE, TRUE) ) { status = STATUS_DELETE_PENDING; DbgDump(DBG_ERR, ("QueueWorkItem: 0x%x\n", status)); } else if ( PDevObj && WorkerRoutine ) { pWorkItem = ExAllocateFromNPagedLookasideList( &pDevExt->WorkItemPool ); if ( pWorkItem ) { status = AcquireRemoveLock(&pDevExt->RemoveLock, pWorkItem); if ( !NT_SUCCESS(status) ) { DbgDump(DBG_ERR, ("QueueWorkItem: 0x%x\n", status)); TEST_TRAP(); ExFreeToNPagedLookasideList( &pDevExt->WorkItemPool, pWorkItem ); KeReleaseSpinLock(&pDevExt->ControlLock, irql); return status; } RtlZeroMemory( pWorkItem, sizeof(*pWorkItem) ); // bump the pending count InterlockedIncrement(&pDevExt->PendingWorkItemsCount); DbgDump(DBG_WORK_ITEMS, ("PendingWorkItemsCount: %d\n", pDevExt->PendingWorkItemsCount)); // // put the worker on our pending list // InsertTailList(&pDevExt->PendingWorkItems, &pWorkItem->ListEntry ); // // store parameters // pWorkItem->DeviceObject = PDevObj; pWorkItem->Context = Context; pWorkItem->Flags = Flags; ExInitializeWorkItem( &pWorkItem->Item, (PWORKER_THREAD_ROUTINE)WorkerRoutine, (PVOID)pWorkItem // Context passed to WorkerRoutine ); // finally, queue the worker ExQueueWorkItem( &pWorkItem->Item, CriticalWorkQueue ); status = STATUS_SUCCESS; } else { status = STATUS_INSUFFICIENT_RESOURCES; DbgDump(DBG_ERR, ("AllocateWorkItem failed!\n")); TEST_TRAP() } } KeReleaseSpinLock(&pDevExt->ControlLock, irql); DbgDump(DBG_WORK_ITEMS, ("DeviceExtension; KIRQL irql; DbgDump(DBG_WORK_ITEMS, (">DequeueWorkItem\n" )); // // remove the worker from the pending list // KeAcquireSpinLock( &pDevExt->ControlLock, &irql ); RemoveEntryList( &PWorkItem->ListEntry ); KeReleaseSpinLock( &pDevExt->ControlLock, irql); // // free the worker back to pool // ExFreeToNPagedLookasideList( &pDevExt->WorkItemPool, PWorkItem ); // // signal event if this is the last one // if (0 == InterlockedDecrement( &pDevExt->PendingWorkItemsCount) ) { DbgDump(DBG_WORK_ITEMS, ("PendingWorkItemsEvent signalled\n" )); KeSetEvent( &pDevExt->PendingWorkItemsEvent, IO_NO_INCREMENT, FALSE); } DbgDump(DBG_WORK_ITEMS, ("PendingWorkItemsCount: %d\n", pDevExt->PendingWorkItemsCount)); ASSERT(pDevExt->PendingWorkItemsCount >= 0); ReleaseRemoveLock(&pDevExt->RemoveLock, PWorkItem); DbgDump(DBG_WORK_ITEMS, ("DeviceExtension; LARGE_INTEGER timeOut = {0,0}; LONG itemsLeft; NTSTATUS status = STATUS_SUCCESS; DbgDump(DBG_PNP, (">WaitForPendingItem\n")); if ( !PDevObj || !PPendingEvent || !PPendingCount ) { status = STATUS_INVALID_PARAMETER; DbgDump(DBG_ERR, ("WaitForPendingItem: STATUS_INVALID_PARAMETER\n")); TEST_TRAP(); } else { // // wait for pending item to signal it's complete // while ( itemsLeft = InterlockedExchange( PPendingCount, *PPendingCount) ) { DbgDump(DBG_PNP|DBG_EVENTS, ("Pending Items Remain: %d\n", itemsLeft ) ); timeOut.QuadPart = MILLISEC_TO_100NANOSEC( DEFAULT_PENDING_TIMEOUT ); DbgDump(DBG_PNP|DBG_EVENTS, ("Waiting for %d msec...\n", timeOut.QuadPart/10000)); PAGED_CODE(); KeWaitForSingleObject( PPendingEvent, Executive, KernelMode, FALSE, &timeOut ); } DbgDump(DBG_PNP, ("Pending Items: %d\n", itemsLeft ) ); } DbgDump(DBG_PNP, ("DeviceExtension; BOOLEAN bRc = FALSE; KIRQL irql; if (AcquireLock) { KeAcquireSpinLock(&pDevExt->ControlLock, &irql); } if ( !InterlockedCompareExchange(&pDevExt->DeviceRemoved, FALSE, FALSE) && InterlockedCompareExchange(&pDevExt->AcceptingRequests, TRUE, TRUE) && InterlockedCompareExchange((PULONG)&pDevExt->PnPState, PnPStateStarted, PnPStateStarted) && (CheckOpened ? InterlockedCompareExchange(&pDevExt->DeviceOpened, TRUE, TRUE) : TRUE) ) { bRc = TRUE; } #if defined(DBG) else DbgDump(DBG_WRN|DBG_PNP, ("CanAcceptIoRequests = FALSE\n")); #endif if (AcquireLock) { KeReleaseSpinLock(&pDevExt->ControlLock, irql); } return bRc; } BOOLEAN IsWin9x( VOID ) /*++ Routine Description: Determine whether or not we are running on Win9x (vs. NT). Arguments: Return Value: TRUE iff we're running on Win9x. --*/ { OBJECT_ATTRIBUTES objectAttributes; UNICODE_STRING keyName; HANDLE hKey; NTSTATUS status; BOOLEAN result; PAGED_CODE(); /* * Try to open the COM Name Arbiter, which exists only on NT. */ RtlInitUnicodeString(&keyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\COM Name Arbiter"); InitializeObjectAttributes( &objectAttributes, &keyName, OBJ_CASE_INSENSITIVE, NULL, (PSECURITY_DESCRIPTOR)NULL); status = ZwOpenKey(&hKey, KEY_QUERY_VALUE, &objectAttributes); if (NT_SUCCESS(status)){ status = ZwClose(hKey); ASSERT(NT_SUCCESS(status)); result = FALSE; } else { result = TRUE; } return result; } VOID LogError( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObject OPTIONAL, IN ULONG SequenceNumber, IN UCHAR MajorFunctionCode, IN UCHAR RetryCount, IN ULONG UniqueErrorValue, IN NTSTATUS FinalStatus, IN NTSTATUS SpecificIOStatus, IN ULONG LengthOfInsert1, IN PWCHAR Insert1, IN ULONG LengthOfInsert2, IN PWCHAR Insert2 ) /*++ Routine Description: Stolen from serial.sys This routine allocates an error log entry, copies the supplied data to it, and requests that it be written to the error log file. Arguments: DriverObject - A pointer to the driver object for the device. DeviceObject - A pointer to the device object associated with the device that had the error, early in initialization, one may not yet exist. SequenceNumber - A ulong value that is unique to an IRP over the life of the irp in this driver - 0 generally means an error not associated with an irp. MajorFunctionCode - If there is an error associated with the irp, this is the major function code of that irp. RetryCount - The number of times a particular operation has been retried. UniqueErrorValue - A unique long word that identifies the particular call to this function. FinalStatus - The final status given to the irp that was associated with this error. If this log entry is being made during one of the retries this value will be STATUS_SUCCESS. SpecificIOStatus - The IO status for a particular error. LengthOfInsert1 - The length in bytes (including the terminating NULL) of the first insertion string. Insert1 - The first insertion string. LengthOfInsert2 - The length in bytes (including the terminating NULL) of the second insertion string. NOTE, there must be a first insertion string for their to be a second insertion string. Insert2 - The second insertion string. Return Value: None. --*/ { PIO_ERROR_LOG_PACKET errorLogEntry; PVOID objectToUse = NULL; SHORT dumpToAllocate = 0; PUCHAR ptrToFirstInsert = NULL; PUCHAR ptrToSecondInsert = NULL; PAGED_CODE(); DbgDump(DBG_ERR, (">LogError\n")); if (Insert1 == NULL) { LengthOfInsert1 = 0; } if (Insert2 == NULL) { LengthOfInsert2 = 0; } if (ARGUMENT_PRESENT(DeviceObject)) { objectToUse = DeviceObject; } else if (ARGUMENT_PRESENT(DriverObject)) { objectToUse = DriverObject; } errorLogEntry = IoAllocateErrorLogEntry( objectToUse, (UCHAR)(sizeof(IO_ERROR_LOG_PACKET) + dumpToAllocate + LengthOfInsert1 + LengthOfInsert2) ); if ( errorLogEntry != NULL ) { errorLogEntry->ErrorCode = SpecificIOStatus; errorLogEntry->SequenceNumber = SequenceNumber; errorLogEntry->MajorFunctionCode = MajorFunctionCode; errorLogEntry->RetryCount = RetryCount; errorLogEntry->UniqueErrorValue = UniqueErrorValue; errorLogEntry->FinalStatus = FinalStatus; errorLogEntry->DumpDataSize = dumpToAllocate; ptrToFirstInsert = (PUCHAR)&errorLogEntry->DumpData[0]; ptrToSecondInsert = ptrToFirstInsert + LengthOfInsert1; if (LengthOfInsert1) { errorLogEntry->NumberOfStrings = 1; errorLogEntry->StringOffset = (USHORT)(ptrToFirstInsert - (PUCHAR)errorLogEntry); RtlCopyMemory( ptrToFirstInsert, Insert1, LengthOfInsert1 ); if (LengthOfInsert2) { errorLogEntry->NumberOfStrings = 2; RtlCopyMemory( ptrToSecondInsert, Insert2, LengthOfInsert2 ); } } IoWriteErrorLogEntry(errorLogEntry); } DbgDump(DBG_ERR, ("