diff --git a/sys/i386/i386/intr_machdep.c b/sys/i386/i386/intr_machdep.c index d023a866f6cf..f7f51c04fd78 100644 --- a/sys/i386/i386/intr_machdep.c +++ b/sys/i386/i386/intr_machdep.c @@ -1,309 +1,320 @@ /*- * Copyright (c) 2003 John Baldwin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may 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$ */ /* * Machine dependent interrupt code for i386. For the i386, we have to * deal with different PICs. Thus, we use the passed in vector to lookup * an interrupt source associated with that vector. The interrupt source * describes which PIC the source belongs to and includes methods to handle * that source. */ #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DDB #include #endif #define MAX_STRAY_LOG 5 typedef void (*mask_fn)(uintptr_t vector); static int intrcnt_index; static struct intsrc *interrupt_sources[NUM_IO_INTS]; static struct mtx intr_table_lock; static void intr_init(void *__dummy); static void intrcnt_setname(const char *name, int index); static void intrcnt_updatename(struct intsrc *is); static void intrcnt_register(struct intsrc *is); /* * Register a new interrupt source with the global interrupt system. * The global interrupts need to be disabled when this function is * called. */ int intr_register_source(struct intsrc *isrc) { int error, vector; vector = isrc->is_pic->pic_vector(isrc); if (interrupt_sources[vector] != NULL) return (EEXIST); error = ithread_create(&isrc->is_ithread, (uintptr_t)isrc, 0, (mask_fn)isrc->is_pic->pic_disable_source, (mask_fn)isrc->is_pic->pic_enable_source, "irq%d:", vector); if (error) return (error); mtx_lock_spin(&intr_table_lock); if (interrupt_sources[vector] != NULL) { mtx_unlock_spin(&intr_table_lock); ithread_destroy(isrc->is_ithread); return (EEXIST); } intrcnt_register(isrc); interrupt_sources[vector] = isrc; mtx_unlock_spin(&intr_table_lock); return (0); } struct intsrc * intr_lookup_source(int vector) { return (interrupt_sources[vector]); } int intr_add_handler(const char *name, int vector, driver_intr_t handler, void *arg, enum intr_type flags, void **cookiep) { struct intsrc *isrc; int error; isrc = intr_lookup_source(vector); if (isrc == NULL) return (EINVAL); error = ithread_add_handler(isrc->is_ithread, name, handler, arg, ithread_priority(flags), flags, cookiep); if (error == 0) { intrcnt_updatename(isrc); isrc->is_pic->pic_enable_intr(isrc); isrc->is_pic->pic_enable_source(isrc); } return (error); } int intr_remove_handler(void *cookie) { int error; error = ithread_remove_handler(cookie); #ifdef XXX if (error == 0) intrcnt_updatename(/* XXX */); #endif return (error); } +int +intr_config_intr(int vector, enum intr_trigger trig, enum intr_polarity pol) +{ + struct intsrc *isrc; + + isrc = intr_lookup_source(vector); + if (isrc == NULL) + return (EINVAL); + return (isrc->is_pic->pic_config_intr(isrc, trig, pol)); +} + void intr_execute_handlers(struct intsrc *isrc, struct intrframe *iframe) { struct thread *td; struct ithd *it; struct intrhand *ih; int error, vector; td = curthread; td->td_intr_nesting_level++; /* * We count software interrupts when we process them. The * code here follows previous practice, but there's an * argument for counting hardware interrupts when they're * processed too. */ atomic_add_long(isrc->is_count, 1); atomic_add_int(&cnt.v_intr, 1); it = isrc->is_ithread; if (it == NULL) ih = NULL; else ih = TAILQ_FIRST(&it->it_handlers); /* * XXX: We assume that IRQ 0 is only used for the ISA timer * device (clk). */ vector = isrc->is_pic->pic_vector(isrc); if (vector == 0) clkintr_pending = 1; critical_enter(); if (ih != NULL && ih->ih_flags & IH_FAST) { /* * Execute fast interrupt handlers directly. * To support clock handlers, if a handler registers * with a NULL argument, then we pass it a pointer to * a trapframe as its argument. */ TAILQ_FOREACH(ih, &it->it_handlers, ih_next) { MPASS(ih->ih_flags & IH_FAST); CTR3(KTR_INTR, "%s: executing handler %p(%p)", __func__, ih->ih_handler, ih->ih_argument == NULL ? iframe : ih->ih_argument); if (ih->ih_argument == NULL) ih->ih_handler(iframe); else ih->ih_handler(ih->ih_argument); } isrc->is_pic->pic_eoi_source(isrc); error = 0; } else { /* * For stray and threaded interrupts, we mask and EOI the * source. */ isrc->is_pic->pic_disable_source(isrc); isrc->is_pic->pic_eoi_source(isrc); if (ih == NULL) error = EINVAL; else error = ithread_schedule(it, !cold); } critical_exit(); if (error == EINVAL) { atomic_add_long(isrc->is_straycount, 1); if (*isrc->is_straycount < MAX_STRAY_LOG) log(LOG_ERR, "stray irq%d\n", vector); else if (*isrc->is_straycount == MAX_STRAY_LOG) log(LOG_CRIT, "too many stray irq %d's: not logging anymore\n", vector); } td->td_intr_nesting_level--; } void intr_resume(void) { struct intsrc **isrc; int i; mtx_lock_spin(&intr_table_lock); for (i = 0, isrc = interrupt_sources; i < NUM_IO_INTS; i++, isrc++) if (*isrc != NULL && (*isrc)->is_pic->pic_resume != NULL) (*isrc)->is_pic->pic_resume(*isrc); mtx_unlock_spin(&intr_table_lock); } void intr_suspend(void) { struct intsrc **isrc; int i; mtx_lock_spin(&intr_table_lock); for (i = 0, isrc = interrupt_sources; i < NUM_IO_INTS; i++, isrc++) if (*isrc != NULL && (*isrc)->is_pic->pic_suspend != NULL) (*isrc)->is_pic->pic_suspend(*isrc); mtx_unlock_spin(&intr_table_lock); } static void intrcnt_setname(const char *name, int index) { snprintf(intrnames + (MAXCOMLEN + 1) * index, MAXCOMLEN + 1, "%-*s", MAXCOMLEN, name); } static void intrcnt_updatename(struct intsrc *is) { intrcnt_setname(is->is_ithread->it_td->td_proc->p_comm, is->is_index); } static void intrcnt_register(struct intsrc *is) { char straystr[MAXCOMLEN + 1]; /* mtx_assert(&intr_table_lock, MA_OWNED); */ KASSERT(is->is_ithread != NULL, ("%s: isrc with no ithread", __func__)); is->is_index = intrcnt_index; intrcnt_index += 2; snprintf(straystr, MAXCOMLEN + 1, "stray irq%d", is->is_pic->pic_vector(is)); intrcnt_updatename(is); is->is_count = &intrcnt[is->is_index]; intrcnt_setname(straystr, is->is_index + 1); is->is_straycount = &intrcnt[is->is_index + 1]; } static void intr_init(void *dummy __unused) { intrcnt_setname("???", 0); intrcnt_index = 1; mtx_init(&intr_table_lock, "intr table", NULL, MTX_SPIN); } SYSINIT(intr_init, SI_SUB_INTR, SI_ORDER_FIRST, intr_init, NULL) #ifdef DDB /* * Dump data about interrupt handlers */ DB_SHOW_COMMAND(irqs, db_show_irqs) { struct intsrc **isrc; int i, quit, verbose; quit = 0; if (strcmp(modif, "v") == 0) verbose = 1; else verbose = 0; isrc = interrupt_sources; db_setup_paging(db_simple_pager, &quit, DB_LINES_PER_PAGE); for (i = 0; i < NUM_IO_INTS && !quit; i++, isrc++) if (*isrc != NULL) db_dump_ithread((*isrc)->is_ithread, verbose); } #endif diff --git a/sys/i386/i386/io_apic.c b/sys/i386/i386/io_apic.c index b3e760e663fc..272ebaea0956 100644 --- a/sys/i386/i386/io_apic.c +++ b/sys/i386/i386/io_apic.c @@ -1,696 +1,734 @@ /*- * Copyright (c) 2003 John Baldwin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_isa.h" #include "opt_no_mixed_mode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(DEV_ISA) && !defined(NO_MIXED_MODE) #define MIXED_MODE #endif #define IOAPIC_ISA_INTS 16 #define IOAPIC_MEM_REGION 32 #define IOAPIC_REDTBL_LO(i) (IOAPIC_REDTBL + (i) * 2) #define IOAPIC_REDTBL_HI(i) (IOAPIC_REDTBL_LO(i) + 1) #define VECTOR_EXTINT 252 #define VECTOR_NMI 253 #define VECTOR_SMI 254 #define VECTOR_DISABLED 255 #define DEST_NONE -1 #define DEST_EXTINT -2 #define TODO printf("%s: not implemented!\n", __func__) MALLOC_DEFINE(M_IOAPIC, "I/O APIC", "I/O APIC structures"); /* * New interrupt support code.. * * XXX: we really should have the interrupt cookie passed up from new-bus * just be a int pin, and not map 1:1 to interrupt vector number but should * use INTR_TYPE_FOO to set priority bands for device classes and do all the * magic remapping of intpin to vector in here. For now we just cheat as on * ia64 and map intpin X to vector NRSVIDT + X. Note that we assume that the * first IO APIC has ISA interrupts on pins 1-15. Not sure how you are * really supposed to figure out which IO APIC in a system with multiple IO * APIC's actually has the ISA interrupts routed to it. As far as interrupt * pin numbers, we use the ACPI System Interrupt number model where each * IO APIC has a contiguous chunk of the System Interrupt address space. */ /* * Direct the ExtINT pin on the first I/O APIC to a logical cluster of * CPUs rather than a physical destination of just the BSP. * * Note: This is disabled by default as test systems seem to croak with it * enabled. #define ENABLE_EXTINT_LOGICAL_DESTINATION */ struct ioapic_intsrc { struct intsrc io_intsrc; u_int io_intpin:8; u_int io_vector:8; u_int io_activehi:1; u_int io_edgetrigger:1; u_int io_masked:1; int io_dest:5; }; struct ioapic { struct pic io_pic; u_int io_id:8; /* logical ID */ u_int io_apic_id:4; u_int io_intbase:8; /* System Interrupt base */ u_int io_numintr:8; volatile ioapic_t *io_addr; /* XXX: should use bus_space */ STAILQ_ENTRY(ioapic) io_next; struct ioapic_intsrc io_pins[0]; }; static STAILQ_HEAD(,ioapic) ioapic_list = STAILQ_HEAD_INITIALIZER(ioapic_list); static u_int next_id, program_logical_dest; static u_int ioapic_read(volatile ioapic_t *apic, int reg); static void ioapic_write(volatile ioapic_t *apic, int reg, u_int val); static void ioapic_enable_source(struct intsrc *isrc); static void ioapic_disable_source(struct intsrc *isrc); static void ioapic_eoi_source(struct intsrc *isrc); static void ioapic_enable_intr(struct intsrc *isrc); static int ioapic_vector(struct intsrc *isrc); static int ioapic_source_pending(struct intsrc *isrc); +static int ioapic_config_intr(struct intsrc *isrc, enum intr_trigger trig, + enum intr_polarity pol); static void ioapic_suspend(struct intsrc *isrc); static void ioapic_resume(struct intsrc *isrc); static void ioapic_program_destination(struct ioapic_intsrc *intpin); #ifdef MIXED_MODE static void ioapic_setup_mixed_mode(struct ioapic_intsrc *intpin); #endif struct pic ioapic_template = { ioapic_enable_source, ioapic_disable_source, ioapic_eoi_source, ioapic_enable_intr, ioapic_vector, ioapic_source_pending, - ioapic_suspend, ioapic_resume }; + ioapic_suspend, ioapic_resume, + ioapic_config_intr }; static int next_ioapic_base, logical_clusters, current_cluster; static u_int ioapic_read(volatile ioapic_t *apic, int reg) { mtx_assert(&icu_lock, MA_OWNED); apic->ioregsel = reg; return (apic->iowin); } static void ioapic_write(volatile ioapic_t *apic, int reg, u_int val) { mtx_assert(&icu_lock, MA_OWNED); apic->ioregsel = reg; apic->iowin = val; } static void ioapic_enable_source(struct intsrc *isrc) { struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc; struct ioapic *io = (struct ioapic *)isrc->is_pic; uint32_t flags; mtx_lock_spin(&icu_lock); if (intpin->io_masked) { flags = ioapic_read(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin)); flags &= ~(IOART_INTMASK); ioapic_write(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin), flags); intpin->io_masked = 0; } mtx_unlock_spin(&icu_lock); } static void ioapic_disable_source(struct intsrc *isrc) { struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc; struct ioapic *io = (struct ioapic *)isrc->is_pic; uint32_t flags; mtx_lock_spin(&icu_lock); if (!intpin->io_masked && !intpin->io_edgetrigger) { flags = ioapic_read(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin)); flags |= IOART_INTMSET; ioapic_write(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin), flags); intpin->io_masked = 1; } mtx_unlock_spin(&icu_lock); } static void ioapic_eoi_source(struct intsrc *isrc) { lapic_eoi(); } /* * Program an individual intpin's logical destination. */ static void ioapic_program_destination(struct ioapic_intsrc *intpin) { struct ioapic *io = (struct ioapic *)intpin->io_intsrc.is_pic; uint32_t value; KASSERT(intpin->io_dest != DEST_NONE, ("intpin not assigned to a cluster")); KASSERT(intpin->io_dest != DEST_EXTINT, ("intpin routed via ExtINT")); if (bootverbose) { printf("ioapic%u: routing intpin %u (", io->io_id, intpin->io_intpin); if (intpin->io_vector == VECTOR_EXTINT) printf("ExtINT"); else printf("IRQ %u", intpin->io_vector); printf(") to cluster %u\n", intpin->io_dest); } mtx_lock_spin(&icu_lock); value = ioapic_read(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin)); value &= ~IOART_DESTMOD; value |= IOART_DESTLOG; ioapic_write(io->io_addr, IOAPIC_REDTBL_LO(intpin->io_intpin), value); value = ioapic_read(io->io_addr, IOAPIC_REDTBL_HI(intpin->io_intpin)); value &= ~IOART_DEST; value |= (intpin->io_dest << APIC_ID_CLUSTER_SHIFT | APIC_ID_CLUSTER_ID) << APIC_ID_SHIFT; ioapic_write(io->io_addr, IOAPIC_REDTBL_HI(intpin->io_intpin), value); mtx_unlock_spin(&icu_lock); } static void ioapic_assign_cluster(struct ioapic_intsrc *intpin) { /* * Assign this intpin to a logical APIC cluster in a * round-robin fashion. We don't actually use the logical * destination for this intpin until after all the CPU's * have been started so that we don't end up with interrupts * that don't go anywhere. Another alternative might be to * start up the CPU's earlier so that they can handle interrupts * sooner. */ intpin->io_dest = current_cluster; current_cluster++; if (current_cluster >= logical_clusters) current_cluster = 0; if (program_logical_dest) ioapic_program_destination(intpin); } static void ioapic_enable_intr(struct intsrc *isrc) { struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc; KASSERT(intpin->io_dest != DEST_EXTINT, ("ExtINT pin trying to use ioapic enable_intr method")); if (intpin->io_dest == DEST_NONE) { ioapic_assign_cluster(intpin); lapic_enable_intr(intpin->io_vector); } } static int ioapic_vector(struct intsrc *isrc) { struct ioapic_intsrc *pin; pin = (struct ioapic_intsrc *)isrc; return (pin->io_vector); } static int ioapic_source_pending(struct intsrc *isrc) { struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc; return (lapic_intr_pending(intpin->io_vector)); } +static int +ioapic_config_intr(struct intsrc *isrc, enum intr_trigger trig, + enum intr_polarity pol) +{ + struct ioapic_intsrc *intpin = (struct ioapic_intsrc *)isrc; + struct ioapic *io = (struct ioapic *)isrc->is_pic; + + KASSERT(!(trig == INTR_TRIGGER_CONFORM || pol == INTR_POLARITY_CONFORM), + ("%s: Conforming trigger or polarity\n", __func__)); + + /* + * For now we ignore any requests but do output any changes that + * would be made to the console it bootverbose is enabled. The only + * known causes of these messages so far is a bug in acpi(4) that + * causes the ISA IRQs used for PCI interrupts in PIC mode to be + * set to level/low when they aren't being used. There are possibly + * legitimate requests, so at some point when the acpi(4) driver is + * fixed this code can be changed to actually change the intpin as + * requested. + */ + if (!bootverbose) + return (0); + if (intpin->io_edgetrigger != (trig == INTR_TRIGGER_EDGE)) + printf( + "ioapic%u: Request to change trigger for pin %u to %s ignored\n", + io->io_id, intpin->io_intpin, trig == INTR_TRIGGER_EDGE ? + "edge" : "level"); + if (intpin->io_activehi != (pol == INTR_POLARITY_HIGH)) + printf( + "ioapic%u: Request to change polarity for pin %u to %s ignored\n", + io->io_id, intpin->io_intpin, pol == INTR_POLARITY_HIGH ? + "high" : "low"); + return (0); +} + static void ioapic_suspend(struct intsrc *isrc) { TODO; } static void ioapic_resume(struct intsrc *isrc) { TODO; } /* * Allocate and return a logical cluster ID. Note that the first time * this is called, it returns cluster 0. ioapic_enable_intr() treats * the two cases of logical_clusters == 0 and logical_clusters == 1 the * same: one cluster of ID 0 exists. The logical_clusters == 0 case is * for UP kernels, which should never call this function. */ int ioapic_next_logical_cluster(void) { if (logical_clusters >= APIC_MAX_CLUSTER) panic("WARNING: Local APIC cluster IDs exhausted!"); return (logical_clusters++); } /* * Create a plain I/O APIC object. */ void * ioapic_create(uintptr_t addr, int32_t apic_id, int intbase) { struct ioapic *io; struct ioapic_intsrc *intpin; volatile ioapic_t *apic; u_int numintr, i; uint32_t value; apic = (ioapic_t *)pmap_mapdev(addr, IOAPIC_MEM_REGION); mtx_lock_spin(&icu_lock); numintr = ((ioapic_read(apic, IOAPIC_VER) & IOART_VER_MAXREDIR) >> MAXREDIRSHIFT) + 1; mtx_unlock_spin(&icu_lock); io = malloc(sizeof(struct ioapic) + numintr * sizeof(struct ioapic_intsrc), M_IOAPIC, M_WAITOK); io->io_pic = ioapic_template; mtx_lock_spin(&icu_lock); io->io_id = next_id++; io->io_apic_id = ioapic_read(apic, IOAPIC_ID) >> APIC_ID_SHIFT; if (apic_id != -1 && io->io_apic_id != apic_id) { ioapic_write(apic, IOAPIC_ID, apic_id << APIC_ID_SHIFT); mtx_unlock_spin(&icu_lock); io->io_apic_id = apic_id; printf("ioapic%u: Changing APIC ID to %d\n", io->io_id, apic_id); } else mtx_unlock_spin(&icu_lock); if (intbase == -1) { intbase = next_ioapic_base; printf("ioapic%u: Assuming intbase of %d\n", io->io_id, intbase); } else if (intbase != next_ioapic_base) printf("ioapic%u: WARNING: intbase %d != expected base %d\n", io->io_id, intbase, next_ioapic_base); io->io_intbase = intbase; next_ioapic_base = intbase + numintr; io->io_numintr = numintr; io->io_addr = apic; /* * Initialize pins. Start off with interrupts disabled. Default * to active-hi and edge-triggered for ISA interrupts and active-lo * and level-triggered for all others. */ bzero(io->io_pins, sizeof(struct ioapic_intsrc) * numintr); mtx_lock_spin(&icu_lock); for (i = 0, intpin = io->io_pins; i < numintr; i++, intpin++) { intpin->io_intsrc.is_pic = (struct pic *)io; intpin->io_intpin = i; intpin->io_vector = intbase + i; /* * Assume that pin 0 on the first IO APIC is an ExtINT pin by * default. Assume that intpins 1-15 are ISA interrupts and * use suitable defaults for those. Assume that all other * intpins are PCI interrupts. Enable the ExtINT pin by * default but mask all other pins. */ if (intpin->io_vector == 0) { intpin->io_activehi = 1; intpin->io_edgetrigger = 1; intpin->io_vector = VECTOR_EXTINT; intpin->io_masked = 0; } else if (intpin->io_vector < IOAPIC_ISA_INTS) { intpin->io_activehi = 1; intpin->io_edgetrigger = 1; intpin->io_masked = 1; } else { intpin->io_activehi = 0; intpin->io_edgetrigger = 0; intpin->io_masked = 1; } /* * Start off without a logical cluster destination until * the pin is enabled. */ intpin->io_dest = DEST_NONE; if (bootverbose) { printf("ioapic%u: intpin %d -> ", io->io_id, i); if (intpin->io_vector == VECTOR_EXTINT) printf("ExtINT"); else printf("irq %u", intpin->io_vector); printf(" (%s, active%s)\n", intpin->io_edgetrigger ? "edge" : "level", intpin->io_activehi ? "hi" : "lo"); } value = ioapic_read(apic, IOAPIC_REDTBL_LO(i)); ioapic_write(apic, IOAPIC_REDTBL_LO(i), value | IOART_INTMSET); } mtx_unlock_spin(&icu_lock); return (io); } int ioapic_get_vector(void *cookie, u_int pin) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr) return (-1); return (io->io_pins[pin].io_vector); } int ioapic_disable_pin(void *cookie, u_int pin) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr) return (EINVAL); if (io->io_pins[pin].io_vector == VECTOR_DISABLED) return (EINVAL); io->io_pins[pin].io_vector = VECTOR_DISABLED; if (bootverbose) printf("ioapic%u: intpin %d disabled\n", io->io_id, pin); return (0); } int ioapic_remap_vector(void *cookie, u_int pin, int vector) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr || vector < 0) return (EINVAL); if (io->io_pins[pin].io_vector >= NUM_IO_INTS) return (EINVAL); io->io_pins[pin].io_vector = vector; if (bootverbose) printf("ioapic%u: Routing IRQ %d -> intpin %d\n", io->io_id, vector, pin); return (0); } int ioapic_set_nmi(void *cookie, u_int pin) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr) return (EINVAL); if (io->io_pins[pin].io_vector >= NUM_IO_INTS) return (EINVAL); io->io_pins[pin].io_vector = VECTOR_NMI; io->io_pins[pin].io_masked = 0; io->io_pins[pin].io_edgetrigger = 1; io->io_pins[pin].io_activehi = 1; if (bootverbose) printf("ioapic%u: Routing NMI -> intpin %d\n", io->io_id, pin); return (0); } int ioapic_set_smi(void *cookie, u_int pin) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr) return (EINVAL); if (io->io_pins[pin].io_vector >= NUM_IO_INTS) return (EINVAL); io->io_pins[pin].io_vector = VECTOR_SMI; io->io_pins[pin].io_masked = 0; io->io_pins[pin].io_edgetrigger = 1; io->io_pins[pin].io_activehi = 1; if (bootverbose) printf("ioapic%u: Routing SMI -> intpin %d\n", io->io_id, pin); return (0); } int ioapic_set_extint(void *cookie, u_int pin) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr) return (EINVAL); if (io->io_pins[pin].io_vector >= NUM_IO_INTS) return (EINVAL); io->io_pins[pin].io_vector = VECTOR_EXTINT; io->io_pins[pin].io_masked = 0; io->io_pins[pin].io_edgetrigger = 1; io->io_pins[pin].io_activehi = 1; if (bootverbose) printf("ioapic%u: Routing external 8259A's -> intpin %d\n", io->io_id, pin); return (0); } int ioapic_set_polarity(void *cookie, u_int pin, enum intr_polarity pol) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr || pol == INTR_POLARITY_CONFORM) return (EINVAL); if (io->io_pins[pin].io_vector >= NUM_IO_INTS) return (EINVAL); io->io_pins[pin].io_activehi = (pol == INTR_POLARITY_HIGH); if (bootverbose) printf("ioapic%u: intpin %d polarity: %s\n", io->io_id, pin, pol == INTR_POLARITY_HIGH ? "high" : "low"); return (0); } int ioapic_set_triggermode(void *cookie, u_int pin, enum intr_trigger trigger) { struct ioapic *io; io = (struct ioapic *)cookie; if (pin >= io->io_numintr || trigger == INTR_TRIGGER_CONFORM) return (EINVAL); if (io->io_pins[pin].io_vector >= NUM_IO_INTS) return (EINVAL); io->io_pins[pin].io_edgetrigger = (trigger == INTR_TRIGGER_EDGE); if (bootverbose) printf("ioapic%u: intpin %d trigger: %s\n", io->io_id, pin, trigger == INTR_TRIGGER_EDGE ? "edge" : "level"); return (0); } /* * Register a complete I/O APIC object with the interrupt subsystem. */ void ioapic_register(void *cookie) { struct ioapic_intsrc *pin; struct ioapic *io; volatile ioapic_t *apic; uint32_t flags; int i; io = (struct ioapic *)cookie; apic = io->io_addr; mtx_lock_spin(&icu_lock); flags = ioapic_read(apic, IOAPIC_VER) & IOART_VER_VERSION; STAILQ_INSERT_TAIL(&ioapic_list, io, io_next); mtx_unlock_spin(&icu_lock); printf("ioapic%u irqs %u-%u on motherboard\n", io->io_id, flags >> 4, flags & 0xf, io->io_intbase, io->io_intbase + io->io_numintr - 1); for (i = 0, pin = io->io_pins; i < io->io_numintr; i++, pin++) { /* * Finish initializing the pins by programming the vectors * and delivery mode. */ if (pin->io_vector == VECTOR_DISABLED) continue; flags = IOART_DESTPHY; if (pin->io_edgetrigger) flags |= IOART_TRGREDG; else flags |= IOART_TRGRLVL; if (pin->io_activehi) flags |= IOART_INTAHI; else flags |= IOART_INTALO; if (pin->io_masked) flags |= IOART_INTMSET; switch (pin->io_vector) { case VECTOR_EXTINT: KASSERT(pin->io_edgetrigger, ("EXTINT not edge triggered")); flags |= IOART_DELEXINT; break; case VECTOR_NMI: KASSERT(pin->io_edgetrigger, ("NMI not edge triggered")); flags |= IOART_DELNMI; break; case VECTOR_SMI: KASSERT(pin->io_edgetrigger, ("SMI not edge triggered")); flags |= IOART_DELSMI; break; default: flags |= IOART_DELLOPRI | apic_irq_to_idt(pin->io_vector); } mtx_lock_spin(&icu_lock); ioapic_write(apic, IOAPIC_REDTBL_LO(i), flags); /* * Route interrupts to the BSP by default using physical * addressing. Vectored interrupts get readdressed using * logical IDs to CPU clusters when they are enabled. */ flags = ioapic_read(apic, IOAPIC_REDTBL_HI(i)); flags &= ~IOART_DEST; flags |= PCPU_GET(apic_id) << APIC_ID_SHIFT; ioapic_write(apic, IOAPIC_REDTBL_HI(i), flags); mtx_unlock_spin(&icu_lock); if (pin->io_vector < NUM_IO_INTS) { #ifdef MIXED_MODE /* Route IRQ0 via the 8259A using mixed mode. */ if (pin->io_vector == 0) ioapic_setup_mixed_mode(pin); else #endif intr_register_source(&pin->io_intsrc); } } } /* * Program all the intpins to use logical destinations once the AP's * have been launched. */ static void ioapic_set_logical_destinations(void *arg __unused) { struct ioapic *io; int i; program_logical_dest = 1; STAILQ_FOREACH(io, &ioapic_list, io_next) for (i = 0; i < io->io_numintr; i++) if (io->io_pins[i].io_dest != DEST_NONE && io->io_pins[i].io_dest != DEST_EXTINT) ioapic_program_destination(&io->io_pins[i]); } SYSINIT(ioapic_destinations, SI_SUB_SMP, SI_ORDER_SECOND, ioapic_set_logical_destinations, NULL) #ifdef MIXED_MODE /* * Support for mixed-mode interrupt sources. These sources route an ISA * IRQ through the 8259A's via the ExtINT on pin 0 of the I/O APIC that * routes the ISA interrupts. We just ignore the intpins that use this * mode and allow the atpic driver to register its interrupt source for * that IRQ instead. */ void ioapic_setup_mixed_mode(struct ioapic_intsrc *intpin) { struct ioapic_intsrc *extint; struct ioapic *io; /* * Mark the associated I/O APIC intpin as being delivered via * ExtINT and enable the ExtINT pin on the I/O APIC if needed. */ intpin->io_dest = DEST_EXTINT; io = (struct ioapic *)intpin->io_intsrc.is_pic; extint = &io->io_pins[0]; if (extint->io_vector != VECTOR_EXTINT) panic("Can't find ExtINT pin to route through!"); #ifdef ENABLE_EXTINT_LOGICAL_DESTINATION if (extint->io_dest == DEST_NONE) ioapic_assign_cluster(extint); #endif } #endif /* MIXED_MODE */ diff --git a/sys/i386/i386/nexus.c b/sys/i386/i386/nexus.c index ecfd035ff1be..818da86e6be4 100644 --- a/sys/i386/i386/nexus.c +++ b/sys/i386/i386/nexus.c @@ -1,580 +1,590 @@ /*- * Copyright 1998 Massachusetts Institute of Technology * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that both the above copyright notice and this * permission notice appear in all copies, that both the above * copyright notice and this permission notice appear in all * supporting documentation, and that the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. M.I.T. makes * no representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT * SHALL M.I.T. 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. */ #include __FBSDID("$FreeBSD$"); /* * This code implements a `root nexus' for Intel Architecture * machines. The function of the root nexus is to serve as an * attachment point for both processors and buses, and to manage * resources which are common to all of them. In particular, * this code implements the core resource managers for interrupt * requests, DMA requests (which rightfully should be a part of the * ISA code but it's easier to do it here for now), I/O port addresses, * and I/O memory address space. */ #include "opt_isa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEV_ISA #include #ifdef PC98 #include #else #include #endif #endif #include static MALLOC_DEFINE(M_NEXUSDEV, "nexusdev", "Nexus device"); struct nexus_device { struct resource_list nx_resources; }; #define DEVTONX(dev) ((struct nexus_device *)device_get_ivars(dev)) static struct rman irq_rman, drq_rman, port_rman, mem_rman; static int nexus_probe(device_t); static int nexus_attach(device_t); static int nexus_print_all_resources(device_t dev); static int nexus_print_child(device_t, device_t); static device_t nexus_add_child(device_t bus, int order, const char *name, int unit); static struct resource *nexus_alloc_resource(device_t, device_t, int, int *, u_long, u_long, u_long, u_int); +static int nexus_config_intr(device_t, int, enum intr_trigger, + enum intr_polarity); static int nexus_activate_resource(device_t, device_t, int, int, struct resource *); static int nexus_deactivate_resource(device_t, device_t, int, int, struct resource *); static int nexus_release_resource(device_t, device_t, int, int, struct resource *); static int nexus_setup_intr(device_t, device_t, struct resource *, int flags, void (*)(void *), void *, void **); static int nexus_teardown_intr(device_t, device_t, struct resource *, void *); static int nexus_set_resource(device_t, device_t, int, int, u_long, u_long); static int nexus_get_resource(device_t, device_t, int, int, u_long *, u_long *); static void nexus_delete_resource(device_t, device_t, int, int); static device_method_t nexus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nexus_probe), DEVMETHOD(device_attach, nexus_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, nexus_print_child), DEVMETHOD(bus_add_child, nexus_add_child), DEVMETHOD(bus_alloc_resource, nexus_alloc_resource), DEVMETHOD(bus_release_resource, nexus_release_resource), DEVMETHOD(bus_activate_resource, nexus_activate_resource), DEVMETHOD(bus_deactivate_resource, nexus_deactivate_resource), DEVMETHOD(bus_setup_intr, nexus_setup_intr), DEVMETHOD(bus_teardown_intr, nexus_teardown_intr), + DEVMETHOD(bus_config_intr, nexus_config_intr), DEVMETHOD(bus_set_resource, nexus_set_resource), DEVMETHOD(bus_get_resource, nexus_get_resource), DEVMETHOD(bus_delete_resource, nexus_delete_resource), { 0, 0 } }; static driver_t nexus_driver = { "nexus", nexus_methods, 1, /* no softc */ }; static devclass_t nexus_devclass; DRIVER_MODULE(nexus, root, nexus_driver, nexus_devclass, 0, 0); static int nexus_probe(device_t dev) { int irq, last; device_quiet(dev); /* suppress attach message for neatness */ /* * XXX working notes: * * - IRQ resource creation should be moved to the PIC/APIC driver. * - DRQ resource creation should be moved to the DMAC driver. * - The above should be sorted to probe earlier than any child busses. * * - Leave I/O and memory creation here, as child probes may need them. * (especially eg. ACPI) */ /* * IRQ's are on the mainboard on old systems, but on the ISA part * of PCI->ISA bridges. There would be multiple sets of IRQs on * multi-ISA-bus systems. PCI interrupts are routed to the ISA * component, so in a way, PCI can be a partial child of an ISA bus(!). * APIC interrupts are global though. */ irq_rman.rm_start = 0; irq_rman.rm_type = RMAN_ARRAY; irq_rman.rm_descr = "Interrupt request lines"; irq_rman.rm_end = NUM_IO_INTS - 1; if (rman_init(&irq_rman)) panic("nexus_probe irq_rman"); /* * We search for regions of existing IRQs and add those to the IRQ * resource manager. */ last = -1; for (irq = 0; irq < NUM_IO_INTS; irq++) if (intr_lookup_source(irq) != NULL) { if (last == -1) last = irq; } else if (last != -1) { if (rman_manage_region(&irq_rman, last, irq - 1) != 0) panic("nexus_probe irq_rman add"); last = -1; } if (last != -1 && rman_manage_region(&irq_rman, last, irq - 1) != 0) panic("nexus_probe irq_rman add"); /* * ISA DMA on PCI systems is implemented in the ISA part of each * PCI->ISA bridge and the channels can be duplicated if there are * multiple bridges. (eg: laptops with docking stations) */ drq_rman.rm_start = 0; #ifdef PC98 drq_rman.rm_end = 3; #else drq_rman.rm_end = 7; #endif drq_rman.rm_type = RMAN_ARRAY; drq_rman.rm_descr = "DMA request lines"; /* XXX drq 0 not available on some machines */ if (rman_init(&drq_rman) || rman_manage_region(&drq_rman, drq_rman.rm_start, drq_rman.rm_end)) panic("nexus_probe drq_rman"); /* * However, IO ports and Memory truely are global at this level, * as are APIC interrupts (however many IO APICS there turn out * to be on large systems..) */ port_rman.rm_start = 0; port_rman.rm_end = 0xffff; port_rman.rm_type = RMAN_ARRAY; port_rman.rm_descr = "I/O ports"; if (rman_init(&port_rman) || rman_manage_region(&port_rman, 0, 0xffff)) panic("nexus_probe port_rman"); mem_rman.rm_start = 0; mem_rman.rm_end = ~0u; mem_rman.rm_type = RMAN_ARRAY; mem_rman.rm_descr = "I/O memory addresses"; if (rman_init(&mem_rman) || rman_manage_region(&mem_rman, 0, ~0)) panic("nexus_probe mem_rman"); return 0; } static int nexus_attach(device_t dev) { bus_generic_probe(dev); bus_generic_attach(dev); return 0; } static int nexus_print_all_resources(device_t dev) { struct nexus_device *ndev = DEVTONX(dev); struct resource_list *rl = &ndev->nx_resources; int retval = 0; if (SLIST_FIRST(rl)) retval += printf(" at"); retval += resource_list_print_type(rl, "port", SYS_RES_IOPORT, "%#lx"); retval += resource_list_print_type(rl, "iomem", SYS_RES_MEMORY, "%#lx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%ld"); return retval; } static int nexus_print_child(device_t bus, device_t child) { int retval = 0; retval += bus_print_child_header(bus, child); retval += nexus_print_all_resources(child); retval += printf(" on motherboard\n"); /* XXX "motherboard", ick */ return (retval); } static device_t nexus_add_child(device_t bus, int order, const char *name, int unit) { device_t child; struct nexus_device *ndev; ndev = malloc(sizeof(struct nexus_device), M_NEXUSDEV, M_NOWAIT|M_ZERO); if (!ndev) return(0); resource_list_init(&ndev->nx_resources); child = device_add_child_ordered(bus, order, name, unit); /* should we free this in nexus_child_detached? */ device_set_ivars(child, ndev); return(child); } /* * Allocate a resource on behalf of child. NB: child is usually going to be a * child of one of our descendants, not a direct child of nexus0. * (Exceptions include npx.) */ static struct resource * nexus_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct nexus_device *ndev = DEVTONX(child); struct resource *rv; struct resource_list_entry *rle; struct rman *rm; int needactivate = flags & RF_ACTIVE; /* * If this is an allocation of the "default" range for a given RID, and * we know what the resources for this device are (ie. they aren't maintained * by a child bus), then work out the start/end values. */ if ((start == 0UL) && (end == ~0UL) && (count == 1)) { if (ndev == NULL) return(NULL); rle = resource_list_find(&ndev->nx_resources, type, *rid); if (rle == NULL) return(NULL); start = rle->start; end = rle->end; count = rle->count; } flags &= ~RF_ACTIVE; switch (type) { case SYS_RES_IRQ: rm = &irq_rman; break; case SYS_RES_DRQ: rm = &drq_rman; break; case SYS_RES_IOPORT: rm = &port_rman; break; case SYS_RES_MEMORY: rm = &mem_rman; break; default: return 0; } rv = rman_reserve_resource(rm, start, end, count, flags, child); if (rv == 0) return 0; if (type == SYS_RES_MEMORY) { rman_set_bustag(rv, I386_BUS_SPACE_MEM); } else if (type == SYS_RES_IOPORT) { rman_set_bustag(rv, I386_BUS_SPACE_IO); #ifndef PC98 rman_set_bushandle(rv, rv->r_start); #endif } #ifdef PC98 if ((type == SYS_RES_MEMORY || type == SYS_RES_IOPORT) && i386_bus_space_handle_alloc(rv->r_bustag, rv->r_start, count, &rv->r_bushandle) != 0) { rman_release_resource(rv); return 0; } #endif if (needactivate) { if (bus_activate_resource(child, type, *rid, rv)) { #ifdef PC98 if (type == SYS_RES_MEMORY || type == SYS_RES_IOPORT) { i386_bus_space_handle_free(rv->r_bustag, rv->r_bushandle, rv->r_bushandle->bsh_sz); } #endif rman_release_resource(rv); return 0; } } return rv; } static int nexus_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { /* * If this is a memory resource, map it into the kernel. */ if (rman_get_bustag(r) == I386_BUS_SPACE_MEM) { caddr_t vaddr = 0; if (rman_get_end(r) < 1024 * 1024) { /* * The first 1Mb is mapped at KERNBASE. */ vaddr = (caddr_t)(uintptr_t)(KERNBASE + rman_get_start(r)); } else { u_int32_t paddr; u_int32_t psize; u_int32_t poffs; paddr = rman_get_start(r); psize = rman_get_size(r); poffs = paddr - trunc_page(paddr); vaddr = (caddr_t) pmap_mapdev(paddr-poffs, psize+poffs) + poffs; } rman_set_virtual(r, vaddr); #ifdef PC98 /* PC-98: the type of bus_space_handle_t is the structure. */ r->r_bushandle->bsh_base = (bus_addr_t) vaddr; #else /* IBM-PC: the type of bus_space_handle_t is u_int */ rman_set_bushandle(r, (bus_space_handle_t) vaddr); #endif } return (rman_activate_resource(r)); } static int nexus_deactivate_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { /* * If this is a memory resource, unmap it. */ if ((rman_get_bustag(r) == I386_BUS_SPACE_MEM) && (rman_get_end(r) >= 1024 * 1024)) { u_int32_t psize; psize = rman_get_size(r); pmap_unmapdev((vm_offset_t)rman_get_virtual(r), psize); } return (rman_deactivate_resource(r)); } static int nexus_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { if (rman_get_flags(r) & RF_ACTIVE) { int error = bus_deactivate_resource(child, type, rid, r); if (error) return error; } #ifdef PC98 if (type == SYS_RES_MEMORY || type == SYS_RES_IOPORT) { i386_bus_space_handle_free(r->r_bustag, r->r_bushandle, r->r_bushandle->bsh_sz); } #endif return (rman_release_resource(r)); } /* * Currently this uses the really grody interface from kern/kern_intr.c * (which really doesn't belong in kern/anything.c). Eventually, all of * the code in kern_intr.c and machdep_intr.c should get moved here, since * this is going to be the official interface. */ static int nexus_setup_intr(device_t bus, device_t child, struct resource *irq, int flags, void (*ihand)(void *), void *arg, void **cookiep) { int error; /* somebody tried to setup an irq that failed to allocate! */ if (irq == NULL) panic("nexus_setup_intr: NULL irq resource!"); *cookiep = 0; if ((irq->r_flags & RF_SHAREABLE) == 0) flags |= INTR_EXCL; /* * We depend here on rman_activate_resource() being idempotent. */ error = rman_activate_resource(irq); if (error) return (error); error = intr_add_handler(device_get_nameunit(child), irq->r_start, ihand, arg, flags, cookiep); return (error); } static int nexus_teardown_intr(device_t dev, device_t child, struct resource *r, void *ih) { return (intr_remove_handler(ih)); } +static int +nexus_config_intr(device_t dev, int irq, enum intr_trigger trig, + enum intr_polarity pol) +{ + return (intr_config_intr(irq, trig, pol)); +} + static int nexus_set_resource(device_t dev, device_t child, int type, int rid, u_long start, u_long count) { struct nexus_device *ndev = DEVTONX(child); struct resource_list *rl = &ndev->nx_resources; /* XXX this should return a success/failure indicator */ resource_list_add(rl, type, rid, start, start + count - 1, count); return(0); } static int nexus_get_resource(device_t dev, device_t child, int type, int rid, u_long *startp, u_long *countp) { struct nexus_device *ndev = DEVTONX(child); struct resource_list *rl = &ndev->nx_resources; struct resource_list_entry *rle; rle = resource_list_find(rl, type, rid); device_printf(child, "type %d rid %d startp %p countp %p - got %p\n", type, rid, startp, countp, rle); if (!rle) return(ENOENT); if (startp) *startp = rle->start; if (countp) *countp = rle->count; return(0); } static void nexus_delete_resource(device_t dev, device_t child, int type, int rid) { struct nexus_device *ndev = DEVTONX(child); struct resource_list *rl = &ndev->nx_resources; resource_list_delete(rl, type, rid); } #ifdef DEV_ISA /* * Placeholder which claims PnP 'devices' which describe system * resources. */ static struct isa_pnp_id sysresource_ids[] = { { 0x010cd041 /* PNP0c01 */, "System Memory" }, { 0x020cd041 /* PNP0c02 */, "System Resource" }, { 0 } }; static int sysresource_probe(device_t dev) { int result; if ((result = ISA_PNP_PROBE(device_get_parent(dev), dev, sysresource_ids)) <= 0) { device_quiet(dev); } return(result); } static int sysresource_attach(device_t dev) { return(0); } static device_method_t sysresource_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sysresource_probe), DEVMETHOD(device_attach, sysresource_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), { 0, 0 } }; static driver_t sysresource_driver = { "sysresource", sysresource_methods, 1, /* no softc */ }; static devclass_t sysresource_devclass; DRIVER_MODULE(sysresource, isa, sysresource_driver, sysresource_devclass, 0, 0); #endif /* DEV_ISA */ diff --git a/sys/i386/include/intr_machdep.h b/sys/i386/include/intr_machdep.h index 6794751d4c53..d8256c98de20 100644 --- a/sys/i386/include/intr_machdep.h +++ b/sys/i386/include/intr_machdep.h @@ -1,96 +1,100 @@ /*- * Copyright (c) 2003 John Baldwin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 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$ */ #ifndef __MACHINE_INTR_MACHDEP_H__ #define __MACHINE_INTR_MACHDEP_H__ #ifdef _KERNEL /* With I/O APIC's we can have up to 191 interrupts. */ #define NUM_IO_INTS 191 #define INTRCNT_COUNT (1 + NUM_IO_INTS * 2) #ifndef LOCORE typedef void inthand_t(u_int cs, u_int ef, u_int esp, u_int ss); #define IDTVEC(name) __CONCAT(X,name) struct intsrc; /* * Methods that a PIC provides to mask/unmask a given interrupt source, * "turn on" the interrupt on the CPU side by setting up an IDT entry, and * return the vector associated with this source. */ struct pic { void (*pic_enable_source)(struct intsrc *); void (*pic_disable_source)(struct intsrc *); void (*pic_eoi_source)(struct intsrc *); void (*pic_enable_intr)(struct intsrc *); int (*pic_vector)(struct intsrc *); int (*pic_source_pending)(struct intsrc *); void (*pic_suspend)(struct intsrc *); void (*pic_resume)(struct intsrc *); + int (*pic_config_intr)(struct intsrc *, enum intr_trigger, + enum intr_polarity); }; /* * An interrupt source. The upper-layer code uses the PIC methods to * control a given source. The lower-layer PIC drivers can store additional * private data in a given interrupt source such as an interrupt pin number * or an I/O APIC pointer. */ struct intsrc { struct pic *is_pic; struct ithd *is_ithread; u_long *is_count; u_long *is_straycount; u_int is_index; }; struct intrframe; extern struct mtx icu_lock; /* XXX: The elcr_* prototypes probably belong somewhere else. */ int elcr_probe(void); enum intr_trigger elcr_read_trigger(u_int irq); void elcr_resume(void); void elcr_write_trigger(u_int irq, enum intr_trigger trigger); int intr_add_handler(const char *name, int vector, driver_intr_t handler, void *arg, enum intr_type flags, void **cookiep); +int intr_config_intr(int vector, enum intr_trigger trig, + enum intr_polarity pol); void intr_execute_handlers(struct intsrc *isrc, struct intrframe *iframe); struct intsrc *intr_lookup_source(int vector); int intr_register_source(struct intsrc *isrc); int intr_remove_handler(void *cookie); void intr_resume(void); void intr_suspend(void); #endif /* !LOCORE */ #endif /* _KERNEL */ #endif /* !__MACHINE_INTR_MACHDEP_H__ */ diff --git a/sys/i386/isa/atpic.c b/sys/i386/isa/atpic.c index 470783aa3538..7567c917fc13 100644 --- a/sys/i386/isa/atpic.c +++ b/sys/i386/isa/atpic.c @@ -1,515 +1,574 @@ /*- * Copyright (c) 2003 John Baldwin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may 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. */ /* * PIC driver for the 8259A Master and Slave PICs in PC/AT machines. */ #include __FBSDID("$FreeBSD$"); #include "opt_auto_eoi.h" #include "opt_isa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PC98 #include #else #include #endif #include #define MASTER 0 #define SLAVE 1 /* * Determine the base master and slave modes not including auto EOI support. * All machines that FreeBSD supports use 8086 mode. */ #ifdef PC98 /* * PC-98 machines do not support auto EOI on the second PIC. Also, it * seems that PC-98 machine PICs use buffered mode, and the master PIC * uses special fully nested mode. */ #define BASE_MASTER_MODE (ICW4_SFNM | ICW4_BUF | ICW4_MS | ICW4_8086) #define BASE_SLAVE_MODE (ICW4_BUF | ICW4_8086) #else #define BASE_MASTER_MODE ICW4_8086 #define BASE_SLAVE_MODE ICW4_8086 #endif /* Enable automatic EOI if requested. */ #ifdef AUTO_EOI_1 #define MASTER_MODE (BASE_MASTER_MODE | ICW4_AEOI) #else #define MASTER_MODE BASE_MASTER_MODE #endif #ifdef AUTO_EOI_2 #define SLAVE_MODE (BASE_SLAVE_MODE | ICW4_AEOI) #else #define SLAVE_MODE BASE_SLAVE_MODE #endif #define IMEN_MASK(ai) (1 << (ai)->at_irq) #define NUM_ISA_IRQS 16 static void atpic_init(void *dummy); unsigned int imen; /* XXX */ static int using_elcr; inthand_t IDTVEC(atpic_intr0), IDTVEC(atpic_intr1), IDTVEC(atpic_intr2), IDTVEC(atpic_intr3), IDTVEC(atpic_intr4), IDTVEC(atpic_intr5), IDTVEC(atpic_intr6), IDTVEC(atpic_intr7), IDTVEC(atpic_intr8), IDTVEC(atpic_intr9), IDTVEC(atpic_intr10), IDTVEC(atpic_intr11), IDTVEC(atpic_intr12), IDTVEC(atpic_intr13), IDTVEC(atpic_intr14), IDTVEC(atpic_intr15); #define IRQ(ap, ai) ((ap)->at_irqbase + (ai)->at_irq) #define ATPIC(io, base, eoi, imenptr) \ { { atpic_enable_source, atpic_disable_source, (eoi), \ atpic_enable_intr, atpic_vector, atpic_source_pending, NULL, \ - atpic_resume }, (io), (base), IDT_IO_INTS + (base), (imenptr) } + atpic_resume, atpic_config_intr }, (io), (base), \ + IDT_IO_INTS + (base), (imenptr) } #define INTSRC(irq) \ { { &atpics[(irq) / 8].at_pic }, IDTVEC(atpic_intr ## irq ), \ (irq) % 8 } struct atpic { struct pic at_pic; int at_ioaddr; int at_irqbase; uint8_t at_intbase; uint8_t *at_imen; }; struct atpic_intsrc { struct intsrc at_intsrc; inthand_t *at_intr; int at_irq; /* Relative to PIC base. */ enum intr_trigger at_trigger; u_long at_count; u_long at_straycount; }; static void atpic_enable_source(struct intsrc *isrc); static void atpic_disable_source(struct intsrc *isrc); static void atpic_eoi_master(struct intsrc *isrc); static void atpic_eoi_slave(struct intsrc *isrc); static void atpic_enable_intr(struct intsrc *isrc); static int atpic_vector(struct intsrc *isrc); static void atpic_resume(struct intsrc *isrc); static int atpic_source_pending(struct intsrc *isrc); +static int atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig, + enum intr_polarity pol); static void i8259_init(struct atpic *pic, int slave); static struct atpic atpics[] = { ATPIC(IO_ICU1, 0, atpic_eoi_master, (uint8_t *)&imen), ATPIC(IO_ICU2, 8, atpic_eoi_slave, ((uint8_t *)&imen) + 1) }; static struct atpic_intsrc atintrs[] = { INTSRC(0), INTSRC(1), INTSRC(2), INTSRC(3), INTSRC(4), INTSRC(5), INTSRC(6), INTSRC(7), INTSRC(8), INTSRC(9), INTSRC(10), INTSRC(11), INTSRC(12), INTSRC(13), INTSRC(14), INTSRC(15), }; CTASSERT(sizeof(atintrs) / sizeof(struct atpic_intsrc) == NUM_ISA_IRQS); static void atpic_enable_source(struct intsrc *isrc) { struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc; struct atpic *ap = (struct atpic *)isrc->is_pic; mtx_lock_spin(&icu_lock); if (*ap->at_imen & IMEN_MASK(ai)) { *ap->at_imen &= ~IMEN_MASK(ai); outb(ap->at_ioaddr + ICU_IMR_OFFSET, *ap->at_imen); } mtx_unlock_spin(&icu_lock); } static void atpic_disable_source(struct intsrc *isrc) { struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc; struct atpic *ap = (struct atpic *)isrc->is_pic; if (ai->at_trigger == INTR_TRIGGER_EDGE) return; mtx_lock_spin(&icu_lock); *ap->at_imen |= IMEN_MASK(ai); outb(ap->at_ioaddr + ICU_IMR_OFFSET, *ap->at_imen); mtx_unlock_spin(&icu_lock); } static void atpic_eoi_master(struct intsrc *isrc) { KASSERT(isrc->is_pic == &atpics[MASTER].at_pic, ("%s: mismatched pic", __func__)); #ifndef AUTO_EOI_1 mtx_lock_spin(&icu_lock); outb(atpics[MASTER].at_ioaddr, ICU_EOI); mtx_unlock_spin(&icu_lock); #endif } /* * The data sheet says no auto-EOI on slave, but it sometimes works. * So, if AUTO_EOI_2 is enabled, we use it. */ static void atpic_eoi_slave(struct intsrc *isrc) { KASSERT(isrc->is_pic == &atpics[SLAVE].at_pic, ("%s: mismatched pic", __func__)); #ifndef AUTO_EOI_2 mtx_lock_spin(&icu_lock); outb(atpics[SLAVE].at_ioaddr, ICU_EOI); #ifndef AUTO_EOI_1 outb(atpics[MASTER].at_ioaddr, ICU_EOI); #endif mtx_unlock_spin(&icu_lock); #endif } static void atpic_enable_intr(struct intsrc *isrc) { } static int atpic_vector(struct intsrc *isrc) { struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc; struct atpic *ap = (struct atpic *)isrc->is_pic; return (IRQ(ap, ai)); } static int atpic_source_pending(struct intsrc *isrc) { struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc; struct atpic *ap = (struct atpic *)isrc->is_pic; return (inb(ap->at_ioaddr) & IMEN_MASK(ai)); } static void atpic_resume(struct intsrc *isrc) { struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc; struct atpic *ap = (struct atpic *)isrc->is_pic; if (ai->at_irq == 0) { i8259_init(ap, ap == &atpics[SLAVE]); if (ap == &atpics[SLAVE] && using_elcr) elcr_resume(); } } +static int +atpic_config_intr(struct intsrc *isrc, enum intr_trigger trig, + enum intr_polarity pol) +{ + struct atpic_intsrc *ai = (struct atpic_intsrc *)isrc; + u_int vector; + + /* Map conforming values to edge/hi and sanity check the values. */ + if (trig == INTR_TRIGGER_CONFORM) + trig = INTR_TRIGGER_EDGE; + if (pol == INTR_POLARITY_CONFORM) + pol = INTR_POLARITY_HIGH; + vector = atpic_vector(isrc); + if ((trig == INTR_TRIGGER_EDGE && pol == INTR_POLARITY_LOW) || + (trig == INTR_TRIGGER_LEVEL && pol == INTR_POLARITY_HIGH)) { + printf( + "atpic: Mismatched config for IRQ%u: trigger %s, polarity %s\n", + vector, trig == INTR_TRIGGER_EDGE ? "edge" : "level", + pol == INTR_POLARITY_HIGH ? "high" : "low"); + return (EINVAL); + } + + /* If there is no change, just return. */ + if (ai->at_trigger == trig) + return (0); + + /* + * Certain IRQs can never be level/lo, so don't try to set them + * that way if asked. At least some ELCR registers ignore setting + * these bits as well. + */ + if ((vector == 0 || vector == 1 || vector == 2 || vector == 13) && + trig == INTR_TRIGGER_LEVEL) { + if (bootverbose) + printf( + "atpic: Ignoring invalid level/low configuration for IRQ%u\n", + vector); + return (EINVAL); + } + if (!using_elcr) { + if (bootverbose) + printf("atpic: No ELCR to configure IRQ%u as %s\n", + vector, trig == INTR_TRIGGER_EDGE ? "edge/high" : + "level/low"); + return (ENXIO); + } + if (bootverbose) + printf("atpic: Programming IRQ%u as %s\n", vector, + trig == INTR_TRIGGER_EDGE ? "edge/high" : "level/low"); + mtx_lock_spin(&icu_lock); + elcr_write_trigger(atpic_vector(isrc), trig); + ai->at_trigger = trig; + mtx_unlock_spin(&icu_lock); + return (0); +} + static void i8259_init(struct atpic *pic, int slave) { int imr_addr; /* Reset the PIC and program with next four bytes. */ mtx_lock_spin(&icu_lock); #ifdef DEV_MCA /* MCA uses level triggered interrupts. */ if (MCA_system) outb(pic->at_ioaddr, ICW1_RESET | ICW1_IC4 | ICW1_LTIM); else #endif outb(pic->at_ioaddr, ICW1_RESET | ICW1_IC4); imr_addr = pic->at_ioaddr + ICU_IMR_OFFSET; /* Start vector. */ outb(imr_addr, pic->at_intbase); /* * Setup slave links. For the master pic, indicate what line * the slave is configured on. For the slave indicate * which line on the master we are connected to. */ if (slave) outb(imr_addr, ICU_SLAVEID); /* my slave id is 7 */ else outb(imr_addr, IRQ_SLAVE); /* slave on line 7 */ /* Set mode. */ if (slave) outb(imr_addr, SLAVE_MODE); else outb(imr_addr, MASTER_MODE); /* Set interrupt enable mask. */ outb(imr_addr, *pic->at_imen); /* Reset is finished, default to IRR on read. */ outb(pic->at_ioaddr, OCW3_SEL | OCW3_RR); #ifndef PC98 /* OCW2_L1 sets priority order to 3-7, 0-2 (com2 first). */ if (!slave) outb(pic->at_ioaddr, OCW2_R | OCW2_SL | OCW2_L1); #endif mtx_unlock_spin(&icu_lock); } void atpic_startup(void) { struct atpic_intsrc *ai; int i; /* Start off with all interrupts disabled. */ imen = 0xffff; i8259_init(&atpics[MASTER], 0); i8259_init(&atpics[SLAVE], 1); atpic_enable_source((struct intsrc *)&atintrs[ICU_SLAVEID]); /* Install low-level interrupt handlers for all of our IRQs. */ for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) { if (i == ICU_SLAVEID) continue; ai->at_intsrc.is_count = &ai->at_count; ai->at_intsrc.is_straycount = &ai->at_straycount; setidt(((struct atpic *)ai->at_intsrc.is_pic)->at_intbase + ai->at_irq, ai->at_intr, SDT_SYS386IGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); } #ifdef DEV_MCA /* For MCA systems, all interrupts are level triggered. */ if (MCA_system) for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) ai->at_trigger = INTR_TRIGGER_LEVEL; else #endif /* * Look for an ELCR. If we find one, update the trigger modes. * If we don't find one, assume that IRQs 0, 1, 2, and 13 are * edge triggered and that everything else is level triggered. * We only use the trigger information to reprogram the ELCR if * we have one and as an optimization to avoid masking edge * triggered interrupts. For the case that we don't have an ELCR, * it doesn't hurt to mask an edge triggered interrupt, so we * that is why we assume level trigger for any interrupt that we * aren't sure is edge triggered. */ if (elcr_probe() == 0) { using_elcr = 1; for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) ai->at_trigger = elcr_read_trigger(i); } else { for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) switch (i) { case 0: case 1: case 2: case 8: case 13: ai->at_trigger = INTR_TRIGGER_EDGE; break; default: ai->at_trigger = INTR_TRIGGER_LEVEL; break; } } } static void atpic_init(void *dummy __unused) { struct atpic_intsrc *ai; int i; /* Loop through all interrupt sources and add them. */ for (i = 0, ai = atintrs; i < NUM_ISA_IRQS; i++, ai++) { if (i == ICU_SLAVEID) continue; intr_register_source(&ai->at_intsrc); } } SYSINIT(atpic_init, SI_SUB_INTR, SI_ORDER_SECOND + 1, atpic_init, NULL) void atpic_handle_intr(struct intrframe iframe) { struct intsrc *isrc; KASSERT((u_int)iframe.if_vec < ICU_LEN, ("unknown int %d\n", iframe.if_vec)); isrc = &atintrs[iframe.if_vec].at_intsrc; /* * If we don't have an ithread, see if this is a spurious * interrupt. */ if (isrc->is_ithread == NULL && (iframe.if_vec == 7 || iframe.if_vec == 15)) { int port, isr; /* * Read the ISR register to see if IRQ 7/15 is really * pending. Reset read register back to IRR when done. */ port = ((struct atpic *)isrc->is_pic)->at_ioaddr; mtx_lock_spin(&icu_lock); outb(port, OCW3_SEL | OCW3_RR | OCW3_RIS); isr = inb(port); outb(port, OCW3_SEL | OCW3_RR); mtx_unlock_spin(&icu_lock); if ((isr & IRQ7) == 0) return; } intr_execute_handlers(isrc, &iframe); } #ifdef DEV_ISA /* * Bus attachment for the ISA PIC. */ static struct isa_pnp_id atpic_ids[] = { { 0x0000d041 /* PNP0000 */, "AT interrupt controller" }, { 0 } }; static int atpic_probe(device_t dev) { int result; result = ISA_PNP_PROBE(device_get_parent(dev), dev, atpic_ids); if (result <= 0) device_quiet(dev); return (result); } /* * We might be granted IRQ 2, as this is typically consumed by chaining * between the two PIC components. If we're using the APIC, however, * this may not be the case, and as such we should free the resource. * (XXX untested) * * The generic ISA attachment code will handle allocating any other resources * that we don't explicitly claim here. */ static int atpic_attach(device_t dev) { struct resource *res; int rid; /* Try to allocate our IRQ and then free it. */ rid = 0; res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, 0); if (res != NULL) bus_release_resource(dev, SYS_RES_IRQ, rid, res); return (0); } static device_method_t atpic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, atpic_probe), DEVMETHOD(device_attach, atpic_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), { 0, 0 } }; static driver_t atpic_driver = { "atpic", atpic_methods, 1, /* no softc */ }; static devclass_t atpic_devclass; DRIVER_MODULE(atpic, isa, atpic_driver, atpic_devclass, 0, 0); #ifndef PC98 DRIVER_MODULE(atpic, acpi, atpic_driver, atpic_devclass, 0, 0); #endif /* * Return a bitmap of the current interrupt requests. This is 8259-specific * and is only suitable for use at probe time. */ intrmask_t isa_irq_pending(void) { u_char irr1; u_char irr2; irr1 = inb(IO_ICU1); irr2 = inb(IO_ICU2); return ((irr2 << 8) | irr1); } #endif /* DEV_ISA */