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.
504 lines
14 KiB
504 lines
14 KiB
/***************************************************************************\
|
|
*
|
|
* ************************
|
|
* * MINIPORT SAMPLE CODE *
|
|
* ************************
|
|
*
|
|
* Module Name:
|
|
*
|
|
* interupt.c
|
|
*
|
|
* Abstract:
|
|
*
|
|
* This module contains code to control interrupts for Permedia 3.
|
|
*
|
|
* Environment:
|
|
*
|
|
* Kernel mode
|
|
*
|
|
*
|
|
* Copyright (c) 1994-1999 3Dlabs Inc. Ltd. All rights reserved.
|
|
* Copyright (c) 1995-2003 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
\***************************************************************************/
|
|
|
|
#include "perm3.h"
|
|
|
|
#pragma alloc_text(PAGE,Perm3InitializeInterruptBlock)
|
|
|
|
BOOLEAN
|
|
Perm3VideoInterrupt(
|
|
PVOID HwDeviceExtension
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Permedia3 interrupt service routine.
|
|
|
|
THIS ROUTINE CANNOT BE PAGED
|
|
|
|
Arguments:
|
|
|
|
HwDeviceExtension
|
|
Supplies a pointer to the miniport's device extension.
|
|
|
|
Return Value:
|
|
|
|
Return FALSE if it is not our interrupt. Otherwise, we'll dismiss the
|
|
interrupt on Permedia 3 before returning TRUE.
|
|
|
|
--*/
|
|
|
|
{
|
|
PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
|
|
PINTERRUPT_CONTROL_BLOCK pBlock;
|
|
ULONG intrFlags;
|
|
ULONG enableFlags;
|
|
ULONG backIndex;
|
|
ULONG bHaveCommandBlockMutex = FALSE;
|
|
ULONG errFlags = 0;
|
|
pPerm3ControlRegMap pCtrlRegs = hwDeviceExtension->ctrlRegBase[0];
|
|
|
|
pBlock = &hwDeviceExtension->InterruptControl.ControlBlock;
|
|
|
|
if(!hwDeviceExtension->InterruptControl.bInterruptsInitialized) {
|
|
|
|
//
|
|
// This is not our interrupt since we don't generate interrupt
|
|
// before the interrpt block got initialized
|
|
//
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
if (hwDeviceExtension->PreviousPowerState != VideoPowerOn) {
|
|
|
|
//
|
|
// We reach here because we are sharing IRQ with other devices
|
|
// and another device on the chain is in D0 and functioning
|
|
//
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Find out what caused the interrupt. We AND with the enabled interrupts
|
|
// since the flags are set if the event occurred even though no interrupt
|
|
// was enabled.
|
|
//
|
|
|
|
intrFlags = VideoPortReadRegisterUlong(INT_FLAGS);
|
|
enableFlags = VideoPortReadRegisterUlong(INT_ENABLE);
|
|
|
|
intrFlags &= enableFlags;
|
|
|
|
if (intrFlags == 0) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Clear the interrupts we detected.
|
|
//
|
|
|
|
VideoPortWriteRegisterUlong(INT_FLAGS, intrFlags);
|
|
VideoPortReadRegisterUlong(INT_FLAGS);
|
|
|
|
if((pBlock->Control & PXRX_CHECK_VFIFO_IN_VBLANK) ||
|
|
(intrFlags & INTR_ERROR_SET)) {
|
|
|
|
errFlags = VideoPortReadRegisterUlong(ERROR_FLAGS);
|
|
|
|
//
|
|
// Keep a record of the errors. It will help us to debug
|
|
// hardware issues
|
|
//
|
|
|
|
if (errFlags & ERROR_VFIFO_UNDERRUN) {
|
|
hwDeviceExtension->UnderflowErrors++;
|
|
}
|
|
|
|
if (errFlags & ERROR_OUT_FIFO) {
|
|
hwDeviceExtension->OutputFifoErrors++;
|
|
}
|
|
|
|
if (errFlags & ERROR_IN_FIFO) {
|
|
hwDeviceExtension->InputFifoErrors++;
|
|
}
|
|
|
|
if (errFlags) {
|
|
hwDeviceExtension->TotalErrors++;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Handle VBLANK interrupt
|
|
//
|
|
|
|
if (intrFlags & INTR_VBLANK_SET) {
|
|
|
|
//
|
|
// Need this only on very first interrupt but it's not a big thing.
|
|
//
|
|
|
|
pBlock->Control |= VBLANK_INTERRUPT_AVAILABLE;
|
|
|
|
//
|
|
// Get General Mutex
|
|
//
|
|
|
|
REQUEST_INTR_CMD_BLOCK_MUTEX((&(pBlock->General)), bHaveCommandBlockMutex);
|
|
|
|
if(bHaveCommandBlockMutex) {
|
|
|
|
ULONG ulValue;
|
|
|
|
//
|
|
// DirectDraw needs to have it's VBLANK flag set, when it
|
|
// sets the DIRECTDRAW_VBLANK_ENABLED bit.
|
|
//
|
|
|
|
if( pBlock->Control & (DIRECTDRAW_VBLANK_ENABLED | PXRX_SEND_ON_VBLANK_ENABLED | PXRX_CHECK_VFIFO_IN_VBLANK) ) {
|
|
|
|
if( pBlock->Control & DIRECTDRAW_VBLANK_ENABLED ) {
|
|
|
|
pBlock->DDRAW_VBLANK = TRUE;
|
|
}
|
|
|
|
//
|
|
// Don't need to do anything here. The actual processing is
|
|
// lower down, outside of the VBlank mutex.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Disable VBLANK interrupts. DD enables them when it needs to
|
|
//
|
|
|
|
VideoPortWriteRegisterUlong(INT_ENABLE, (enableFlags & ~INTR_ENABLE_VBLANK));
|
|
}
|
|
|
|
//
|
|
// If DMA was suspended till VBLANK then simulate a DMA interrupt
|
|
// to start it off again.
|
|
//
|
|
|
|
if (pBlock->Control & SUSPEND_DMA_TILL_VBLANK) {
|
|
|
|
pBlock->Control &= ~SUSPEND_DMA_TILL_VBLANK;
|
|
|
|
//
|
|
// execute the DMA interrupt code
|
|
//
|
|
|
|
intrFlags |= INTR_ERROR_SET;
|
|
}
|
|
|
|
RELEASE_INTR_CMD_BLOCK_MUTEX((&(pBlock->General)));
|
|
}
|
|
|
|
|
|
if( (pBlock->Control & PXRX_CHECK_VFIFO_IN_VBLANK) &&
|
|
(--hwDeviceExtension->VideoFifoControlCountdown == 0) ) {
|
|
|
|
//
|
|
// It's time to check the error flags for an underrun (we don't
|
|
// keep the error interrupt turned on for long because Perm3
|
|
// generates a lot of spurious host-in DMA errors)
|
|
//
|
|
|
|
if(enableFlags & INTR_ERROR_SET) {
|
|
|
|
//
|
|
// Turn off the error interrupts now and rely on the periodic VBLANK check
|
|
//
|
|
|
|
enableFlags &= ~INTR_ERROR_SET;
|
|
VideoPortWriteRegisterUlong(INT_ENABLE, enableFlags);
|
|
}
|
|
|
|
//
|
|
// Set-up counter for our periodic check
|
|
//
|
|
|
|
hwDeviceExtension->VideoFifoControlCountdown = NUM_VBLANKS_BETWEEN_VFIFO_CHECKS;
|
|
|
|
if(errFlags & ERROR_VFIFO_UNDERRUN) {
|
|
|
|
if((enableFlags & INTR_ERROR_SET) == 0) {
|
|
|
|
//
|
|
// We've got a video FIFO underrun error: turn on error
|
|
// interrupts for a little while in order to catch any
|
|
// other errors ASAP
|
|
//
|
|
|
|
hwDeviceExtension->VideoFifoControlCountdown = NUM_VBLANKS_AFTER_VFIFO_ERROR;
|
|
|
|
VideoPortWriteRegisterUlong(INT_ENABLE,
|
|
enableFlags | INTR_ERROR_SET);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Handle underrun error
|
|
//
|
|
|
|
if(errFlags & ERROR_VFIFO_UNDERRUN) {
|
|
|
|
ULONG highWater, lowWater;
|
|
|
|
//
|
|
// Clear the error
|
|
//
|
|
|
|
VideoPortWriteRegisterUlong(ERROR_FLAGS, ERROR_VFIFO_UNDERRUN);
|
|
|
|
//
|
|
// Lower the video FIFO thresholds. If the new upper threshold is 0
|
|
// (indicating the thresholds are currently both 1) then we can't
|
|
// go any lower, so just leave the underrun bit set (that way at
|
|
// least we won't get any more error interrupts)
|
|
//
|
|
|
|
highWater = ((hwDeviceExtension->VideoFifoControl >> 8) & 0xff) - 1;
|
|
|
|
if(highWater) {
|
|
|
|
//
|
|
// Load up the new FIFO control and clear the underrun bit.
|
|
// The lower threshold is set to 8 if the upper threshold
|
|
// is >= 15, otherwise it's set to 1/2 the upper threshold
|
|
//
|
|
|
|
lowWater = highWater >= 15 ? 8 : ((highWater + 1) >> 1);
|
|
|
|
hwDeviceExtension->VideoFifoControl = (1 << 16) |
|
|
(highWater << 8) |
|
|
lowWater;
|
|
|
|
do {
|
|
|
|
VideoPortWriteRegisterUlong(VIDEO_FIFO_CTL,
|
|
hwDeviceExtension->VideoFifoControl);
|
|
|
|
} while(VideoPortReadRegisterUlong(VIDEO_FIFO_CTL) & (1 << 16));
|
|
|
|
VideoDebugPrint((3, "Perm3: Setting new Video Fifo thresholds to %d and %d\n", highWater, lowWater));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Handle outfifo error
|
|
//
|
|
|
|
if(errFlags & ERROR_OUT_FIFO) {
|
|
|
|
//
|
|
// If we got here by generating an OutputFIFO error, clear it
|
|
//
|
|
|
|
VideoPortWriteRegisterUlong(ERROR_FLAGS, ERROR_OUT_FIFO);
|
|
|
|
#ifdef MASK_OUTFIFO_ERROR_INTERRUPT
|
|
enableFlags &= ~INTR_ERROR_SET;
|
|
VideoPortWriteRegisterUlong(INT_ENABLE, enableFlags);
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// The error interrupt occurs each time the display driver adds an entry
|
|
// to the queue. We treat it exactly as though we had received a DMA
|
|
// interrupt.
|
|
//
|
|
|
|
if (intrFlags & (INTR_DMA_SET | INTR_ERROR_SET)) {
|
|
|
|
//
|
|
// if suspended till VBLANK we can't do any DMA.
|
|
//
|
|
|
|
if (pBlock->Control & SUSPEND_DMA_TILL_VBLANK) {
|
|
|
|
VideoDebugPrint(( 1, "Perm3: DMA suspended till VBLANK\n"));
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// If the previous DMA has not completed we can't do anything. We've
|
|
// cleared the interrupt flag for this interrupt so even if the DMA
|
|
// completes before we return, we'll immediately get another
|
|
// interrupt. Since we will be getting another interrupt we do not
|
|
// have to clear the InterruptPending flag.
|
|
//
|
|
|
|
if (VideoPortReadRegisterUlong(DMA_COUNT) != 0) {
|
|
|
|
//
|
|
// DMA in progress, leaving
|
|
//
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// We may return without starting any DMA and hence not expecting any
|
|
// interrupt so clear the InterruptPending flag. This will force the
|
|
// display driver to wake us. MUST DO THIS BEFORE CHECKING THE QUEUE.
|
|
// Since the display driver adds entries and then checks the flag, we
|
|
// must do it in the reverse order.
|
|
//
|
|
|
|
pBlock->InterruptPending = 0;
|
|
|
|
//
|
|
// if the DMA queue is empty then we have nothing to do
|
|
//
|
|
|
|
backIndex = pBlock->backIndex;
|
|
|
|
if (pBlock->frontIndex == backIndex) {
|
|
|
|
//
|
|
// Queue is empty, leaving.
|
|
//
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// Since we know we'll get a DMA interrupt, we don't need a wakeup so
|
|
// set the InterruptPending flag to true.
|
|
//
|
|
|
|
pBlock->InterruptPending = 1;
|
|
|
|
//
|
|
// kick off DMA for the next Q entry and remove it. DO NOT remove from
|
|
// the queue first because on a multi-processor machine the display
|
|
// driver could modify the now free queue entry before we read it.
|
|
//
|
|
|
|
VideoPortWriteRegisterUlong(DMA_ADDRESS, pBlock->dmaQueue[backIndex].address);
|
|
VideoPortWriteRegisterUlong(DMA_COUNT, pBlock->dmaQueue[backIndex].count);
|
|
|
|
//
|
|
// Keep track of where the last DMA to start was from:
|
|
//
|
|
|
|
pBlock->lastAddr = pBlock->dmaQueue[backIndex].address;
|
|
|
|
//
|
|
// now remove the entry from the queue
|
|
//
|
|
|
|
if (++backIndex == pBlock->endIndex)
|
|
backIndex = 0;
|
|
|
|
pBlock->backIndex = backIndex;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
Perm3InitializeInterruptBlock(
|
|
PHW_DEVICE_EXTENSION hwDeviceExtension
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do any initialization needed for interrupts, such as allocating the shared
|
|
memory control block.
|
|
|
|
Arguments:
|
|
|
|
HwDeviceExtension - Supplies a pointer to the miniport's device extension.
|
|
|
|
Return Value:
|
|
|
|
TRUE
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID HwDeviceExtension = (PVOID)hwDeviceExtension;
|
|
PINTERRUPT_CONTROL_BLOCK pBlock;
|
|
PVOID SavedPtr;
|
|
PVOID pkdpc;
|
|
|
|
//
|
|
// This is set to zero since it is on longer used
|
|
//
|
|
|
|
hwDeviceExtension->InterruptControl.PhysAddress.LowPart =
|
|
hwDeviceExtension->InterruptControl.PhysAddress.HighPart = 0;
|
|
|
|
//
|
|
// Set up the control block
|
|
//
|
|
|
|
pBlock = &hwDeviceExtension->InterruptControl.ControlBlock;
|
|
|
|
//
|
|
// Initialize the circular DMA queue
|
|
//
|
|
|
|
pBlock->frontIndex = pBlock->backIndex = 0;
|
|
pBlock->maximumIndex = MAX_DMA_QUEUE_ENTRIES - 1;
|
|
|
|
//
|
|
// The size of the queue we actually use is dynamically configurable but
|
|
// initialize it to be as small as possible. This default size will work
|
|
// for all interrupt driven DMA buffers regardless of how many buffers
|
|
// are actually available.
|
|
//
|
|
|
|
pBlock->endIndex = 2;
|
|
|
|
//
|
|
// Initially no interrupts are available. Later we try to enable the
|
|
// interrupts and if they happen the interrupt handler will set the
|
|
// available bits in this word. So it's a sort of auto-sensing mechanism.
|
|
//
|
|
|
|
pBlock->Control = 0;
|
|
pBlock->InterruptPending = 0;
|
|
|
|
//
|
|
// Initialize the VBLANK interrupt command field
|
|
//
|
|
|
|
pBlock->VBCommand = NO_COMMAND;
|
|
|
|
//
|
|
// Initialize the General update in VBLANK fields (only used by P2)
|
|
//
|
|
|
|
pBlock->General.bDisplayDriverHasAccess = FALSE;
|
|
pBlock->General.bMiniportHasAccess = FALSE;
|
|
|
|
VideoPortZeroMemory( &pBlock->pxrxDMA, sizeof(pBlock->pxrxDMA) );
|
|
|
|
hwDeviceExtension->InterruptControl.bInterruptsInitialized = TRUE;
|
|
|
|
hwDeviceExtension->OutputFifoErrors = 0;
|
|
hwDeviceExtension->InputFifoErrors = 0;
|
|
hwDeviceExtension->UnderflowErrors = 0;
|
|
hwDeviceExtension->TotalErrors = 0;
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
|