/******************************Module*Header*******************************\
* Module Name: dci.c
*
* This module contains the functions required to support DCI.
*
* Copyright (c) 1992-1995 Microsoft Corporation
\**************************************************************************/


#include "precomp.h"

#if (TARGET_BUILD == 351)
    /*
     * DCI support requires the use of structures and defined values
     * found in a header file that is only present in versions of
     * the DDK that support DCI, rather than having these items
     * in a DCI section of one of the standard header files. For this
     * reason, we can't do conditional compilation based on whether
     * the DCI-specific values are defined, because our first indication
     * would be an error due to the header file not being found.
     *
     * Explicit DCI support is only needed when building for NT 3.51,
     * since it was added for this version, but for version 4.0 (next
     * version) and above it is incorporated into Direct Draw rather
     * than being handled separately.
     *
     * Since this entire module depends on DCI being supported by the
     * build environment, null it out if this is not the case.
     */
#include <dciddi.h>
#include "dci.h"


/******************************Public*Routine******************************\
* DCIRVAL BeginAccess
*
* Map in the screen memory so that the DCI application can access it.

\**************************************************************************/

DCIRVAL BeginAccess(DCISURF* pDCISurf, LPRECT rcl)
{
    PDEV*                           ppdev;
    VIDEO_SHARE_MEMORY              shareMemory;
    VIDEO_SHARE_MEMORY_INFORMATION  shareMemoryInformation;
    DWORD                           returnedDataLength;

    DISPDBG((DEBUG_ENTRY_EXIT, "--> BeginAccess with pDCISurf %08lx", pDCISurf));

    ppdev = pDCISurf->ppdev;

    if (pDCISurf->SurfaceInfo.dwOffSurface != 0)
        {
        /*
         * We have already mapped in the frame buffer. All our
         * accelerators unmap the frame buffer in the
         * DestroySurface() call, so if this is the beginning
         * of the second or subsequent BeginAccess()/EndAccess()
         * pair since the surface was created, we don't need to
         * map the frame buffer again.
         *
         * Wait for any pending accelerator operations to complete before
         * yielding control, in case it affects the same screen region
         * that DCI wants.
         */
        if (ppdev->iMachType == MACH_MM_64)
            {
            vM64QuietDown(ppdev, ppdev->pjMmBase);
            }
        else if (ppdev->iMachType == MACH_MM_32)
            {
            vM32QuietDown(ppdev, ppdev->pjMmBase);
            }
        else    /* if (ppdev->iMachType == MACH_IO_32) */
            {
            vI32QuietDown(ppdev, ppdev->pjIoBase);
            }

        DISPDBG((DEBUG_ENTRY_EXIT, "<-- BeginAccess"));
        return(DCI_OK);
        }
    else
        {
        shareMemory.ProcessHandle           = EngGetProcessHandle();
        shareMemory.RequestedVirtualAddress = 0;
        shareMemory.ViewOffset              = pDCISurf->Offset;
        shareMemory.ViewSize                = pDCISurf->Size;

        /*
         * Wait for any pending accelerator operations to complete
         * before yielding control, in case it affects the same
         * screen region that DCI wants.
         */
        if (ppdev->iMachType == MACH_MM_64)
            {
            vM64QuietDown(ppdev, ppdev->pjMmBase);
            }
        else if (ppdev->iMachType == MACH_MM_32)
            {
            vM32QuietDown(ppdev, ppdev->pjMmBase);
            }
        else    /* if (ppdev->iMachType == MACH_IO_32) */
            {
            vI32QuietDown(ppdev, ppdev->pjIoBase);
            }

        /*
         * Now map the frame buffer into the caller's address space:
         *
         * Be careful when mixing VideoPortMapBankedMemory (i.e., vflatd)
         * access with explicit banking in the driver -- the two may get
         * out of sync with respect to what bank they think the hardware
         * is currently configured for.  The easiest way to avoid any
         * problem is to call VideoPortMapBankedMemory/VideoPortUnmapMemory
         * in the miniport for every BeginAccess/EndAccess pair, and to
         * always explicitly reset the bank after the EndAccess.
         * (VideoPortMapBankedMemory will always reset vflatd's current
         * bank.)
         */
        if (!AtiDeviceIoControl(pDCISurf->ppdev->hDriver,
                             IOCTL_VIDEO_SHARE_VIDEO_MEMORY,
                             &shareMemory,
                             sizeof(VIDEO_SHARE_MEMORY),
                             &shareMemoryInformation,
                             sizeof(VIDEO_SHARE_MEMORY_INFORMATION),
                             &returnedDataLength))
            {
            DISPDBG((DEBUG_ERROR, "BeginAccess: failed IOCTL_VIDEO_SHARE_VIDEO_MEMORY"));
            return(DCI_FAIL_GENERIC);
            }

        pDCISurf->SurfaceInfo.wSelSurface  = 0;
        pDCISurf->SurfaceInfo.dwOffSurface =
            (ULONG) shareMemoryInformation.VirtualAddress;

        /*
         * We return DCI_STATUS_POINTERCHANGED because we have
         * just created a new pointer to the frame buffer.
         * Repeated BeginAccess()/EndAccess() calls without
         * a call to DestroySurface() will hit this case on the
         * first call, but meet the "if" condition (buffer
         * already mapped) on subsequent calls.
         *
         * We would only need to map the DCI pointer on every
         * call to BeginAccess() and unmap it on every call
         * to EndAccess() if we couldn't support simultaneous
         * accelerator and frame buffer access. All our cards
         * with frame buffer capability support such access,
         * and it's GDI's responsibility to ensure that no
         * GDI call is made between the calls to BeginAccess()
         * and EndAccess(), so we don't need a critical section
         * to ensure that a GDI call doesn't change the page
         * while DCI is accessing the frame buffer if we are
         * using a banked aperture.
         */
#if DBG
        DISPDBG((DEBUG_ENTRY_EXIT, "<-- BeginAccess DCI_STATUS_POINTERCHANGED %08lx\n", pDCISurf));
#endif

        return(DCI_STATUS_POINTERCHANGED);
        }
}

/******************************Public*Routine******************************\
* VOID vUnmap
*
* Unmap the screen memory so that the DCI application can no longer access
* it.

\**************************************************************************/

VOID vUnmap(DCISURF* pDCISurf)
{
    PDEV*               ppdev;
    VIDEO_SHARE_MEMORY  shareMemory;
    DWORD               returnedDataLength;

    ppdev = pDCISurf->ppdev;

    /*
     * We no longer need to have the frame buffer mapped for DCI,
     * so unmap it.
     */
    shareMemory.ProcessHandle           = EngGetProcessHandle();
    shareMemory.ViewOffset              = 0;
    shareMemory.ViewSize                = 0;
    shareMemory.RequestedVirtualAddress =
        (VOID*) pDCISurf->SurfaceInfo.dwOffSurface;

    if (!AtiDeviceIoControl(pDCISurf->ppdev->hDriver,
                         IOCTL_VIDEO_UNSHARE_VIDEO_MEMORY,
                         &shareMemory,
                         sizeof(VIDEO_SHARE_MEMORY),
                         NULL,
                         0,
                         &returnedDataLength))
        {
        DISPDBG((DEBUG_ERROR, "EndAccess failed IOCTL_VIDEO_UNSHARE_VIDEO_MEMORY"));
        }
    else
        {
        /*
         * Be sure to signal to GDI that the surface is no longer mapped.
         */
        pDCISurf->SurfaceInfo.dwOffSurface = 0;
        }
}

/******************************Public*Routine******************************\
* DCIRVAL EndAccess
*
* Switch control of the frame buffer from DCI back to GDI.
\**************************************************************************/

DCIRVAL EndAccess(DCISURF* pDCISurf)
{
    PDEV*   ppdev;

    /*
     * We would only need to unmap the frame buffer at this
     * point if our cards couldn't support simultaneous frame
     * buffer and accelerator access. Since our cards with
     * frame buffer capability all support such access (provided
     * the two accesses refer to different parts of the screen,
     * since otherwise they'd corrupt each other, but it's
     * GDI's responsibility to ensure that this is the case),
     * this function only needs to ensure that no call to
     * EndAccess() is made without a corresponding call to
     * BeginAccess() having already been made.
     */

    DISPDBG((DEBUG_ENTRY_EXIT, "EndAccess with pDCISurf %08lx\n", pDCISurf));

    ASSERTDD(pDCISurf->SurfaceInfo.dwOffSurface != 0,
        "GDI should assure us that EndAccess can't be recursive");

    ppdev = pDCISurf->ppdev;

    return(DCI_OK);
}

/******************************Public*Routine******************************\
* VOID DestroySurface
*
* Destroy the DCI surface and free up any allocations.

\**************************************************************************/

VOID DestroySurface(DCISURF* pDCISurf)
{
    DISPDBG((DEBUG_ENTRY_EXIT, "DestroySurface with pDCISurf %08lx\n", pDCISurf));

    if (pDCISurf->SurfaceInfo.dwOffSurface != 0)
        {
        /*
         * Because we can support simultaneous frame buffer and
         * accelerator access, we optimized a bit by not unmapping
         * the frame buffer on every EndAccess() call, but we
         * finally have to do the unmap now. The dwOffSurface field
         * should always be nonzero (a frame buffer has been mapped),
         * but there's no harm in checking.
         */
        vUnmap(pDCISurf);
        }

    LocalFree(pDCISurf);
}

/******************************Public*Routine******************************\
* ULONG DCICreatePrimarySurface
*
* Create a DCI surface to provide access to the visible screen.

\**************************************************************************/

ULONG DCICreatePrimarySurface(PDEV* ppdev, ULONG cjIn, VOID* pvIn, ULONG cjOut, VOID* pvOut)
{
    DCISURF*         pDCISurf;
    LPDCICREATEINPUT pInput;
    LONG             lRet;


    #if defined(MIPS) || defined(_PPC_)
        {
        /*
         * !!! vflatd seems to currently have a bug on Mips and PowerPC:
         */
        return (ULONG) (DCI_FAIL_UNSUPPORTED);
        }
    #endif

    if( !(ppdev->FeatureFlags & EVN_DENSE_CAPABLE) )
        {
        /*
         * We don't support DCI on the Alpha when running in sparse
         * space, because we can't.
         */
        lRet = DCI_FAIL_UNSUPPORTED;
        }
    else
        {
        pInput = (DCICREATEINPUT*) pvIn;

        if (cjIn >= sizeof(DCICREATEINPUT))
            {
            pDCISurf = (DCISURF*) LocalAlloc(LMEM_ZEROINIT, sizeof(DCISURF));

            if (pDCISurf)
                {
                /*
                 * Initializate all public information about the primary
                 * surface.
                 */
                pDCISurf->SurfaceInfo.dwSize         = sizeof(DCISURFACEINFO);
                pDCISurf->SurfaceInfo.dwDCICaps      = DCI_PRIMARY | DCI_VISIBLE;
                pDCISurf->SurfaceInfo.BeginAccess    = BeginAccess;
                pDCISurf->SurfaceInfo.EndAccess      = EndAccess;
                pDCISurf->SurfaceInfo.DestroySurface = DestroySurface;
                pDCISurf->SurfaceInfo.dwMask[0]      = ppdev->flRed;
                pDCISurf->SurfaceInfo.dwMask[1]      = ppdev->flGreen;
                pDCISurf->SurfaceInfo.dwMask[2]      = ppdev->flBlue;
                pDCISurf->SurfaceInfo.dwBitCount     = ppdev->cBitsPerPel;
                pDCISurf->SurfaceInfo.dwWidth        = ppdev->cxScreen;
                pDCISurf->SurfaceInfo.dwHeight       = ppdev->cyScreen;
                pDCISurf->SurfaceInfo.lStride        = ppdev->lDelta;
                pDCISurf->SurfaceInfo.wSelSurface    = 0;
                pDCISurf->SurfaceInfo.dwOffSurface   = 0;

                if (pDCISurf->SurfaceInfo.dwBitCount <= 8)
                    {
                    pDCISurf->SurfaceInfo.dwCompression = BI_RGB;
                    }
                else
                    {
                    pDCISurf->SurfaceInfo.dwCompression = BI_BITFIELDS;
                    }

                /*
                 * Now initialize our private fields that we want associated
                 * with the DCI surface:
                 */
                pDCISurf->ppdev  = ppdev;
                pDCISurf->Offset = 0;

                /*
                 * Under NT, all mapping is done with a 64K granularity.
                 */
                pDCISurf->Size = ROUND_UP_TO_64K(ppdev->cyScreen * ppdev->lDelta);

                /*
                 * Return a pointer to the DCISURF to GDI by placing
                 * it in the 'pvOut' buffer.
                 */
                *((DCISURF**) pvOut) = pDCISurf;

                lRet = DCI_OK;
                }
            else
                {
                lRet = DCI_ERR_OUTOFMEMORY;
                }
            }
        else
            {
            lRet = DCI_FAIL_GENERIC;
            }
        }

    return(lRet);
}

#endif  /* TARGET_BUILD == 351 */