/*++
*******************************************************************************
* Copyright (c) 1990-98 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				*
*																										*
*******************************************************************************

Module Name:

   digimem.c

Abstract:

   Memory mapping and locking routines.  Provide for mapping physical adapter memory 
   and locking down pageable memory.

Author:

   Michael Zarns

--*/

#include "digilib.h"


NTSTATUS
DigiMapAddress( 
	OUT PVOID            *ppVirtualAddr,
	OUT PBOOLEAN         pfUnmapAddr,
	IN INTERFACE_TYPE    BusType,
	IN ULONG             BusNumber,
	IN PHYSICAL_ADDRESS  PhysAddr,
	IN ULONG             Length,
	IN ULONG             AddrSpace )
/*++

Routine Description:

    This routine maps an IO address to system address space.

Arguments:

	ppVirtualAddr - pointer to output virtual address, if successful
	pfUnmapAddr   - output flag to unmap address, if successful
	BusType       - what type of bus - eisa, mca, isa
	IoBusNumber   - which IO bus (for machines with multiple buses).
	PhysAddr      - base device address to be mapped.
	Length        - number of bytes for which address is valid.
	AddrSpace     - Denotes whether the address is in io space or memory.

Return Value:

    STATUS_SUCCESS if successful.

--*/
{
	PHYSICAL_ADDRESS TranslatedAddr;
	PVOID pVirtual;
	BOOLEAN fUnmapAddr;
	BOOLEAN fSuccess;
	NTSTATUS Status;

	// NOTE: AddrSpace may be modified by this call!
	fSuccess = HalTranslateBusAddress( BusType, BusNumber, PhysAddr, 
	                                   &AddrSpace, &TranslatedAddr );

	if ( !fSuccess )
	{
		Status = STATUS_UNSUCCESSFUL;
		goto Exit;
	}

	if( AddrSpace == 0 )
	{
		// Address is in memory space and may be translated.  Get the virtual addr.
		ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );
		pVirtual = MmMapIoSpace( TranslatedAddr, Length, FALSE );
		fUnmapAddr = pVirtual ? TRUE : FALSE;
	}
	else
	{
#ifdef _M_IA64
		pVirtual = (PVOID)TranslatedAddr.QuadPart;
#else
		pVirtual = (PVOID)TranslatedAddr.LowPart;
#endif
		fUnmapAddr = FALSE;
	}

	Status = STATUS_SUCCESS;
	*ppVirtualAddr = pVirtual;
	*pfUnmapAddr = fUnmapAddr;

Exit:
	return Status;
}


VOID
DigiUnmapAddress(
	IN PVOID pVirtualAddress, 
	IN ULONG Length )
/*++

Description:

	This routine unmaps an Io address.

Parameters:

	pVirtualAddress - the address to unmap
	Length          - length in bytes of the mapped memory region

Return Value:

	None

--*/
{
	ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );
	if ( pVirtualAddress != NULL && Length != 0 )
		MmUnmapIoSpace( pVirtualAddress, Length );
}


VOID 
DigiUnlockBuffers( 
	IN PIRP pIrp )
/*++

Description:

	Unlock all previously locked-down memory buffers attached to this Irp.  Walk the
	Irp's MDL list unlocking buffers and releasing MDL resources associated with them.
	
Parameters:

	pIrp
		The Irp contains a single-linked list of MDLs (via MdlAddress) which will be 
		released.

Return Value:

	None

See Also:

	DigiLockBuffer

--*/
{
   PMDL pNext;

   ASSERT( pIrp );
   while ( pIrp->MdlAddress )
   {
      // Cleanup alloc'd MDLs
      pNext = pIrp->MdlAddress->Next;
      MmUnlockPages( pIrp->MdlAddress );
      IoFreeMdl( pIrp->MdlAddress );
      pIrp->MdlAddress = pNext;
   }
}

NTSTATUS
DigiLockBuffer( 
   IN PIRP   pIrp, 
   IN PVOID  pBuffer, 
   IN ULONG  cbBuffer, 
   OUT PVOID *ppLockedBuffer )
/*++

Description:

	Lock down the physical pages of memory mapped by the virtual address range in 
	memory.  Allows access to usermode memory from kernelmode in an arbitrary context
	and at DISPATCH_LEVEL (which otherwise is not possible).

	Locked down memory *must* be released using DigiUnlockBuffers.

	Must be called at Irql < DISPATCH_LEVEL for pageable addresses.

Parameters:

	pIrp
		The Irp which owns the buffer.  
	pBuffer
	cbBuffer
	ppLockedBuffer

Return Value:

	STATUS_SUCCESS if locked successfully, or error status.

--*/
{
   NTSTATUS Status = STATUS_SUCCESS;

	ASSERT( pIrp != NULL );
	ASSERT( ppLockedBuffer != NULL );
	ASSERT( ( pBuffer == NULL ) == ( cbBuffer == 0 ) );

	if( pIrp->RequestorMode == UserMode )
	{
		__try 
		{
			if ( pBuffer )
			{
				PMDL pMdl = IoAllocateMdl( pBuffer, cbBuffer, (BOOLEAN)(pIrp->MdlAddress != NULL), 
				                           TRUE, pIrp );
				if ( pMdl == NULL )
					Status = STATUS_INSUFFICIENT_RESOURCES;
				else
				{
			      MmProbeAndLockPages( pMdl, KernelMode, IoModifyAccess );
#if NT5_BUILD
					// NT50 nicely returns null if this call fails.
					*ppLockedBuffer = MmGetSystemAddressForMdlSafe( pMdl, NormalPagePriority );
					if ( *ppLockedBuffer == NULL )
					{
                  // In this case, unlock all buffers
						DigiUnlockBuffers( pIrp );
						Status = STATUS_UNSUCCESSFUL;
					}
#else
					// NT40 happily bugchecks if this call fails.
					*ppLockedBuffer = MmGetSystemAddressForMdl( pMdl );
#endif
				}
			}
		} 
		__except( Status = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER )  
		{
#if NT5_BUILD
         // Only one way to get here for W2K, MmProbeAndLockPages() threw an exception
         // That means the last MDL got alloc'd, but the buffer didn't get locked
         // Free all of the MDLs, but don't unlock the last memory buffer
         PMDL pNext;
         while ( pIrp->MdlAddress )
         {
            // Last buffer?
            if (NULL != (pNext = pIrp->MdlAddress->Next))
               MmUnlockPages( pIrp->MdlAddress ); // Unlock all but the last...
            IoFreeMdl( pIrp->MdlAddress ); // Always free
            pIrp->MdlAddress = pNext;
         }
#else // #if NT5_BUILD
			DigiUnlockBuffers( pIrp );
#endif // #if NT5_BUILD
         DigiDump( DEBUG_ERROR, ( "DigiLockBuffer: failed to lock user memory, status 0x%x ***\n", Status ) );
			Status = STATUS_UNSUCCESSFUL;
		}
	}
	else
		// Kernelmode buffers are assumed to already be locked down!
		*ppLockedBuffer = pBuffer;

	return Status;
}
