/*
** PSD : EVRXDP.C (EVRXDP.PSD)
**
**       This PSD provides the platform specific code for interfacing
**       to the Everex MPDP (Modular Platform Dual Processor) system.
**
**/

// BUGBUG - We need to determine how many processors the Everex DP has.
//          It could have 1 or 2.  We return hardcoded two CPUs for now.
// BUGBUG - Determine the IO delay for Everex.
// BUGBUG - We always enable the L2 in the 2nd processor du eto a bug
//          in the BIOS.

#define INCL_ERROR_H

#include <os2.h>
#include <psd.h>
#include <evrxdp.h>

/*
 *  Global Variables
 */

ulong_t  *pLocalApic;         // Local APIC memory - set by Init()
ulong_t  *pIOApic;            // I/O APIC memory   - set by Init()

ulong_t  SaveRMIPIVector;     // RM vector replaced by warm boot vector
ulong_t *PhysicalPageZero;

P_F_2    router = 0;          // Set by Install()
char    *pParmString = 0;     // Set by Install()
PLMA    *pPSDPLMA = 0;        // Set by Install()
ulong_t  sizePLMA = 0;        // Set by Install()
ulong_t  PSD_IPIInProgress = 0;
ulong_t  SpuriousIntCount = 0;

int     IODelayCount = 30;    // BUGBUG - needs to be imported

ulong_t GenIPI (ulong_t procnum);
ulong_t EndIPI (ulong_t procnum);
ulong_t IPIPresent (void);
ulong_t SpuriousIntHandler(void);
ulong_t InByte (ulong_t port);
void OutByte(ulong_t port, ulong_t data);

/***  Install - Install PSD
 *
 *    This function checks to see if this PSD is installable on the
 *    current platform.
 *
 *    1) EISA ports are checked for Everex IDs.
 *    2) Install data is saved.
 *
 *    ENTRY   pinstall - pointer to an INSTALL structure
 *
 *    EXIT    NO_ERROR - PSD Installed
 *            -1       - PSD not valid for this platform
 *
 */

ulong_t Install(INSTALL register *pinstall) {

   ulong_t rc = NO_ERROR;

   /*
    * Check to see if we are on an Everex DP.
    */

    if ( EVERX_ID_AT_PORT_C80 == InByte(EISA_PRODUCT_ID_PORT1)
         &&
         EVERX_ID_AT_PORT_C81 == InByte(EISA_PRODUCT_ID_PORT2)
         &&
         EVERX_ID_AT_PORT_C82 == InByte(EISA_PRODUCT_ID_PORT3) )
    {
       /*
        * HW is an Everex DP.
        * Save Install data to global variables.
        */

       router = pinstall->pPSDHlpRouter;
       pParmString = pinstall->pParmString;
       pPSDPLMA = (void *)pinstall->pPSDPLMA;
       sizePLMA = pinstall->sizePLMA;
    }
    else
    {
      rc = -1;           // HW is not an Everex DP
    }

   return (rc);

}

/***  DeInstall - DeInstall PSD
 *
 *    This function deinstalls the PSD.
 *
 *    ENTRY   None
 *
 *    EXIT    NO_ERROR
 *
 */

ulong_t DeInstall(void) {

   return (NO_ERROR);
}

/***  Init - Initialize the PSD
 *
 *    This function initalizes the PSD.
 *
 *    - Map physical page zero
 *    - Replace real mode IPI vector with warm boot vector
 *    - Map I/O APIC memory
 *    - Map local APIC memory
 *    - Register an IPI interrupt handler
 *    - Set init flags
 *
 *    ENTRY   pinit - pointer to INIT structure
 *
 *    EXIT    NO_ERROR - PSD initialized
 *            rc from    PSDHelp (PSDHLP_VMALLOC)
 *            rc from    PSDHelp (PSDHLP_SET_IRQ)
 */

ulong_t Init(INIT *pinit) {

   VMALLOC vmac;
   SET_IRQ irq;
   ulong_t rc;

   /*
    * We need to access the BIOS warm boot vector at 40h:67h and
    * the real mode vector (0:IPI vector*4) ) that will be used for
    * the "start processor IPI".
    *
    * P1 is in a real mode halted state.  We send an IPI to P1 and it
    * will execute the code hooked at the real mode vector.
    *
    * Here, we will copy cs:ip from the warm boot vector to the real mode
    * vector.  The warm boot vector points to InitProcessor().
    *
    * To do this, we map physical page 0.  This will give us access
    * to the warm boot vector in the BIOS data area (40:67) and the real mode
    * vector (0:EVERX_IPI_VECTOR*4). Save the original real mode vector so
    * we can replace it when the other processor initializes.
    */

   vmac.addr   = 0x0;           // start : physical address zero
   vmac.cbsize = 0x1000;        // count : 1 page
   vmac.flags  = VMALLOC_PHYS;

   if ((rc = PSDHelp(router, PSDHLP_VMALLOC, &vmac)) == NO_ERROR)
   {
     PhysicalPageZero = (ulong_t*) vmac.addr;                     // global
     SaveRMIPIVector  = *(PhysicalPageZero + EVERX_IPI_VECTOR);   // global

     *(PhysicalPageZero + EVERX_IPI_VECTOR) =  // set RM vector to warm boot
     *(ulong_t*) ( (char*)PhysicalPageZero + BIOS_WARM_BOOT_OFFSET);

     /*
      * Map the I/O APIC memory.
      */

     vmac.addr   = APIC_IO_PHYS_ADDR;
     vmac.cbsize = APIC_IO_MEMORY_SIZE;
     vmac.flags  = VMALLOC_PHYS;

     if ((rc = PSDHelp(router, PSDHLP_VMALLOC, &vmac)) == NO_ERROR)
     {
       pIOApic = (ulong_t*)vmac.addr;                              // global

       /*
        * Map the Local APIC memory.
        */

       vmac.addr   = APIC_LOCAL_PHYS_ADDR;
       vmac.cbsize = APIC_LOCAL_MEMORY_SIZE;
       vmac.flags  = VMALLOC_PHYS;

       if ((rc = PSDHelp(router, PSDHLP_VMALLOC, &vmac)) == NO_ERROR)
       {
         pLocalApic = (ulong_t*)vmac.addr;                         // global

         /*
          * Setup Handler for spurious interrupts.
          */

         irq.irq     = EVERX_SPR_IRQ;
         irq.flags   = 0;
         irq.vector  = EVERX_SPR_VECTOR;
         irq.handler = (P_F_2)SpuriousIntHandler;

         if ((rc = PSDHelp(router, PSDHLP_SET_IRQ, &irq)) == NO_ERROR)
         {
           /*
            * Setup Handler for IPI IRQ.
            */

           irq.irq     = EVERX_IPI_IRQ;
           irq.flags   = IRQf_IPI;
           irq.vector  = EVERX_IPI_VECTOR;
           irq.handler = (P_F_2)IPIPresent;

           if ((rc = PSDHelp(router, PSDHLP_SET_IRQ, &irq)) == NO_ERROR)
           {
             /*
              * To determine the number of processors, we attempt to do
              * an APIC remote read of CPU1s local APIC unit ID register.
              * If the Remote Read Status is 0 (invalid), then we assume that
              * we are on a one processor system.
              */

              // BUGBUG - add this function here.

             /*
              * Save init data
              */
//             pinit->flags = INIT_GLOBAL_IRQ_ACCESS;
             pinit->flags = INIT_GLOBAL_IRQ_ACCESS | INIT_USE_FPERR_TRAP;
             pinit->version = VERSION;
           }
         }
       }
     }
   }

   return (rc);
}

/***  GetNumOfProcs - Get number of processors
 *
 *    This function gets the number of processors which exist on this
 *    platform.
 *
 *    ENTRY   None
 *
 *    EXIT    Number of processors
 *
 */

ulong_t GetNumOfProcs(void) {

   return (EVERX_MAXCPU);
}


/***  StartProcessor - Start a processor
 *
 *    This function starts a processor.
 *
 *    ENTRY   procnum - processor number to start (0-based)
 *
 *    EXIT    rc from GenIPI
 *
 */

ulong_t StartProcessor(ulong_t procnum) {

   /*
    * Start a processor by sending it an interrupt.
    */

   return (GenIPI (procnum));
}

/***  ProcInit - Processor initalization
 *
 *    This function initalizes per processor items.
 *
 *    NOTE: This function is called once on each processor
 *          in the system.
 *
 *    ENTRY   None
 *
 *    EXIT    NO_ERROR - Processor initialized
 *            rc from EndIPI
 */

ulong_t ProcInit(void) {

   ulong_t rc = NO_ERROR;
   ulong_t Register;

   /*
    * Save the Local Unit ID as the physical processor number.
    * Set the Destination Format Register to 0 so that we use
    * a Destination Mode of Physical for IPIs.
    */

   pPSDPLMA->physical_procnum = (*(pLocalApic + APIC_LOC_REG_UNIT_ID)) >> 24 ;

   /*
    * Disable float point interrupts from going to the Motherboard.
    * Also, enable L2 cache in 2nd processor due to a bug in BIOS.
    * BUGBUG
    */

   OutByte (0xcf0, 1);
   Register = Inbyte (0xcf8);
   Register |= 0x08;
#if 0
   if (pPSDPLMA->physical_procnum)
   {
     Register |= 0x01;
   }
#endif
   OutByte (0xcf8, Register);

   OutByte (0xcf0, 0x0);          // disable

   *(pLocalApic + APIC_LOC_REG_DEST_FORMAT) = 0;
   *(pLocalApic + APIC_LOC_REG_SPUR_VECTOR) = 0x100 | EVERX_SPR_VECTOR;

   /*
    * If we are NOT processor 0, then were were started by an IPI and
    * we need to end the IPI now that we are in protect mode.
    */


   if (pPSDPLMA->physical_procnum)
   {

     /*
      * First, reset the real mode vector for the IPI
      * and free the mapping.
      */

     *(PhysicalPageZero + EVERX_IPI_VECTOR) = SaveRMIPIVector;
     PSDHelp(router, PSDHLP_VMFREE, PhysicalPageZero);
     rc = EndIPI (pPSDPLMA->physical_procnum);
   }

   return (rc);
}

/***  IPIPresent - Detects the presence of an IPI
 *
 *    This function detects the presence of an IPI on the current
 *    processor.
 *
 *    ENTRY   None
 *
 *    EXIT    NO_ERROR - IPI present
 *            -1       - IPI not present
 *
 */

ulong_t IPIPresent (void) {

   ulong_t rc = NO_ERROR;

   /*
    *  Check if the ISR bit for Vector being used for IPIs is set.
    */

   if ( ! CHECK_IPI_ISR_BIT (pLocalApic) )
   {
      rc = -1;
   }

   return (rc);
}

/***  SpuriousIntHandler - Spuriuous interrupt handler
 *
 *    This function is called when a spurious interrupt occurs.
 *
 *    ENTRY   None
 *
 *    EXIT    NO_ERROR
 *
 */

ulong_t SpuriousIntHandler (void) {

   SpuriousIntCount++;

   return (NO_ERROR);
}

/***  GenIPI - Generate an inter-processor interrupt
 *
 *    This function generates an IPI.
 *
 *    Vector           : EVERX_IPI_VECTOR
 *    Delivery mode    : Fixed (0)
 *    Destination Mode : Physical (0)
 *
 *    ENTRY   procnum - processor number to interrupt (0-based)
 *
 *    EXIT    NO_ERROR
 *
 */

ulong_t GenIPI(ulong_t procnum) {

   ulong_t register *pLocalApicICR_1;
   ulong_t register *pLocalApicICR_2;

   PSD_IPIInProgress++;

   pLocalApicICR_1 = pLocalApic + APIC_LOC_REG_INTCMD_31_0;
   pLocalApicICR_2 = pLocalApic + APIC_LOC_REG_INTCMD_63_32;

   *pLocalApicICR_2 = procnum << 24;    // 8 MSB is Destination Local Unit ID
   *pLocalApicICR_1 = EVERX_IPI_VECTOR; // after write, the IPI is sent

   /*
    * Loop on the Delivery Status bit until we see that it is idle.
    */

   while (*pLocalApicICR_1 & APIC_ICR_DEL_STATUS_PEND);

   PSD_IPIInProgress++;

   return (NO_ERROR);
}


/***  EndIPI - End an inter-processor interrupt
 *
 *    This function ends an IPI.
 *
 *    ENTRY   procnum - processor number to end interrupt on (0-based)
 *
 *    EXIT    Return Code
 *
 */

ulong_t EndIPI(ulong_t procnum) {

   ulong_t  rc = NO_ERROR;

   /*
    * To EOI an IPI, a write must occur to the EOI register of the local
    * APIC.  The value can be anything, we write the IPI vector being
    * used.  First, check that the in service bit is set for the IPI
    * vector.
    */

   PSD_IPIInProgress--;

   if ( CHECK_IPI_ISR_BIT (pLocalApic) )
   {
      *(pLocalApic + APIC_LOC_REG_EOI) = EVERX_IPI_VECTOR;
       PSD_IPIInProgress--;
   }
   else
   {
      _asm { int 3};     // IPE here ??
      rc = -1;           // In service bit not set in ISR
   }
   return (rc);
}

/***  InByte - Read a byte from a port
 *
 *    This function reads a byte from a specified port
 *
 *    ENTRY   port - port number to read from
 *
 *    EXIT    data read
 *
 */

ulong_t InByte(ulong_t port) {

   ulong_t data;

   _asm {
      mov   dx,port
      in    al,dx
      movzx eax,al
      mov   data,eax
   };

   return (data);
}


/***  InWord - Read a word from a port
 *
 *    This function reads a word from a specified port
 *
 *    ENTRY   port - port number to read from
 *
 *    EXIT    data read
 *
 */

ulong_t InWord(ulong_t port) {

   ulong_t data;

   _asm {
      mov   dx,port
      in    ax,dx
      movzx eax,ax
      mov   data,eax
   };

   return (data);
}


/***  InDword - Read a dword from a port
 *
 *    This function reads a dword from a specified port
 *
 *    ENTRY   port - port number to read from
 *
 *    EXIT    data read
 *
 */

ulong_t InDword(ulong_t port) {

   ulong_t data;

   _asm {
      mov   dx,port
      in    eax,dx
      mov   data,eax
   };

   return (data);
}


/***  OutByte - Writes a byte to a port
 *
 *    This function writes a byte to a specified port
 *
 *    ENTRY   port - port number to read from
 *            data - data to write
 *
 *    EXIT    None
 *
 */

void OutByte(ulong_t port, ulong_t data) {

   _asm {
      mov   dx,port
      mov   al,byte ptr data
      out   dx,al
   };

   return;
}


/***  OutWord - Writes a word to a port
 *
 *    This function writes a word to a specified port
 *
 *    ENTRY   port - port number to read from
 *            data - data to write
 *
 *    EXIT    None
 *
 */

void OutWord(ulong_t port, ulong_t data) {

   _asm {
      mov   dx,port
      mov   ax,word ptr data
      out   dx,ax
   };

   return;
}


/***  OutDword - Writes a Dword to a port
 *
 *    This function writes a Dword to a specified port
 *
 *    ENTRY   port - port number to read from
 *            data - data to write
 *
 *    EXIT    None
 *
 */

void OutDword(ulong_t port, ulong_t data) {

   _asm {
      mov   dx,port
      mov   eax,data
      out   dx,eax
   };

   return;
}

/***  WHO_AM_I - Returns the current CPU module ID code
 *
 *    This function returns the current CPU module ID code (1 or 2).
 *
 *    The index register (EVERX_LOC_IDX_REG) will always be set to
 *    point to the internal I/O register (IR_WHO_AM_I), so that reading
 *    the local data register will return the CPU module ID.
 *
 *    ENTRY   NONE
 *
 *    EXIT    Current processor number
 *
 */

ulong_t WHO_AM_I (void) {

   OutByte(EVERX_LOC_IDX_REG, IR_WHO_AM_I);
   IODelay;    // ? needed
   return(InByte(EVERX_LOC_DATA_REG));
}
