/*++
 * Copyright (c) 1990-96 Digi International Inc., All Rights Reserved
 * 
 * This software contains proprietary and confidential information of Digi
 * International Inc.  By accepting transfer of this copy, Recipient agrees
 * to retain this software in confidence, to prevent disclosure to others,
 * and to make no use of this software other than that for which it was
 * delivered.  This is an unpublished copyrighted work of Digi International
 * Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 * prohibited.
 * 
 * Restricted Rights Legend
 *
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 * Technical Data and Computer Software clause at DFARS 252.227-7031 or
 * subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 * Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 *
 * Last Update: $Date: 10/22/97 11:56a $
 *
 * $Header: /Components/Windows/NT/Sync/Build/Mp570/mp_api.c 6     10/22/97 11:56a Darinh $
 *
 * Revision History:
 * $Log: /Components/Windows/NT/Sync/Build/Mp570/mp_api.c $
 * 
 * 6     10/22/97 11:56a Darinh
 * Changes to make multiple board installations work better.
 * 
 * 5     9/08/97 3:45p Darinh
 * A variety of changes, including Data General fixes are included.
 * 
 * 4     7/15/97 1:05p Darinh
 * Added DigiLogError statements and message file, altered code to function
 * in DStreams environment and SNA environment with a conditional
 * compilation.
 * 
 * 3     6/06/97 1:48p Darinh
 * Everything seems functional, but more testing is required.
 * 
 * 2     4/08/97 4:55p Darinh
 * Changes made to correspond to the new DigiUtil library.  It
 * successfully transmits data!  I don't know about receiving data yet.
 * 
 * 1     4/02/97 10:08a Darinh
 * Initial checkin; ISA cards initialize, but do not pass data.
 * 
--*/
#include "digilib.h"

#include <ntddk.h>
#include "board.h"
#include "mp_api.h"
#include "ioctls.h"
#include "dsmp.h"
#include "hw570.h"
#include "hwsca.h"
#include "proto.h"
#include "debug.h"
#include "snaioctl.h"

#if !NT5_BUILD

// Access to global variables
extern KSPIN_LOCK          gControllerListSpinLock;
extern LIST_ENTRY          gControllerList;

NTSTATUS
MapAndLockUserMemory (
	IN PIO_STACK_LOCATION	pIrpSp,
	IN PIRP					pIrp,
	IN PUCHAR				pUserDataBuffer,
	IN ULONG				UserDataMapSize,
	OUT PVOID				*pMappedSystemBuffer,
	OUT PVOID				*pMappedUserData);


NTSTATUS
fnOpen (
	IN PDEVICE_OBJECT	pDeviceObject,
	IN PIRP				pIrp)
/*++

Description:


Parameters:

	pDeviceObject

	pIrp

Return Value:

	NT Status.

--*/
{
	PDEVICE_EXT	pDeviceExt = pDeviceObject->DeviceExtension;
	KIRQL		OldIrqL;
	NTSTATUS	Status = STATUS_SUCCESS;


	DigiDump (DIGIFLOW, ("Entering fnOpen()  <Port: %d>\n",
		pDeviceExt->PortNumber));

	KeAcquireSpinLock (&pDeviceExt->pControllerExt->MemoryLock, &OldIrqL);

	InterlockedIncrement (&pDeviceExt->DeviceOpens);
	if (pDeviceExt->DeviceOpens == 1)
	{
		RtlZeroMemory (&pDeviceExt->IR, sizeof (IR));
		pDeviceExt->IR.TransmitBufferSpace =
			(pDeviceExt->NumberOfDmaChains - 1) * DEFAULT_FRAME_SIZE;
		//  start the receiver
		fnStartChannel (pDeviceExt);
	}

#if !SNA_DRIVER

	// A U.K. customer (Farsite) uses our driver.  They use the driver
	// compile for a miniport, but then they use the SNADS interface.
	// They require the Event Handle to be NULL'ed out on a close.
	// The Miniport Interface does not use the Event.
	pDeviceExt->pUserEvent = NULL;

#endif

	KeReleaseSpinLock (&pDeviceExt->pControllerExt->MemoryLock, OldIrqL);

	pIrp->IoStatus.Status = Status;
	pIrp->IoStatus.Information = 0L;

	KeRaiseIrql (DISPATCH_LEVEL, &OldIrqL);
	IoCompleteRequest (pIrp, 0);
	KeLowerIrql (OldIrqL);

	DigiDump (DIGIFLOW, ("Exiting fnOpen()\n"));

	return (Status);
}


NTSTATUS
fnClose (
	IN PDEVICE_OBJECT	pDeviceObject,
	IN PIRP				pIrp)
/*++

Description:


Parameters:

	pDeviceObject

	pIrp

Return Value:

	NT Status.

--*/
{
	PDEVICE_EXT	pDeviceExt = pDeviceObject->DeviceExtension;
	KIRQL		OldIrqL;
	NTSTATUS	Status = STATUS_SUCCESS;


	DigiDump (DIGIFLOW, ("Entering fnClose()  <Port: %d>\n",
		pDeviceExt->PortNumber));

	pIrp->IoStatus.Information = 0L;

	fnDropModemSignals (pDeviceExt);

	KeAcquireSpinLock (&pDeviceExt->pControllerExt->MemoryLock, &OldIrqL);

	//  Only stop the channel if it is the last open to the link
	if (pDeviceExt->DeviceOpens == 1)
	{
		InterlockedDecrement (&pDeviceExt->DeviceOpens);
		fnStopChannel (pDeviceExt);
	}
	else if (pDeviceExt->DeviceOpens == 0)
	{
		DigiDump (DIGIERRORS, ("*** ERROR - closing too many\n"));
		fnStopChannel (pDeviceExt);
	}
	else
	{
		InterlockedDecrement (&pDeviceExt->DeviceOpens);
		Status = STATUS_FILE_CLOSED;
	}

	KeReleaseSpinLock (&pDeviceExt->pControllerExt->MemoryLock, OldIrqL);

	pIrp->IoStatus.Status = Status;

	KeRaiseIrql (DISPATCH_LEVEL, &OldIrqL);
	IoCompleteRequest (pIrp, 0);
	KeLowerIrql (OldIrqL);

	DigiDump (DIGIFLOW, ("Exiting fnClose()\n"));

	return (Status);
}


VOID
fnUnload (
	IN PDRIVER_OBJECT	pDriverObject)
/*++

Description:

	This will be called when NT wants to unload the driver.  This routine
	will perform the following:

		1.	Release the resources allocated at init time
		2.	Delete the Device objects and the Controller objects for each card.
		3.	Unmap the I/O and Memory resources

Parameters:

	pDeviceObject
		Address of the Driver Object.

Return Value:

	None.

--*/
{

	PCONTROLLER_EXT		pControllerExt;
	PLIST_ENTRY			pControllerListEntry;

	DigiDump (DIGIFLOW, ("Entering fnUnload()\n"));
	DigiDump (DIGIINIT, ("  &gControllerList = 0x%x\n", &gControllerList));
 
	//
    //  The open instance of the device is about to close
    //  We need to complete all pending Irp's
    //  First we complete any pending read requests
    //
    while ((pControllerListEntry=ExInterlockedRemoveHeadList(
											&gControllerList,
											&gControllerListSpinLock
											)) != NULL) {

        pControllerExt=CONTAINING_RECORD(pControllerListEntry,CONTROLLER_EXT,ListElement);

		DigiDump (DIGIINIT, ("  pControllerListEntry = 0x%x\n", pControllerListEntry));

		// Unload this controller
		fnUnloadController(pControllerExt,pDriverObject);

	}

	DigiDump (DIGIINIT, ("  pControllerListEntry = 0x%x\n", pControllerListEntry));

	DigiDump (DIGIFLOW, ("Exiting fnUnload()\n"));
}


NTSTATUS
fnDevIoctl (
	IN PDEVICE_OBJECT	pDeviceObject,
	IN PIRP				pIrp)
/*++

Description:

		This function serves as the API between the miniport driver and the
		Digi Streams engine above.  The following are the supported commands:
		1. Get Frame
			- Transfer a received frame from the receive queue into a DSTREAMs
				buffer.
			- Decrement Receive Frame Count
		2. Put Frame
			- Transmit a frame from the DSTREAMS driver out the line
			- The IRP will return immediately, even though the frame
				has not physically been put on the line
			- Update the Transmit Buffer Space in the Interface Record
			- When the frame is physically transmitted, we will update
				the Transmit Buffer Space in the Interface Record.
		3. Get Config
			- Report Max frame size, baud rate, and physical interface type.
		4. Set Config
			- Set Max frame size, baud rate, and physical interface type.
		5. Get Modem signals
			- Retrieve the DCD, DSR, and CTS signals. These are the only
				signals we need to know.
		6. Set Modem signals
			- Set DTR and RTS signals. These are the only two modem
				signals we need to control.
		7. Get Statistics
			- Copy the line statistics into the user buffer.
		8. Zero Statistics
			- Set all line statistics counters to zero.

Parameters:

	pDeviceObject
		Address of the Device Extension

	pIrp
		Pointer to the Io Request Packet

Return Value:

	NT Status.

--*/
{
	PDEVICE_EXT			pDeviceExt;
	PCONTROLLER_EXT		pControllerExt;
	PIO_STACK_LOCATION	pIrpSp;
	LONG				InputBufferLength = 0, OutputBufferLength = 0;
	PMDL				pMdl = NULL, pNextMdl = NULL;
	KIRQL				OldIrqL;
	NTSTATUS			Status = STATUS_SUCCESS;


	DigiDump (DIGIFLOW, ("Entering fnDevIoctl()\n"));

	pIrpSp = IoGetCurrentIrpStackLocation (pIrp);
	InputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
	OutputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
	pDeviceExt = pDeviceObject->DeviceExtension;
	pControllerExt = pDeviceExt->pControllerExt;

	pIrp->IoStatus.Information = 0L;

	DigiDump (DIGIVERYLOUD, ("Port: %d  DeviceExt: 0x%x  IOCTL code = 0x%x\n", 
		pDeviceExt->PortNumber,
		pDeviceExt,
		pIrpSp->Parameters.DeviceIoControl.IoControlCode));

	//
	//  Make sure we are protected - all of these routines need to
	//  manipulate the window on ISA cards, or cannot be interrupted
	//
	KeAcquireSpinLock (&pDeviceExt->pControllerExt->MemoryLock, &OldIrqL);

	switch (pIrpSp->Parameters.DeviceIoControl.IoControlCode)
	{
	case IOCTL_DSMP_GETFRAME:
	{
		PDSMP_GETFRAME		pFrame;

		if ((sizeof (DSMP_GETFRAME) == InputBufferLength) &&
			(sizeof (DSMP_GETFRAME) == OutputBufferLength))
		{
			//
			// Determine if the request is from user-mode.  If it is, lock down
			// the memory so the driver can access it freely.
			//
			if (pIrp->RequestorMode == UserMode)
			{
				pFrame = (PDSMP_GETFRAME) pIrp->AssociatedIrp.SystemBuffer;
				Status = MapAndLockUserMemory (pIrpSp,
					pIrp,
					pFrame->pUserDataBuffer,
					pFrame->DataMaxLength,
					(PVOID)&pFrame,
					(PVOID)&pFrame->pDataBuffer);
				
				if (NT_SUCCESS (Status))
				{
					Status = IoctlGetFrame (pDeviceExt, pFrame);

					// Now unlock the user memory
					pMdl = pIrp->MdlAddress;
					while (pMdl != NULL)
					{
						pNextMdl = pMdl->Next;
						MmUnlockPages (pMdl);
						IoFreeMdl (pMdl);
						pMdl = pNextMdl;
					}
					pIrp->MdlAddress = NULL;
				}
				else
				{
					Status = STATUS_UNSUCCESSFUL;
				}

			}
			else
			{
				pFrame = (PDSMP_GETFRAME) pIrp->AssociatedIrp.SystemBuffer;
				pFrame->pDataBuffer = pFrame->pUserDataBuffer;

				Status = IoctlGetFrame (pDeviceExt, pFrame);
			}

			if (NT_SUCCESS (Status))
				pIrp->IoStatus.Information = sizeof (DSMP_GETFRAME);
		}
		else if (sizeof (DSMP_GETFRAME) != InputBufferLength)
		{
			Status = STATUS_INVALID_PARAMETER;
		}
		else
		{
			Status = STATUS_BUFFER_TOO_SMALL;
		}

		break;
	}

	case IOCTL_DSMP_PUTFRAME:
	{
		PDSMP_PUTFRAME	pFrame = NULL;

		if (sizeof (DSMP_PUTFRAME) == InputBufferLength)
		{
			//
			// Determine if the request is from user-mode.  If it is, lock
			// down the memory so the driver can access it freely.
			//
			if (pIrp->RequestorMode == UserMode)
			{
				pFrame = (PDSMP_PUTFRAME) pIrp->AssociatedIrp.SystemBuffer;
				Status = MapAndLockUserMemory (pIrpSp,
					pIrp,
					pFrame->pUserDataBuffer,
					pFrame->DataLength,
					(PVOID)&pFrame,
					(PVOID)&pFrame->pDataBuffer);
				
				if (NT_SUCCESS (Status))
				{
					IoctlPutFrame (pDeviceExt, pFrame);

					// Now unlock the user memory
					pMdl = pIrp->MdlAddress;
					while (pMdl != NULL)
					{
						pNextMdl = pMdl->Next;
						MmUnlockPages (pMdl);
						IoFreeMdl (pMdl);
						pMdl = pNextMdl;
					}
					pIrp->MdlAddress = NULL;
				}
				else
				{
					Status = STATUS_UNSUCCESSFUL;
				}
			}
			else
			{
				pFrame = (PDSMP_PUTFRAME) pIrp->AssociatedIrp.SystemBuffer;
				pFrame->pDataBuffer = pFrame->pUserDataBuffer;

				IoctlPutFrame (pDeviceExt, pFrame);
			}
		}
		else
		{
			Status = STATUS_INVALID_PARAMETER;
		}

		break;
	}

	case IOCTL_DSMP_GETCONFIG:
	{
		PDSMP_CONFIG	pConfig = NULL;


		if ((sizeof (DSMP_CONFIG) == InputBufferLength) &&
			(sizeof (DSMP_CONFIG) == OutputBufferLength))
		{
			//
			// Determine if the request is from user-mode.  If it is, lock
			// down the memory so the driver can access it freely.
			//
			if (pIrp->RequestorMode == UserMode)
			{
				Status = MapAndLockUserMemory (pIrpSp,
					pIrp,
					NULL,				// No pointer from user space
					0,					// No pointer from user space
					(PVOID)&pConfig,
					NULL);
				
				if (NT_SUCCESS (Status))
				{
					Status = IoctlGetConfig (pDeviceExt, pConfig);

					// Now unlock the user memory
					pMdl = pIrp->MdlAddress;
					while (pMdl != NULL)
					{
						pNextMdl = pMdl->Next;
						MmUnlockPages (pMdl);
						IoFreeMdl (pMdl);
						pMdl = pNextMdl;
					}
					pIrp->MdlAddress = NULL;
				}
				else
				{
					Status = STATUS_UNSUCCESSFUL;
				}
			}
			else
			{
				pConfig = (PDSMP_CONFIG) pIrp->AssociatedIrp.SystemBuffer;
				Status = IoctlGetConfig (pDeviceExt, pConfig);
			}
		}
		else if (sizeof (DSMP_CONFIG) != InputBufferLength)
		{
			Status = STATUS_INVALID_PARAMETER;
		}
		else
		{
			Status = STATUS_BUFFER_TOO_SMALL;
		}

		if (NT_SUCCESS (Status))
			pIrp->IoStatus.Information = sizeof (DSMP_CONFIG);

		break;
	}

	case IOCTL_DSMP_SETCONFIG:
	{
		PDSMP_CONFIG	pConfig = NULL;

		if (sizeof (DSMP_CONFIG) == InputBufferLength)
		{
			//
			// Determine if the request is from user-mode.  If it is, lock
			// down the memory so the driver can access it freely.
			//
			if (pIrp->RequestorMode == UserMode)
			{
				Status = MapAndLockUserMemory (pIrpSp,
					pIrp,
					NULL,				// No pointer from user space
					0,					// No pointer from user space
					(PVOID)&pConfig,
					NULL);
				
				if (NT_SUCCESS (Status))
				{
					Status = IoctlSetConfig (pDeviceExt, pConfig);

					// Now unlock the user memory
					pMdl = pIrp->MdlAddress;
					while (pMdl != NULL)
					{
						pNextMdl = pMdl->Next;
						MmUnlockPages (pMdl);
						IoFreeMdl (pMdl);
						pMdl = pNextMdl;
					}
					pIrp->MdlAddress = NULL;
				}
				else
				{
					Status = STATUS_UNSUCCESSFUL;
				}
			}
			else
			{
				pConfig = (PDSMP_CONFIG) pIrp->AssociatedIrp.SystemBuffer;
				Status = IoctlSetConfig (pDeviceExt, pConfig);
			}
		}
		else
		{
			Status = STATUS_INVALID_PARAMETER;
		}

		break;
	}

	case IOCTL_DSMP_GETMODEM:
	{
		PDSMP_MODEM		pModem = NULL;

		if ((sizeof (DSMP_MODEM) == InputBufferLength) &&
			(sizeof (DSMP_MODEM) == OutputBufferLength))
		{
			//
			// Determine if the request is from user-mode.  If it is, lock
			// down the memory so the driver can access it freely.
			//
			if (pIrp->RequestorMode == UserMode)
			{
				Status = MapAndLockUserMemory (pIrpSp,
					pIrp,
					NULL,				// No pointer from user space
					0,					// No pointer from user space
					(PVOID)&pModem,
					NULL);
				
				if (NT_SUCCESS (Status))
				{
					Status = IoctlGetModem (pDeviceExt, pModem);

					// Now unlock the user memory
					pMdl = pIrp->MdlAddress;
					while (pMdl != NULL)
					{
						pNextMdl = pMdl->Next;
						MmUnlockPages (pMdl);
						IoFreeMdl (pMdl);
						pMdl = pNextMdl;
					}
					pIrp->MdlAddress = NULL;
				}
				else
				{
					Status = STATUS_UNSUCCESSFUL;
				}
			}
			else
			{
				pModem = (PDSMP_MODEM) pIrp->AssociatedIrp.SystemBuffer;
				Status = IoctlGetModem (pDeviceExt, pModem);
			}
		}
		else if (sizeof (DSMP_MODEM) != InputBufferLength)
		{
			Status = STATUS_INVALID_PARAMETER;
		}
		else
		{
			Status = STATUS_BUFFER_TOO_SMALL;
		}

		if (NT_SUCCESS (Status))
			pIrp->IoStatus.Information = sizeof (DSMP_MODEM);

		break;
	}

	case IOCTL_DSMP_SETMODEM:
	{
		PDSMP_MODEM		pModem = NULL;

		if ((sizeof (DSMP_MODEM) == InputBufferLength) &&
			(sizeof (DSMP_MODEM) == OutputBufferLength))
		{
			//
			// Determine if the request is from user-mode.  If it is, lock
			// down the memory so the driver can access it freely.
			//
			if (pIrp->RequestorMode == UserMode)
			{
				Status = MapAndLockUserMemory (pIrpSp,
					pIrp,
					NULL,				// No pointer from user space
					0,					// No pointer from user space
					(PVOID)&pModem,
					NULL);
				
				if (NT_SUCCESS (Status))
				{
					Status = IoctlSetModem (pDeviceExt, pModem);

					// Now unlock the user memory
					pMdl = pIrp->MdlAddress;
					while (pMdl != NULL)
					{
						pNextMdl = pMdl->Next;
						MmUnlockPages (pMdl);
						IoFreeMdl (pMdl);
						pMdl = pNextMdl;
					}
					pIrp->MdlAddress = NULL;
				}
				else
				{
					Status = STATUS_UNSUCCESSFUL;
				}
			}
			else
			{
				pModem = (PDSMP_MODEM) pIrp->AssociatedIrp.SystemBuffer;
				Status = IoctlSetModem (pDeviceExt, pModem);
			}
		}
		else if (sizeof (DSMP_MODEM) != InputBufferLength)
		{
			Status = STATUS_INVALID_PARAMETER;
		}
		else
		{
			Status = STATUS_BUFFER_TOO_SMALL;
		}

		if (NT_SUCCESS (Status))
			pIrp->IoStatus.Information = sizeof (DSMP_MODEM);

		break;
	}

	case IOCTL_DSMP_GETSTATS:
	{
		PDSMP_STATS		pStats = NULL;

		if ((sizeof (DSMP_STATS) == InputBufferLength) &&
			(sizeof (DSMP_STATS) == OutputBufferLength))
		{
			//
			// Determine if the request is from user-mode.  If it is, lock
			// down the memory so the driver can access it freely.
			//
			if (pIrp->RequestorMode == UserMode)
			{
				Status = MapAndLockUserMemory (pIrpSp,
					pIrp,
					NULL,				// No pointer from user space
					0,					// No pointer from user space
					(PVOID)&pStats,
					NULL);
				
				if (NT_SUCCESS (Status))
				{
					Status = IoctlGetStats (pDeviceExt, pStats);

					// Now unlock the user memory
					pMdl = pIrp->MdlAddress;
					while (pMdl != NULL)
					{
						pNextMdl = pMdl->Next;
						MmUnlockPages (pMdl);
						IoFreeMdl (pMdl);
						pMdl = pNextMdl;
					}
					pIrp->MdlAddress = NULL;
				}
				else
				{
					Status = STATUS_UNSUCCESSFUL;
				}
			}
			else
			{
				pStats = (PDSMP_STATS) pIrp->AssociatedIrp.SystemBuffer;
				Status = IoctlGetStats (pDeviceExt, pStats);
			}
		}
		else if (sizeof (DSMP_STATS) != InputBufferLength)
		{
			Status = STATUS_INVALID_PARAMETER;
		}
		else
		{
			Status = STATUS_BUFFER_TOO_SMALL;
		}

		if (NT_SUCCESS (Status))
			pIrp->IoStatus.Information = sizeof (DSMP_STATS);

		break;
	}

	case IOCTL_DSMP_ZEROSTATS:
		Status = IoctlZeroStats (pDeviceExt);
		break;

	//
	//  The SNADIS IOCTLs start here
	//
	case IOCTLSETEVENTHANDLE:
		if (pIrp->UserEvent != NULL)
		{
			pDeviceExt->pUserEvent = pIrp->UserEvent;
			KeSetEvent (pDeviceExt->pUserEvent, 0, FALSE);
		}
		else
		{
			Status = STATUS_INVALID_PARAMETER;
			pIrp->IoStatus.Information = INFO_SET_EVENT_NO_EVENT;
		}
		break;

	case IOCTLSETLINK:
	{
		PLINKCHAR	pLinkChars = (PLINKCHAR) pIrp->AssociatedIrp.SystemBuffer;

		if (sizeof (LINKCHAR) == InputBufferLength)
		{
			Status = SnaIoctlSetLinkConfig (pDeviceExt, pLinkChars);

			if (!NT_SUCCESS (Status))
			{
				pIrp->IoStatus.Information = pDeviceExt->Information;
			}
		}
		else
		{
			Status = STATUS_INVALID_PARAMETER;
			pIrp->IoStatus.Information = INFO_LINKCHAR_BUF_WRONG_SIZE;
		}

		break;
	}

	case IOCTLGETV24STATUS:
		//  This updates the Modem signals in the Interface Record
		SnaIoctlGetV24Status (pDeviceExt);
		break;

	case IOCTLSETV24OUTPUTSTATUS:
	{
		pDeviceExt->IR.OutputV24Status = *(PUCHAR) pIrp->UserBuffer;
		SnaIoctlSetV24Status (pDeviceExt);
		break;
	}

	case IOCTLRXFRAME:
	{
		DSMP_GETFRAME		Frame;

		RtlZeroMemory (&Frame, sizeof (Frame));
		Frame.DataMaxLength = OutputBufferLength;

		if (OutputBufferLength >= sizeof (USHORT))
		{
			//
			// Determine if the request is from user-mode.  If it is, lock down
			// the memory so the driver can access it freely.
			//
			Frame.pDataBuffer = MmGetSystemAddressForMdl (pIrp->MdlAddress);
			Status = IoctlGetFrame (pDeviceExt, &Frame);

			if (NT_SUCCESS (Status))
				pIrp->IoStatus.Information = Frame.DataLength;
		}
		else
		{
			Status = STATUS_BUFFER_TOO_SMALL;
		}

		break;
	}

	case IOCTLTXFRAME:
	{
		DSMP_PUTFRAME	Frame;

		if (InputBufferLength < sizeof (USHORT))
		{
			Status = STATUS_BUFFER_TOO_SMALL;
			pDeviceExt->Information = INFO_TX_FRAME_TOO_SMALL;
		}
		else if (InputBufferLength > pDeviceExt->Config.MaxFrameSize)
		{
			Status = STATUS_INVALID_PARAMETER;
			pDeviceExt->Information = INFO_TX_FRAME_TOO_BIG;
		}
		else
		{
			RtlZeroMemory (&Frame, sizeof (Frame));
			Frame.DataLength = InputBufferLength;

			Frame.pDataBuffer = (PUCHAR) pIrp->AssociatedIrp.SystemBuffer;
			Status = IoctlPutFrame (pDeviceExt, &Frame);
		}

		break;
	}

	case IOCTLABORTRX:
		SnaIoctlAbortRx (pDeviceExt);
		break;

	case IOCTLABORTTX:
		SnaIoctlAbortTx (pDeviceExt);
		break;

	case IOCTLGETINTERFACERECORD:
		if (sizeof (IR) == OutputBufferLength)
		{
			DigiDump (DIGIVERYLOUD, (
				"GetIR():  IR.RxFrames 0x%x  IR.OutputV24 0x%x  IR.InputV24 0x%x\n",
				pDeviceExt->IR.ReceivedFrames, pDeviceExt->IR.OutputV24Status,
				pDeviceExt->IR.InputV24Status));

			SnaIoctlGetIR (pDeviceExt, pIrp);
		}
		else
		{
			Status = STATUS_INVALID_PARAMETER;
			pIrp->IoStatus.Information = INFO_READ_IR_BUFFER_WRONG_SIZE;
		}
		break;

	case DEBUG_GETBOARDMEM:
	{
		PDSMP_GETFRAME	pFrame;

		//
		// Determine if the request is from user-mode.  If it is, lock
		// down the memory so the driver can access it freely.
		//
		if (pIrp->RequestorMode == UserMode)
		{
			pFrame = (PDSMP_GETFRAME) pIrp->AssociatedIrp.SystemBuffer;
			Status = MapAndLockUserMemory (pIrpSp,
				pIrp,
				pFrame->pUserDataBuffer,
				pFrame->DataMaxLength,
				(PVOID)&pFrame,
				(PVOID)&pFrame->pDataBuffer);

			if (NT_SUCCESS (Status))
			{
				IoctlCopyCardMemory (pDeviceExt, pFrame);

				// Now unlock the user memory
				pMdl = pIrp->MdlAddress;
				while (pMdl != NULL)
				{
					pNextMdl = pMdl->Next;
					MmUnlockPages (pMdl);
					IoFreeMdl (pMdl);
					pMdl = pNextMdl;
				}
				pIrp->MdlAddress = NULL;
			}
			else
			{
				Status = STATUS_UNSUCCESSFUL;
			}
		}
		else
		{
			pFrame = (PDSMP_GETFRAME) pIrp->AssociatedIrp.SystemBuffer;
			IoctlCopyCardMemory (pDeviceExt, pFrame);
		}

		if (NT_SUCCESS (Status))
			pIrp->IoStatus.Information = pFrame->DataLength;

		break;
	}

	default:
		DigiDump (DIGIERRORS, ("*** Bad IO Control Code (0x%x)\n",
			pIrpSp->Parameters.DeviceIoControl.IoControlCode));

		Status = STATUS_INVALID_DEVICE_REQUEST;
		break;
	}

	KeReleaseSpinLock (&pDeviceExt->pControllerExt->MemoryLock, OldIrqL);

	pIrp->IoStatus.Status = Status;

	KeRaiseIrql (DISPATCH_LEVEL, &OldIrqL);
	IoCompleteRequest (pIrp, 0);
	KeLowerIrql (OldIrqL);

	DigiDump (DIGIFLOW, ("Exiting fnDevIoctl()\n"));

	return (Status);
}


NTSTATUS
MapAndLockUserMemory (
	IN PIO_STACK_LOCATION	pIrpSp,
	IN PIRP					pIrp,
	IN PUCHAR				pUserDataBuffer,
	IN ULONG				UserDataMapSize,
	OUT PVOID				*pMappedSystemBuffer,
	OUT PVOID				*pMappedUserData)
/*++

Description:

	This function takes the input IRP, and maps in the user memory used
	for the input buffer, and also for the memory pointed to in the input
	buffer.

Parameters:

	pAdapter
		Address of the Adapter object.

	pEntry
		Address of the object to be inserted into the adapter context queue.

Return Value:

	None.

--*/
{
	NTSTATUS	Status = STATUS_SUCCESS;


	DigiDump (DIGIFLOW, ("Entering MapAndLockUserMemory()\n"));

	__try
	{
		PMDL			pMdl;

		// Lock down the user mode memory (struct) before accessing it
		pMdl = IoAllocateMdl (pIrp->AssociatedIrp.SystemBuffer,
			pIrpSp->Parameters.DeviceIoControl.InputBufferLength,
			FALSE,
			TRUE,
			pIrp);

		MmProbeAndLockPages (pMdl,
			KernelMode,
			IoModifyAccess);

		*pMappedSystemBuffer = MmGetSystemAddressForMdl (pMdl);

		//  If there is a pointer from user space
		if (UserDataMapSize)
		{
			// Lock and probe the 1st (and only) level of indirection
			pMdl = IoAllocateMdl (pUserDataBuffer,
				UserDataMapSize,
				TRUE,	//  Chain to the Irp
				TRUE,
				pIrp);

			MmProbeAndLockPages (pMdl,
				KernelMode,
				IoModifyAccess);

			*pMappedUserData = MmGetSystemAddressForMdl (pMdl);
		}

	}
	__except (Status = GetExceptionCode (), EXCEPTION_EXECUTE_HANDLER)
	{
		//
		// Unable to access user mode memory.  Bail!
		//
		DigiDump (DIGIERRORS, ("*** Failed to lock user memory (0x%x)\n",
			Status));

		while (pIrp->MdlAddress)
		{
			// Cleanup allocated MDLs
			PMDL	pNextMdl = pIrp->MdlAddress->Next;
			MmUnlockPages (pIrp->MdlAddress);
			IoFreeMdl (pIrp->MdlAddress);
			pIrp->MdlAddress = pNextMdl;
		}
	}


	DigiDump (DIGIFLOW, ("Exiting MapAndLockUserMemory()\n"));

	return (Status);
}

#endif


VOID
fnUnloadController (
	IN PCONTROLLER_EXT	pControllerExt,
	IN PDRIVER_OBJECT	pDriverObject)
/*++

Description:

	This will be called when NT wants to Uload a Controller.  This routine
	will perform the following:

		1.	Release the resources allocated at init time
		2.	Delete the Device objects and the Controller objects for each card.
		3.	Unmap the I/O and Memory resources

Parameters:

	pDeviceObject
		Address of the Driver Object.

Return Value:

	None.

--*/
{
	PCONTROLLER_OBJECT	pControllerObj;
	PDEVICE_EXT			pDeviceExt;
	UCHAR				i;

	DigiDump (DIGIFLOW, ("Entering fnUnloadController() %08x\n",pControllerExt));
	
	//  Disable interrupts from the adapter
	DisableInterrupt (pControllerExt);

	//  Disconnect from the interrupt vector
	if (pControllerExt->InterruptObject)
	{
		IoDisconnectInterrupt (pControllerExt->InterruptObject);
		pControllerExt->InterruptObject = NULL;
	}

	//  Make sure the modem signals are cleared
	for (i = 0; i < pControllerExt->AdapterLines; i++)
	{
		if (pControllerExt->pDeviceExt[i]) {
			fnDropModemSignals (pControllerExt->pDeviceExt[i]);
		}
	}

	if (PCIBus != pControllerExt->BusType)
	{
		//  Disable the card
		IO_OUT (pControllerExt->VirtualIO + REG570_INT_SEL, 0);

		//  Reset the Memory select register
		IO_OUT (pControllerExt->VirtualIO + REG570_MEM_SEL, 0);
	}
#if !NT5_BUILD

	//  Delete the Symbolic links for each Device Object
	for (i = 0; i < pControllerExt->AdapterLines; i++)
	{
		pDeviceExt = pControllerExt->pDeviceExt[i];
		if (pDeviceExt) {
			if (pDeviceExt->SymbolicLinkNameCreated) {
				IoDeleteSymbolicLink (&pDeviceExt->SymbolicLinkName);
			}
		}
	}
#endif

#if NT5_BUILD

	//  Delete all Device Objects (1 per port)
	for (i = 0; i < pControllerExt->AdapterLines; i++)
	{
		if (pControllerExt->pDeviceExt[i]) {
			DigiFreeMem(pControllerExt->pDeviceExt[i]);
		}
	}

#else

		//  Delete all Device Objects (1 per port)
	for (i = 0; i < pControllerExt->AdapterLines; i++)
	{
		if (pControllerExt->pDeviceObject[i]) {
			IoDeleteDevice (pControllerExt->pDeviceObject[i]);
		}
	}

#endif

	//  Unmap the I/O space
	if (pControllerExt->UnMapVirtualIO)
	{
		MmUnmapIoSpace (pControllerExt->VirtualIO,
			pControllerExt->IOSpan);
		pControllerExt->UnMapVirtualIO = FALSE;
	}

	//  Unmap the Memory space
	if (pControllerExt->UnMapVirtualWindow)
	{
		MmUnmapIoSpace (pControllerExt->VirtualWindow,
			pControllerExt->WindowSize);
		pControllerExt->UnMapVirtualWindow = FALSE;
	}

	//  Unmap the PLX I/O space
	if (pControllerExt->UnMapVirtualPlxRegisters)
	{
		MmUnmapIoSpace (pControllerExt->VirtualPlxRegisters,
			pControllerExt->PlxRegistersSpan);
		pControllerExt->UnMapVirtualPlxRegisters = FALSE;
	}

#if NT5_BUILD

	DigiFreeMem(pControllerExt);

#else
	
	pControllerObj = pControllerExt->pControllerObj;

	//  Release Device Resources
	if (pControllerExt->hReportedResources) {
		DigiUnreportResources (pDriverObject,	pControllerExt->hReportedResources);
	}

	//  Free the controller object
	IoFreeController (pControllerObj);

#endif

	DigiDump (DIGIFLOW, ("Exiting fnUnloadController()\n"));
}

