#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <machine/atomic.h>
#include <machine/i8259.h>
#include <machine/cpu.h>
#include <machine/pio.h>
#include <machine/cpufunc.h>
#include "lapic.h"
#include "xen.h"
#include "hyperv.h"
#include "xcall.h"
#if NLAPIC > 0
#include <machine/i82489var.h>
#endif
struct pic softintr_pic = {
{0, {NULL}, NULL, 0, "softintr_pic0", NULL, 0, 0},
PIC_SOFT,
#ifdef MULTIPROCESSOR
{},
#endif
NULL,
NULL,
NULL,
NULL,
NULL,
};
const int softintr_to_ssir[NSOFTINTR] = {
SIR_CLOCK,
SIR_NET,
SIR_TTY,
};
int intr_suspended;
struct intrhand *intr_nowake;
void
intr_default_setup(void)
{
int i;
for (i = 0; i < NUM_LEGACY_IRQS; i++) {
idt_allocmap[ICU_OFFSET + i] = 1;
setgate(&idt[ICU_OFFSET + i],
i8259_stubs[i].ist_entry, 0, SDT_SYS386IGT,
SEL_KPL, GSEL(GCODE_SEL, SEL_KPL));
}
i8259_default_setup();
}
int
x86_nmi(void)
{
log(LOG_CRIT, "NMI port 61 %x, port 70 %x\n", inb(0x61), inb(0x70));
return(0);
}
void
intr_calculatemasks(struct cpu_info *ci)
{
int irq, level;
u_int64_t unusedirqs, intrlevel[MAX_INTR_SOURCES];
struct intrhand *q;
unusedirqs = 0xffffffffffffffffUL;
for (irq = 0; irq < MAX_INTR_SOURCES; irq++) {
int levels = 0;
if (ci->ci_isources[irq] == NULL) {
intrlevel[irq] = 0;
continue;
}
for (q = ci->ci_isources[irq]->is_handlers; q; q = q->ih_next)
levels |= (1 << q->ih_level);
intrlevel[irq] = levels;
if (levels)
unusedirqs &= ~(1UL << irq);
}
for (level = 0; level < NIPL; level++) {
u_int64_t irqs = 0;
for (irq = 0; irq < MAX_INTR_SOURCES; irq++)
if (intrlevel[irq] & (1 << level))
irqs |= (1UL << irq);
ci->ci_imask[level] = irqs | unusedirqs;
}
for (level = 0; level< (NIPL - 1); level++)
ci->ci_imask[level + 1] |= ci->ci_imask[level];
for (irq = 0; irq < MAX_INTR_SOURCES; irq++) {
int maxlevel = IPL_NONE;
int minlevel = IPL_HIGH;
if (ci->ci_isources[irq] == NULL)
continue;
for (q = ci->ci_isources[irq]->is_handlers; q;
q = q->ih_next) {
if (q->ih_level < minlevel)
minlevel = q->ih_level;
if (q->ih_level > maxlevel)
maxlevel = q->ih_level;
}
ci->ci_isources[irq]->is_maxlevel = maxlevel;
ci->ci_isources[irq]->is_minlevel = minlevel;
}
for (level = 0; level < NIPL; level++)
ci->ci_iunmask[level] = ~ci->ci_imask[level];
}
int
intr_allocate_slot_cpu(struct cpu_info *ci, struct pic *pic, int pin,
int *index)
{
int start, slot, i;
struct intrsource *isp;
start = CPU_IS_PRIMARY(ci) ? NUM_LEGACY_IRQS : 0;
slot = -1;
for (i = 0; i < start; i++) {
isp = ci->ci_isources[i];
if (isp != NULL && isp->is_pic == pic && isp->is_pin == pin) {
slot = i;
start = MAX_INTR_SOURCES;
break;
}
}
for (i = start; i < MAX_INTR_SOURCES ; i++) {
isp = ci->ci_isources[i];
if (isp != NULL && isp->is_pic == pic && isp->is_pin == pin) {
slot = i;
break;
}
if (isp == NULL && slot == -1) {
slot = i;
continue;
}
}
if (slot == -1) {
return EBUSY;
}
isp = ci->ci_isources[slot];
if (isp == NULL) {
isp = malloc(sizeof (struct intrsource), M_DEVBUF,
M_NOWAIT|M_ZERO);
if (isp == NULL) {
return ENOMEM;
}
snprintf(isp->is_evname, sizeof (isp->is_evname),
"pin %d", pin);
ci->ci_isources[slot] = isp;
}
*index = slot;
return 0;
}
int
intr_allocate_slot(struct pic *pic, int legacy_irq, int pin, int level,
struct cpu_info **cip, int *index, int *idt_slot)
{
CPU_INFO_ITERATOR cii;
struct cpu_info *ci;
struct intrsource *isp;
int slot, idtvec, error;
if (legacy_irq != -1) {
ci = &cpu_info_primary;
for (slot = 0 ; slot < MAX_INTR_SOURCES ; slot++) {
isp = ci->ci_isources[slot];
if (isp != NULL && isp->is_pic == pic &&
isp->is_pin == pin ) {
goto duplicate;
}
}
slot = legacy_irq;
isp = ci->ci_isources[slot];
if (isp == NULL) {
isp = malloc(sizeof (struct intrsource), M_DEVBUF,
M_NOWAIT|M_ZERO);
if (isp == NULL)
return ENOMEM;
snprintf(isp->is_evname, sizeof (isp->is_evname),
"pin %d", pin);
ci->ci_isources[slot] = isp;
} else {
if (isp->is_pic != pic || isp->is_pin != pin) {
if (pic == &i8259_pic)
return EINVAL;
goto other;
}
}
duplicate:
if (pic == &i8259_pic)
idtvec = ICU_OFFSET + legacy_irq;
else {
#ifdef IOAPIC_HWMASK
if (level > isp->is_maxlevel) {
#else
if (isp->is_minlevel == 0 || level < isp->is_minlevel) {
#endif
idtvec = idt_vec_alloc(APIC_LEVEL(level),
IDT_INTR_HIGH);
if (idtvec == 0)
return EBUSY;
} else
idtvec = isp->is_idtvec;
}
} else {
other:
if (*cip == NULL)
ci = &cpu_info_primary;
else
ci = *cip;
error = intr_allocate_slot_cpu(ci, pic, pin, &slot);
if (error == 0)
goto found;
if (*cip != NULL)
return EBUSY;
CPU_INFO_FOREACH(cii, ci) {
if (CPU_IS_PRIMARY(ci))
continue;
error = intr_allocate_slot_cpu(ci, pic, pin, &slot);
if (error == 0)
goto found;
}
return EBUSY;
found:
if (pic->pic_allocidtvec) {
idtvec = pic->pic_allocidtvec(pic, pin,
APIC_LEVEL(level), IDT_INTR_HIGH);
} else {
idtvec = idt_vec_alloc(APIC_LEVEL(level),
IDT_INTR_HIGH);
}
if (idtvec == 0) {
free(ci->ci_isources[slot], M_DEVBUF,
sizeof (struct intrsource));
ci->ci_isources[slot] = NULL;
return EBUSY;
}
}
*idt_slot = idtvec;
*index = slot;
*cip = ci;
return 0;
}
int intr_shared_edge;
void *
intr_establish(int legacy_irq, struct pic *pic, int pin, int type, int level,
struct cpu_info *ci, int (*handler)(void *), void *arg, const char *what)
{
struct intrhand **p, *q, *ih;
int slot, error, idt_vec;
struct intrsource *source;
struct intrstub *stubp;
int flags;
#ifdef DIAGNOSTIC
if (legacy_irq != -1 && (legacy_irq < 0 || legacy_irq > 15))
panic("intr_establish: bad legacy IRQ value");
if (legacy_irq == -1 && pic == &i8259_pic)
panic("intr_establish: non-legacy IRQ on i8259");
#endif
flags = level & (IPL_MPSAFE | IPL_WAKEUP);
level &= ~(IPL_MPSAFE | IPL_WAKEUP);
KASSERT(level <= IPL_TTY || level >= IPL_CLOCK || flags & IPL_MPSAFE);
error = intr_allocate_slot(pic, legacy_irq, pin, level, &ci, &slot,
&idt_vec);
if (error != 0) {
printf("failed to allocate interrupt slot for PIC %s pin %d\n",
pic->pic_dev.dv_xname, pin);
return NULL;
}
ih = malloc(sizeof *ih, M_DEVBUF, cold ? M_NOWAIT : M_WAITOK);
if (ih == NULL) {
printf("intr_establish: can't allocate handler info\n");
return NULL;
}
source = ci->ci_isources[slot];
if (source->is_handlers != NULL &&
source->is_pic->pic_type != pic->pic_type) {
free(ih, M_DEVBUF, sizeof(*ih));
printf("intr_establish: can't share intr source between "
"different PIC types (legacy_irq %d pin %d slot %d)\n",
legacy_irq, pin, slot);
return NULL;
}
source->is_pin = pin;
source->is_pic = pic;
switch (source->is_type) {
case IST_NONE:
source->is_type = type;
break;
case IST_EDGE:
intr_shared_edge = 1;
case IST_LEVEL:
if (source->is_type == type)
break;
case IST_PULSE:
if (type != IST_NONE) {
printf("intr_establish: pic %s pin %d: can't share "
"type %d with %d\n", pic->pic_name, pin,
source->is_type, type);
free(ih, M_DEVBUF, sizeof(*ih));
return NULL;
}
break;
default:
panic("intr_establish: bad intr type %d for pic %s pin %d",
source->is_type, pic->pic_dev.dv_xname, pin);
}
if (!cold)
pic->pic_hwmask(pic, pin);
for (p = &ci->ci_isources[slot]->is_handlers;
(q = *p) != NULL && q->ih_level > level;
p = &q->ih_next)
;
ih->ih_fun = handler;
ih->ih_arg = arg;
ih->ih_next = *p;
ih->ih_level = level;
ih->ih_flags = flags;
ih->ih_pin = pin;
ih->ih_cpu = ci;
ih->ih_slot = slot;
evcount_attach(&ih->ih_count, what, &source->is_idtvec);
*p = ih;
intr_calculatemasks(ci);
if (ci->ci_isources[slot]->is_resume == NULL ||
source->is_idtvec != idt_vec) {
if (source->is_idtvec != 0 && source->is_idtvec != idt_vec)
idt_vec_free(source->is_idtvec);
source->is_idtvec = idt_vec;
stubp = type == IST_LEVEL ?
&pic->pic_level_stubs[slot] : &pic->pic_edge_stubs[slot];
ci->ci_isources[slot]->is_resume = stubp->ist_resume;
ci->ci_isources[slot]->is_recurse = stubp->ist_recurse;
setgate(&idt[idt_vec], stubp->ist_entry, 0, SDT_SYS386IGT,
SEL_KPL, GSEL(GCODE_SEL, SEL_KPL));
}
pic->pic_addroute(pic, ci, pin, idt_vec, type);
if (!cold)
pic->pic_hwunmask(pic, pin);
#ifdef INTRDEBUG
printf("allocated pic %s type %s pin %d level %d to cpu%u slot %d idt entry %d\n",
pic->pic_name, type == IST_EDGE ? "edge" : "level", pin, level,
ci->ci_apicid, slot, idt_vec);
#endif
return (ih);
}
void
intr_disestablish(struct intrhand *ih)
{
struct intrhand **p, *q;
struct cpu_info *ci;
struct pic *pic;
struct intrsource *source;
int idtvec;
ci = ih->ih_cpu;
pic = ci->ci_isources[ih->ih_slot]->is_pic;
source = ci->ci_isources[ih->ih_slot];
idtvec = source->is_idtvec;
pic->pic_hwmask(pic, ih->ih_pin);
x86_atomic_clearbits_u64(&ci->ci_ipending, (1UL << ih->ih_slot));
for (p = &source->is_handlers; (q = *p) != NULL && q != ih;
p = &q->ih_next)
;
if (q == NULL) {
panic("intr_disestablish: handler not registered");
}
*p = q->ih_next;
intr_calculatemasks(ci);
if (source->is_handlers == NULL)
pic->pic_delroute(pic, ci, ih->ih_pin, idtvec, source->is_type);
else
pic->pic_hwunmask(pic, ih->ih_pin);
#ifdef INTRDEBUG
printf("cpu%u: remove slot %d (pic %s pin %d vec %d)\n",
ci->ci_apicid, ih->ih_slot, pic->pic_dev.dv_xname, ih->ih_pin,
idtvec);
#endif
if (source->is_handlers == NULL) {
free(source, M_DEVBUF, sizeof (struct intrsource));
ci->ci_isources[ih->ih_slot] = NULL;
if (pic != &i8259_pic)
idt_vec_free(idtvec);
}
evcount_detach(&ih->ih_count);
free(ih, M_DEVBUF, sizeof(*ih));
}
int
intr_handler(struct intrframe *frame, struct intrhand *ih)
{
struct cpu_info *ci = curcpu();
int floor;
int rc;
#ifdef MULTIPROCESSOR
int need_lock;
#endif
if (intr_suspended && (ih->ih_flags & IPL_WAKEUP) == 0) {
intr_nowake = ih;
return 0;
}
#ifdef MULTIPROCESSOR
if (ih->ih_flags & IPL_MPSAFE)
need_lock = 0;
else
need_lock = 1;
if (need_lock)
__mp_lock(&kernel_lock);
#endif
floor = ci->ci_handled_intr_level;
ci->ci_handled_intr_level = ih->ih_level;
rc = (*ih->ih_fun)(ih->ih_arg ? ih->ih_arg : frame);
ci->ci_handled_intr_level = floor;
#ifdef MULTIPROCESSOR
if (need_lock)
__mp_unlock(&kernel_lock);
#endif
return rc;
}
struct intrhand fake_softclock_intrhand;
struct intrhand fake_softnet_intrhand;
struct intrhand fake_softtty_intrhand;
struct intrhand fake_timer_intrhand;
#ifdef MULTIPROCESSOR
struct intrhand fake_ipi_intrhand;
#endif
#if NXEN > 0
struct intrhand fake_xen_intrhand;
#endif
#if NHYPERV > 0
struct intrhand fake_hyperv_intrhand;
#endif
void
cpu_intr_init(struct cpu_info *ci)
{
struct intrsource *isp;
#if NLAPIC > 0 && defined(MULTIPROCESSOR) && 0
int i;
#endif
isp = malloc(sizeof (struct intrsource), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xsoftclock;
isp->is_resume = Xsoftclock;
fake_softclock_intrhand.ih_level = IPL_SOFTCLOCK;
isp->is_handlers = &fake_softclock_intrhand;
isp->is_pic = &softintr_pic;
ci->ci_isources[SIR_CLOCK] = isp;
isp = malloc(sizeof (struct intrsource), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xsoftnet;
isp->is_resume = Xsoftnet;
fake_softnet_intrhand.ih_level = IPL_SOFTNET;
isp->is_handlers = &fake_softnet_intrhand;
isp->is_pic = &softintr_pic;
ci->ci_isources[SIR_NET] = isp;
isp = malloc(sizeof (struct intrsource), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xsofttty;
isp->is_resume = Xsofttty;
fake_softtty_intrhand.ih_level = IPL_SOFTTTY;
isp->is_handlers = &fake_softtty_intrhand;
isp->is_pic = &softintr_pic;
ci->ci_isources[SIR_TTY] = isp;
#if NLAPIC > 0
isp = malloc(sizeof (struct intrsource), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xrecurse_lapic_ltimer;
isp->is_resume = Xresume_lapic_ltimer;
fake_timer_intrhand.ih_level = IPL_CLOCK;
isp->is_handlers = &fake_timer_intrhand;
isp->is_pic = &local_pic;
ci->ci_isources[LIR_TIMER] = isp;
#ifdef MULTIPROCESSOR
isp = malloc(sizeof (struct intrsource), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xrecurse_lapic_ipi;
isp->is_resume = Xresume_lapic_ipi;
fake_ipi_intrhand.ih_level = IPL_IPI;
isp->is_handlers = &fake_ipi_intrhand;
isp->is_pic = &local_pic;
ci->ci_isources[LIR_IPI] = isp;
#if NXCALL > 0
isp = malloc(sizeof(*isp), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xxcallintr;
isp->is_resume = Xxcallintr;
fake_ipi_intrhand.ih_level = IPL_SOFTCLOCK;
isp->is_handlers = &fake_ipi_intrhand;
isp->is_pic = &local_pic;
ci->ci_isources[SIR_XCALL] = isp;
#endif
#endif
#if NXEN > 0
isp = malloc(sizeof (struct intrsource), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xrecurse_xen_upcall;
isp->is_resume = Xresume_xen_upcall;
fake_xen_intrhand.ih_level = IPL_NET;
isp->is_handlers = &fake_xen_intrhand;
isp->is_pic = &local_pic;
ci->ci_isources[LIR_XEN] = isp;
#endif
#if NHYPERV > 0
isp = malloc(sizeof (struct intrsource), M_DEVBUF, M_NOWAIT|M_ZERO);
if (isp == NULL)
panic("can't allocate fixed interrupt source");
isp->is_recurse = Xrecurse_hyperv_upcall;
isp->is_resume = Xresume_hyperv_upcall;
fake_hyperv_intrhand.ih_level = IPL_NET;
isp->is_handlers = &fake_hyperv_intrhand;
isp->is_pic = &local_pic;
ci->ci_isources[LIR_HYPERV] = isp;
#endif
#endif
intr_calculatemasks(ci);
}
void
intr_printconfig(void)
{
#ifdef INTRDEBUG
int i;
struct intrhand *ih;
struct intrsource *isp;
struct cpu_info *ci;
CPU_INFO_ITERATOR cii;
CPU_INFO_FOREACH(cii, ci) {
printf("cpu%d: interrupt masks:\n", ci->ci_apicid);
for (i = 0; i < NIPL; i++)
printf("IPL %d mask %lx unmask %lx\n", i,
(u_long)ci->ci_imask[i], (u_long)ci->ci_iunmask[i]);
for (i = 0; i < MAX_INTR_SOURCES; i++) {
isp = ci->ci_isources[i];
if (isp == NULL)
continue;
printf("cpu%u source %d is pin %d from pic %s maxlevel %d\n",
ci->ci_apicid, i, isp->is_pin,
isp->is_pic->pic_name, isp->is_maxlevel);
for (ih = isp->is_handlers; ih != NULL;
ih = ih->ih_next)
printf("\thandler %p level %d\n",
ih->ih_fun, ih->ih_level);
}
}
#endif
}
void
intr_barrier(void *cookie)
{
struct intrhand *ih = cookie;
sched_barrier(ih->ih_cpu);
}
void
intr_set_wakeup(void *cookie)
{
struct intrhand *ih = cookie;
ih->ih_flags |= IPL_WAKEUP;
}
#ifdef SUSPEND
void
intr_enable_wakeup(void)
{
struct cpu_info *ci = curcpu();
struct pic *pic;
int irq, pin;
for (irq = 0; irq < MAX_INTR_SOURCES; irq++) {
if (ci->ci_isources[irq] == NULL)
continue;
if (ci->ci_isources[irq]->is_handlers->ih_flags & IPL_WAKEUP)
continue;
pic = ci->ci_isources[irq]->is_pic;
pin = ci->ci_isources[irq]->is_pin;
if (pic->pic_hwmask)
pic->pic_hwmask(pic, pin);
}
intr_suspended = 1;
}
void
intr_disable_wakeup(void)
{
struct cpu_info *ci = curcpu();
struct pic *pic;
int irq, pin;
intr_suspended = 0;
for (irq = 0; irq < MAX_INTR_SOURCES; irq++) {
if (ci->ci_isources[irq] == NULL)
continue;
if (ci->ci_isources[irq]->is_handlers->ih_flags & IPL_WAKEUP)
continue;
pic = ci->ci_isources[irq]->is_pic;
pin = ci->ci_isources[irq]->is_pin;
if (pic->pic_hwunmask)
pic->pic_hwunmask(pic, pin);
}
if (intr_nowake) {
printf("last non-wakeup interrupt: irq%d/%s\n",
*(int *)intr_nowake->ih_count.ec_data,
intr_nowake->ih_count.ec_name);
intr_nowake = NULL;
}
}
#endif
int
splraise(int nlevel)
{
int olevel;
struct cpu_info *ci = curcpu();
KASSERT(nlevel >= IPL_NONE);
olevel = ci->ci_ilevel;
ci->ci_ilevel = MAX(ci->ci_ilevel, nlevel);
return (olevel);
}
int
spllower(int nlevel)
{
int olevel;
struct cpu_info *ci = curcpu();
u_int64_t imask;
u_long flags;
imask = IUNMASK(ci, nlevel);
olevel = ci->ci_ilevel;
flags = intr_disable();
if (ci->ci_ipending & imask) {
Xspllower(nlevel);
} else {
ci->ci_ilevel = nlevel;
intr_restore(flags);
}
return (olevel);
}
void
softintr(int si_level)
{
struct cpu_info *ci = curcpu();
int sir = softintr_to_ssir[si_level];
__asm volatile("lock; orq %1, %0" :
"=m"(ci->ci_ipending) : "ir" (1UL << sir));
}
void
dosoftint(int si_level)
{
struct cpu_info *ci = curcpu();
int floor;
floor = ci->ci_handled_intr_level;
ci->ci_handled_intr_level = ci->ci_ilevel;
softintr_dispatch(si_level);
ci->ci_handled_intr_level = floor;
}