/*
    Copyright (C) Corollary, Inc., 1993, 1994.
    All Rights Reserved.
    This Module contains Proprietary Information of
    Corollary, Inc., and should be treated as Confidential.
 */

/*
 * OPEN ISSUES
 * - add PSD hooks for NMI, reset - the PSD code is already done

 * - the localunit ID is incorrectly set to 0xff (should be 0x1) for virtual
 *   wire mode, but more than this needs to be fixed for virtual wire mode
 *   to work.  note for Cbus2, there is no virtualwire mode possible.

 * - remove #if 0 around the ADJ_PG_RANGES PSDHelp once the kernel is ready,
 *   the PSD code is already done

 * - rip out all GetMask and GetISR code once the kernel is ready
 */

#include <psd.h>

#include "cbustype.h"
#include "cbus.h"
#include "cbusrrd.h"
#include "cbus_sw.h"

/*--------------- function prototypes start here  ----------------------*/

P_F_2
CbusSpuriousHandler();

BOOL
CbusInitializePlatform();

ULONG
CbusResolveNMI();

VOID
CbusInitializeProcessor(
ULONG
);

PUCHAR
CbusFindString (
IN PUCHAR       Str,
IN PUCHAR       StartAddr,
IN LONG         Len
);

LONG
CbusStrLen(
IN PUCHAR       Str
);

void cbus2EOI(ULONG);

/*--------------- function prototypes end here  ----------------------*/

P_F_2                           CbusRouter = 0;

/*
 * this is needed because a few PSD entrypoints are called both before
 * and after we are allowed to switch from 8259 into APIC/CBC mode.
 */
BOOL                            Cbus8259Mode = TRUE;

/*
 * this can be set by the user by typing "MODE=8259" in config.sys
 */
BOOL                            CbusVirtualWire = FALSE;

char                            *CbusConfigSysString;

extern ULONG                    CbusProcessors;

extern ULONG                    CbusEisaVectors[];

PULONG                          CbusTaskPri[MAX_ELEMENT_CSRS];
PULONG                          CbusEOIReg[MAX_ELEMENT_CSRS];

CBUS_PRIVATE                    *CbusPerCPU;
ULONG                           CbusPerCPUsize;

ULONG                           CbusIrqPolarity;

extern EXT_ID_INFO_T            CbusExtIDTable[];

#define MAX_MEMORY_RANGES       32

struct adj_pg_ranges_s          CbusExtraMemory;
struct _pagerange_s             CbusMemory [MAX_MEMORY_RANGES];

#define BYTES_TO_PAGES(x)       (x >> 12)

#define SIXTEEN_MB               (16 * 1024 * 1024)



ULONG Irq0();
ULONG Irq1();
ULONG Irq2();
ULONG Irq3();
ULONG Irq4();
ULONG Irq5();
ULONG Irq6();
ULONG Irq7();
ULONG Irq8();
ULONG Irq9();
ULONG Irq10();
ULONG Irq11();
ULONG Irq12();
ULONG Irq13();
ULONG Irq14();
ULONG Irq15();

typedef ULONG (*P_F_0)();

P_F_0 IrqHandler[EISA_IRQLINES] = {Irq0,
                                   Irq1,
                                   Irq2,
                                   Irq3,
                                   Irq4,
                                   Irq5,
                                   Irq6,
                                   Irq7,
                                   Irq8,
                                   Irq9,
                                   Irq10,
                                   Irq11,
                                   Irq12,
                                   Irq13,
                                   Irq14,
                                   Irq15};


ULONG
Install(
INSTALL *p
)
{
        CbusRouter = p->pPSDHlpRouter;

        CbusConfigSysString = p->pParmString;

        CbusPerCPU = (CBUS_PRIVATE *)p->pPSDPLMA;
        CbusPerCPUsize = p->sizePLMA;

        if (CbusInitializePlatform() == FALSE)
                return API_FAILED;

        return NO_ERROR;
}

void
printf(
char *s, ...
)
{
        /*
         * no printf currently available for PSDs.
         */
}

ULONG
DeInstall(
)
{
        /*
         * release resources here, can't be called after init
         */
        return NO_ERROR;
}

ULONG
Init(
INIT    *p
)
{
        SET_IRQ         irq;
        if (CbusConfigSysString) {
          if (CbusFindString("MODE=8259", CbusConfigSysString,
                CbusStrLen(CbusConfigSysString)))
                        CbusVirtualWire = TRUE;
        }

        /*
         * set up a handler for spurious interrupts
         */
        irq.irq     = SPURIOUS_IRQ;
        irq.flags   = 0;
        irq.vector  = SPURIOUS_VECTOR;
        irq.handler = (P_F_2)CbusSpuriousHandler;

        if (PSDHelp(CbusRouter, PSDHLP_SET_IRQ, &irq) != NO_ERROR) {
                printf("cannot setirq for the spurious interrupt\n");
                return API_FAILED;
        }

        /*
         * all IPI vectors will be marked as directed, not lowest in group,
         * because they will be intended for a specific processor.
         * set up the IDT entry to push the IDT vector - note we don't need
         * a handler to distinguish whether the interrupt really was an IPI -
         * we know it must be because we give it a unique vector.
         */
        if (CbusProcessors > 1) {
                irq.irq     = IPI_IRQ;
                irq.flags   = IRQf_IPI;
                irq.vector  = IPI_VECTOR;
                irq.handler = (P_F_2)0;

                if (PSDHelp(CbusRouter, PSDHLP_SET_IRQ, &irq) != NO_ERROR) {
                        printf("cannot setirq for IPI interrupts\n");
                        return API_FAILED;
                }
        }

        /*
         * we cannot enable the global_irq_access flag for Cbus1 because
         * we have to turn redirection table entries on and off all the time
         * and only the boot CPU can do that for the Cbus1 APICs.  for
         * Cbus2, all processors can access any bridge's CBC.
         */

        if ((*CbusBackend->ReachIOPic)() == TRUE)
                p->flags = INIT_GLOBAL_IRQ_ACCESS | INIT_USE_FPERR_TRAP;
        else
                p->flags = INIT_USE_FPERR_TRAP;

        p->version = 1;

// JIM  PSDHelp(CbusRouter, PSDHLP_ADJ_PG_RANGES, &CbusExtraMemory);

// whh(022495) temp fix to switch from connected to posted IO writes

// #define tempfix 1
// V2.03 added tempo fix back
// #define tempfix 1

#ifdef tempfix
        {
          PLONG p;
          ULONG value;  // make it a DWORD write

//          _asm int 3;
//                                     broadcast +csr       +8000h, 1 dword
//          p = (PLONG)psd_map ((ULONG 0xFC000000+0x03c20000+0x8000, 4);
          p = (PLONG)psd_map ((ULONG)0xFFC28000, 4);
          if (p)
          {
            value = *p;
            value &= ~(0x00000080); // clearing bit 7 sets to posted writes
            *p = value;
            psd_unmap((caddr_t)p, 4);
          }
          else
          {
            return API_FAILED;
          }
        }
#endif
        return NO_ERROR;
}

ULONG
GetNumOfProcs(
)
{
   ulong_t AllowableCPUs;

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

   return AllowableCPUs;
}

/*
 * initialize the current processor, called by all processors.
 * the boot cpu runs this long before any other cpus have booted.
 */
ULONG
ProcInit()
{
        unsigned processor = CbusActiveProcessors;

        /*
         * initialize the local PLMA percpu area
         */
        ASSERT (CbusPerCPUsize >= sizeof (CBUS_PRIVATE));

        CbusPerCPU->processor = processor;

        CbusPerCPU->IrqCount = 0;
        CbusPerCPU->IrqStack[0] = -1;

        /*
         * initialize this processor's interrupt handling as well as any
         * other per-processor characteristics.
         */
        CbusInitializeProcessor(processor);

        CbusPerCPU->taskpri = CbusTaskPri[processor];
        CbusPerCPU->EOIReg = CbusEOIReg[processor];

        return NO_ERROR;
}

/*
 * this routine is called serially for all CPUs, but not
 * necessarily by the boot CPU in all cases.
 */
ULONG
StartProcessor(
ULONG processor
)
{
        ULONG   start_csip;             /* in cs:ip real-mode format */
        PLONG   Bios;

        /*
         * OS/2 has left us the real-mode cs:ip at physical address 0x467.
         * pull it out and give it to the backend hardware routines so they
         * know where to start the cpu off at.
         */
        Bios = (PLONG)psd_map(
                        (WARM_REBOOT_VECTOR_SEG << 4) | WARM_REBOOT_VECTOR_OFF,
                        4);

        if (!Bios) {
                printf("cannot map start address to boot processor %d\n",
                        processor);
                return API_FAILED;
        }

        start_csip = *Bios;

        psd_unmap((caddr_t)Bios, 4);

        /*
         * Boot the specified (zero-based, so always >= 1) CPU
         */
        (*CbusBackend->BootCPU)(processor, start_csip);

        return NO_ERROR;
}

ULONG
GenIPI(
ULONG processor
)
{
        /*
         * generate an IPI interrupt at the specified spl level
         * on the specified processor.  interrupts are already disabled
         * on the calling processor.
         */
        (*CbusBackend->RequestSoftwareInterrupt)(IPI_VECTOR, processor);

        return NO_ERROR;
}

/*
 * this is not taken care of by standard interrupt EOI (irqeoi is not
 * called for IPIs), so the actual EOI needs to be done here when we are
 * in APIC mode.
 */
ULONG
EndIPI(
ULONG processor
)
{
        PEXT_ID_INFO Table;

        ASSERT (processor == CbusPerCPU->processor);

        /*
         * need to EOI the IPI in APIC mode.
         */
        Table = &CbusExtIDTable[processor];
        if (Cbus8259Mode == FALSE && Table->pel_features & ELEMENT_HAS_APIC) {
                *CbusPerCPU->EOIReg = IPI_VECTOR;
        }
        return NO_ERROR;
}

/*
 * now have the backend enable the vector on the actual hardware.
 *
 * All device interrupts including clock interrupts should be enabled as
 * lowest-in-group with all booted CPUs participating, but this can't
 * be done until OS/2 supports fully distributed interrupts.
 *
 * Clocks are marked as LIG, with lock synchronization being done
 * within the clock handler code itself.  the clock code then does
 * the processing for all CPUs, rescheduling any other processors
 * as needed.
 *
 * A conceptual difference between the APIC and the CBC is that when
 * an I/O APIC or CBC first gets a LIG interrupt from a device:

 * The CBC broadcasts it on the bus, and each processor that
 * is a member of the receiving group will contend for the interrupt.
 * The lowest priority processor will be the one to take it.

 * The I/O APIC knows what the set of the receiving group looks
 * like, and broadcasts it only to them, where the lowest priority
 * processor will be the one to take it.

 * This difference (transparent in hardware) does influence software
 * setup and teardown of the interrupt which is why the Enable and
 * Disable routines are not common to both Cbus1 and Cbus2.
 */
int
IrqMask(
PSD_IRQ *p
)
{
        int     rv;
        ULONG   i;

        if (Cbus8259Mode == TRUE || CbusVirtualWire == TRUE)
                return API_FAILED;

        rv = NO_ERROR;

        _asm pushfd;
        _asm cli;

        switch (p->flags) {
                case    IRQ_MASK:
                                /*
                                 * a bit set to 1 means disable the line
                                 */
                                for (i = 0; i < EISA_IRQLINES; i++) {
                                        if (p->data & (1<<i))
                                                (*CbusBackend->DisableInterrupt)
                                                        (CbusEisaVectors[i], i);
                                }

                                break;

                case    IRQ_UNMASK:
                                /*
                                 * a bit set to 1 means enable the line
                                 */
                                for (i = 0; i < EISA_IRQLINES; i++) {
                                        if (p->data & (1<<i))
                                                (*CbusBackend->EnableInterrupt)
                                                        (CbusEisaVectors[i], i);
                                }

                                break;

                case    IRQ_GETMASK:
                                /*
                                 * a bit set to 1 means the line is disabled,
                                 * a bit set to 0 means the line is enabled
                                 */
                                p->data = (*CbusBackend->GetMasks)();
                                break;

                case    IRQ_NEWMASK:
                                /*
                                 * a bit set to 1 means disable the line,
                                 * a bit set to 0 means enable the line,
                                 */
                                for (i = 0; i < EISA_IRQLINES; i++) {
                                        if (p->data & (1<<i))
                                                (*CbusBackend->DisableInterrupt)
                                                        (CbusEisaVectors[i], i);
                                        else
                                                (*CbusBackend->EnableInterrupt)
                                                        (CbusEisaVectors[i], i);
                                }
                                break;

                default:
                                rv = API_FAILED;
                                break;
        }

        _asm popfd;

        return rv;
}

int
IrqReg(
PSD_IRQ *p
)
{
        if (Cbus8259Mode == TRUE || CbusVirtualWire == TRUE)
                return API_FAILED;

        switch (p->flags) {
                /*
                 * a bit set to 1 means the line has an interrupt pending
                 */
                case    IRQ_READ_IRR:
                                p->data = (*CbusBackend->GetIRR)();
                                break;
                case    IRQ_READ_ISR:
                                p->data = (*CbusBackend->GetISR)();
                                break;
                default:
                                return API_FAILED;
        }

        return NO_ERROR;
}

int
IrqEOI(
PSD_IRQ *p
)
{
        PEXT_ID_INFO Table;

        ASSERT (p->procnum == CbusPerCPU->processor);

        if (Cbus8259Mode == TRUE)
                return API_FAILED;

        Table = &CbusExtIDTable[p->procnum];
        if (Table->pel_features & ELEMENT_HAS_APIC) {
                *CbusPerCPU->EOIReg = p->data;
        } else {
                Cbus2EOI(p->data);
        }

        return NO_ERROR;
}

#ifdef DISTRIBUTED_INTERRUPTS
/*
 * even though the APIC uses only the high 4 bits for mask comparison during
 * interrupt acceptance, the APIC still uses all 8 bits to arbitrate for a given
 * lowest-in-group interrupt.  hence, ipl0 code will be running at
 * task priority 0xf, while idle code will run at task priority 0.
 * in this manner, idle processors will get the interrupt instead of
 * processors running user or kernel code at ipl 0.
 */

#define LOWEST_POSSIBLE_TASKPRI 0x0
#define IPL0_TASKPRI            0xF
#endif

void
SetProcState(
ulong_t state
)
{
        ULONG Processor = CbusPerCPU->processor;

#ifdef DISTRIBUTED_INTERRUPTS
        register PULONG         taskpri;

        /*
         * get the address of this processor's PIC task priority register
         */
        taskpri = CbusTaskPri[Processor];

        if (state == PROC_STATE_IDLE) {
           /*
            * and set this processor's task priority to below ipl 0.
            */
           *taskpri = LOWEST_POSSIBLE_TASKPRI;
        }
        else {
           /*
            * and set this processor's task priority back to ipl 0.
            */
           *taskpri = IPL0_TASKPRI;
        }
#endif

        if (state == PROC_STATE_IDLE) {
           /*
            * and turn off his LED
            */
           COUTB(CbusCSR[Processor].csr, CbusGlobal.smp_cled,
                           CbusGlobal.smp_cled_val);
        }
        else {
           /*
            * turn on his LED
            */
           COUTB(CbusCSR[Processor].csr, CbusGlobal.smp_sled,
                           CbusGlobal.smp_sled_val);
        }
}

/*
 * called to enable APIC or CBC mode - the OS/2 kernel has set up the boot
 * processor's 8259s - we enable the backend advanced interrupt controller,
 * copying (and translating) the 8259 current state onto the new interrupt
 * controller.
 *
 * note that no IPIs are available before this happens.
 */
ULONG
SetAdvIntMode()
{
        ULONG           IrqState;
        USHORT          i;
        ULONG           rv = NO_ERROR;
        SET_IRQ         irq;


        _asm pushfd;
        _asm cli;

        Cbus8259Mode = FALSE;

        /*
         * get the current 8259 state
         */
        IrqState = ( ((ULONG)inb(SIMR_PORT) << 8) | ((ULONG)inb(MIMR_PORT)) );

        if (CbusVirtualWire == FALSE)
          for (i = 0; i < EISA_IRQLINES; i++) {
                /*
                 * repoint the IDT stubs to use the new vectors for
                 * all the driver irqlines.
                 */
                irq.irq     = i;
                irq.flags   = 0;
                irq.vector  = CbusEisaVectors[i];

//              irq.handler = (P_F_2)0;

                /*
                 * This should be CBUS 2 specific BUGBUG
                 */

                if (i == 2)
                {
                  irq.handler = (P_F_2)0;
                }
                else
                {
                  irq.handler = (P_F_2) IrqHandler[i];
                }

                if (PSDHelp(CbusRouter, PSDHLP_SET_IRQ, &irq) != NO_ERROR) {
                        printf("cannot setirq for a driver interrupt\n");
                        rv = API_FAILED;
                }
          }

        /*
         * tell the backend...
         */
        (*CbusBackend->EnablePIC)(IrqState);

//      _asm int 3;

        _asm popfd;

        return rv;
}

#if 0
void
SetProcPri(
LONG priority
)
{

        if (!Cbus8259Mode)  // if not in 8259 mode (in advanced interrupt mode)
        {
          /*
           * set the processor's task priority register
           * this prevents spurious interrupts when level triggered
           * and have not EOIed yet.
           */

          if (priority == -1L)   // if default priority
          {
//           _asm int 3;
            *(CbusPerCPU->taskpri) = 0L;   // unmask all IRQs
          }
          else
          {
//            _asm int 3;
            *(CbusPerCPU->taskpri) = CbusEisaVectors[priority];
          }
        }

}
#endif

void
Shutdown()
{
        /*
         * we need this routine to be run from the boot processor because:
         *
         * a) Cbus1 additional processors can reset each other, but cannot
         *         reset the boot processor.
         *
         * b) When Cbus2 additional processors reset the 8042, it only resets
         *   the bridge processor because that's where the keyboard controller
         *   physically is.  To make this wart symmetric, we'd have to put
         *   special-cased 8042 logic in the general-purpose CBC.
         */
        ASSERT(CbusPerCPU->processor == 0);

        (*CbusBackend->ResetAllOtherProcessors)(CbusPerCPU->processor);
}




/*--------------- end of PSD function interfaces  ----------------------*/



/*
 * map a given physical address space into usable virtual space.
 * all physical mappings have PCD set by default in the PTE.
 */
ULONG
psd_map(
ULONG address,
ULONG size
)
{
        VMALLOC         vmac;

        vmac.addr = address;
        vmac.cbsize = size;
        vmac.flags = VMALLOC_PHYS;

        if (PSDHelp(CbusRouter, PSDHLP_VMALLOC, &vmac) == NO_ERROR) {

                /*
                 * and then turn on the page-bits and return the
                 * physical address
                 */
                return (vmac.addr |= (address & 0xFFF));
        }
        return (ULONG)0;
}

void
psd_unmap(
caddr_t address,
ULONG size
)
{
        UNREFERENCED_PARAMETER(size);

        PSDHelp(CbusRouter, PSDHLP_VMFREE, address);
}

ULONG
psd_vtop(
caddr_t         VirtualAddress
)
{
        ULONG         PhysicalAddress;

        /*
         * feed the help routine a page-aligned address...
         */
        PhysicalAddress = PSDHelp(CbusRouter, PSDHLP_VMLINTOPHYS,
                                (ULONG)VirtualAddress & ~0xFFF);

        ASSERT(PhysicalAddress != API_FAILED);

        /*
         * and then turn on the page-bits and return the physical address
         */
        return (PhysicalAddress |= ((ULONG)VirtualAddress & 0xFFF));
}

P_F_2
CbusSpuriousHandler()
{
        return NO_ERROR;
}

/*
 * hardly the most optimized routine in the world, but it isn't used
 * much anyway...
 */
VOID
bcopy(
PVOID   from,
PVOID   to,
ULONG   count
)
{
        ULONG   i;
        caddr_t src = (caddr_t)from;
        caddr_t dest = (caddr_t)to;

        for (i = 0; i < count; i++)
                *dest++ = *src++;
}

/*
 * Implement the handling of additional memory
 * ranges to be freed to the OS/2 kernel for the Corollary MP
 * architectures.
 *
 * Add the specified memory range to our list of memory ranges to
 * give to the OS/2 kernel later.  Called each time we find a memory
 * card during startup,
 * also called on Cbus1 systems when we add a jumpered range (between 8 and
 * 16MB).  ranges are jumpered via EISA config when the user has added a
 * dual-ported RAM card and wants to configure it into the middle of memory
 * (not including 640K-1MB, which is jumpered for free) somewhere.
 *
 * Arguments:
 *
 *  Address - Supplies a start physical address in bytes of memory to free
 *
 *  Size - Supplies a length in bytes spanned by this range
 */
VOID
CbusMemoryFree(
IN ULONG Address,
IN ULONG Size
)
{
        struct _pagerange_s     *p;
        struct _pagerange_s     *q;
        struct _pagerange_s     *endlist;
        ULONG                   firstpage;
        ULONG                   lastpage;

        /*
         * first call, so initialize the array pointer
         */
        if (CbusExtraMemory.nranges == 0)
                CbusExtraMemory.pprt = CbusMemory;

        /*
         * note that Cbus1 "hole" memory is always reclaimed at the
         * doubly-mapped address in high memory, never in low memory.
         */

        //
        // any memory below 16MB has already been reported by the BIOS,
        // so trim the request.  this is because requests can arrive in
        // the flavor of a memory board with 64MB (or more) on one board!
        //
        // note that Cbus1 "hole" memory is always reclaimed at the
        // doubly-mapped address in high memory, never in low memory.
        //

        if (Address + Size <= SIXTEEN_MB) {
                return;
        }

        if (Address < SIXTEEN_MB) {
                Size -= (SIXTEEN_MB - Address);
                Address = SIXTEEN_MB;
        }

        if (Size == 0)
                return;

        /*
         * add the card provided we have room left in our array,
         */
        if (CbusExtraMemory.nranges >= MAX_MEMORY_RANGES)
                return;         /* no more space */

        firstpage = BYTES_TO_PAGES(Address);
        lastpage = BYTES_TO_PAGES(Address + Size - 1);

        /*
         * insert this entry into the array - the array must be
         * sorted in numerically ascending addresses for the kernel.
         */

        p = CbusExtraMemory.pprt;
        endlist = CbusExtraMemory.pprt + CbusExtraMemory.nranges;

        while (p < endlist) {
                if (firstpage < p->firstframe) {
                        /*
                         * found the insertion point, shuffle all
                         * the remaining entries up one to make room.
                         */
                        for (q = endlist - 1; q >= p; q--) {
                                *(q+1) = *q;    /* copy up each entry */
                        }
                        break;
                }
                p++;
        }

        /*
         * p now points at the entry to fill in.  any needed shuffling has
         * been done.
         */

        CbusExtraMemory.nranges++;

        /*
         * fill in the first and last valid pages in the range - note that
         * we already converted from bytes to pages...
         */
        p->firstframe = firstpage;
        p->lastframe = lastpage;
}

VOID IrqCommon (ULONG Irq)
{
        _asm pushfd
        _asm cli
        *(CbusPerCPU->taskpri) = CbusEisaVectors[Irq];
        CbusPerCPU->IrqStack[++CbusPerCPU->IrqCount] = Irq;
        _asm popfd
}

ULONG Irq0()
{
        IrqCommon(0);
        return (-1);
}
ULONG Irq1()
{
        IrqCommon(1);
        return (-1);
}
ULONG Irq2()
{
        IrqCommon(2);
        return (-1);
}
ULONG Irq3 ()
{
        IrqCommon(3);
        return (-1);
}
ULONG Irq4 ()
{
        IrqCommon(4);
        return (-1);
}
ULONG Irq5 ()
{
        IrqCommon(5);
        return (-1);
}
ULONG Irq6 ()
{
        IrqCommon(6);
        return (-1);
}
ULONG Irq7 ()
{
        IrqCommon(7);
        return (-1);
}
ULONG Irq8 ()
{
        IrqCommon(8);
        return (-1);
}
ULONG Irq9 ()
{
        IrqCommon(9);
        return (-1);
}

ULONG Irq10 ()
{
        IrqCommon(10);
        return (-1);
}

ULONG Irq11 ()
{
        IrqCommon(11);
        return (-1);
}
ULONG Irq12 ()
{
        IrqCommon(12);
        return (-1);
}

ULONG Irq13 ()
{
        IrqCommon(13);
        return (-1);
}

ULONG Irq14 ()
{
        IrqCommon(14);
        return (-1);
}

ULONG Irq15 ()
{
        IrqCommon(15);
        return (-1);
}


