//-------------------------------------------------------------------------
 
// 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;
 
}