//-------------------------------------------------------------------------
// WINNT driver for PCIVME interface from ARW Elektronik, Germany ---------
// all around irq handling
//
// (c) 1999-2004 ARW Elektronik
//
// this source code is published under GPL (Open Source). You can use, redistrubute and
// modify it unless this header is not modified or deleted. No warranty is given that
// this software will work like expected.
// This product is not authorized for use as critical component in life support systems
// wihout the express written approval of ARW Elektronik Germany.
//
// Please announce changes and hints to ARW Elektronik
//
// $Log: pcivme_i.c,v $
// Revision 1.3 2004/07/24 07:07:26 klaus
// Update copyright to 2004
//
// Revision 1.2 2003/11/15 19:12:50 klaus
// Update copyright to 2003
//
// Revision 1.1.1.1 2003/11/14 23:16:33 klaus
// First put into repository
//
// Revision 1.3 2002/10/27 16:17:48 klaus
// Typing bug fixed caused at log addition
//
// Revision 1.2 2002/10/27 16:11:02 klaus
// Added CVS log into header
//
// what who when
// started AR 01.08.1999
// first release 1.0 AR 17.10.1999
// IoConnectInterrupt, share vector now true AR 04.03.2000
// changed resource allocation caused by WIN2000 AR 08.06.2002
//
//-------------------------------------------------------------------------
// COMMENTS
//
// Each path (file_obj) maintains its own FIFO to hold incoming vector from
// hardware acknowledged interrupts. These FIFOs are always accessible for read
// while the path is open. In case a path enables its interrupts the link to
// its FIFO is included in a list of "interrupt enabled" FIFOs. The opposite
// happens when a path disables its interrupts.
// There are up to MAX_PCIADA lists of "interrupt enabled" FIFOs, each list is
// associated to a PCIADA (pciada).
// Each time a pciada receives a interrupt request the software first checks
// for a bus error interrupt. This kind of requests are directly associated with
// a user initiated access and put forward to the accessor. (Excluding spurious
// interrupts). If the request is no bus error request the vector is pushed
// into all FIFOs queued for this PCIADA.
// After this a DPC call is fired to wake up queued IRPs waiting for a vector.
// The DPC call iterates through the list of FIFOs and looks if a queued IRP
// exists for this path. If so the vector is pulled off the FIFO and the IRP
// is released.
// If a IRP arrives to request a vector the driver checks if there is a vector
// stored in the path associated FIFO. If so the IRP returns immediately with
// the pulled vector from the FIFO. If not the IRP is queued in a PCIADA
// associated queue of pending IRPs.
// When the interrupts for a path are disabled all pending vectors in the FIFO
// associated with this path are still available. But if the FIFO is empty
// request to get the next vector will block.
//-------------------------------------------------------------------------
// INCLUDES
//
#include <ntddk.h>
#include <pcivme_drv.h>
#include <pcivme_i.h>
#include <pcivme.h>
#include <pciif.h>
#include <pcivme_fifo.h>
//------------------------------------------------------------------------
// DEFINES
//
#ifndef DWORD
#define DWORD ULONG
#endif
#ifndef WORD
#define WORD USHORT
#endif
//------------------------------------------------------------------------
// GLOBALS
//
//------------------------------------------------------------------------
// FUNCTIONS
//
//------------------------------------------------------------------------
// determine which interrupt and evaluates vector
//
static UCHAR evaluate_vector(PCIADA *pciada)
{
USHORT wIntCSR = READ_REGISTER_USHORT(pciada->pwIntCSR);
USHORT offset;
USHORT wCntrl = READ_REGISTER_USHORT(pciada->pwCntrl);
if (wCntrl & 0x100) // pciada switched on ?
{
if ((wIntCSR & 0x68) == 0x68)
{
// it's the pci interrupt # 2
// get current Cntrl - and clear interrupt
wCntrl &= ~0x0100; // disable
WRITE_REGISTER_USHORT(pciada->pwCntrl, wCntrl);
wCntrl |= 0x0100; // enable again
WRITE_REGISTER_USHORT(pciada->pwCntrl, wCntrl);
return 1;
}
if ((wIntCSR & 0x45) == 0x45)
{
// it's the interrupt # 1
offset = READ_REGISTER_USHORT(pciada->pwIRQStat);
if (offset & 1)
return 0xff & READ_REGISTER_UCHAR((PUCHAR)pciada->pbVector + offset);
}
}
return 0;
}
//------------------------------------------------------------------------
// enable and disable of interrupts
//
void globalInterruptEnable(PCIADA *pciada)
{
WRITE_REGISTER_USHORT(pciada->pwIntCSR, ENABLE_PCIADA_IRQS);
}
void globalInterruptDisable(PCIADA *pciada)
{
WRITE_REGISTER_USHORT(pciada->pwIntCSR, DISABLE_PCIADA_IRQS);
}
//------------------------------------------------------------------------
// insert the vector in a queue of interrupts (and rescue waiting IRPs)
//
static void InsertInIrqQueues(PCIADA *pciada, UCHAR vector)
{
FIFO_LIST *next;
KIRQL oldIrqlList;
register PLIST_ENTRY pList = &pciada->IrqListList;
KdPrint(("InsertInIrqQueues\n"));
// iterate all fifos and insert the vector in each fifo
KeAcquireSpinLock(&pciada->IrqListLock, &oldIrqlList);
while (pList->Flink != &pciada->IrqListList)
{
pList = pList->Flink;
next = CONTAINING_RECORD(pList, FIFO_LIST, entry);
KdPrint(("Vector %d pushed\n", vector));
// push the vector into the fifo
PushElement(next->pIrqListHandle, vector);
}
KeReleaseSpinLock(&pciada->IrqListLock, oldIrqlList);
// more handling in the deffered procedure call
KeInsertQueueDpc(&pciada->kDPCobj, (PVOID)pciada, (PVOID)&vector);
}
//------------------------------------------------------------------------
// main interrupt handler function
//
BOOLEAN irq_service(PKINTERRUPT Interrupt, PVOID ServiceContext)
{
PCIADA *pciada = (PCIADA *)ServiceContext;
UCHAR vector;
UNREFERENCED_PARAMETER(Interrupt);
vector = evaluate_vector(pciada);
if (!(vector)) return FALSE;
KdPrint(("irq_service: %d\n", vector & 0xff));
if ((pciada->pbBusError) && ((vector == 7) || (vector == 1)))
*pciada->pbBusError = TRUE;
else
InsertInIrqQueues(pciada, vector); // insert at top of the queues
return TRUE;
}
//------------------------------------------------------------------------
// translate interrupt resources for PCIADAs to neutral ones
//
NTSTATUS PCIVMETranslateInterrupts(PDEVICE_OBJECT device_Obj)
{
int i;
NTSTATUS result = STATUS_SUCCESS;
DEVICE_EXT *pDevExt = (DEVICE_EXT*)device_Obj->DeviceExtension;
int nPCIADAs = pDevExt->nPCIADAs;
PCIADA *pciada = (PCIADA *) NULL;
KdPrint(("TranslateInterrupt()\n"));
if (nPCIADAs) return STATUS_SUCCESS;
for (i = 0; i < nPCIADAs; i++)
{
pciada = &pDevExt->pciada[i];
KdPrint(("In - Bus:%d, IrqLine:%d\n", pciada->Bus, pciada->Irql));
if (pciada->Irql)
{
pciada->Vector = HalGetInterruptVector(PCIBus, pciada->Bus,
pciada->Irql,
pciada->Vector,
&pciada->Irql, &pciada->Affinity);
}
else
result = STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT;
}
KdPrint(("Out - Irql:%d, Vector: %d, Affinity:%d\n", pciada->Irql, pciada->Vector, pciada->Affinity));
return result;
}
//------------------------------------------------------------------------
// connect service routines to all PCIADA interrupts
//
NTSTATUS PCIVMEConnectInterrupt(PDEVICE_OBJECT device_Obj)
{
int i;
NTSTATUS result = STATUS_SUCCESS;
DEVICE_EXT *pDevExt = (DEVICE_EXT*)device_Obj->DeviceExtension;
int nPCIADAs = pDevExt->nPCIADAs;
PCIADA *pciada;
KdPrint(("ConnectInterrupt()\n"));
// connect the interrupts to service routines
for (i = 0; i < nPCIADAs; i++)
{
pciada = &pDevExt->pciada[i];
pciada->InterruptObject = NULL;
if (pciada->Vector)
result = IoConnectInterrupt(&pciada->InterruptObject,
irq_service,
(PVOID)pciada,
NULL,
pciada->Vector,
pciada->Irql,
pciada->Irql,
LevelSensitive,
TRUE,
pciada->Affinity,
FALSE);
KdPrint(("pIrqObj:0x%08x, VirtVect:%d, Irql:%d, hIrql:%d, Aff:0x%08x, Status:0x%08x\n",
pciada->InterruptObject,
pciada->Vector, pciada->Irql, pciada->Irql, pciada->Affinity, result));
if (result != STATUS_SUCCESS) break;
}
return result;
}
//------------------------------------------------------------------------
// dis-connect service routines to all PCIADA interrupts
//
NTSTATUS PCIVMEDisConnectInterrupt(PDEVICE_OBJECT device_Obj)
{
int i;
DEVICE_EXT *pDevExt = (DEVICE_EXT*)device_Obj->DeviceExtension;
int nPCIADAs = pDevExt->nPCIADAs;
PCIADA *pciada;
KdPrint(("DisConnectInterrupt()\n"));
// dis connect the interrupts to service routines
for (i = 0; i < nPCIADAs; i++)
{
pciada = &pDevExt->pciada[i];
KdPrint(("IrqObj:0x%08x\n", pciada->InterruptObject));
if (pciada->InterruptObject)
IoDisconnectInterrupt(pciada->InterruptObject);
}
return STATUS_SUCCESS;
}