/*
** PSD : VIPERMP.C (VIPERMP.PSD)
**
**       This PSD provides the platform specific code for interfacing
**       to the Viper MP (VTech Platinum SMP) system.
**
**       NOTE : when ANDing or ORing values with APIC memory, be sure
**              the compiler does not optimize in such a way that accesses
**              are no longer DWORD.  This can be accomplished by first moving
**              the value into a DWORD variable.
**
**  Change History
**
**  #81140 03/23/94 WHH     Wordpefect was unmasking IRQ 2 and we had coded
**                          and INT 3.
**  #81531 04/01/94 WHH     IRQ 5 and 7 were switched.
**  #85831 06/16/94 WHH     Fix reboot and system dump problems. Also, unmask
**                          NMI on the IO apic.
**  #87964 06/27/94 WHH     Bad pointer math in ProcInit.
**  #89163 07/14/94 WHH     QueryTimer was returning incorrect data (sometimes).
**/

/*
 * BUGBUG - Deinstall need to unmap and ....
 * BUGBUG - There are some timer functions in dev\tmr.asm that do
 *          not call into the psd yet.
 */

#define INCL_ERROR_H

#include <os2.h>
#include <psd.h>
#include <timer.h>
#include <vipermp.h>

/*
 *  Global Variables
 */

/*  APICIO vector 0xff is spurious
 *                0xf0 is ipi
 *                0xe0 is EISA IRQ 3 (highest priority)
 *                0xd0 is EISA IRQ 4 (next highest ........
 *
 *  This matches the IRQ priority on the 8259s with the following
 *  exception : On 8259 based systems, the (2) 8259 PICs are programmed
 *  as master / slave and the slave can not handle more than one
 *  active IRQ.
 *  e.g.
 *  If an IRQ B is being serviced, IRQ 8 would be help in the IRR
 *  until the IRQ B is EOIed.  On the apic, we prioritize the IRQ
 *  levels that would normally be on the 2nd PIC.  This is possible
 *  to do with 8259s, we just don't do it.
 *
 *  FYI : The tricrd.psd will reprogram the PICs so that IRQs are
 *        prioritzed on the slave PICs.
 */

APICIO apicio[MAX_APIC_IO] = {           // all interrupts fixed to cpu 0
   0x67 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x08,              // IRQ08/IRQ00 (from 4021)    67
   0x88 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x01,              // IRQ01 keyboard             88
   0xe0 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x03,              // IRQ03 com 2                e0
   0xd0 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x04,              // IRQ04 com 1                d0
// #81531   0xa0       , 0x00, 0x07,              // IRQ07 lpt2                 a0
   0xa0 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x05,              // IRQ05 lpt2                 a0
   0xb0 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x06,              // IRQ06 floppy               b0
// #81531   0xc0       , 0x00, 0x05,              // IRQ05 lpt1                 c0
   0x4f | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x07,              // IRQ07 lpt1                 4f
   0x66 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x09,              // IRQ09                      66
   0x65 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x0a,              // IRQ10                      65
   0x64 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x0b,              // IRQ11                      64
   0x63 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x0c,              // IRQ12 mouse                63
   0x62 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x0d,              // IRQ13 NPX                  62
   0x61 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x0e,              // IRQ14 DASD                 61
   0x60 | APIC_REDIR_MASK_INTERRUPT , 0x00, 0x0f,              // IRQ15                      60
   APIC_REDIR_DELIVER_NMI |
           APIC_REDIR_MASK_INTERRUPT, 0x00, 0xff, // NMI - NOTE - vector ignored
                                                  //            - deliver on NMI pin
   APIC_REDIR_MASK_INTERRUPT        , 0x00, 0xff // CPU reset - NOTE - masked
};

                                      // This is how TNT has mapped the
                                      // EISA IRQs to the APIC IO Intin pins
                                      // NOTE : IRQ0 and IRQ8 both come in on
                                      // the INT0 pin on athe APICIO.  We have
                                      // moved IRQ0 to the local APIC timer.
ulong_t PicRemap[MAX_APIC_IO] =
{
   0xff,                              // e.g. ff = not remapped
   0x01,                              // e.g. IRQ01 is remapped to apicio[1]
   0xff,                              //      (it comes in on IO pin 1)
   0x02,                              // e.g. IRQ03 is remapped to apicio[2]
   0x03,                              //      (it comes in one IO pin 2)
// #81531  0x06,                              // e.g. IRQ05 is remapped to apicio[6]
   0x04,                              // e.g. IRQ05 is remapped to apicio[6]
   0x05,                              //      (it comes in on IO pin 6)
// #81531  0x04,
   0x06,
   0x00,
   0x07,
   0x08,
   0x09,
   0x0a,
   0x0b,
   0x0c,
   0x0d
};

ulong_t *pLocalApic = 0;      // Local APIC memory - set by Init()
ulong_t *pIOApic = 0;         // I/O APIC memory   - set by Init()
ulong_t  AdvancedIntMode = 0; // = 1 when in advanced interrupt mode
P_F_2    router = 0;          // Set by Install()
PLMA    *pPSDPLMA = 0;        // Set by Install()

int      IODelayCount = 30;   // BUGBUG - SHOULD BE IMPORTED

ulong_t *PhysicalPageZero = 0;// ptr to physical page zero - set by Init()
ulong_t  SaveResetVector = 0; // Reset vector replaced by warm boot vector

ulong_t  TimerState  = STATE_43_WRITE;
ulong_t  TimerValue  = 0;
ulong_t  TimerStatus = 0xb4;

ulong_t ApicInitCount;      // countdown value for local apic timer #89163
ulong_t RollOverCount;      // 8254 countdown value (1 =< x =< 0x10000) #89163

ulong_t  fGarbageIntNotTaken = 0;

#if 0
ulong_t PortSem = 0;
ulong_t PortCounter = 0;
ulong_t PortArray[256];       // for debug
#endif

void    CallIODelay(void);
ulong_t EndIPI(ulong_t procnum);
ulong_t IPIPresent (void);
ulong_t SpuriousHandler (void);
ulong_t In4021 (ulong_t reg);
ulong_t InByte (ulong_t port);
void    OutByte(ulong_t port, ulong_t data);
ulong_t Disable (void);
void    Enable (ulong_t eflags);
ulong_t RemoteRead (uchar_t Register, ulong_t Cpu);
ulong_t GetInitCount (unsigned long InitCount8254);
void    CleanupApic(void);
ulong_t GarbageHandler (void);

/***  Install - Install PSD
 *
 *    This function checks to see if this PSD is installable on the
 *    current platform.
 *
 *    1) Chip and Technologies 4021 chipset registers are checked for
 *       unique values identifying a VIPERMP system.
 *
 *    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 a VIPERMP system.
    */

    if ( VIPERMP_IO_DECODE_0 == In4021(CT4021_IO_DECODE_0)
         &&
         VIPERMP_CS_1_ADDR   == In4021(CT4021_CS_1_ADDR)
         &&
         VIPERMP_IO_DECODE_1 == In4021(CT4021_IO_DECODE_1)
         &&
         VIPERMP_CS_ID       == (In4021(CT4021_CS_ID) & CS_ID_MASK) )
    {
       /*
        * HW is a VIPERMP system.
        * Save Install data to global variables.
        */

       router   = pinstall->pPSDHlpRouter;
       pPSDPLMA = (void *)pinstall->pPSDPLMA;
    }
    else
    {
      rc = -1;           // HW is not a VIPERMP system
    }

   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 default POST reset vector with OS/2 supplied 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;

   /*
    * Transfer the BIOS warm boot vector at 40h:67h to the location in
    * in the BIOS data area the BIOS expects to find the second processor's
    * reset vector (40h:d2h).  The second processor is currently held in
    * reset, and once released, will eventually vector off to the address
    * stored in this location.
    *
    * 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) as well as
    * the second processor's reset vector (40:d2).  We save the original
    * reset vector until the second processor is up and running.
    */

   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
     SaveResetVector  = * (ulong_t*)( (char*)PhysicalPageZero + RESET_VECTOR_OFFSET); // global
     /*
      * reset vector to warm boot
      */
     *(ulong_t*) ( (char*)PhysicalPageZero + RESET_VECTOR_OFFSET) =
     *(ulong_t*) ( (char*)PhysicalPageZero + BIOS_WARM_BOOT_OFFSET);

     /*
      * Set up CMOS so that when the 2nd processor is taken out of
      * reset, it will use the 40:d2 as the warm-boot vector.
      */

     OutByte (CMOS_ADDR, CMOS_CTRL);
     OutByte (CMOS_DATA, CMOS_WARM);

     /*
      * 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 IPI IRQ.  This will be the highest priority
          * vector.
          */

         irq.irq     = IPI_IRQ;
         irq.flags   = IRQf_IPI;
         irq.vector  = IPI_VECTOR;
         irq.handler = (P_F_2)&IPIPresent;

         if ((rc = PSDHelp(router, PSDHLP_SET_IRQ, &irq)) == NO_ERROR)
         {
           /*
            * Setup Handler for spurious interrupt IRQ.  This will be the
            * lowest priority vector.
            */
           irq.irq     = SPR_IRQ;
           irq.flags   = NULL;
           irq.vector  = SPR_VECTOR;
           irq.handler = (P_F_2)&SpuriousHandler;

           if ((rc = PSDHelp(router, PSDHLP_SET_IRQ, &irq)) == NO_ERROR)
           {
             /*
              * Save initialization data
              */
/* #89163    pinit->flags = INIT_TIMER_CPU0 | INIT_USE_FPERR_TRAP ; */
             pinit->flags = INIT_TIMER_CPU0_SET | INIT_USE_FPERR_TRAP ;
             pinit->version = VERSION;

             /*
              * If the APIC has already been enabled, then we have done a
              * warm reset (reboot).  We need to clear any IRR and ISR bits
              * that were left in the APIC from the previous shutdown.
              */

             if ( *(pLocalApic + APIC_LOC_REG_SPUR_VECTOR) & APIC_LOC_ENABLE)
             {
               CleanupApic();
             }
           } // register spurious handler OK
         } // register IPI handler OK
       } // map local apic mem OK
     } // map IO apic-0 mem OK
   } // map phys addr 0 OK

   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) {

 ulong_t cprocs, AllowableCPUs;

 cprocs = (((InByte(MP_STATUS_PORT) & MP_CARD_PRESENT) >> 7) ^ 0x01) + 1;

 AllowableCPUs = ( cprocs > MAXCPUS ) ? MAXCPUS : cprocs;

 return (AllowableCPUs);
}

/***  StartProcessor - Start a processor
 *
 *    This function starts the second processor on a VIPERMP system.
 *
 *    ENTRY   procnum - processor number to start (0-based)
 *
 *    EXIT    NO_ERROR
 *
 */

ulong_t StartProcessor(ulong_t procnum) {

   OutByte (MP_CONTROL_PORT, InByte (MP_CONTROL_PORT) | MP_ENABLE);

   return (NO_ERROR);
}

/***  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
 */

ulong_t ProcInit(void) {

   ulong_t register *pLocal = pLocalApic; // make global ptrs local
   ulong_t register *pIO    = pIOApic;    // Make global ptrs local for optimizing
   PLMA             *pPLMA  = pPSDPLMA;   // to optimize
   ulong_t Value , i;
   ulong_t eflags = Disable();     // Disable interrupts

  /*
   * Processor identification is performed by querying the MP status port
   * (bit P1_ACTIVE: 0=p1 active, 1=p0 active).  This bit toggles according
   * to which processor is accessing the port.
   */

  if (InByte(MP_STATUS_PORT) & P1_ACTIVE)
  {
    pPLMA->physical_procnum = 0;   // processor 0
  }
  else
  {
    pPLMA->physical_procnum = 1;   // processor 1
  }

  /*
   * As the APICs on the VIPERMP system do not latch any IDs into the
   * local unit's ID register at reset, we must initialize it here.
   */

  *(pLocal + APIC_LOC_REG_UNIT_ID) = (pPLMA->physical_procnum) << 24;

  /*
   * Set the Destination Format Register to 0 so that we use a Destination
   * Mode of Physical for IPIs.
   */

  *(pLocal + APIC_LOC_REG_DEST_FORMAT) = 0;

  /*
   * Set task priority to unmask interrupts.
   */

  *(pLocal + APIC_LOC_REG_TASK_PRI) = 0x00;

  /*
   * Set up spurious interrupt vector and and enable local unit.
   */

  *(pLocal + APIC_LOC_REG_SPUR_VECTOR) = APIC_LOC_ENABLE + SPR_VECTOR;

  if (pPLMA->physical_procnum)  // if CPU 1
  {

    /*
     * if we are rebooting (as opposed to hard reset), then end the ipi
     * that was never ended when CPU 1 was ipied to enter the halt state.
     */

    EndIPI(1);

    /*
     * Restore the processor reset vector for POST if second processor
     * is running.  Unmap physical address 0.
     */

/* #87964  *(PhysicalPageZero + RESET_VECTOR_OFFSET) = SaveResetVector; */

    *(ulong_t*) ( (char*)PhysicalPageZero + RESET_VECTOR_OFFSET) = SaveResetVector;

    PSDHelp(router, PSDHLP_VMFREE, PhysicalPageZero);
  }
  else
  {
    /*
     * Program the local APIC timer - we will use this for System Timer 0
     * Leave it masked.
     *
     * The 8254 Prog. Interval Timer has a freq of 1193167
     * Being a 16 bit counter initialized at 65536, it fires
     * every 54.926 ms. (or 18.206 times a second).
     * 1193167 / 65536 = 18.206 ints / sec
     * It is set up for Rate Generator mode (periodic timer).
     *
     * We will program the the local APIC timer to fire at the
     * same rate. TMBASE is 8MHz for Platinum Plus.  Therefore,
     * the initial Counter register is setup with the value
     * 439409.  8000000 / 439409 = 18.206 ints / sec
     *
     * Vector used will be 0x98
     *
     */

    *(pLocal + APIC_LOC_REG_VECT_TMR)  = APIC_TIMER_PERIODIC +
                                         APIC_TIMER_BASE_1 + // use TMBASE
                                         APIC_TIMER_MASKED +
                                         APIC_TIMER_VECTOR;
/* #89163    *(pLocal + APIC_LOC_REG_INIT_COUNT) = APIC_TIMER_INIT_COUNT; */
    *(pLocal + APIC_LOC_REG_INIT_COUNT) = ApicInitCount = APIC_TIMER_INIT_COUNT;

    /*
     * Assign an ID to the IO apic.
     */

    *(pIO + APIC_IO_REG_SELECT) = APIC_IO_ID_REGISTER;
    *(pIO + APIC_IO_REG_WINDOW) = APIC_IO_ID_1;          //0x02<<24

    /*
     * Mask all interrupts on the IO apic.
     */

    for (i = 0; i < MAX_APIC_IO; i++)
    {
       *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (i * 2) + 1;
       *(pIO + APIC_IO_REG_WINDOW) = apicio[i].dest;

       *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (i * 2) + 0;
       *(pIO + APIC_IO_REG_WINDOW) = apicio[i].redir;
    }
  }

  Enable(eflags);
  return (NO_ERROR);
}


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

ulong_t IPIPresent (void) {

   /*
    *  Check if the ISR bit for Vector being used for IPIs is set.
    */
   if (CHECKISRBIT(pLocalApic,IPI_VECTOR))
   {
      return(NO_ERROR);
   }
   else
   {
      return(-1);
   }
}

/***  GenIPI - Generate an interprocessor interrupt
 *
 *    This function generates an IPI.
 *
 *    Vector           : 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 *pLocalApicICR_2;
   ulong_t Value; // force dword reads and writes to apic memory

   if (AdvancedIntMode)
   {
     pLocalApicICR_1 = pLocalApic + APIC_LOC_REG_INTCMD_31_0;
     pLocalApicICR_2 = pLocalApic + APIC_LOC_REG_INTCMD_63_32;

     *pLocalApicICR_2 = procnum << 24;      // Destination Local Unit ID
     *pLocalApicICR_1 = IPI_VECTOR;         // After write, the IPI is sent

     /*
      * Loop on the Delivery Status bit until we see that it is idle.
      */
     Value = APIC_ICR_DEL_STATUS_PEND;
     while (*pLocalApicICR_1 & Value);
     return (NO_ERROR);
   }
   else
   {
     return (-1);
   }
}

/***  EndIPI - End an interprocessor 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) {

   /*
    * 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.
    */

   if (CHECKISRBIT (pLocalApic,IPI_VECTOR))
   {
      *(pLocalApic + APIC_LOC_REG_EOI) = IPI_VECTOR;
      return (NO_ERROR);
   }
   else
   {
      return (-1);           // In service bit not set in ISR
   }
}

/***  SetAdvancedIntMode - Enable advanced interrupt mode
 *
 *    This function enables the APICs on the VIPERMP system.
 *
 *    ENTRY   None
 *
 *    EXIT    NO_ERROR
 *
 */

ulong_t SetAdvancedIntMode(void) {

   ulong_t eflags = Disable();     // Disable interrupts
   ulong_t picmask;
   ulong_t i,rc;
   SET_IRQ irq;
   ulong_t register *pIO = pIOApic;   // Make global ptrs local for optimizing

   /*
    * Mask those vectors that are currently have the IRQ masked on the
    * 8259.  Also, unmask NMI on the apic
    */

   picmask = (InByte(PIC2_PORT1) << 8) + InByte(PIC1_PORT1);

   for (i = 0; i < MAX_EXT_IRQ; i++)
   {
      if (PicRemap[i] != 0xff)    // if 8259 is mapped to an APICIO int pin
      {
        if (picmask & 0x0001)
        {
          apicio[PicRemap[i]].redir |= APIC_REDIR_MASK_INTERRUPT;
        }
        else
        {
          apicio[PicRemap[i]].redir &= ~(APIC_REDIR_MASK_INTERRUPT);
        }
      }
      picmask = picmask >> 1;
   }

   apicio[14].redir &= ~(APIC_REDIR_MASK_INTERRUPT);  // unmask NMI

   /*
    * Initialize only the first APIC's I/O unit.
    * Setup the redirection table.
    */

   for (i = 0; i < MAX_APIC_IO; i++)
   {
      *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (i * 2) + 1;
      *(pIO + APIC_IO_REG_WINDOW) = apicio[i].dest;

      *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (i * 2) + 0;
      *(pIO + APIC_IO_REG_WINDOW) = apicio[i].redir;
   }

   /*
    * Register vector information with kernel.
    *
    * Order of priority from lowest to highest :
    *
    *  IRQ 3,4,5,6,7,0,1, and then 8,9,a,b,c,d,e,f
    */

    for (i = 0; i < MAX_EXT_IRQ; i++)
    {
       if (apicio[i].irq != 0xff)
       {
          irq.irq     = apicio[i].irq;
          irq.flags   = NULL;
          irq.vector  = (uchar_t) apicio[i].redir; // low byte is vector
          irq.handler = NULL;

          if ((rc = PSDHelp(router, PSDHLP_SET_IRQ, &irq)) != NO_ERROR)
          {
            _asm int 3;
          }
       }
    }

   /*
    * Also remap IRQ 0
    */

   irq.irq     =
   irq.flags   = 0;
   irq.handler = NULL;
   irq.vector  = APIC_TIMER_VECTOR;

   if ((rc = PSDHelp(router, PSDHLP_SET_IRQ, &irq)) != NO_ERROR)
   {
     _asm int 3;
   }

   /*
    * Mask all interrupts at the 8259.
    */

   OutByte(PIC1_PORT1, 0x000000ff);
   CallIODelay();
   OutByte(PIC2_PORT1, 0x000000ff);
   CallIODelay();

   /*
    * Switch from 8259 to APIC and then restore eflags
    */

   OutByte (MP_CONTROL_PORT, (InByte(MP_CONTROL_PORT) | APIC_ENABLE));

   /*
    * Unmask IRQ08/IRQ02 at the 8259.  This will then come in on APICIO INTIN0
    * pin.  Leave IRQ 0 masked.  We implement IRQ0 using the local
    * APIC timer.
    */

   OutByte(PIC2_PORT1, 0x000000fe);
   CallIODelay();
   OutByte(PIC1_PORT1, 0x000000fb);
   CallIODelay();

   AdvancedIntMode = 1;

   Enable(eflags);

   return (NO_ERROR);
}

/***  IRQMask - Mask/unmask IRQ levels
 *
 *    This function allows masking (disabling) or unmasking (enabling)
 *    of interrupts.
 *
 *    ENTRY   pirq_mask - pointer to an IRQ_MASK structure
 *
 *    EXIT    Return Code
 *
 */

ulong_t IRQMask(PSD_IRQ *ppsd_irq) {

   ulong_t rc = NO_ERROR;
   ulong_t register Value; // used to force DWORD write to apic memory

   ulong_t register *pIO    = pIOApic;     // Make global ptrs local for optimizing
   ulong_t register *pLocal = pLocalApic;

   if (AdvancedIntMode)
   {
      ulong_t PicMask;
      ulong_t i;
      ulong_t eflags = Disable();

      PicMask = ppsd_irq->data;

      switch (ppsd_irq->flags)
      {
         case IRQ_MASK:

           for (i = 0; i < MAX_EXT_IRQ; i++)
           {
             if (PicMask & 0x0001)  // Should we mask ??
             {
               if (i == 0)  // IRQ 0 uses local timer - mask it
               {
                 Value = APIC_TIMER_MASKED;
                 *(pLocal + APIC_LOC_REG_VECT_TMR) |= Value;
               }
               else
               {
                 if (i == 2)  // masking IRQ 2 ????
                 {
//#81140 Start
//                 _asm int 3;

                   /*
                    * Set the task priority register so 2nd PIC IRQs are
                    * masked. IRQ 8 is at 0x67, IRQ 7 is at 0x66, ...
                    * We set the task register to 0x67.
                    */

                    *(pLocal + APIC_LOC_REG_TASK_PRI) = PRIORITY_2ND_PIC_MASKED;
//#81140 End
                 }
                 else
                 {
                   *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (PicRemap[i] * 2);
                   Value = APIC_REDIR_MASK_INTERRUPT;
                   *(pIO + APIC_IO_REG_WINDOW) |= Value;
                 }
               }
             }
             PicMask = PicMask >> 1;
           }

           break;

         case IRQ_UNMASK:

           for (i = 0; i < MAX_EXT_IRQ; i++)
           {
             if (PicMask & 0x0001)
             {
               if (i == 0)  // unmask the local apic timer
               {
                 Value = ~(APIC_TIMER_MASKED);
                 *(pLocal + APIC_LOC_REG_VECT_TMR) &= Value;
               }
               else
               {
                 if (i == 2)  // if IRQ2, then we are unmasking the second PIC
                 {
//#81140 Start
//                    _asm int 3;
                    *(pLocal + APIC_LOC_REG_TASK_PRI) = 0; // unmask 2nd PIC IRQs
//#81140 End
                 }
                 else
                 {
                   *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (PicRemap[i] * 2);
                   Value = ~APIC_REDIR_MASK_INTERRUPT;
                   *(pIO + APIC_IO_REG_WINDOW) &= Value;
                 }
               }
             }
             PicMask = PicMask >> 1;
           }

           break;

         case IRQ_GETMASK:

           if (*(pLocal + APIC_LOC_REG_VECT_TMR) & APIC_TIMER_MASKED)
           {
             ppsd_irq->data = 1;
           }
           else
           {
             ppsd_irq->data = 0;
           }

           for (i = 1; i < MAX_EXT_IRQ; i++)
           {
             if (PicRemap[i] != 0xff)
             {

               *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (PicRemap[i] * 2);

               if (*(pIO + APIC_IO_REG_WINDOW) & APIC_REDIR_MASK_INTERRUPT)
               {
                  ppsd_irq->data |= (1 << i);
               }
             }
           }

           break;

         case IRQ_NEWMASK:

           if (PicMask & 0x0001)
           {
             Value = APIC_TIMER_MASKED;
             *(pLocal + APIC_LOC_REG_VECT_TMR) |= Value;
           }
           else
           {
             Value = ~(APIC_TIMER_MASKED);
             *(pLocal + APIC_LOC_REG_VECT_TMR) &= Value;
           }

           PicMask = PicMask >> 1;

           for (i = 1; i < MAX_EXT_IRQ; i++)
           {
             if (PicRemap[i] != 0xff)
             {
               *(pIO + APIC_IO_REG_SELECT) = REDIR_BASE + (PicRemap[i] * 2);

               if (PicMask & 0x0001)
               {
                  Value = APIC_REDIR_MASK_INTERRUPT;
                  *(pIO + APIC_IO_REG_WINDOW) |= Value;
               }
               else
               {
                  Value = ~APIC_REDIR_MASK_INTERRUPT;
                  *(pIO + APIC_IO_REG_WINDOW) &= Value;
               }
             }
             PicMask = PicMask >> 1;
           }

           break;

         default:
            rc = -1;
            break;
      }

      Enable(eflags);
   }
   else       // else we are not in advanced interrupt mode
   {
      rc = -1;
   }

   return (rc);
}

/***  IRQReg - Access IRQ related registers
 *
 *    This function permits access to the IRQ related registers.
 *
 *    ENTRY   pirq_reg - pointer to an IRQ_REG structure
 *
 *    EXIT    Return Code
 *
 */

ulong_t IRQReg(PSD_IRQ *ppsd_irq) {

   ulong_t rc = NO_ERROR;
   ulong_t Data = 0;
   ulong_t i;

   _asm int 3;  // Break if we try this

   if (AdvancedIntMode)
   {
     if (!pPSDPLMA->physical_procnum)
     {
       switch (ppsd_irq->flags)
       {
         case IRQ_READ_IRR:

           if (CHECKIRRBIT(pLocalApic,APIC_TIMER_VECTOR))
           {
             Data = 1;
           }

           for (i = 1; i < MAX_EXT_IRQ; i++)
           {
             if (PicRemap[i] != 0xff)
             {
               if (CHECKIRRBIT(pLocalApic,(uchar_t)apicio[PicRemap[i]].redir))
               {
                 Data |= (i << i);
               }
             }
           }

           ppsd_irq->data = Data;

           break;

         case IRQ_READ_ISR:

           if (CHECKISRBIT (pLocalApic,APIC_TIMER_VECTOR))
           {
             Data = 1;
           }

           for (i = 1; i < MAX_EXT_IRQ; i++)
           {
             if (PicRemap[i] != 0xff)
             {
               if (CHECKISRBIT(pLocalApic,(uchar_t)apicio[PicRemap[i]].redir))
               {
                 Data |= (i << i);
               }
             }
           }

           ppsd_irq->data = Data;

           break;

         default:
            rc = -1;
            break;

       } // end switch ()
     }
     else
     {
       _asm int 3;  // BUGBUG can't read the registers from CPU 1
                    // Need to add remote read code here
     }
   }
   else
   {
      rc = -1;
   }

   return (rc);
}

/***  IRQEOI - Issue an EOI
 *
 *    This function is used to issue an End-Of-Interrupt.
 *
 *    ENTRY   pirq_eoi - pointer to an IRQ_EOI structure
 *
 *    EXIT    Return Code
 *
 */

ulong_t IRQEOI(PSD_IRQ *ppsd_irq) {

   if (AdvancedIntMode)
   {
     /*
      * Issue an EOI to the current processor's local unit by performing a
      * a dummy write to the APIC EOI register.
      */

     *(pLocalApic + APIC_LOC_REG_EOI) = ppsd_irq->data;
     return (NO_ERROR);
   }
   else
   {
     return (-1);
   }
}

/***  SpuriousHandler - Dismisses APIC spurious interrupts
 *
 *    This function ends an APIC spurious interrupt.
 *
 *    ENTRY   None
 *
 *    EXIT    IRETD
 *
 */

ulong_t SpuriousHandler (void) {

//  _asm clc;  // Need to do this ??? bugbug

  return(NO_ERROR);

}

/***  GarbageHandler - Dismisses interrupts during init time
 *
 *    This function ends a garbage APIC interrupt.
 *
 *    ENTRY   None
 *
 *    EXIT    IRETD
 *
 */

ulong_t GarbageHandler (void)
{
//  _asm int 3;
  *(pLocalApic + APIC_LOC_REG_EOI) = 0xff;
  fGarbageIntNotTaken = 0;
  return(0);
}

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

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

/***  In4021 - Read a register from the 4021 chipset
 *
 *    This function reads a byte from the 4021 chipset
 *
 *    ENTRY   reg - 4021 register number to read from
 *
 *    EXIT    data read
 *
 */

ulong_t In4021(ulong_t reg) {

   OutByte (CT4021_IO_PORT, reg);

   return (InByte (CT4021_IO_DATA));
}


/***  Disable - Disable interrupts
 *
 *    This function disables interrupts, and returns the original state
 *    of eflags.
 *
 *    ENTRY   None
 *
 *    EXIT    eflags
 *
 */

ulong_t Disable(void) {

   ulong_t eflags;

   _asm {
      pushfd
      pop   eax
      mov   eflags,eax
      cli
   };

   return (eflags);
}

/***  Enable - Restore the state of eflags
 *
 *    This function restores the state of eflags.
 *
 *    ENTRY   eflags - state of eflags to restore
 *
 *    EXIT    None
 *
 */

void Enable(ulong_t eflags) {

   _asm {
      push  eflags
      popfd
   };

   return;
}

/***  Port_IO - Perform I/O to a specified port
 *
 *    This function reads from port 43 and port 40.
 *    This port is implemented using the locakl apic timer.
 *
 *    ENTRY   pport_io - pointer to PORT_IO structure
 *
 *    EXIT    NO_ERROR
 *
 */


ulong_t Port_IO(PORT_IO *pport_io) {

   ulong_t register Port = pport_io->port;
   ulong_t register Data = pport_io->data;
   ulong_t rc = NO_ERROR;                         // assume handled
   ulong_t Cpu = pPSDPLMA->physical_procnum;
   uchar_t TimerCommandData;

#if 0

   _asm int 3;

   _asm push eax
   _asm mov eax, -1
   _asm tryagain:
   _asm xchg [PortSem], eax
   _asm cmp eax, -1
   _asm jz  tryagain
   _asm pop eax

   PortArray[PortCounter]   = Port;
   PortArray[PortCounter+1] = Data;
   PortArray[PortCounter+2] = pport_io->flags;
   PortArray[PortCounter+3] = -1;

   PortCounter += 4;

   if (PortCounter == 256)
   {
     PortCounter = 0;
   }

   _asm mov [PortSem], 0

#endif

/*
 * STATE MACHINE TRANSITIONS
 *
 * (1) clock01.asm(rtinit2) will write to port 43 then to port 40 twice
 *
 *     STATE_43_WRITE
 *           |
 *     STATE_40_LSB_LM_WRITE
 *           |
 *     STATE_40_MSB_LM_WRITE
 *           |
 *     STATE_43_WRITE
 *
 *
 * (2) vtimer\vtio.c(vtGetCount) will latch counter 0 then read port 40 twice
 *
 *     STATE_43_WRITE
 *           |
 *     (STATE_40_LSB_LM_READ | STATE_TIMER_LATCHED)
 *           |
 *     (STATE_40_MSB_LM_READ | STATE_TIMER_LATCHED)
 *           |
 *     STATE_43_WRITE
 *
 */

   if (AdvancedIntMode)
   {
     /*
      * Since timer 0 has been implemented using an APIC local timer,
      * we need to trap any port IO for this timer (ports 43 and 40).
      * If the current CPU != 0, then we will TIMER_IPI for resetting
      * the init count or remote read the current count register.
      */

     if (Port == PORT_CW)  // if port 43
     {
       if (pport_io->flags == IO_WRITE_BYTE)  // must be a byte write
       {
         /*
          * State must be STATE_43_WRITE, that is, we are not in the middle
          * of updating the init count or reading the latched count.  Both
          * are done 1 byte at a time.
          */

         TimerCommandData = (uchar_t)Data;

         if (TimerCommandData == (SC_CNT0+RW_LSBMSB+CM_MODE2))
         {
           /*
            * clock01.asm(rtinit2)
            */

           if (TimerState & STATE_43_WRITE)
           {
             /*
              * Writing new count and mode.
              * The next write of port 40 will be LSB.
              */
             TimerState = STATE_40_LSB_LM_WRITE;
             TimerStatus &= ~(0x40);
           }
           else
           {
             _asm int 3; // SM violation - we have two threads accessing
                         // timer port concurrently
             rc = -1;
           }
         }
         else if (TimerCommandData == (SC_READBACK+RW_NOLATCHSTATUS+RB_SELCNT0))
         {
           /*
            * (11-01-0010) Latch counter 0 vdev\vtimer\vtio.c(vtGetCount)
            */

           if (TimerState & STATE_43_WRITE)
           {
             /*
              * Latch the current value.
              * The next read of port 40 will be LSB.
              */

             TimerValue = (Cpu
                           ?
                           RemoteRead ((uchar_t)(APIC_LOC_REG_CURR_COUNT>>2),
                                       0 )
                           :
                           *(pLocalApic + APIC_LOC_REG_CURR_COUNT));

             TimerState = (STATE_40_LSB_LM_READ | STATE_TIMER_LATCHED);
           }
           else if (TimerState & STATE_TIMER_LATCHED)
           {
              // additional timer latch counter commands are ignored
           }
           else
           {
             _asm int 3; // SM violation - we have two threads accessing
                         // timer port concurrently
             rc = -1;
           }
         }
         else if (TimerCommandData == SC_READBACK+RW_NOLATCHCOUNT+RB_SELCNT0)
         {
           /*
            * (11-10-0010) Counter 0 status vdev\vtimer\vtio.c(vtGetStatus)
            */

           if (TimerState & STATE_43_WRITE)
           {
             TimerState = STATE_40_STATUS;
           }
           else
           {
             _asm int 3; // SM violation - we have two threads accessing
                         // timer port concurrently
             rc = -1;
           }
         }
         else
         {
           // not for timer 0 so ignore
           rc = -1;
         }
       }
       else // else not write byte
       {
         /*
          * IO to port 43 must be a byte write.
          */
         rc = -1;
       }
     }
     else if (pport_io->port == PORT_CNT0)  // if port 40
     {
       if (pport_io->flags == IO_READ_BYTE)  // if a byte read
       {
         switch (TimerState)
         {
           case (STATE_40_LSB_LM_READ | STATE_TIMER_LATCHED): // 1st part of read

             TimerState = (STATE_40_MSB_LM_READ | STATE_TIMER_LATCHED);
             pport_io->data = (uchar_t)TimerValue;

             break;

           case (STATE_40_MSB_LM_READ | STATE_TIMER_LATCHED): // 2nd part of read

             TimerState = STATE_43_WRITE;
             pport_io->data = (uchar_t)(TimerValue >> 8);

             break;

           case (STATE_40_STATUS):

             TimerState = STATE_43_WRITE;
             pport_io->data = (uchar_t)(TimerStatus);

             break;

           default:
             _asm int 3; // SM violation - we have two threads accessing
                         // timer port concurrently
             rc = -1;
             break;
         }
       }
       else if (pport_io->flags == IO_WRITE_BYTE)  // must be a byte write
       {
                      // Must be on CPU 0 because the
         if (Cpu)     // local apic timer is there.
         {
           /*
            * Only the clock DD will do this using port IO so
            * we should never hit this condition as long as the
            * DD is excuting on CPU 0.
            */
           _asm int 3;
           rc = -1;
         }
         else
         {
           switch (TimerState)
           {
             case STATE_40_LSB_LM_WRITE :

               TimerValue = (uchar_t)Data;
               TimerState = STATE_40_MSB_LM_WRITE;
               rc = NO_ERROR;
               break;

             case STATE_40_MSB_LM_WRITE :

               TimerValue += ((uchar_t)Data << 8);

               if (TimerValue == 0
                   ||
                   TimerValue == 0x10000)
               {
                 *(pLocalApic + APIC_LOC_REG_INIT_COUNT) = APIC_TIMER_INIT_COUNT;
                 RollOverCount = 0x10000;
               }
               else
               {
                 RollOverCount = TimerValue;
                 *(pLocalApic + APIC_LOC_REG_INIT_COUNT)
                 =
                 ApicInitCount
                 =
                 GetInitCount (TimerValue);
               }

               TimerState = STATE_43_WRITE;
               TimerStatus |= 0x40;
               break;

             default:
              _asm int 3; // SM violation - we have two threads accessing
                          // timer port concurrently
               rc = -1;
               break;
           }
         }
       }
       else
       {
         rc = -1;     // not a read and not a wite
       }
     }
     else // not a port we care about
     {
       rc = -1;
     }
   }
   else // not in advanced interrupt mode
   {
     rc = -1;
   }

   return (rc);

}

/*
 * Takes an 8254 countdown value ( <= 65536 ) and determines what
 * the value would be on the local APIC timer.
 *
 * EAX = (InitCount8254 * 439409) / 65536
 * round EAX up to 1
 */

ulong_t GetInitCount (ulong_t InitCount8254)
{
   if (InitCount8254 >= MAX_8254_INIT_COUNTER)
   {
     return (APIC_TIMER_INIT_COUNT);
   }
   else
   {
     _asm mov   eax,    [InitCount8254];        ;
     _asm mov   ecx,    APIC_TIMER_INIT_COUNT   ;
     _asm mul   ecx;                            ; InitCount8254   EAX
     _asm mov   ecx,    MAX_8254_INIT_COUNTER   ; ------------- = -------------
     _asm div   ecx;                            ; 65536           439409
     _asm clc;                                  ;
     _asm add   dx,     32768                   ; round up remainder
     _asm jnc   exit2                           ;
     _asm inc   eax                             ;
     _asm exit2:                                ;
   }
}

void UpdateTimerRollover (ulong_t NewRollover, ulong_t pTimerRollover)
{
     _asm push  ds;

     _asm xor edx, edx
     _asm lds edx,   [pTimerRollover];
     _asm mov eax,   [NewRollover];
     _asm mov [edx+4], eax;               // [edx]   = Current Rollover count
                                          // [edx+4] = Next Rollover count
     _asm pop ds;
}


/***  Set_SysTimer - Init the local apic timer to a new rate
 *
 *    This function will only be executed on cpu 0 since we set the PSD
 *    flag INIT_TIMER_CPU0.  We must be in advanced interrupt mode.
 *
 *    vtimer.sys calls VDHSETTIMER0 - if on cpu 1, then we use the timer IPI
 *                                    if on cpu 0, then we just call into PSD
 *
 *                                    Regardless of what CPU is setting the
 *                                    system timer, we will only be called on
 *                                    CPU 0.
 *
 *    ENTRY
 *
 *    EXIT
 *
 */

ulong_t Set_Sys_Timer(PSD_SETTMR  *ppsd_settmr_s)
{
   ulong_t rc = NO_ERROR;
   ulong_t eflags;
   ulong_t pTimerRollOver;          // 16:16 ptr to rollover count (QWORD).
                                    // points to qwTmrRollover in the
                                    // DosHlpData segemnt (src\bios\dhtmr.asm).
                                    // The clock device driver checks this
                                    // when handling IRQ 0.  He uses the
                                    // rollover count to increment a running
                                    // count of timer ticks.  If we change the
                                    // initial value for the countdown of the
                                    // timer, then the rollover count must
                                    // reflect this new value so the timer tick
                                    // count is updated correctly.

   if (AdvancedIntMode)
   {
     if (pPSDPLMA->physical_procnum)  // if processor 1
     {
       _asm int 3;  // BUGBUG - should be PANIC - this should not happen
                    //          since we use INIT_TIMER_CPU0 PSD flag.
     }

     eflags = Disable();      // Disable interrupts

     /*
      * Save new RollOverCount
      * Update the rollover with the new rollover count
      */

     RollOverCount = ppsd_settmr_s->NewRollOver;
     UpdateTimerRollover (RollOverCount,
                          ppsd_settmr_s->pqwTmrRollover);

     if ((ushort_t)RollOverCount)
     {
       /*
        * Since the new countdown is not the maximum that can be used for
        * the 8254 16 bit counter 65K (or 0), we must compute what the
        * initial counter should be when using the 8 MHz clock rate for the
        * local apic timer.  We need to have the same interrupt rate.
        */

       *(pLocalApic + APIC_LOC_REG_INIT_COUNT)
       =
       ApicInitCount
       =
       GetInitCount (RollOverCount);
     }
     else
     {
       /*
        * The countdown was set a the maximum 65K (or 0 because the 8254
        * is a 16 bit counter).  The local apic timer is fed with an 8 Mhz
        * clock, so we have already computed what the initial count should
        * be in order get the same interrupt rate as the 1.193167 MHz
        * 8254 clock.
        */


       *(pLocalApic + APIC_LOC_REG_INIT_COUNT)
       =
       ApicInitCount
       =
       APIC_TIMER_INIT_COUNT;
     }

     Enable(eflags);
   }
   else
   {
     rc = -1;
   }
   return(rc);
}

/*
 * Converts the roller count used for the local apic timer (InitCount)
 * and converts it the 8254.  Since 8254 is fed by a 1.19 Mhz clock
 * and the local apic is fed by a 8 Mhz clock, we have to convert
 * between the two.  This function returns the 8254 RolloverCount.
 *
 *    Rollover     InitCount
 *    --------  =  ---------
 *    1193167       8000000
 *
 *    or
 *
 *    (EAX) 8254 Rollover = (TIMERFREQ) * (InitCount) / (APIC_TIMER_CLOCK_FREQ)
 */

ulong_t GetRollOverCount (ulong_t InitCount)
{
  if (InitCount == APIC_TIMER_INIT_COUNT)
  {
    return (MAX_8254_INIT_COUNTER);
  }
  else
  {
    _asm mov   eax,    [InitCount];                  ;
    _asm mov   ecx,    TIMERFREQ;                    ; 1193167 Hz
    _asm mul   ecx;                                  ;
    _asm mov   ecx,    APIC_TIMER_CLOCK_FREQ         ; 8000000 Hz
    _asm div   ecx;                                  ;

    _asm cmp   edx,    (APIC_TIMER_CLOCK_FREQ>>1)    ; round up remainder
    _asm jl    exit2                                 ;
    _asm inc   eax                                   ;
    _asm exit2:                                      ;
  }
}

/*
 * Takes the apic current timer count and the init count and then
 * converts it to the corresonding 8254 value
 *
 * y = 8254 count
 * x = current apic timer count (CurrentCount)
 * z = apic timer init count    (InitCount)
 *
 *  y       x
 * ---  =  ---   or  y = (64K)(x)/(z)
 * 65K      z    or (MAX_8254_INIT_COUNTER) * (CurrentCount) / (InitCount)
 *
 * x and z are in local apic memory
 */

ulong_t CurrTo8254 (ulong_t InitCount, ulong_t CurrentCount)
{
  _asm mov   eax,    [CurrentCount]                 ;
  _asm mov   ecx,    MAX_8254_INIT_COUNTER          ;
  _asm mul   ecx                                    ;
  _asm mov   ecx,    [InitCount]                    ;
  _asm div   ecx                                    ;

  _asm mov   ecx,    [InitCount]                    ;
  _asm shr   ecx,    1                              ;
  _asm cmp   edx,    ecx                            ;
  _asm jl    exit3                                  ;
  _asm inc eax                                      ;
  _asm exit3:                                       ;
}

/*
 * Goes out to the DOS segment and gets the accumulated timer ticks
 * from qwTmr.
 */


void GetAccumulatedTicks (ulong_t *TimerLo, ulong_t *TimerHi, ulong_t pqwTmr)
{
     _asm push  es;

     _asm xor edx, edx
     _asm les edx, [pqwTmr]

     _asm mov eax, es:[edx];            // [edx]   = low DWORD
     _asm mov ecx, [TimerLo];
     _asm mov [ecx], eax;

     _asm mov eax, es:[edx+4];          // [edx]   = Hi DWORD
     _asm mov ecx, [TimerHi];
     _asm mov [ecx], eax;

     _asm pop es;
}

/*
 * Handles adding current timer value to 64 bit counter.
 */

void AddCurrentTime (ulong_t *TimerLo,
                     ulong_t *TimerHi,
                     ulong_t CurrentTimerValue)
{
     _asm mov eax, [CurrentTimerValue]       ;
     _asm mov edx, [TimerLo]                 ;
     _asm add [edx], eax                     ;
     _asm jnc exit4                          ;
     _asm mov edx, [TimerHi]                 ;
     _asm inc dword ptr [edx]                ;
     _asm exit4:                             ;
}

/*
 * Must be called with ints disabled.
 */

ulong_t RemoteRead (uchar_t Register, ulong_t Cpu)
{
  ulong_t RegisterValue;
  ulong_t register ICRRegister;
  ulong_t register *pLocal = pLocalApic;

  *(pLocal + APIC_LOC_REG_INTCMD_63_32) = Cpu;  // dest
  *(pLocal + APIC_LOC_REG_INTCMD_31_0)  = Register
                                          +
                                          APIC_ICR_DEL_MODE_REM_READ;
  /*
   * Loop on the Remote Read Status until not in progress.
   */

  while (1)
  {
    ICRRegister = *(pLocal + APIC_LOC_REG_INTCMD_31_0);
    ICRRegister &= APIC_ICR_REM_READ_RSVD;
    if (ICRRegister != APIC_ICR_REM_READ_IN_PROG)
    {
      break;
    }
  }

  if (ICRRegister != APIC_ICR_REM_READ_VALID)
  {
    _asm int 3;
  }

  return (*(pLocal + APIC_LOC_REG_REMOTE));
}

/*
 * For this code to work correctly, it is assumed that we must own the
 * R0 spinlock to execute this code.
 *
 * If we are inside the cli/sti pair below and the ISR bit is set,
 * then we know that the rollover count has not yet been updated, since
 * the r0 owner (us) must be spun with an IPI.
 * They will be stuck in INTIRQROUTER until we are spun, so the rollover
 * count won't change if the ISR or IRR bits are set once we are inside the
 * cli/sti pair.
 *
 * The correct fix is to add a spinlock around updating and reading the
 * rollover count.
 */

ulong_t Query_Sys_Timer(PSD_QRYTMR *ppsd_qrytmr_s)
{
  ulong_t eflags;
  ulong_t TimerLo;                 // Low dword that 16:16 ptr points to
  ulong_t TimerHi;                 // Hi  dword that 16:16 ptr points to

  ulong_t ApicCurrentCount;
  ulong_t CurrentTimerValue;       // The value of the apic timer counter
                                   // converted to 8254.

  ulong_t IRRFlag = 0;
  ulong_t rc = NO_ERROR;
  ulong_t register *pLocal = pLocalApic;
  ulong_t Cpu = pPSDPLMA->physical_procnum;

  if (AdvancedIntMode)
  {
    eflags = Disable();              // Disable interrupts

    /*
     * Get the current accumulated timer ticks.
     *
     * qwTmr is in DOSSEG (src\bios\dhtmr.asm)
     * 16:16 ptr to qwtmr is in _pqwTmr (see src\dos\dev\tmr.asm)
     */

    GetAccumulatedTicks (&TimerLo, &TimerHi, ppsd_qrytmr_s->pqwTmr);

    /*
     * Read the Current count of the apic timer.
     */

    ApicCurrentCount = (Cpu
                        ?
                        RemoteRead ((uchar_t)(APIC_LOC_REG_CURR_COUNT>>2), 0 )
                        :
                        *(pLocal + APIC_LOC_REG_CURR_COUNT));

    CurrentTimerValue = CurrTo8254 ( ApicInitCount,
                                     ApicInitCount - ApicCurrentCount);

    /*
     * Assume that we service IRQ 0 completly before the next IRQ 0
     * comes in.
     */

    if (Cpu)  // if processor 1
    {
      IRRFlag  = CHECKIRRBITREMOTE (0,APIC_TIMER_VECTOR);
      IRRFlag |= CHECKISRBITREMOTE (0,APIC_TIMER_VECTOR);
    }
    else
    {
      IRRFlag  = CHECKIRRBIT (pLocal,APIC_TIMER_VECTOR);
      IRRFlag |= CHECKISRBIT (pLocal,APIC_TIMER_VECTOR);
    }

    if (IRRFlag)
    {
      /*
       * IRR and or ISR bit is set so we have to add in the current rollover
       * count. Get the current timer count again.
       */

      /*
       * Read the Init and Current counts of the apic timer.
       */

      ApicCurrentCount = (Cpu
                          ?
                          RemoteRead ((uchar_t)(APIC_LOC_REG_CURR_COUNT>>2), 0 )
                          :
                          *(pLocal + APIC_LOC_REG_CURR_COUNT));


      CurrentTimerValue = RollOverCount
                          +
                          CurrTo8254 ( ApicInitCount,
                                       ApicInitCount - ApicCurrentCount );

    }

    AddCurrentTime (&TimerLo, &TimerHi, CurrentTimerValue);

    ppsd_qrytmr_s->qw_ulLo_psd = TimerLo;
    ppsd_qrytmr_s->qw_ulHi_psd = TimerHi;

    Enable(eflags);
  }
  else  // not in advanced interrupt mode
  {
    rc = -1;
  }

  return(rc);
}

ulong_t PSDReboot ()
{

  ulong_t eflags = Disable();     // Disable interrupts
  ulong_t Value,i;

  if (AdvancedIntMode)
  {
    /*
     * if we were IPIed by CPU 1, then we need to end it.
     */

    EndIPI(0);

    /*
     * Mask all io apic interrupts and the local timer interrupt.
     */

    for (i = 0; i < MAX_APIC_IO; i++)
    {
       *(pIOApic + APIC_IO_REG_SELECT) = REDIR_BASE + (i * 2) + 1;
       *(pIOApic + APIC_IO_REG_WINDOW) = apicio[i].dest;

       *(pIOApic + APIC_IO_REG_SELECT) = REDIR_BASE + (i * 2) + 0;
       *(pIOApic + APIC_IO_REG_WINDOW) = apicio[i].redir | APIC_REDIR_MASK_INTERRUPT;
    }

    Value = APIC_TIMER_MASKED;
    *(pLocalApic + APIC_LOC_REG_VECT_TMR) |= Value;

    /*
     * Mask lll 8259 ints. - ( when we switch to reak mode for a system dump,
     *                         the proper ints will be unmasked at the 8259 )
     * Switch from APIC to 8259.
     * Switch back to UP.
     */

    OutByte(PIC1_PORT1, 0x000000ff);
    CallIODelay();
    OutByte(PIC2_PORT1, 0x000000ff);
    CallIODelay();

    AdvancedIntMode = 0;

    OutByte (MP_CONTROL_PORT,
            (InByte (MP_CONTROL_PORT) & ~(APIC_ENABLE | MP_ENABLE)));

#if 0
    OUT(0x47);OUT(0xd);OUT(0xa);
#endif

    Enable(eflags);
  }
  else
  {
    /*
     * if we are not in advanced interrupt mode, then all apic interrupts
     * should be masked and the local timer interrupt should be masked.
     * We only need to switch to UP mode.
     */

    OutByte (MP_CONTROL_PORT, (InByte (MP_CONTROL_PORT) & ~(MP_ENABLE)));
  }

  return (0);
}

/*
 * Cleanup any pending ISR and IRR bits in the apic.  Some DDs can't handle
 * these interrupts coming in when we switched to advanced int mode.
 */

void CleanupApic(void)
{
   ulong_t i,j;
   SET_IRQ irq;
   ulong_t rc, fFound;
   ulong_t eflags = Disable();     // Disable interrupts

   /*
    * Switch to advanced int mode.
    */

   OutByte (MP_CONTROL_PORT, (InByte(MP_CONTROL_PORT) | APIC_ENABLE));

   /*
    * EOI any interrupts that are in the ISR registers.
    */

   for (i = 255 ; i > 15 ; i--)
   {

     if (CHECKISRBIT(pLocalApic,i))
     {
       *(pLocalApic + APIC_LOC_REG_EOI) = i;
//       _asm int 3;
     }
   }

   /*
    * Take any interrupts that are pending in the IRR registers
    */

   for (i = 0 ; i < 256 ; i++)
   {
     /*
      * If any of the 256 IRR bits are set then
      *
      * (1) register a handler
      * (2) setup the io APIC
      *       unmask interrupt
      *       set the vector
      * (3) take the interrupt.
      * (4)   mask the interrupt
      * (5) get rid of the handler
      *
      */

     if (CHECKIRRBIT(pLocalApic,i))
     {
       if (i != APIC_TIMER_VECTOR)   // we can ignore this IRR bit.
       {
//         _asm int 3;

         fFound = 0;

         for (j = 0 ; j < MAX_APIC_IO ; j++)
         if ( (uchar_t) apicio[j].redir == (uchar_t) i)
         {
           fFound = 1;
           break;
         }

//         if (!fFound)
//         {
//           _asm int 3;
//         }

         irq.irq     = 0xdf;
         irq.flags   = IRQf_LSI;
         irq.vector  = i;
         irq.handler = (P_F_2)&GarbageHandler;

         if ((rc = PSDHelp(router, PSDHLP_SET_IRQ, &irq)) == NO_ERROR)
         {
           /*
            * Set interrupt destination
            */

           *(pIOApic + APIC_IO_REG_SELECT) = REDIR_BASE + (j * 2) + 1;
           *(pIOApic + APIC_IO_REG_WINDOW) = 0;

           /*
            * Unmask interrupt
            */

           *(pIOApic + APIC_IO_REG_SELECT) = REDIR_BASE + (j * 2) + 0;
           *(pIOApic + APIC_IO_REG_WINDOW) = irq.vector;

           /*
            * Take interrupt
            */

           _asm sti;
           while (fGarbageIntNotTaken);
           fGarbageIntNotTaken = 1;
           _asm cli;

           /*
            * Mask interrupt
            */

           *(pIOApic + APIC_IO_REG_SELECT) = REDIR_BASE + (j * 2);
           *(pIOApic + APIC_IO_REG_WINDOW) = irq.vector | APIC_REDIR_MASK_INTERRUPT;

         }
         else
         {
           _asm int 3;
         }
       }
     }
   }

   OutByte (MP_CONTROL_PORT, (InByte(MP_CONTROL_PORT) & ~(APIC_ENABLE)));
   Enable(eflags);
}

void CallIODelay(void)
{
  IODelay;
}

#if 0

void OUT(unsigned char _q)
{
    _asm push  eax ;
    _asm push  edx ;
    _asm mov   edx, 0x2f8 ;
    _asm push  edx
    _asm add   edx, 5

    _asm oc1:
    _asm in      al,dx
    _asm test    al,020H
    _asm jz      short oc1

    _asm pop   edx
    _asm mov   ax,  _q ;
    _asm out   dx, al  ;

    _asm pop   edx     ;
    _asm pop   eax     ;
}
#endif
