/*

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

Module Name:

    cbus1.c

Abstract:

    This module implements the initialization of the system dependent
    functions that define the Platform Specific Driver (PSD) for the
    Corollary Cbus1 MP machines under OS/2 2.x

Author:

    Landy Wang (landy@corollary.com) 11-Jan-1994
 */

#include <psd.h>

#include "cbustype.h"
#include "cbusrrd.h"            /* PSD <-> RRD interface definitions */
#include "cbus.h"               /* Cbus1 & Cbus2 max number of elements is here */
#include "cbus1.h"              /* Cbus1 hardware architecture stuff */
#include "cbus_sw.h"            /* Cbus OS/2-specific implementation stuff */

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

VOID
Cbus1Arbitrate(VOID);

VOID
ApicArbsync(VOID);

VOID
Cbus1ECCEnable(VOID);

VOID
Cbus1ECCDisable(VOID);

ULONG
READ_IOAPIC_ULONG(ULONG);

VOID
WRITE_IOAPIC_ULONG(ULONG, ULONG);

VOID
Cbus1InitializeLocalAPIC(
ULONG
);

VOID
Cbus1InitializeIOAPIC(
ULONG
);

VOID
CbusClearEISANMI(VOID);

PVOID
Cbus1FindDeadID( ULONG );

VOID
CbusDisable8259s( USHORT );

VOID
CbusMemoryFree(ULONG, ULONG);

VOID
CbusIOPresent(ULONG, ULONG, ULONG, ULONG, ULONG, PVOID);

VOID
Cbus1Boot1(
IN ULONG        Processor,
IN ULONG        Dest,
IN ULONG        Code,
IN ULONG        ResetAddress,
IN ULONG        ResetValue
);

extern ULONG    Cbus1Boot1End;

VOID
Cbus1Boot2(
IN ULONG        Processor,
IN ULONG        Dest,
IN ULONG        Code,
IN ULONG        ResetAddress,
IN ULONG        ResetValue
);

ULONG
psd_vtop(
IN caddr_t      VirtualAddress
);

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

extern ULONG                    CbusIrqPolarity;
extern UCHAR                    CbusVectorToIrq[];
extern PULONG                   CbusTaskPri[];
extern PULONG                   CbusEOIReg[];
extern RRD_CONFIGURATION_T      CbusJumpers;
extern ULONG                    CbusEisaVectors[];

ULONG                           Cbus1CachedMask;

/*
 * each I/O APIC is visible only to its attached processor.
 */

PIOAPIC_REGISTERS               Cbus1IOApic;
PAPIC_REGISTERS                 Cbus1LocalApic;
PUCHAR                          Cbus1ID0CSR;

/*
 * Used for holding IDs of disabled Cbus1 processors.  This is
 * done to detect whether the user has inserted any obsolete
 * boards, since they can still win arbitrations.  Thus, we can
 * detect disabled arbitration winners from just plain broken systems.
 */
PEXT_ID_INFO                    Cbus1DeadIDs[MAX_ELEMENT_CSRS];
ULONG                           Cbus1DeadIDsIndex;

PUCHAR                          Cbus1ResetVector;

#define APIC_WAIT() while (Cbus1LocalApic->ApicICR.ra.delivery_status)  \
                                        ;
/*
 *  defines for the Cbus1 ECC syndrome
 */
#define MULTIBIT        3
#define DOUBLEBIT       2
#define SINGLEBIT       1
#define NOECCERROR      0x7f

/*
 *  defines for the Cbus1 ECC error register
 */
typedef struct _extmear_t {
        ULONG offset:24;
        ULONG SyndromeHigh:5;
        ULONG SyndromeLow:2;
        ULONG simmtype:1;
} EXTMEAR_T, *PEXTMEAR;

UCHAR                           Cbus1EdacSyndrome[] = {

MULTIBIT,  /* 00 */ DOUBLEBIT, /* 01 */ DOUBLEBIT, /* 02 */ MULTIBIT,  /* 03 */
DOUBLEBIT, /* 04 */ MULTIBIT,  /* 05 */ MULTIBIT,  /* 06 */ DOUBLEBIT, /* 07 */
DOUBLEBIT, /* 08 */ MULTIBIT,  /* 09 */ SINGLEBIT, /* 0A */ DOUBLEBIT, /* 0B */
MULTIBIT,  /* 0C */ DOUBLEBIT, /* 0D */ DOUBLEBIT, /* 0E */ SINGLEBIT, /* 0F */
DOUBLEBIT, /* 10 */ MULTIBIT,  /* 11 */ SINGLEBIT, /* 12 */ DOUBLEBIT, /* 13 */
SINGLEBIT, /* 14 */ DOUBLEBIT, /* 15 */ DOUBLEBIT, /* 16 */ SINGLEBIT, /* 17 */
SINGLEBIT, /* 18 */ DOUBLEBIT, /* 19 */ DOUBLEBIT, /* 1A */ SINGLEBIT, /* 1B */
DOUBLEBIT, /* 1C */ SINGLEBIT, /* 1D */ MULTIBIT,  /* 1E */ DOUBLEBIT, /* 1F */
DOUBLEBIT, /* 20 */ MULTIBIT,  /* 21 */ SINGLEBIT, /* 22 */ DOUBLEBIT, /* 23 */
MULTIBIT,  /* 24 */ DOUBLEBIT, /* 25 */ DOUBLEBIT, /* 26 */ SINGLEBIT, /* 27 */
SINGLEBIT, /* 28 */ DOUBLEBIT, /* 29 */ DOUBLEBIT, /* 2A */ SINGLEBIT, /* 2B */
DOUBLEBIT, /* 2C */ SINGLEBIT, /* 2D */ MULTIBIT,  /* 2E */ DOUBLEBIT, /* 2F */
SINGLEBIT, /* 30 */ DOUBLEBIT, /* 31 */ DOUBLEBIT, /* 32 */ MULTIBIT,  /* 33 */
DOUBLEBIT, /* 34 */ SINGLEBIT, /* 35 */ MULTIBIT,  /* 36 */ DOUBLEBIT, /* 37 */
DOUBLEBIT, /* 38 */ MULTIBIT,  /* 39 */ MULTIBIT,  /* 3A */ DOUBLEBIT, /* 3B */
MULTIBIT,  /* 3C */ DOUBLEBIT, /* 3D */ DOUBLEBIT, /* 3E */ SINGLEBIT, /* 3F */
DOUBLEBIT, /* 40 */ MULTIBIT,  /* 41 */ MULTIBIT,  /* 42 */ DOUBLEBIT, /* 43 */
MULTIBIT,  /* 44 */ DOUBLEBIT, /* 45 */ DOUBLEBIT, /* 46 */ MULTIBIT,  /* 47 */
MULTIBIT,  /* 48 */ DOUBLEBIT, /* 49 */ DOUBLEBIT, /* 4A */ SINGLEBIT, /* 4B */
DOUBLEBIT, /* 4C */ MULTIBIT,  /* 4D */ SINGLEBIT, /* 4E */ DOUBLEBIT, /* 4F */
MULTIBIT,  /* 50 */ DOUBLEBIT, /* 51 */ DOUBLEBIT, /* 52 */ SINGLEBIT, /* 53 */
DOUBLEBIT, /* 54 */ SINGLEBIT, /* 55 */ SINGLEBIT, /* 56 */ DOUBLEBIT, /* 57 */
DOUBLEBIT, /* 58 */ SINGLEBIT, /* 59 */ SINGLEBIT, /* 5A */ DOUBLEBIT, /* 5B */
SINGLEBIT, /* 5C */ DOUBLEBIT, /* 5D */ DOUBLEBIT, /* 5E */ SINGLEBIT, /* 5F */
MULTIBIT,  /* 60 */ DOUBLEBIT, /* 61 */ DOUBLEBIT, /* 62 */ SINGLEBIT, /* 63 */
DOUBLEBIT, /* 64 */ SINGLEBIT, /* 65 */ SINGLEBIT, /* 66 */ DOUBLEBIT, /* 67 */
DOUBLEBIT, /* 68 */ SINGLEBIT, /* 69 */ SINGLEBIT, /* 6A */ DOUBLEBIT, /* 6B */
SINGLEBIT, /* 6C */ DOUBLEBIT, /* 6D */ DOUBLEBIT, /* 6E */ SINGLEBIT, /* 6F */
DOUBLEBIT, /* 70 */ SINGLEBIT, /* 71 */ MULTIBIT,  /* 72 */ DOUBLEBIT, /* 73 */
SINGLEBIT, /* 74 */ MULTIBIT,  /* 75 */ DOUBLEBIT, /* 76 */ SINGLEBIT, /* 77 */
MULTIBIT,  /* 78 */ DOUBLEBIT, /* 79 */ DOUBLEBIT, /* 7A */ SINGLEBIT, /* 7B */
DOUBLEBIT, /* 7C */ SINGLEBIT, /* 7D */ SINGLEBIT, /* 7E */ NOECCERROR,/* 7F */

};

/*

Routine Description:

    Remove reset from the specified processor, allowing him to boot,
    beginning execution at the specified start address.  This ends up
    being tricky due to cache flushing concepts generally hidden in ROMs,
    but in the PSD for the Cbus1 platform.

Arguments:

    Processor - Supplies a logical processor number to boot

    StartAddress - Supplies a start address containing real-mode code
                   for the processor to execute.

Return Value:

    None.

 */
VOID
Cbus1BootCPU(
IN ULONG Processor,
IN ULONG RealModeSegOff
)
{
        ULONG                   BeginAddress, EndAddress;
        ULONG                   BeginCacheLineIndex;
        ULONG                   EndCacheLineIndex;
        UCHAR                   StartVectorCode[5];
        PVOID                   PhysicalResetVector;
        ULONG                   ip = RealModeSegOff & 0xFFFF;
        ULONG                   cs = ((RealModeSegOff >> 16) & 0xFFFF);
        ULONG                   CacheFlush = CBUS1_CACHE_SIZE;
        ULONG                   LastCacheLineIndex;

        /*
         * if we haven't yet mapped in the reset vector, do so now.  don't
         * add in the page offset since psd_map() does this for us.
         */
        if (!Cbus1ResetVector) {

                PhysicalResetVector =
                        (PVOID)(CbusGlobal.resetvec - CBUS1_CACHE_LINE);

                Cbus1ResetVector = (PUCHAR) psd_map(
                        (ULONG)PhysicalResetVector, 16);

                if (!Cbus1ResetVector) {
                        printf("PSD: could not map the reset vector to boot a CPU\n");
                        return;
                }
        }

        /*
         * build start vector (entry offset followed by cs load base)
         */
        StartVectorCode[0] = (UCHAR)0xea;
        StartVectorCode[1] = (UCHAR)(ip & 0xff);
        StartVectorCode[2] = (UCHAR)((ip >> 8) & 0xff);
        StartVectorCode[3] = (UCHAR)(cs & 0xff);
        StartVectorCode[4] = (UCHAR)((cs >> 8) & 0xff);

        /*
         * determine which low-level boot function to use.  although
         * the 2 functions are exactly the same, we must make sure that
         * we don't call one which would cause the cache line containing
         * the reset vector to be evicted prematurely.
         */

        LastCacheLineIndex = ((CacheFlush - 1) >> CBUS1_CACHE_SHIFT);

        BeginAddress = psd_vtop((caddr_t)Cbus1Boot1);
        BeginCacheLineIndex =
                (BeginAddress & (CacheFlush - 1)) >> CBUS1_CACHE_SHIFT;

        EndAddress = psd_vtop((caddr_t)Cbus1Boot1End);
        EndCacheLineIndex =
                (EndAddress & (CacheFlush - 1)) >> CBUS1_CACHE_SHIFT;

        /*
         * in order to startup additional CPUs, we need to write its
         * startup vector in the last 16 bytes of the memory
         * address space.  this memory may or may not be
         * present.  more specifically, a partially populated
         * memory card which doesn't have memory at the end of
         * the space, ie: a memory card at 48Mb->56Mb in a
         * system with a 64Mb upper limit) will cause an ecc
         * error if ecc is enabled.
         *
         * thus we must disable ecc to prevent such boards from
         * generating ecc errors.  note that the errors are only
         * generated on the read when we initially fill the
         * startvector.  it's ok to enable ecc after startup
         * even though the cacheline containing the StartVectorCode
         * has not necessarily been flushed.  when it is flushed,
         * it will go in the bit bucket and ecc errors are
         * generated only on reads (not writes).
         */

        Cbus1ECCDisable();

        /*
         * call Cbus1Boot1 if there is no wrap...
         * see the detailed comment at the top of Cbus1Boot1 for more details.
         */
        if ((EndCacheLineIndex != LastCacheLineIndex) && EndAddress > BeginAddress) {
                Cbus1Boot1(Processor,
                           (ULONG) Cbus1ResetVector,
                           (ULONG) StartVectorCode,
                           (ULONG)CbusCSR[Processor].csr +
                                CbusGlobal.smp_creset,
                           CbusGlobal.smp_creset_val);
        }
        else {
                Cbus1Boot2(Processor,
                           (ULONG) Cbus1ResetVector,
                           (ULONG) StartVectorCode,
                           (ULONG)CbusCSR[Processor].csr +
                                CbusGlobal.smp_creset,
                           CbusGlobal.smp_creset_val);
        }

        Cbus1ECCEnable();
}

/*
 * if there are any Cbus1 additional processors present,
 * put them into reset and arbitrate them so they get
 * branded with proper bus arbitration IDs.
 */
VOID
Cbus1InitializePlatform()
{
        if (CbusProcessors > 1)
                Cbus1Arbitrate();
}

/*
 *   Each processor assigns an APIC ID to his I/O APIC so
 *   it can arbitrate for the APIC bus, etc.  Intel documentation
 *   says that every local and I/O APIC must have a unique id.
 *
 *   Arguments:
 *
 *   Processor - Supplies a logical processor number
 */
VOID
Cbus1BrandIOUnitID(
IN ULONG Processor
)
{
        WRITE_IOAPIC_ULONG(IO_APIC_ID_OFFSET, (2 * Processor)<<APIC_BIT_TO_ID);

        ApicArbsync();
}


/*
 *    Initialize this processor's local and I/O APIC units.
 *
 *    Arguments:
 *
 *    Processor - Supplies a logical processor number
 */
VOID
Cbus1InitializeCPU(
IN ULONG Processor
)
{
        /*
         * If the APIC mapping has not been set up yet,
         * do it now.
         *
         * We map in the APIC into global space.  All processors will now
         * be able to see it at the same _physical_ and virtual address.
         * Note the page is mapped PWT.
         */
        if (!Cbus1LocalApic) {
                Cbus1LocalApic = (PAPIC_REGISTERS) psd_map(
                                (ULONG)LOCAL_APIC_LOCATION,
                                LOCAL_APIC_SIZE);

                if (!Cbus1LocalApic)
                        printf("PSD: failed to map the APIC\n");
        }

        /*
         * Provide portability in the event that the APIC might move.
         */

#if (LOCAL_APIC_LOCATION == IO_APIC_LOCATION)
        Cbus1IOApic = (PIOAPIC_REGISTERS)Cbus1LocalApic;
#else
        Cbus1IOApic = (PIOAPIC_REGISTERS) psd_map(
                        (ULONG)IO_APIC_LOCATION,
                                IO_APIC_SIZE);

        if (!Cbus1IOApic)
                printf("PSD: failed to map the APIC\n");
#endif

        CbusTaskPri[Processor] = (PULONG)&Cbus1LocalApic->ApicTaskPriority;
        CbusEOIReg[Processor] = (PULONG)&Cbus1LocalApic->ApicEOI;

        /*
         * the boot processor is the only one who can reach his I/O
         * APIC - this is the APIC which controls all the EISA interrupts.
         * since he can't enable APIC mode until the OS/2 kernel tells us
         * it's safe, postpone his I/O APIC initialization till later.
         */
         if (Processor) {
                Cbus1InitializeLocalAPIC(Processor);
                Cbus1InitializeIOAPIC(Processor);
         }
}

VOID
Cbus1ChangeRedirectionEntry(
ULONG                   RedirectionAddress,
REDIRECTION_T           *RedirectionEntry
)
{
        if (CbusVirtualWire == TRUE) {
                /*
                 *
                 */
                RedirectionEntry->ra.Mask = 0;         /* don't mask the intr */
                RedirectionEntry->ra.Delivery_mode = 7;  /* ext int */
                RedirectionEntry->ra.Dest_mode = ~APIC_LOGICAL_MODE;
        }
        /*
         * all interrupts must be disabled.  note
         * that all processors can receive distributed interrupts from
         * the I/O APIC, and all processors can access their own local APICs.
         */
        _asm pushfd;
        _asm cli;

        /*
         * we must be the boot processor, just poke his I/O APIC and we're done.
         */
        ASSERT(CbusPerCPU->processor == 0);

        WRITE_IOAPIC_ULONG(RedirectionAddress + 1,
                        RedirectionEntry->ra.Destination);

        WRITE_IOAPIC_ULONG(RedirectionAddress,
                        RedirectionEntry->rb.dword1);

        _asm popfd;
}

/*
 * Do not detect collisions here because interrupts are allowed to be
 * shared at a higher level - the OS/2 kernel will take care of
 * the sharing.  Just make sure that for any given irqline, only one
 * vector is generated, regardless of how many drivers may try to share
 * the line.
 *

    Determine if the supplied vector belongs to an EISA device - if so,
    then the corresponding I/O APIC entry will need to be modified to
    enable the line.  Otherwise, no work is needed, so just return TRUE
    immediately.  For a software interrupt, the irqline must be equivalent
    to the vector.

Arguments:

    Vector - Supplies a vector number to enable

Return Value:

    TRUE if the vector was enabled, FALSE if not.

 */
BOOL
Cbus1EnableInterrupt(
IN ULONG        Vector,
IN ULONG        irqline
)
{
        REDIRECTION_T           RedirectionEntry = { 0 };
        ULONG                   RedirectionAddress;
        BOOL                    EdgeTriggered;

        /*
         * OK, a real hardware interrupt, action is needed.
         * remember only the boot processor can add/remove processors from
         * the I/O APIC's redirection entries.  thus, this operation must
         * be done on the boot processor.  additional processors will context
         * switch to the boot processor here.
         * set up the APIC's redirection entry for this irqline.
         */
        if ((CbusIrqPolarity & (1 << irqline)) == EDGE_TRIGGERED)
                EdgeTriggered = TRUE;
        else
                EdgeTriggered = FALSE;

        /*
         * calculate the I/O APIC's redirection table offset so we
         * can enable the interrupt below.
         */
        RedirectionAddress = (ULONG)(IO_APIC_REDIRLO + 2 * irqline);

        /*
         * Let the I/O APIC know that the calling processor wishes to
         * participate in receipt of the interrupt.  This must be done
         * regardless of whether or not any other processors have already
         * enabled the interrupt.
         */

        RedirectionEntry.ra.Vector = Vector;

        /*
         * Mark the caller's interrupt as level or edge triggered,
         * based on the ELCR register we read earlier.
         *
         * the default is to program the interrupt as level triggered.
         * so only set the bit if it's an edge triggered interrupt.
         */
        if (EdgeTriggered == TRUE)
                RedirectionEntry.ra.Trigger = APIC_EDGE;
        else
                RedirectionEntry.ra.Trigger = APIC_LEVEL;

        RedirectionEntry.ra.Mask = APIC_INTR_UNMASKED;
        RedirectionEntry.ra.Dest_mode = APIC_LOGICAL_MODE;
        RedirectionEntry.ra.Delivery_mode = APIC_INTR_FIXED;

#ifdef DISTRIBUTED_INTERRUPTS

        /*
         * all active processors will participate
         */
        RedirectionEntry.ra.Destination =
                (CbusActiveProcessorsMask << APIC_BIT_TO_ID);

        /*
         * Only enable APIC LIG arbitration delay (at least 4 cycles on
         * our 10Mhz APIC bus, which is 0.4 microseconds) if there is
         * more than one processor in the machine.
         */
        if (CbusProcessors > 1)
                RedirectionEntry.ra.Delivery_mode = APIC_INTR_LIG;
#else
        RedirectionEntry.ra.Destination = (1 << APIC_BIT_TO_ID);
#endif

        /*
         * Add the mask of all processors to the
         * I/O APIC in order to be considered for receipt of
         * the interrupt.  Remember, each processor can only
         * access his own I/O APIC.
         */

        Cbus1ChangeRedirectionEntry(RedirectionAddress, &RedirectionEntry);

        /*
         * clear this bit in our cached mask because the line is now enabled.
         */
        Cbus1CachedMask &= ~(1 << irqline);

        return TRUE;
}

/*
 *    Disable the specified interrupt so it can not occur on the calling
 *    processor upon return from this routine.  Remember only the boot processor
 *    can add/remove processors from his I/O APIC's redirection entries.
 *
 *    This operation is trivial for the boot processor.  However, additional
 *    processors must context switch to the boot processor to complete this
 *    request.
 */
VOID
Cbus1DisableInterrupt(
IN ULONG        Vector,
IN ULONG        irqline
)
{
        REDIRECTION_T           RedirectionEntry = { 0 };
        ULONG                   RedirectionAddress;

        /*
         * If this is a pure software interrupt (ie:
         * APIC timer or IPI), no action is needed.
         * however, we are guaranteed to be called only for real hardware
         * interrupts.
         */

        /*
         * calculate the I/O APIC's redirection table offset so we
         * can disable the interrupt below.
         */
        RedirectionAddress = (ULONG)(IO_APIC_REDIRLO + 2 * irqline);

        /*
         * explicitly remove all processors from the interrupt
         * participation list.
         */

        RedirectionEntry.ra.Destination = 0;
        RedirectionEntry.ra.Mask |= APIC_INTR_MASKED;

        /*
         * Let the I/O APIC know that the processors are no longer
         * participating in the receipt of this interrupt.
         */

        Cbus1ChangeRedirectionEntry(RedirectionAddress, &RedirectionEntry);

        /*
         * set this bit in our cached mask because the line is now disabled.
         */
        Cbus1CachedMask |= (1 << irqline);
}


#ifdef DEBUG
#define NMI_BUTTON_PRESSED()    1
#else
#define NMI_BUTTON_PRESSED()    0
#endif

/*

Routine Description:

    This function determines the cause of the NMI so that the user can
    replace any offending SIMMs.

Arguments:

    NmiInfo - pointer to the NMI information structure

Return Value:

        - NMI return values to continue the chain
                DDI_INTR_CLAIMED/DDI_INTR_UNCLAIMED

 */
ULONG
Cbus1ResolveNMI()
{
        ULONG                   Board;
        PMEMORY_CARD            pm;
        ULONG                   Syndrome;
        PEXTMEAR                Mear;
        ULONG                   NMIAddress;

        if (NMI_BUTTON_PRESSED()) {

                /*
                 * NMI button was pressed, so go to the debugger
                 */
                _asm    int 3;

                /*
                 * Clear the NMI in hardware so the system can continue
                 */

                CbusClearEISANMI();

                return NO_ERROR;          /* ignore this NMI */
        }

        if (CbusGlobal.nonstdecc)
                return API_FAILED;

        pm = CbusMemoryBoards;

        for (Board = 0; Board < CbusMemoryBoardIndex; Board++, pm++) {

                if (pm->io_attr == 0)
                        continue;

                /*
                 * the syndrome/page routines below are for
                 * the Corollary Cbus1 smp/XM architecture.
                 * this architecture currently supports 256Mb of RAM.
                 *
                 * this method is the defacto standard for Corollary
                 * Cbus1 licensees with > 64Mb.
                 */

                Mear = (PEXTMEAR)pm->regmap;

                /*
                 * check whether this memory board generated the ecc error
                 * double check all the byte vs. clicks conversions and
                 * also the baseram (for jumpered megabytes)
                 */

                Syndrome =
                  Cbus1EdacSyndrome[(Mear->SyndromeHigh<<2)|Mear->SyndromeLow];

                if (Syndrome == NOECCERROR || Syndrome == SINGLEBIT)
                        continue;

                /*
                 * Cbus1 will always have less than 4Gig of physical memory,
                 * so just calculate LowPart here.  no need to add in
                 * CbusGlobal.baseram, because that will always be zero.
                 *
                 * the Mear->offset will always be zero based, regardless of
                 * whether the memory was reclaimed above 256MB or not.
                 */

                NMIAddress = (Mear->offset << 4);

                /*
                 * Disable ecc so the system can be safely taken down
                 * without getting another double-bit error (because the
                 * kernel may iret later).
                 */

                Cbus1ECCDisable();

                printf("NMI: double-bit ecc error address=0x%x\n", NMIAddress);

                return NO_ERROR;
        }

        /*
         * No errors found in Cbus RAM, caller should check for EISA errors
         */

        return API_FAILED;
}

/*

Routine Description:

    Called to put all the other processors in reset prior to reboot.
    Only the Cbus1 boot processor can reset all the others with a
    global broadcast.  Although each additional processor has the
    capability to reset any other additional processor, no additional
    processor can reset the boot processor.  So the calling processor
    sends an interrupt to the boot processor to have him take care of
    it all.

    The boot processor must return to poke the 8042 into oblivion.

Arguments:

    Processor - Supplies the caller's logical processor number
 */
VOID
Cbus1ResetAllOtherProcessors(
IN ULONG  Processor
)
{
        PUCHAR          BiosProcessorCSR;

        ASSERT(Processor == 0);

        /*
         * issue a global reset to the broadcast address
         */

        *(PULONG)((PUCHAR)CbusBroadcastCSR + CbusGlobal.smp_sreset) =
                CbusGlobal.smp_sreset_val;

        /*
         * Disable BIOS shadowing until RRD recopies the BIOS ROM
         * into shadow RAM again.  This is because we "reclaim" the
         * BIOS hole up at 256MB + 640K, to use it as general purpose
         * (shadowed) RAM for OS/2.  Referring to the ROM at 640K
         * (NOT 256MB + 640K) you will see a pristine BIOS copy, but
         * accessing it at the high address is like using any other
         * area of general-purpose DRAM.
         *
         * thus, during OS/2, the shadowed copy will be overwritten
         * with random data as the pages are used, and we must warn
         * the BIOS to recopy the ROM into the shadowed RAM on bootup.
         * Here's the magic to instruct the BIOS accordingly:
         *
         * Note that once RRD regains control, he will again
         * shadow the BIOS as long as the EISA Config CMOS bits
         * instruct him to.  The shadowing is only being disabled
         * here because the BIOS will initially get control _before_
         * the Corollary RRD ROM does.
         *
         * if this code wasn't here, the user would have to cycle
         * power to boot up successfully.
         */

        BiosProcessorCSR = (PUCHAR) CbusCSR[Processor].csr;

        *(BiosProcessorCSR + CBUS1_SHADOW_REGISTER) = DISABLE_BIOS_SHADOWING;
}

/*
 *
 *
 *              most internal Cbus1 support routines begin here.
 *
 *              the exception is the internal routines needed for booting,
 *              which must be adjacent to the externally-visible boot
 *              routine above.  see the above comments for more details.
 *
 *
 *
 */

/*
 *    Enable Cbus1 ECC detection as ordered by the RRD.
 */
VOID
Cbus1ECCEnable(VOID)
{
        ULONG           Index;
        PMEMORY_CARD    pm;

        if (CbusGlobal.nonstdecc)
                return;

        pm = CbusMemoryBoards;

        for (Index = 0; Index < CbusMemoryBoardIndex; Index++, pm++) {

                if (pm->io_attr)
                        COUTB(pm->regmap, 0, pm->io_attr);

        }
}

VOID
Cbus1ECCDisable(VOID)
{
        ULONG           Index;
        PMEMORY_CARD    pm;

        if (CbusGlobal.nonstdecc)
                return;

        pm = CbusMemoryBoards;

        for (Index = 0; Index < CbusMemoryBoardIndex; Index++, pm++) {

                if (pm->io_attr)
                        COUTB(pm->regmap, 0, pm->io_attr & ~CBUS1_EDAC_EN);
        }
}

/*

Routine Description:

    Search for the given arbitration ID in the
    global extended ID table, then return its CSR.

Arguments:

    ArbID - Supplies an arbitration ID (that just won the contention cycle)

Return Value:

    CSR of the specified arbitration ID, 0 if none found

 */
PUCHAR
CbusIDtoCSR(
IN ULONG ArbID
)
{
        ULONG           Index;
        PEXT_ID_INFO    Idp;

        for ( Index = 0; Index < CbusProcessors; Index++) {

                Idp = (PEXT_ID_INFO)CbusCSR[Index].idp;

                if (Idp->id == ArbID) {

                        return (PUCHAR)CbusCSR[Index].csr;

                }
        }

        return (PUCHAR)0;
}


/*

Routine Description:

    Called by the boot processor to arbitrate the other processors
    out of reset, and brand them with IDs.

 */
VOID
Cbus1Arbitrate()
{
        ULONG           Index, ArbID;
        PUCHAR          csr, ArbCSR;
        ULONG           AdditionalProcessors = 0;

        /*
         * issue a global broadcast to put all additional processors into reset.
         */

        csr = (PUCHAR)CbusBroadcastCSR + CbusGlobal.smp_sreset;
        *((PULONG)csr) = CbusGlobal.smp_sreset_val;

        /*
         * access ID 0's C-bus I/O space - this is different from the boot CPU.
         */
        for (Index = 0; Index < CbusGlobal.broadcast_id; Index++) {

                COUTB(Cbus1ID0CSR, CbusGlobal.smp_contend,
                        CbusGlobal.smp_contend_val);

                COUTB(CbusBroadcastCSR, CbusGlobal.smp_contend,
                        CbusGlobal.smp_contend_val);


                ArbID = inb(ATB_STATREG) & BS_ARBVALUE;

                ArbCSR = CbusIDtoCSR(ArbID);

                if (ArbCSR) {
                        COUTB(ArbCSR, CbusGlobal.smp_setida,
                                CbusGlobal.smp_setida_val);

                        if (ArbID >= LOWCPUID && ArbID <= HICPUID)
                                AdditionalProcessors++;
                }
                else {
                        if (!Cbus1FindDeadID(ArbID)) {
                                printf("FATAL CbusIDtoCSR failure\n");
                        }
                }

                ArbID = inb(ATB_STATREG) & BS_ARBVALUE;

                if (ArbID == 0) {
                        ASSERT(AdditionalProcessors == CbusProcessors - 1);
                        return;
                }
        };

        printf("Cbus1 CPU arbitration failed");
}


/*

Routine Description:

    Note that all interrupts are blocked on entry since
    this routine is called from InitializeProcessor().
    Initialize this processor's local and I/O APIC units.

Arguments:

    Processor - Supplies a logical processor number

Return Value:

    None.

 */
VOID
Cbus1InitializeIOAPIC(
IN ULONG Processor
)
{
        Cbus1BrandIOUnitID(Processor);

        if (Processor || CbusVirtualWire == TRUE)
                return;

        /*
         * Disable all 8259 inputs except the irq0 clock.
         * remember the irq0 clock and the irq13 DMA
         * chaining interrupts are internal to the Intel EISA
         * chipset (specifically, the ISP chip), and if the PSD
         * wants to enable them, it must be done here.
         * This is done by enabling the 8259 ISP to send them
         * to the processor(s) via the APIC.

         * Irq0 is used by the PSD for clock interrupts.  local APIC
         * timers are not used because they cannot be distributed
         * lowest-in-group.
         *
         * Note that all other EISA bus device interrupts only need to
         * be enabled in the APIC for processors to see them.
         */
        CbusDisable8259s((USHORT)0xFFFE);

        /*
         * All redirection table entries are disabled by default when the
         * processor emerges from reset.  Later, each entry is individually
         * enabled from their respective drivers via EnableInterrupt().
         *
         * Indicate the APIC (not the 8259s) will now handle provide
         * the interrupt vectors to the processor during an INTA cycle.
         * This is done by writing to the APMode port.  Note that at this
         * time we will also sync the APIC polarity control registers with
         * the ELCR.  Since irq0 has no polarity control, the hardware
         * uses bit0 for the APMode enable, so make sure this bit is on too.
         */

        Cbus1LocalApic->APMode = (UCHAR)((CbusIrqPolarity & 0xFF) | 0x1);
        Cbus1LocalApic->PolarityPortHigh =
                (UCHAR)((CbusIrqPolarity >> 8) & 0xFF);
}

/*

    Called by each processor to initialize his local APIC.
    The first processor to run this routine will map the
    local APICs for all processors.

    All interrupts are blocked on entry.

Arguments:

    Processor - Supplies a logical processor number
 */
VOID
Cbus1InitializeLocalAPIC(
IN ULONG Processor
)
{
        ULONG           ProcessorBit;
        ULONG           ApicIDBit;
        REDIRECTION_T   RedirectionEntry = { 0 };

        if (CbusVirtualWire == TRUE) {
                Cbus1LocalApic->LocalUnitID =
                        ((2 * Processor + 1) << APIC_BIT_TO_ID);

                /*
                 * set up the destination format register so
                 * that the addressing is disabled since we won't
                 * be using the APIC except for IPIs.
                 */
                Cbus1LocalApic->ApicDestinationFormat = ~APIC_ALL_PROCESSORS;

                /*
                 * designate the spurious interrupt vector we want to see,
                 * and inform this processor's APIC to enable interrupt
                 * acceptance.
                 */
                Cbus1LocalApic->ApicSpuriousVector =
                                        SPURIOUS_VECTOR|LOCAL_APIC_ENABLE;

                ApicArbsync();
                return;
        }

        /*
         * Here we initialize our destination format and
         * logical destination registers so that we can get IPIs
         * from other processors.
         *
         * Specify full decode mode in the destination format register -
         * ie: each processor sets only his own bit, and a "match" requires
         * that at least one bit match.  The alternative is encoded mode,
         * in which _ALL_ encoded bits must match the sender's target for
         * this processor to see the sent IPI.
         */
        Cbus1LocalApic->ApicDestinationFormat = APIC_ALL_PROCESSORS;

        /*
         * the logical destination register is what the redirection destination
         * entry compares against.  only the high 8 bits will be supported
         * in Intel's future APICs, although this isn't documented anywhere!
         */
        ProcessorBit = (1 << Processor);

        ApicIDBit = (ProcessorBit << APIC_BIT_TO_ID);

        Cbus1LocalApic->ApicLogicalDestination = ApicIDBit;

        /*
         * designate the spurious interrupt vector we want to see,
         * and inform this processor's APIC to enable interrupt
         * acceptance.
         */
        Cbus1LocalApic->ApicSpuriousVector =
                                SPURIOUS_VECTOR|LOCAL_APIC_ENABLE;

        /*
         * as each processor comes online here, we must have ALL
         * processors resync their arbitration IDs to take into
         * account the new processor.  note that we will set:
         * arb id == APIC id == processor number.
         *
         * the strange ID setting is to satisfy Intel's need for
         * uniqueness amongst I/O and local unit ID numbering.
         */

        Cbus1LocalApic->LocalUnitID = ((2 * Processor + 1) << APIC_BIT_TO_ID);

        /*
         * sync up our new ID with everyone else
         */

        ApicArbsync();

        /*
         * Create the NMI routing linkage for this processor
         * It is set as edge sensitive, enabled and generating NMI trap 2.
         */

        RedirectionEntry.ra.Trigger = APIC_EDGE;
        RedirectionEntry.ra.Mask = APIC_INTR_UNMASKED;
        RedirectionEntry.ra.Delivery_mode = APIC_INTR_NMI;
        Cbus1LocalApic->ApicLocalInt1 = RedirectionEntry;
}

/*

Routine Description:

    "Recover" any low memory which has been repointed at the EISA bus
    via EISA config.  This typically happens when ISA/EISA cards with
    dual-ported memory are in the machine, and memory accesses need
    to be pointed at the card, as opposed to C-bus general purpose memory.
    The Corollary architecture provides a way to still gain use of the
    general purpose memory, as well as be able to use the I/O dual-port
    memory, by double mapping the C-bus memory at a high memory address.

    note that memory accesses in the 640K-1MB hole are by default, pointed
    at the EISA bus, and don't need to be repointed via EISA config.

    note this is where jumper decoding and memory_holes set up
    happens, but the actual memory is not released here.

 */
VOID
Cbus1HandleJumpers()
{
        ULONG                           i, j;
        ULONG                           DoublyMapped;
        ULONG                           Address;

        if (CbusGlobal.useholes == 0)
                return;

        /*
         * if the base of RAM is _zero_ (ie: XM or later), then we
         * will recover the holes up above the "top of RAM", so any
         * I/O device dual-port RAM at the low address will continue to work.
         *
         * if the base of RAM is NON-ZERO, then we are on an older
         * Corollary system which is not supported by this PSD - the
         * standard OS/2 uniprocessor PSD should be used instead.
         */

        if (CbusGlobal.baseram != 0)
                return;

        DoublyMapped = CbusGlobal.memory_ceiling;

        /*
         * reclaim the 640K->1MB hole first
         */
//      CbusMemoryFree(DoublyMapped + 640 * 1024, 384 * 1024);

        /*
         * see if this memory span has been dipswitch
         * disabled.  if this memory exists (in C-bus
         * space and it has been jumpered, add it to
         * the list so it will be freed later.
         */
        for (i = 0; i < ATMB; i++) {
                if (CbusJumpers.jmp[i] && CbusJumpers.mem[i]) {
                        Address = MB(i) + DoublyMapped;
                        j = 1;
                        for (i++; i < ATMB && CbusJumpers.jmp[i] && CbusJumpers.mem[i]; i++)
                                j++;
                        CbusMemoryFree(Address, MB(j));
                }
        }
}


/*

Routine Description:

    Check for Cbus1 system boards being up to date for running OS/2.
    Obsolete additional CPU boards are removed from the configuration
    structure, and are not booted later.  If the boot CPU is obsolete,
    the system is halted.

Arguments:

    Table - Supplies a pointer to the RRD extended ID information table

    Count - Supplies a pointer to the number of valid entries in the
            RRD extended ID information table

Return Value:

    Shuffles the input table, and modifies the count of valid entries,
    in order to remove any obsolete processors.

    None.

 */
VOID
Cbus1ParseRRD(
IN PEXT_ID_INFO Table,
IN OUT PULONG Count
)
{
        ULONG                   Index;
        ULONG                   j;
        PEXT_ID_INFO            p;
        PEXT_ID_INFO            s;

        Cbus1DeadIDsIndex = 0;
        p = Table;

        for (Index = 0; Index < *Count; Index++, p++) {

                /*
                 * check for the ID 0 entry, which must be the first one.
                 * although this is NOT a processor, it is needed to
                 * arbitrate all the Cbus1 additional processors and set their
                 * IDs.  so capture it now.
                 */
                if (Index == 0) {
                        /*
                         * RRD specifies how much to map in per processor -
                         * for Cbus1, RRD must give us a size which includes any
                         * processor-specific registers the PSD may access,
                         * generally indicated via the CbusGlobal structure.
                         */
                        Cbus1ID0CSR = (PUCHAR)psd_map(
                                                (ULONG)p->pel_start,
                                                p->pel_size);

                        if (!Cbus1ID0CSR)
                                printf("PSD: cannot map ID0 CSR\n");

                        continue;
                }

                if (p->pm == 0)
                        continue;

                if ((p->pel_features & ELEMENT_HAS_APIC) == 0) {

                        /*
                         * the boot processor MUST have an APIC
                         * so that we can support distributed
                         * interrupts on Cbus1 platforms.
                         */
                        if ((UCHAR)p->id == CbusGlobal.bootid) {
                                printf("No APIC on the boot CPU - Use default OS/2 PSD instead\n");
                        }

                        *(PEXT_ID_INFO)&Cbus1DeadIDs[Cbus1DeadIDsIndex] =
                                *p;

                        Cbus1DeadIDsIndex++;

                        /*
                         * remove old board from "found" array
                         */
                        s = p;
                        for (j = Index+1; j < *Count; j++, s++) {
                                *s = *(s + 1);
                        }

                }

        }

        if (Cbus1DeadIDsIndex) {

                /*
                 * can't let any disabled processors be found,
                 * warn the user that we are disabling some number
                 * of processor boards because they cannot access
                 * the EISA bus.
                 */
                *Count -= Cbus1DeadIDsIndex;

                printf("WARNING: %d early C-bus I CPU board(s) disabled\n", Cbus1DeadIDsIndex);
        }

        Cbus1HandleJumpers();
}

/*

Routine Description:

    Check for a given arbitration ID (that just won) is a "dead" Cbus1
    system board - ie: one that we have software disabled because it is
    a processor incapable of symmetric I/O.

Arguments:

    ArbID - Supplies an arbitration ID to match

Return Value:

    An opaque pointer to the disabled processor's CSR space, 0
    if the ID was not found.

 */
PVOID
Cbus1FindDeadID(
IN ULONG ArbID
)
{
        ULONG           Dead;
        PUCHAR          csr = (PUCHAR)0;
        PEXT_ID_INFO    Idp = (PEXT_ID_INFO)Cbus1DeadIDs;

        for (Dead = 0; Dead < Cbus1DeadIDsIndex; Dead++, Idp++) {
                if (Idp->id != ArbID) {
                        continue;
                }

                csr = (PUCHAR)psd_map((ULONG)Idp->pel_start, Idp->pel_size);

                if (!csr)
                        printf("PSD: arbitration mapping failed\n");

                COUTB(csr, CbusGlobal.smp_setida,
                        CbusGlobal.smp_setida_val);

                switch (Idp->io_function) {
                        case IOF_INVALID_ENTRY:
                        case IOF_NO_IO:
                        case IOF_MEMORY:
                                break;
                        default:
                                /*
                                 * Add this I/O functionality to our table
                                 * to make available to Cbus hardware drivers.
                                 *
                                 * pel_features wasn't set for the old boards,
                                 * so check io_function instead.
                                 */

                                if (Idp->io_function == IOF_CBUS1_SIO ||
                                   (Idp->io_function == IOF_CBUS1_SCSI)) {

                                        /*
                                         * Add this I/O card to our list of
                                         * available I/O peripherals.
                                         */
                                        CbusIOPresent(
                                                (ULONG)Idp->id,
                                                (ULONG)Idp->io_function,
                                                (ULONG)Idp->io_attr,
                                                Idp->pel_start,
                                                Idp->pel_size,
                                                (PVOID)csr );
                                }
                                break;
                }

        }

        return (PVOID)csr;
}

/*
 * This routine is used to issue a software interrupt to either the
 * LIG processor or a specific processor.  Since this is all done in
 * hardware, the code to implement this is trivial.
 */
VOID
Cbus1RequestSoftwareInterrupt(
IN ULONG        vector,
IN ULONG        processor
)
{
        ULONG           target_bit;
        ULONG           cmd_dword;

#ifdef DISTRIBUTED_INTERRUPTS
        if (processor == SHOOT_LIG) {
                /*
                 * spray it, and the lowest priority processor will pick it up.
                 */
                target_bit = CbusActiveProcessorsMask;
                cmd_dword = (APIC_LOGICAL_MODE_DWORD | APIC_INTR_LIG_DWORD |
                                        vector);
        }
        else {
#endif
                /*
                 * caller has a specific target in mind.
                 */
                target_bit = (1 << processor);
                cmd_dword = (APIC_LOGICAL_MODE_DWORD | APIC_INTR_FIXED_DWORD |
                                        vector);
#ifdef DISTRIBUTED_INTERRUPTS
        }
#endif

        /*
         * interrupts are disabled on entry.  they must be
         * so that polling the register and poking it becomes
         * an atomic operation (for this processor),
         * as specified in the Intel 82489DX specification.
         * this is needed since interrupt service routines must
         * be allowed to send IPIs.
         */

        /*
         * wait for the delivery status register to become idle
         */
        APIC_WAIT();

        if (CbusVirtualWire == TRUE) {
                /*
                 * since we're using physical addressing in virtual wire
                 * mode (logical addressing is the norm for our full-blown
                 * APIC code), we're need to set the destination accordingly.
                 */
                Cbus1LocalApic->ApicICR.ra.destination =
                        ((2 * processor + 1) << APIC_BIT_TO_ID);
                Cbus1LocalApic->ApicICR.rb.dword1 = cmd_dword;
                return;
        }

        /*
         * use APIC logical mode to pop randomly-specified sets of processors
         * we cannot use "destination shorthand" mode for this;
         * we must write the whole 64 bit register.  ie:
         *
         *      The APIC icr.destination = processor_mask
         *      The APIC icr.vector = specified vector
         *      The APIC icr.destination_mode = 1 (LOGICAL);
         *
         *      all other fields are zero.
         *
         *      note that the high 32 bits of the interrupt command
         *      register must be written _BEFORE_ the low 32 bits.
         *
         *
         * specify the CPUs by translating logical processor mask into
         * the high byte of edx, since this is the only portion of the
         * logical destination register that future APICs will use to
         * compare with.
         */
        Cbus1LocalApic->ApicICR.ra.destination = (target_bit << APIC_BIT_TO_ID);

        /*
         * send the command...
         */
        Cbus1LocalApic->ApicICR.rb.dword1 = cmd_dword;
}

ULONG
READ_IOAPIC_ULONG(
ULONG Address
)
{
        Cbus1IOApic->register_select = Address;
        return Cbus1IOApic->window_register;
}

VOID
WRITE_IOAPIC_ULONG(
ULONG Address,
ULONG Value
)
{
        Cbus1IOApic->register_select = Address;
        Cbus1IOApic->window_register = Value;
}

/*
 *    Broadcast an ALL-INCLUDING-SELF interrupt with deassert, reset &
 *    physical mode set.  This routine is called after each APIC assigns
 *    itself a unique ID that can be used in APIC bus arbitration and
 *    priority arbitration.  This syncs up the picture that each APIC
 *    has with the new ID that has just been added.
 */
void
ApicArbsync()
{
        /*
         * disable interrupts so that polling the register and
         * poking it becomes an atomic operation (for this processor),
         * as specified in the Intel 82489DX specification.
         * this is needed since interrupt service routines must
         * be allowed to send IPIs (for example, DPCs, etc).
         */

        _asm pushfd;
        _asm cli;

        /*
         * wait for the delivery status register to become idle
         */
        APIC_WAIT();

        /*
         * it is ILLEGAL to use the "destination shorthand" mode of the APIC
         * for this command - we must set up the whole 64 bit register).
         * both destination and vector are DONT_CARE for this request.
         *
         * no recipients (probably a don't care), but must be written
         * _before_ the command is sent...
         */
        Cbus1LocalApic->ApicICR.ra.destination = 0;

        /*
         * now we can send the full deassert-reset command
         */
        if (CbusVirtualWire == TRUE)
                Cbus1LocalApic->ApicICR.rb.dword1 =
                        APIC_FULL_DRESET & ~APIC_LOGICAL_MODE_DWORD;
        else
                Cbus1LocalApic->ApicICR.rb.dword1 = APIC_FULL_DRESET;

        _asm popfd;
}

/*
 * switch the system from 8259 mode into APIC mode - this is guaranteed
 * to be called only by the boot processor.
 */
VOID
Cbus1EnablePIC(
ULONG   IrqState
)
{
        ULONG   i;
        ULONG   Processor = CbusPerCPU->processor;

        Cbus1InitializeLocalAPIC(Processor);
        Cbus1InitializeIOAPIC(Processor);

        for (i = 0; i < EISA_IRQLINES; i++) {
                if ((IrqState & (1 << i)) == 0)
                        Cbus1EnableInterrupt(CbusEisaVectors[i], i);
                else
                        Cbus1DisableInterrupt(CbusEisaVectors[i], i);
        }
}

/*
 * retrieve the current state of all the irqlines in system, returning
 * 1 for the bit in all lines that are currently disabled.
 * note that we cache the status of these lines in enable/disable interrupt.
 */
ULONG
Cbus1GetMasks()
{
#ifdef DEBUG
        ULONG           rv = 0;
        ULONG           irqline;
        ULONG           RedirectionAddress;
        REDIRECTION_T   entry;

        for (irqline = 0; irqline < EISA_IRQLINES; irqline++) {

                /*
                 * calculate the I/O APIC's redirection table offset so we
                 * can enable the interrupt below.
                 */
                RedirectionAddress = (ULONG)(IO_APIC_REDIRLO + 2 * irqline);

                entry.rb.dword2 = READ_IOAPIC_ULONG(RedirectionAddress + 1);

                if (entry.ra.Mask & APIC_INTR_MASKED)
                        rv |= (1 << irqline);
        }

        ASSERT (Cbus1CachedMask == rv);
#endif

        return Cbus1CachedMask;
}

/*
 * currently we only return status on the valid EISA irqlines -
 * as per the OS/2 kernel, we don't return status on spurious or IPI vectors.
 */
ULONG
Cbus1GetIRR()
{
        ULONG           rv = 0;
        ULONG           i;
        ULONG           irqline;
        ULONG           *IRRp;
        ULONG           irr;
        ULONG           vector;

        /*
         * note this function is not called if we are in virtualwiremode
         */
        ASSERT(CbusVirtualWire == FALSE);

        IRRp = &Cbus1LocalApic->ApicIRR;

        for (vector = 0; vector < 0xFF; vector += 32, IRRp += 4) {

                /*
                 * read 32 vectors at a time, then map the active
                 * ones to irqlines to return status for.
                 */
                irr = *IRRp;

                if (!irr)
                        continue;

                for (i = 0; i < 32; i++) {
                        if (irr & (1 << i)) {
                                irqline = CbusVectorToIrq[vector + i];

                                if (irqline != NO_IRQLINE) {
                                        rv |= (1 << irqline);
                                }
                        }
                }
        }

        return rv;
}

/*
 * currently we only return status on the valid EISA irqlines -
 * as per the OS/2 kernel, we don't return status on spurious or IPI vectors.
 */
ULONG
Cbus1GetISR()
{
        ULONG           rv = 0;
        ULONG           i;
        ULONG           irqline;
        ULONG           *ISRp;
        ULONG           isr;
        ULONG           vector;

        /*
         * note this function is not called if we are in virtualwiremode
         */
        ASSERT(CbusVirtualWire == FALSE);

        ISRp = &Cbus1LocalApic->ApicISR;

        for (vector = 0; vector < 0xFF; vector += 32, ISRp += 4) {

                /*
                 * read 32 vectors at a time, then map the active
                 * ones to irqlines to return status for.
                 */
                isr = *ISRp;

                if (!isr)
                        continue;

                for (i = 0; i < 32; i++) {
                        if (isr & (1 << i)) {
                                irqline = CbusVectorToIrq[vector + i];

                                if (irqline != NO_IRQLINE) {
                                        rv |= (1 << irqline);
                                }
                        }
                }
        }

        return rv;
}

/*
 * inform the caller that only the boot CPU can directly access the I/O APIC.
 * all CPUs can access their own local APIC, but each one can only access
 * his own I/O APIC.  and only the boot processor's I/O APIC is connected
 * to our first EISA bus.
 */
BOOL
Cbus1ReachIOPic()
{
        return FALSE;
}
