/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    atkdrvr.c

Abstract:

    This module implements Appletalk Transport Provider driver interfaces
    for NT

Author:

    Nikhil Kamkolkar (NikhilK)    8-Jun-1992
    (Code liberally adapted from NBF and various NT components)

Revision History:

--*/

#define GLOBALS
#include "atalknt.h"
#include "atkdrvr.h"




NTSTATUS
DriverEntry (
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
/*++

Routine Description:

    This is the initialization routine for the LAN Manager Atalk
    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;

    //DbgBreakPoint();

    DBGPRINT(ATALK_DEBUG_INIT, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: DriverEntry(ATALK) - entered\n" ));


    //
    // Create the device object.  (IoCreateDevice zeroes the memory
    // occupied by the object.)
    //

    for (i = 0; i < ATALK_NODEVICES; i++) {

        RtlInitUnicodeString (&deviceName, AtalkDeviceNames[i]);
        status = IoCreateDevice(
                    DriverObject,                             // DriverObject
                    ATALK_DEVICE_EXTENSION_LENGTH,            // DeviceExtension
                    &deviceName,                              // DeviceName
                    FILE_DEVICE_NETWORK,                      // DeviceType
                    0,                                        // DeviceCharacteristics
                    (BOOLEAN)FALSE,                           // Exclusive
                    (PDEVICE_OBJECT *) &AtalkDeviceObject[i]  // DeviceObject
                 );

        if ( !NT_SUCCESS(status) ) {

            //
            //  BUGBUG:
            //  Log error
            //

            //
            //    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]->Context.DeviceType = (ATALK_DEVICE_TYPE)i;

        //
        //  Initialize the provider info and statistics structures for this device
        //

        AtalkQueryInitProviderInfo(
            (ATALK_DEVICE_TYPE)i,
            &AtalkDeviceObject[i]->Context.ProviderInfo);

        AtalkQueryInitProviderStatistics(
            (ATALK_DEVICE_TYPE)i,
            &AtalkDeviceObject[i]->Context.ProviderStatistics);
    }

    //
    // 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;

    //
    //  Initialize critical section module
    //

    InitCriticalSectionNt();

    status=AtalkInitializeTransport(
                DriverObject,
                RegistryPath,
                &NdisPortDesc,
                &NumberOfPorts
                );


    if (!NT_SUCCESS(status)) {

        //
        //   Delete all the devices created
        //

        for (j=0; j < ATALK_NODEVICES; j++ )
           IoDeleteDevice((PDEVICE_OBJECT)AtalkDeviceObject[j]);

        DBGPRINT(ATALK_DEBUG_INIT, DEBUG_LEVEL_FATAL,
        ("ERROR: DriverEntry - AtalkInitializeTransport failed - %lx\n", status));

        return(status);
    }

#if DBG
    NdisAllocateSpinLock(&AtalkGlobalInterlock);
#endif

    NdisAllocateSpinLock(&AtalkGlobalRefLock);
    NdisAllocateSpinLock(&AtalkGlobalStatLock);

    DBGPRINT(ATALK_DEBUG_INIT, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: DriverEntry - AtalkInitialize complete %lx\n",status));

    return status;

} // DriverEntry




NTSTATUS
AtalkDispatchCreate(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This is the dispatch routine for Create functions for the LAN
    Manager Atalk 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 irpSp;
    PFILE_FULL_EA_INFORMATION ea;

    INT createObject;
    TA_APPLETALK_ADDRESS    tdiAddress;
    CONNECTION_CONTEXT  connectionContext;
    PATALK_DEVICE_OBJECT    atalkDeviceObject;

    UCHAR   protocolType, socketType;


    DBGPRINT(ATALK_DEBUG_CREATE, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchCreate - entered for irp %lx\n", Irp ));

    //
    // Make sure status information is consistent every time.
    //


    IoMarkIrpPending (Irp);
    Irp->IoStatus.Status = STATUS_PENDING;
    Irp->IoStatus.Information = 0;

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    atalkDeviceObject = (PATALK_DEVICE_OBJECT)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. Due to our portable stack's design
    //  we will not return from the following calls until the actions are
    //  complete. The stack blocks until the actions complete. So we can be
    //  assured that we can complete the irp upon return from these calls.
    //

    createObject = IrpGetEaCreateType(Irp);
    ea = (PFILE_FULL_EA_INFORMATION)Irp->AssociatedIrp.SystemBuffer;

    switch (createObject) {
    case TDI_TRANSPORT_ADDRESS_FILE :

        if (ea->EaValueLength < sizeof(TA_APPLETALK_ADDRESS)) {

            DBGPRINT(ATALK_DEBUG_CREATE, DEBUG_LEVEL_ERROR,
            ("ERROR: 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.
        //

        RtlMoveMemory(&tdiAddress, &ea->EaName[ea->EaNameLength+1],
                                                sizeof(TA_APPLETALK_ADDRESS));

        //
        //  Also, get the protocol type field for the socket
        //

        DBGPRINT(ATALK_DEBUG_CREATE, DEBUG_LEVEL_INFOCLASS0,
        ("INFO0: AtalkDispatchCreate - Remaining File Name : %S\n",
            &irpSp->FileObject->FileName));

        if (!NT_SUCCESS(AtalkGetProtocolSocketType(
                            &atalkDeviceObject->Context,
                            &irpSp->FileObject->FileName,
                            &protocolType,
                            &socketType ))) {

            status = STATUS_NO_SUCH_DEVICE;
            break;
        }

        status = AtalkTdiOpenAddress(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    &tdiAddress,
                    irpSp->Parameters.Create.SecurityContext,
                    irpSp->Parameters.Create.ShareAccess,
                    protocolType,
                    socketType,
                    &atalkDeviceObject->Context);

        break;

    case TDI_CONNECTION_FILE :

        if (ea->EaValueLength < sizeof(CONNECTION_CONTEXT)) {

            DBGPRINT(ATALK_DEBUG_CREATE, DEBUG_LEVEL_ERROR,
            ("ERROR: AtalkDispatchCreate - Context size %d\n", ea->EaValueLength));

            status = STATUS_EA_LIST_INCONSISTENT;
			break;
        }

        connectionContext =
            *((CONNECTION_CONTEXT *)&ea->EaName[ea->EaNameLength+1]);

        status = AtalkTdiOpenConnection(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    connectionContext,
                    &atalkDeviceObject->Context);
        break;


    case ATALK_FILE_TYPE_CONTROL :

        status = AtalkTdiOpenControlChannel(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    &atalkDeviceObject->Context);
        break;

    default:

        DBGPRINT(ATALK_DEBUG_CREATE, DEBUG_LEVEL_ERROR,
        ("ERROR: AtalkDispatchCreate - unknown EA passed!\n"));

        status = STATUS_INVALID_EA_NAME;
        break;
    }

    //
    // Successful completion.
    //

    DBGPRINT(ATALK_DEBUG_CREATE, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchCreate complete irp %lx status %lx\n", Irp, status ));

    if (status != STATUS_PENDING) {
		irpSp->Control &= ~SL_PENDING_RETURNED;
        TdiCompleteRequest(Irp, status);
    }

    return status;

} // AtalkDispatchCreate




NTSTATUS
AtalkDispatchCleanup(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This is the dispatch routine for Cleanup functions for the LAN
    Manager Atalk 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
                started/completed

--*/
{
    NTSTATUS    status;
    PATALK_DEVICE_OBJECT  atalkDeviceObject;
    PIO_STACK_LOCATION irpSp;

	KIRQL		oldIrql;		// All user requests at DPC level

    DBGPRINT(ATALK_DEBUG_CLOSE, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchCleanup - entered irp %lx\n", Irp ));

    //
    // Make sure status information is consistent every time.
    //


    IoMarkIrpPending (Irp);
    Irp->IoStatus.Status = STATUS_PENDING;
    Irp->IoStatus.Information = 0;

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    atalkDeviceObject = (PATALK_DEVICE_OBJECT)DeviceObject;

	//	RAISE IRQL to DPC level
	KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);

    switch ((ULONG)irpSp->FileObject->FsContext2) {
    case TDI_TRANSPORT_ADDRESS_FILE :

        status = AtalkTdiCleanupAddress(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    Irp,
                    &atalkDeviceObject->Context);

        break;

    case TDI_CONNECTION_FILE :

        status = AtalkTdiCleanupConnection(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    Irp,
                    &atalkDeviceObject->Context);

        break;

    case ATALK_FILE_TYPE_CONTROL :

        //
        //  No cleanup for control channel
        //

        status = STATUS_SUCCESS;
        break;

    default:

        DBGPRINT(ATALK_DEBUG_CLOSE, DEBUG_LEVEL_ERROR,
        ("ERROR: AtalkDispatchCleanup - invalid obj %s\n",
            irpSp->FileObject->FsContext));

        status = STATUS_INVALID_HANDLE;
    }

	//	LOWER IRQL to old level
	KeLowerIrql(oldIrql);

    DBGPRINT(ATALK_DEBUG_CLOSE, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchCleanup - complete irp %lx status %lx\n", Irp, status ));

    if (status != STATUS_PENDING) {
		irpSp->Control &= ~SL_PENDING_RETURNED;
        TdiCompleteRequest(Irp, status);
    }

    return status;

} // AtalkDispatchCleanup




NTSTATUS
AtalkDispatchClose(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This is the dispatch routine for Close functions for the LAN
    Manager Atalk 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 irpSp;
    PATALK_DEVICE_OBJECT  atalkDeviceObject;

	KIRQL		oldIrql;		// All user requests at DPC level

    DBGPRINT(ATALK_DEBUG_CLOSE, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchClose - entered for IRP %lx\n", Irp ));

    //
    // Make sure status information is consistent every time.
    //


    IoMarkIrpPending (Irp);
    Irp->IoStatus.Status = STATUS_PENDING;
    Irp->IoStatus.Information = 0;

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    atalkDeviceObject = (PATALK_DEVICE_OBJECT)DeviceObject;

	//	RAISE IRQL to DPC level
	KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);

    switch ((ULONG)irpSp->FileObject->FsContext2) {
    case TDI_TRANSPORT_ADDRESS_FILE :

        status = AtalkTdiCloseAddress(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    Irp,
                    &atalkDeviceObject->Context);

        break;

    case TDI_CONNECTION_FILE :

        status = AtalkTdiCloseConnection(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    Irp,
                    &atalkDeviceObject->Context);

        break;

    case ATALK_FILE_TYPE_CONTROL :

        status = AtalkTdiCloseControlChannel(
                    &Irp->IoStatus,
                    irpSp->FileObject,
                    Irp,
                    &atalkDeviceObject->Context);
        break;

    default:

        DBGPRINT(ATALK_DEBUG_CLOSE, DEBUG_LEVEL_ERROR,
        ("ERROR: AtalkDispatchClose - Invalid object %s\n",
            irpSp->FileObject->FsContext));

        status = STATUS_INVALID_HANDLE;
    }

	//	LOWER IRQL to old level
	KeLowerIrql(oldIrql);

    DBGPRINT(ATALK_DEBUG_CLOSE, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchClose complete irp %lx status %lx\n", Irp, status ));

    if (status != STATUS_PENDING) {
		irpSp->Control &= ~SL_PENDING_RETURNED;
        TdiCompleteRequest(Irp, status);
    }

    return(status);

} // AtalkDispatchClose




NTSTATUS
AtalkDispatchDeviceControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    This is the dispatch routine for Device Control functions for the
    LAN Manager Atalk 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;
    PATALK_DEVICE_OBJECT  atalkDeviceObject;
    PIO_STACK_LOCATION irpSp;


    DBGPRINT(ATALK_DEBUG_DISPATCH, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchDeviceControl - irp %lx\n", Irp ));

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    atalkDeviceObject = (PATALK_DEVICE_OBJECT)DeviceObject;

    //
    //  Do a map and call the internal device io control function.
    //  That will also perform the completion.
    //

    status = TdiMapUserRequest(
                DeviceObject,
                Irp,
                irpSp);

    if (status == STATUS_SUCCESS) {

        status = AtalkDispatchInternalDeviceControl(
                    DeviceObject,
                    Irp);

        //
        //  AtalkDispatchInternalDeviceControl expects to complete the
        //  irp
        //

    } else {

        DBGPRINT(ATALK_DEBUG_DISPATCH, DEBUG_LEVEL_ERROR,
        ("ERROR: AtalkDispatchDeviceControl - TdiMap failed %lx\n", status));

        TdiCompleteRequest(Irp, status);
    }

    return(status);

} // AtalkDispatchDeviceControl




NTSTATUS
AtalkDispatchInternalDeviceControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This is the dispatch routine for Internal Device Control functions
    for the LAN Manager Atalk 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 irpSp;
    PATALK_DEVICE_OBJECT  atalkDeviceObject;

    PATALK_TDI_REQUEST  request;

	KIRQL		oldIrql;		// All user requests at DPC level

    DBGPRINT(ATALK_DEBUG_DISPATCH, DEBUG_LEVEL_INFOCLASS0,
	("\n\nAtalkDispatchInternalDeviceControl entered for IRP %lx\n", Irp ));

    //
    // Make sure status information is consistent every time.
    //


    IoMarkIrpPending (Irp);
    Irp->IoStatus.Status = STATUS_PENDING;
    Irp->IoStatus.Information = 0;

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    atalkDeviceObject = (PATALK_DEVICE_OBJECT)DeviceObject;

    //
    //  Branch to the appropriate request handler. Create a request block to
    //  shield called routines from being irp-dependent.
    //

    status = AtalkCreateTdiRequest(&request);
    if (NT_SUCCESS(status)) {
	
		//
		//  Set the common parameter pointers
		//
	
		request->OwningDevice = atalkDeviceObject->Context.DeviceType;
		request->Parameters = (PVOID)NULL;
		request->CompletionRoutine = (PVOID)NULL;
	
		request->FileObject = irpSp->FileObject;
		request->DeviceObject = atalkDeviceObject;
		request->DeviceContext = &atalkDeviceObject->Context;
		request->IoRequestIrp = Irp;
		request->IoStatus = &Irp->IoStatus;
	
		switch (irpSp->MinorFunction) {
	
		case TDI_ACCEPT:
	
			request->MinorCommand = TDI_ACCEPT;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiAccept(request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_RECEIVE_DATAGRAM:
	
			request->MinorCommand = TDI_RECEIVE_DATAGRAM;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			request->ReceiveDatagram.MdlAddress = Irp->MdlAddress;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiReceiveDatagram(request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_SEND_DATAGRAM:
	
			request->MinorCommand = TDI_SEND_DATAGRAM;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			request->SendDatagram.MdlAddress = Irp->MdlAddress;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiSendDatagram(request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_SET_EVENT_HANDLER:
	
			request->MinorCommand = TDI_SET_EVENT_HANDLER;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiSetEventHandler(request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_RECEIVE:
	
			request->MinorCommand = TDI_RECEIVE;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//
			//  These need to be done here so we can distinguish between
			//  a NtRead and TdiWrite
			//
	
			request->Receive.SystemRead = FALSE;
			request->Receive.ReceiveBufferLength =
				((PTDI_REQUEST_KERNEL_RECEIVE)&irpSp->Parameters)->ReceiveLength;
			request->Receive.ReceiveFlags =
				&((PTDI_REQUEST_KERNEL_RECEIVE)&irpSp->Parameters)->ReceiveFlags;
	
			request->Receive.MdlAddress = Irp->MdlAddress;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiReceive(request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_SEND:
	
			request->MinorCommand = TDI_SEND;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//
			//  These need to be done here so we can distinguish between
			//  a NtWrite and TdiSend
			//
	
			request->Send.SystemWrite = FALSE;
			request->Send.SendBufferLength =
				((PTDI_REQUEST_KERNEL_SEND)&irpSp->Parameters)->SendLength;
			request->Send.SendFlags =
				((PTDI_REQUEST_KERNEL_SEND)&irpSp->Parameters)->SendFlags;
	
			request->Send.MdlAddress = Irp->MdlAddress;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiSend(request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_ACTION:
	
			//
			//  Set the request specific parameters in the request block
			//  Completion routine for TdiAction is set further on
			//
	
			request->MinorCommand = TDI_ACTION;
			request->Action.MdlAddress = Irp->MdlAddress;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			ASSERT(Irp->MdlAddress != NULL);
			status = AtalkTdiAction (request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_ASSOCIATE_ADDRESS:
	
			request->MinorCommand = TDI_ASSOCIATE_ADDRESS;
			request->Parameters = (PVOID)&irpSp->Parameters;

			//	Do not raise irql for associate! it calls system
			//	routines expecting to be at < Dispatch level
			status = AtalkTdiAssociateAddress(request);
			break;
	
		case TDI_DISASSOCIATE_ADDRESS:
	
			request->MinorCommand = TDI_DISASSOCIATE_ADDRESS;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiDisassociateAddress (request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_CONNECT:
	
			request->MinorCommand = TDI_CONNECT;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiConnect (request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_DISCONNECT:
	
			request->MinorCommand = TDI_DISCONNECT;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiDisconnect (request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_LISTEN:
	
			request->MinorCommand = TDI_LISTEN;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			request->Listen.ListenFlags =
				((PTDI_REQUEST_KERNEL_LISTEN)&irpSp->Parameters)->RequestFlags;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiListen (request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_QUERY_INFORMATION:
	
			request->MinorCommand = TDI_QUERY_INFORMATION;
			request->Query.MdlAddress = Irp->MdlAddress;
			request->Parameters = (PVOID)&irpSp->Parameters;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			ASSERT(Irp->MdlAddress != NULL);
			status = AtalkTdiQueryInformation (request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		case TDI_SET_INFORMATION:
	
			request->MinorCommand = TDI_SET_INFORMATION;
	
			//	RAISE IRQL to DPC level
			KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
		
			status = AtalkTdiSetInformation (request);

			//	LOWER IRQL to old level
			KeLowerIrql(oldIrql);
		
			break;
	
		default:
	
			//
			// Something we don't know about was submitted.
			//
	
			DBGPRINT(ATALK_DEBUG_DISPATCH, DEBUG_LEVEL_ERROR,
			("ERROR: AtalkDispatchInternal -  fnct %lx\n",  irpSp->MinorFunction));
	
			status = STATUS_INVALID_DEVICE_REQUEST;
		}
	}

    DBGPRINT(ATALK_DEBUG_DISPATCH, DEBUG_LEVEL_INFOCLASS0,
    ("INFO0: AtalkDispatchInternal complete irp %lx status %lx\n", Irp, status ));

    //
    // Return the immediate status code to the caller.
    //

    if (status != STATUS_PENDING) {

		irpSp->Control &= ~SL_PENDING_RETURNED;

        //
        //  Complete the request, this will also dereference it.
        //

        AtalkCompleteTdiRequest (
            request,
            status);
    }

    return status;

} // AtalkDispatchInternalDeviceControl




VOID
AtalkUnload(
    IN PDRIVER_OBJECT DriverObject
    )
/*++

Routine Description:

    This is the unload routine for the LAN Manager Atalk driver.

Arguments:

    DriverObject - Pointer to driver object for this driver.

Return Value:

    None.

--*/
{
    USHORT i;

    UNREFERENCED_PARAMETER (DriverObject);

    DBGPRINT(ATALK_DEBUG_DISPATCH, DEBUG_LEVEL_ERROR,
    ("ERROR: AtalkUnload - Maybe it'll work, maybe it won't!\n"));

    UnloadAppleTalk();
    AtalkUnloadStack(&NdisPortDesc, &NumberOfPorts);

    for (i = 0; i < ATALK_NODEVICES; i++) {

        //
        //    Delete all the devices created
        //

        IoDeleteDevice((PDEVICE_OBJECT)AtalkDeviceObject[i]);
    }

    return;

} // AtalkUnload


