
/*
** PSD : MAIN.C (WYSE.PSD)
**
**       This PSD provides the platform specific code for interfacing
**       to the Wyse Models 760MP (486) and 765MP (Pentium) systems.  Both
**       of these systems use the Columbia-2 system board (WYS7801).
**
**       NOTE : Model 765MP has not been tested.
**
**       NOTE : We need to add supoort for Columbia-1 system board (WYS7001).
**              This board is used in Model 740MP and well as in a UP product.
**
**       NOTE : We need to add supoort for Columbia-3 system board (WYS7809).
**              This board is used in Model 775MP.
**
**       Currently, we only have a model 760MP with 486 33/66 DX2 (5 CPUs)
**
****************************************************************************
**
**       Pseudo Code for Install()
**          --
**          -- Save global data
**          -- if (EISA product ID == Columbia-2 System Board)
**             then return (NO_ERROR)
**             else return (-1)
**
**       Pseudo Code for Init()
**          --
**          -- Determine phys addr of SwitchToRealMode()
**             -- Map page directory
**             -- Map page table
**             -- Get phys addr of _SwitchToRealMode
**             -- Map _SwitchToRealMode()
**             -- Update operand with phys addr
**          -- Map page table for upper 128K (MP Firmware)
**             -- Create ptes for upper 128K (temporary on PSDHelp available)
**          -- Save FW addr of icu_sync_master and icu_send_master
**          -- icu_synch_master to get slave CPU config
**          -- Save slot ID of slave CPUs and master
**          -- Register IPI handler
**          -- Set init flags
**
**       Pseudo Code for StartProcessor
**          --
**          -- icu_send_master a CPU_BOOT package to slave CPU
**
**       Pseudo Code for ProcInit()
**          --
**          -- Save logical CPU number
**          -- Disable interrupts in ICU Control Register
**          -- Stop the counter/timer
**          -- Set the Base Vector Registers
**          -- Set the Local Interrupt Pointer Register to use interrupt
**             level 1 for INT_SLOT commands
**          -- if (slave CPU)
**          -- then mask all interrupts except level 1 (used for IPIs)
**          --      set the ICU control register
**          -- if (master CPU)
**          -- then mask all interrupts except EISA IRQs and interrupt level 1
**          --      set the ICU control register
**
**       Pseudo Code for GenIPI()
**          --
**          -- Write an INT_SLOT command to command register
**
**/

// BUGBUG - need to modify PSD to support all MP system boards.
// BUGBUG - Install() will return NO_ERROR for a Columbia-2 system
//          board.  The model could be a 760MP (486) or a 765MP (Pentium).
//          We should determine if we  are on a 760MP (486) or a 765MP
//          (Pentium).  We should return -1 if on Pentium since we do not
//          have one to test with.
// BUGBUG - TESTCASE - switch master processor to different slots. Leave
//                     slot 1 or 2 empty.
// BUGBUG - TESTCASE - Try only one or two CPUs.
// BUGBUG - added IPEs instead of int 3.
// BUGBUG - verify a20 line, nmis, and IRQ 13s set up correctly ??
// BUGBUG - after last icu_send_master is sent, the Firmware can be unmapped.
// BUGBUG - what IDT entry does OS/2 now map the third PIC off of.
// BUGBUG - For GenIPI, after the INT_SLOT command is written , what event
//          causes the busy bit to be cleared in the command register ?
//          ---- cmd sent on ICU bus ? other processor received int ?
//          We loop until the busy bit is cleared.
// BUGBUG - What are the max number of cpus for the other system boards
//          this PSD may support.  Currently we are set at 32, although
//          Model 760MP does not support that many (only 5).
// BUGBUG - unmap all of the junk we mapped after new PSDHelp available

#define INCL_ERROR_H

#include <os2.h>
#include <psd.h>
#include <int.h>
#include <wyse.h>

/*
 *  Global Variables
 */

ulong_t    NumberOfCPUs;           // Set by Init()
ulong_t   *CPUNumber;              // Set by ProcInit() - (in CPU local memory)
                                   // Pointer set by Init()
ulong_t    CPUsInited;             // Incremented by ProcInit()
ulong_t    pCode;                  // Physical addr of _SwitchToRealMode()
S_CPUINFO  CPUINFO[MAX_CPUS];      // Filled in by Init()
P_F_2      router;                 // Set by Install()

// char    *pParmString;           // Set by Install()
// ulong_t  sizePLMA;              // Set by Install()

/*
 * Following are in Firmware.
 *
 * icu_synch_master - used to get config of slave cpus
 * icu_send_master  - used to send a start message to the slave cpus
 */

ulong_t (_cdecl *icu_sync_master) (unsigned char i_sync,
                                   unsigned char i_exp_cfg,
                                   unsigned int  i_timeout);

ulong_t (_cdecl *icu_send_master) (char* buffer,
                                   unsigned char i_exp_cfg);

ulong_t GenIPI (ulong_t procnum);
ulong_t IPIPresent (void);
ulong_t InByte (ulong_t port);
ulong_t InWord (ulong_t port);
ulong_t InDword(ulong_t port);
void    OutByte (ulong_t port, ulong_t data);
void    OutWord (ulong_t port, ulong_t data);

extern   void SwitchToRealMode();    // switch.asm

//ulong_t EndIPI (ulong_t procnum);  // not EXPORTED

/***  Install - Install PSD
 *
 *    This function checks to see if this PSD is installable on the
 *    current platform.
 *
 *    0) Global data is initialized and pinstall data is saved.
 *    1) EISA port is checked for correct product ID.
 *
 *    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;

   /*
    * Init and set up global variables
    */

   NumberOfCPUs = CPUsInited = 0;

   router      = pinstall->pPSDHlpRouter;
   CPUNumber   = (void *)pinstall->pPSDPLMA;

//   pParmString = pinstall->pParmString;
//   sizePLMA    = pinstall->sizePLMA;

   /*
    * Check the EISA port ID to see if we have the supported Wyse
    * sytem board for this PSD.
    */

   if (InDword (EISA_PRODUCT_ID_PORT1) == WYSE_7801_ID)
   {
     rc = NO_ERROR;
   }
   else
   {
     rc = -1;
   }

   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.
 *
 *    - Inits the master CPU
 *        - Gets its base IO addr
 *        - Gets the CPU type
 *
 *    - 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) {

   ulong_t rc = NO_ERROR;
   ulong_t i;

   ulong_t  CR3Reg;                  // used to get CR3
   ulong_t *pCR3Reg = &CR3Reg;       //
   ulong_t *PageDirectory;           // ptr to page directory
   ulong_t *PageTable;               // ptr to last last page table

                                     // pSwitch=lin addr of _SwitchToRealMode()
   ulong_t  pSwitch = (ulong_t)*SwitchToRealMode;
   ulong_t  CodePageTableEntry;      // pte for _SwitchToRealMode()
   ulong_t *pOperand;                // ptr to operand in _SwitchToRealMode

   ulong_t *PageTableEntry;          // ptr to last 32 ptes

   VMALLOC vmac;
   SET_IRQ irq;

//   _asm int 3;

   /*
    * Determine the physical address that _SwitchToRealMode() is at.
    * This physical address is used in the icu_send_master call to
    * start the processor.  The slave processors come up in protect
    * mode with paging disabled (linear = physical).  In order
    * for _SwitchToRealMode to work, we need to update an instruction
    * operand in _SwitchToRealMode with the physical address of
    * of _SwitchToRealMode.  Also, _SwitchToRealMode depends on it
    * being the only object in the CODE1 class.  That is, * it needs to be
    * on a page boundary.
    */

    // We could have use in-line asm to get phsy addr of _SwitchToRealMode()
    // and saved mapping the page directory and page table

    // Map the page directory

   _asm { mov ebx, pCR3Reg };
   _asm { mov eax, cr3   };
   _asm { mov dword ptr [ebx], eax   };

   vmac.addr   = CR3Reg;
   vmac.cbsize = 0x1000;
   vmac.flags  = VMALLOC_PHYS;

   if (PSDHelp(router, PSDHLP_VMALLOC, &vmac) == NO_ERROR)
   {
     PageDirectory = (void*)vmac.addr;

     // Map the Page Table entry that _SwitchToRealMode is on.

     vmac.addr   = *(PageDirectory + ((pSwitch & 0xffc00000) >> 22));
     vmac.cbsize = 0x1000;
     vmac.flags  = VMALLOC_PHYS;

     if (PSDHelp(router, PSDHLP_VMALLOC, &vmac) == NO_ERROR)
     {
       // Get a pointer to _SwitchToRealMode and update the
       // instruction operand for MOV EBX, 01020304. (01020304 is dummy operand)

       PageTable = (void*)vmac.addr;
       CodePageTableEntry = *(PageTable + ((pSwitch & 0x003ff000) >> 12) );
       pCode = (CodePageTableEntry & 0xfffff000) + (pSwitch & 0x00000fff);

       /*
        * Map the physical address that _SwitchToRelaMode is on so that
        * we can update the instruction operand.  Because _SwitchToRealMode
        * has its own code class for it's segment, it will be loaded on
        * a page boundary.
        */

       vmac.addr   = pCode;
       vmac.cbsize = 0x1000;
       vmac.flags  = VMALLOC_PHYS;

       if (PSDHelp(router, PSDHLP_VMALLOC, &vmac) == NO_ERROR)
       {
          pOperand = (ulong_t*) (vmac.addr+2);
         *pOperand = pCode;
       }
       else
       {
          _asm int 3;
       }
     }
     else
     {
        _asm int 3;
     }
   }
   else
   {
      _asm int 3;
   }

/******* TEMPORARY CODE BEGIN **************/

   /*
    * Create our own PTEs for mapping Firmware until a PSDHelp
    * is provided that will enable us to map physical = linear
    */

   /*
    * We need to map the physical addresses %%fffe0000-%ffffffff (128k)
    * to the linear addresses %%fffe0000-%%ffffffff.  Temporarily, we
    * make our own pte entries, 1st saving off what is in the page
    * table and then restore it after leaving.  This is all done so
    * we can execute protect mode code that is in the firmware.  The
    * firmware expects this mapping.
    */

   // Map the last page table.

   vmac.addr   = *(PageDirectory + 0x3ff) & 0xfffff000;
   vmac.cbsize = 0x1000;
   vmac.flags  = VMALLOC_PHYS;

   if (PSDHelp(router, PSDHLP_VMALLOC, &vmac) == NO_ERROR)
   {
     // Create entries for the last 32 pages in this page table
     // They map physical addresses %%fffe000 thru %%ffffffff

     PageTable      = (void*)vmac.addr;
     PageTableEntry = PageTable + (0x400 - 0x20);  // 1st of 32 ptes

     for (i = 0 ; i < 32 ; i++)
     {
       if (PageTableEntry[i] != 0)
       {
          _asm int 3;   // Something already mapped a firmware linear addr.
       }
       PageTableEntry[i] = (FW7801_FW_BASE_PHYS_ADDR + (i * 4096)) | 0x00000613;
     }
   }
   else
   {
     _asm int 3;   // map page table entry failed
   }

/******* TEMPORARY CODE END **************/

   /*
    * Map the MP firmware.
    */

//   vmac.addr   = FW7801_FW_BASE_PHYS_ADDR;
//   vmac.cbsize = FW7801_FW_SIZE;
//   vmac.flags  = VMALLOC_PHYS;   // add loc_specific

   icu_sync_master =  *((ulong_t*)(FW7801_FT_BASE_PHYS_ADDR
                                  +
                                  (FW7801_ICU_SYCH_MSTR<<2)));

   icu_send_master =  *((ulong_t*)(FW7801_FT_BASE_PHYS_ADDR
                                  +
                                  (FW7801_ICU_SEND_MSTR<<2)));

   /*
    * Get the number of other CPUs not started yet.
    * Could get a timeout, which is OK.
    */

   rc = icu_sync_master (ICU_SYNC_WAIT,
                         ICU_EXPECT_ALL,
                         ICU_SYNCH_MSTR_TIMEOUT);
   if ( (((rc >> 16) & 0xff) == IC_OK)
        ||
        (((rc >> 16) & 0xff) == IC_TIMEOUT) )
   {
     NumberOfCPUs++;  // Increment one for this CPU (we are the master)

     /*
      * Get the configuration.  The master CPU is not included.
      * Loop through the configuration and :
      * - save the slot id.
      * - increment number of cpus
      */

     rc = (rc & 0x0000ff00) >> 8;

     for (i = 0 ; i < 8 ; i++)
     {
       if (rc & 0x01)
       {
         // Save slot
         CPUINFO[NumberOfCPUs].Slot = i;

         // Increment number of CPUs
         NumberOfCPUs++;
       }

       rc = rc >> 1;
     }

     /*
      * Read the master slot ID.  The slaves were obtained above.
      */

     OutByte (WYSE_MY_PTR_REG ,WYSE_BCU_ID_PTR);
     CPUINFO[0].Slot = InByte(WYSE_MY_DATA_REG) & 0x0f;

  // wwb_int from isp - only one
  // convert to icu level
  // for dist

     /*
      * Register an IPI handler.  We have based the "third PIC" off of
      * IDT vector 0x30.  The third PIC is for the ICU interrupts.
      * We have chosen interrupt level 1 which is higher than any
      * of the levels assigned to the EISA IRQ levels.  Level 0 is
      * at the base of the "third PIC".  We also use an IRQ level
      * of 16, which would be at the base of the "third PIC".
      * The system actually does not physically have a third PIC.
      */

     irq.irq     = WYSE_IPI_IRQ;
     irq.flags   = IRQf_IPI;
     irq.vector  = PIC3_BASE + WYSE_IPI_PRIORITY_LVL;
     irq.handler = (P_F_2)IPIPresent;

     if (PSDHelp(router,PSDHLP_SET_IRQ, &irq))
     {
        _asm int 3; // IPE here
     }
   }
   else
   {
      _asm int 3;  // IPE - ICU_SYNC_MASTER failed - cannot get config data.
      rc = -1;
   }

   /*
    * Set Init flags.
    */

   pinit->flags = INIT_GLOBAL_IRQ_ACCESS | INIT_USE_FPERR_TRAP;
   pinit->version = VERSION;

   return (NO_ERROR);
}

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

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

   return (AllowableCPUs);   // Detrmined during Init()
}

/***  StartProcessor - Start a processor
 *
 *    This function starts a processor.
 *
 *    Firmware is called to start a processor.
 *
 *    ENTRY   procnum - processor number to start (0-based)
 *
 *
 *    EXIT
 *
 */

ulong_t StartProcessor(ulong_t procnum) {

   ulong_t rc;
   struct _PacCpuBoot Buffer;

   Buffer.Length       = PAC_CPUBOOT_LENGTH | ICU_SYNC_CONT;
   Buffer.Destination  = 1 << CPUINFO[procnum].Slot;
   Buffer.Type         = PAC_CPUBOOT_CMD | PA_CMD | PA_MPFW;
   Buffer.Reserved     = 0;
   Buffer.BootPhysAddr = pCode;

   rc = icu_send_master ((uchar_t*)&Buffer,
                         (uchar_t)(1 << CPUINFO[procnum].Slot));

   if ( ((rc >> 16) & 0x000000ff) == IC_OK)
   {
     return (NO_ERROR);
   }
   else
   {
     CPUINFO[procnum].SavedRc = rc;   // Save for debugging
     return (-1);
   }
}

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

   ushort_t ccu;

   /*
    * Set the logical processor number (zero based).
    */

   *CPUNumber = CPUsInited++;

   /*
    * First, disable interrupts at the ICU.
    */

   OutByte (WYSE_MY_PTR_REG  , WYSE_ICU_CTRL_PTR);
   OutByte (WYSE_MY_DATA_REG , 0);

   /*
    * Stop counter/timer.
    */

   OutByte (WYSE_MY_PTR_REG  ,WYSE_ICU_CNT_REG_PTR);
   OutByte (WYSE_MY_DATA_REG , 0);

   /*
    * Set Base Vector Registers.
    */

   OutByte ( WYSE_MY_PTR_REG  ,WYSE_VBR0_PTR);
   OutByte ( WYSE_MY_DATA_REG ,PIC1_BASE);
   OutByte ( WYSE_MY_PTR_REG  ,WYSE_VBR1_PTR);
   OutByte ( WYSE_MY_DATA_REG ,PIC2_BASE);
   OutByte ( WYSE_MY_PTR_REG  ,WYSE_VBR2_PTR);
   OutByte ( WYSE_MY_DATA_REG ,PIC3_BASE);

   /*
    * Set the IPI interrupt level to 1.  This is on the third
    * set of interupts (16 thru 22).  We already installed a IPI handler
    * during Init().  Interrupt level 1 is the highest priority interrupt
    * that we can use.
    */

   OutByte ( WYSE_MY_PTR_REG  ,WYSE_ICU_LIPTR_PTR);
   OutWord ( WYSE_MY_DATA_REG ,WYSE_SLOT_INT_LVL);

   /*
    * We set the ICUs to auto eoi the IPI.  If we didn't auto eoi,
    * then we would have to use an ICU clear-in-service command
    * to clear the MSK_PND0 bit of the ICU status register.  Also,
    * since we auto-eoi, there is no point in exporting an _EndIPI()
    * function since there is no eoi required.  Also, _IPIPresnt()
    * will always return (NO_ERROR) since only IPI will use ICU
    * interrupt priority level of 1 (it must be an IPI).
    */

   /*
    * Setup mask registers and ICU Control register.
    */

   if (*CPUNumber)
   {
     OutByte (WYSE_MY_PTR_REG  ,WYSE_ICU_IMR0_PTR);
     OutWord (WYSE_MY_DATA_REG ,WYSE_ICU_SLAVE_MASK_1);

     OutByte (WYSE_MY_PTR_REG  ,WYSE_ICU_IMR1_PTR);
     OutWord (WYSE_MY_DATA_REG ,WYSE_ICU_SLAVE_MASK_2);

     OutByte (WYSE_MY_PTR_REG  ,WYSE_ICU_CTRL_PTR);
     OutByte (WYSE_MY_DATA_REG ,WYSE_ICTRL_IACK_MODE |
                                WYSE_ICTRL_ICU_AEOI |
                                WYSE_ICTRL_INT_ENB |
                                WYSE_ICTRL_MSK_CPURST);
   }
   else
   {
     OutByte (WYSE_MY_PTR_REG  ,WYSE_ICU_IMR0_PTR);
     OutWord (WYSE_MY_DATA_REG ,WYSE_ICU_MASTER_MASK_1);

     OutByte (WYSE_MY_PTR_REG  ,WYSE_ICU_IMR1_PTR);
     OutWord (WYSE_MY_DATA_REG ,WYSE_ICU_MASTER_MASK_2);

     OutByte (WYSE_MY_PTR_REG  ,WYSE_ICU_CTRL_PTR);
     OutByte (WYSE_MY_DATA_REG , WYSE_ICTRL_WWB_INT |
                                 WYSE_ICTRL_ICU_AEOI |
                                 WYSE_ICTRL_INT_ENB);
   }

   /*
    * Setup the CCU of each processor, to use trap 16 for a
    * floating point error.
    */

   OutByte (0xcf0, 0);
   ccu=InWord(0xcf2);
   ccu &= ~4;
   OutByte (0xcf0, 0);
   OutWord (0xcf2, ccu);

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

   return (NO_ERROR);
}

/***  GenIPI - Generate an inter-processor interrupt
 *
 *    This function generates an IPI.
 *
 *    ENTRY   procnum - processor number to interrupt (0-based)
 *
 *    EXIT    NO_ERROR
 *
 */

ulong_t GenIPI(ulong_t procnum) {

   /*
    * While the command register is busy...
    * Write Interrupt Slot command.
    * While the command register is busy...
    */

   while (Inword(WYSE_MY_PROC_CMD_REG) & 0x0100);
   OutWord (WYSE_MY_PROC_CMD_REG ,INT_SLOT_CMD(CPUINFO[procnum].Slot));
   while (Inword(WYSE_MY_PROC_CMD_REG) & 0x0100);

   return (NO_ERROR);
}


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

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

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

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

#if 0
/***  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) {

   return (NO_ERROR);
}
#endif
