/* SCCSID = "src/dev/usb/UHCI/UHCIRQ.C, usb, c.basedd 98/07/10" */
/*
*   Licensed Material -- Property of IBM
*
*   (c) Copyright IBM Corp. 1997, 1998  All Rights Reserved
*/
/************************** START OF SPECIFICATIONS ***************************/
/*                                                                            */
/*   SOURCE FILE NAME:  UHCIRQ.C                                              */
/*                                                                            */
/*   DESCRIPTIVE NAME:  UHCD IRQ processing routines.                         */
/*                                                                            */
/*   FUNCTION: These routines handles USB Host controller generated           */
/*             hardware IRQ's.                                                */
/*                                                                            */
/*   NOTES:                                                                   */
/*      DEPENDENCIES: None                                                    */
/*      RESTRICTIONS: None                                                    */
/*                                                                            */
/*   ENTRY POINTS:                                                            */
/*             UHCIInterrupt                                                  */
/*             DefAddrTimeOut                                                 */
/*             CheckQHs                                                       */
/*             FinshIO                                                        */
/*                                                                            */
/*   EXTERNAL REFERENCES:                                                     */
/*                                                                            */
/* Change Log                                                                 */
/*                                                                            */
/*  Mark    yy/mm/dd  Programmer      Comment                                 */
/*  ----    --------  ----------      -------                                 */
/*          98/01/31  MB                                                      */
/*                                                                            */
/**************************** END OF SPECIFICATIONS ***************************/

#include "uhci.h"

static BOOL IsTDFinished(QH *qh, TD *tdList, ULONG currHeadElement);

/********************** START OF SPECIFICATIONS ***********************/
/*                                                                    */
/* SUBROUTINE NAME:  UHCIInterrupt                                    */
/*                                                                    */
/* DESCRIPTIVE NAME:  UHCI compliant USB Host Controller              */
/*                    interrupt service routine.                      */
/*                                                                    */
/* FUNCTION:  The function of this routine is to service USB Host     */
/*            hardware interrupts. Routine checks USB host controller */
/*            status register and continues interrupt procesing if    */
/*            it is caused by UBS host. It saves interrupt flags and  */
/*            arms interrupt processing context thread, clears host   */
/*            status register.                                        */
/*                                                                    */
/* NOTES:                                                             */
/*                                                                    */
/* CONTEXT: Interrupt time                                            */
/*                                                                    */
/* ENTRY POINT:  UHCIInterrupt                                        */
/*    LINKAGE:  CALL FAR                                              */
/*                                                                    */
/* INPUT:  NONE                                                       */
/*                                                                    */
/* EXIT-NORMAL:  NC - USB host device generated interrupt (claim int.)*/
/*                                                                    */
/* EXIT-ERROR:  CY - USB host device did not generate interrupt       */
/*                                                                    */
/* EFFECTS: None                                                      */
/*                                                                    */
/* INTERNAL REFERENCES:  None                                         */
/*                                                                    */
/* EXTERNAL REFERENCES:  None                                         */
/*                                                                    */
/************************ END OF SPECIFICATIONS ***********************/
#pragma optimize("eglt", off)
void FAR UHCIInterrupt()
{
   USHORT   usbStatus;

   inp16(gusbStsReg, usbStatus);

#ifdef   DEBUG
{
   USHORT   port1Status, port2Status;

   inp16(gusbPortSc1Reg, port1Status);
   inp16(gusbPortSc2Reg, port2Status);

   dsPrint3( DBG_DETAILED, "UHCI: UHCIInterrupt Status=%x,Port1=%x, Port2=%x\r\n",
             usbStatus, port1Status, port2Status );
}
#endif

   if (!(usbStatus&(UHCI_USBSTS_HCPE|UHCI_USBSTS_HSE|UHCI_USBSTS_RDE|UHCI_USBSTS_USBEI|UHCI_USBSTS_USBI)))
   {  // this is not interrupt caused by USB host
      STC();   // set carry flag to indicate that it is not our interrupt
      return;
   }

   // save interrupt flags that need special processing (hardware error, processing error, resume detect)
   if (usbStatus&UHCI_USBSTS_HCPE)
      gInterruptFlags|=UHCI_USBSTS_HCPE;
   if (usbStatus&UHCI_USBSTS_HSE)
      gInterruptFlags|=UHCI_USBSTS_HSE;
   if (usbStatus&UHCI_USBSTS_RDE)
      gInterruptFlags|=UHCI_USBSTS_RDE;

   if (IsIsoInterrupt())   // arm processing thread if there are completed iso request(s)
      DevHelp_ArmCtxHook( 0, gIsoIrqHookHandle );

   DevHelp_ArmCtxHook( 0, gCTXHookHandle );  // aquire task time to process completed I/O requests

   // clear interrupt flags
   usbStatus|=UHCI_USBSTS_HCPE|UHCI_USBSTS_HSE|UHCI_USBSTS_RDE|UHCI_USBSTS_USBEI|UHCI_USBSTS_USBI;
   outp16(gusbStsReg, usbStatus);

   CLI();   // clear interrupts to prevent nested interrupt processing in shared IRQ environment
   CLC();                                 /* Clear the carry flag to indicate */
                                          /* to kernel that we have claimed   */
                                          /* (serviced) the interrupt.        */
   DevHelp_EOI( gusbIRQ );                /* issue end of interrupt */
}
#pragma optimize("", on)

/********************** START OF SPECIFICATIONS ***********************/
/*                                                                    */
/* SUBROUTINE NAME:  DefAddrTimeOut                                   */
/*                                                                    */
/* DESCRIPTIVE NAME:  Default address I/O request timed out           */
/*                                                                    */
/* FUNCTION:  The function of this routine is to retire default       */
/*            address request and arm context thread to process it.   */
/*                                                                    */
/* NOTES:                                                             */
/*                                                                    */
/* CONTEXT: Timer interrupt time                                      */
/*                                                                    */
/* ENTRY POINT:  DefAddrTimeOut                                       */
/*    LINKAGE:  CALL FAR                                              */
/*                                                                    */
/* INPUT:  NONE                                                       */
/*                                                                    */
/* EXIT-NORMAL:  n/a                                                  */
/*                                                                    */
/* EXIT-ERROR:  n/a                                                   */
/*                                                                    */
/* EFFECTS: clears 'active' flag and sets timeout flag in I/O request */
/*                                                                    */
/* INTERNAL REFERENCES:  None                                         */
/*                                                                    */
/* EXTERNAL REFERENCES:  None                                         */
/*                                                                    */
/************************ END OF SPECIFICATIONS ***********************/
void DefAddrTimeOut(void)
{
   TD       *currTD;

#ifdef   DEBUG
   dsPrint( DBG_CRITICAL, "UHCI: DefAddrTimeOut\r\n" );
#endif

   if (!g0QH)
      return;

   if (g0QH->elementType)
   {
      for (currTD=(TD *)g0QH->qeLinkVirt; currTD && g0QH; currTD=currTD->virtLP)
      {
         if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_ACTIVE)
         {
            currTD->ctrlStat&=~TD_CTRLSTAT_STATUS_ACTIVE;   // mark inactive (completed)
            currTD->ctrlStat|=TD_CTRLSTAT_STATUS_CRC;       // set timed out flag
         }
      }
      g0QH=NULL;

      DevHelp_ArmCtxHook( 0, gCTXHookHandle );  // aquire task time to process completed I/O requests
   }
   else
      g0QH=NULL;
}

/********************** START OF SPECIFICATIONS ***********************/
/*                                                                    */
/* SUBROUTINE NAME:  CheckQHs                                         */
/*                                                                    */
/* DESCRIPTIVE NAME:  Check bottom level QHs for finished requests    */
/*                                                                    */
/* FUNCTION:  The function of this routine is to loop through         */
/*            TD/QH array and process finished/failed requests.       */
/*                                                                    */
/* NOTES:                                                             */
/*                                                                    */
/* CONTEXT: Task time                                                 */
/*                                                                    */
/* ENTRY POINT:  CheckQHs                                             */
/*    LINKAGE:  CALL FAR                                              */
/*                                                                    */
/* INPUT:  NONE                                                       */
/*                                                                    */
/* EXIT-NORMAL:  n/a                                                  */
/*                                                                    */
/* EXIT-ERROR:  n/a                                                   */
/*                                                                    */
/* EFFECTS:                                                           */
/*                                                                    */
/* INTERNAL REFERENCES:  None                                         */
/*                                                                    */
/* EXTERNAL REFERENCES:  None                                         */
/*                                                                    */
/************************ END OF SPECIFICATIONS ***********************/
#pragma optimize("eglt", off)
void CheckQHs(void)
{
   QH       *qh;
   TD       *td;
   USHORT   flags;
#ifdef   DEBUG
   BOOL     noFinishedTDs=TRUE;
#endif

   _asm
   {
      pushf
      pop   flags
   }   

   // check bottm level QH entries
   for (qh=gFirstBottomQH; qh; qh=gNextForIRQ)
   {
      gNextForIRQ=qh->nextBotQH;
      td=(TD *)qh->qeLinkVirt;
      if (IsTDFinished(qh, td, qh->qeLink))
      {  // process completed request: 1) detach&release QH; 2) process TD's;
         DetachQH(qh); 
         FinshIO(td, FALSE);
#ifdef   DEBUG
         noFinishedTDs=FALSE;
#endif
      }
   }

   if (gInterruptsDisabled)
   {
      if (flags&EFLAGS_IF)   //  enable interrupts if disabled in CheckQHs
         STI();   // enable interrupts
      gInterruptsDisabled=FALSE;
   }

#ifdef   DEBUG
   if (noFinishedTDs)
      dsPrint(DBG_DETAILED, "UHCI: CheckQ - no finished requests\r\n");
#endif

}
#pragma optimize("", on)

/********************** START OF SPECIFICATIONS ***********************/
/*                                                                    */
/* SUBROUTINE NAME:  IsTDFinished                                     */
/*                                                                    */
/* DESCRIPTIVE NAME:  Check QH for finished request                   */
/*                                                                    */
/* FUNCTION:  This routine checks all the TD's to detect whether      */
/*            QH processing is finished or not                        */
/*                                                                    */
/* NOTES:                                                             */
/*                                                                    */
/* CONTEXT: Task time                                                 */
/*                                                                    */
/* ENTRY POINT:  IsTDFinished                                         */
/*    LINKAGE:  CALL FAR                                              */
/*                                                                    */
/* INPUT:  TD *tdList - pointer to 1st TD is QHs list                 */
/*         ULONG currHeadElement - physical address of currently 1st  */
/*                                 TD in QH list                      */
/*                                                                    */
/* EXIT-NORMAL:  n/a                                                  */
/*                                                                    */
/* EXIT-ERROR:  n/a                                                   */
/*                                                                    */
/* EFFECTS:                                                           */
/*                                                                    */
/* INTERNAL REFERENCES:  None                                         */
/*                                                                    */
/* EXTERNAL REFERENCES:  None                                         */
/*                                                                    */
/************************ END OF SPECIFICATIONS ***********************/
static BOOL IsTDFinished(QH *qh, TD *tdList, ULONG currHeadElement)
{
   register TD    *currTD;
   ULONG          currLen, maxLen;

   if (!tdList)
      return (FALSE);

   // check all the request chain for completion
   currHeadElement&=UHCI_LP_MASK;
   if (!currHeadElement) // all TD's processed
      return (TRUE);
   for (currTD=tdList; currTD; currTD=currTD->virtLP)
   {
      if (currHeadElement!=currTD->phyTDAddr)
         continue;
      if (!(currTD->ctrlStat&TD_CTRLSTAT_STATUS_ACTIVE))  // completed
      {
         currLen=currTD->ctrlStat&TD_CTRLSTAT_ACTLEN_MASK;
         maxLen=currTD->token>>21;
         if (currTD->ctrlStat&TD_CTRLSTAT_SPD && (currTD->token&TD_TOKEN_PID_MASK)==TD_TOKEN_PID_IN)
         {
#ifdef   DEBUG
            dsPrint4(DBG_DETAILED, "UHCI: CheckQ - currlen=%lx, maxLen=%lx, chead=%lx, tdAdd=%lx\r\n",
                     currLen,maxLen,currHeadElement,currTD->phyTDAddr);
            dsPrint3(DBG_DETAILED, "UHCI: CheckQ - currTD=%lx, ctrlStat=%lx, token=%lx\r\n",
                     (USHORT)currTD,currTD->ctrlStat,currTD->token);
#endif
            if (currLen<=maxLen && maxLen!=USB_ZERO_PACKET)   // processed - short packet received
               currTD=NULL;
         }
         if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_ERRMASK)  // finished with error
            currTD=NULL;
      }
      break;
   }

   return (currTD==NULL);
}

/********************** START OF SPECIFICATIONS ***********************/
/*                                                                    */
/* SUBROUTINE NAME:  FinshIO                                          */
/*                                                                    */
/* DESCRIPTIVE NAME:  Processes TD list to Finish I/O request         */
/*                                                                    */
/* FUNCTION:  This routine fills in USBD request block and calls      */
/*            specified IRQ extension routine to finish I/O completion*/
/*            processing                                              */
/*                                                                    */
/* NOTES:                                                             */
/*                                                                    */
/* CONTEXT: Task time                                                 */
/*                                                                    */
/* ENTRY POINT:  FinshIO                                              */
/*    LINKAGE:  CALL FAR                                              */
/*                                                                    */
/* INPUT:  TD *tdList - pointer to 1st TD is QHs list                 */
/*         BOOL cancelled - true if request must be cancelled         */
/*                                                                    */
/* EXIT-NORMAL:  TRUE                                                 */
/*                                                                    */
/* EXIT-ERROR:  n/a                                                   */
/*                                                                    */
/* EFFECTS:                                                           */
/*                                                                    */
/* INTERNAL REFERENCES:  None                                         */
/*                                                                    */
/* EXTERNAL REFERENCES:  setmem                                       */
/*                       USBCallIDC                                   */
/*                                                                    */
/************************ END OF SPECIFICATIONS ***********************/
BOOL FinshIO(TD *tdList, BOOL cancelled)
{
   TD             *currTD, *nextTD;
   RP_GENIOCTL    rpIRQ;
   USBRB          irqRB;
   USHORT         packetSize;
   BOOL           activeTD;
   BOOL           dataToggle;
   ULONG          ttype;
   BOOL           lastProcessed;

   while (tdList)
   {
      dataToggle=FALSE;

      //  fill in request block
      irqRB.controllerId=ghcdID; // set controller ID in request block
      irqRB.deviceAddress=tdList->deviceAddress; // set device address
      irqRB.endPointId=tdList->endPointId;    // set endpoint address

      irqRB.status=0;        // clear request status flags

      activeTD= (tdList->ctrlStat&TD_CTRLSTAT_STATUS_ACTIVE)!=0;

      // set flags field value to match TD status
      irqRB.flags=0;
      if (tdList->ctrlStat&TD_CTRLSTAT_LSD)
         irqRB.flags|=USRB_FLAGS_DET_LOWSPEED;

      irqRB.serviceTime=0;       // ignored for finished non-isohronous requests

      ttype=tdList->token&TD_TOKEN_PID_MASK;
      switch (ttype)
      {
      case TD_TOKEN_PID_IN:
         irqRB.flags|=USRB_FLAGS_TTYPE_IN;
         break;
      case TD_TOKEN_PID_OUT:
         irqRB.flags|=USRB_FLAGS_TTYPE_OUT;
         break;
      case TD_TOKEN_PID_SETUP:
         irqRB.flags|=USRB_FLAGS_TTYPE_SETUP;
         break;
      }

      irqRB.buffer1=(PUCHAR)tdList->virtBufferPointer;   // Virtual address of data buffer
      irqRB.buffer1Length=0;     // Buffer length in bytes
      irqRB.buffer2=NULL;        // Virtual address of second data buffer.
      irqRB.buffer2Length=0;     // Buffer length in bytes
      irqRB.maxPacketSize=0;     // ignored for finished requests
      irqRB.maxErrorCount=(UCHAR)((tdList->ctrlStat&TD_CTRLSTAT_ERRCNT_MASK)>>27);  // current error count value
      irqRB.usbIDC=(PUSBIDCEntry)tdList->usbIDC;        // Address of IRQ processing routine to be called for this request
      irqRB.usbDS=tdList->usbDS; // ; set data segment value
      irqRB.category=tdList->category; // I/O issuer category
      irqRB.requestData1=tdList->requestData1;  // data stored within request
      irqRB.requestData2=tdList->requestData2;  // data stored within request
      irqRB.requestData3=tdList->requestData3;  // data stored within request

      // process all TDs for single request and OR error flags, set transferred byte count
      for (currTD=tdList; currTD; currTD=nextTD)
      {
         nextTD=currTD->virtLP;

#ifdef   DEBUG
         if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_ERRMASK)
         {
            dsPrint4(DBG_CRITICAL, "UHCI: IRQ TD=%x,r1=%lx,r2=%lx,r3=%lx",(USHORT)currTD,
                     currTD->requestData1, currTD->requestData2, currTD->requestData3);
            dsPrint4(DBG_CRITICAL,",a=%d,e=%d,stat=%0lx,token=%0lx\r\n",
                     irqRB.deviceAddress, irqRB.endPointId, currTD->ctrlStat, currTD->token);
            dsPrint1(DBG_DETAILED,"UHCI: IRQ caller's DS=%0x\r\n", currTD->usbDS);
         }
#endif
         if (!(currTD->ctrlStat&TD_CTRLSTAT_STATUS_ACTIVE))
         {  // set error flags in USBD request block
            if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_STALLED)
               irqRB.status|=USRB_STATUS_STALLED;
            if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_DBUFERR)
               irqRB.status|=USRB_STATUS_BUFFERR;
            if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_BABBLE)
               irqRB.status|=USRB_STATUS_BABBLE;
            if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_NAK)
               irqRB.status|=USRB_STATUS_NAK;
            if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_CRC)
               irqRB.status|=USRB_STATUS_CRC;
            if (currTD->ctrlStat&TD_CTRLSTAT_STATUS_BITSTUFF)
               irqRB.status|=USRB_STATUS_BITSTUFF;
         }

         if (!(currTD->ctrlStat&TD_CTRLSTAT_STATUS_ERRMASK))
         {  // get last data toggle status
            dataToggle=(currTD->token&TD_TOKEN_DATA_TOGGLE)!=0;
         }

         // get no of bytes transferred within current TD
         packetSize=(USHORT)(currTD->ctrlStat&TD_CTRLSTAT_ACTLEN_MASK);
         if (packetSize!=USB_ZERO_PACKET)
         {
            packetSize++;
            if (packetSize>irqRB.maxPacketSize)
               irqRB.maxPacketSize=packetSize;
         }
         else
            packetSize=0;

         // add transferred byte count to corresponding buffer length value
         if (irqRB.flags&USRB_FLAGS_TTYPE_SETUP && (currTD->token&TD_TOKEN_PID_MASK)!=TD_TOKEN_PID_SETUP)
         {
            if (!irqRB.buffer2)
               irqRB.buffer2=(PUCHAR)currTD->virtBufferPointer;
            irqRB.buffer2Length+=packetSize;
         }
         else
            irqRB.buffer1Length+=packetSize;

         if (currTD->elementType==ELEMENT_TYPE_LASTD)
         {
            tdList=nextTD;
            currTD->virtLP=NULL; FreeTDsQH(currTD, TRUE); // release single TD
            break;
         }
         currTD->virtLP=NULL; FreeTDsQH(currTD, TRUE); // release single TD
      }
      if (dataToggle) // set data toggle status flag
         irqRB.flags|=USRB_FLAGS_DET_DTGGLEON;
      if (!currTD)
         tdList=NULL;

#ifdef   DEBUG
      dsPrint2(DBG_SPECIFIC, "UHCI: IRQ datatoggle=%d, devaddr=%d\r\n",dataToggle,irqRB.deviceAddress);
#endif
      lastProcessed= tdList==NULL;

      setmem((PSZ)&rpIRQ, 0, sizeof(rpIRQ));
      rpIRQ.rph.Cmd=CMDGenIOCTL;   // IOCTL
      if (activeTD && cancelled)
         rpIRQ.rph.Status=USB_IDC_RC_CANCELED;
      else if (irqRB.status)  // set return code
         rpIRQ.rph.Status=USB_IDC_RC_IOFAILED;
      else
         rpIRQ.rph.Status=USB_IDC_RC_OK;

      // call IRQ extension routine to finish 'request ended' processing
      if (!cancelled)
      {
         rpIRQ.Category=irqRB.category;
         rpIRQ.Function=USB_IDC_FUNCTION_PRCIRQ;
         rpIRQ.ParmPacket=(PVOID)&irqRB;
         USBCallIDC( irqRB.usbIDC, irqRB.usbDS, (RP_GENIOCTL FAR *)&rpIRQ );
      }

      if (lastProcessed)
         break;
   }

   return (TRUE);
}

