Main Page | Class Hierarchy | Class List | File List | Class Members | File Members

errorlog.c

Go to the documentation of this file.
00001 /*++ 00002 00003 Copyright (c) 1989 Microsoft Corporation 00004 00005 Module Name: 00006 00007 errorlog.c 00008 00009 Abstract: 00010 00011 This module contains the code for the I/O error log thread. 00012 00013 Author: 00014 00015 Darryl E. Havens (darrylh) May 3, 1989 00016 00017 Environment: 00018 00019 Kernel mode, system process thread 00020 00021 Revision History: 00022 00023 00024 --*/ 00025 00026 #include "iop.h" 00027 #include "elfkrnl.h" 00028 00029 typedef struct _IOP_ERROR_LOG_CONTEXT { 00030 KDPC ErrorLogDpc; 00031 KTIMER ErrorLogTimer; 00032 }IOP_ERROR_LOG_CONTEXT, *PIOP_ERROR_LOG_CONTEXT; 00033 00034 // 00035 // Declare routines local to this module. 00036 // 00037 00038 BOOLEAN 00039 IopErrorLogConnectPort( 00040 VOID 00041 ); 00042 00043 VOID 00044 IopErrorLogDpc( 00045 IN struct _KDPC *Dpc, 00046 IN PVOID DeferredContext, 00047 IN PVOID SystemArgument1, 00048 IN PVOID SystemArgument2 00049 ); 00050 00051 PLIST_ENTRY 00052 IopErrorLogGetEntry( 00053 ); 00054 00055 VOID 00056 IopErrorLogQueueRequest( 00057 VOID 00058 ); 00059 00060 VOID 00061 IopErrorLogRequeueEntry( 00062 IN PLIST_ENTRY ListEntry 00063 ); 00064 00065 #ifdef ALLOC_PRAGMA 00066 #pragma alloc_text(PAGE, IopErrorLogThread) 00067 #pragma alloc_text(PAGE, IopErrorLogConnectPort) 00068 #pragma alloc_text(PAGE, IopErrorLogQueueRequest) 00069 #endif 00070 00071 // 00072 // Define a global varibles used by the error logging code. 00073 // 00074 00075 WORK_QUEUE_ITEM IopErrorLogWorkItem; 00076 HANDLE ErrorLogPort; 00077 BOOLEAN ErrorLogPortConnected; 00078 BOOLEAN IopErrorLogPortPending; 00079 BOOLEAN IopErrorLogDisabledThisBoot; 00080 00081 // 00082 // Define the amount of space required for the device and driver names. 00083 // 00084 00085 #define IO_ERROR_NAME_LENGTH 100 00086 00087 VOID 00088 IopErrorLogThread( 00089 IN PVOID StartContext 00090 ) 00091 00092 /*++ 00093 00094 Routine Description: 00095 00096 This is the main loop for the I/O error log thread which executes in the 00097 system process context. This routine is started when the system is 00098 initialized. 00099 00100 Arguments: 00101 00102 StartContext - Startup context; not used. 00103 00104 Return Value: 00105 00106 None. 00107 00108 --*/ 00109 00110 { 00111 PERROR_LOG_ENTRY errorLogEntry; 00112 UNICODE_STRING nameString; 00113 PLIST_ENTRY listEntry; 00114 PIO_ERROR_LOG_MESSAGE errorMessage; 00115 NTSTATUS status; 00116 PELF_PORT_MSG portMessage; 00117 PCHAR objectName; 00118 ULONG messageLength; 00119 ULONG driverNameLength; 00120 ULONG deviceNameLength; 00121 ULONG objectNameLength; 00122 ULONG remainingLength; 00123 ULONG stringLength; 00124 CHAR nameBuffer[IO_ERROR_NAME_LENGTH+sizeof( OBJECT_NAME_INFORMATION )]; 00125 PDRIVER_OBJECT driverObject; 00126 POBJECT_NAME_INFORMATION nameInformation; 00127 PIO_ERROR_LOG_PACKET errorData; 00128 PWSTR string; 00129 00130 PAGED_CODE(); 00131 00132 UNREFERENCED_PARAMETER( StartContext ); 00133 00134 // 00135 // Check to see whether a connection has been made to the error log 00136 // port. If the port is not connected return. 00137 // 00138 00139 if (!IopErrorLogConnectPort()) { 00140 00141 // 00142 // The port could not be connected. A timer was started that will 00143 // try again later. 00144 // 00145 00146 return; 00147 } 00148 00149 // 00150 // Allocate and zero the port message structure, include space for the 00151 // name of the device and driver. 00152 // 00153 00154 messageLength = IO_ERROR_LOG_MESSAGE_LENGTH; 00155 portMessage = ExAllocatePool(PagedPool, messageLength); 00156 00157 if (portMessage == NULL) { 00158 00159 // 00160 // The message buffer could not be allocated. Request that 00161 // the error log thread routine be called again later. 00162 // 00163 00164 IopErrorLogQueueRequest(); 00165 return; 00166 } 00167 00168 RtlZeroMemory( portMessage, sizeof( *portMessage ) ); 00169 portMessage->MessageType = IO_ERROR_LOG; 00170 errorMessage = &portMessage->u.IoErrorLogMessage; 00171 00172 nameInformation = (PVOID) &nameBuffer; 00173 00174 // 00175 // Now enter the main loop for this thread. This thread performs the 00176 // following operations: 00177 // 00178 // 1) If a connection has been made to the error log port, dequeue a 00179 // packet from the queue head and attempt to send it to the port. 00180 // 00181 // 2) If the send works, loop sending packets until there are no more 00182 // packets; otherwise, indicate that the connection has been broken, 00183 // cleanup, place the packet back onto the head of the queue and 00184 // return. 00185 // 00186 // 3) After all the packets are sent clear the pending variable and 00187 // return. 00188 // 00189 00190 for (;;) { 00191 00192 // 00193 // Loop dequeueing packets from the queue head and attempt to send 00194 // each to the port. 00195 // 00196 // If the send works, continue looping until there are no more packets. 00197 // Otherwise, indicate that the connection has been broken, cleanup, 00198 // place the packet back onto the head of the queue, and start from the 00199 // top of the loop again. 00200 // 00201 00202 if (!(listEntry = IopErrorLogGetEntry())) { 00203 break; 00204 } 00205 00206 errorLogEntry = CONTAINING_RECORD( listEntry, 00207 ERROR_LOG_ENTRY, 00208 ListEntry ); 00209 00210 // 00211 // The size of errorLogEntry is ERROR_LOG_ENTRY + 00212 // IO_ERROR_LOG_PACKET + (Extra Dump data). The size of the 00213 // initial message length should be IO_ERROR_LOG_MESSAGE + 00214 // (Extra Dump data), since IO_ERROR_LOG_MESSAGE contains an 00215 // IO_ERROR_LOG_PACKET. Using the above calculations set the 00216 // message length. 00217 // 00218 00219 messageLength = sizeof( IO_ERROR_LOG_MESSAGE ) - 00220 sizeof( ERROR_LOG_ENTRY ) - sizeof( IO_ERROR_LOG_PACKET ) + 00221 errorLogEntry->Size; 00222 00223 errorData = (PIO_ERROR_LOG_PACKET) (errorLogEntry + 1); 00224 00225 // 00226 // Copy the error log packet and the extra data to the message. 00227 // 00228 00229 RtlMoveMemory( &errorMessage->EntryData, 00230 errorData, 00231 errorLogEntry->Size - sizeof( ERROR_LOG_ENTRY ) ); 00232 00233 errorMessage->TimeStamp = errorLogEntry->TimeStamp; 00234 errorMessage->Type = IO_TYPE_ERROR_MESSAGE; 00235 00236 // 00237 // Add the driver and device name string. These strings go 00238 // before the error log strings. Just write over the current 00239 // strings and they will be recopied later. 00240 // 00241 00242 if (errorData->NumberOfStrings != 0) { 00243 00244 // 00245 // Start the driver and device strings where the current 00246 // strings start. 00247 // 00248 00249 objectName = (PCHAR) (&errorMessage->EntryData) + 00250 errorData->StringOffset; 00251 00252 } else { 00253 00254 // 00255 // Put the driver and device strings at the end of the 00256 // data. 00257 // 00258 00259 objectName = (PCHAR) errorMessage + messageLength; 00260 00261 } 00262 00263 // 00264 // Make sure the driver offset starts on an even bountry. 00265 // 00266 00267 objectName = (PCHAR) ((ULONG_PTR) (objectName + sizeof(WCHAR) - 1) & 00268 ~(ULONG_PTR)(sizeof(WCHAR) - 1)); 00269 00270 errorMessage->DriverNameOffset = (ULONG)(objectName - (PCHAR) errorMessage); 00271 00272 remainingLength = (ULONG)((PCHAR) portMessage + IO_ERROR_LOG_MESSAGE_LENGTH 00273 - objectName); 00274 00275 // 00276 // Calculate the length of the driver name and 00277 // the device name. If the driver object has a name then get 00278 // it from there; otherwise try to query the device object. 00279 // 00280 00281 driverObject = errorLogEntry->DriverObject; 00282 driverNameLength = 0; 00283 00284 if (driverObject != NULL) { 00285 if (driverObject->DriverName.Buffer != NULL) { 00286 00287 nameString.Buffer = driverObject->DriverName.Buffer; 00288 driverNameLength = driverObject->DriverName.Length; 00289 } 00290 00291 if (driverNameLength == 0) { 00292 00293 // 00294 // Try to query the driver object for a name. 00295 // 00296 00297 status = ObQueryNameString( driverObject, 00298 nameInformation, 00299 IO_ERROR_NAME_LENGTH + sizeof( OBJECT_NAME_INFORMATION ), 00300 &objectNameLength ); 00301 00302 if (!NT_SUCCESS( status ) || !nameInformation->Name.Length) { 00303 00304 // 00305 // No driver name was available. 00306 // 00307 00308 driverNameLength = 0; 00309 00310 } else { 00311 nameString = nameInformation->Name; 00312 } 00313 00314 } 00315 00316 } else { 00317 00318 // 00319 // If no driver object, this message must be from the 00320 // kernel. We need to point the eventlog service to 00321 // an event message file containing ntstatus messages, 00322 // ie, ntdll, we do this by claiming this event is an 00323 // application popup. 00324 // 00325 00326 nameString.Buffer = L"Application Popup"; 00327 driverNameLength = wcslen(nameString.Buffer) * sizeof(WCHAR); 00328 } 00329 00330 if (driverNameLength != 0 ) { 00331 00332 // 00333 // Pick out the module name. 00334 // 00335 00336 string = nameString.Buffer + 00337 (driverNameLength / sizeof(WCHAR)); 00338 00339 driverNameLength = sizeof(WCHAR); 00340 string--; 00341 while (*string != L'\\' && string != nameString.Buffer) { 00342 string--; 00343 driverNameLength += sizeof(WCHAR); 00344 } 00345 00346 if (*string == L'\\') { 00347 string++; 00348 driverNameLength -= sizeof(WCHAR); 00349 } 00350 00351 // 00352 // Ensure there is enough room for the driver name. 00353 // Save space for 3 NULLs one for the driver name, 00354 // one for the device name and one for strings. 00355 // 00356 00357 if (driverNameLength > remainingLength - (3 * sizeof(WCHAR))) { 00358 driverNameLength = remainingLength - (3 * sizeof(WCHAR)); 00359 } 00360 00361 RtlMoveMemory( 00362 objectName, 00363 string, 00364 driverNameLength 00365 ); 00366 00367 } 00368 00369 // 00370 // Add a null after the driver name even if there is no 00371 // driver name. 00372 // 00373 00374 *((PWSTR) (objectName + driverNameLength)) = L'\0'; 00375 driverNameLength += sizeof(WCHAR); 00376 00377 // 00378 // Determine where the next string goes. 00379 // 00380 00381 objectName += driverNameLength; 00382 remainingLength -= driverNameLength; 00383 00384 errorMessage->EntryData.StringOffset = (USHORT)(objectName - (PCHAR) errorMessage); 00385 00386 if (errorLogEntry->DeviceObject != NULL) { 00387 00388 status = ObQueryNameString( errorLogEntry->DeviceObject, 00389 nameInformation, 00390 IO_ERROR_NAME_LENGTH + sizeof( OBJECT_NAME_INFORMATION ) - driverNameLength, 00391 &objectNameLength ); 00392 00393 if (!NT_SUCCESS( status ) || !nameInformation->Name.Length) { 00394 00395 // 00396 // No device name was available. Add a Null string. 00397 // 00398 00399 nameInformation->Name.Length = 0; 00400 nameInformation->Name.Buffer = L"\0"; 00401 00402 } 00403 00404 // 00405 // No device name was available. Add a Null string. 00406 // Always add a device name string so that the 00407 // insertion string counts are correct. 00408 // 00409 00410 } else { 00411 00412 // 00413 // No device name was available. Add a Null string. 00414 // Always add a device name string so that the 00415 // insertion string counts are correct. 00416 // 00417 00418 nameInformation->Name.Length = 0; 00419 nameInformation->Name.Buffer = L"\0"; 00420 00421 } 00422 00423 deviceNameLength = nameInformation->Name.Length; 00424 00425 // 00426 // Ensure there is enough room for the device name. 00427 // Save space for a NULL. 00428 // 00429 00430 if (deviceNameLength > remainingLength - (2 * sizeof(WCHAR))) { 00431 00432 deviceNameLength = remainingLength - (2 * sizeof(WCHAR)); 00433 00434 } 00435 00436 RtlMoveMemory( objectName, 00437 nameInformation->Name.Buffer, 00438 deviceNameLength ); 00439 00440 // 00441 // Add a null after the device name even if there is no 00442 // device name. 00443 // 00444 00445 *((PWSTR) (objectName + deviceNameLength)) = L'\0'; 00446 deviceNameLength += sizeof(WCHAR); 00447 00448 // 00449 // Update the string count for the device object. 00450 // 00451 00452 errorMessage->EntryData.NumberOfStrings++; 00453 objectName += deviceNameLength; 00454 remainingLength -= deviceNameLength; 00455 00456 if (errorData->NumberOfStrings) { 00457 00458 stringLength = errorLogEntry->Size - sizeof( ERROR_LOG_ENTRY ) - 00459 errorData->StringOffset; 00460 00461 // 00462 // Ensure there is enough room for the strings. 00463 // Save space for a NULL. 00464 // 00465 00466 if (stringLength > remainingLength - sizeof(WCHAR)) { 00467 00468 00469 messageLength -= stringLength - remainingLength; 00470 stringLength = remainingLength - sizeof(WCHAR); 00471 00472 } 00473 00474 // 00475 // Copy the strings to the end of the message. 00476 // 00477 00478 RtlMoveMemory( objectName, 00479 (PCHAR) errorData + errorData->StringOffset, 00480 stringLength ); 00481 00482 // 00483 // Add a null after the strings 00484 // 00485 // 00486 00487 *((PWSTR) (objectName + stringLength)) = L'\0'; 00488 00489 } 00490 00491 // 00492 // Update the message length. 00493 // 00494 00495 errorMessage->DriverNameLength = (USHORT) driverNameLength; 00496 messageLength += deviceNameLength + driverNameLength; 00497 errorMessage->Size = (USHORT) messageLength; 00498 00499 messageLength += FIELD_OFFSET ( ELF_PORT_MSG, u ) - 00500 FIELD_OFFSET (ELF_PORT_MSG, MessageType); 00501 00502 portMessage->PortMessage.u1.s1.TotalLength = (USHORT) 00503 (sizeof( PORT_MESSAGE ) + messageLength); 00504 portMessage->PortMessage.u1.s1.DataLength = (USHORT) (messageLength); 00505 status = NtRequestPort( ErrorLogPort, (PPORT_MESSAGE) portMessage ); 00506 00507 if (!NT_SUCCESS( status )) { 00508 00509 // 00510 // The send failed. Place the packet back onto the head of 00511 // the error log queue, forget the current connection since 00512 // it no longer works, and close the handle to the port. 00513 // Set a timer up for another attempt later. 00514 // Finally, exit the loop since there is no connection 00515 // to do any work on. 00516 // 00517 00518 NtClose( ErrorLogPort ); 00519 00520 IopErrorLogRequeueEntry( &errorLogEntry->ListEntry ); 00521 00522 IopErrorLogQueueRequest(); 00523 00524 break; 00525 00526 } else { 00527 00528 // 00529 // The send worked fine. Free the packet and the update 00530 // the allocation count. 00531 // 00532 00533 ExInterlockedAddUlong( &IopErrorLogAllocation, 00534 (ULONG) ( -errorLogEntry->Size ), 00535 &IopErrorLogAllocationLock ); 00536 00537 // 00538 // Dereference the object pointers now that the name has been 00539 // captured. 00540 // 00541 00542 00543 if (errorLogEntry->DeviceObject != NULL) { 00544 ObDereferenceObject( errorLogEntry->DeviceObject ); 00545 } 00546 00547 if (driverObject != NULL) { 00548 ObDereferenceObject( errorLogEntry->DriverObject ); 00549 } 00550 00551 ExFreePool( errorLogEntry ); 00552 00553 } // if 00554 00555 } // for 00556 00557 // 00558 // Finally, free the message buffer and return. 00559 // 00560 00561 ExFreePool(portMessage); 00562 00563 } 00564 00565 BOOLEAN 00566 IopErrorLogConnectPort( 00567 VOID 00568 ) 00569 /*++ 00570 00571 Routine Description: 00572 00573 This routine attempts to connect to the error log port. If the connection 00574 was made successfully and the port allows suficiently large messages, then 00575 the ErrorLogPort to the port handle, ErrorLogPortConnected is set to 00576 TRUE and TRUE is retuned. Otherwise a timer is started to queue a 00577 worker thread at a later time, unless there is a pending connection. 00578 00579 Arguments: 00580 00581 None. 00582 00583 Return Value: 00584 00585 Returns TRUE if the port was connected. 00586 00587 --*/ 00588 00589 { 00590 00591 UNICODE_STRING errorPortName; 00592 NTSTATUS status; 00593 ULONG maxMessageLength; 00594 SECURITY_QUALITY_OF_SERVICE dynamicQos; 00595 00596 PAGED_CODE(); 00597 00598 // 00599 // If the ErrorLogPort is connected then return true. 00600 // 00601 00602 if (ErrorLogPortConnected) { 00603 00604 // 00605 // The port is connect return. 00606 // 00607 00608 return(TRUE); 00609 } 00610 00611 // 00612 // Set up the security quality of service parameters to use over the 00613 // port. Use the most efficient (least overhead) - which is dynamic 00614 // rather than static tracking. 00615 // 00616 00617 dynamicQos.ImpersonationLevel = SecurityImpersonation; 00618 dynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; 00619 dynamicQos.EffectiveOnly = TRUE; 00620 00621 // 00622 // Generate the string structure for describing the error logger's port. 00623 // 00624 00625 RtlInitUnicodeString( &errorPortName, ELF_PORT_NAME_U ); 00626 00627 status = NtConnectPort( &ErrorLogPort, 00628 &errorPortName, 00629 &dynamicQos, 00630 (PPORT_VIEW) NULL, 00631 (PREMOTE_PORT_VIEW) NULL, 00632 &maxMessageLength, 00633 (PVOID) NULL, 00634 (PULONG) NULL ); 00635 00636 if (NT_SUCCESS( status )) { 00637 if (maxMessageLength >= IO_ERROR_LOG_MESSAGE_LENGTH) { 00638 ErrorLogPortConnected = TRUE; 00639 return(TRUE); 00640 } else { 00641 NtClose(ErrorLogPort); 00642 } 00643 } 00644 00645 // 00646 // The port was not successfully opened, or its message size was unsuitable 00647 // for use here. Queue a later request to run the error log thread. 00648 // 00649 00650 IopErrorLogQueueRequest(); 00651 00652 // 00653 // The port could not be connected at this time return false. 00654 // 00655 00656 return(FALSE); 00657 } 00658 00659 VOID 00660 IopErrorLogDpc( 00661 IN struct _KDPC *Dpc, 00662 IN PVOID DeferredContext, 00663 IN PVOID SystemArgument1, 00664 IN PVOID SystemArgument2 00665 ) 00666 00667 /*++ 00668 00669 Routine Description: 00670 00671 This routine queues a work request to the worker thread to process logged 00672 errors. It is called by a timer DPC when the error log port cannot be 00673 connected. The DPC structure itself is freed by this routine. 00674 00675 Arguments: 00676 00677 Dpc - Supplies a pointer to the DPC structure. This structure is freed by 00678 this routine. 00679 00680 DeferredContext - Unused. 00681 00682 SystemArgument1 - Unused. 00683 00684 SystemArgument2 - Unused. 00685 00686 Return Value: 00687 00688 None 00689 00690 --*/ 00691 00692 { 00693 // 00694 // Free the DPC structure if there is one. 00695 // 00696 00697 if (Dpc != NULL) { 00698 ExFreePool(Dpc); 00699 } 00700 00701 ExInitializeWorkItem( &IopErrorLogWorkItem, IopErrorLogThread, NULL ); 00702 00703 ExQueueWorkItem( &IopErrorLogWorkItem, DelayedWorkQueue ); 00704 } 00705 00706 PLIST_ENTRY 00707 IopErrorLogGetEntry( 00708 ) 00709 00710 /*++ 00711 00712 Routine Description: 00713 00714 This routine gets the next entry from the head of the error log queue 00715 and returns it to the caller. 00716 00717 Arguments: 00718 00719 None. 00720 00721 Return Value: 00722 00723 The return value is a pointer to the packet removed, or NULL if there were 00724 no packets on the queue. 00725 00726 --*/ 00727 00728 { 00729 KIRQL irql; 00730 PLIST_ENTRY listEntry; 00731 00732 // 00733 // Remove the next packet from the queue, if there is one. 00734 // 00735 00736 ExAcquireSpinLock( &IopErrorLogLock, &irql ); 00737 if (IsListEmpty( &IopErrorLogListHead )) { 00738 00739 // 00740 // Indicate no more work will be done in the context of this worker 00741 // thread and indicate to the caller that no packets were located. 00742 // 00743 00744 IopErrorLogPortPending = FALSE; 00745 listEntry = (PLIST_ENTRY) NULL; 00746 } else { 00747 00748 // 00749 // Remove the next packet from the head of the list. 00750 // 00751 00752 listEntry = RemoveHeadList( &IopErrorLogListHead ); 00753 } 00754 00755 ExReleaseSpinLock( &IopErrorLogLock, irql ); 00756 return listEntry; 00757 } 00758 00759 VOID 00760 IopErrorLogQueueRequest( 00761 VOID 00762 ) 00763 00764 /*++ 00765 00766 Routine Description: 00767 00768 This routine sets a timer to fire after 30 seconds. The timer queues a 00769 DPC which then queues a worker thread request to run the error log thread 00770 routine. 00771 00772 Arguments: 00773 00774 None. 00775 00776 Return Value: 00777 00778 None. 00779 00780 --*/ 00781 00782 { 00783 LARGE_INTEGER interval; 00784 PIOP_ERROR_LOG_CONTEXT context; 00785 00786 PAGED_CODE(); 00787 00788 // 00789 // Allocate a context block which will contain the timer and the DPC. 00790 // 00791 00792 context = ExAllocatePool( NonPagedPool, sizeof( IOP_ERROR_LOG_CONTEXT ) ); 00793 00794 if (context == NULL) { 00795 00796 // 00797 // The context block could not be allocated. Clear the error log 00798 // pending bit. If there is another error then a new attempt will 00799 // be made. Note the spinlock does not need to be held here since 00800 // new attempt should be made later not right now, so if another 00801 // error log packet is currently being queue, it waits with the 00802 // others. 00803 // 00804 00805 IopErrorLogPortPending = FALSE; 00806 return; 00807 } 00808 00809 KeInitializeDpc( &context->ErrorLogDpc, 00810 IopErrorLogDpc, 00811 NULL ); 00812 00813 KeInitializeTimer( &context->ErrorLogTimer ); 00814 00815 // 00816 // Delay for 30 seconds and try for the port again. 00817 // 00818 00819 interval.QuadPart = - 10 * 1000 * 1000 * 30; 00820 00821 // 00822 // Set the timer to fire a DPC in 30 seconds. 00823 // 00824 00825 KeSetTimer( &context->ErrorLogTimer, interval, &context->ErrorLogDpc ); 00826 } 00827 00828 VOID 00829 IopErrorLogRequeueEntry( 00830 IN PLIST_ENTRY ListEntry 00831 ) 00832 00833 /*++ 00834 00835 Routine Description: 00836 00837 This routine puts an error packet back at the head of the error log queue 00838 since it cannot be processed at the moment. 00839 00840 Arguments: 00841 00842 ListEntry - Supplies a pointer to the packet to be placed back onto the 00843 error log queue. 00844 00845 Return Value: 00846 00847 None. 00848 00849 --*/ 00850 00851 { 00852 KIRQL irql; 00853 00854 // 00855 // Simply insert the packet back onto the head of the queue, indicate that 00856 // the error log port is not connected, queue a request to check again 00857 // soon, and return. 00858 // 00859 00860 ExAcquireSpinLock( &IopErrorLogLock, &irql ); 00861 InsertHeadList( &IopErrorLogListHead, ListEntry ); 00862 ErrorLogPortConnected = FALSE; 00863 ExReleaseSpinLock( &IopErrorLogLock, irql ); 00864 }

Generated on Sat May 15 19:39:56 2004 for test by doxygen 1.3.7