/*++

Copyright (c) 2000  Microsoft Corporation

Module Name:

    vesa.c

Abstract:

    This module implements VESA support.

Author:

    Erick Smith (ericks) Sep. 2000

Environment:

    kernel mode only

Revision History:

--*/

#include "dderror.h"
#include "devioctl.h"
#include "miniport.h"

#include "ntddvdeo.h"
#include "video.h"
#include "vga.h"
#include "vesa.h"

#if defined(ALLOC_PRAGMA)
#pragma alloc_text(PAGE,ValidateVbeInfo)
#pragma alloc_text(PAGE,InitializeModeTable)
#pragma alloc_text(PAGE,UpdateRegistry)
#pragma alloc_text(PAGE,VgaSaveHardwareState)
#pragma alloc_text(PAGE,VesaSaveHardwareState)
#pragma alloc_text(PAGE,GetVideoMemoryBaseAddress)
#pragma alloc_text(PAGE,RaiseToPower2)
#pragma alloc_text(PAGE,RaiseToPower2Ulong)
#pragma alloc_text(PAGE,IsPower2)
#pragma alloc_text(PAGE,VBESetMode)
#pragma alloc_text(PAGE,VBEGetMode)
#pragma alloc_text(PAGE,VBEGetModeInfo)
#pragma alloc_text(PAGE,VBESaveState)
#pragma alloc_text(PAGE,VBERestoreState)
#pragma alloc_text(PAGE,VBESetDisplayWindow)
#pragma alloc_text(PAGE,VBEGetDisplayWindow)
#pragma alloc_text(PAGE,VBEGetScanLineLength)
#pragma alloc_text(PAGE,IsSavedModeVesa)
#pragma alloc_text(PAGE,VesaSaveHardwareState)
#pragma alloc_text(PAGE,VesaRestoreHardwareState)
#pragma alloc_text(PAGE,SaveFrameBuffer)
#pragma alloc_text(PAGE,RestoreFrameBuffer)
#endif

USHORT
RaiseToPower2(
    USHORT x
    )

{
    USHORT Mask = x;

    if (Mask & (Mask - 1)) {

        Mask = 1;

        while (Mask < x && Mask != 0) {
            Mask <<= 1;
        }
    }

    return Mask;
}

ULONG
RaiseToPower2Ulong(
    ULONG x
    )

{
    ULONG Mask = x;

    if (Mask & (Mask - 1)) {

        Mask = 1;

        while (Mask < x && Mask != 0) {
            Mask <<= 1;
        }
    }

    return Mask;
}

BOOLEAN
IsPower2(
    USHORT x
    )

{
    return( !(x & (x- 1)) );
}

VOID
UpdateRegistry(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    PWSTR ValueName,
    PUCHAR Value
    )

/*++

--*/

{
    ULONG Len = (strlen(Value) + 1) * 2;
    PWSTR WideString;

    WideString = VideoPortAllocatePool(hwDeviceExtension,
                                       VpPagedPool,
                                       Len,
                                       ' agV');

    if (WideString) {

        PWSTR ptr = WideString;

        while(*Value) {
            *ptr++ = (WCHAR) *Value++;
        }
        *ptr = 0;

        VideoPortSetRegistryParameters(hwDeviceExtension,
                                       ValueName,
                                       WideString,
                                       Len);

        VideoPortFreePool(hwDeviceExtension, WideString);
    }
}

BOOLEAN
IsVesaBiosOk(
    PVIDEO_PORT_INT10_INTERFACE pInt10,
    USHORT OemSoftwareRev,
    PUCHAR OemVendorName,
    PUCHAR OemProductName,
    PUCHAR OemProductRev
    )

{

    VideoDebugPrint((1, "OemSoftwareRev = %d\n",   OemSoftwareRev));
    VideoDebugPrint((1, "OemVendorName  = '%s'\n", OemVendorName));
    VideoDebugPrint((1, "OemProductName = '%s'\n", OemProductName));
    VideoDebugPrint((1, "OemProductRev  = '%s'\n", OemProductRev));

    //
    // The ATI ArtX boxes currently have a VESA Bios bug where
    // they indicate they support linear mode access when
    // they don't.  Fail these boards.
    //

    if ((strcmp(OemProductName, "ATI S1-370TL") == 0) ||
        (strcmp(OemProductName, "ArtX I") == 0)) {

        return FALSE;
    }

    //
    // Several 3dfx boards have buggy vesa bioses.  The mode set
    // works, but the display is corrupted.
    //

    if ((strcmp(OemProductName, "Voodoo4 4500 ") == 0) ||
        (strcmp(OemProductName, "Voodoo3 3000 LC ") == 0) ||
        (strcmp(OemProductName, "Voodoo3 2000 LC ") == 0) || 
        (strcmp(OemProductName, "3Dfx Banshee") == 0)) {

        return FALSE;
    }

    //
    // Matrox G100 boards with rev 1.05 bioses can't set vesa modes.
    // We hang in the bios.
    //

    if ((strcmp(OemProductName, "MGA-G100") == 0) &&
        (OemSoftwareRev == 0x105)) {

        //
        // We must also disable 800x600x16 color mode for this
        // device.  This makes the assumption that the mode we
        // are deleting is the last mode in our table.
        //

        NumVideoModes--;

        return FALSE;
    }

    //
    // We have seen at least on SIS 5597 part which returns a bad
    // linear address.  Lets disable these parts.
    //

    if (strcmp(OemProductName, "SiS 5597") == 0) {

        return FALSE;
    }

    //
    // We found a bad nVidia GeForce MX part.  It hangs in the bios
    // on boot.
    //

    if ((strcmp(OemVendorName, "NVidia Corporation") == 0) &&
        (strcmp(OemProductName, "NV11 (GeForce2) Board") == 0) &&
        (strcmp(OemProductRev, "Chip Rev B2") == 0) &&
        (OemSoftwareRev == 0x311)) {

        //
        // This bios "may" be buggy, but in an effort to not kill
        // vesa support on all GeForce MX boards, lets also look at
        // the version string embedded in the bios.
        //
        // We know the bad bios's have the following string at location
        // c000:0159:
        //
        // "Version 3.11.01.24N16"
        //
        // Lets read from this location and try to match on this string
        //

        UCHAR Version[22];
        pInt10->Int10ReadMemory(pInt10->Context,
                                (USHORT)0xC000,
                                (USHORT)0x0159,
                                Version,
                                21);

        Version[21] = 0;

        if (strcmp(Version, "Version 3.11.01.24N16") == 0) {

            return FALSE;
        }
    }

    return TRUE;
}

BOOLEAN
ValidateVbeInfo(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    PVGA_INFO_BLOCK InfoBlock
    )

/*++

Notes:

    This routine makes the assumption that the InfoBlock is still
    valid in the VDM transfer area.

--*/

{
    PVIDEO_PORT_INT10_INTERFACE pInt10;
    BOOLEAN UseVesa = FALSE;

    pInt10 = &hwDeviceExtension->Int10;

    if (InfoBlock->VesaSignature == 'ASEV') {

        PUCHAR OemString;
        UCHAR OemStringBuffer[80];
        ULONG MemorySize;

        //
        // Capture OEM String
        //

        pInt10->Int10ReadMemory(pInt10->Context,
                                (USHORT)SEG(InfoBlock->OemStringPtr),
                                (USHORT)OFF(InfoBlock->OemStringPtr),
                                OemStringBuffer,
                                80);

        OemString = OemStringBuffer;

        VideoDebugPrint((1, "*********************************************\n"));
        VideoDebugPrint((1, "  VBE Signature:      VESA\n"));
        VideoDebugPrint((1, "  VBE Version:        %d.%02d\n",
                            InfoBlock->VbeVersion >> 8,
                            InfoBlock->VbeVersion & 0xff));
        VideoDebugPrint((1, "  OEM String:         %s\n",
                             OemString));

        if (InfoBlock->TotalMemory < 16) {

            //
            // If less than 1 meg, display in KB
            //

            VideoDebugPrint((1, "  Total Memory:       %dKB\n",
                                InfoBlock->TotalMemory * 64));

        } else {

            //
            // Else display in MB
            //

            VideoDebugPrint((1, "  Total Memory:       %dMB\n",
                                InfoBlock->TotalMemory / 16));
        }

        if (InfoBlock->VbeVersion >= 0x102) {

            UCHAR OemVendorName[80];
            UCHAR OemProductName[80];
            UCHAR OemProductRev[80];

            pInt10->Int10ReadMemory(pInt10->Context,
                                    (USHORT)SEG(InfoBlock->OemVendorNamePtr),
                                    (USHORT)OFF(InfoBlock->OemVendorNamePtr),
                                    OemVendorName,
                                    80);

            pInt10->Int10ReadMemory(pInt10->Context,
                                    (USHORT)SEG(InfoBlock->OemProductNamePtr),
                                    (USHORT)OFF(InfoBlock->OemProductNamePtr),
                                    OemProductName,
                                    80);

            pInt10->Int10ReadMemory(pInt10->Context,
                                    (USHORT)SEG(InfoBlock->OemProductRevPtr),
                                    (USHORT)OFF(InfoBlock->OemProductRevPtr),
                                    OemProductRev,
                                    80);

            OemVendorName[79] = 0;
            OemProductName[79] = 0;
            OemProductRev[79] = 0;

            VideoDebugPrint((1, "  OEM Software Rev:   %d.%02d\n",
                                InfoBlock->OemSoftwareRev >> 8,
                                InfoBlock->OemSoftwareRev & 0xff));
            VideoDebugPrint((1, "  OEM Vendor Name:    %s\n", OemVendorName));
            VideoDebugPrint((1, "  OEM Product Name:   %s\n", OemProductName));
            VideoDebugPrint((1, "  OEM Product Rev:    %s\n", OemProductRev));

            UseVesa = IsVesaBiosOk(pInt10,
                                   InfoBlock->OemSoftwareRev,
                                   OemVendorName,
                                   OemProductName,
                                   OemProductRev);

        }

        VideoDebugPrint((1, "*********************************************\n"));

#if 0
        //
        // It would be nice if we could dump the following info into the
        // registry.  But as the GDI code currently stands, if we add
        // ChipType or AdapterString info into the registry, we lose
        // fullscreen support.  This has to do with the way GDI currently
        // determines the fullscreen device.
        //
        // For now, lets just not add this registry info.
        //

        UpdateRegistry(hwDeviceExtension,
                       L"HardwareInformation.ChipType",
                       OemString);

        //
        // Adapter String MUST be VGA.  Without it, the system won't
        // recognize this driver as the VGA driver.
        //

        UpdateRegistry(hwDeviceExtension,
                       L"HardwareInformation.AdapterString",
                       "VGA");

        UpdateRegistry(hwDeviceExtension,
                       L"HardwareInformation.DacType",
                       (InfoBlock->Capabilities & VBE_CAP_DAC_WIDTH_8BPP)
                           ? "8 bit" : "6 bit");

        UpdateRegistry(hwDeviceExtension,
                       L"HardwareInformation.BiosString",
                       OemProductRev);

        //
        // Store memory size in registry
        //

        MemorySize = InfoBlock->TotalMemory << 16;

        VideoPortSetRegistryParameters(hwDeviceExtension,
                                       L"HardwareInformation.MemorySize",
                                       &MemorySize,
                                       sizeof(ULONG));
#endif

    } else {

        VideoDebugPrint((0, "Invalid VBE Info Block.\n"));
    }

    return UseVesa;
}

VOID
InitializeModeTable(
    PVOID HwDeviceExtension
    )

/*++

Routine Description:

    This routine does one time initialization of the device.

Arguments:

    HwDeviceExtension - Pointer to the miniport driver's adapter information.

Return Value:

    None.

--*/

{
    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    INT10_BIOS_ARGUMENTS BiosArguments;
    PVGA_INFO_BLOCK InfoBlock;
    PMODE_INFO_BLOCK ModeBlock;
    PUSHORT ModeTable;
    PUSHORT ModePtr;
    ULONG NumVesaModes;
    PVIDEOMODE VideoModePtr;
    LONG TotalMemory;
    ULONG VideoMemoryRequired;
    USHORT VbeVersion;
    PULONG Memory;
    ULONG AdditionalModes = 0;

    USHORT VdmSeg, VdmOff;
    VP_STATUS Status;
    PVIDEO_PORT_INT10_INTERFACE pInt10;
    ULONG Length = 0x1000;

    BOOLEAN LinearModeSupported;
    BOOLEAN ModeValid;

    hwDeviceExtension->Int10.Size = sizeof(VIDEO_PORT_INT10_INTERFACE);
    hwDeviceExtension->Int10.Version = 1;

    Status = VideoPortQueryServices(hwDeviceExtension,
                                    VideoPortServicesInt10,
                                    (PINTERFACE)&hwDeviceExtension->Int10);

    VgaModeList = ModesVGA;

    if (Status == NO_ERROR) {

        pInt10 = &hwDeviceExtension->Int10;

        pInt10->InterfaceReference(pInt10->Context);

        //
        // Get a chunk of memory in VDM area to use for buffers.
        //

        Status = pInt10->Int10AllocateBuffer(pInt10->Context,
                                             &VdmSeg,
                                             &VdmOff,
                                             &Length);

        if ((Status != NO_ERROR) || (Length < 0x1000)) {
            ASSERT(FALSE);
        }

        hwDeviceExtension->VdmSeg = VdmSeg;
        hwDeviceExtension->VdmOff = VdmOff;

        //
        // Allocate Memory
        //

        InfoBlock = VideoPortAllocatePool(hwDeviceExtension,
                                          VpPagedPool,
                                          sizeof(VGA_INFO_BLOCK) +
                                          sizeof(MODE_INFO_BLOCK) +
                                          256 +
                                          2, // space for a 0xffff terminator
                                          ' agV');

        if (InfoBlock) {

            ModeBlock = (PMODE_INFO_BLOCK)((ULONG_PTR)InfoBlock + sizeof(VGA_INFO_BLOCK));
            ModeTable = (PUSHORT)((ULONG_PTR)ModeBlock + sizeof(MODE_INFO_BLOCK));

            ModeTable[128] = 0xffff;  // make sure we have a mode terminator

            //
            // Get VESA mode information
            //

            InfoBlock->VesaSignature = '2EBV';

            pInt10->Int10WriteMemory(pInt10->Context,
                                     VdmSeg,
                                     VdmOff,
                                     InfoBlock,
                                     sizeof(VGA_INFO_BLOCK));

            //
            // Get SuperVGA support info
            //

            BiosArguments.Eax = 0x4f00;
            BiosArguments.Edi = VdmOff;
            BiosArguments.SegEs = VdmSeg;

            pInt10->Int10CallBios(pInt10->Context, &BiosArguments);

            pInt10->Int10ReadMemory(pInt10->Context,
                                    VdmSeg,
                                    VdmOff,
                                    InfoBlock,
                                    sizeof(VGA_INFO_BLOCK));

            TotalMemory = InfoBlock->TotalMemory * 0x10000;
            VbeVersion = InfoBlock->VbeVersion;

            //
            // NOTE: We must call ValidateVbeInfo while the info block
            // is still in the transfer area.
            //

            if (ValidateVbeInfo(hwDeviceExtension, InfoBlock)) {

                //
                // Capture the list of mode numbers
                //

                pInt10->Int10ReadMemory(pInt10->Context,
                                        (USHORT)(InfoBlock->VideoModePtr >> 16),
                                        (USHORT)(InfoBlock->VideoModePtr & 0xffff),
                                        ModeTable,
                                        256);

                //
                // Count the number of VESA modes, and allocate memory for the
                // mode list.  The mode list is terminated by a -1.
                //

                ModePtr = ModeTable;
                NumVesaModes = 0;

                while (*ModePtr != 0xffff) {
                    NumVesaModes++;
                    ModePtr++;
                }

                if (NumVesaModes == 128) {

                    //
                    // Something is wrong.  We hit our hard coded terminator.
                    // Don't try to use vesa modes.
                    //

                    return;
                }

                VgaModeList = VideoPortAllocatePool(hwDeviceExtension,
                                                    VpPagedPool,
                                                    (NumVesaModes + NumVideoModes) *
                                                        sizeof(VIDEOMODE),
                                                    ' agV');

                if (VgaModeList == NULL) {

                    VideoDebugPrint((0, "failed to allocate %d bytes.\n",
                                     (NumVesaModes + NumVideoModes) * sizeof(VIDEOMODE)));

                    VgaModeList = ModesVGA;

                    //
                    // Perform clean up.
                    //

                    VideoPortFreePool(hwDeviceExtension, InfoBlock);

                    return;
                }

                //
                // Copy the existing constant VGA modes into our mode list table.
                //

                memmove(VgaModeList, ModesVGA, sizeof(VIDEOMODE) * NumVideoModes);

                //
                // Now populate the rest of the table based on the VESA mode
                // table.
                //

                VideoModePtr = VgaModeList + NumVideoModes;
                ModePtr = ModeTable;

                while (NumVesaModes--) {

                    ModeValid = FALSE;

                    //
                    // Get info about the VESA mode.
                    //

                    BiosArguments.Eax = 0x4f01;
                    BiosArguments.Ecx = *ModePtr;
                    BiosArguments.Edi = VdmOff;
                    BiosArguments.SegEs = VdmSeg;

                    pInt10->Int10CallBios(pInt10->Context, &BiosArguments);

                    if ((BiosArguments.Eax & 0xffff) == VESA_STATUS_SUCCESS) {

                        //
                        // Copy the mode information out of the csrss process
                        //

                        pInt10->Int10ReadMemory(pInt10->Context,
                                                VdmSeg,
                                                VdmOff,
                                                ModeBlock,
                                                sizeof(MODE_INFO_BLOCK));

                        //
                        // Make sure that this is a graphics mode, and
                        // that it is supported by this hardware.
                        //

                        if ((ModeBlock->ModeAttributes & 0x11) == 0x11) {

                            if ((VbeVersion >= 0x200) &&
                                (ModeBlock->PhysBasePtr) &&
                                (ModeBlock->ModeAttributes & 0x80)) {

                                LinearModeSupported = TRUE;

                            } else {

                                //
                                // Make sure banked modes are supported
                                //

                                ASSERT((ModeBlock->ModeAttributes & 0x40) == 0);
                                LinearModeSupported = FALSE;
                            }

                            //
                            // Only include this mode if the following are true:
                            //
                            //   1. The mode is an 8bpp or higher mode
                            //   2. The resolution is 640x480 or greater
                            //

                            if ((ModeBlock->XResolution >= 640) &&
                                (ModeBlock->YResolution >= 480) &&
                                (ModeBlock->NumberOfPlanes != 0) &&
                                (ModeBlock->BitsPerPixel >= 8)) {

                                //
                                // Fill in the video mode structure.
                                //

                                memset(VideoModePtr, 0, sizeof(VIDEOMODE));

                                if (ModeBlock->ModeAttributes & 0x08) {
                                    VideoModePtr->fbType |= VIDEO_MODE_COLOR;
                                }

                                if (ModeBlock->ModeAttributes & 0x10) {
                                    VideoModePtr->fbType |= VIDEO_MODE_GRAPHICS;
                                }

                                VideoModePtr->numPlanes = ModeBlock->NumberOfPlanes;
                                VideoModePtr->bitsPerPlane = ModeBlock->BitsPerPixel /
                                                                 ModeBlock->NumberOfPlanes;

                                if (VideoModePtr->bitsPerPlane == 16) {

                                    //
                                    // Check to see if this is really a 15 bpp mode
                                    //

                                    if (ModeBlock->GreenMaskSize == 5) {
                                        VideoModePtr->bitsPerPlane = 15;
                                    }
                                }

                                if (ModeBlock->XCharSize) {
                                    VideoModePtr->col = ModeBlock->XResolution / ModeBlock->XCharSize;
                                } else {
                                    VideoModePtr->col = 80;
                                }

                                if (ModeBlock->YCharSize) {
                                    VideoModePtr->row = ModeBlock->YResolution / ModeBlock->YCharSize;
                                } else {
                                    VideoModePtr->row = 25;
                                }

                                VideoModePtr->hres = ModeBlock->XResolution;
                                VideoModePtr->vres = ModeBlock->YResolution;
                                VideoModePtr->frequency = 1;
                                VideoModePtr->Int10ModeNumber = (((ULONG)*ModePtr) << 16) | 0x00004f02;
                                VideoModePtr->Granularity = ModeBlock->WinGranularity << 10;
                                VideoModePtr->NonVgaHardware = (ModeBlock->ModeAttributes & 0x20) ? TRUE : FALSE;

                                if (LinearModeSupported) {

                                    if (VbeVersion >= 0x300) {
                                        VideoModePtr->wbytes = ModeBlock->LinBytesPerScanLine;
                                    } else {
                                        VideoModePtr->wbytes = ModeBlock->BytesPerScanLine;
                                    }

                                    //
                                    // We first try to round up VideoMemoryRequired
                                    // to power of 2 so that we'll have a better 
                                    // chance to get it mapped as write combined 
                                    // on systems where MTRR is the only mechanism
                                    // for such mappings. If the rounded up value
                                    // is larger than the size of on-board memory
                                    // we'll at least round it up to page boundary
                                    //

                                    VideoMemoryRequired = RaiseToPower2Ulong(VideoModePtr->wbytes * VideoModePtr->vres);

                                    if(VideoMemoryRequired > (ULONG)TotalMemory) {
                                        VideoMemoryRequired = 
                                                   (VideoModePtr->wbytes * VideoModePtr->vres + 0x1000 - 1) & ~(0x1000 - 1);
                                    }
     
                                    VideoModePtr->sbytes = VideoMemoryRequired;
                                    VideoModePtr->PixelsPerScan = VideoModePtr->hres;
                                    VideoModePtr->banktype = NoBanking;

                                    VideoModePtr->Int10ModeNumber |= 0x40000000;

                                    VideoModePtr->MemoryBase = ModeBlock->PhysBasePtr;
                                    VideoModePtr->MemoryLength = VideoMemoryRequired;
                                    VideoModePtr->FrameOffset = 0;
                                    VideoModePtr->FrameLength = VideoMemoryRequired;
                                    VideoModePtr->fbType |= VIDEO_MODE_LINEAR;

                                } else {

                                    VideoModePtr->wbytes = RaiseToPower2(ModeBlock->BytesPerScanLine);

                                    //
                                    // Round up to bank boundary if possible.
                                    //
 
                                    VideoMemoryRequired = 
                                         (VideoModePtr->wbytes * VideoModePtr->vres + 0x10000 - 1) & ~(0x10000 - 1);

                                    if(VideoMemoryRequired > (ULONG)TotalMemory) {

                                        //
                                        // Round up to page boundary.
                                        //

                                        VideoMemoryRequired = 
                                             (VideoModePtr->wbytes * VideoModePtr->vres + 0x1000 - 1) & ~(0x1000 - 1);
                                    }

                                    VideoModePtr->sbytes = VideoMemoryRequired;
                                    VideoModePtr->PixelsPerScan = RaiseToPower2(VideoModePtr->hres);
                                    VideoModePtr->banktype = VideoBanked1RW;
                                    VideoModePtr->MemoryBase = 0xa0000;
                                    VideoModePtr->MemoryLength = 0x10000;
                                    VideoModePtr->FrameOffset = 0;
                                    VideoModePtr->FrameLength = 0x10000;
                                    VideoModePtr->fbType |= VIDEO_MODE_BANKED;
                                }

                                if (ModeBlock->ModeAttributes & 0x40) {
                                    VideoModePtr->banktype = NormalBanking;
                                }

                                //
                                // Make sure there is enough memory for the mode
                                //

                                if ((VideoModePtr->wbytes * VideoModePtr->vres) <= TotalMemory) {
                                    ModeValid = TRUE;
                                } 
                            }
                        }
                    }

                    if (ModeValid) {

                        VideoDebugPrint((1, "Supported: %dx%dx%dbpp\n",
                                            VideoModePtr->hres,
                                            VideoModePtr->vres,
                                            VideoModePtr->bitsPerPlane));

                        VideoModePtr++;
                        AdditionalModes++;

                    } else {

                        VideoDebugPrint((1, "Rejecting: %dx%dx%dbpp\n",
                                            ModeBlock->XResolution,
                                            ModeBlock->YResolution,
                                            ModeBlock->BitsPerPixel));
                    }

                    ModePtr++;
                }

                //
                // Lets check to see if we can map the memory for one of these modes.
                // If not, don't support the extended modes.
                //
                // Note: this is a temporary hack, until I can implent the correct
                // fix.
                //

                VideoModePtr--;

                if (IS_LINEAR_MODE(VideoModePtr)) {

                    PHYSICAL_ADDRESS Address;
                    UCHAR inIoSpace = 0;

                    Address.LowPart = VideoModePtr->MemoryBase;
                    Address.HighPart = 0;
                    
#if defined(PLUG_AND_PLAY)
                    inIoSpace |= VIDEO_MEMORY_SPACE_P6CACHE;
#endif
                    Memory = VideoPortGetDeviceBase(hwDeviceExtension,
                                                    Address,
                                                    0x1000,
                                                    inIoSpace);

                    if (Memory) {

                        VideoPortFreeDeviceBase(hwDeviceExtension, Memory);

                    } else {

                        //
                        // We can't map the memory, so don't expose the extra modes.
                        //

                        VideoDebugPrint((0, "vga.sys: Mapping 0x%x failed\n", VideoModePtr->MemoryBase));
                        AdditionalModes = 0;
                    }
                }
            }

            VideoPortFreePool(hwDeviceExtension, InfoBlock);
        }
    }

    NumVideoModes += AdditionalModes;

} // VgaInitialize()

ULONG
GetVideoMemoryBaseAddress(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    PVIDEOMODE pRequestedMode
    )

/*++

Routine Description:

    This routine get the base address of the framebuffer of a given mode

Return Value:

    Base address of framebuffer

--*/

{
    PMODE_INFO_BLOCK ModeBlock;
    ULONG Length = 0x1000;
    INT10_BIOS_ARGUMENTS BiosArguments;
    PVIDEO_PORT_INT10_INTERFACE pInt10;
    ULONG RetValue;

    //
    // If this is not a vesa mode, just return the saved base address
    //

    if (pRequestedMode->fbType & VIDEO_MODE_BANKED) {

        return 0;
    }

    pInt10 = &hwDeviceExtension->Int10;

    if(!(pInt10->Size))
    {

        //
        // This structure should be initialized in VgaInitialize
        // If this function get called before VgaInitialize, just return 0;
        //

        return 0;
    }

    ModeBlock = VideoPortAllocatePool(hwDeviceExtension,
                                      VpPagedPool,
                                      sizeof(MODE_INFO_BLOCK),
                                      ' agV');

    if(!ModeBlock) {

        return 0;
    }

    //
    // Get info about the VESA mode.
    //

    BiosArguments.Eax = 0x4f01;
    BiosArguments.Ecx = pRequestedMode->Int10ModeNumber >> 16;
    BiosArguments.Edi = hwDeviceExtension->VdmOff;
    BiosArguments.SegEs = hwDeviceExtension->VdmSeg;

    pInt10->Int10CallBios(pInt10->Context, &BiosArguments);

    if ((BiosArguments.Eax & 0xffff) == VESA_STATUS_SUCCESS) {

        //
        // Copy the mode information out of the csrss process
        //

        pInt10->Int10ReadMemory(pInt10->Context,
                                hwDeviceExtension->VdmSeg,
                                hwDeviceExtension->VdmOff,
                                ModeBlock,
                                sizeof(MODE_INFO_BLOCK));

        RetValue = ModeBlock->PhysBasePtr;

     } else {

        RetValue = 0;
    }

    VideoPortFreePool(hwDeviceExtension, ModeBlock);
    return( RetValue );

}

VP_STATUS
VBEGetModeInfo(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    USHORT ModeNumber,
    PMODE_INFO_BLOCK ModeInfoBlock
    )
{
    INT10_BIOS_ARGUMENTS Int10BiosArguments;
    PVIDEO_PORT_INT10_INTERFACE pInt10;
    VP_STATUS status;

    pInt10 = &hwDeviceExtension->Int10;

    Int10BiosArguments.Eax = VBE_GET_MODE_INFO;
    Int10BiosArguments.Ecx = ModeNumber;
    Int10BiosArguments.Edi = hwDeviceExtension->VdmOff;
    Int10BiosArguments.SegEs = hwDeviceExtension->VdmSeg;

    status = pInt10->Int10CallBios(pInt10->Context, &Int10BiosArguments);

    if (status == NO_ERROR && 
        (Int10BiosArguments.Eax & 0xffff) == VESA_STATUS_SUCCESS) {

        //
        // Copy the mode information out of the csrss process
        //

        status = pInt10->Int10ReadMemory(pInt10->Context,
                                         hwDeviceExtension->VdmSeg,
                                         hwDeviceExtension->VdmOff,
                                         ModeInfoBlock,
                                         sizeof(MODE_INFO_BLOCK));

        if (status == NO_ERROR) {
            return NO_ERROR;
        }
    }

    return ERROR_INVALID_PARAMETER;
}

VP_STATUS
VBESetMode(
    PHW_DEVICE_EXTENSION HwDeviceExtension,
    USHORT VesaModeNumber
    )
{
    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    VP_STATUS status;

    biosArguments.Eax = VBE_SET_MODE;
    biosArguments.Ebx = VesaModeNumber;

    status = VideoPortInt10(HwDeviceExtension, &biosArguments);

    if ((status == NO_ERROR) && 
        ((biosArguments.Eax & 0x0000FFFF) == VESA_STATUS_SUCCESS)) {

        return NO_ERROR;
    }

    return ERROR_INVALID_PARAMETER;
}

USHORT
VBEGetMode(
    PHW_DEVICE_EXTENSION HwDeviceExtension
    )
{
    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    VP_STATUS status;

    biosArguments.Eax = VBE_GET_MODE;

    status = VideoPortInt10(HwDeviceExtension, &biosArguments);

    if ((status == NO_ERROR) && 
        ((biosArguments.Eax & 0x0000FFFF) == VESA_STATUS_SUCCESS)) {

        return (USHORT)(biosArguments.Ebx & 0x0000FFFF) ;

    } else {

        return 0;
    }
}

ULONG
VBESaveState(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    PCHAR StateBuffer
    )
{
    INT10_BIOS_ARGUMENTS Int10BiosArguments;
    PVIDEO_PORT_INT10_INTERFACE pInt10;
    VP_STATUS status;
    ULONG Size;

    pInt10 = &hwDeviceExtension->Int10;

    Int10BiosArguments.Eax = VBE_SAVE_RESTORE_STATE;
    Int10BiosArguments.Edx = 0x0;

    //
    // Save all the state
    //

    Int10BiosArguments.Ecx = 0x0F;

    status = pInt10->Int10CallBios(pInt10->Context, &Int10BiosArguments);

    if (status != NO_ERROR || 
        (Int10BiosArguments.Eax & 0xffff) != VESA_STATUS_SUCCESS) {

        return 0;
    }

    Size = (Int10BiosArguments.Ebx & 0xffff) << 6 ;

    //
    // if StateBuffer is NULL, the caller is only want to know the size 
    // of the buffer needed to store the state 
    //

    if (StateBuffer == NULL) {
        return Size;
    }

    Int10BiosArguments.Eax = VBE_SAVE_RESTORE_STATE;
    Int10BiosArguments.Edx = 0x1;
    Int10BiosArguments.Ecx = 0x0F;
    Int10BiosArguments.Ebx = hwDeviceExtension->VdmOff;
    Int10BiosArguments.SegEs = hwDeviceExtension->VdmSeg;

    status = pInt10->Int10CallBios(pInt10->Context, &Int10BiosArguments);

    if (status == NO_ERROR && 
        (Int10BiosArguments.Eax & 0xffff) == VESA_STATUS_SUCCESS) {

        //
        // Copy the state data of the csrss process
        //

        status = pInt10->Int10ReadMemory(pInt10->Context,
                                         hwDeviceExtension->VdmSeg,
                                         hwDeviceExtension->VdmOff,
                                         StateBuffer,
                                         Size);
        if (status == NO_ERROR) {

            return Size;
        }
    }

    return 0;
}

VP_STATUS
VBERestoreState(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    PCHAR StateBuffer,
    ULONG Size
    )
{
    INT10_BIOS_ARGUMENTS Int10BiosArguments;
    PVIDEO_PORT_INT10_INTERFACE pInt10;
    VP_STATUS status;

    pInt10 = &hwDeviceExtension->Int10;

    //
    // Copy the state data to the csrss process
    //

    status = pInt10->Int10WriteMemory(pInt10->Context,
                                      hwDeviceExtension->VdmSeg,
                                      hwDeviceExtension->VdmOff,
                                      StateBuffer,
                                      Size);

    if (status != NO_ERROR) {
        return ERROR_INVALID_PARAMETER;
    }

    Int10BiosArguments.Eax = VBE_SAVE_RESTORE_STATE;
    Int10BiosArguments.Edx = 0x2;
    Int10BiosArguments.Ecx = 0x0f;
    Int10BiosArguments.Ebx = hwDeviceExtension->VdmOff;
    Int10BiosArguments.SegEs = hwDeviceExtension->VdmSeg;

    status = pInt10->Int10CallBios(pInt10->Context, &Int10BiosArguments);

    if (status != NO_ERROR ||
        (Int10BiosArguments.Eax & 0xffff) != VESA_STATUS_SUCCESS) {

        return ERROR_INVALID_PARAMETER;
    }

    return NO_ERROR;
}

VP_STATUS
VBESetDisplayWindow(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    UCHAR WindowSelect,
    USHORT WindowNumber
    )

/*++

Routine Description:

    This routine set the position of the specified window in the
    frame buffer memory

Arguments:

    HwDeviceExtension  
        Pointer to the miniport driver's adapter information.

    WindowSelect
        0 for Window A and 1 for Window B

    WindowNumber
        Window number in video memory in window granularity units

Return Value:

    VP_STATUS

--*/

{
    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    VP_STATUS status;

    biosArguments.Eax = VBE_WINDOW_CONTROL;
    biosArguments.Ebx = WindowSelect & 0x01;
    biosArguments.Edx = WindowNumber;

    status = VideoPortInt10(hwDeviceExtension, &biosArguments);

    if ((status != NO_ERROR) || 
        ((biosArguments.Eax & 0x0000FFFF) != VESA_STATUS_SUCCESS)) {

        return ERROR_INVALID_PARAMETER;
    }

    return NO_ERROR;
}

USHORT
VBEGetDisplayWindow(
    PHW_DEVICE_EXTENSION hwDeviceExtension,
    UCHAR WindowSelect
    )

/*++

Routine Description:

    This routine set the position of the specified window in the
    frame buffer memory

Arguments:

    HwDeviceExtension
        Pointer to the miniport driver's adapter information.

    WindowSelect 
        0 for Window A and 1 for Window B

Return Value:

    Window number in video memory in window granularity units

--*/

{
    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    VP_STATUS status;

    biosArguments.Eax = VBE_WINDOW_CONTROL;
    biosArguments.Ebx = (WindowSelect & 0x1) | 0x100;

    status = VideoPortInt10(hwDeviceExtension, &biosArguments);

    if ((status != NO_ERROR) || 
        ((biosArguments.Eax & 0x0000FFFF) != VESA_STATUS_SUCCESS)) {

        return 0;
    }

    return ((USHORT)(biosArguments.Edx & 0xFFFF));
}

USHORT
VBEGetScanLineLength(
    PHW_DEVICE_EXTENSION HwDeviceExtension
    )
{
    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    VP_STATUS status;

    biosArguments.Eax = VBE_SCANLINE;
    biosArguments.Ebx = 0x1;

    status = VideoPortInt10(HwDeviceExtension, &biosArguments);

    if ((status == NO_ERROR) && 
        ((biosArguments.Eax & 0x0000FFFF) == VESA_STATUS_SUCCESS)) {

        return (USHORT)(biosArguments.Ebx & 0x0000FFFF) ;

    } else {

        return 0;
    }
}

VP_STATUS
VesaSaveHardwareState(
    PHW_DEVICE_EXTENSION HwDeviceExtension,
    PVIDEO_HARDWARE_STATE HardwareState,
    ULONG HardwareStateSize,
    USHORT ModeNumber
    )
{
    PVIDEO_HARDWARE_STATE_HEADER hardwareStateHeader;
    VP_STATUS status;
    ULONG FrameBufferSize;
    PMODE_INFO_BLOCK ModeInfoBlock;
    PVESA_INFO pVesaInfo;

    hardwareStateHeader = 
            (PVIDEO_HARDWARE_STATE_HEADER) HardwareState->StateHeader;

    //
    // Zero out the structure
    //

    VideoPortZeroMemory((PVOID) hardwareStateHeader, 
                         sizeof(VIDEO_HARDWARE_STATE_HEADER));

    //
    // Set the Header field
    //

    hardwareStateHeader->Length = sizeof(VIDEO_HARDWARE_STATE_HEADER);
    hardwareStateHeader->VGAStateFlags |= VIDEO_STATE_UNEMULATED_VGA_STATE;

    hardwareStateHeader->VesaInfoOffset = 
                        (sizeof(VIDEO_HARDWARE_STATE_HEADER) + 7) & ~7;
 
    pVesaInfo = (PVESA_INFO)((PCHAR)hardwareStateHeader + 
                              hardwareStateHeader->VesaInfoOffset);

    //
    // Check the size needed to store hardware state
    //

    if (!(pVesaInfo->HardwareStateSize = 
                     VBESaveState(HwDeviceExtension, NULL))) {

        return ERROR_INVALID_FUNCTION;
    }

    //
    // In the case the size needed is too big just retrun failure
    // This should not happen in reality 
    // 

    if( VGA_TOTAL_STATE_SIZE < hardwareStateHeader->VesaInfoOffset + 
                               sizeof(VESA_INFO) + 
                               pVesaInfo->HardwareStateSize) {

        return ERROR_INVALID_FUNCTION;
    }

    //
    // Save hardware state
    //

    if (pVesaInfo->HardwareStateSize !=  
                   VBESaveState(HwDeviceExtension, pVesaInfo->HardwareState)) {

        return ERROR_INVALID_FUNCTION;
    }

    pVesaInfo->ModeNumber = ModeNumber;

    ModeInfoBlock = &(pVesaInfo->ModeInfoBlock);

    //
    // Retrieve mode info
    //

    if( VBEGetModeInfo(HwDeviceExtension, 
                       ModeNumber, 
                       ModeInfoBlock) != NO_ERROR) {

        return ERROR_INVALID_FUNCTION;
    }

    //
    // Save framebuffer
    //

    hardwareStateHeader->FrameBufferData = 
                         SaveFrameBuffer(HwDeviceExtension, pVesaInfo);

    if(hardwareStateHeader->FrameBufferData) {

        return NO_ERROR;

    } else {

        return ERROR_NOT_ENOUGH_MEMORY;  
    }
}

PCHAR
SaveFrameBuffer(
    PHW_DEVICE_EXTENSION hwDeviceExtension, 
    PVESA_INFO pVesaInfo
    ) 
{
    ULONG FrameBufferSize, BankSize, CopySize, LeftSize, k;
    USHORT i;
    PCHAR FrameBufferData, pFrameBuffer;
    PHYSICAL_ADDRESS FBPhysicalAddress; 
    PMODE_INFO_BLOCK ModeInfoBlock;

    ModeInfoBlock = (PMODE_INFO_BLOCK) &(pVesaInfo->ModeInfoBlock);

    //
    // We'll try to get the current value of scanline size just in case a DOS 
    // app changed it. But we stay on the value we have if the vesa function
    // is not supported or failed.
    //

    i = VBEGetScanLineLength(hwDeviceExtension);

    if(i) { 
        
        ModeInfoBlock->BytesPerScanLine = i;
    }

    // 
    // 1) Calculate Framebuffer size
    // 

    //
    // Check if it is graphics or text mode. For text mode we simply
    // assume a size of 32k
    //

    if (ModeInfoBlock->ModeAttributes & 0x10) {

        FrameBufferSize = ModeInfoBlock->BytesPerScanLine * 
                          ModeInfoBlock->YResolution;

    } else {

        FrameBufferSize = 0x8000;
    }

    pVesaInfo->FrameBufferSize = FrameBufferSize;

    // 
    // 2) Determine the location and the size to be mapped and map it
    // 

    if (!(ModeInfoBlock->ModeAttributes & 0x10)) {

        //
        // This is a text mode
        //

        FBPhysicalAddress.HighPart = 0;
        FBPhysicalAddress.LowPart = ModeInfoBlock->WinASegment << 4;

        if( FBPhysicalAddress.LowPart == 0) {

            FBPhysicalAddress.LowPart = 0xB8000;
        }

        BankSize = 0x8000;
        
    } else if (pVesaInfo->ModeNumber & 0x4000) {

        //
        // Linear framebuffer can be viewed as one large bank
        //

        FBPhysicalAddress.LowPart = ModeInfoBlock->PhysBasePtr;
        FBPhysicalAddress.HighPart = 0;
        BankSize = FrameBufferSize;

    } else {

        //
        // This is a banked mode
        //

        FBPhysicalAddress.HighPart = 0;
        FBPhysicalAddress.LowPart = ModeInfoBlock->WinASegment << 4;

        if( FBPhysicalAddress.LowPart == 0) {

            FBPhysicalAddress.LowPart = 0xA0000;
        }

        BankSize = 1024 * ModeInfoBlock->WinSize;

        //
        // The bank size shouldn't exceed 64k. But we'd better guard 
        // the bad BIOS
        //

        if(BankSize > 0x10000 || BankSize == 0) {
            return NULL;
        }

        //
        // k will be used later to translate the window number 
        // in the unit of WinSize to the window number in the 
        // unit of WinGranularity
        //
 
        if (ModeInfoBlock->WinGranularity) {

           k = ModeInfoBlock->WinSize/ModeInfoBlock->WinGranularity;

        } else {

           k = 1;
        }
    }

    if(( pFrameBuffer = VideoPortGetDeviceBase(hwDeviceExtension, 
                                               FBPhysicalAddress,
                                               BankSize,
                                               FALSE)) == NULL ) {
        return NULL;
    }

    //
    // 3) Allocate memory for framebuffer data
    //
    
    if((FrameBufferData = VideoPortAllocatePool(hwDeviceExtension,
                                                VpPagedPool,
                                                FrameBufferSize,
                                                ' agV')) == NULL) {

        VideoPortFreeDeviceBase(hwDeviceExtension, pFrameBuffer);
        return NULL;
    }

    //
    // 4) Save famebuffer data
    //
    
    LeftSize = FrameBufferSize;

    for ( i = 0; LeftSize > 0; i++ ) {
    
        if (!(pVesaInfo->ModeNumber & 0x4000)) {

            // 
            // If this is a banked mode, switch to the right bank.
            // We set both Window A and B, as some VBEs have these 
            // set as separately available read and write windows.
            //

            VBESetDisplayWindow(hwDeviceExtension, 0, i * (USHORT)k);
            VBESetDisplayWindow(hwDeviceExtension, 1, i * (USHORT)k);
        }

        CopySize = (LeftSize < BankSize) ? LeftSize : BankSize;

        VideoPortMoveMemory(FrameBufferData + i * BankSize, 
                            pFrameBuffer, 
                            CopySize); 

        LeftSize -= CopySize;
    }

    // 
    // 5) Relese resource
    // 

    VideoPortFreeDeviceBase(hwDeviceExtension, pFrameBuffer);

    return FrameBufferData;
}

BOOLEAN
IsSavedModeVesa(
    PVIDEO_HARDWARE_STATE HardwareState
    )
{
    PVIDEO_HARDWARE_STATE_HEADER hardwareStateHeader;

    hardwareStateHeader = 
                (PVIDEO_HARDWARE_STATE_HEADER) HardwareState->StateHeader;

    if (hardwareStateHeader->Length == sizeof(VIDEO_HARDWARE_STATE_HEADER) &&
        hardwareStateHeader->VesaInfoOffset ) {

        return TRUE;

    } else {

        return FALSE;
    }
}


VP_STATUS
VesaRestoreHardwareState(
    PHW_DEVICE_EXTENSION HwDeviceExtension,
    PVIDEO_HARDWARE_STATE HardwareState,
    ULONG HardwareStateSize
    )
{

    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    PVIDEO_HARDWARE_STATE_HEADER hardwareStateHeader;
    PMODE_INFO_BLOCK ModeInfoBlock;
    PVESA_INFO pVesaInfo;
    VP_STATUS status;

    hardwareStateHeader = 
           (PVIDEO_HARDWARE_STATE_HEADER) HardwareState->StateHeader;

    pVesaInfo = (PVESA_INFO)((PCHAR)hardwareStateHeader + 
                                    hardwareStateHeader->VesaInfoOffset);

    // 
    // 
    // 1) set the original mode
    // 2) restore hardware state 
    // 
    // Please note that both steps are necessary
    // 

    // 
    // We always use default CRTC value
    // 

    VBESetMode (HwDeviceExtension, pVesaInfo->ModeNumber & (~0x800));
              
    if ( VBERestoreState(HwDeviceExtension, 
                         pVesaInfo->HardwareState,
                         pVesaInfo->HardwareStateSize) != NO_ERROR ) {

        return ERROR_INVALID_FUNCTION;
    }

    ModeInfoBlock = (PMODE_INFO_BLOCK) &(pVesaInfo->ModeInfoBlock);

    //
    // Restore framebuffer data
    //

    if(RestoreFrameBuffer(HwDeviceExtension, 
                          pVesaInfo,
                          hardwareStateHeader->FrameBufferData)) {

        hardwareStateHeader->FrameBufferData = 0;
        return NO_ERROR;

    } else {

        return ERROR_INVALID_PARAMETER;
    }
}

ULONG
RestoreFrameBuffer(
    PHW_DEVICE_EXTENSION HwDeviceExtension, 
    PVESA_INFO pVesaInfo,
    PCHAR FrameBufferData
    ) 
{
    ULONG FrameBufferSize, BankSize, CopySize, LeftSize, k;
    PHYSICAL_ADDRESS FBPhysicalAddress; 
    USHORT i, WinA, WinB;
    PCHAR pFrameBuffer;
    PMODE_INFO_BLOCK ModeInfoBlock;

    if(!FrameBufferData) {

        return 0;
    }

    ModeInfoBlock = (PMODE_INFO_BLOCK) &(pVesaInfo->ModeInfoBlock);

    // 
    // 1) Get Framebuffer size
    // 

    FrameBufferSize = pVesaInfo->FrameBufferSize;

    if (!FrameBufferSize) {

        return 0;
    }

    // 
    // 2) Determine the location and the size to be mapped and map it
    // 

    if (!(ModeInfoBlock->ModeAttributes & 0x10)) {

        //
        // This is a text mode
        //

        FBPhysicalAddress.HighPart = 0;
        FBPhysicalAddress.LowPart = ModeInfoBlock->WinASegment << 4;

        if( FBPhysicalAddress.LowPart == 0) {

            FBPhysicalAddress.LowPart = 0xB8000;
        }

        BankSize = 0x8000;
        
    } else if (pVesaInfo->ModeNumber & 0x4000) {

        //
        // Linear framebuffer can be viewed as one large bank
        //

        FBPhysicalAddress.LowPart = ModeInfoBlock->PhysBasePtr;
        FBPhysicalAddress.HighPart = 0;
        BankSize = FrameBufferSize;

    } else {

        //
        // This is a banked mode
        //

        FBPhysicalAddress.HighPart = 0;
        FBPhysicalAddress.LowPart = ModeInfoBlock->WinASegment << 4;

        if( FBPhysicalAddress.LowPart == 0) {

            FBPhysicalAddress.LowPart = 0xA0000;
        }

        BankSize = 1024 * ModeInfoBlock->WinSize;

        //
        // The bank size shouldn't exceed 64k. But we'd better guard 
        // the bad BIOS
        //

        if(BankSize > 0x10000 || BankSize == 0) {
            return 0;
        }

        //
        // k will be used later to translate the window number 
        // in the unit of WinSize to the window number in the 
        // unit of WinGranularity
        //
 
        if (ModeInfoBlock->WinGranularity) {

            k = ModeInfoBlock->WinSize/ModeInfoBlock->WinGranularity;

        } else {

           k = 1;
        }

    }

    if((pFrameBuffer = VideoPortGetDeviceBase(HwDeviceExtension, 
                                              FBPhysicalAddress,
                                              FrameBufferSize,
                                              FALSE)) == NULL) {
        return 0;
    }

    // 
    // 3) Restore framebuffer data
    // 

    // 
    // For banked mode we need to save the current bank number before
    // we change it.
    // 

    if (!(pVesaInfo->ModeNumber & 0x4000)) {

        // 
        // We need to save the curren window number for banked mode
        // 

        WinA = VBEGetDisplayWindow(HwDeviceExtension, 0);
        WinB = VBEGetDisplayWindow(HwDeviceExtension, 1);

    }

    LeftSize = FrameBufferSize;

    for (i = 0; LeftSize > 0; i++) {
    
        if (!(pVesaInfo->ModeNumber & 0x4000)) {

            // 
            // This is a banked mode.
            // 
            // We need set both Window A and B, as some VBEs have these 
            // set as separately available read and write windows.
            //

            VBESetDisplayWindow(HwDeviceExtension, 0, i * (USHORT)k);
            VBESetDisplayWindow(HwDeviceExtension, 1, i * (USHORT)k);
        }

        CopySize = (LeftSize < BankSize) ? LeftSize : BankSize;

        VideoPortMoveMemory(pFrameBuffer, 
                            FrameBufferData + i * BankSize, 
                            CopySize); 

        LeftSize -= CopySize;
    }

    if (!(pVesaInfo->ModeNumber & 0x4000)) {

        // 
        // For banked mode we need to restore the window number after
        // we changed it.
        // 

        VBESetDisplayWindow(HwDeviceExtension, 0, WinA);
        VBESetDisplayWindow(HwDeviceExtension, 1, WinB);
    }


    // 
    // 4) Relese resource
    // 

    VideoPortFreeDeviceBase(HwDeviceExtension, pFrameBuffer);
    VideoPortFreePool(HwDeviceExtension, FrameBufferData);

    return FrameBufferSize;
}