Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2080 lines
52 KiB

/*++
Copyright (c) 1990-2000 Microsoft Corporation
Module Name:
agp.c
Abstract:
This is the agp portion of the video port driver.
Author:
Erick Smith (ericks) Oct. 1997
Environment:
kernel mode only
Revision History:
--*/
#include "videoprt.h"
#define AGP_PAGE_SIZE PAGE_SIZE
#define AGP_BLOCK_SIZE (AGP_PAGE_SIZE * 16)
#define AGP_CLUSTER_SIZE (AGP_BLOCK_SIZE * 16)
#define PAGES_PER_BLOCK (AGP_BLOCK_SIZE / AGP_PAGE_SIZE)
#define BLOCKS_PER_CLUSTER (AGP_CLUSTER_SIZE / AGP_BLOCK_SIZE)
PVOID
AllocateReservedRegion(
IN HANDLE ProcessHandle,
IN ULONG Pages
);
BOOLEAN
UpdateReservedRegion(
IN PFDO_EXTENSION fdoExtension,
IN PVIRTUAL_RESERVE_CONTEXT VirtualContext,
IN ULONG Pages,
IN ULONG Offset
);
VOID
ReleaseReservedRegion(
IN HANDLE ProcessHandle,
IN PVOID VirtualAddress,
IN ULONG Pages
);
#if DBG
VOID
DumpBitField(
PREGION Region
);
#endif
#pragma alloc_text(PAGE,VpQueryAgpInterface)
#pragma alloc_text(PAGE,AgpReservePhysical)
#pragma alloc_text(PAGE,AgpReleasePhysical)
#pragma alloc_text(PAGE,AgpCommitPhysical)
#pragma alloc_text(PAGE,AgpFreePhysical)
#pragma alloc_text(PAGE,AgpReserveVirtual)
#pragma alloc_text(PAGE,AgpReleaseVirtual)
#pragma alloc_text(PAGE,AgpCommitVirtual)
#pragma alloc_text(PAGE,AgpFreeVirtual)
#pragma alloc_text(PAGE,AgpSetRate)
#pragma alloc_text(PAGE,VideoPortGetAgpServices)
#pragma alloc_text(PAGE,VpGetAgpServices2)
#pragma alloc_text(PAGE,AllocateReservedRegion)
#pragma alloc_text(PAGE,UpdateReservedRegion)
#pragma alloc_text(PAGE,ReleaseReservedRegion)
#pragma alloc_text(PAGE,CreateBitField)
#pragma alloc_text(PAGE,ModifyRegion)
#pragma alloc_text(PAGE,FindFirstRun)
#if DBG
#pragma alloc_text(PAGE,DumpBitField)
#endif
#if DBG
VOID
DumpBitField(
PREGION Region
)
{
ULONG i;
ULONG Index = 0;
USHORT Mask = 1;
ASSERT(Region != NULL);
for (i=0; i<Region->Length; i++) {
if (Mask & Region->BitField[Index]) {
pVideoDebugPrint((1, "1"));
} else {
pVideoDebugPrint((1, "0"));
}
Mask <<= 1;
if (Mask == 0) {
Index++;
Mask = 1;
}
}
pVideoDebugPrint((1, "\n"));
}
#endif
BOOLEAN
CreateBitField(
PREGION *Region,
ULONG Length
)
/*++
Routine Description:
This routine creates and initializes a bitfield.
Arguments:
Length - Number of items to track.
Region - Location in which to store the pointer to the REGION handle.
Returns:
TRUE - the the bitfield was created successfully,
FALSE - otherwise.
--*/
{
ULONG NumWords = (Length + 15) / 16;
BOOLEAN bRet = FALSE;
PREGION Buffer;
ASSERT(Length != 0);
Buffer = (PREGION) ExAllocatePoolWithTag(PagedPool | POOL_COLD_ALLOCATION, sizeof(REGION) + (NumWords - 1) * sizeof(USHORT), VP_TAG);
if (Buffer) {
Buffer->Length = Length;
Buffer->NumWords = NumWords;
RtlZeroMemory(Buffer->BitField, NumWords * sizeof(USHORT));
bRet = TRUE;
}
*Region = Buffer;
return bRet;
}
VOID
ModifyRegion(
PREGION Region,
ULONG Offset,
ULONG Length,
BOOLEAN Set
)
/*++
Routine Description:
Sets 'Length' bits starting at position 'Offset' in the bitfield.
Arguments:
Region - Pointer to the region to modify.
Offset - Offset into the bitfield at which to start.
Length - Number of bits to set.
Set - TRUE if you want to set the region, FALSE to clear it.
--*/
{
ULONG Index = Offset / 16;
ULONG Count = ((Offset + Length - 1) / 16) - Index;
USHORT lMask = ~((1 << (Offset & 15)) - 1);
USHORT rMask = ((1 << ((Offset + Length - 1) & 15)) * 2) - 1;
PUSHORT ptr = &Region->BitField[Index];
ASSERT(Region != NULL);
ASSERT(Length != 0);
if (Count == 0) {
//
// Only one WORD is modified, so combine left and right masks.
//
lMask &= rMask;
}
if (Set) {
*ptr++ |= lMask;
while (Count > 1) {
*ptr++ |= 0xFFFF;
Count--;
}
if (Count) {
*ptr |= rMask;
}
} else {
*ptr++ &= ~lMask;
while (Count > 1) {
*ptr++ &= 0;
Count--;
}
if (Count) {
*ptr++ &= ~rMask;
}
}
#if DBG
pVideoDebugPrint((1, "Current BitField for Region: 0x%x\n", Region));
//DumpBitField(Region);
#endif
}
BOOLEAN
FindFirstRun(
PREGION Region,
PULONG Offset,
PULONG Length
)
/*++
Routine Description:
This routine finds the first run of bits in a bitfield.
Arguments:
Region - Pointer to the region to operate on.
Offset - Pointer to a ULONG to hold the offset of the run.
Length - Pointer to a ULONG to hold the length of a run.
Returns:
TRUE if a run was detected,
FALSE otherwise.
--*/
{
PUSHORT ptr = Region->BitField;
ULONG Index = 0;
USHORT BitMask;
ULONG lsb;
ULONG Count;
USHORT ptrVal;
ASSERT(Region != NULL);
ASSERT(Offset != NULL);
ASSERT(Length != NULL);
while ((Index < Region->NumWords) && (*ptr == 0)) {
ptr++;
Index++;
}
if (Index == Region->NumWords) {
return FALSE;
}
//
// Find least significant bit
//
lsb = 0;
ptrVal = *ptr;
BitMask = 1;
while ((ptrVal & BitMask) == 0) {
BitMask <<= 1;
lsb++;
}
*Offset = (Index * 16) + lsb;
//
// Determine the run length
//
Count = 0;
while (Index < Region->NumWords) {
if (ptrVal & BitMask) {
BitMask <<= 1;
Count++;
if (BitMask == 0) {
BitMask = 0x1;
Index++;
ptrVal = *++ptr;
while ((ptrVal == 0xFFFF) && (Index < Region->NumWords)) {
Index++;
Count += 16;
ptrVal = *ptr++;
}
}
} else {
break;
}
}
*Length = Count;
return TRUE;
}
BOOLEAN
VpQueryAgpInterface(
PFDO_EXTENSION FdoExtension,
USHORT InterfaceVersion
)
/*++
Routine Description:
Send a QueryInterface Irp to our parent (the PCI bus driver) to
retrieve the AGP_BUS_INTERFACE.
Returns:
NT_STATUS code
--*/
{
KEVENT Event;
PIRP QueryIrp = NULL;
IO_STATUS_BLOCK IoStatusBlock;
PIO_STACK_LOCATION NextStack;
NTSTATUS Status;
ASSERT(FdoExtension != NULL);
KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
QueryIrp = IoBuildSynchronousFsdRequest(IRP_MJ_FLUSH_BUFFERS,
FdoExtension->AttachedDeviceObject,
NULL,
0,
NULL,
&Event,
&IoStatusBlock);
if (QueryIrp == NULL) {
return FALSE;
}
NextStack = IoGetNextIrpStackLocation(QueryIrp);
//
// Set the default error code.
//
QueryIrp->IoStatus.Status = IoStatusBlock.Status = STATUS_NOT_SUPPORTED;
//
// Set up for a QueryInterface Irp.
//
NextStack->MajorFunction = IRP_MJ_PNP;
NextStack->MinorFunction = IRP_MN_QUERY_INTERFACE;
NextStack->Parameters.QueryInterface.InterfaceType = &GUID_AGP_BUS_INTERFACE_STANDARD;
NextStack->Parameters.QueryInterface.Size = sizeof(AGP_BUS_INTERFACE_STANDARD);
NextStack->Parameters.QueryInterface.Version = InterfaceVersion;
NextStack->Parameters.QueryInterface.Interface = (PINTERFACE) &FdoExtension->AgpInterface;
NextStack->Parameters.QueryInterface.InterfaceSpecificData = NULL;
FdoExtension->AgpInterface.Size = sizeof(AGP_BUS_INTERFACE_STANDARD);
FdoExtension->AgpInterface.Version = InterfaceVersion;
Status = IoCallDriver(FdoExtension->AttachedDeviceObject, QueryIrp);
if (Status == STATUS_PENDING) {
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
Status = IoStatusBlock.Status;
}
return NT_SUCCESS(Status);
}
PHYSICAL_ADDRESS
AgpReservePhysical(
IN PVOID Context,
IN ULONG Pages,
IN VIDEO_PORT_CACHE_TYPE Caching,
OUT PVOID *PhysicalReserveContext
)
/*++
Routine Description:
Reserves a range of physical addresses for AGP.
Arguments:
Context - The Agp Context
Pages - Number of pages to reserve
Caching - Specifies the type of caching to use
PhysicalReserveContext - Location to store our reservation context.
Returns:
The base of the physical address range reserved.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
PHYSICAL_ADDRESS PhysicalAddress = {0,0};
NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
PPHYSICAL_RESERVE_CONTEXT ReserveContext;
PVOID MapHandle;
ULONG Blocks;
MEMORY_CACHING_TYPE CacheType;
ASSERT(PhysicalReserveContext != NULL);
ASSERT(Caching <= VpCached);
Pages = (Pages + PAGES_PER_BLOCK - 1) & ~(PAGES_PER_BLOCK - 1);
Blocks = Pages / PAGES_PER_BLOCK;
pVideoDebugPrint((1, "AGP: Reserving 0x%x Pages of Address Space\n", Pages));
switch (Caching) {
case VpNonCached: CacheType = MmNonCached; break;
case VpWriteCombined: CacheType = MmWriteCombined; break;
case VpCached: CacheType = MmCached; break;
}
ReserveContext = ExAllocatePoolWithTag(PagedPool | POOL_COLD_ALLOCATION,
sizeof(PHYSICAL_RESERVE_CONTEXT),
VP_TAG);
if (ReserveContext) {
RtlZeroMemory(ReserveContext, sizeof(PHYSICAL_RESERVE_CONTEXT));
if (CreateBitField(&ReserveContext->MapTable, Blocks)) {
if (CreateBitField(&ReserveContext->Region, Pages)) {
status = fdoExtension->AgpInterface.ReserveMemory(
fdoExtension->AgpInterface.AgpContext,
Pages,
CacheType,
&MapHandle,
&PhysicalAddress);
if (NT_SUCCESS(status)) {
ReserveContext->Pages = Pages;
ReserveContext->Caching = CacheType;
ReserveContext->MapHandle = MapHandle;
ReserveContext->PhysicalAddress = PhysicalAddress;
}
}
}
}
if (NT_SUCCESS(status) == FALSE) {
if (ReserveContext) {
if (ReserveContext->Region) {
ExFreePool(ReserveContext->Region);
}
if (ReserveContext->MapTable) {
ExFreePool(ReserveContext->MapTable);
}
ExFreePool(ReserveContext);
ReserveContext = NULL;
}
PhysicalAddress.QuadPart = 0;
}
*PhysicalReserveContext = ReserveContext;
return PhysicalAddress;
}
VOID
AgpReleasePhysical(
PVOID Context,
PVOID PhysicalReserveContext
)
/*++
Routine Description:
Releases a range of reserved physical address.
Arguments:
Context - The Agp Context
PhysicalReserveContext - The reservation context.
Returns:
none.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
PPHYSICAL_RESERVE_CONTEXT ReserveContext;
ULONG Pages;
ULONG Offset;
ASSERT(PhysicalReserveContext != NULL);
ReserveContext = (PPHYSICAL_RESERVE_CONTEXT) PhysicalReserveContext;
pVideoDebugPrint((1, "AGP: Releasing 0x%x Pages of Address Space\n", ReserveContext->Pages));
//
// Make sure all pages have been freed
//
while (FindFirstRun(ReserveContext->Region, &Offset, &Pages)) {
AgpFreePhysical(Context, PhysicalReserveContext, Pages, Offset);
}
fdoExtension->AgpInterface.ReleaseMemory(fdoExtension->AgpInterface.AgpContext,
ReserveContext->MapHandle);
ExFreePool(ReserveContext->Region);
ExFreePool(ReserveContext->MapTable);
ExFreePool(ReserveContext);
}
BOOLEAN
AgpCommitPhysical(
PVOID Context,
PVOID PhysicalReserveContext,
ULONG Pages,
ULONG Offset
)
/*++
Routine Description:
Locks down system memory and backs a portion of the reserved region.
Arguments:
Context - The Agp Context
PhysicalReserveContext - The reservation context.
Pages - Number of pages to commit.
Offset - The offset into the reserved region at which to commit the pages.
Returns:
TRUE if successful,
FALSE otherwise.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
PHYSICAL_ADDRESS MemoryBase = {0,0};
PPHYSICAL_RESERVE_CONTEXT ReserveContext;
NTSTATUS status;
PMDL Mdl;
ULONG StartBlock = Offset / PAGES_PER_BLOCK;
ULONG EndBlock = (Offset + Pages + PAGES_PER_BLOCK - 1) / PAGES_PER_BLOCK;
ULONG i;
PUSHORT MapTable;
PUSHORT BitField;
ASSERT(PhysicalReserveContext != NULL);
ASSERT(Pages != 0);
ReserveContext = (PPHYSICAL_RESERVE_CONTEXT) PhysicalReserveContext;
MapTable = ReserveContext->MapTable->BitField;
BitField = ReserveContext->Region->BitField;
//
// Try to commit the new pages. The agp filter driver handles
// the case where some of these pages are already committed, so
// lets try to get them all at once.
//
status =
fdoExtension->AgpInterface.CommitMemory(
fdoExtension->AgpInterface.AgpContext,
ReserveContext->MapHandle,
PAGES_PER_BLOCK * (EndBlock - StartBlock),
Offset & ~(PAGES_PER_BLOCK - 1),
NULL,
&MemoryBase);
if (NT_SUCCESS(status)) {
ModifyRegion(ReserveContext->Region, Offset, Pages, TRUE);
for (i=StartBlock; i<EndBlock; i++) {
ULONG Cluster = i / BLOCKS_PER_CLUSTER;
ULONG Block = 1 << (i & (BLOCKS_PER_CLUSTER - 1));
//
// Update the MapTable for the committed pages.
//
MapTable[Cluster] |= Block;
}
} else {
pVideoDebugPrint((0, "Commit Physical failed with status: 0x%x\n", status));
}
return NT_SUCCESS(status);
}
VOID
AgpFreePhysical(
IN PVOID Context,
IN PVOID PhysicalReserveContext,
IN ULONG Pages,
IN ULONG Offset
)
/*++
Routine Description:
Releases the memory used to back a portion of the reserved region.
Arguments:
Context - The Agp Context
PhysicalReserveContext - The reservation context.
Pages - Number of pages to release.
Offset - The offset into the reserved region at which to release the pages.
Returns:
none.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
PPHYSICAL_RESERVE_CONTEXT ReserveContext;
PMDL Mdl;
ULONG StartBlock = Offset / PAGES_PER_BLOCK;
ULONG EndBlock = (Offset + Pages + PAGES_PER_BLOCK - 1) / PAGES_PER_BLOCK;
ULONG i;
PUSHORT MapTable;
PUSHORT BitField;
ASSERT(PhysicalReserveContext != NULL);
ASSERT(Pages != 0);
ReserveContext = (PPHYSICAL_RESERVE_CONTEXT) PhysicalReserveContext;
MapTable = ReserveContext->MapTable->BitField;
BitField = ReserveContext->Region->BitField;
ModifyRegion(ReserveContext->Region, Offset, Pages, FALSE);
//
// Postion the offset to the start of the first block
//
Offset = Offset & ~(PAGES_PER_BLOCK - 1);
for (i=StartBlock; i<EndBlock; i++) {
ULONG Cluster = i / BLOCKS_PER_CLUSTER;
ULONG Block = 1 << (i & (BLOCKS_PER_CLUSTER - 1));
//
// If this block is mapped, then release it.
//
if ((BitField[i] == 0) && (MapTable[Cluster] & Block)) {
fdoExtension->AgpInterface.FreeMemory(
fdoExtension->AgpInterface.AgpContext,
ReserveContext->MapHandle,
PAGES_PER_BLOCK,
Offset);
MapTable[Cluster] &= ~Block;
}
//
// Go to the next 64k block
//
Offset += PAGES_PER_BLOCK;
}
}
PVOID
AgpReserveVirtual(
IN PVOID Context,
IN HANDLE ProcessHandle,
IN PVOID PhysicalReserveContext,
OUT PVOID *VirtualReserveContext
)
/*++
Routine Description:
Reserves a range of virtual addresses for AGP.
Arguments:
Context - The Agp Context
ProcessHandle - The handle of the process in which to reserve the
virtual address range.
PhysicalReserveContext - The physical reservation context to assoctiate
with the given virtual reservation.
VirtualReserveContext - The location in which to store the virtual
reserve context.
Returns:
The base of the virtual address range reserved.
Notes:
You can't reserve a range of kernel address space, but if you want to
commit into kernel space you still need a reservation handle. Pass in
NULL for the process handle in this case.
For the moment, we'll commit the entire region when the do a reservation
in kernel space. Then Commit and Free will be no-ops.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
NTSTATUS status = STATUS_SUCCESS;
ULONG Protect = PAGE_READWRITE;
PVIRTUAL_RESERVE_CONTEXT ReserveContext;
PPHYSICAL_RESERVE_CONTEXT PhysicalContext;
PVOID VirtualAddress = NULL;
PEPROCESS Process = NULL;
ULONG Blocks;
ASSERT(PhysicalReserveContext != NULL);
ASSERT(VirtualReserveContext != NULL);
PhysicalContext = (PPHYSICAL_RESERVE_CONTEXT) PhysicalReserveContext;
Blocks = (PhysicalContext->Pages + PAGES_PER_BLOCK - 1) / PAGES_PER_BLOCK;
ReserveContext = ExAllocatePoolWithTag(PagedPool | POOL_COLD_ALLOCATION,
sizeof(VIRTUAL_RESERVE_CONTEXT),
VP_TAG);
if (ReserveContext) {
RtlZeroMemory(ReserveContext, sizeof(VIRTUAL_RESERVE_CONTEXT));
if (CreateBitField(&ReserveContext->MapTable, Blocks)) {
if (CreateBitField(&ReserveContext->Region, PhysicalContext->Pages)) {
if (PhysicalContext->Caching == MmNonCached) {
Protect |= PAGE_NOCACHE;
}
//
// Make sure we have the real process handle.
//
if (ProcessHandle == NtCurrentProcess()) {
Process = PsGetCurrentProcess();
}
ReserveContext->ProcessHandle = ProcessHandle;
ReserveContext->Process = Process;
ReserveContext->PhysicalReserveContext =
(PPHYSICAL_RESERVE_CONTEXT) PhysicalReserveContext;
if (ProcessHandle) {
VirtualAddress =
AllocateReservedRegion(
ProcessHandle,
PhysicalContext->Pages);
} else {
//
// For a kernel reservation, go ahead and commit the
// entire range.
//
if (fdoExtension->AgpInterface.Capabilities &
AGP_CAPABILITIES_MAP_PHYSICAL)
{
//
// CPU can access AGP memory through AGP aperature.
//
VirtualAddress =
MmMapIoSpace(PhysicalContext->PhysicalAddress,
PhysicalContext->Pages * AGP_PAGE_SIZE,
PhysicalContext->Caching);
//
// Not all systems support USWC, so if we attempted to map USWC
// and failed, try again with just non-cached.
//
if ((VirtualAddress == NULL) &&
(PhysicalContext->Caching != MmNonCached)) {
pVideoDebugPrint((1, "Attempt to map cached memory failed. Try uncached.\n"));
VirtualAddress = MmMapIoSpace(PhysicalContext->PhysicalAddress,
PhysicalContext->Pages * AGP_PAGE_SIZE,
MmNonCached);
}
} else {
PMDL Mdl;
//
// Get the MDL for the range we are trying to map.
//
Mdl = MmCreateMdl(NULL, NULL, PhysicalContext->Pages * AGP_PAGE_SIZE);
if (Mdl) {
fdoExtension->AgpInterface.GetMappedPages(
fdoExtension->AgpInterface.AgpContext,
PhysicalContext->MapHandle,
PhysicalContext->Pages,
0,
Mdl);
Mdl->MdlFlags |= MDL_PAGES_LOCKED | MDL_MAPPING_CAN_FAIL;
//
// We must use the CPU's virtual memory mechanism to
// make the non-contiguous MDL look contiguous.
//
VirtualAddress =
MmMapLockedPagesSpecifyCache(
Mdl,
(KPROCESSOR_MODE)KernelMode,
PhysicalContext->Caching,
NULL,
TRUE,
HighPagePriority);
ExFreePool(Mdl);
}
}
}
ReserveContext->VirtualAddress = VirtualAddress;
}
}
}
//
// If anything failed, make sure we clean everything up.
//
if (VirtualAddress == NULL) {
if (ReserveContext) {
if (ReserveContext->Region) {
ExFreePool(ReserveContext->Region);
}
if (ReserveContext->MapTable) {
ExFreePool(ReserveContext->MapTable);
}
ExFreePool(ReserveContext);
ReserveContext = NULL;
}
}
*VirtualReserveContext = ReserveContext;
return VirtualAddress;
}
VOID
AgpReleaseVirtual(
IN PVOID Context,
IN PVOID VirtualReserveContext
)
/*++
Routine Description:
Releases a range of reserved virtual addresses.
Arguments:
Context - The Agp Context
VirtualReserveContext - The reservation context.
Returns:
none.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
PVIRTUAL_RESERVE_CONTEXT VirtualContext;
PPHYSICAL_RESERVE_CONTEXT PhysicalContext;
BOOLEAN Attached = FALSE;
ULONG Offset;
ULONG Pages;
ASSERT(VirtualReserveContext != NULL);
VirtualContext = (PVIRTUAL_RESERVE_CONTEXT) VirtualReserveContext;
PhysicalContext = VirtualContext->PhysicalReserveContext;
if (VirtualContext->ProcessHandle) {
//
// Make sure all pages have been freed
//
while (FindFirstRun(VirtualContext->Region, &Offset, &Pages)) {
AgpFreeVirtual(Context, VirtualReserveContext, Pages, Offset);
}
//
// Now release all the reserved pages
//
if (VirtualContext->ProcessHandle == NtCurrentProcess()) {
if (VirtualContext->Process != PsGetCurrentProcess()) {
KeAttachProcess(PEProcessToPKProcess(VirtualContext->Process));
Attached = TRUE;
}
}
ReleaseReservedRegion(
VirtualContext->ProcessHandle,
VirtualContext->VirtualAddress,
VirtualContext->PhysicalReserveContext->Pages);
if (Attached) {
KeDetachProcess();
}
} else {
//
// This was kernel virtual memory, so release the memory we
// committed at reserve time.
//
if (fdoExtension->AgpInterface.Capabilities &
AGP_CAPABILITIES_MAP_PHYSICAL)
{
MmUnmapIoSpace(VirtualContext->VirtualAddress,
PhysicalContext->Pages * AGP_PAGE_SIZE);
} else {
PMDL Mdl;
//
// Get the MDL for the range we are trying to free.
//
Mdl = MmCreateMdl(NULL, NULL, PhysicalContext->Pages * AGP_PAGE_SIZE);
if (Mdl) {
fdoExtension->AgpInterface.GetMappedPages(
fdoExtension->AgpInterface.AgpContext,
PhysicalContext->MapHandle,
PhysicalContext->Pages,
0,
Mdl);
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
Mdl->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA;
Mdl->MappedSystemVa = VirtualContext->VirtualAddress;
MmUnmapLockedPages(
VirtualContext->VirtualAddress,
Mdl);
ExFreePool(Mdl);
} else {
//
// We couldn't free the memory because we couldn't allocate
// memory for the MDL. We can free a small chunk at a time
// by using a MDL on the stack.
//
ASSERT(FALSE);
}
}
}
ExFreePool(VirtualContext->Region);
ExFreePool(VirtualContext->MapTable);
ExFreePool(VirtualContext);
}
PVOID
AllocateReservedRegion(
IN HANDLE ProcessHandle,
IN ULONG Pages
)
/*++
Routine Description:
Reserves a range of user mode virtual addresses.
Arguments:
ProcessHandle - The process in which we need to modify the mappings.
Pages - The number of pages to reserve.
Returns:
Pointer to the reserved region of memory.
--*/
{
NTSTATUS Status;
ULONG_PTR VirtualAddress = 0;
ULONG Blocks = (Pages + PAGES_PER_BLOCK - 1) / PAGES_PER_BLOCK;
//
// Pad the length so we can get an AGP_BLOCK_SIZE aligned region.
//
SIZE_T Length = Blocks * AGP_BLOCK_SIZE + AGP_BLOCK_SIZE - PAGE_SIZE;
ASSERT(ProcessHandle != 0);
ASSERT(Pages != 0);
//
// Find a chunk of virtual addresses where we can put our reserved
// region.
//
// Note: We are using ZwAllocateVirtualMemory to reserve the memory,
// but ZwMapViewOfSection to commit pages. Since ZwMapViewOfSection
// wants to align to 64K, lets try to get a 64K aligned pointer.
//
Status =
ZwAllocateVirtualMemory(
ProcessHandle,
(PVOID)&VirtualAddress,
0,
&Length,
MEM_RESERVE,
PAGE_READWRITE);
if (NT_SUCCESS(Status)) {
ULONG_PTR NewAddress = (VirtualAddress + AGP_BLOCK_SIZE - 1) & ~(AGP_BLOCK_SIZE - 1);
ULONG i;
pVideoDebugPrint((1, "Reserved 0x%x, length = 0x%x\n", VirtualAddress, Length));
//
// We were able to reserve a region of memory. Now lets free it, and
// reallocate in AGP_BLOCK_SIZE size blocks.
//
ZwFreeVirtualMemory(
ProcessHandle,
(PVOID)&VirtualAddress,
&Length,
MEM_RELEASE);
//
// Reserve the memory again in 64k chunks.
//
VirtualAddress = NewAddress;
Length = AGP_BLOCK_SIZE;
for (i=0; i<Blocks; i++) {
Status =
ZwAllocateVirtualMemory(
ProcessHandle,
(PVOID)&VirtualAddress,
0,
&Length,
MEM_RESERVE,
PAGE_READWRITE);
if (NT_SUCCESS(Status) == FALSE) {
break;
}
VirtualAddress += AGP_BLOCK_SIZE;
}
if (NT_SUCCESS(Status) == FALSE) {
//
// clean up and return error
//
VirtualAddress = NewAddress;
while (i--) {
ZwFreeVirtualMemory(
ProcessHandle,
(PVOID)&VirtualAddress,
&Length,
MEM_RELEASE);
VirtualAddress += AGP_BLOCK_SIZE;
}
//
// Indicate we failed to reserve the memory
//
pVideoDebugPrint((0, "We failed to allocate the reserved region\n"));
return NULL;
}
return (PVOID)NewAddress;
} else {
pVideoDebugPrint((0, "AllocateReservedRegion Failed: Status = 0x%x\n", Status));
return NULL;
}
}
VOID
ReleaseReservedRegion(
IN HANDLE ProcessHandle,
IN PVOID VirtualAddress,
IN ULONG Pages
)
/*++
Routine Description:
Reserves a range of user mode virtual addresses.
Arguments:
ProcessHandle - The process in which we need to modify the mappings.
Pages - The number of pages to reserve.
Returns:
Pointer to the reserved region of memory.
--*/
{
NTSTATUS Status;
ULONG_PTR RunningVirtualAddress = (ULONG_PTR)VirtualAddress;
ULONG Blocks = (Pages + PAGES_PER_BLOCK - 1) / PAGES_PER_BLOCK;
ULONG i;
SIZE_T Length = AGP_BLOCK_SIZE;
ASSERT(ProcessHandle != 0);
ASSERT(Pages != 0);
//
// Individually release each block we have reserved.
//
for (i=0; i<Blocks; i++) {
Status =
ZwFreeVirtualMemory(
ProcessHandle,
(PVOID)&RunningVirtualAddress,
&Length,
MEM_RELEASE);
RunningVirtualAddress += AGP_BLOCK_SIZE;
if (NT_SUCCESS(Status) == FALSE) {
pVideoDebugPrint((0, "ReleaseReservedRegion Failed: Status = 0x%x\n", Status));
ASSERT(FALSE);
}
}
}
NTSTATUS
MapBlock(
IN PFDO_EXTENSION fdoExtension,
IN PVIRTUAL_RESERVE_CONTEXT VirtualContext,
IN HANDLE ProcessHandle,
IN PHYSICAL_ADDRESS *PhysicalAddress,
IN PVOID VirtualAddress,
IN ULONG Protect,
IN BOOLEAN Release
)
/*++
Routine Desciption:
Notes:
This function assumes it is being calling within the context of the
correct process.
--*/
{
NTSTATUS ntStatus;
if (Release) {
if (fdoExtension->AgpInterface.Capabilities &
AGP_CAPABILITIES_MAP_PHYSICAL)
{
ZwUnmapViewOfSection(
VirtualContext->ProcessHandle,
VirtualAddress);
} else {
PMDL Mdl;
//
// Get the MDL for the range we are trying to map.
//
Mdl = MmCreateMdl(NULL, NULL, AGP_BLOCK_SIZE);
if (Mdl) {
ULONG Offset;
//
// Calculate the offset into the range
//
Offset = (ULONG)(((ULONG_PTR)VirtualAddress - (ULONG_PTR)VirtualContext->VirtualAddress) / PAGE_SIZE);
fdoExtension->AgpInterface.GetMappedPages(
fdoExtension->AgpInterface.AgpContext,
VirtualContext->PhysicalReserveContext->MapHandle,
AGP_BLOCK_SIZE / PAGE_SIZE,
Offset,
Mdl);
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
MmUnmapLockedPages(
VirtualAddress,
Mdl);
ExFreePool(Mdl);
} else {
//
// We couldn't free the memory because we couldn't allocate
// memory for the MDL. We can free a small chunk at a time
// by using a MDL on the stack.
//
ASSERT(FALSE);
}
}
ntStatus = STATUS_SUCCESS;
} else {
if (fdoExtension->AgpInterface.Capabilities &
AGP_CAPABILITIES_MAP_PHYSICAL)
{
HANDLE PhysicalMemoryHandle;
//
// CPU can access AGP memory through AGP aperature.
//
//
// Get a handle to the physical memory section using our pointer.
// If this fails, return.
//
ntStatus =
ObOpenObjectByPointer(
PhysicalMemorySection,
0L,
(PACCESS_STATE) NULL,
SECTION_ALL_ACCESS,
(POBJECT_TYPE) NULL,
KernelMode,
&PhysicalMemoryHandle);
//
// If successful, map the memory.
//
if (NT_SUCCESS(ntStatus)) {
SIZE_T Length = AGP_BLOCK_SIZE;
pVideoDebugPrint((2, "Mapping VA 0x%x for 0x%x bytes.\n",
VirtualAddress,
Length));
ntStatus =
ZwMapViewOfSection(
PhysicalMemoryHandle,
ProcessHandle,
&VirtualAddress,
0,
AGP_BLOCK_SIZE,
PhysicalAddress,
&Length,
ViewUnmap,
0,
Protect);
if (NT_SUCCESS(ntStatus) == FALSE) {
pVideoDebugPrint((1, "ntStatus = 0x%x\n", ntStatus));
}
ZwClose(PhysicalMemoryHandle);
}
} else {
PMDL Mdl;
//
// Get the MDL for the range we are trying to map.
//
Mdl = MmCreateMdl(NULL, NULL, AGP_BLOCK_SIZE);
if (Mdl) {
ULONG Offset;
//
// Calculate the offset into the range
//
Offset = (ULONG)(((ULONG_PTR)VirtualAddress - (ULONG_PTR)VirtualContext->VirtualAddress) / PAGE_SIZE);
fdoExtension->AgpInterface.GetMappedPages(
fdoExtension->AgpInterface.AgpContext,
VirtualContext->PhysicalReserveContext->MapHandle,
AGP_BLOCK_SIZE / PAGE_SIZE,
Offset,
Mdl);
Mdl->MdlFlags |= MDL_PAGES_LOCKED | MDL_MAPPING_CAN_FAIL;
//
// We must use the CPU's virtual memory mechanism to
// make the non-contiguous MDL look contiguous.
//
VirtualAddress =
MmMapLockedPagesSpecifyCache(
Mdl,
(KPROCESSOR_MODE)UserMode,
VirtualContext->PhysicalReserveContext->Caching,
(PVOID)VirtualAddress,
TRUE,
HighPagePriority);
ASSERT(VirtualAddress);
ExFreePool(Mdl);
}
ntStatus = STATUS_SUCCESS;
}
}
return ntStatus;
}
NTSTATUS
UpdateBlock(
IN HANDLE ProcessHandle,
IN PVOID VirtualAddress,
IN ULONG Protect,
IN BOOLEAN Release
)
/*++
Routine Description:
Marks a region of user mode memory as being either reserved, or
available.
Arguments:
ProcessHandle - The process in which we need to modify the mappings.
VirtualAddress - The address to update
Protect - Caching attributes
Release - TRUE release the block, FALSE reserve it.
Returns:
Status of the operation.
--*/
{
NTSTATUS Status;
SIZE_T Length = AGP_BLOCK_SIZE;
pVideoDebugPrint((1, "Update VA 0x%x. Action = %s\n",
VirtualAddress,
Release ? "Release" : "Reserve"));
if (Release) {
Status =
ZwFreeVirtualMemory(
ProcessHandle,
&VirtualAddress,
&Length,
MEM_RELEASE);
} else {
Status =
ZwAllocateVirtualMemory(
ProcessHandle,
&VirtualAddress,
0,
&Length,
MEM_RESERVE,
Protect);
}
return Status;
}
BOOLEAN
UpdateReservedRegion(
IN PFDO_EXTENSION fdoExtension,
IN PVIRTUAL_RESERVE_CONTEXT VirtualContext,
IN ULONG Pages,
IN ULONG Offset
)
/*++
Routine Description:
Ensure that the range of pages specified is correctly reserved/released.
Arguments:
fdoExtension - The device extension for the miniport.
VirtualContext - The context for the reserved region to update.
Pages - The number of 4K pages to reserve.
Offset - The offset into the reserved region to update.
Release - TRUE if we are releasing memory, FALSE otherwise.
Returns:
TRUE success, FALSE at least a partial failure occured.
Notes:
No cleanup is done on failure. So part of the range may ultimately
be mapped and the rest not. This is ok. The only side effect is that
even though we return a failure we did do part of the work. Our
internal data structures remain in a consistent state.
--*/
{
ULONG StartBlock = Offset / PAGES_PER_BLOCK;
ULONG EndBlock = (Offset + Pages + PAGES_PER_BLOCK - 1) / PAGES_PER_BLOCK;
ULONG Protect;
ULONG i;
NTSTATUS Status;
PUSHORT BitField = VirtualContext->Region->BitField;
PUSHORT MapTable = VirtualContext->MapTable->BitField;
HANDLE Process = VirtualContext->ProcessHandle;
PVOID VirtualAddress = (PUCHAR)VirtualContext->VirtualAddress + StartBlock * AGP_BLOCK_SIZE;
PHYSICAL_ADDRESS PhysicalAddress;
BOOLEAN bRet = TRUE;
ASSERT(VirtualContext != NULL);
ASSERT(Pages != 0);
//
// Calculate the effective Physical Address
//
PhysicalAddress = VirtualContext->PhysicalReserveContext->PhysicalAddress;
PhysicalAddress.QuadPart += StartBlock * AGP_BLOCK_SIZE;
//
// Determine the appropriate page protection
//
if (VirtualContext->PhysicalReserveContext->Caching != MmNonCached) {
Protect = PAGE_READWRITE | PAGE_WRITECOMBINE;
} else {
Protect = PAGE_READWRITE | PAGE_NOCACHE;
}
for (i=StartBlock; i<EndBlock; i++) {
ULONG Cluster = i / BLOCKS_PER_CLUSTER;
ULONG Block = 1 << (i & (BLOCKS_PER_CLUSTER - 1));
if ((BitField[i] == 0) && (MapTable[Cluster] & Block)) {
//
// Unmap user mode memory
//
Status = MapBlock(fdoExtension, VirtualContext, Process, &PhysicalAddress, VirtualAddress, Protect, TRUE);
if (NT_SUCCESS(Status) == FALSE) {
pVideoDebugPrint((0, "MapBlock(TRUE) failed. Status = 0x%x\n", Status));
ASSERT(FALSE);
bRet = FALSE;
}
//
// Reserve the memory so we can map into it again in the
// future.
//
Status = UpdateBlock(Process, VirtualAddress, PAGE_READWRITE, FALSE);
MapTable[Cluster] &= ~Block;
if (NT_SUCCESS(Status) == FALSE) {
pVideoDebugPrint((0, "UpdateBlock(FALSE) failed. Status = 0x%x\n", Status));
ASSERT(FALSE);
bRet = FALSE;
}
} else if ((BitField[i]) && ((MapTable[Cluster] & Block) == 0)) {
//
// Release claim on memory so we can map it.
//
Status = UpdateBlock(Process, VirtualAddress, PAGE_READWRITE, TRUE);
MapTable[Cluster] |= Block;
if (NT_SUCCESS(Status) == FALSE) {
pVideoDebugPrint((0, "UpdateBlock(TRUE) failed. Status = 0x%x\n", Status));
ASSERT(FALSE);
bRet = FALSE;
}
//
// Map the pages into the user mode process
//
Status = MapBlock(fdoExtension, VirtualContext, Process, &PhysicalAddress, VirtualAddress, Protect, FALSE);
if (NT_SUCCESS(Status) == FALSE) {
pVideoDebugPrint((0, "MapBlock(FALSE) failed. Status = 0x%x\n", Status));
ASSERT(FALSE);
bRet = FALSE;
}
}
//
// Go to the next 64k block
//
VirtualAddress = (PUCHAR)VirtualAddress + AGP_BLOCK_SIZE;
PhysicalAddress.QuadPart += AGP_BLOCK_SIZE;
}
return bRet;
}
PVOID
AgpCommitVirtual(
IN PVOID Context,
IN PVOID VirtualReserveContext,
IN ULONG Pages,
IN ULONG Offset
)
/*++
Routine Description:
Reserves a range of physical addresses for AGP.
Arguments:
Context - The Agp Context
VirtualReserveContext - The reservation context.
Pages - Number of pages to commit.
Offset - The offset into the reserved region at which to commit the pages.
Returns:
The virtual address for the base of the commited pages.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
PVIRTUAL_RESERVE_CONTEXT VirtualContext;
PPHYSICAL_RESERVE_CONTEXT PhysicalContext;
PVOID VirtualAddress = NULL;
NTSTATUS ntStatus;
BOOLEAN Attached = FALSE;
ASSERT(VirtualReserveContext != NULL);
ASSERT(Pages >= 1);
VirtualContext = (PVIRTUAL_RESERVE_CONTEXT) VirtualReserveContext;
PhysicalContext = VirtualContext->PhysicalReserveContext;
//
// Confirm that the pages being committed fit into the reserved
// region.
//
// We only need to check the last page they are trying to commit. If
// it is not in the reserved region then we need to fail.
//
if ((Offset + Pages) > PhysicalContext->Pages) {
pVideoDebugPrint((1, "Attempt to commit pages outside of reserved region\n"));
ASSERT(FALSE);
return NULL;
}
//
// Calculate the effective virtual address.
//
VirtualAddress = ((PUCHAR)VirtualContext->VirtualAddress + Offset * AGP_PAGE_SIZE);
if (VirtualContext->ProcessHandle) {
//
// Make sure we are in the correct process context.
//
if (VirtualContext->ProcessHandle == NtCurrentProcess()) {
if (VirtualContext->Process != PsGetCurrentProcess()) {
KeAttachProcess(PEProcessToPKProcess(VirtualContext->Process));
Attached = TRUE;
}
}
ModifyRegion(VirtualContext->Region, Offset, Pages, TRUE);
//
// Update the virtual address space.
//
if (UpdateReservedRegion(fdoExtension,
VirtualContext,
Pages,
Offset) == FALSE) {
//
// Part of the commit failed. Indicate this by returning
// a NULL.
//
VirtualAddress = NULL;
}
//
// Restore initial process context.
//
if (Attached) {
KeDetachProcess();
}
} else {
//
// Kernel mode commit. Do nothing, the memory is already mapped.
//
}
return VirtualAddress;
}
VOID
AgpFreeVirtual(
IN PVOID Context,
IN PVOID VirtualReserveContext,
IN ULONG Pages,
IN ULONG Offset
)
/*++
Routine Description:
Frees a range of virtual addresses.
Arguments:
Context - The Agp Context
VirtualReserveContext - The reservation context.
Pages - Number of pages to release.
Offset - The offset into the reserved region at which to release the pages.
Returns:
none.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(Context);
PVIRTUAL_RESERVE_CONTEXT VirtualContext;
PPHYSICAL_RESERVE_CONTEXT PhysicalContext;
PVOID VirtualAddress;
BOOLEAN Attached=FALSE;
NTSTATUS Status;
ASSERT(VirtualReserveContext != NULL);
VirtualContext = (PVIRTUAL_RESERVE_CONTEXT) VirtualReserveContext;
PhysicalContext = VirtualContext->PhysicalReserveContext;
VirtualAddress = (PUCHAR)((ULONG_PTR)VirtualContext->VirtualAddress + Offset * AGP_PAGE_SIZE);
//
// Make sure we are in the correct process context.
//
if (VirtualContext->ProcessHandle != NULL) {
if (VirtualContext->ProcessHandle == NtCurrentProcess()) {
if (VirtualContext->Process != PsGetCurrentProcess()) {
KeAttachProcess(PEProcessToPKProcess(VirtualContext->Process));
Attached = TRUE;
}
}
ModifyRegion(VirtualContext->Region, Offset, Pages, FALSE);
UpdateReservedRegion(fdoExtension,
VirtualContext,
Pages,
Offset);
if (Attached) {
KeDetachProcess();
}
} else {
//
// Kernel Space Free - do nothing.
//
}
}
BOOLEAN
AgpSetRate(
IN PVOID Context,
IN ULONG AgpRate
)
/*++
Routine Description:
Thsi function sets chipset's AGP rate.
Arguments:
Context - The Agp Context
AgpRate - The Agp rate to set.
Returns:
TRUE if successful,
FALSE otherwise.
--*/
{
PFDO_EXTENSION fdoExtension;
NTSTATUS ntStatus;
BOOLEAN bStatus = FALSE;
ASSERT(NULL != Context);
fdoExtension = GET_FDO_EXT(Context);
ASSERT(NULL != fdoExtension);
if (fdoExtension->AgpInterface.Version > VIDEO_PORT_AGP_INTERFACE_VERSION_1)
{
ASSERT(NULL != fdoExtension->AgpInterface.AgpContext);
//
// Try to set chipset's AGP rate.
//
ntStatus = fdoExtension->AgpInterface.SetRate(fdoExtension->AgpInterface.AgpContext, AgpRate);
bStatus = NT_SUCCESS(ntStatus);
}
return bStatus;
}
BOOLEAN
VideoPortGetAgpServices(
IN PVOID HwDeviceExtension,
OUT PVIDEO_PORT_AGP_SERVICES AgpServices
)
/*++
Routine Description:
This routine returns a set of AGP services to the caller.
Arguments:
HwDeviceExtension - Pointer to the miniports device extension
AgpServices - A buffer in which to place the AGP services.
Returns:
TRUE if successful,
FALSE otherwise.
--*/
{
PFDO_EXTENSION fdoExtension = GET_FDO_EXT(HwDeviceExtension);
SYSTEM_BASIC_INFORMATION basicInfo;
NTSTATUS status;
ASSERT(HwDeviceExtension != NULL);
ASSERT(AgpServices != NULL);
//
// This entry point is only valid for PnP Drivers.
//
if ((fdoExtension->Flags & LEGACY_DRIVER) == 0) {
if (VpQueryAgpInterface(fdoExtension, AGP_BUS_INTERFACE_V1)) {
//
// Fill in the list of function pointers.
//
AgpServices->AgpReservePhysical = AgpReservePhysical;
AgpServices->AgpCommitPhysical = AgpCommitPhysical;
AgpServices->AgpFreePhysical = AgpFreePhysical;
AgpServices->AgpReleasePhysical = AgpReleasePhysical;
AgpServices->AgpReserveVirtual = AgpReserveVirtual;
AgpServices->AgpCommitVirtual = AgpCommitVirtual;
AgpServices->AgpFreeVirtual = AgpFreeVirtual;
AgpServices->AgpReleaseVirtual = AgpReleaseVirtual;
AgpServices->AllocationLimit = VpSystemMemorySize / 8;
pVideoDebugPrint((Trace, "VIDEOPRT: AGP system information success.\n"));
return TRUE;
} else {
pVideoDebugPrint((0, "VIDEOPRT: Failed AGP system information.\n"));
return FALSE;
}
} else {
pVideoDebugPrint((1, "VideoPortGetAgpServices - only valid on PnP drivers\n"));
return FALSE;
}
}
VP_STATUS
VpGetAgpServices2(
IN PVOID pHwDeviceExtension,
OUT PVIDEO_PORT_AGP_INTERFACE_2 pAgpInterface
)
/*++
Routine Description:
This routine returns a set of AGP services to the caller.
Arguments:
pHwDeviceExtension - Pointer to the miniports device extension
pAgpInterface - A buffer in which to place the AGP services.
Returns:
TRUE if successful,
FALSE otherwise.
--*/
{
PFDO_EXTENSION pFdoExtension;
SYSTEM_BASIC_INFORMATION basicInfo;
NTSTATUS ntStatus;
ASSERT(NULL != pHwDeviceExtension);
ASSERT(NULL != pAgpInterface);
pFdoExtension = GET_FDO_EXT(pHwDeviceExtension);
//
// This entry point is only valid for PnP Drivers.
//
if ((pFdoExtension->Flags & LEGACY_DRIVER) == 0)
{
if (VpQueryAgpInterface(pFdoExtension, AGP_BUS_INTERFACE_V2))
{
//
// Fill in an interface structure.
//
pAgpInterface->Context = pHwDeviceExtension;
pAgpInterface->InterfaceReference = VpInterfaceDefaultReference;
pAgpInterface->InterfaceDereference = VpInterfaceDefaultDereference;
pAgpInterface->AgpReservePhysical = AgpReservePhysical;
pAgpInterface->AgpCommitPhysical = AgpCommitPhysical;
pAgpInterface->AgpFreePhysical = AgpFreePhysical;
pAgpInterface->AgpReleasePhysical = AgpReleasePhysical;
pAgpInterface->AgpReserveVirtual = AgpReserveVirtual;
pAgpInterface->AgpCommitVirtual = AgpCommitVirtual;
pAgpInterface->AgpFreeVirtual = AgpFreeVirtual;
pAgpInterface->AgpReleaseVirtual = AgpReleaseVirtual;
pAgpInterface->AgpSetRate = AgpSetRate;
pAgpInterface->AgpAllocationLimit = VpSystemMemorySize / 8;
//
// Reference the interface before handing it out.
//
pAgpInterface->InterfaceReference(pAgpInterface->Context);
pVideoDebugPrint((Trace, "VIDEOPRT!VideoPortGetAgpServices2: AGP system information success.\n"));
return NO_ERROR;
}
else
{
pVideoDebugPrint((0, "VIDEOPRT!VideoPortGetAgpServices2: Failed AGP system information.\n"));
return ERROR_DEV_NOT_EXIST;
}
}
else
{
pVideoDebugPrint((1, "VIDEOPRT!VideoPortGetAgpServices2: Only valid on PnP drivers\n"));
return ERROR_DEV_NOT_EXIST;
}
}