/*

Copyright (c) 1992  Microsoft Corporation

Module Name:

	cachemdl.c

Abstract:

	This module contains the routines for to get Mdl for Reads and Writes
    directly from the Cache Mgr, which helps avoid one data copy and reduces
    our non-paged memory consumption (significantly!)

Author:

	Shirish Koti


Revision History:
	June 12, 1998		Initial Version

Notes:	Tab stop: 4
--*/

#define	FILENUM	FILE_CACHEMDL

#include <afp.h>
#include <forkio.h>
#include <gendisp.h>

VOID FASTCALL
AfpAllocWriteMdl(
    IN PDELAYEDALLOC    pDelAlloc
)
{
	PREQUEST        pRequest;
    POPENFORKENTRY  pOpenForkEntry;
    NTSTATUS        status=STATUS_SUCCESS;


    ASSERT(KeGetCurrentIrql() == LOW_LEVEL);
    ASSERT(VALID_SDA(pDelAlloc->pSda));
    ASSERT(pDelAlloc->BufSize >= CACHEMGR_WRITE_THRESHOLD);

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_WRITE_MDL);

    pRequest = pDelAlloc->pRequest;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;

    ASSERT((VALID_OPENFORKENTRY(pOpenForkEntry)) || (pOpenForkEntry == NULL));

    // assume for now that cache mgr will fail to return the mdl
    status = STATUS_UNSUCCESSFUL;
    pRequest->rq_WriteMdl = NULL;

    if (pOpenForkEntry)
    {
        status = AfpBorrowWriteMdlFromCM(pDelAlloc, &pRequest->rq_WriteMdl);
    }

    if (status != STATUS_PENDING)
    {
        AfpAllocWriteMdlCompletion(NULL, NULL, pDelAlloc);
    }
}


NTSTATUS FASTCALL
AfpBorrowWriteMdlFromCM(
    IN  PDELAYEDALLOC   pDelAlloc,
    OUT PMDL           *ppReturnMdl
)
{

    IO_STATUS_BLOCK     IoStsBlk;
    PIRP                pIrp;
    PIO_STACK_LOCATION  pIrpSp;
    PFAST_IO_DISPATCH   pFastIoDisp;
    LARGE_INTEGER       LargeOffset;
    BOOLEAN             fGetMdlWorked;
	PSDA	            pSda;
    POPENFORKENTRY      pOpenForkEntry;
    PFILE_OBJECT        pFileObject;



    pSda = pDelAlloc->pSda;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;

    ASSERT(VALID_SDA(pSda));
    ASSERT(VALID_OPENFORKENTRY(pOpenForkEntry));

    pFastIoDisp = pOpenForkEntry->ofe_pDeviceObject->DriverObject->FastIoDispatch;

    pFileObject = AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject);

    ASSERT(pFileObject->Flags & FO_CACHE_SUPPORTED);

    ASSERT(pFastIoDisp->PrepareMdlWrite != NULL);

    LargeOffset = pDelAlloc->Offset;

    fGetMdlWorked = pFastIoDisp->PrepareMdlWrite(
                            pFileObject,
                            &LargeOffset,
                            pDelAlloc->BufSize,      // how big is the Write
                            pSda->sda_SessionId,
                            ppReturnMdl,
                            &IoStsBlk,
                            pOpenForkEntry->ofe_pDeviceObject);

    if (fGetMdlWorked && (*ppReturnMdl != NULL))
    {
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_INFO,
	        ("AfpBorrowWriteMdlFromCM: fast path workd, Mdl = %lx\n",*ppReturnMdl));

        pDelAlloc->pMdl = *ppReturnMdl;

        return(STATUS_SUCCESS);
    }


    //
    // fast path didn't work (or worked only partially).  We must give an irp down
    // to get the (rest of the) mdl
    //

	// Allocate and initialize the IRP for this operation.
	pIrp = AfpAllocIrp(pOpenForkEntry->ofe_pDeviceObject->StackSize);

    // yikes, how messy can it get!
	if (pIrp == NULL)
	{
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpBorrowWriteMdlFromCM: irp alloc failed!\n"));

        // if cache mgr returned a partial mdl, give it back!
        if (*ppReturnMdl)
        {
	        DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	            ("AfpBorrowWriteMdlFromCM: giving back partial Mdl\n"));

            pDelAlloc->pMdl = *ppReturnMdl;
            pDelAlloc->Flags |= AFP_CACHEMDL_ALLOC_ERROR;

            pDelAlloc->pRequest->rq_CacheMgrContext = NULL;

            AfpReturnWriteMdlToCM(pDelAlloc);
        }
        return(STATUS_INSUFFICIENT_RESOURCES);
	}

	// Set up the completion routine.
	IoSetCompletionRoutine(
            pIrp,
			(PIO_COMPLETION_ROUTINE)AfpAllocWriteMdlCompletion,
			pDelAlloc,
			True,
			True,
			True);

	pIrpSp = IoGetNextIrpStackLocation(pIrp);

	pIrp->Tail.Overlay.OriginalFileObject =
                        AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject);
	pIrp->Tail.Overlay.Thread = AfpThread;
	pIrp->RequestorMode = KernelMode;

    pIrp->Flags = IRP_SYNCHRONOUS_API;

	pIrpSp->MajorFunction = IRP_MJ_WRITE;
	pIrpSp->MinorFunction = IRP_MN_MDL;
	pIrpSp->FileObject = AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject);
	pIrpSp->DeviceObject = pOpenForkEntry->ofe_pDeviceObject;

	pIrpSp->Parameters.Write.Length = pDelAlloc->BufSize;
	pIrpSp->Parameters.Write.Key = pSda->sda_SessionId;
	pIrpSp->Parameters.Write.ByteOffset = LargeOffset;

    //
    // *ppReturnMdl could potentially be non-null if the fast-path returned
    // a partial mdl
    //
    pIrp->MdlAddress = *ppReturnMdl;

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_REQUESTED);
    AFP_DBG_SET_DELALLOC_IRP(pDelAlloc,pIrp);

	IoCallDriver(pOpenForkEntry->ofe_pDeviceObject, pIrp);

    return(STATUS_PENDING);
}



NTSTATUS
AfpAllocWriteMdlCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           pIrp,
    IN PVOID          Context
)
{
	PSDA	            pSda;
	PBYTE	            pBuf;
	PREQUEST            pRequest;
    PDELAYEDALLOC       pDelAlloc;
    PMDL                pMdl=NULL;
    NTSTATUS            status=STATUS_SUCCESS;
    POPENFORKENTRY      pOpenForkEntry;


    pDelAlloc = (PDELAYEDALLOC)Context;

    pSda = pDelAlloc->pSda;
    pRequest = pDelAlloc->pRequest;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;


    ASSERT(VALID_SDA(pSda));
    ASSERT(pDelAlloc->BufSize >= CACHEMGR_WRITE_THRESHOLD);
    ASSERT((VALID_OPENFORKENTRY(pOpenForkEntry)) || (pOpenForkEntry == NULL));

    if (pIrp)
    {
        status = pIrp->IoStatus.Status;

        //
        // mark the fact that this mdl belongs to the cache mgr
        //
        if (NT_SUCCESS(status))
        {
            pRequest->rq_WriteMdl = pIrp->MdlAddress;
            ASSERT(pRequest->rq_WriteMdl != NULL);

            pDelAlloc->pMdl = pRequest->rq_WriteMdl;
        }
        else
        {
	        DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	            ("AfpAllocWriteMdlCompletion: irp %lx failed %lx\n",pIrp,status));

            ASSERT(pRequest->rq_WriteMdl == NULL);
            pRequest->rq_WriteMdl = NULL;
        }

        AfpFreeIrp(pIrp);

        AFP_DBG_SET_DELALLOC_IRP(pDelAlloc, NULL);
    }


    //
    // if we didn't get Mdl from cache mgr, fall back to the old, traditional
    // way of allocating!
    //
    if (pRequest->rq_WriteMdl == NULL)
    {
	    pBuf = AfpIOAllocBuffer(pDelAlloc->BufSize);

	    if (pBuf != NULL)
	    {
		    pMdl = AfpAllocMdl(pBuf, pDelAlloc->BufSize, NULL);
		    if (pMdl == NULL)
		    {
			    AfpIOFreeBuffer(pBuf);
		    }
	    }

        pRequest->rq_WriteMdl = pMdl;

        //
        // for whatever reason, we didn't get Mdl from cache mgr.  Undo the
        // things we had done in preparation (NOTE: if we do get the Mdl from
        // cache mgr, we leave the refcount etc. in tact until the Mdl is actually
        // returned to cache mgr)
        //

        pRequest->rq_CacheMgrContext = NULL;

        // make sure we aren't forgetting cache mgr's mdl
        ASSERT(pDelAlloc->pMdl == NULL);

        // don't need that memory no more
        AfpFreeDelAlloc(pDelAlloc);

        AfpSdaDereferenceSession(pSda);

        if (pOpenForkEntry)
        {
            AfpForkDereference(pOpenForkEntry);
        }
    }
    else
    {
        AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_IN_USE);
        AFP_DBG_INC_DELALLOC_BYTECOUNT(AfpWriteCMAlloced, pDelAlloc->BufSize);
    }

    //
    // tell the transport below to continue with the write
    //
    (*(pSda->sda_XportTable->asp_WriteContinue))(pRequest);

    return(STATUS_MORE_PROCESSING_REQUIRED);
}




VOID FASTCALL
AfpReturnWriteMdlToCM(
    IN  PDELAYEDALLOC   pDelAlloc
)
{
    PDEVICE_OBJECT      pDeviceObject;
    PFAST_IO_DISPATCH   pFastIoDisp;
    PIRP                pIrp;
    PIO_STACK_LOCATION  pIrpSp;
    LARGE_INTEGER       LargeOffset;
	PFILE_OBJECT        pFileObject;
    PSDA                pSda;
    POPENFORKENTRY      pOpenForkEntry;
    PMDL                pMdl;
    PVOID               Context;


    ASSERT(pDelAlloc != NULL);
    ASSERT(pDelAlloc->pMdl != NULL);

    //
    // are we at DPC? if so, can't do this now
    //
    if (KeGetCurrentIrql() == DISPATCH_LEVEL)
    {
        AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_PROC_QUEUED);

        AfpInitializeWorkItem(&pDelAlloc->WorkItem,
                              AfpReturnWriteMdlToCM,
                              pDelAlloc);

        AfpQueueWorkItem(&pDelAlloc->WorkItem);
        return;
    }

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_PROC_IN_PROGRESS);

    pSda = pDelAlloc->pSda;
    pMdl = pDelAlloc->pMdl;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;

    ASSERT(VALID_SDA(pSda));
    ASSERT(VALID_OPENFORKENTRY(pOpenForkEntry));

    pFileObject = AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject),
    pDeviceObject = pOpenForkEntry->ofe_pDeviceObject;

    LargeOffset = pDelAlloc->Offset;

    pFastIoDisp = pDeviceObject->DriverObject->FastIoDispatch;

    Context = pDelAlloc;

    //
    // if we came here because the cache mdl alloc failed but had partially
    // succeeded, then we don't want the completion routine to free up things
    // prematurely: in this case, pass NULL context
    //
    if (pDelAlloc->Flags & AFP_CACHEMDL_ALLOC_ERROR)
    {
        Context = NULL;
    }

    if (pFastIoDisp->MdlWriteComplete)
    {
        if (pFastIoDisp->MdlWriteComplete(
                pFileObject,
                &LargeOffset,
                pMdl,
                pDeviceObject) == TRUE)
        {
            AfpReturnWriteMdlToCMCompletion(NULL, NULL, Context);
            return;
        }
    }


	// Allocate and initialize the IRP for this operation.
	pIrp = AfpAllocIrp(pDeviceObject->StackSize);

    // yikes, how messy can it get!
	if (pIrp == NULL)
	{
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpReturnWriteMdlToCM: irp alloc failed!\n"));

        // log an event here - that's all we can do here!
        AFPLOG_ERROR(AFPSRVMSG_ALLOC_IRP, STATUS_INSUFFICIENT_RESOURCES,
						                     NULL, 0, NULL);
    
		AfpReturnWriteMdlToCMCompletion(NULL, NULL, Context);

        ASSERT(0);
        return;
	}

	// Set up the completion routine.
	IoSetCompletionRoutine(
            pIrp,
			(PIO_COMPLETION_ROUTINE)AfpReturnWriteMdlToCMCompletion,
			Context,
			True,
			True,
			True);

	pIrpSp = IoGetNextIrpStackLocation(pIrp);

	pIrp->Tail.Overlay.OriginalFileObject = AfpGetRealFileObject(pFileObject);
	pIrp->Tail.Overlay.Thread = AfpThread;
	pIrp->RequestorMode = KernelMode;

    pIrp->Flags = IRP_SYNCHRONOUS_API;

	pIrpSp->MajorFunction = IRP_MJ_WRITE;
	pIrpSp->MinorFunction = IRP_MN_MDL | IRP_MN_COMPLETE;
	pIrpSp->FileObject = AfpGetRealFileObject(pFileObject);
	pIrpSp->DeviceObject = pDeviceObject;

	pIrpSp->Parameters.Write.Length = pDelAlloc->BufSize;

	pIrpSp->Parameters.Write.ByteOffset = LargeOffset;

    pIrp->MdlAddress = pMdl;

    AFP_DBG_SET_DELALLOC_IRP(pDelAlloc, pIrp);
    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_RETURN_IN_PROGRESS);

	IoCallDriver(pDeviceObject, pIrp);

}


NTSTATUS
AfpReturnWriteMdlToCMCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           pIrp,
    IN PVOID          Context
)
{
    PSDA            pSda;
    PDELAYEDALLOC   pDelAlloc;
    POPENFORKENTRY  pOpenForkEntry;
    NTSTATUS        status;
    AFPSTATUS       AfpStatus=AFP_ERR_NONE;

	struct _ResponsePacket
	{
		BYTE	__RealOffset[4];
	};


    pDelAlloc = (PDELAYEDALLOC)Context;

    if (pIrp)
    {
        status = pIrp->IoStatus.Status;

        //
        // mark the fact that this mdl belongs to the cache mgr
        //
        if (NT_SUCCESS(status))
        {

            AfpStatus = AFP_ERR_NONE;
        }
        else
        {
	        DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	            ("AfpReturnWriteMdlToCMCompletion: irp failed %lx\n",status));

            ASSERT(0);
            AfpStatus = AFP_ERR_MISC;
        }

        AfpFreeIrp(pIrp);
    }

    //
    // if pDelAlloc is NULL, then some error occured while borrowing CM's mdl.  We
    // We already finished up with the API at the time of the failure, so done here
    //
    if (pDelAlloc == NULL)
    {
        return(STATUS_MORE_PROCESSING_REQUIRED);
    }


    pSda = pDelAlloc->pSda;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;

	if (AfpStatus == AFP_ERR_NONE)
	{
	    pSda->sda_ReplySize = SIZE_RESPPKT;
	    if ((AfpStatus = AfpAllocReplyBuf(pSda)) == AFP_ERR_NONE)
	    {
		    PUTDWORD2DWORD(pRspPkt->__RealOffset,
                           (pDelAlloc->Offset.LowPart + pDelAlloc->BufSize));
	    }
	}
    else
    {
        pSda->sda_ReplySize = 0;
    }

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_RETURN_COMPLETED);
    AFP_DBG_DEC_DELALLOC_BYTECOUNT(AfpWriteCMAlloced, pDelAlloc->BufSize);

    //
    // call the completion routine only if everything is ok (we don't want
    // to call completion if session went dead)
    //
    if (!(pDelAlloc->Flags & AFP_CACHEMDL_DEADSESSION))
    {
        AfpCompleteApiProcessing(pSda, AfpStatus);
    }

    // remove the refcount when we referenced this
    AfpForkDereference(pOpenForkEntry);

    // remove the DelAlloc refcount
    AfpSdaDereferenceSession(pSda);

    // don't need that memory no more
    AfpFreeDelAlloc(pDelAlloc);

    return(STATUS_MORE_PROCESSING_REQUIRED);

}



NTSTATUS FASTCALL
AfpBorrowReadMdlFromCM(
    IN PSDA             pSda
)
{

    IO_STATUS_BLOCK     IoStsBlk;
    PIRP                pIrp;
    PIO_STACK_LOCATION  pIrpSp;
    PFAST_IO_DISPATCH   pFastIoDisp;
    PMDL                pReturnMdl=NULL;
    KIRQL               OldIrql;
    PREQUEST            pRequest;
    PDELAYEDALLOC       pDelAlloc;
    POPENFORKENTRY      pOpenForkEntry;
    PFILE_OBJECT        pFileObject;
    LARGE_INTEGER       Offset;
    LARGE_INTEGER       ReadSize;
    BOOLEAN             fGetMdlWorked;
	struct _RequestPacket
	{
		POPENFORKENTRY	_pOpenForkEntry;
		LONG			_Offset;
		LONG			_Size;
		DWORD			_NlMask;
		DWORD			_NlChar;
	};


    ASSERT(VALID_SDA(pSda));

	Offset.QuadPart = pReqPkt->_Offset;
	ReadSize.QuadPart = pReqPkt->_Size;

    pOpenForkEntry = pReqPkt->_pOpenForkEntry;

    pFileObject = AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject);

    ASSERT(VALID_OPENFORKENTRY(pOpenForkEntry));

    pFastIoDisp = pOpenForkEntry->ofe_pDeviceObject->DriverObject->FastIoDispatch;

    if (!(pFileObject->Flags & FO_CACHE_SUPPORTED))
    {
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpBorrowReadMdlFromCM: FO_CACHE_SUPPORTED not set\n"));

        return(STATUS_UNSUCCESSFUL);
    }

    if (pFastIoDisp->MdlRead == NULL)
    {
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpBorrowReadMdlFromCM: PrepareMdl is NULL\n"));

        return(STATUS_UNSUCCESSFUL);
    }

    pDelAlloc = AfpAllocDelAlloc();

    if (pDelAlloc == NULL)
    {
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpBorrowReadMdlFromCM: malloc for pDelAlloc failed\n"));

        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_READ_MDL);

    // put DelAlloc refcount on pSda
    if (AfpSdaReferenceSessionByPointer(pSda) == NULL)
    {
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpBorrowReadMdlFromCM: couldn't reference pSda %lx\n",pSda));

        AfpFreeDelAlloc(pDelAlloc);
        return(STATUS_UNSUCCESSFUL);
    }

    // put DelAlloc refcount on pOpenForkEntry
    if (AfpForkReferenceByPointer(pOpenForkEntry) == NULL)
    {
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpBorrowReadMdlFromCM: couldn't reference %lx\n",pOpenForkEntry));

        // remove DelAlloc refcount
        AfpSdaDereferenceSession(pSda);
        AfpFreeDelAlloc(pDelAlloc);
        return(STATUS_UNSUCCESSFUL);
    }

    pRequest = pSda->sda_Request;

    ASSERT(pRequest->rq_ReplyMdl == NULL);

    pRequest->rq_CacheMgrContext = pDelAlloc;

    pDelAlloc->pSda = pSda;
    pDelAlloc->pRequest = pRequest;
    pDelAlloc->pOpenForkEntry = pOpenForkEntry;
    pDelAlloc->Offset = Offset;
    pDelAlloc->BufSize = ReadSize.LowPart;

    fGetMdlWorked = pFastIoDisp->MdlRead(
                            pFileObject,
                            &Offset,
                            ReadSize.LowPart,
                            pSda->sda_SessionId,
                            &pReturnMdl,
                            &IoStsBlk,
                            pOpenForkEntry->ofe_pDeviceObject);

    if (fGetMdlWorked && (pReturnMdl != NULL))
    {
        pDelAlloc->pMdl = pReturnMdl;

        // call the completion routine, so the read can complete
        AfpBorrowReadMdlFromCMCompletion(NULL, NULL, pDelAlloc);

        return(STATUS_PENDING);
    }


    //
    // fast path didn't work (or worked only partially).  We must give an irp down
    // to get the (rest of the) mdl
    //

	// Allocate and initialize the IRP for this operation.
	pIrp = AfpAllocIrp(pOpenForkEntry->ofe_pDeviceObject->StackSize);

    // yikes, how messy can it get!
	if (pIrp == NULL)
	{
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpBorrowReadMdlFromCM: irp alloc failed!\n"));

        // if cache mgr returned a partial mdl, give it back!
        if (pReturnMdl)
        {
	        DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	            ("AfpBorrowReadMdlFromCM: giving back partial Mdl\n"));

            pDelAlloc->pMdl = pReturnMdl;
            pRequest->rq_CacheMgrContext = NULL;

            AfpReturnReadMdlToCM(pDelAlloc);
        }
        return(STATUS_INSUFFICIENT_RESOURCES);
	}

	// Set up the completion routine.
	IoSetCompletionRoutine(
            pIrp,
			(PIO_COMPLETION_ROUTINE)AfpBorrowReadMdlFromCMCompletion,
			pDelAlloc,
			True,
			True,
			True);

	pIrpSp = IoGetNextIrpStackLocation(pIrp);

	pIrp->Tail.Overlay.OriginalFileObject =
                        AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject);
	pIrp->Tail.Overlay.Thread = AfpThread;
	pIrp->RequestorMode = KernelMode;

    pIrp->Flags = IRP_SYNCHRONOUS_API;

	pIrpSp->MajorFunction = IRP_MJ_READ;
	pIrpSp->MinorFunction = IRP_MN_MDL;
	pIrpSp->FileObject = AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject);
	pIrpSp->DeviceObject = pOpenForkEntry->ofe_pDeviceObject;

	pIrpSp->Parameters.Write.Length = ReadSize.LowPart;
	pIrpSp->Parameters.Write.Key = pSda->sda_SessionId;
	pIrpSp->Parameters.Write.ByteOffset = Offset;

    //
    // pReturnMdl could potentially be non-null if the fast-path returned
    // a partial mdl
    //
    pIrp->MdlAddress = pReturnMdl;

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_REQUESTED);
    AFP_DBG_SET_DELALLOC_IRP(pDelAlloc,pIrp);

	IoCallDriver(pOpenForkEntry->ofe_pDeviceObject, pIrp);

    return(STATUS_PENDING);
}


NTSTATUS
AfpBorrowReadMdlFromCMCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           pIrp,
    IN PVOID          Context
)
{

	PSDA	            pSda;
	PREQUEST            pRequest;
    PDELAYEDALLOC       pDelAlloc;
    PMDL                pMdl=NULL;
    NTSTATUS            status=STATUS_SUCCESS;
    AFPSTATUS           AfpStatus=AFP_ERR_NONE;
    PMDL                pCurrMdl;
    DWORD               CurrMdlSize;
    POPENFORKENTRY      pOpenForkEntry;
    PBYTE               pBuf;
    LONG                iLoc;
    LONG                i, Size;
	struct _RequestPacket
	{
		POPENFORKENTRY	_pOpenForkEntry;
		LONG			_Offset;
		LONG			_Size;
		DWORD			_NlMask;
		DWORD			_NlChar;
	};


    pDelAlloc = (PDELAYEDALLOC)Context;

    pSda = pDelAlloc->pSda;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;
    pRequest = pDelAlloc->pRequest;

    ASSERT(VALID_SDA(pSda));
    ASSERT(pDelAlloc->BufSize >= CACHEMGR_READ_THRESHOLD);
    ASSERT(VALID_OPENFORKENTRY(pOpenForkEntry));


    if (pIrp)
    {
        status = pIrp->IoStatus.Status;

        //
        // mark the fact that this mdl belongs to the cache mgr
        //
        if (NT_SUCCESS(status))
        {
            pDelAlloc->pMdl = pIrp->MdlAddress;

            ASSERT(pDelAlloc->pMdl != NULL);
        }
        else
        {
	        DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_WARN,
	            ("AfpBorrowReadMdlFromCMCompletion: irp %lx failed %lx\n",pIrp,status));

            ASSERT(pDelAlloc->pMdl == NULL);
            pDelAlloc->pMdl = NULL;

            AfpStatus = AFP_ERR_MISC;
        }

        AfpFreeIrp(pIrp);

        AFP_DBG_SET_DELALLOC_IRP(pDelAlloc, NULL);
    }

    pRequest->rq_ReplyMdl = pDelAlloc->pMdl;

    // did we get Mdl from the cache mgr?  If so, we need to compute the reply size
    if (pRequest->rq_ReplyMdl != NULL)
    {
        Size = AfpMdlChainSize(pRequest->rq_ReplyMdl);

        if (Size == 0)
        {
            AfpStatus = AFP_ERR_EOF;
        }
		else if (pReqPkt->_NlMask != 0)
		{
            AfpStatus = AFP_ERR_NONE;

            pCurrMdl = pRequest->rq_ReplyMdl;

            CurrMdlSize = MmGetMdlByteCount(pCurrMdl);
            pBuf = MmGetSystemAddressForMdlSafe(
					pCurrMdl,
					NormalPagePriority);

			if (pBuf == NULL) {
				AfpStatus = AFP_ERR_MISC;
				goto error_end;
			}

			for (i=0, iLoc=0; i < Size; iLoc++, i++, pBuf++)
			{
                // move to the next Mdl if we exhausted this one
                if (iLoc >= (LONG)CurrMdlSize)
                {
                    ASSERT(i < Size);

                    pCurrMdl = pCurrMdl->Next;
                    ASSERT(pCurrMdl != NULL);

                    CurrMdlSize = MmGetMdlByteCount(pCurrMdl);
                    pBuf = MmGetSystemAddressForMdlSafe(
							pCurrMdl,
							NormalPagePriority);
					if (pBuf == NULL) {
						AfpStatus = AFP_ERR_MISC;
						goto error_end;
					}

                    iLoc = 0;
                }

			    if ((*pBuf & (BYTE)(pReqPkt->_NlMask)) == (BYTE)(pReqPkt->_NlChar))
				{
					Size = ++i;
					break;
				}
			}
		}

		pSda->sda_ReplySize = (USHORT)Size;

        AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_IN_USE);
        AFP_DBG_INC_DELALLOC_BYTECOUNT(AfpReadCMAlloced, pDelAlloc->BufSize);
    }

    //
    // we didn't get Mdl from cache mgr, fall back to the old, traditional
    // way of allocating and reading the file
    //
    else
    {
        // make sure we aren't forgetting cache mgr's mdl
        ASSERT(pDelAlloc->pMdl == NULL);

        pRequest->rq_CacheMgrContext = NULL;

        AfpForkDereference(pOpenForkEntry);

        AfpSdaDereferenceSession(pSda);

        // don't need that memory no more
        AfpFreeDelAlloc(pDelAlloc);

        AfpStatus = AfpFspDispReadContinue(pSda);
    }

error_end:
    if (AfpStatus != AFP_ERR_EXTENDED)
    {
        AfpCompleteApiProcessing(pSda, AfpStatus);
    }

    return(STATUS_MORE_PROCESSING_REQUIRED);

}


VOID FASTCALL
AfpReturnReadMdlToCM(
    IN  PDELAYEDALLOC   pDelAlloc
)
{
    PDEVICE_OBJECT      pDeviceObject;
    PFAST_IO_DISPATCH   pFastIoDisp;
    PIRP                pIrp;
    PIO_STACK_LOCATION  pIrpSp;
    LARGE_INTEGER       LargeOffset;
    DWORD               ReadSize;
	PFILE_OBJECT        pFileObject;
    PSDA                pSda;
    PMDL                pMdl;
    POPENFORKENTRY      pOpenForkEntry;


    ASSERT(pDelAlloc != NULL);
    ASSERT(pDelAlloc->pMdl != NULL);


    //
    // are we at DPC? if so, can't do this now
    //
    if (KeGetCurrentIrql() == DISPATCH_LEVEL)
    {
        AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_PROC_QUEUED);

        AfpInitializeWorkItem(&pDelAlloc->WorkItem,
                              AfpReturnReadMdlToCM,
                              pDelAlloc);
        AfpQueueWorkItem(&pDelAlloc->WorkItem);
        return;
    }

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_PROC_IN_PROGRESS);

    pSda = pDelAlloc->pSda;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;

    pMdl = pDelAlloc->pMdl;

    ASSERT(VALID_SDA(pSda));
    ASSERT(VALID_OPENFORKENTRY(pOpenForkEntry));

    pFileObject = AfpGetRealFileObject(pOpenForkEntry->ofe_pFileObject),
    pDeviceObject = pOpenForkEntry->ofe_pDeviceObject;

    LargeOffset = pDelAlloc->Offset;
    ReadSize = pDelAlloc->BufSize;

    pFastIoDisp = pDeviceObject->DriverObject->FastIoDispatch;

    //
    // try the fast path to return the Mdl to cache mgr
    //
    if (pFastIoDisp->MdlReadComplete)
    {
        if (pFastIoDisp->MdlReadComplete(pFileObject,pMdl,pDeviceObject) == TRUE)
        {
            AfpReturnReadMdlToCMCompletion(NULL, NULL, pDelAlloc);
            return;
        }
    }

    //
    // hmmm: fast path didn't work, got to post an irp!
    //

	// Allocate and initialize the IRP for this operation.
	pIrp = AfpAllocIrp(pDeviceObject->StackSize);

    // yikes, how messy can it get!
	if (pIrp == NULL)
	{
	    DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	        ("AfpReturnReadMdlToCM: irp alloc failed!\n"));

        // log an event here - that's all we can do here!
        AFPLOG_ERROR(AFPSRVMSG_ALLOC_IRP, STATUS_INSUFFICIENT_RESOURCES,
						                     NULL, 0, NULL);

		AfpReturnReadMdlToCMCompletion(NULL, NULL, pDelAlloc);

    	ASSERT(0);
        return;
	}

	// Set up the completion routine.
	IoSetCompletionRoutine(
            pIrp,
			(PIO_COMPLETION_ROUTINE)AfpReturnReadMdlToCMCompletion,
			pDelAlloc,
			True,
			True,
			True);

	pIrpSp = IoGetNextIrpStackLocation(pIrp);

	pIrp->Tail.Overlay.OriginalFileObject = AfpGetRealFileObject(pFileObject);
	pIrp->Tail.Overlay.Thread = AfpThread;
	pIrp->RequestorMode = KernelMode;

    pIrp->Flags = IRP_SYNCHRONOUS_API;

	pIrpSp->MajorFunction = IRP_MJ_READ;
	pIrpSp->MinorFunction = IRP_MN_MDL | IRP_MN_COMPLETE;
	pIrpSp->FileObject = AfpGetRealFileObject(pFileObject);
	pIrpSp->DeviceObject = pDeviceObject;

    pIrpSp->Parameters.Read.ByteOffset = LargeOffset;
    pIrpSp->Parameters.Read.Length = ReadSize;

    pIrp->MdlAddress = pMdl;

    AFP_DBG_SET_DELALLOC_IRP(pDelAlloc, pIrp);
    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_RETURN_IN_PROGRESS);

	IoCallDriver(pDeviceObject, pIrp);

}



NTSTATUS
AfpReturnReadMdlToCMCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           pIrp,
    IN PVOID          Context
)
{
    PDELAYEDALLOC       pDelAlloc;
    PSDA                pSda;
    POPENFORKENTRY      pOpenForkEntry;
    NTSTATUS            status;


    pDelAlloc = (PDELAYEDALLOC)Context;

    ASSERT(pDelAlloc != NULL);

    pSda = pDelAlloc->pSda;
    pOpenForkEntry = pDelAlloc->pOpenForkEntry;

    ASSERT(VALID_SDA(pSda));
    ASSERT(VALID_OPENFORKENTRY(pOpenForkEntry));

    if (pIrp)
    {
        status = pIrp->IoStatus.Status;

        if (!NT_SUCCESS(status))
        {
	        DBGPRINT(DBG_COMP_AFPAPI, DBG_LEVEL_ERR,
	            ("AfpReturnReadMdlToCMCompletion: irp failed %lx\n",status));

            ASSERT(0);
        }

        AfpFreeIrp(pIrp);
    }

    AfpForkDereference(pOpenForkEntry);

    AfpSdaDereferenceSession(pSda);

    AFP_DBG_SET_DELALLOC_STATE(pDelAlloc, AFP_DBG_MDL_RETURN_COMPLETED);
    AFP_DBG_DEC_DELALLOC_BYTECOUNT(AfpReadCMAlloced, pDelAlloc->BufSize);

    // don't need that memory no more
    AfpFreeDelAlloc(pDelAlloc);

    return(STATUS_MORE_PROCESSING_REQUIRED);

}


PDELAYEDALLOC FASTCALL
AfpAllocDelAlloc(
    IN VOID
)
{
    PDELAYEDALLOC   pDelAlloc;
    KIRQL           OldIrql;

    pDelAlloc = (PDELAYEDALLOC) AfpAllocZeroedNonPagedMemory(sizeof(DELAYEDALLOC));

#if DBG
    if (pDelAlloc)
    {
        pDelAlloc->Signature = AFP_DELALLOC_SIGNATURE;
        pDelAlloc->State = AFP_DBG_MDL_INIT;

        ACQUIRE_SPIN_LOCK(&AfpDebugSpinLock, &OldIrql);
        InsertTailList(&AfpDebugDelAllocHead, &pDelAlloc->Linkage);
        RELEASE_SPIN_LOCK(&AfpDebugSpinLock, OldIrql);
    }
#endif

    return(pDelAlloc);
}


VOID FASTCALL
AfpFreeDelAlloc(
    IN PDELAYEDALLOC    pDelAlloc
)
{
    KIRQL   OldIrql;

#if DBG

    ASSERT(pDelAlloc->Signature == AFP_DELALLOC_SIGNATURE);

    pDelAlloc->State |= AFP_DBG_MDL_END;

    ACQUIRE_SPIN_LOCK(&AfpDebugSpinLock, &OldIrql);
    RemoveEntryList(&pDelAlloc->Linkage);

    pDelAlloc->Linkage.Flink = (PLIST_ENTRY)0x11111111;
    pDelAlloc->Linkage.Blink = (PLIST_ENTRY)0x33333333;
    RELEASE_SPIN_LOCK(&AfpDebugSpinLock, OldIrql);
#endif

    AfpFreeMemory(pDelAlloc);
}