/* $NetBSD: sysasic.c,v 1.14 2008/04/28 20:23:16 martin Exp $ */ /*- * Copyright (c) 2001, 2002 The NetBSD Foundation, Inc. * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 __KERNEL_RCSID(0, "$NetBSD: sysasic.c,v 1.14 2008/04/28 20:23:16 martin Exp $"); #include #include #include #include #include #include #include #define SYSASIC_INTR_ST 0xa05f6900 #define SYSASIC_INTR_EN(level) (0xa05f6910 + ((level) << 4)) #define SYSASIC_IRQ_LEVEL_13 0 #define SYSASIC_IRQ_LEVEL_11 1 #define SYSASIC_IRQ_LEVEL_9 2 #define SYSASIC_IRQ_LEVEL_MAX 2 #define SYSASIC_IRQ_INDEX_TO_IRQ(i) (13 - 2 * (i)) /* per-irq */ struct sysasic_intrhand { /* for quick check on interrupt */ #define SYSASIC_EVENT_NMAP ((SYSASIC_EVENT_MAX + 1 + (32 - 1)) / 32) #define SYSASIC_EVENT_INTR_MAP(ev) ((ev) >> 5) #define SYSASIC_EVENT_INTR_BIT(ev) ((unsigned) 1 << ((ev) & 31)) unsigned syh_events[SYSASIC_EVENT_NMAP]; /* enabled */ unsigned syh_hndmap[SYSASIC_EVENT_NMAP]; /* handler installed */ void *syh_intc; int syh_idx; } sysasic_intrhand[SYSASIC_IRQ_LEVEL_MAX + 1]; /* per-event */ struct syh_eventhand { int (*hnd_fn)(void *); /* sub handler */ void *hnd_arg; struct sysasic_intrhand *hnd_syh; } sysasic_eventhand[SYSASIC_EVENT_MAX + 1]; int sysasic_intr(void *); const char * __pure sysasic_intr_string(int irl) { switch (irl) { default: #ifdef DEBUG panic("sysasic_intr_string: unknown IRL%d", irl); #endif case SYSASIC_IRL9: return "SH4 IRL 9"; case SYSASIC_IRL11: return "SH4 IRL 11"; case SYSASIC_IRL13: return "SH4 IRL 13"; } /* NOTREACHED */ } /* * Set up an interrupt handler to start being called. */ void * sysasic_intr_establish(int event, int ipl, int irl, int (*ih_fun)(void *), void *ih_arg) { struct sysasic_intrhand *syh; struct syh_eventhand *hnd; int idx; static const int idx2evt[3] = { SH_INTEVT_IRL13, SH_INTEVT_IRL11, SH_INTEVT_IRL9 }; #ifdef DEBUG int i; #endif KDASSERT(event >= 0 && event <= SYSASIC_EVENT_MAX); KDASSERT(ih_fun); /* * Dreamcast use SH4 INTC as IRL mode. If IRQ is specified, * its priority level is fixed. * * We use IPL to specify the IRQ for clearness, that is, we use * a splxxx() and IPL_XXX pair in a device driver. */ switch (irl) { default: #ifdef DEBUG panic("sysasic_intr_establish: unknown IRL %d", irl); #endif case SYSASIC_IRL9: idx = SYSASIC_IRQ_LEVEL_9; break; case SYSASIC_IRL11: idx = SYSASIC_IRQ_LEVEL_11; break; case SYSASIC_IRL13: idx = SYSASIC_IRQ_LEVEL_13; break; } syh = &sysasic_intrhand[idx]; if (syh->syh_intc == NULL) { syh->syh_idx = idx; syh->syh_intc = intc_intr_establish(idx2evt[idx], IST_LEVEL, irl, sysasic_intr, syh); } #ifdef DEBUG /* check if the event handler is already installed */ for (i = 0; i <= SYSASIC_IRQ_LEVEL_MAX; i++) if ((sysasic_intrhand[i].syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] & SYSASIC_EVENT_INTR_BIT(event)) != 0) panic("sysasic_intr_establish: event %d already installed irq %d", event, SYSASIC_IRQ_INDEX_TO_IRQ(i)); #endif /* mark this event is established */ syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] |= SYSASIC_EVENT_INTR_BIT(event); hnd = &sysasic_eventhand[event]; hnd->hnd_fn = ih_fun; hnd->hnd_arg = ih_arg; hnd->hnd_syh = syh; sysasic_intr_enable(hnd, 1); return (void *)hnd; } /* * Deregister an interrupt handler. */ void sysasic_intr_disestablish(void *arg) { struct syh_eventhand *hnd = arg; struct sysasic_intrhand *syh; int event; int i; event = hnd - sysasic_eventhand; KDASSERT(event >= 0 && event <= SYSASIC_EVENT_MAX); syh = hnd->hnd_syh; #ifdef DIAGNOSTIC if ((syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] & SYSASIC_EVENT_INTR_BIT(event)) == 0) panic("sysasic_intr_disestablish: event %d not installed for irq %d", event, SYSASIC_IRQ_INDEX_TO_IRQ(syh->syh_idx)); #endif sysasic_intr_enable(hnd, 0); hnd->hnd_fn = 0; hnd->hnd_arg = 0; hnd->hnd_syh = 0; syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] &= ~SYSASIC_EVENT_INTR_BIT(event); /* deinstall intrc if no event exists */ for (i = 0; i < SYSASIC_EVENT_NMAP; i++) if (syh->syh_hndmap[i]) return; intc_intr_disestablish(syh->syh_intc); syh->syh_intc = 0; } void sysasic_intr_enable(void *arg, int on) { struct syh_eventhand *hnd = arg; struct sysasic_intrhand *syh; int event; volatile uint32_t *masks, *stats; int evmap; unsigned evbit; event = hnd - sysasic_eventhand; KDASSERT(event >= 0 && event <= SYSASIC_EVENT_MAX); syh = hnd->hnd_syh; #ifdef DIAGNOSTIC if ((syh->syh_hndmap[SYSASIC_EVENT_INTR_MAP(event)] & SYSASIC_EVENT_INTR_BIT(event)) == 0) panic("sysasic_intr_enable: event %d not installed for irq %d", event, SYSASIC_IRQ_INDEX_TO_IRQ(syh->syh_idx)); #endif masks = (volatile uint32_t *) SYSASIC_INTR_EN(syh->syh_idx); stats = (volatile uint32_t *) SYSASIC_INTR_ST; evmap = SYSASIC_EVENT_INTR_MAP(event); evbit = SYSASIC_EVENT_INTR_BIT(event); if (on) { /* clear pending event if any */ stats[evmap] = evbit; /* set event map */ syh->syh_events[evmap] |= evbit; /* enable interrupt */ masks[evmap] = syh->syh_events[evmap]; } else { /* disable interrupt */ masks[evmap] = syh->syh_events[evmap] & ~evbit; /* clear pending event if any */ stats[evmap] = evbit; /* clear event map */ syh->syh_events[evmap] &= ~evbit; } } int sysasic_intr(void *arg) { struct sysasic_intrhand *syh = arg; unsigned ev; int n, pos; uint32_t *evwatched; volatile uint32_t *evmap; struct syh_eventhand *evh; #ifdef DEBUG int handled = 0; static int reportcnt = 10; #endif /* bitmap of watched events */ evwatched = syh->syh_events; /* status / clear */ evmap = (volatile uint32_t *) SYSASIC_INTR_ST; for (n = 0; n < (SYSASIC_EVENT_NMAP << 5); n |= 31, n++, evmap++) { if ((ev = *evwatched++) && (ev &= *evmap)) { /* clear interrupts */ *evmap = ev; n--; /* to point at current bit after n += pos */ /* call handlers */ do { pos = ffs(ev); n += pos; #ifdef __OPTIMIZE__ /* optimized, assuming 1 <= pos <= 32 */ __asm("shld %2,%0" : "=r" (ev) : "0" (ev), "r" (-pos)); #else /* ``shift count >= bit width'' is undefined */ if (pos >= 32) ev = 0; else ev >>= pos; #endif evh = &sysasic_eventhand[n]; #ifdef DEBUG KDASSERT(evh->hnd_fn); if ((*evh->hnd_fn)(evh->hnd_arg)) handled = 1; #else (*evh->hnd_fn)(evh->hnd_arg); #endif } while (ev); } } #ifdef DEBUG if (!handled && reportcnt > 0) { reportcnt--; log(LOG_ERR, "sysasic_intr: stray irq%d interrupt%s\n", SYSASIC_IRQ_INDEX_TO_IRQ(syh->syh_idx), reportcnt == 0 ? "; stopped logging" : ""); } #endif return 0; }