Mike Smith c3c50c4e3a Further fixes for multiple-IO-APIC systems from Tor Egge:
Further experimentation showed that some Dell 2450 machines with the
prevention kludge installed still got T_RESERVED traps.  CPU interrupt
vector 0x7A was observed to be triggered.  This might have been the
bitwise OR of two different vectors sent from each of the IOAPICs at
the same time.

	IOAPIC #0: 0x68 --> irq 8: RTC timer interrupt
	IOAPIC #1: 0x32 --> irq 18: scsi host adapter or network interface
		   ----
		   0x7a --> T_RESERVED

Both IOAPICs had ID 0.

Appendix B.3 in the MP spec indicates that the operating system is
responsible for assigning unique IDs to the IOAPICs.

The enclosed patch programs the IOAPIC IDs according to the IOAPIC
entries in the MP table.

Submitted by:	tegge
2000-05-31 21:37:28 +00:00

749 lines
17 KiB
C

/*
* Copyright (c) 1996, by Steve Passe
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. The name of the developer may NOT be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include "opt_smp.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <machine/smptests.h> /** TEST_TEST1 */
#include <machine/smp.h>
#include <machine/mpapic.h>
#include <machine/segments.h>
#include <i386/isa/intr_machdep.h> /* Xspuriousint() */
/* EISA Edge/Level trigger control registers */
#define ELCR0 0x4d0 /* eisa irq 0-7 */
#define ELCR1 0x4d1 /* eisa irq 8-15 */
/*
* pointers to pmapped apic hardware.
*/
#if defined(APIC_IO)
volatile ioapic_t *ioapic[NAPIC];
#endif /* APIC_IO */
/*
* Enable APIC, configure interrupts.
*/
void
apic_initialize(void)
{
u_int temp;
/* setup LVT1 as ExtINT */
temp = lapic.lvt_lint0;
temp &= ~(APIC_LVT_M | APIC_LVT_TM | APIC_LVT_IIPP | APIC_LVT_DM);
if (cpuid == 0)
temp |= 0x00000700; /* process ExtInts */
else
temp |= 0x00010700; /* mask ExtInts */
lapic.lvt_lint0 = temp;
/* setup LVT2 as NMI, masked till later... */
temp = lapic.lvt_lint1;
temp &= ~(APIC_LVT_M | APIC_LVT_TM | APIC_LVT_IIPP | APIC_LVT_DM);
temp |= 0x00010400; /* masked, edge trigger, active hi */
lapic.lvt_lint1 = temp;
/* set the Task Priority Register as needed */
temp = lapic.tpr;
temp &= ~APIC_TPR_PRIO; /* clear priority field */
temp |= LOPRIO_LEVEL; /* allow INT arbitration */
lapic.tpr = temp;
/* enable the local APIC */
temp = lapic.svr;
temp |= APIC_SVR_SWEN; /* software enable APIC */
temp &= ~APIC_SVR_FOCUS; /* enable 'focus processor' */
/* set the 'spurious INT' vector */
if ((XSPURIOUSINT_OFFSET & APIC_SVR_VEC_FIX) != APIC_SVR_VEC_FIX)
panic("bad XSPURIOUSINT_OFFSET: 0x%08x", XSPURIOUSINT_OFFSET);
temp &= ~APIC_SVR_VEC_PROG; /* clear (programmable) vector field */
temp |= (XSPURIOUSINT_OFFSET & APIC_SVR_VEC_PROG);
#if defined(TEST_TEST1)
if (cpuid == GUARD_CPU) {
temp &= ~APIC_SVR_SWEN; /* software DISABLE APIC */
}
#endif /** TEST_TEST1 */
lapic.svr = temp;
if (bootverbose)
apic_dump("apic_initialize()");
}
/*
* dump contents of local APIC registers
*/
void
apic_dump(char* str)
{
printf("SMP: CPU%d %s:\n", cpuid, str);
printf(" lint0: 0x%08x lint1: 0x%08x TPR: 0x%08x SVR: 0x%08x\n",
lapic.lvt_lint0, lapic.lvt_lint1, lapic.tpr, lapic.svr);
}
#if defined(APIC_IO)
/*
* IO APIC code,
*/
#define IOAPIC_ISA_INTS 16
#define REDIRCNT_IOAPIC(A) \
((int)((io_apic_versions[(A)] & IOART_VER_MAXREDIR) >> MAXREDIRSHIFT) + 1)
static int trigger __P((int apic, int pin, u_int32_t * flags));
static void polarity __P((int apic, int pin, u_int32_t * flags, int level));
#define DEFAULT_FLAGS \
((u_int32_t) \
(IOART_INTMSET | \
IOART_DESTPHY | \
IOART_DELLOPRI))
#define DEFAULT_ISA_FLAGS \
((u_int32_t) \
(IOART_INTMSET | \
IOART_TRGREDG | \
IOART_INTAHI | \
IOART_DESTPHY | \
IOART_DELLOPRI))
void
io_apic_set_id(int apic, int id)
{
u_int32_t ux;
ux = io_apic_read(apic, IOAPIC_ID); /* get current contents */
if (((ux & APIC_ID_MASK) >> 24) != id) {
ux &= ~APIC_ID_MASK; /* clear the ID field */
ux |= (id << 24);
io_apic_write(apic, IOAPIC_ID, ux); /* write new value */
ux = io_apic_read(apic, IOAPIC_ID); /* re-read && test */
if (((ux & APIC_ID_MASK) >> 24) != id)
panic("can't control IO APIC #%d ID, reg: 0x%08x",
apic, ux);
}
}
/*
* Setup the IO APIC.
*/
extern int apic_pin_trigger; /* 'opaque' */
int
io_apic_setup(int apic)
{
int maxpin;
u_char select; /* the select register is 8 bits */
u_int32_t flags; /* the window register is 32 bits */
u_int32_t target; /* the window register is 32 bits */
u_int32_t vector; /* the window register is 32 bits */
int pin, level;
target = IOART_DEST;
if (apic == 0)
apic_pin_trigger = 0; /* default to edge-triggered */
maxpin = REDIRCNT_IOAPIC(apic); /* pins in APIC */
printf("Programming %d pins in IOAPIC #%d\n", maxpin, apic);
for (pin = 0; pin < maxpin; ++pin) {
int bus, bustype, irq;
select = pin * 2 + IOAPIC_REDTBL0; /* register */
/*
* Always disable interrupts, and by default map
* pin X to IRQX because the disable doesn't stick
* and the uninitialize vector will get translated
* into a panic.
*
* This is correct for IRQs 1 and 3-15. In the other cases,
* any robust driver will handle the spurious interrupt, and
* the effective NOP beats a panic.
*
* A dedicated "bogus interrupt" entry in the IDT would
* be a nicer hack, although some one should find out
* why some systems are generating interrupts when they
* shouldn't and stop the carnage.
*/
vector = NRSVIDT + pin; /* IDT vec */
io_apic_write(apic, select,
(io_apic_read(apic, select) & ~IOART_INTMASK
& ~0xff)|IOART_INTMSET|vector);
/* we only deal with vectored INTs here */
if (apic_int_type(apic, pin) != 0)
continue;
irq = apic_irq(apic, pin);
if (irq < 0)
continue;
/* determine the bus type for this pin */
bus = apic_src_bus_id(apic, pin);
if (bus == -1)
continue;
bustype = apic_bus_type(bus);
if ((bustype == ISA) &&
(pin < IOAPIC_ISA_INTS) &&
(irq == pin) &&
(apic_polarity(apic, pin) == 0x1) &&
(apic_trigger(apic, pin) == 0x3)) {
/*
* A broken BIOS might describe some ISA
* interrupts as active-high level-triggered.
* Use default ISA flags for those interrupts.
*/
flags = DEFAULT_ISA_FLAGS;
} else {
/*
* Program polarity and trigger mode according to
* interrupt entry.
*/
flags = DEFAULT_FLAGS;
level = trigger(apic, pin, &flags);
if (level == 1)
apic_pin_trigger |= (1 << irq);
polarity(apic, pin, &flags, level);
}
/* program the appropriate registers */
if (apic != 0 || pin != irq)
printf("IOAPIC #%d intpin %d -> irq %d\n",
apic, pin, irq);
vector = NRSVIDT + irq; /* IDT vec */
io_apic_write(apic, select, flags | vector);
io_apic_write(apic, select + 1, target);
}
/* return GOOD status */
return 0;
}
#undef DEFAULT_ISA_FLAGS
#undef DEFAULT_FLAGS
#define DEFAULT_EXTINT_FLAGS \
((u_int32_t) \
(IOART_INTMSET | \
IOART_TRGREDG | \
IOART_INTAHI | \
IOART_DESTPHY | \
IOART_DELLOPRI))
/*
* Setup the source of External INTerrupts.
*/
int
ext_int_setup(int apic, int intr)
{
u_char select; /* the select register is 8 bits */
u_int32_t flags; /* the window register is 32 bits */
u_int32_t target; /* the window register is 32 bits */
u_int32_t vector; /* the window register is 32 bits */
if (apic_int_type(apic, intr) != 3)
return -1;
target = IOART_DEST;
select = IOAPIC_REDTBL0 + (2 * intr);
vector = NRSVIDT + intr;
flags = DEFAULT_EXTINT_FLAGS;
io_apic_write(apic, select, flags | vector);
io_apic_write(apic, select + 1, target);
return 0;
}
#undef DEFAULT_EXTINT_FLAGS
/*
* Set the trigger level for an IO APIC pin.
*/
static int
trigger(int apic, int pin, u_int32_t * flags)
{
int id;
int eirq;
int level;
static int intcontrol = -1;
switch (apic_trigger(apic, pin)) {
case 0x00:
break;
case 0x01:
*flags &= ~IOART_TRGRLVL; /* *flags |= IOART_TRGREDG */
return 0;
case 0x03:
*flags |= IOART_TRGRLVL;
return 1;
case -1:
default:
goto bad;
}
if ((id = apic_src_bus_id(apic, pin)) == -1)
goto bad;
switch (apic_bus_type(id)) {
case ISA:
*flags &= ~IOART_TRGRLVL; /* *flags |= IOART_TRGREDG; */
return 0;
case EISA:
eirq = apic_src_bus_irq(apic, pin);
if (eirq < 0 || eirq > 15) {
printf("EISA IRQ %d?!?!\n", eirq);
goto bad;
}
if (intcontrol == -1) {
intcontrol = inb(ELCR1) << 8;
intcontrol |= inb(ELCR0);
printf("EISA INTCONTROL = %08x\n", intcontrol);
}
/* Use ELCR settings to determine level or edge mode */
level = (intcontrol >> eirq) & 1;
/*
* Note that on older Neptune chipset based systems, any
* pci interrupts often show up here and in the ELCR as well
* as level sensitive interrupts attributed to the EISA bus.
*/
if (level)
*flags |= IOART_TRGRLVL;
else
*flags &= ~IOART_TRGRLVL;
return level;
case PCI:
*flags |= IOART_TRGRLVL;
return 1;
case -1:
default:
goto bad;
}
bad:
panic("bad APIC IO INT flags");
}
/*
* Set the polarity value for an IO APIC pin.
*/
static void
polarity(int apic, int pin, u_int32_t * flags, int level)
{
int id;
switch (apic_polarity(apic, pin)) {
case 0x00:
break;
case 0x01:
*flags &= ~IOART_INTALO; /* *flags |= IOART_INTAHI */
return;
case 0x03:
*flags |= IOART_INTALO;
return;
case -1:
default:
goto bad;
}
if ((id = apic_src_bus_id(apic, pin)) == -1)
goto bad;
switch (apic_bus_type(id)) {
case ISA:
*flags &= ~IOART_INTALO; /* *flags |= IOART_INTAHI */
return;
case EISA:
/* polarity converter always gives active high */
*flags &= ~IOART_INTALO;
return;
case PCI:
*flags |= IOART_INTALO;
return;
case -1:
default:
goto bad;
}
bad:
panic("bad APIC IO INT flags");
}
/*
* Print contents of apic_imen.
*/
extern u_int apic_imen; /* keep apic_imen 'opaque' */
void
imen_dump(void)
{
int x;
printf("SMP: enabled INTs: ");
for (x = 0; x < 24; ++x)
if ((apic_imen & (1 << x)) == 0)
printf("%d, ", x);
printf("apic_imen: 0x%08x\n", apic_imen);
}
/*
* Inter Processor Interrupt functions.
*/
/*
* Send APIC IPI 'vector' to 'destType' via 'deliveryMode'.
*
* destType is 1 of: APIC_DEST_SELF, APIC_DEST_ALLISELF, APIC_DEST_ALLESELF
* vector is any valid SYSTEM INT vector
* delivery_mode is 1 of: APIC_DELMODE_FIXED, APIC_DELMODE_LOWPRIO
*/
#define DETECT_DEADLOCK
int
apic_ipi(int dest_type, int vector, int delivery_mode)
{
u_long icr_lo;
#if defined(DETECT_DEADLOCK)
#define MAX_SPIN1 10000000
#define MAX_SPIN2 1000
int x;
/* "lazy delivery", ie we only barf if they stack up on us... */
for (x = MAX_SPIN1; x; --x) {
if ((lapic.icr_lo & APIC_DELSTAT_MASK) == 0)
break;
}
if (x == 0)
panic("apic_ipi was stuck");
#endif /* DETECT_DEADLOCK */
/* build IRC_LOW */
icr_lo = (lapic.icr_lo & APIC_RESV2_MASK)
| dest_type | delivery_mode | vector;
/* write APIC ICR */
lapic.icr_lo = icr_lo;
/* wait for pending status end */
#if defined(DETECT_DEADLOCK)
for (x = MAX_SPIN2; x; --x) {
if ((lapic.icr_lo & APIC_DELSTAT_MASK) == 0)
break;
}
#ifdef needsattention
/*
* XXX FIXME:
* The above loop waits for the message to actually be delivered.
* It breaks out after an arbitrary timout on the theory that it eventually
* will be delivered and we will catch a real failure on the next entry to
* this function, which would panic().
* We could skip this wait entirely, EXCEPT it probably protects us from
* other "less robust" routines that assume the message was delivered and
* acted upon when this function returns. TLB shootdowns are one such
* "less robust" function.
*/
if (x == 0)
printf("apic_ipi might be stuck\n");
#endif
#undef MAX_SPIN2
#undef MAX_SPIN1
#else
while (lapic.icr_lo & APIC_DELSTAT_MASK)
/* spin */ ;
#endif /* DETECT_DEADLOCK */
/** XXX FIXME: return result */
return 0;
}
static int
apic_ipi_singledest(int cpu, int vector, int delivery_mode)
{
u_long icr_lo;
u_long icr_hi;
u_long eflags;
#if defined(DETECT_DEADLOCK)
#define MAX_SPIN1 10000000
#define MAX_SPIN2 1000
int x;
/* "lazy delivery", ie we only barf if they stack up on us... */
for (x = MAX_SPIN1; x; --x) {
if ((lapic.icr_lo & APIC_DELSTAT_MASK) == 0)
break;
}
if (x == 0)
panic("apic_ipi was stuck");
#endif /* DETECT_DEADLOCK */
eflags = read_eflags();
__asm __volatile("cli" : : : "memory");
icr_hi = lapic.icr_hi & ~APIC_ID_MASK;
icr_hi |= (CPU_TO_ID(cpu) << 24);
lapic.icr_hi = icr_hi;
/* build IRC_LOW */
icr_lo = (lapic.icr_lo & APIC_RESV2_MASK)
| APIC_DEST_DESTFLD | delivery_mode | vector;
/* write APIC ICR */
lapic.icr_lo = icr_lo;
write_eflags(eflags);
/* wait for pending status end */
#if defined(DETECT_DEADLOCK)
for (x = MAX_SPIN2; x; --x) {
if ((lapic.icr_lo & APIC_DELSTAT_MASK) == 0)
break;
}
#ifdef needsattention
/*
* XXX FIXME:
* The above loop waits for the message to actually be delivered.
* It breaks out after an arbitrary timout on the theory that it eventually
* will be delivered and we will catch a real failure on the next entry to
* this function, which would panic().
* We could skip this wait entirely, EXCEPT it probably protects us from
* other "less robust" routines that assume the message was delivered and
* acted upon when this function returns. TLB shootdowns are one such
* "less robust" function.
*/
if (x == 0)
printf("apic_ipi might be stuck\n");
#endif
#undef MAX_SPIN2
#undef MAX_SPIN1
#else
while (lapic.icr_lo & APIC_DELSTAT_MASK)
/* spin */ ;
#endif /* DETECT_DEADLOCK */
/** XXX FIXME: return result */
return 0;
}
/*
* Send APIC IPI 'vector' to 'target's via 'delivery_mode'.
*
* target contains a bitfield with a bit set for selected APICs.
* vector is any valid SYSTEM INT vector
* delivery_mode is 1 of: APIC_DELMODE_FIXED, APIC_DELMODE_LOWPRIO
*/
int
selected_apic_ipi(u_int target, int vector, int delivery_mode)
{
int x;
int status;
if (target & ~0x7fff)
return -1; /* only 15 targets allowed */
for (status = 0, x = 0; x <= 14; ++x)
if (target & (1 << x)) {
/* send the IPI */
if (apic_ipi_singledest(x, vector,
delivery_mode) == -1)
status |= (1 << x);
}
return status;
}
#if defined(READY)
/*
* Send an IPI INTerrupt containing 'vector' to CPU 'target'
* NOTE: target is a LOGICAL APIC ID
*/
int
selected_proc_ipi(int target, int vector)
{
u_long icr_lo;
u_long icr_hi;
/* write the destination field for the target AP */
icr_hi = (lapic.icr_hi & ~APIC_ID_MASK) |
(cpu_num_to_apic_id[target] << 24);
lapic.icr_hi = icr_hi;
/* write command */
icr_lo = (lapic.icr_lo & APIC_RESV2_MASK) |
APIC_DEST_DESTFLD | APIC_DELMODE_FIXED | vector;
lapic.icr_lo = icr_lo;
/* wait for pending status end */
while (lapic.icr_lo & APIC_DELSTAT_MASK)
/* spin */ ;
return 0; /** XXX FIXME: return result */
}
#endif /* READY */
#endif /* APIC_IO */
/*
* Timer code, in development...
* - suggested by rgrimes@gndrsh.aac.dev.com
*/
/** XXX FIXME: temp hack till we can determin bus clock */
#ifndef BUS_CLOCK
#define BUS_CLOCK 66000000
#define bus_clock() 66000000
#endif
#if defined(READY)
int acquire_apic_timer __P((void));
int release_apic_timer __P((void));
/*
* Acquire the APIC timer for exclusive use.
*/
int
acquire_apic_timer(void)
{
#if 1
return 0;
#else
/** XXX FIXME: make this really do something */
panic("APIC timer in use when attempting to aquire");
#endif
}
/*
* Return the APIC timer.
*/
int
release_apic_timer(void)
{
#if 1
return 0;
#else
/** XXX FIXME: make this really do something */
panic("APIC timer was already released");
#endif
}
#endif /* READY */
/*
* Load a 'downcount time' in uSeconds.
*/
void
set_apic_timer(int value)
{
u_long lvtt;
long ticks_per_microsec;
/*
* Calculate divisor and count from value:
*
* timeBase == CPU bus clock divisor == [1,2,4,8,16,32,64,128]
* value == time in uS
*/
lapic.dcr_timer = APIC_TDCR_1;
ticks_per_microsec = bus_clock() / 1000000;
/* configure timer as one-shot */
lvtt = lapic.lvt_timer;
lvtt &= ~(APIC_LVTT_VECTOR | APIC_LVTT_DS | APIC_LVTT_M | APIC_LVTT_TM);
lvtt |= APIC_LVTT_M; /* no INT, one-shot */
lapic.lvt_timer = lvtt;
/* */
lapic.icr_timer = value * ticks_per_microsec;
}
/*
* Read remaining time in timer.
*/
int
read_apic_timer(void)
{
#if 0
/** XXX FIXME: we need to return the actual remaining time,
* for now we just return the remaining count.
*/
#else
return lapic.ccr_timer;
#endif
}
/*
* Spin-style delay, set delay time in uS, spin till it drains.
*/
void
u_sleep(int count)
{
set_apic_timer(count);
while (read_apic_timer())
/* spin */ ;
}