|
|
/***************************************************************************\
* * ************************ * * 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); }
|