/*++ Copyright (c) 1992 Microsoft Corporation Module Name: atkdrvr.c Abstract: This module implements Appletalk Transport Provider driver interfaces for NT Author: Jameel Hyder (jameelh@microsoft.com) Nikhil Kamkolkar (nikhilk@microsoft.com) Revision History: 19 Jun 1992 Initial Version Notes: Tab stop: 4 --*/ #include #pragma hdrstop // File module number for errorlogging #define FILENUM ATKDRVR NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ); #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, DriverEntry) #pragma alloc_text(PAGEINIT, AtalkCleanup) #pragma alloc_text(PAGE, atalkUnload) #pragma alloc_text(PAGE, AtalkDispatchCreate) #pragma alloc_text(PAGE, AtalkDispatchCleanup) #pragma alloc_text(PAGE, AtalkDispatchClose) #pragma alloc_text(PAGE, AtalkDispatchDeviceControl) #endif NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) /*++ Routine Description: This is the initialization routine for the Windows NT Appletalk driver. This routine creates the device object for the Atalk device and performs all other driver initialization. Arguments: DriverObject - Pointer to driver object created by the system. RegistryPath- Path to the root of the section in the registry for this driver Return Value: The function value is the final status from the initialization operation. If this is not STATUS_SUCCESS the driver will not load. --*/ { NTSTATUS status; UNICODE_STRING deviceName; USHORT i, j; DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_INFO, ("Appletalk DriverEntry - Entered !!!\n")); TdiInitialize(); INITIALIZE_SPIN_LOCK(&AtalkStatsLock); INITIALIZE_SPIN_LOCK(&AtalkSktCacheLock); INITIALIZE_SPIN_LOCK(&ArapSpinLock); #if DBG INITIALIZE_SPIN_LOCK(&AtalkDebugSpinLock); #endif // Initialize event for locking/unlocking pageable sections. Set it to signalled state // so that the first wait is satisfied. KeInitializeMutex(&AtalkPgLkMutex, 0xFFFF); // Create the device object. (IoCreateDevice zeroes the memory // occupied by the object.) for (i = 0; i < ATALK_NO_DEVICES; i++) { RtlInitUnicodeString(&deviceName, AtalkDeviceNames[i]); status = IoCreateDevice( DriverObject, // DriverObject ATALK_DEV_EXT_LEN, // DeviceExtension &deviceName, // DeviceName FILE_DEVICE_NETWORK, // DeviceType FILE_DEVICE_SECURE_OPEN, // DeviceCharacteristics (BOOLEAN)FALSE, // Exclusive (PDEVICE_OBJECT *) &AtalkDeviceObject[i]); // DeviceObject if (!NT_SUCCESS(status)) { LOG_ERROR(EVENT_ATALK_CANT_CREATE_DEVICE, status, NULL, 0); // Delete all the devices created so far, if any for (j = 0; j < i; j++) { IoDeleteDevice((PDEVICE_OBJECT)AtalkDeviceObject[j]); } return status; } // Assumption: // 'i' will correspond to the Device type in the ATALK_DEVICE_TYPE enum AtalkDeviceObject[i]->Ctx.adc_DevType = (ATALK_DEV_TYPE)i; // Initialize the provider info and statistics structures for this device AtalkQueryInitProviderInfo((ATALK_DEV_TYPE)i, &AtalkDeviceObject[i]->Ctx.adc_ProvInfo); #if 0 // NOTE: Implement AtalkQueryInitProviderStatistics((ATALK_DEV_TYPE)i, &AtalkDeviceObject[i]->Ctx.adc_ProvStats); #endif } // Initialize the driver object for this driver's entry points. DriverObject->MajorFunction[IRP_MJ_CREATE] = AtalkDispatchCreate; DriverObject->MajorFunction[IRP_MJ_CLEANUP] = AtalkDispatchCleanup; DriverObject->MajorFunction[IRP_MJ_CLOSE] = AtalkDispatchClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AtalkDispatchDeviceControl; DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = AtalkDispatchInternalDeviceControl; DriverObject->DriverUnload = atalkUnload; // Get lock handles to all the conditional pageable sections AtalkLockInit(&AtalkPgLkSection[NBP_SECTION], AtalkNbpAction); AtalkLockInit(&AtalkPgLkSection[ZIP_SECTION], AtalkZipGetMyZone); AtalkLockInit(&AtalkPgLkSection[TDI_SECTION], AtalkTdiCleanupAddress); AtalkLockInit(&AtalkPgLkSection[ATP_SECTION], AtalkAtpCloseAddress); AtalkLockInit(&AtalkPgLkSection[ASP_SECTION], AtalkAspCloseAddress); AtalkLockInit(&AtalkPgLkSection[PAP_SECTION], AtalkPapCleanupAddress); AtalkLockInit(&AtalkPgLkSection[ASPC_SECTION], AtalkAspCCloseAddress); AtalkLockInit(&AtalkPgLkSection[ADSP_SECTION], AtalkAdspCleanupAddress); AtalkLockInit(&AtalkPgLkSection[ROUTER_SECTION], AtalkRtmpPacketInRouter); AtalkLockInit(&AtalkPgLkSection[INIT_SECTION], AtalkInitRtmpStartProcessingOnPort); AtalkLockInit(&AtalkPgLkSection[ARAP_SECTION], ArapExchangeParms); AtalkLockInit(&AtalkPgLkSection[PPP_SECTION], AllocPPPConn); AtalkLockInitIfNecessary(); status = AtalkInitializeTransport(DriverObject, RegistryPath); AtalkUnlockInitIfNecessary(); if (!NT_SUCCESS(status)) { #if DBG // Make sure we are not unloading with any locked sections for (i = 0; i < LOCKABLE_SECTIONS; i++) { ASSERT (AtalkPgLkSection[i].ls_LockCount == 0); } #endif DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR, ("DriverEntry: AtalkInitializeTransport failed %lx\n",status)); } else { DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_INFO, ("DriverEntry: AtalkInitializeTransport complete %lx\n",status)); } return status; } // DriverEntry NTSTATUS AtalkDispatchCreate( IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp ) /*++ Routine Description: This is the dispatch routine for Create functions for the Appletalk driver. Arguments: DeviceObject - Pointer to device object for target device pIrp - Pointer to I/O request packet Return Value: NTSTATUS -- Indicates whether the request was successfully queued. --*/ { NTSTATUS status; PIO_STACK_LOCATION pIrpSp; PFILE_FULL_EA_INFORMATION ea; INT createObject; TA_APPLETALK_ADDRESS tdiAddress; CONNECTION_CONTEXT connectionContext; PATALK_DEV_OBJ atalkDeviceObject; UCHAR protocolType, socketType; DBGPRINT(DBG_COMP_CREATE, DBG_LEVEL_INFO, ("AtalkDispatchCreate: entered for irp %lx\n", pIrp)); // Make sure status information is consistent every time. IoMarkIrpPending(pIrp); pIrp->IoStatus.Status = STATUS_PENDING; pIrp->IoStatus.Information = 0; pIrpSp = IoGetCurrentIrpStackLocation(pIrp); atalkDeviceObject = (PATALK_DEV_OBJ)DeviceObject; // Both opens must complete synchronously. It is possible we return // status_pending to the system, but it will not return to the caller // until the call actually completes. In our case, we block until the // actions are complete. So we can be assured that we can complete the irp // upon return from these calls. createObject = AtalkIrpGetEaCreateType(pIrp); ea = (PFILE_FULL_EA_INFORMATION)pIrp->AssociatedIrp.SystemBuffer; switch (createObject) { case TDI_TRANSPORT_ADDRESS_FILE : if (ea->EaValueLength < sizeof(TA_APPLETALK_ADDRESS)) { DBGPRINT(DBG_COMP_CREATE, DBG_LEVEL_ERR, ("AtalkDispatchCreate: addr size %d\n", ea->EaValueLength)); status = STATUS_EA_LIST_INCONSISTENT; break; } // We have the AtalkTdiOpenAddress routine look at only the first // address in the list of addresses by casting the passed address // to TA_APPLETALK_ADDRESS. RtlCopyMemory( &tdiAddress, (PBYTE)(&ea->EaName[ea->EaNameLength+1]), sizeof(TA_APPLETALK_ADDRESS)); // Also, get the protocol type field for the socket DBGPRINT(DBG_COMP_CREATE, DBG_LEVEL_INFO, ("AtalkDispatchCreate: Remaining File Name : %S\n", &pIrpSp->FileObject->FileName)); if (!NT_SUCCESS(AtalkGetProtocolSocketType(&atalkDeviceObject->Ctx, &pIrpSp->FileObject->FileName, &protocolType, &socketType))) { status = STATUS_NO_SUCH_DEVICE; break; } status = AtalkTdiOpenAddress( pIrp, pIrpSp, &tdiAddress, protocolType, socketType, &atalkDeviceObject->Ctx); break; case TDI_CONNECTION_FILE : if (ea->EaValueLength < sizeof(CONNECTION_CONTEXT)) { DBGPRINT(DBG_COMP_CREATE, DBG_LEVEL_ERR, ("AtalkDispatchCreate: Context size %d\n", ea->EaValueLength)); status = STATUS_EA_LIST_INCONSISTENT; break; } RtlCopyMemory(&connectionContext, &ea->EaName[ea->EaNameLength+1], sizeof(CONNECTION_CONTEXT)); status = AtalkTdiOpenConnection(pIrp, pIrpSp, connectionContext, &atalkDeviceObject->Ctx); break; case TDI_CONTROL_CHANNEL_FILE : status = AtalkTdiOpenControlChannel(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; default: DBGPRINT(DBG_COMP_CREATE, DBG_LEVEL_ERR, ("AtalkDispatchCreate: unknown EA passed!\n")); status = STATUS_INVALID_EA_NAME; break; } // Successful completion. DBGPRINT(DBG_COMP_CREATE, DBG_LEVEL_INFO, ("AtalkDispatchCreate complete irp %lx status %lx\n", pIrp, status)); if (NT_SUCCESS(status)) INTERLOCKED_INCREMENT_LONG(&AtalkHandleCount, &AtalkStatsLock); if (status != STATUS_PENDING) { pIrpSp->Control &= ~SL_PENDING_RETURNED; ASSERT (status != STATUS_PENDING); TdiCompleteRequest(pIrp, status); } return status; } // AtalkDispatchCreate NTSTATUS AtalkDispatchCleanup( IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp ) /*++ Routine Description: This is the dispatch routine for Cleanup functions for the Appletalk driver. Arguments: DeviceObject - Pointer to device object for target device pIrp - Pointer to I/O request packet Return Value: NTSTATUS -- Indicates whether the request was successfully started/completed --*/ { NTSTATUS status; PATALK_DEV_OBJ atalkDeviceObject; PIO_STACK_LOCATION pIrpSp; DBGPRINT(DBG_COMP_CLOSE, DBG_LEVEL_INFO, ("AtalkDispatchCleanup: entered irp %lx\n", pIrp)); // Make sure status information is consistent every time. IoMarkIrpPending (pIrp); pIrp->IoStatus.Status = STATUS_PENDING; pIrp->IoStatus.Information = 0; pIrpSp = IoGetCurrentIrpStackLocation(pIrp); atalkDeviceObject = (PATALK_DEV_OBJ)DeviceObject; switch ((ULONG_PTR)(pIrpSp->FileObject->FsContext2) & 0xFF) { case TDI_TRANSPORT_ADDRESS_FILE : status = AtalkTdiCleanupAddress(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_CONNECTION_FILE : status = AtalkTdiCleanupConnection(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_CONTROL_CHANNEL_FILE : status = STATUS_SUCCESS; break; default: DBGPRINT(DBG_COMP_CLOSE, DBG_LEVEL_ERR, ("AtalkDispatchCleaup: Invalid object %s\n", pIrpSp->FileObject->FsContext)); status = STATUS_INVALID_HANDLE; break; } DBGPRINT(DBG_COMP_CLOSE, DBG_LEVEL_INFO, ("AtalkDispatchCleanup complete irp %lx status %lx\n", pIrp, status)); if (status != STATUS_PENDING) { pIrpSp->Control &= ~SL_PENDING_RETURNED; ASSERT (status != STATUS_PENDING); TdiCompleteRequest(pIrp, status); } return(status); } // AtalkDispatchCleanup NTSTATUS AtalkDispatchClose( IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp ) /*++ Routine Description: This is the dispatch routine for Close functions for the Appletalk driver. Arguments: DeviceObject - Pointer to device object for target device irp - Pointer to I/O request packet Return Value: NTSTATUS -- Indicates whether the request was successfully queued. --*/ { NTSTATUS status; PIO_STACK_LOCATION pIrpSp; PATALK_DEV_OBJ atalkDeviceObject; DBGPRINT(DBG_COMP_CLOSE, DBG_LEVEL_INFO, ("AtalkDispatchClose: entered for IRP %lx\n", pIrp)); // Make sure status information is consistent every time. IoMarkIrpPending(pIrp); pIrp->IoStatus.Status = STATUS_PENDING; pIrp->IoStatus.Information = 0; pIrpSp = IoGetCurrentIrpStackLocation(pIrp); atalkDeviceObject = (PATALK_DEV_OBJ)DeviceObject; switch ((ULONG_PTR)(pIrpSp->FileObject->FsContext2) & 0xFF) { case TDI_TRANSPORT_ADDRESS_FILE : status = AtalkTdiCloseAddress(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_CONNECTION_FILE : status = AtalkTdiCloseConnection(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_CONTROL_CHANNEL_FILE : status = AtalkTdiCloseControlChannel(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; default: DBGPRINT(DBG_COMP_CLOSE, DBG_LEVEL_ERR, ("AtalkDispatchClose: Invalid object %s\n", pIrpSp->FileObject->FsContext)); status = STATUS_INVALID_HANDLE; break; } DBGPRINT(DBG_COMP_CLOSE, DBG_LEVEL_INFO, ("AtalkDispatchClose complete irp %lx status %lx\n", pIrp, status)); if (status != STATUS_PENDING) { pIrpSp->Control &= ~SL_PENDING_RETURNED; ASSERT (status != STATUS_PENDING); TdiCompleteRequest(pIrp, status); } INTERLOCKED_DECREMENT_LONG(&AtalkHandleCount, &AtalkStatsLock); return(status); } // AtalkDispatchClose NTSTATUS AtalkDispatchDeviceControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp ) /*++ Routine Description: This is the dispatch routine for Device Control functions for the Appletalk driver. Arguments: DeviceObject - Pointer to device object for target device pIrp - Pointer to I/O request packet Return Value: NTSTATUS -- Indicates whether the request was successfully queued. --*/ { NTSTATUS status; PATALK_DEV_OBJ atalkDeviceObject; PIO_STACK_LOCATION pIrpSp; ULONG IoControlCode; DBGPRINT(DBG_COMP_DISPATCH, DBG_LEVEL_INFO, ("AtalkDispatchDeviceControl: irp %lx\n", pIrp)); pIrpSp = IoGetCurrentIrpStackLocation(pIrp); atalkDeviceObject = (PATALK_DEV_OBJ)DeviceObject; IoControlCode = pIrpSp->Parameters.DeviceIoControl.IoControlCode; // // if it's a request from ARAP, process it here and return // if (IoControlCode > IOCTL_ARAP_START && IoControlCode < IOCTL_ARAP_END) { status = ArapProcessIoctl(pIrp); return(status); } // Do a map and call the internal device io control function. // That will also perform the completion. status = TdiMapUserRequest(DeviceObject, pIrp, pIrpSp); if (status == STATUS_SUCCESS) { status = AtalkDispatchInternalDeviceControl( DeviceObject, pIrp); // // AtalkDispatchInternalDeviceControl expects to complete the // irp // } else { DBGPRINT(DBG_COMP_DISPATCH, DBG_LEVEL_WARN, ("AtalkDispatchDeviceControl: TdiMap failed %lx\n", status)); pIrpSp->Control &= ~SL_PENDING_RETURNED; ASSERT (status != STATUS_PENDING); TdiCompleteRequest(pIrp, status); } return(status); } // AtalkDispatchDeviceControl NTSTATUS AtalkDispatchInternalDeviceControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp ) /*++ Routine Description: This is the dispatch routine for Internal Device Control functions for the Appletalk driver. Arguments: DeviceObject - Pointer to device object for target device pIrp - Pointer to I/O request packet Return Value: NTSTATUS -- Indicates whether the request was successfully queued. --*/ { NTSTATUS status; PIO_STACK_LOCATION pIrpSp; PATALK_DEV_OBJ atalkDeviceObject; KIRQL oldIrql; DBGPRINT(DBG_COMP_DISPATCH, DBG_LEVEL_INFO, ("AtalkDispatchInternalDeviceControl entered for IRP %lx\n", pIrp)); // Make sure status information is consistent every time. IoMarkIrpPending (pIrp); pIrp->IoStatus.Status = STATUS_PENDING; pIrp->IoStatus.Information = 0; pIrpSp = IoGetCurrentIrpStackLocation(pIrp); atalkDeviceObject = (PATALK_DEV_OBJ)DeviceObject; IoAcquireCancelSpinLock(&oldIrql); if (!pIrp->Cancel) { IoSetCancelRoutine(pIrp, (PDRIVER_CANCEL)AtalkTdiCancel); } else { IoReleaseCancelSpinLock(oldIrql); status = STATUS_CANCELLED; TdiCompleteRequest(pIrp, status); return(status); } IoReleaseCancelSpinLock(oldIrql); // Branch to the appropriate request handler. switch (pIrpSp->MinorFunction) { case TDI_ACCEPT: status = AtalkTdiAccept(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_RECEIVE_DATAGRAM: status = AtalkTdiReceiveDgram(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_SEND_DATAGRAM: status = AtalkTdiSendDgram(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_SET_EVENT_HANDLER: status = AtalkTdiSetEventHandler(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_RECEIVE: status = AtalkTdiReceive(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_SEND: status = AtalkTdiSend(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_ACTION: ASSERT(pIrp->MdlAddress != NULL); status = AtalkTdiAction(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_ASSOCIATE_ADDRESS: status = AtalkTdiAssociateAddress(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_DISASSOCIATE_ADDRESS: status = AtalkTdiDisassociateAddress(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_CONNECT: status = AtalkTdiConnect(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_DISCONNECT: status = AtalkTdiDisconnect(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_LISTEN: status = AtalkTdiListen(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_QUERY_INFORMATION: ASSERT(pIrp->MdlAddress != NULL); status = AtalkTdiQueryInformation(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; case TDI_SET_INFORMATION: status = AtalkTdiSetInformation(pIrp, pIrpSp, &atalkDeviceObject->Ctx); break; default: // Something we don't know about was submitted. DBGPRINT(DBG_COMP_DISPATCH, DBG_LEVEL_ERR, ("AtalkDispatchInternal: fnct %lx\n", pIrpSp->MinorFunction)); status = STATUS_INVALID_DEVICE_REQUEST; } DBGPRINT(DBG_COMP_DISPATCH, DBG_LEVEL_INFO, ("AtalkDispatchInternal complete irp %lx status %lx\n", pIrp, status)); // Return the immediate status code to the caller. if (status != STATUS_PENDING) { pIrpSp->Control &= ~SL_PENDING_RETURNED; // Complete the request, this will also dereference it. pIrp->CancelRoutine = NULL; ASSERT (status != STATUS_PENDING); TdiCompleteRequest(pIrp, status); } return status; } // AtalkDispatchInternalDeviceControl VOID atalkUnload( IN PDRIVER_OBJECT DriverObject ) /*++ Routine Description: This is the unload routine for the Appletalk driver. NOTE: Unload will not be called until all the handles have been closed successfully. We just shutdown all the ports, and do misc. cleanup. Arguments: DriverObject - Pointer to driver object for this driver. Return Value: None. --*/ { UNREFERENCED_PARAMETER (DriverObject); AtalkBindnUnloadStates |= ATALK_UNLOADING; // if we hit that timing window where binding or PnP event is going on, // sleep (for a second each time) until that action completes while (AtalkBindnUnloadStates & (ATALK_BINDING | ATALK_PNP_IN_PROGRESS)) { AtalkSleep(1000); } AtalkLockInitIfNecessary(); AtalkCleanup(); AtalkUnlockInitIfNecessary(); #if DBG { int i; // Make sure we are not unloading with any locked sections for (i = 0; i < LOCKABLE_SECTIONS; i++) { ASSERT (AtalkPgLkSection[i].ls_LockCount == 0); } } DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR, ("Appletalk driver unloaded\n")); #endif } // atalkUnload VOID AtalkCleanup( VOID ) /*++ Routine Description: This is synchronous and will block until Unload Completes Arguments: None. Return Value: None. --*/ { PPORT_DESCRIPTOR pPortDesc; LONG i; KIRQL OldIrql; // Stop the timer subsystem AtalkTimerFlushAndStop(); ASSERT(KeGetCurrentIrql() == LOW_LEVEL); ACQUIRE_SPIN_LOCK(&AtalkPortLock, &OldIrql); // Shut down all ports while ((pPortDesc = AtalkPortList) != NULL) { RELEASE_SPIN_LOCK(&AtalkPortLock, OldIrql); AtalkPortShutdown(pPortDesc); ACQUIRE_SPIN_LOCK(&AtalkPortLock, &OldIrql); } RELEASE_SPIN_LOCK(&AtalkPortLock, OldIrql); if (AtalkRegPath.Buffer != NULL) { AtalkFreeMemory(AtalkRegPath.Buffer); } if (AtalkDefaultPortName.Buffer != NULL) { AtalkFreeMemory(AtalkDefaultPortName.Buffer); } for (i = 0; i < ATALK_NO_DEVICES; i++) { // // Delete all the devices created // IoDeleteDevice((PDEVICE_OBJECT)AtalkDeviceObject[i]); } // Deinitialize the Block Package AtalkDeInitMemorySystem(); // Check if routing is on, if so unlock the router code now if (AtalkRouter) AtalkUnlockRouterIfNecessary(); // Free the rtmp tables AtalkRtmpInit(FALSE); // Free the zip tables AtalkZipInit(FALSE); // Release ndis resources (buffer/packet pools) AtalkNdisReleaseResources(); // Deregister the protocol from ndis if handle is non-null if (AtalkNdisProtocolHandle != (NDIS_HANDLE)NULL) AtalkNdisDeregisterProtocol(); ASSERT(AtalkStatistics.stat_CurAllocSize == 0); ASSERT(AtalkDbgMdlsAlloced == 0); ASSERT(AtalkDbgIrpsAlloced == 0); #ifdef PROFILING ASSERT(AtalkStatistics.stat_CurAllocCount == 0); ASSERT(AtalkStatistics.stat_CurMdlCount == 0); #endif }